mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-22 21:41:37 +00:00
Enhance WhitespaceUtil with LCS-based diff algorithm for improved change detection
- Implemented a Longest Common Subsequence (LCS) algorithm to accurately compute line differences, enhancing the detection of whitespace-only changes. - Refactored the CountLineDifferences function to utilize LCS for both exact and normalized comparisons, improving performance and clarity. - Added normalization option to handle whitespace discrepancies effectively, ensuring more precise diff results.
This commit is contained in:
parent
1af3a18bcd
commit
c64aec6132
1 changed files with 76 additions and 21 deletions
|
|
@ -1,5 +1,7 @@
|
|||
--[[
|
||||
Utility for detecting whitespace-only changes in source code.
|
||||
Uses LCS (Longest Common Subsequence) based diff algorithm for accurate
|
||||
change detection that properly handles insertions and deletions.
|
||||
]]
|
||||
|
||||
local WhitespaceUtil = {}
|
||||
|
|
@ -16,7 +18,52 @@ local function NormalizeLine(line: string): string
|
|||
return string.gsub(string.gsub(line, "\r+$", ""), "[ \t]+$", "")
|
||||
end
|
||||
|
||||
-- Counts lines that differ between two strings (capped at 100)
|
||||
-- Computes the length of the Longest Common Subsequence between two line arrays
|
||||
-- If normalize is true, lines are compared after whitespace normalization
|
||||
local function ComputeLcsLength(linesA: { string }, linesB: { string }, normalize: boolean): number
|
||||
local m, n = #linesA, #linesB
|
||||
if m == 0 or n == 0 then
|
||||
return 0
|
||||
end
|
||||
|
||||
-- Precompute comparison lines (normalized if requested)
|
||||
local compareA, compareB = linesA, linesB
|
||||
if normalize then
|
||||
compareA = table.create(m)
|
||||
compareB = table.create(n)
|
||||
for i = 1, m do
|
||||
compareA[i] = NormalizeLine(linesA[i])
|
||||
end
|
||||
for j = 1, n do
|
||||
compareB[j] = NormalizeLine(linesB[j])
|
||||
end
|
||||
end
|
||||
|
||||
-- Space-optimized DP for LCS
|
||||
-- prev[j+1] = LCS length for linesA[1..i-1] and linesB[1..j]
|
||||
-- curr[j+1] = LCS length for linesA[1..i] and linesB[1..j]
|
||||
local prev = table.create(n + 1, 0)
|
||||
local curr = table.create(n + 1, 0)
|
||||
|
||||
for i = 1, m do
|
||||
for j = 1, n do
|
||||
if compareA[i] == compareB[j] then
|
||||
curr[j + 1] = prev[j] + 1
|
||||
else
|
||||
curr[j + 1] = math.max(prev[j + 1], curr[j])
|
||||
end
|
||||
end
|
||||
-- Swap rows and reset curr
|
||||
prev, curr = curr, prev
|
||||
for j = 1, n + 1 do
|
||||
curr[j] = 0
|
||||
end
|
||||
end
|
||||
|
||||
return prev[n + 1]
|
||||
end
|
||||
|
||||
-- Counts lines that differ between two strings using LCS-based diff
|
||||
-- Returns: totalDiff, whitespaceDiff (lines that only differ in whitespace)
|
||||
-- Returns 10000 for totalDiff if exceeds 100 (frontend displays as "100+")
|
||||
-- whitespaceDiff is 10000 only if ALL checked differences were whitespace-only
|
||||
|
|
@ -27,29 +74,37 @@ function WhitespaceUtil.CountLineDifferences(current: string, incoming: string):
|
|||
|
||||
local currentLines = SplitLines(current)
|
||||
local incomingLines = SplitLines(incoming)
|
||||
local maxLines = math.max(#currentLines, #incomingLines)
|
||||
local m, n = #currentLines, #incomingLines
|
||||
|
||||
local totalDiff, whitespaceDiff = 0, 0
|
||||
local hasContentChange = false
|
||||
for i = 1, maxLines do
|
||||
local currentLine = currentLines[i] or ""
|
||||
local incomingLine = incomingLines[i] or ""
|
||||
|
||||
if currentLine ~= incomingLine then
|
||||
totalDiff += 1
|
||||
if NormalizeLine(currentLine) == NormalizeLine(incomingLine) then
|
||||
whitespaceDiff += 1
|
||||
else
|
||||
hasContentChange = true
|
||||
end
|
||||
if totalDiff > 100 then
|
||||
-- Return 10000 for both only if ALL differences were whitespace-only
|
||||
-- Otherwise return different values so isWhitespaceOnly is false
|
||||
return 10000, if hasContentChange then 0 else 10000
|
||||
end
|
||||
end
|
||||
-- Quick check: if size difference alone exceeds limit
|
||||
local minPossibleDiff = math.abs(m - n)
|
||||
if minPossibleDiff > 100 then
|
||||
return 10000, 0
|
||||
end
|
||||
|
||||
-- Compute LCS with exact matching
|
||||
-- totalDiff = deletions + insertions = (m - lcs) + (n - lcs)
|
||||
local exactLcs = ComputeLcsLength(currentLines, incomingLines, false)
|
||||
local totalDiff = (m - exactLcs) + (n - exactLcs)
|
||||
|
||||
if totalDiff > 100 then
|
||||
-- Compute normalized LCS to determine if changes are whitespace-only
|
||||
local normalizedLcs = ComputeLcsLength(currentLines, incomingLines, true)
|
||||
local normalizedDiff = (m - normalizedLcs) + (n - normalizedLcs)
|
||||
return 10000, if normalizedDiff == 0 then 10000 else 0
|
||||
end
|
||||
|
||||
if totalDiff == 0 then
|
||||
return 0, 0
|
||||
end
|
||||
|
||||
-- Compute LCS with normalized matching to find whitespace-only changes
|
||||
local normalizedLcs = ComputeLcsLength(currentLines, incomingLines, true)
|
||||
local normalizedDiff = (m - normalizedLcs) + (n - normalizedLcs)
|
||||
|
||||
-- whitespaceDiff = changes that disappear after normalization
|
||||
local whitespaceDiff = totalDiff - normalizedDiff
|
||||
|
||||
return totalDiff, whitespaceDiff
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue