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(language-core): handle named default import of components correctly #5066

Merged
merged 3 commits into from
Dec 18, 2024
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
4 changes: 2 additions & 2 deletions packages/language-core/lib/codegen/script/componentSelf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ export function* generateComponentSelf(
? [options.sfc.script.content, options.scriptRanges.bindings] as const
: ['', []] as const,
]) {
for (const expose of bindings) {
const varName = content.slice(expose.start, expose.end);
for (const { range } of bindings) {
const varName = content.slice(range.start, range.end);
if (!templateUsageVars.has(varName) && !templateCodegenCtx.accessExternalVariables.has(varName)) {
continue;
}
Expand Down
8 changes: 6 additions & 2 deletions packages/language-core/lib/codegen/script/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@ export function createScriptCodegenContext(options: ScriptCodegenOptions) {
scriptSetupGeneratedOffset: undefined as number | undefined,
bypassDefineComponent: options.lang === 'js' || options.lang === 'jsx',
bindingNames: new Set([
...options.scriptRanges?.bindings.map(range => options.sfc.script!.content.slice(range.start, range.end)) ?? [],
...options.scriptSetupRanges?.bindings.map(range => options.sfc.scriptSetup!.content.slice(range.start, range.end)) ?? [],
...options.scriptRanges?.bindings.map(
({ range }) => options.sfc.script!.content.slice(range.start, range.end)
) ?? [],
...options.scriptSetupRanges?.bindings.map(
({ range }) => options.sfc.scriptSetup!.content.slice(range.start, range.end)
) ?? [],
]),
localTypes,
inlayHints,
Expand Down
106 changes: 63 additions & 43 deletions packages/language-core/lib/parsers/scriptSetupRanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ export function parseScriptSetupRanges(
const definePropProposalA = vueCompilerOptions.experimentalDefinePropProposal === 'kevinEdition' || ast.text.trimStart().startsWith('// @experimentalDefinePropProposal=kevinEdition');
const definePropProposalB = vueCompilerOptions.experimentalDefinePropProposal === 'johnsonEdition' || ast.text.trimStart().startsWith('// @experimentalDefinePropProposal=johnsonEdition');
const text = ast.text;
const importComponentNames = new Set<string>();

const leadingCommentRanges = ts.getLeadingCommentRanges(text, 0)?.reverse() ?? [];
const leadingCommentEndOffset = leadingCommentRanges.find(
Expand Down Expand Up @@ -114,22 +113,11 @@ export function parseScriptSetupRanges(
}
foundNonImportExportNode = true;
}

if (
ts.isImportDeclaration(node)
&& node.importClause?.name
&& !node.importClause.isTypeOnly
) {
const moduleName = _getNodeText(node.moduleSpecifier).slice(1, -1);
if (vueCompilerOptions.extensions.some(ext => moduleName.endsWith(ext))) {
importComponentNames.add(_getNodeText(node.importClause.name));
}
}
});
ts.forEachChild(ast, child => visitNode(child, [ast]));
ts.forEachChild(ast, node => visitNode(node, [ast]));

const templateRefNames = new Set(useTemplateRef.map(ref => ref.name));
bindings = bindings.filter(range => {
bindings = bindings.filter(({ range }) => {
const name = text.slice(range.start, range.end);
return !templateRefNames.has(name);
});
Expand All @@ -138,7 +126,6 @@ export function parseScriptSetupRanges(
leadingCommentEndOffset,
importSectionEndOffset,
bindings,
importComponentNames,
defineProp,
defineProps,
withDefaults,
Expand Down Expand Up @@ -433,72 +420,105 @@ export function parseScriptSetupRanges(
}
}

export function parseBindingRanges(ts: typeof import('typescript'), sourceFile: ts.SourceFile) {
const bindings: TextRange[] = [];
ts.forEachChild(sourceFile, node => {
export function parseBindingRanges(ts: typeof import('typescript'), ast: ts.SourceFile) {
const bindings: {
range: TextRange;
moduleName?: string;
isDefaultImport?: boolean;
isNamespace?: boolean;
}[] = [];

ts.forEachChild(ast, node => {
if (ts.isVariableStatement(node)) {
for (const decl of node.declarationList.declarations) {
const vars = _findBindingVars(decl.name);
bindings.push(...vars);
bindings.push(...vars.map((range) => ({ range })));
}
}
else if (ts.isFunctionDeclaration(node)) {
if (node.name && ts.isIdentifier(node.name)) {
bindings.push(_getStartEnd(node.name));
bindings.push({
range: _getStartEnd(node.name)
});
}
}
else if (ts.isClassDeclaration(node)) {
if (node.name) {
bindings.push(_getStartEnd(node.name));
bindings.push({
range: _getStartEnd(node.name)
});
}
}
else if (ts.isEnumDeclaration(node)) {
bindings.push(_getStartEnd(node.name));
bindings.push({
range: _getStartEnd(node.name)
});
}

if (ts.isImportDeclaration(node)) {
const moduleName = _getNodeText(node.moduleSpecifier).slice(1, -1);

if (node.importClause && !node.importClause.isTypeOnly) {
if (node.importClause.name) {
bindings.push(_getStartEnd(node.importClause.name));
const { name, namedBindings } = node.importClause;

if (name) {
bindings.push({
range: _getStartEnd(name),
moduleName,
isDefaultImport: true
});
}
if (node.importClause.namedBindings) {
if (ts.isNamedImports(node.importClause.namedBindings)) {
for (const element of node.importClause.namedBindings.elements) {
if (namedBindings) {
if (ts.isNamedImports(namedBindings)) {
for (const element of namedBindings.elements) {
if (element.isTypeOnly) {
continue;
}
bindings.push(_getStartEnd(element.name));
bindings.push({
range: _getStartEnd(element.name),
moduleName,
isDefaultImport: element.propertyName?.text === 'default'
});
}
}
else if (ts.isNamespaceImport(node.importClause.namedBindings)) {
bindings.push(_getStartEnd(node.importClause.namedBindings.name));
else {
bindings.push({
range: _getStartEnd(namedBindings.name),
moduleName,
isNamespace: true
});
}
}
}
}
});

return bindings;

function _getStartEnd(node: ts.Node) {
return getStartEnd(ts, node, sourceFile);
return getStartEnd(ts, node, ast);
}

function _getNodeText(node: ts.Node) {
return getNodeText(ts, node, ast);
}

function _findBindingVars(left: ts.BindingName) {
return findBindingVars(ts, left, sourceFile);
return findBindingVars(ts, left, ast);
}
}

export function findBindingVars(
ts: typeof import('typescript'),
left: ts.BindingName,
sourceFile: ts.SourceFile
ast: ts.SourceFile
) {
const vars: TextRange[] = [];
worker(left);
return vars;
function worker(node: ts.Node) {
if (ts.isIdentifier(node)) {
vars.push(getStartEnd(ts, node, sourceFile));
vars.push(getStartEnd(ts, node, ast));
}
// { ? } = ...
// [ ? ] = ...
Expand All @@ -515,7 +535,7 @@ export function findBindingVars(
}
// { foo } = ...
else if (ts.isShorthandPropertyAssignment(node)) {
vars.push(getStartEnd(ts, node.name, sourceFile));
vars.push(getStartEnd(ts, node.name, ast));
}
// { ...? } = ...
// [ ...? ] = ...
Expand All @@ -528,43 +548,43 @@ export function findBindingVars(
export function getStartEnd(
ts: typeof import('typescript'),
node: ts.Node,
sourceFile: ts.SourceFile
ast: ts.SourceFile
): TextRange {
return {
start: (ts as any).getTokenPosOfNode(node, sourceFile) as number,
start: (ts as any).getTokenPosOfNode(node, ast) as number,
end: node.end,
};
}

export function getNodeText(
ts: typeof import('typescript'),
node: ts.Node,
sourceFile: ts.SourceFile
ast: ts.SourceFile
) {
const { start, end } = getStartEnd(ts, node, sourceFile);
return sourceFile.text.slice(start, end);
const { start, end } = getStartEnd(ts, node, ast);
return ast.text.slice(start, end);
}

function getStatementRange(
ts: typeof import('typescript'),
parents: ts.Node[],
node: ts.Node,
sourceFile: ts.SourceFile
ast: ts.SourceFile
) {
let statementRange: TextRange | undefined;
for (let i = parents.length - 1; i >= 0; i--) {
if (ts.isStatement(parents[i])) {
const statement = parents[i];
ts.forEachChild(statement, child => {
const range = getStartEnd(ts, child, sourceFile);
const range = getStartEnd(ts, child, ast);
statementRange ??= range;
statementRange.end = range.end;
});
break;
}
}
if (!statementRange) {
statementRange = getStartEnd(ts, node, sourceFile);
statementRange = getStartEnd(ts, node, ast);
}
return statementRange;
}
19 changes: 16 additions & 3 deletions packages/language-core/lib/plugins/vue-tsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,29 @@ function createTsx(
const newNames = new Set<string>();
const bindings = scriptSetupRanges.get()?.bindings;
if (_sfc.scriptSetup && bindings) {
for (const binding of bindings) {
newNames.add(_sfc.scriptSetup?.content.slice(binding.start, binding.end));
for (const { range } of bindings) {
newNames.add(_sfc.scriptSetup.content.slice(range.start, range.end));
}
}
return newNames;
})
);
const scriptSetupImportComponentNames = Unstable.computedSet(
computed(() => {
const newNames = scriptSetupRanges.get()?.importComponentNames ?? new Set();
const newNames = new Set<string>();
const bindings = scriptSetupRanges.get()?.bindings;
if (_sfc.scriptSetup && bindings) {
for (const { range, moduleName, isDefaultImport, isNamespace } of bindings) {
if (
moduleName
&& isDefaultImport
&& !isNamespace
&& ctx.vueCompilerOptions.extensions.some(ext => moduleName.endsWith(ext))
) {
newNames.add(_sfc.scriptSetup.content.slice(range.start, range.end));
}
}
}
return newNames;
})
);
Expand Down
10 changes: 7 additions & 3 deletions packages/language-service/lib/plugins/vue-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ export function create(
}

for (const binding of scriptSetupRanges?.bindings ?? []) {
const name = vueCode._sfc.scriptSetup!.content.slice(binding.start, binding.end);
const name = vueCode._sfc.scriptSetup!.content.slice(binding.range.start, binding.range.end);
if (casing.tag === TagNameCasing.Kebab) {
names.add(hyphenateTag(name));
}
Expand Down Expand Up @@ -568,8 +568,12 @@ export function create(
return [];
}
let ctxVars = [
..._tsCodegen.scriptRanges.get()?.bindings.map(binding => vueCode._sfc.script!.content.slice(binding.start, binding.end)) ?? [],
..._tsCodegen.scriptSetupRanges.get()?.bindings.map(binding => vueCode._sfc.scriptSetup!.content.slice(binding.start, binding.end)) ?? [],
..._tsCodegen.scriptRanges.get()?.bindings.map(
({ range }) => vueCode._sfc.script!.content.slice(range.start, range.end)
) ?? [],
..._tsCodegen.scriptSetupRanges.get()?.bindings.map(
({ range }) => vueCode._sfc.scriptSetup!.content.slice(range.start, range.end)
) ?? [],
...templateContextProps,
];
ctxVars = [...new Set(ctxVars)];
Expand Down
Loading