Skip to content

Commit 335381e

Browse files
authored
fix(language-core): handle named default import of components correctly (#5066)
1 parent f94d7a1 commit 335381e

File tree

5 files changed

+94
-53
lines changed

5 files changed

+94
-53
lines changed

packages/language-core/lib/codegen/script/componentSelf.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ export function* generateComponentSelf(
2727
? [options.sfc.script.content, options.scriptRanges.bindings] as const
2828
: ['', []] as const,
2929
]) {
30-
for (const expose of bindings) {
31-
const varName = content.slice(expose.start, expose.end);
30+
for (const { range } of bindings) {
31+
const varName = content.slice(range.start, range.end);
3232
if (!templateUsageVars.has(varName) && !templateCodegenCtx.accessExternalVariables.has(varName)) {
3333
continue;
3434
}

packages/language-core/lib/codegen/script/context.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ export function createScriptCodegenContext(options: ScriptCodegenOptions) {
2121
scriptSetupGeneratedOffset: undefined as number | undefined,
2222
bypassDefineComponent: options.lang === 'js' || options.lang === 'jsx',
2323
bindingNames: new Set([
24-
...options.scriptRanges?.bindings.map(range => options.sfc.script!.content.slice(range.start, range.end)) ?? [],
25-
...options.scriptSetupRanges?.bindings.map(range => options.sfc.scriptSetup!.content.slice(range.start, range.end)) ?? [],
24+
...options.scriptRanges?.bindings.map(
25+
({ range }) => options.sfc.script!.content.slice(range.start, range.end)
26+
) ?? [],
27+
...options.scriptSetupRanges?.bindings.map(
28+
({ range }) => options.sfc.scriptSetup!.content.slice(range.start, range.end)
29+
) ?? [],
2630
]),
2731
localTypes,
2832
inlayHints,

packages/language-core/lib/parsers/scriptSetupRanges.ts

+63-43
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ export function parseScriptSetupRanges(
8181
const definePropProposalA = vueCompilerOptions.experimentalDefinePropProposal === 'kevinEdition' || ast.text.trimStart().startsWith('// @experimentalDefinePropProposal=kevinEdition');
8282
const definePropProposalB = vueCompilerOptions.experimentalDefinePropProposal === 'johnsonEdition' || ast.text.trimStart().startsWith('// @experimentalDefinePropProposal=johnsonEdition');
8383
const text = ast.text;
84-
const importComponentNames = new Set<string>();
8584

8685
const leadingCommentRanges = ts.getLeadingCommentRanges(text, 0)?.reverse() ?? [];
8786
const leadingCommentEndOffset = leadingCommentRanges.find(
@@ -114,22 +113,11 @@ export function parseScriptSetupRanges(
114113
}
115114
foundNonImportExportNode = true;
116115
}
117-
118-
if (
119-
ts.isImportDeclaration(node)
120-
&& node.importClause?.name
121-
&& !node.importClause.isTypeOnly
122-
) {
123-
const moduleName = _getNodeText(node.moduleSpecifier).slice(1, -1);
124-
if (vueCompilerOptions.extensions.some(ext => moduleName.endsWith(ext))) {
125-
importComponentNames.add(_getNodeText(node.importClause.name));
126-
}
127-
}
128116
});
129-
ts.forEachChild(ast, child => visitNode(child, [ast]));
117+
ts.forEachChild(ast, node => visitNode(node, [ast]));
130118

131119
const templateRefNames = new Set(useTemplateRef.map(ref => ref.name));
132-
bindings = bindings.filter(range => {
120+
bindings = bindings.filter(({ range }) => {
133121
const name = text.slice(range.start, range.end);
134122
return !templateRefNames.has(name);
135123
});
@@ -138,7 +126,6 @@ export function parseScriptSetupRanges(
138126
leadingCommentEndOffset,
139127
importSectionEndOffset,
140128
bindings,
141-
importComponentNames,
142129
defineProp,
143130
defineProps,
144131
withDefaults,
@@ -433,72 +420,105 @@ export function parseScriptSetupRanges(
433420
}
434421
}
435422

436-
export function parseBindingRanges(ts: typeof import('typescript'), sourceFile: ts.SourceFile) {
437-
const bindings: TextRange[] = [];
438-
ts.forEachChild(sourceFile, node => {
423+
export function parseBindingRanges(ts: typeof import('typescript'), ast: ts.SourceFile) {
424+
const bindings: {
425+
range: TextRange;
426+
moduleName?: string;
427+
isDefaultImport?: boolean;
428+
isNamespace?: boolean;
429+
}[] = [];
430+
431+
ts.forEachChild(ast, node => {
439432
if (ts.isVariableStatement(node)) {
440433
for (const decl of node.declarationList.declarations) {
441434
const vars = _findBindingVars(decl.name);
442-
bindings.push(...vars);
435+
bindings.push(...vars.map((range) => ({ range })));
443436
}
444437
}
445438
else if (ts.isFunctionDeclaration(node)) {
446439
if (node.name && ts.isIdentifier(node.name)) {
447-
bindings.push(_getStartEnd(node.name));
440+
bindings.push({
441+
range: _getStartEnd(node.name)
442+
});
448443
}
449444
}
450445
else if (ts.isClassDeclaration(node)) {
451446
if (node.name) {
452-
bindings.push(_getStartEnd(node.name));
447+
bindings.push({
448+
range: _getStartEnd(node.name)
449+
});
453450
}
454451
}
455452
else if (ts.isEnumDeclaration(node)) {
456-
bindings.push(_getStartEnd(node.name));
453+
bindings.push({
454+
range: _getStartEnd(node.name)
455+
});
457456
}
458457

459458
if (ts.isImportDeclaration(node)) {
459+
const moduleName = _getNodeText(node.moduleSpecifier).slice(1, -1);
460+
460461
if (node.importClause && !node.importClause.isTypeOnly) {
461-
if (node.importClause.name) {
462-
bindings.push(_getStartEnd(node.importClause.name));
462+
const { name, namedBindings } = node.importClause;
463+
464+
if (name) {
465+
bindings.push({
466+
range: _getStartEnd(name),
467+
moduleName,
468+
isDefaultImport: true
469+
});
463470
}
464-
if (node.importClause.namedBindings) {
465-
if (ts.isNamedImports(node.importClause.namedBindings)) {
466-
for (const element of node.importClause.namedBindings.elements) {
471+
if (namedBindings) {
472+
if (ts.isNamedImports(namedBindings)) {
473+
for (const element of namedBindings.elements) {
467474
if (element.isTypeOnly) {
468475
continue;
469476
}
470-
bindings.push(_getStartEnd(element.name));
477+
bindings.push({
478+
range: _getStartEnd(element.name),
479+
moduleName,
480+
isDefaultImport: element.propertyName?.text === 'default'
481+
});
471482
}
472483
}
473-
else if (ts.isNamespaceImport(node.importClause.namedBindings)) {
474-
bindings.push(_getStartEnd(node.importClause.namedBindings.name));
484+
else {
485+
bindings.push({
486+
range: _getStartEnd(namedBindings.name),
487+
moduleName,
488+
isNamespace: true
489+
});
475490
}
476491
}
477492
}
478493
}
479494
});
495+
480496
return bindings;
481497

482498
function _getStartEnd(node: ts.Node) {
483-
return getStartEnd(ts, node, sourceFile);
499+
return getStartEnd(ts, node, ast);
500+
}
501+
502+
function _getNodeText(node: ts.Node) {
503+
return getNodeText(ts, node, ast);
484504
}
485505

486506
function _findBindingVars(left: ts.BindingName) {
487-
return findBindingVars(ts, left, sourceFile);
507+
return findBindingVars(ts, left, ast);
488508
}
489509
}
490510

491511
export function findBindingVars(
492512
ts: typeof import('typescript'),
493513
left: ts.BindingName,
494-
sourceFile: ts.SourceFile
514+
ast: ts.SourceFile
495515
) {
496516
const vars: TextRange[] = [];
497517
worker(left);
498518
return vars;
499519
function worker(node: ts.Node) {
500520
if (ts.isIdentifier(node)) {
501-
vars.push(getStartEnd(ts, node, sourceFile));
521+
vars.push(getStartEnd(ts, node, ast));
502522
}
503523
// { ? } = ...
504524
// [ ? ] = ...
@@ -515,7 +535,7 @@ export function findBindingVars(
515535
}
516536
// { foo } = ...
517537
else if (ts.isShorthandPropertyAssignment(node)) {
518-
vars.push(getStartEnd(ts, node.name, sourceFile));
538+
vars.push(getStartEnd(ts, node.name, ast));
519539
}
520540
// { ...? } = ...
521541
// [ ...? ] = ...
@@ -528,43 +548,43 @@ export function findBindingVars(
528548
export function getStartEnd(
529549
ts: typeof import('typescript'),
530550
node: ts.Node,
531-
sourceFile: ts.SourceFile
551+
ast: ts.SourceFile
532552
): TextRange {
533553
return {
534-
start: (ts as any).getTokenPosOfNode(node, sourceFile) as number,
554+
start: (ts as any).getTokenPosOfNode(node, ast) as number,
535555
end: node.end,
536556
};
537557
}
538558

539559
export function getNodeText(
540560
ts: typeof import('typescript'),
541561
node: ts.Node,
542-
sourceFile: ts.SourceFile
562+
ast: ts.SourceFile
543563
) {
544-
const { start, end } = getStartEnd(ts, node, sourceFile);
545-
return sourceFile.text.slice(start, end);
564+
const { start, end } = getStartEnd(ts, node, ast);
565+
return ast.text.slice(start, end);
546566
}
547567

548568
function getStatementRange(
549569
ts: typeof import('typescript'),
550570
parents: ts.Node[],
551571
node: ts.Node,
552-
sourceFile: ts.SourceFile
572+
ast: ts.SourceFile
553573
) {
554574
let statementRange: TextRange | undefined;
555575
for (let i = parents.length - 1; i >= 0; i--) {
556576
if (ts.isStatement(parents[i])) {
557577
const statement = parents[i];
558578
ts.forEachChild(statement, child => {
559-
const range = getStartEnd(ts, child, sourceFile);
579+
const range = getStartEnd(ts, child, ast);
560580
statementRange ??= range;
561581
statementRange.end = range.end;
562582
});
563583
break;
564584
}
565585
}
566586
if (!statementRange) {
567-
statementRange = getStartEnd(ts, node, sourceFile);
587+
statementRange = getStartEnd(ts, node, ast);
568588
}
569589
return statementRange;
570590
}

packages/language-core/lib/plugins/vue-tsx.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,29 @@ function createTsx(
9292
const newNames = new Set<string>();
9393
const bindings = scriptSetupRanges.get()?.bindings;
9494
if (_sfc.scriptSetup && bindings) {
95-
for (const binding of bindings) {
96-
newNames.add(_sfc.scriptSetup?.content.slice(binding.start, binding.end));
95+
for (const { range } of bindings) {
96+
newNames.add(_sfc.scriptSetup.content.slice(range.start, range.end));
9797
}
9898
}
9999
return newNames;
100100
})
101101
);
102102
const scriptSetupImportComponentNames = Unstable.computedSet(
103103
computed(() => {
104-
const newNames = scriptSetupRanges.get()?.importComponentNames ?? new Set();
104+
const newNames = new Set<string>();
105+
const bindings = scriptSetupRanges.get()?.bindings;
106+
if (_sfc.scriptSetup && bindings) {
107+
for (const { range, moduleName, isDefaultImport, isNamespace } of bindings) {
108+
if (
109+
moduleName
110+
&& isDefaultImport
111+
&& !isNamespace
112+
&& ctx.vueCompilerOptions.extensions.some(ext => moduleName.endsWith(ext))
113+
) {
114+
newNames.add(_sfc.scriptSetup.content.slice(range.start, range.end));
115+
}
116+
}
117+
}
105118
return newNames;
106119
})
107120
);

packages/language-service/lib/plugins/vue-template.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,7 @@ export function create(
516516
}
517517

518518
for (const binding of scriptSetupRanges?.bindings ?? []) {
519-
const name = vueCode._sfc.scriptSetup!.content.slice(binding.start, binding.end);
519+
const name = vueCode._sfc.scriptSetup!.content.slice(binding.range.start, binding.range.end);
520520
if (casing.tag === TagNameCasing.Kebab) {
521521
names.add(hyphenateTag(name));
522522
}
@@ -568,8 +568,12 @@ export function create(
568568
return [];
569569
}
570570
let ctxVars = [
571-
..._tsCodegen.scriptRanges.get()?.bindings.map(binding => vueCode._sfc.script!.content.slice(binding.start, binding.end)) ?? [],
572-
..._tsCodegen.scriptSetupRanges.get()?.bindings.map(binding => vueCode._sfc.scriptSetup!.content.slice(binding.start, binding.end)) ?? [],
571+
..._tsCodegen.scriptRanges.get()?.bindings.map(
572+
({ range }) => vueCode._sfc.script!.content.slice(range.start, range.end)
573+
) ?? [],
574+
..._tsCodegen.scriptSetupRanges.get()?.bindings.map(
575+
({ range }) => vueCode._sfc.scriptSetup!.content.slice(range.start, range.end)
576+
) ?? [],
573577
...templateContextProps,
574578
];
575579
ctxVars = [...new Set(ctxVars)];

0 commit comments

Comments
 (0)