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

(fix) deal with shorthands during rename #1876

Merged
merged 1 commit into from
Feb 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ export class RenameProviderImpl implements RenameProvider {
position,
convertedRenameLocations,
docs,
lang
lang,
newName
);
const additionalRenamesForPropRenameOutsideComponentWithProp =
// This is an either-or-situation, don't do both
Expand Down Expand Up @@ -262,7 +263,8 @@ export class RenameProviderImpl implements RenameProvider {
position: Position,
convertedRenameLocations: TsRenameLocation[],
snapshots: SnapshotMap,
lang: ts.LanguageService
lang: ts.LanguageService,
newName: string
) {
// First find out if it's really the "rename prop inside component with that prop" case
// Use original document for that because only there the `export` is present.
Expand Down Expand Up @@ -292,16 +294,94 @@ export class RenameProviderImpl implements RenameProvider {
// It would not work for `return props: {bla}` because then typescript would do a rename of `{bla: renamed}`,
// so other locations would not be affected.
const replacementsForProp = (
lang.findRenameLocations(updatePropLocation.fileName, idxOfOldPropName, false, false) ||
[]
lang.findRenameLocations(
updatePropLocation.fileName,
idxOfOldPropName,
false,
false,
true
) || []
).filter(
(rename) =>
// filter out all renames inside the component except the prop rename,
// because the others were done before and then would show up twice, making a wrong rename.
rename.fileName !== updatePropLocation.fileName ||
this.isInSvelte2TsxPropLine(tsDoc, rename)
);
return await this.mapAndFilterRenameLocations(replacementsForProp, snapshots);

const renameLocations = await this.mapAndFilterRenameLocations(
replacementsForProp,
snapshots
);
const bind = 'bind:';

// Adjust shorthands
return renameLocations.map((location) => {
if (updatePropLocation.fileName === location.fileName) {
return location;
}

const sourceFile = lang.getProgram()?.getSourceFile(location.fileName);

if (
!sourceFile ||
location.fileName !== sourceFile.fileName ||
location.range.start.line < 0 ||
location.range.end.line < 0
) {
return location;
}

const snapshot = snapshots.get(location.fileName);
if (!(snapshot instanceof SvelteDocumentSnapshot)) {
return location;
}

const { parent } = snapshot;

let rangeStart = parent.offsetAt(location.range.start);
let suffixText = location.suffixText?.trimStart();

// suffix is of the form `: oldVarName` -> hints at a shorthand
if (!suffixText?.startsWith(':') || !getNodeIfIsInStartTag(parent.html, rangeStart)) {
return location;
}

const original = parent.getText({
start: Position.create(
location.range.start.line,
location.range.start.character - bind.length
),
end: location.range.end
});

if (original.startsWith(bind)) {
// bind:|foo| -> bind:|newName|={foo}
return {
...location,
prefixText: '',
suffixText: `={${original.slice(bind.length)}}`
};
}

if (snapshot.getOriginalText().charAt(rangeStart - 1) === '{') {
// {|foo|} -> |{foo|}
rangeStart--;
return {
...location,
range: {
start: parent.positionAt(rangeStart),
end: location.range.end
},
// |{foo|} -> newName=|{foo|}
newName: parent.getText(location.range),
prefixText: `${newName}={`,
suffixText: ''
};
}

return location;
});
}

/**
Expand Down
1 change: 1 addition & 0 deletions packages/language-server/src/plugins/typescript/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const pendingReloads = new FileSet();
export function __resetCache() {
services.clear();
serviceSizeMap.clear();
configFileForOpenFiles.clear();
}

export interface LanguageServiceDocumentContext {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,87 @@ describe('RenameProvider', () => {
});
});

it('should do rename of prop without type of component A in component A that is used with shorthands in component B', async () => {
const { provider, renameDoc3 } = await setup();
const result = await provider.rename(renameDoc3, Position.create(2, 20), 'newName');

console.log(JSON.stringify(result, null, 3));

assert.deepStrictEqual(result, {
changes: {
[getUri('rename3.svelte')]: [
{
newText: 'newName',
range: {
start: {
line: 2,
character: 15
},
end: {
line: 2,
character: 21
}
}
}
],
[getUri('rename-shorthand.svelte')]: [
{
newText: 'newName={props2}',
range: {
start: {
line: 6,
character: 12
},
end: {
line: 6,
character: 18
}
}
},
{
newText: 'newName={props2',
range: {
start: {
line: 7,
character: 7
},
end: {
line: 7,
character: 14
}
}
},
{
newText: 'newName',
range: {
start: {
line: 8,
character: 7
},
end: {
line: 8,
character: 13
}
}
},
{
newText: 'newName',
range: {
start: {
line: 9,
character: 7
},
end: {
line: 9,
character: 13
}
}
}
]
}
});
});

it('should do rename of svelte component', async () => {
const { provider, renameDoc4 } = await setup();
const result = await provider.rename(renameDoc4, Position.create(1, 12), 'ChildNew');
Expand Down