Skip to content

Commit cc6cac0

Browse files
authored
(fix) change svelte2tsx' default export (#297)
Append `__SvelteComponent_` to make it unlikely that name clashes occur. #294 Due to this most of the simplifications in #293 had to be reverted.
1 parent 57cebfb commit cc6cac0

File tree

61 files changed

+153
-103
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+153
-103
lines changed

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

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
mapCompletionItemToOriginal,
1515
mapRangeToOriginal,
1616
} from '../../../lib/documents';
17-
import { pathToUrl } from '../../../utils';
17+
import { isNotNullOrUndefined, pathToUrl } from '../../../utils';
1818
import { AppCompletionItem, AppCompletionList, CompletionsProvider } from '../../interfaces';
1919
import { SvelteSnapshotFragment } from '../DocumentSnapshot';
2020
import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
@@ -92,19 +92,27 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
9292
}
9393

9494
const completionItems = completions.entries
95-
.map((comp) => this.toCompletionItem(comp, pathToUrl(tsDoc.filePath), position))
95+
.map((comp) =>
96+
this.toCompletionItem(fragment, comp, pathToUrl(tsDoc.filePath), position),
97+
)
98+
.filter(isNotNullOrUndefined)
9699
.map((comp) => mapCompletionItemToOriginal(fragment, comp));
97100

98101
return CompletionList.create(completionItems, !!tsDoc.parserError);
99102
}
100103

101104
private toCompletionItem(
105+
fragment: SvelteSnapshotFragment,
102106
comp: ts.CompletionEntry,
103107
uri: string,
104108
position: Position,
105-
): AppCompletionItem<CompletionEntryWithIdentifer> {
106-
const { label, insertText, isSvelteComp } = this.getCompletionLabelAndInsert(comp);
109+
): AppCompletionItem<CompletionEntryWithIdentifer> | null {
110+
const completionLabelAndInsert = this.getCompletionLabelAndInsert(fragment, comp);
111+
if (!completionLabelAndInsert) {
112+
return null;
113+
}
107114

115+
const { label, insertText, isSvelteComp } = completionLabelAndInsert;
108116
return {
109117
label,
110118
insertText,
@@ -122,11 +130,21 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
122130
};
123131
}
124132

125-
private getCompletionLabelAndInsert(comp: ts.CompletionEntry) {
126-
const { kind, kindModifiers, name, source } = comp;
133+
private getCompletionLabelAndInsert(
134+
fragment: SvelteSnapshotFragment,
135+
comp: ts.CompletionEntry,
136+
) {
137+
let { kind, kindModifiers, name, source } = comp;
127138
const isScriptElement = kind === ts.ScriptElementKind.scriptElement;
128139
const hasModifier = Boolean(comp.kindModifiers);
129-
const isSvelteComp = this.isSvelteComponentImport(`import ${name} from ${source}`);
140+
const isSvelteComp = this.isSvelteComponentImport(name);
141+
if (isSvelteComp) {
142+
name = this.changeSvelteComponentName(name);
143+
144+
if (this.isExistingSvelteComponentImport(fragment, name, source)) {
145+
return null;
146+
}
147+
}
130148

131149
if (isScriptElement && hasModifier) {
132150
return {
@@ -142,6 +160,15 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
142160
};
143161
}
144162

163+
private isExistingSvelteComponentImport(
164+
fragment: SvelteSnapshotFragment,
165+
name: string,
166+
source?: string,
167+
): boolean {
168+
const importStatement = new RegExp(`import ${name} from ["'\`][\\s\\S]+\\.svelte["'\`]`);
169+
return !!source && !!fragment.text.match(importStatement);
170+
}
171+
145172
async resolveCompletion(
146173
document: Document,
147174
completionItem: AppCompletionItem<CompletionEntryWithIdentifer>,
@@ -193,7 +220,7 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
193220

194221
private getCompletionDocument(compDetail: ts.CompletionEntryDetails) {
195222
const { source, documentation: tsDocumentation, displayParts } = compDetail;
196-
let detail: string = ts.displayPartsToString(displayParts);
223+
let detail: string = this.changeSvelteComponentName(ts.displayPartsToString(displayParts));
197224

198225
if (source) {
199226
const importPath = ts.displayPartsToString(source);
@@ -225,6 +252,8 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
225252
fragment: SvelteSnapshotFragment,
226253
change: ts.TextChange,
227254
): TextEdit {
255+
change.newText = this.changeSvelteComponentName(change.newText);
256+
228257
const scriptTagInfo = fragment.scriptInfo;
229258
if (!scriptTagInfo) {
230259
// no script tag defined yet, add it.
@@ -257,8 +286,12 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
257286
return TextEdit.replace(range, change.newText);
258287
}
259288

260-
private isSvelteComponentImport(text: string) {
261-
return /import \w+ from [\s\S]*.svelte($|"|'| )/.test(text);
289+
private isSvelteComponentImport(className: string) {
290+
return className.endsWith('__SvelteComponent_');
291+
}
292+
293+
private changeSvelteComponentName(name: string) {
294+
return name.replace(/(\w+)__SvelteComponent_/, '$1');
262295
}
263296
}
264297

packages/svelte2tsx/rollup.config.js

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,36 @@ import resolve from 'rollup-plugin-node-resolve';
44
import json from 'rollup-plugin-json';
55
import builtins from 'builtin-modules';
66

7-
export default [{
8-
input: 'src/index.ts',
9-
output: [{
10-
sourcemap: true,
11-
format: 'commonjs',
12-
file: 'index.js'
13-
},{
14-
file: 'index.mjs',
15-
format: 'esm'
16-
}],
17-
plugins: [
18-
resolve({ browser: false, preferBuiltins: true }),
19-
commonjs(),
20-
json(),
21-
typescript()
22-
],
23-
watch: {
24-
clearScreen: false
25-
},
26-
external: [...builtins, 'typescript', 'svelte', 'svelte/compiler']
27-
}];
7+
export default [
8+
{
9+
input: 'src/index.ts',
10+
output: [
11+
{
12+
sourcemap: true,
13+
format: 'commonjs',
14+
file: 'index.js',
15+
},
16+
{
17+
file: 'index.mjs',
18+
format: 'esm',
19+
},
20+
],
21+
plugins: [
22+
resolve({ browser: false, preferBuiltins: true }),
23+
commonjs(),
24+
json(),
25+
typescript(),
26+
],
27+
watch: {
28+
clearScreen: false,
29+
},
30+
external: [
31+
...builtins,
32+
'typescript',
33+
'svelte',
34+
'svelte/compiler',
35+
'dedent-js',
36+
'pascal-case',
37+
],
38+
},
39+
];

packages/svelte2tsx/src/svelte2tsx.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ type pendingStoreResolution<T> = {
6464
*/
6565
const COMPONENT_DOCUMENTATION_HTML_COMMENT_TAG = '@component';
6666

67+
/**
68+
* A component class name suffix is necessary to prevent class name clashes
69+
* like reported in https://github.com/sveltejs/language-tools/issues/294
70+
*/
71+
const COMPONENT_SUFFIX = '__SvelteComponent_';
72+
6773
function processSvelteTemplate(str: MagicString): TemplateProcessResult {
6874
const htmlxAst = parseHtmlx(str.original);
6975

@@ -437,9 +443,7 @@ function processInstanceScriptContent(str: MagicString, script: Node): InstanceS
437443
return;
438444
}
439445

440-
const hasInitializers = node.declarations.filter(
441-
(declaration) => declaration.initializer
442-
);
446+
const hasInitializers = node.declarations.filter((declaration) => declaration.initializer);
443447
const handleTypeAssertion = (declaration: ts.VariableDeclaration) => {
444448
const identifier = declaration.name;
445449
const tsType = declaration.type;
@@ -455,10 +459,11 @@ function processInstanceScriptContent(str: MagicString, script: Node): InstanceS
455459
str.appendLeft(end, `;${name} = __sveltets_any(${name});`);
456460
};
457461

458-
const findComma = (target: ts.Node) => target.getChildren()
459-
.filter((child) => child.kind === ts.SyntaxKind.CommaToken);
462+
const findComma = (target: ts.Node) =>
463+
target.getChildren().filter((child) => child.kind === ts.SyntaxKind.CommaToken);
460464
const splitDeclaration = () => {
461-
const commas = node.getChildren()
465+
const commas = node
466+
.getChildren()
462467
.filter((child) => child.kind === ts.SyntaxKind.SyntaxList)
463468
.map(findComma)
464469
.reduce((current, previous) => [...current, ...previous], []);
@@ -802,7 +807,7 @@ function formatComponentDocumentation(contents?: string | null) {
802807

803808
const lines = dedent(contents)
804809
.split('\n')
805-
.map(line => ` *${line ? ` ${line}` : ''}`)
810+
.map((line) => ` *${line ? ` ${line}` : ''}`)
806811
.join('\n');
807812

808813
return `/**\n${lines}\n */\n`;
@@ -815,7 +820,7 @@ function addComponentExport(
815820
isTsFile: boolean,
816821
/** A named export allows for TSDoc-compatible docstrings */
817822
className?: string,
818-
componentDocumentation?: string | null
823+
componentDocumentation?: string | null,
819824
) {
820825
const propDef =
821826
// Omit partial-wrapper only if both strict mode and ts file, because
@@ -830,7 +835,9 @@ function addComponentExport(
830835
const doc = formatComponentDocumentation(componentDocumentation);
831836

832837
// eslint-disable-next-line max-len
833-
const statement = `\n\n${doc}export default class ${className ? `${className} ` : ''}{\n $$prop_def = ${propDef}\n $$slot_def = render().slots\n}`;
838+
const statement = `\n\n${doc}export default class ${
839+
className ? `${className} ` : ''
840+
}{\n $$prop_def = ${propDef}\n $$slot_def = render().slots\n}`;
834841

835842
str.append(statement);
836843
}
@@ -845,7 +852,7 @@ export function classNameFromFilename(filename: string): string | undefined {
845852
try {
846853
const withoutExtensions = path.parse(filename).name?.split('.')[0];
847854
const inPascalCase = pascalCase(withoutExtensions);
848-
return inPascalCase;
855+
return `${inPascalCase}${COMPONENT_SUFFIX}`;
849856
} catch (error) {
850857
console.warn(`Failed to create a name for the component class from filename ${filename}`);
851858
return undefined;
@@ -912,7 +919,7 @@ function createRenderFunction(
912919

913920
const scriptEndTagStart = htmlx.lastIndexOf('<', scriptTag.end - 1);
914921
str.overwrite(scriptEndTagStart, scriptTag.end, ';\n<>', {
915-
contentOnly: true
922+
contentOnly: true,
916923
});
917924
} else {
918925
str.prependRight(scriptDestination, `</>;function render() {${propsDecl}\n<>`);
@@ -973,9 +980,7 @@ export function svelte2tsx(svelte: string, options?: { filename?: string; strict
973980
uses$$props,
974981
uses$$restProps,
975982
componentDocumentation,
976-
} = processSvelteTemplate(
977-
str,
978-
);
983+
} = processSvelteTemplate(str);
979984

980985
/* Rearrange the script tags so that module is first, and instance second followed finally by the template
981986
* This is a bit convoluted due to some trouble I had with magic string. A simple str.move(start,end,0) for each script wasn't enough

packages/svelte2tsx/test/svelte2tsx/samples/array-binding-export/expected.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<></>
66
return { props: {a: a , b: b , c: c}, slots: {} }}
77

8-
export default class Input {
8+
export default class Input__SvelteComponent_ {
99
$$prop_def = __sveltets_partial(render().props)
1010
$$slot_def = render().slots
1111
}

packages/svelte2tsx/test/svelte2tsx/samples/ast-offset-none/expected.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ __sveltets_store_get(var);
33
<></>
44
return { props: {}, slots: {} }}
55

6-
export default class Input {
6+
export default class Input__SvelteComponent_ {
77
$$prop_def = __sveltets_partial(render().props)
88
$$slot_def = render().slots
99
}

packages/svelte2tsx/test/svelte2tsx/samples/ast-offset-some/expected.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<></>
44
return { props: {}, slots: {} }}
55

6-
export default class Input {
6+
export default class Input__SvelteComponent_ {
77
$$prop_def = __sveltets_partial(render().props)
88
$$slot_def = render().slots
99
}

packages/svelte2tsx/test/svelte2tsx/samples/await-with-$store/expected.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ function render() {
1313
</>})}}</>
1414
return { props: {}, slots: {} }}
1515

16-
export default class Input {
16+
export default class Input__SvelteComponent_ {
1717
$$prop_def = __sveltets_partial(render().props)
1818
$$slot_def = render().slots
1919
}

packages/svelte2tsx/test/svelte2tsx/samples/binding-group-store/expected.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<><input id="dom-input" type="radio" {...__sveltets_any(__sveltets_store_get(compile_options).generate)} value="dom"/></>
33
return { props: {}, slots: {} }}
44

5-
export default class Input {
5+
export default class Input__SvelteComponent_ {
66
$$prop_def = __sveltets_partial(render().props)
77
$$slot_def = render().slots
88
}

packages/svelte2tsx/test/svelte2tsx/samples/circle-drawer-example/expected.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
</>}}}</>
9090
return { props: {}, slots: {} }}
9191

92-
export default class Input {
92+
export default class Input__SvelteComponent_ {
9393
$$prop_def = __sveltets_partial(render().props)
9494
$$slot_def = render().slots
9595
}

packages/svelte2tsx/test/svelte2tsx/samples/component-default-slot/expected.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
</div></>
99
return { props: {}, slots: {default: {a:b}} }}
1010

11-
export default class Input {
11+
export default class Input__SvelteComponent_ {
1212
$$prop_def = __sveltets_partial(render().props)
1313
$$slot_def = render().slots
1414
}

0 commit comments

Comments
 (0)