Skip to content
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
@@ -1,5 +1,5 @@
import { Position, WorkspaceEdit, Range } from 'vscode-languageserver';
import { Document, mapRangeToOriginal, getLineAtPosition } from '../../../lib/documents';
import { Document, mapRangeToOriginal, getLineAtPosition, offsetAt } from '../../../lib/documents';
import { filterAsync, isNotNullOrUndefined, pathToUrl } from '../../../utils';
import { RenameProvider } from '../../interfaces';
import {
Expand All @@ -15,7 +15,8 @@ import {
isComponentAtPosition,
isAfterSvelte2TsxPropsReturn,
isNoTextSpanInGeneratedCode,
SnapshotFragmentMap
SnapshotFragmentMap,
findContainingNode
} from './utils';

export class RenameProviderImpl implements RenameProvider {
Expand Down Expand Up @@ -68,7 +69,13 @@ export class RenameProviderImpl implements RenameProvider {
range: Range;
}
> = await this.mapAndFilterRenameLocations(renameLocations, docs);
// eslint-disable-next-line max-len

convertedRenameLocations = this.checkShortHandBindingLocation(
lang,
convertedRenameLocations,
docs
);

const additionalRenameForPropRenameInsideComponentWithProp =
await this.getAdditionLocationsForRenameOfPropInsideComponentWithProp(
document,
Expand Down Expand Up @@ -237,7 +244,12 @@ export class RenameProviderImpl implements RenameProvider {
const idx = (match.index || 0) + match[0].lastIndexOf(match[1]);
const replacementsForProp =
lang.findRenameLocations(updatePropLocation.fileName, idx, false, false) || [];
return await this.mapAndFilterRenameLocations(replacementsForProp, fragments);

return this.checkShortHandBindingLocation(
lang,
await this.mapAndFilterRenameLocations(replacementsForProp, fragments),
fragments
);
}

// --------> svelte2tsx?
Expand Down Expand Up @@ -369,6 +381,88 @@ export class RenameProviderImpl implements RenameProvider {
private getSnapshot(filePath: string) {
return this.lsAndTsDocResolver.getSnapshot(filePath);
}

private checkShortHandBindingLocation(
lang: ts.LanguageService,
renameLocations: Array<ts.RenameLocation & { range: Range }>,
fragments: SnapshotFragmentMap
): Array<ts.RenameLocation & { range: Range }> {
const bind = 'bind:';

return renameLocations.map((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 fragment = fragments.getFragment(location.fileName);
if (!(fragment instanceof SvelteSnapshotFragment)) {
return location;
}

const { originalText } = fragment;

const possibleJsxAttribute = findContainingNode(
sourceFile,
location.textSpan,
ts.isJsxAttribute
);
if (!possibleJsxAttribute) {
return location;
}

const attributeName = possibleJsxAttribute.name.getText();
const { initializer } = possibleJsxAttribute;

// not props={props}
if (
!initializer ||
!ts.isJsxExpression(initializer) ||
attributeName !== initializer.expression?.getText()
) {
return location;
}

const originalStart = offsetAt(location.range.start, originalText);

const isShortHandBinding =
originalText.substr(originalStart - bind.length, bind.length) === bind;

const directiveName = (isShortHandBinding ? bind : '') + attributeName;
const prefixText = directiveName + '={';

const newRange = mapRangeToOriginal(
fragment,
convertRange(fragment, {
start: possibleJsxAttribute.getStart(),
length: possibleJsxAttribute.getWidth()
})
);

// somehow the mapping is one character before
if (
isShortHandBinding ||
originalText
.substring(offsetAt(newRange.start, originalText), originalStart)
.trimLeft() === '{'
) {
newRange.start.character++;
}

return {
...location,
prefixText,
suffixText: '}',
range: newRange
};
});
}
}

function unique<T>(array: T[]): T[] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ describe('RenameProvider', () => {
const renameDocIgnoreGenerated = await openDoc('rename-ignore-generated.svelte');
const renameDocSlotEventsImporter = await openDoc('rename-slot-events-importer.svelte');
const renameDocPropWithSlotEvents = await openDoc('rename-prop-with-slot-events.svelte');
const renameDocShorthand = await openDoc('rename-shorthand.svelte');

return {
provider,
renameDoc1,
Expand All @@ -49,6 +51,7 @@ describe('RenameProvider', () => {
renameDocIgnoreGenerated,
renameDocSlotEventsImporter,
renameDocPropWithSlotEvents,
renameDocShorthand,
docManager
};

Expand Down Expand Up @@ -595,4 +598,82 @@ describe('RenameProvider', () => {
}
});
});

it('should can rename shorthand props without breaking value-passing', async () => {
const { provider, renameDocShorthand } = await setup();

const result = await provider.rename(renameDocShorthand, Position.create(3, 16), 'newName');

assert.deepStrictEqual(result, {
changes: {
[getUri('rename-shorthand.svelte')]: [
{
newText: 'newName',
range: {
start: {
line: 3,
character: 15
},
end: {
line: 3,
character: 21
}
}
},
{
newText: 'bind:props2={newName}',
range: {
start: {
line: 6,
character: 7
},
end: {
line: 6,
character: 18
}
}
},
{
newText: 'props2={newName}',
range: {
start: {
line: 7,
character: 7
},
end: {
line: 7,
character: 15
}
}
},
{
newText: 'props2={newName}',
range: {
start: {
line: 8,
character: 7
},
end: {
line: 8,
character: 22
}
}
},
{
newText: 'newName',
range: {
start: {
line: 9,
character: 15
},
end: {
line: 9,
character: 21
}
}
}
]
}
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
import Child from './rename3.svelte';

export let props2;
</script>

<Child bind:props2 />
<Child {props2} />
<Child props2={props2} />
<Child props2={props2?.toString()} />
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<script>
export let exportedPropFromJs;
export let props2;
</script>