Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rejected edits trailing whitespaces #4687

Conversation

panyamkeerthana
Copy link
Contributor

Description

Earlier trailing whitespace was stripped in streamDiffLines.ts before generating diffs, causing mismatches when rejecting a diff in the editor.
This fix updates linesMatchPerfectly to compare lines using trimEnd(), ensuring that trailing whitespace differences do not interfere with accurate diff rejection.

Screenshots

Screenshot 2025-03-14 at 3 14 53 PM Screenshot 2025-03-14 at 3 14 28 PM

Granite-Code/granite-code#50

@panyamkeerthana panyamkeerthana requested a review from a team as a code owner March 17, 2025 02:16
@panyamkeerthana panyamkeerthana requested review from tomasz-stefaniak and removed request for a team March 17, 2025 02:16
Copy link

netlify bot commented Mar 17, 2025

Deploy Preview for continuedev canceled.

Name Link
🔨 Latest commit 3ba5452
🔍 Latest deploy log https://app.netlify.com/sites/continuedev/deploys/67dc74500b5731000700b87c

@panyamkeerthana panyamkeerthana changed the title Panyamkeerthana/rejected trailing whitespaces rejected edits trailing whitespaces Mar 17, 2025
//checks if two lines match exactly ignoring trailing whitespace differences
//this prevents false mismatches in diff handling when trailing spaces are stripped

return lineA.trimEnd() === lineB.trimEnd() && lineA !== "";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes up one case where we are comparing oldLines to newLines, but checking over the code, this isn't the only place where we might have issues:

  • in matchLine(), after calling linesMatchPerfectly(), we make more references to oldLines[i] that potentially might need changes.
  • In streamDiff(), we have:
         if (oldLinesCopy[0] !== newLine) {
          yield { type: "new", line: newLine };
        } else {
          isLineRemoval = true;
        }

which looks suspicious, but I'm pretty sure can never actually be hit: if oldLinesCopy[0] === newLine then isPerfectMatch should have been true and the case will be "same" not "old". Needs double checking.

But the takeaway for me here is that the test cases need updating to test different cases where lines differ only in trailing whitespace. From the continue/core directory, you can run:

$ npm test  diff/streamDiff.test.ts

to run streamDiff test cases. Add a test case to check the linesMatchPerfectly case, add a test case case that exercises: // This is a way to fix indentation, but only for sufficiently long lines to avoid matching whitespace or short lines when the indentation changes and there is a change in whitespace.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review, I have made the following changes:

-Added a test case for oldLinesCopy[0] === newLine that confirmed it’s already handled as "same" or "old", so the else statement was unnecessary and removed.
-Trimmed oldLines throughout and added a test to ensure trailing whitespaces are correctly treated as "same", preventing incorrect diffs.
-Added a test to verify that indentation changes in long lines don’t cause unnecessary diffs.

@panyamkeerthana panyamkeerthana force-pushed the panyamkeerthana/rejected-trailing-whitespaces branch 3 times, most recently from f1a5f04 to 58d8f7c Compare March 18, 2025 19:42
});

test("ignores indentation changes for sufficiently long lines", async () => {
const oldLines = [" first item", " second arg", "third param", " fourth val "];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would try to test this more comprehensively - to test that indentation changes on short lines are considered changes, until one line with length > 8 is encountered, then they are ignored, even for short lines.

Importantly, I'd also check here that the same behavior is seen when the old and new lines differ in trailing whitespace - that's the point of adding the check as part of this patch, after all.

Something like:

 test.each([false, true])(
   `ignores identation changes for sufficiently long lines",
   async (trailingWhitespace) => {
      [...]
      if (trailingWhitespace)
         oldLines = oldLines.map(line => line + " ");
      [...]
   });

I'm sure there's a more stylish way to do that.

// Don't match end bracket lines if too far away
if (i > 4 && isEndBracket) {
return { matchIndex: -1, isPerfectMatch: false, newLine };
}

if (linesMatchPerfectly(newLine, oldLines[i])) {
if (linesMatchPerfectly(newLine, oldLineTrimmed)) {
return { matchIndex: i, isPerfectMatch: true, newLine };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this probably should be oldLines[i] not newLine? The goal here should be that when we return same - we actually have the old line, not a version that differs in trailing whitespace. First try to make a test case that fails, then change this.

Copy link
Contributor Author

@panyamkeerthana panyamkeerthana Mar 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test cases I ran for this scenario passed. In streamDiff():

case "same":
    yield { type, line: oldLinesCopy.shift()! };
    break;

So, regardless of what matchLine() returns for newLine, the line that is actually output is the one removed from oldLinesCopy (which is the original old line with the trailing whitespaces). But I can change it to oldLine[i] to maintain logic and uniformity

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turned out that this very much needed to be newLine - the calling code relies on this only being different from the passed in newline when the leading indentation changed. Sorry for the confusion!

Copy link
Contributor Author

@panyamkeerthana panyamkeerthana Mar 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this testing has only made the whitespace and indentation handling more robust!

(permissiveAboutIndentation || newLine.trim().length > 8)
) {
return {
matchIndex: i,
isPerfectMatch: true,
newLine: oldLines[i],
newLine: oldLineTrimmed,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, I think this should be oldLines[i]?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll change that to oldLine[i] here as well

@@ -63,8 +63,6 @@ export async function* streamDiff(

if (oldLinesCopy[0] !== newLine) {
yield { type: "new", line: newLine };
} else {
isLineRemoval = true;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should remove the if/else entirely and just leave the yield, then since isLineRemoval can never be set, it needs to be removed as well.

@@ -223,6 +223,51 @@ describe("streamDiff(", () => {
await expectDiff("fastapi-tabs-vs-spaces.py");
});


test(" condition where oldLinesCopy[0]=== newLine", async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this was useful during development of your patch, but since this code ends up removed in your patches, I'd suggest removing the test case as well.

@panyamkeerthana panyamkeerthana force-pushed the panyamkeerthana/rejected-trailing-whitespaces branch 2 times, most recently from fbdda80 to a8953c9 Compare March 19, 2025 15:55
Copy link
Contributor

@owtaylor owtaylor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few more comments

@@ -79,7 +79,7 @@ export async function* streamDiffLines(
}

// Trim end of oldLines, otherwise we have trailing \r on every line for CRLF files
oldLines = oldLines.map((line) => line.trimEnd());
oldLines = oldLines.map((line) => line);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can just go away, along with the comment

"short",
"middle",
"a long enough line",
"indented short",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"indented short".length is going to be > 8 - this needs to be "short2" or something to actually test that handlng.

// This is a way to fix indentation, but only for sufficiently long lines to avoid matching whitespace or short lines
if (
newLine.trimStart() === oldLines[i].trimStart() &&
newLine.trimStart() === oldLineTrimmed.trimStart() &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can assume that newLine is trimmed at the end, so this should be newLine.trim()? [make sure the test case tests this - one line that is supposed to not reindented should have a trailing space on the new]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead, as discussed I created newLineTrimmed.

newLineTrimmed.trimStart() === oldLineTrimmed.trimStart()

@panyamkeerthana panyamkeerthana force-pushed the panyamkeerthana/rejected-trailing-whitespaces branch 5 times, most recently from f0be628 to 8ebcc0c Compare March 20, 2025 19:51
trailing whitespace was stripped before generating diffs, causing
mismatches when rejecting a diff in the editor.
this fix ensures that trailing whitespace differences do not
interfere with accurate diff rejection.
@panyamkeerthana panyamkeerthana force-pushed the panyamkeerthana/rejected-trailing-whitespaces branch from 8ebcc0c to 4822926 Compare March 20, 2025 19:58
since `oldLinesCopy[0] === newLine` should always be classified as
`"same"` or `"old"` in the earlier block, the `else` statement (which
sets `isLineRemoval = true`)feels redundant and never actually executed.
@panyamkeerthana panyamkeerthana force-pushed the panyamkeerthana/rejected-trailing-whitespaces branch from 4822926 to 3ba5452 Compare March 20, 2025 20:02
@owtaylor
Copy link
Contributor

Looks good to me now!

Copy link
Collaborator

@tomasz-stefaniak tomasz-stefaniak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for contributing @panyamkeerthana, and @owtaylor for the thorough review!

@tomasz-stefaniak tomasz-stefaniak merged commit 7d95521 into continuedev:main Mar 21, 2025
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants