Skip to content

Commit 67f8a4a

Browse files
authored
(feat) rename of component props in other component (#1043)
Allow rename of component's A props in another component B. #110
1 parent bfc657c commit 67f8a4a

File tree

6 files changed

+55
-57
lines changed

6 files changed

+55
-57
lines changed

packages/language-server/src/plugins/typescript/features/CompletionProvider.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,7 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
136136
return null;
137137
}
138138

139-
const eventCompletions = await this.getEventCompletions(
140-
lang,
141-
document,
142-
tsDoc,
143-
fragment,
144-
position
145-
);
139+
const eventCompletions = await this.getEventCompletions(lang, document, tsDoc, position);
146140

147141
if (isEventTriggerCharacter) {
148142
return CompletionList.create(eventCompletions, !!tsDoc.parserError);
@@ -214,15 +208,13 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
214208
lang: ts.LanguageService,
215209
doc: Document,
216210
tsDoc: SvelteDocumentSnapshot,
217-
fragment: SvelteSnapshotFragment,
218211
originalPosition: Position
219212
): Promise<Array<AppCompletionItem<CompletionEntryWithIdentifer>>> {
220213
const snapshot = await getComponentAtPosition(
221214
this.lsAndTsDocResolver,
222215
lang,
223216
doc,
224217
tsDoc,
225-
fragment,
226218
originalPosition
227219
);
228220
if (!snapshot) {

packages/language-server/src/plugins/typescript/features/HoverProvider.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import ts from 'typescript';
22
import { Hover, Position } from 'vscode-languageserver';
33
import { Document, getWordAt, mapObjWithRangeToOriginal } from '../../../lib/documents';
44
import { HoverProvider } from '../../interfaces';
5-
import { SvelteDocumentSnapshot, SvelteSnapshotFragment } from '../DocumentSnapshot';
5+
import { SvelteDocumentSnapshot } from '../DocumentSnapshot';
66
import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
77
import { getMarkdownDocumentation } from '../previewer';
88
import { convertRange } from '../utils';
@@ -15,13 +15,7 @@ export class HoverProviderImpl implements HoverProvider {
1515
const { lang, tsDoc } = await this.getLSAndTSDoc(document);
1616
const fragment = await tsDoc.getFragment();
1717

18-
const eventHoverInfo = await this.getEventHoverInfo(
19-
lang,
20-
document,
21-
tsDoc,
22-
fragment,
23-
position
24-
);
18+
const eventHoverInfo = await this.getEventHoverInfo(lang, document, tsDoc, position);
2519
if (eventHoverInfo) {
2620
return eventHoverInfo;
2721
}
@@ -66,7 +60,6 @@ export class HoverProviderImpl implements HoverProvider {
6660
lang: ts.LanguageService,
6761
doc: Document,
6862
tsDoc: SvelteDocumentSnapshot,
69-
fragment: SvelteSnapshotFragment,
7063
originalPosition: Position
7164
): Promise<Hover | null> {
7265
const possibleEventName = getWordAt(doc.getText(), doc.offsetAt(originalPosition), {
@@ -82,7 +75,6 @@ export class HoverProviderImpl implements HoverProvider {
8275
lang,
8376
doc,
8477
tsDoc,
85-
fragment,
8678
originalPosition
8779
);
8880
if (!component) {

packages/language-server/src/plugins/typescript/features/RenameProvider.ts

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
import { Position, WorkspaceEdit, Range } from 'vscode-languageserver';
2-
import {
3-
Document,
4-
mapRangeToOriginal,
5-
positionAt,
6-
offsetAt,
7-
getLineAtPosition
8-
} from '../../../lib/documents';
2+
import { Document, mapRangeToOriginal, getLineAtPosition } from '../../../lib/documents';
93
import { filterAsync, isNotNullOrUndefined, pathToUrl } from '../../../utils';
104
import { RenameProvider } from '../../interfaces';
115
import {
@@ -17,7 +11,7 @@ import { convertRange } from '../utils';
1711
import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
1812
import ts from 'typescript';
1913
import { uniqWith, isEqual } from 'lodash';
20-
import { isNoTextSpanInGeneratedCode, SnapshotFragmentMap } from './utils';
14+
import { isComponentAtPosition, isNoTextSpanInGeneratedCode, SnapshotFragmentMap } from './utils';
2115

2216
export class RenameProviderImpl implements RenameProvider {
2317
constructor(private readonly lsAndTsDocResolver: LSAndTSDocResolver) {}
@@ -29,7 +23,7 @@ export class RenameProviderImpl implements RenameProvider {
2923
const fragment = await tsDoc.getFragment();
3024

3125
const offset = fragment.offsetAt(fragment.getGeneratedPosition(position));
32-
const renameInfo = this.getRenameInfo(lang, tsDoc, offset);
26+
const renameInfo = this.getRenameInfo(lang, tsDoc, document, position, offset);
3327
if (!renameInfo) {
3428
return null;
3529
}
@@ -47,7 +41,7 @@ export class RenameProviderImpl implements RenameProvider {
4741

4842
const offset = fragment.offsetAt(fragment.getGeneratedPosition(position));
4943

50-
if (!this.getRenameInfo(lang, tsDoc, offset)) {
44+
if (!this.getRenameInfo(lang, tsDoc, document, position, offset)) {
5145
return null;
5246
}
5347

@@ -118,7 +112,9 @@ export class RenameProviderImpl implements RenameProvider {
118112
private getRenameInfo(
119113
lang: ts.LanguageService,
120114
tsDoc: SvelteDocumentSnapshot,
121-
offset: number
115+
doc: Document,
116+
originalPosition: Position,
117+
generatedOffset: number
122118
): {
123119
canRename: true;
124120
kind: ts.ScriptElementKind;
@@ -131,20 +127,18 @@ export class RenameProviderImpl implements RenameProvider {
131127
if (tsDoc.parserError) {
132128
return null;
133129
}
134-
const renameInfo: any = lang.getRenameInfo(tsDoc.filePath, offset, {
130+
const renameInfo: any = lang.getRenameInfo(tsDoc.filePath, generatedOffset, {
135131
allowRenameOfImportPath: false
136132
});
137-
// TODO this will also forbid renames of svelte component properties
138-
// in another component because the ScriptElementKind is a JSXAttribute.
139-
// To fix this we would need to enhance svelte2tsx with info methods like
140-
// "what props does this file have?"
141133
if (
142134
!renameInfo.canRename ||
143-
renameInfo.kind === ts.ScriptElementKind.jsxAttribute ||
144-
renameInfo.fullDisplayName?.includes('JSX.IntrinsicElements')
135+
renameInfo.fullDisplayName?.includes('JSX.IntrinsicElements') ||
136+
(renameInfo.kind === ts.ScriptElementKind.jsxAttribute &&
137+
!isComponentAtPosition(doc, tsDoc, originalPosition))
145138
) {
146139
return null;
147140
}
141+
148142
return renameInfo;
149143
}
150144

@@ -279,13 +273,9 @@ export class RenameProviderImpl implements RenameProvider {
279273

280274
// --------> svelte2tsx?
281275
private isInSvelte2TsxPropLine(fragment: SvelteSnapshotFragment, loc: ts.RenameLocation) {
282-
const pos = positionAt(loc.textSpan.start, fragment.text);
283-
const textInLine = fragment.text.substring(
284-
offsetAt({ ...pos, character: 0 }, fragment.text),
285-
loc.textSpan.start
286-
);
276+
const textBeforeProp = fragment.text.substring(0, loc.textSpan.start);
287277
// This is how svelte2tsx writes out the props
288-
if (textInLine.includes('return { props: {')) {
278+
if (textBeforeProp.includes('\nreturn { props: {')) {
289279
return true;
290280
}
291281
}

packages/language-server/src/plugins/typescript/features/utils.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,7 @@ import {
66
getNodeIfIsInComponentStartTag,
77
isInTag
88
} from '../../../lib/documents';
9-
import {
10-
DocumentSnapshot,
11-
SnapshotFragment,
12-
SvelteDocumentSnapshot,
13-
SvelteSnapshotFragment
14-
} from '../DocumentSnapshot';
9+
import { DocumentSnapshot, SnapshotFragment, SvelteDocumentSnapshot } from '../DocumentSnapshot';
1510
import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
1611

1712
/**
@@ -23,7 +18,6 @@ export async function getComponentAtPosition(
2318
lang: ts.LanguageService,
2419
doc: Document,
2520
tsDoc: SvelteDocumentSnapshot,
26-
fragment: SvelteSnapshotFragment,
2721
originalPosition: Position
2822
): Promise<SvelteDocumentSnapshot | null> {
2923
if (tsDoc.parserError) {
@@ -43,6 +37,7 @@ export async function getComponentAtPosition(
4337
return null;
4438
}
4539

40+
const fragment = await tsDoc.getFragment();
4641
const generatedPosition = fragment.getGeneratedPosition(doc.positionAt(node.start + 1));
4742
const def = lang.getDefinitionAtPosition(
4843
tsDoc.filePath,
@@ -59,6 +54,26 @@ export async function getComponentAtPosition(
5954
return snapshot;
6055
}
6156

57+
export function isComponentAtPosition(
58+
doc: Document,
59+
tsDoc: SvelteDocumentSnapshot,
60+
originalPosition: Position
61+
): boolean {
62+
if (tsDoc.parserError) {
63+
return false;
64+
}
65+
66+
if (
67+
isInTag(originalPosition, doc.scriptInfo) ||
68+
isInTag(originalPosition, doc.moduleScriptInfo)
69+
) {
70+
// Inside script tags -> not a component
71+
return false;
72+
}
73+
74+
return !!getNodeIfIsInComponentStartTag(doc.html, doc.offsetAt(originalPosition));
75+
}
76+
6277
/**
6378
* Checks if this a section that should be completely ignored
6479
* because it's purely generated.

packages/language-server/test/plugins/typescript/features/RenameProvider.test.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,21 @@ describe('RenameProvider', () => {
209209
assert.deepStrictEqual(result, expectedEditsForPropRename);
210210
});
211211

212-
// TODO this does not work right now, see `RenameProviderImpl.cannotRename` for more explanation
213-
// it('should do rename of prop of component A in component B', async () => {
214-
// const { provider, renameDoc2 } = await setup();
215-
// const result = await provider.rename(renameDoc2, Position.create(4, 10), 'newName');
212+
it('should do rename of prop of component A in component B', async () => {
213+
const { provider, renameDoc2 } = await setup();
214+
const result = await provider.rename(renameDoc2, Position.create(5, 10), 'newName');
216215

217-
// assert.deepStrictEqual(result, expectedEditsForPropRename);
218-
// });
216+
assert.deepStrictEqual(result, expectedEditsForPropRename);
217+
});
218+
219+
it('should not allow rename of intrinsic attribute', async () => {
220+
const { provider, renameDoc2 } = await setup();
221+
const prepareResult = await provider.prepareRename(renameDoc2, Position.create(7, 7));
222+
const renameResult = await provider.rename(renameDoc2, Position.create(7, 7), 'newName');
223+
224+
assert.deepStrictEqual(prepareResult, null);
225+
assert.deepStrictEqual(renameResult, null);
226+
});
219227

220228
it('should do rename of prop without type of component A in component A', async () => {
221229
const { provider, renameDoc3 } = await setup();

packages/language-server/test/plugins/typescript/testfiles/rename/rename2.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ import Rename3 from './rename3.svelte';
44
</script>
55

66
<Rename exportedProp={2}></Rename>
7-
<Rename3 exportedPropFromJs={2}></Rename3>
7+
<Rename3 exportedPropFromJs={2}></Rename3>
8+
<div class="foo" />

0 commit comments

Comments
 (0)