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

add baseline for linked editing #54315

Merged
merged 4 commits into from May 26, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
66 changes: 66 additions & 0 deletions src/harness/fourslashImpl.ts
Expand Up @@ -3576,6 +3576,72 @@ export class TestState {
}
}

public baselineLinkedEditing(): void {
const baselineFile = this.getBaselineFileNameForContainingTestFile(".linkedEditing.txt");
const files = this.testData.files;

let baselineContent = "";
let offset = 0;
files.forEach(f => {
const result = getLinkedEditingBaselineWorker(f, offset, this.languageService);
baselineContent += result.baselineContent + `\n\n\n`;
offset = result.offset;
});
iisaduan marked this conversation as resolved.
Show resolved Hide resolved

Harness.Baseline.runBaseline(baselineFile, baselineContent);

function getLinkedEditingBaselineWorker(activeFile: FourSlashFile, offset: number, languageService: ts.LanguageService) {
const fileName = activeFile.fileName;
let baselineContent = `=== ${fileName} ===\n`;

// get linkedEdit at every position in the file, then group positions by their linkedEdit
const linkedEditsInFile = new Map<string, number[]>();
for(let pos = 0; pos < activeFile.content.length; pos++) {
iisaduan marked this conversation as resolved.
Show resolved Hide resolved
const linkedEditAtPosition = languageService.getLinkedEditingRangeAtPosition(fileName, pos);
if (!linkedEditAtPosition) continue;

const linkedEditString = JSON.stringify(linkedEditAtPosition);
const existingPositions = linkedEditsInFile.get(linkedEditString) ?? [];
linkedEditsInFile.set(linkedEditString, [...existingPositions, pos]);
}

const linkedEditsByRange = [...linkedEditsInFile.entries()].sort((a, b) => a[1][0] - b[1][0]);
if (linkedEditsByRange.length === 0) {
return { baselineContent: baselineContent + activeFile.content + `\n\n--No linked edits found--`, offset };
}

let inlineLinkedEditBaseline: { start: number, end: number, index: number }[] = [];
Copy link
Member

Choose a reason for hiding this comment

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

Is there any reason that this is named index if it's always initialized with the current offset? Maybe offsetForBaseline or something?

Copy link
Member

Choose a reason for hiding this comment

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

Also, since it's an array

Suggested change
let inlineLinkedEditBaseline: { start: number, end: number, index: number }[] = [];
let inlineLinkedEditBaselines: { start: number, end: number, index: number }[] = [];

Copy link
Member Author

Choose a reason for hiding this comment

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

index is the number it will be labeled with in the baseline output, and it's initalized to offset since it made sense to me to just use the variable that's passed into the function. offset is incremented per list entry in linkedEditsByRange like an index.

let linkedEditInfoBaseline = "";
linkedEditsByRange.forEach(([linkedEdit, positions]) => {
iisaduan marked this conversation as resolved.
Show resolved Hide resolved
let rangeStart = 0;
for (let j = 0; j < positions.length - 1; j++) {
// for each distinct range in the list of positions, add an entry to the list of places that need to be annotated in the baseline
if (positions[j] + 1 !== positions[j + 1]) {
inlineLinkedEditBaseline.push({ start: positions[rangeStart], end: positions[j], index: offset });
rangeStart = j + 1;
}
}
inlineLinkedEditBaseline.push({ start: positions[rangeStart], end: positions[positions.length - 1], index: offset });

// add the LinkedEditInfo with its index to the baseline
linkedEditInfoBaseline += `\n\n=== ${offset} ===\n` + linkedEdit;
offset++;
});

inlineLinkedEditBaseline = inlineLinkedEditBaseline.sort((a, b) => a.start - b.start);
const fileText = activeFile.content;
baselineContent += fileText.slice(0, inlineLinkedEditBaseline[0].start);
for (let i = 0 ; i < inlineLinkedEditBaseline.length - 1; i++) {
iisaduan marked this conversation as resolved.
Show resolved Hide resolved
const e = inlineLinkedEditBaseline[i];
baselineContent += `[|/*${e.index}*/` + fileText.slice(e.start, e.end) + `|]` + fileText.slice(e.end, inlineLinkedEditBaseline[i + 1].start);
}
const n = inlineLinkedEditBaseline[inlineLinkedEditBaseline.length - 1];
baselineContent += `[|/*${n.index}*/` + fileText.slice(n.start, n.end) + `|]` + fileText.slice(inlineLinkedEditBaseline[inlineLinkedEditBaseline.length - 1].end) + linkedEditInfoBaseline;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
const n = inlineLinkedEditBaseline[inlineLinkedEditBaseline.length - 1];
baselineContent += `[|/*${n.index}*/` + fileText.slice(n.start, n.end) + `|]` + fileText.slice(inlineLinkedEditBaseline[inlineLinkedEditBaseline.length - 1].end) + linkedEditInfoBaseline;
const lastRange = inlineLinkedEditBaseline[inlineLinkedEditBaseline.length - 1];
baselineContent += `[|/*${lastRange.index}*/` + fileText.slice(lastRange.start, lastRange.end) + `|]` + fileText.slice(lastRange.end) + linkedEditInfoBaseline;

Copy link
Member

Choose a reason for hiding this comment

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

Actually, maybe instead of copying the same contents within the loop, you just loop on every element and then write

const sliceEnd = i + 1 < inlineLinkedEditBaseline.length ?
    inlineLinkedEditBaseline[i + 1].start :
    undefined;
const trailingText = fileText.slice(e.end, sliceEnd);

baselineContent += `[|/*${e.index}*/` + fileText.slice(e.start, e.end) + `|]` + sliceEnd;


return { baselineContent, offset };
}
}

public verifyMatchingBracePosition(bracePosition: number, expectedMatchPosition: number) {
const actual = this.languageService.getBraceMatchingAtPosition(this.activeFile.fileName, bracePosition);

Expand Down
4 changes: 4 additions & 0 deletions src/harness/fourslashInterfaceImpl.ts
Expand Up @@ -181,6 +181,10 @@ export class VerifyNegatable {
this.state.verifyLinkedEditingRange(map);
}

public baselineLinkedEditing(): void {
this.state.baselineLinkedEditing();
}

public isInCommentAtPosition(onlyMultiLineDiverges?: boolean) {
this.state.verifySpanOfEnclosingComment(this.negative, onlyMultiLineDiverges);
}
Expand Down
97 changes: 97 additions & 0 deletions tests/baselines/reference/linkedEditingJsxTag10.linkedEditing.txt
@@ -0,0 +1,97 @@
=== /jsx0.tsx ===
const jsx = <>

--No linked edits found--


=== /jsx1.tsx ===
const jsx = </>

--No linked edits found--


=== /jsx2.tsx ===
const jsx = <div>

--No linked edits found--


=== /jsx3.tsx ===
const jsx = </div>

--No linked edits found--


=== /jsx4.tsx ===
const jsx = <div> </>;

--No linked edits found--


=== /jsx5.tsx ===
const jsx = <> </div>;

--No linked edits found--


=== /jsx6.tsx ===
const jsx = div> </div>;

--No linked edits found--


=== /jsx7.tsx ===
const jsx = <div> /div>;

--No linked edits found--


=== /jsx8.tsx ===
const jsx = <div </div>;

--No linked edits found--


=== /jsx9.tsx ===
const jsx = <[|/*0*/div|]> </[|/*0*/div|];

=== 0 ===
{"ranges":[{"start":13,"length":3},{"start":20,"length":3}],"wordPattern":"[a-zA-Z0-9:\\-\\._$]*"}


=== /jsx10.tsx ===
const jsx = <> </;

--No linked edits found--


=== /jsx11.tsx ===
const jsx = < </>;

--No linked edits found--


=== /jsx12.tsx ===
const jsx = > </>;

--No linked edits found--


=== /jsx13.tsx ===
const jsx = <> />;

--No linked edits found--


=== /jsx14.tsx ===
const jsx = <> <div> </> </div>;

--No linked edits found--


=== /jsx15.tsx ===
const jsx = <div> <> </div> </>;

--No linked edits found--


27 changes: 27 additions & 0 deletions tests/baselines/reference/linkedEditingJsxTag11.linkedEditing.txt
@@ -0,0 +1,27 @@
=== /customElements.tsx ===
const jsx = <[|/*0*/fbt:enum|] knownProp="accepted"
unknownProp="rejected">
</[|/*0*/fbt:enum|]>;

const customElement = <[|/*1*/custom-element|]></[|/*1*/custom-element|]>;

const standardElement =
<[|/*2*/Link|] href="/hello" passHref>
<[|/*3*/Button|] component="a">
Next
</[|/*3*/Button|]>
</[|/*2*/Link|]>;

=== 0 ===
{"ranges":[{"start":13,"length":8},{"start":73,"length":8}],"wordPattern":"[a-zA-Z0-9:\\-\\._$]*"}
Copy link
Member

Choose a reason for hiding this comment

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

I think you can skip mentioning ranges here since they are already marked in the text above.


=== 1 ===
{"ranges":[{"start":108,"length":14},{"start":125,"length":14}],"wordPattern":"[a-zA-Z0-9:\\-\\._$]*"}

=== 2 ===
{"ranges":[{"start":172,"length":4},{"start":269,"length":4}],"wordPattern":"[a-zA-Z0-9:\\-\\._$]*"}

=== 3 ===
{"ranges":[{"start":209,"length":6},{"start":256,"length":6}],"wordPattern":"[a-zA-Z0-9:\\-\\._$]*"}


1 change: 1 addition & 0 deletions tests/cases/fourslash/fourslash.ts
Expand Up @@ -263,6 +263,7 @@ declare namespace FourSlashInterface {
isValidBraceCompletionAtPosition(openingBrace?: string): void;
jsxClosingTag(map: { [markerName: string]: { readonly newText: string } | undefined }): void;
linkedEditing(map: { [markerName: string]: LinkedEditingInfo | undefined }): void;
baselineLinkedEditing(): void;
isInCommentAtPosition(onlyMultiLineDiverges?: boolean): void;
codeFix(options: {
description: string | [string, ...(string | number)[]] | DiagnosticIgnoredInterpolations,
Expand Down
83 changes: 43 additions & 40 deletions tests/cases/fourslash/linkedEditingJsxTag10.ts
Expand Up @@ -48,43 +48,46 @@
// @Filename: /jsx15.tsx
////const jsx = </*15*/div> </*15a*/> <//*15b*/div> <//*15c*/>;

const wordPattern = "[a-zA-Z0-9:\\-\\._$]*";
const linkedCursors9 = {
ranges: [{ start: test.markerByName("9").position, length: 3 }, { start: test.markerByName("9a").position, length: 3 }],
wordPattern,
};

verify.linkedEditing( {
"0": undefined,
"1": undefined,
"2": undefined,
"3": undefined,
"4": undefined,
"4a": undefined,
"5": undefined,
"5a": undefined,
"6": undefined,
"6a": undefined,
"7": undefined,
"7a": undefined,
"8": undefined,
"8a": undefined,
"9": linkedCursors9,
"9a": linkedCursors9,
"10": undefined,
"10a": undefined,
"11": undefined,
"11a": undefined,
"12": undefined,
"12a": undefined,
"13": undefined,
"13a": undefined,
"14": undefined,
"14a": undefined,
"14b": undefined,
"14c": undefined,
"15": undefined,
"15a": undefined,
"15b": undefined,
"15c": undefined,
});
verify.baselineLinkedEditing();

// below is the expected result

// const wordPattern = "[a-zA-Z0-9:\\-\\._$]*";
// const linkedCursors9 = {
// ranges: [{ start: test.markerByName("9").position, length: 3 }, { start: test.markerByName("9a").position, length: 3 }],
// wordPattern,
// };
// verify.linkedEditing( {
// "0": undefined,
// "1": undefined,
// "2": undefined,
// "3": undefined,
// "4": undefined,
// "4a": undefined,
// "5": undefined,
// "5a": undefined,
// "6": undefined,
// "6a": undefined,
// "7": undefined,
// "7a": undefined,
// "8": undefined,
// "8a": undefined,
// "9": linkedCursors9,
// "9a": linkedCursors9,
// "10": undefined,
// "10a": undefined,
// "11": undefined,
// "11a": undefined,
// "12": undefined,
// "12a": undefined,
// "13": undefined,
// "13a": undefined,
// "14": undefined,
// "14a": undefined,
// "14b": undefined,
// "14c": undefined,
// "15": undefined,
// "15a": undefined,
// "15b": undefined,
// "15c": undefined,
// });
58 changes: 8 additions & 50 deletions tests/cases/fourslash/linkedEditingJsxTag11.ts
@@ -1,61 +1,19 @@
/// <reference path='fourslash.ts' />

// for readability
////const jsx = (
//// <div style={{ color: 'red' }}>
//// <p>
//// <img />
//// </p>
//// </div>
////);

// @Filename: /customElements.tsx
//// const jsx = </*1*/fbt/*2*/:en/*3*/um knownProp="accepted"
//// const jsx = <fbt:enum knownProp="accepted"
//// unknownProp="rejected">
//// </f/*4*/bt:e/*5*/num>;
//// </fbt:enum>;
////
//// const customElement = </*6*/custom/*7*/-element/*8*/></custom/*9*/-element>;
//// const customElement = <custom-element></custom-element>;
////
//// const standardElement =
//// </*10*/Link/*11*/ href="/hello" passHref>
//// </*12*/But/*13*/ton component="a">
//// <Link href="/hello" passHref>
//// <Button component="a">
//// Next
//// </But/*14*/ton>
//// </Li/*15*/nk>;
//// </Button>
//// </Link>;

const wordPattern = "[a-zA-Z0-9:\\-\\._$]*";

const linkedCursors1 = {
ranges: [{ start: test.markerByName("1").position, length: 8 }, { start: test.markerByName("4").position - 1, length: 8 }],
wordPattern,
};
const linkedCursors2 = {
ranges: [{ start: test.markerByName("6").position, length: 14 }, { start: test.markerByName("9").position - 6, length: 14 }],
wordPattern,
};
const linkedCursors3 = {
ranges: [{ start: test.markerByName("10").position, length: 4 }, { start: test.markerByName("15").position - 2, length: 4 }],
wordPattern,
};
const linkedCursors4 = {
ranges: [{ start: test.markerByName("12").position, length: 6 }, { start: test.markerByName("14").position - 3, length: 6 }],
wordPattern,
};

verify.linkedEditing( {
"1": linkedCursors1,
"2": linkedCursors1,
"3": linkedCursors1,
"4": linkedCursors1,
"5": linkedCursors1,
"6": linkedCursors2,
"7": linkedCursors2,
"8": linkedCursors2,
"9": linkedCursors2,
"10": linkedCursors3,
"11": linkedCursors3,
"12": linkedCursors4,
"13": linkedCursors4,
"14": linkedCursors4,
"15": linkedCursors3,
});
verify.baselineLinkedEditing();