From ad6db7ff3bf5b1cbeb497939c4c1563eaa8df4ca Mon Sep 17 00:00:00 2001 From: so1ve Date: Mon, 6 May 2024 16:02:44 +0800 Subject: [PATCH 01/17] init --- packages/language-core/lib/codegen/common.ts | 2 +- .../lib/codegen/script/context.ts | 5 +- .../lib/codegen/script/internalComponent.ts | 4 +- .../lib/codegen/template/interpolation.ts | 2 +- .../language-core/lib/parsers/scriptRanges.ts | 6 +- .../lib/parsers/scriptSetupRanges.ts | 108 +------- .../language-core/lib/utils/parseBindings.ts | 239 ++++++++++++++++++ .../lib/plugins/vue-template.ts | 6 +- 8 files changed, 255 insertions(+), 117 deletions(-) create mode 100644 packages/language-core/lib/utils/parseBindings.ts diff --git a/packages/language-core/lib/codegen/common.ts b/packages/language-core/lib/codegen/common.ts index 58389a1931..1869f634b8 100644 --- a/packages/language-core/lib/codegen/common.ts +++ b/packages/language-core/lib/codegen/common.ts @@ -1,5 +1,5 @@ import type * as ts from 'typescript'; -import { getNodeText } from '../parsers/scriptSetupRanges'; +import { getNodeText } from '../utils/parseBindings'; import type { Code, SfcBlock, VueCodeInformation } from '../types'; export const newLine = '\n'; diff --git a/packages/language-core/lib/codegen/script/context.ts b/packages/language-core/lib/codegen/script/context.ts index a78d05a82d..9d3960002f 100644 --- a/packages/language-core/lib/codegen/script/context.ts +++ b/packages/language-core/lib/codegen/script/context.ts @@ -108,9 +108,10 @@ export function createScriptCodegenContext(options: ScriptCodegenOptions) { generatedPropsType: false, scriptSetupGeneratedOffset: undefined as number | undefined, bypassDefineComponent: options.lang === 'js' || options.lang === 'jsx', + // TODO: maybe not using substring to get names? bindingNames: new Set([ - ...options.scriptRanges?.bindings.map(range => options.sfc.script!.content.substring(range.start, range.end)) ?? [], - ...options.scriptSetupRanges?.bindings.map(range => options.sfc.scriptSetup!.content.substring(range.start, range.end)) ?? [], + ...options.scriptRanges?.bindings.bindingRanges.map(range => options.sfc.script!.content.substring(range.start, range.end)) ?? [], + ...options.scriptSetupRanges?.bindings.bindingRanges.map(range => options.sfc.scriptSetup!.content.substring(range.start, range.end)) ?? [], ]), helperTypes, generateHelperTypes, diff --git a/packages/language-core/lib/codegen/script/internalComponent.ts b/packages/language-core/lib/codegen/script/internalComponent.ts index c5a0d600e1..85788cc703 100644 --- a/packages/language-core/lib/codegen/script/internalComponent.ts +++ b/packages/language-core/lib/codegen/script/internalComponent.ts @@ -21,9 +21,9 @@ export function* generateInternalComponent( // bindings const templateUsageVars = getTemplateUsageVars(options, ctx); for (const [content, bindings] of [ - [options.sfc.scriptSetup.content, options.scriptSetupRanges.bindings] as const, + [options.sfc.scriptSetup.content, options.scriptSetupRanges.bindings.bindingRanges] as const, options.sfc.script && options.scriptRanges - ? [options.sfc.script.content, options.scriptRanges.bindings] as const + ? [options.sfc.script.content, options.scriptRanges.bindings.bindingRanges] as const : ['', []] as const, ]) { for (const expose of bindings) { diff --git a/packages/language-core/lib/codegen/template/interpolation.ts b/packages/language-core/lib/codegen/template/interpolation.ts index 6f90c64757..f349f03c55 100644 --- a/packages/language-core/lib/codegen/template/interpolation.ts +++ b/packages/language-core/lib/codegen/template/interpolation.ts @@ -1,6 +1,6 @@ import { isGloballyWhitelisted } from '@vue/shared'; import type * as ts from 'typescript'; -import { getNodeText, getStartEnd } from '../../parsers/scriptSetupRanges'; +import { getNodeText, getStartEnd } from '../../utils/parseBindings'; import type { Code, VueCodeInformation, VueCompilerOptions } from '../../types'; import { collectVars, createTsAst } from '../common'; import type { TemplateCodegenContext } from './context'; diff --git a/packages/language-core/lib/parsers/scriptRanges.ts b/packages/language-core/lib/parsers/scriptRanges.ts index c31ca6e161..085879c1e5 100644 --- a/packages/language-core/lib/parsers/scriptRanges.ts +++ b/packages/language-core/lib/parsers/scriptRanges.ts @@ -1,6 +1,6 @@ import type { TextRange } from '../types'; import type * as ts from 'typescript'; -import { getNodeText, getStartEnd, parseBindingRanges } from './scriptSetupRanges'; +import { BindingTypes, getNodeText, getStartEnd, parseBindings } from '../utils/parseBindings'; export interface ScriptRanges extends ReturnType { } @@ -15,7 +15,9 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc nameOption: TextRange | undefined, }) | undefined; - const bindings = hasScriptSetup ? parseBindingRanges(ts, ast) : []; + const bindings = hasScriptSetup + ? parseBindings(ts, ast) + : { bindingRanges: [], bindingTypes: new Map() }; ts.forEachChild(ast, raw => { diff --git a/packages/language-core/lib/parsers/scriptSetupRanges.ts b/packages/language-core/lib/parsers/scriptSetupRanges.ts index e332edccc3..8f34d3bf00 100644 --- a/packages/language-core/lib/parsers/scriptSetupRanges.ts +++ b/packages/language-core/lib/parsers/scriptSetupRanges.ts @@ -1,5 +1,6 @@ import type * as ts from 'typescript'; import type { VueCompilerOptions, TextRange } from '../types'; +import { getNodeText, getStartEnd, parseBindings } from '../utils/parseBindings'; export interface ScriptSetupRanges extends ReturnType { } @@ -45,7 +46,7 @@ export function parseScriptSetupRanges( required: boolean; isModel?: boolean; }[] = []; - const bindings = parseBindingRanges(ts, ast); + const bindings = parseBindings(ts, ast); const text = ast.text; const leadingCommentEndOffset = ts.getLeadingCommentRanges(text, 0)?.reverse()[0].end ?? 0; @@ -248,108 +249,3 @@ export function parseScriptSetupRanges( }); } } - -export function parseBindingRanges(ts: typeof import('typescript'), sourceFile: ts.SourceFile) { - const bindings: TextRange[] = []; - ts.forEachChild(sourceFile, node => { - if (ts.isVariableStatement(node)) { - for (const node_2 of node.declarationList.declarations) { - const vars = _findBindingVars(node_2.name); - for (const _var of vars) { - bindings.push(_var); - } - } - } - else if (ts.isFunctionDeclaration(node)) { - if (node.name && ts.isIdentifier(node.name)) { - bindings.push(_getStartEnd(node.name)); - } - } - else if (ts.isClassDeclaration(node)) { - if (node.name) { - bindings.push(_getStartEnd(node.name)); - } - } - else if (ts.isEnumDeclaration(node)) { - bindings.push(_getStartEnd(node.name)); - } - - if (ts.isImportDeclaration(node)) { - if (node.importClause && !node.importClause.isTypeOnly) { - if (node.importClause.name) { - bindings.push(_getStartEnd(node.importClause.name)); - } - if (node.importClause.namedBindings) { - if (ts.isNamedImports(node.importClause.namedBindings)) { - for (const element of node.importClause.namedBindings.elements) { - bindings.push(_getStartEnd(element.name)); - } - } - else if (ts.isNamespaceImport(node.importClause.namedBindings)) { - bindings.push(_getStartEnd(node.importClause.namedBindings.name)); - } - } - } - } - }); - return bindings; - function _getStartEnd(node: ts.Node) { - return getStartEnd(ts, node, sourceFile); - } - function _findBindingVars(left: ts.BindingName) { - return findBindingVars(ts, left, sourceFile); - } -} - -export function findBindingVars(ts: typeof import('typescript'), left: ts.BindingName, sourceFile: ts.SourceFile) { - const vars: TextRange[] = []; - worker(left); - return vars; - function worker(_node: ts.Node) { - if (ts.isIdentifier(_node)) { - vars.push(getStartEnd(ts, _node, sourceFile)); - } - // { ? } = ... - // [ ? ] = ... - else if (ts.isObjectBindingPattern(_node) || ts.isArrayBindingPattern(_node)) { - for (const property of _node.elements) { - if (ts.isBindingElement(property)) { - worker(property.name); - } - } - } - // { foo: ? } = ... - else if (ts.isPropertyAssignment(_node)) { - worker(_node.initializer); - } - // { foo } = ... - else if (ts.isShorthandPropertyAssignment(_node)) { - vars.push(getStartEnd(ts, _node.name, sourceFile)); - } - // { ...? } = ... - // [ ...? ] = ... - else if (ts.isSpreadAssignment(_node) || ts.isSpreadElement(_node)) { - worker(_node.expression); - } - } -} - -export function getStartEnd( - ts: typeof import('typescript'), - node: ts.Node, - sourceFile: ts.SourceFile -) { - return { - start: (ts as any).getTokenPosOfNode(node, sourceFile) as number, - end: node.end, - }; -} - -export function getNodeText( - ts: typeof import('typescript'), - node: ts.Node, - sourceFile: ts.SourceFile -) { - const { start, end } = getStartEnd(ts, node, sourceFile); - return sourceFile.text.substring(start, end); -} diff --git a/packages/language-core/lib/utils/parseBindings.ts b/packages/language-core/lib/utils/parseBindings.ts new file mode 100644 index 0000000000..59b49cb1ee --- /dev/null +++ b/packages/language-core/lib/utils/parseBindings.ts @@ -0,0 +1,239 @@ +import type * as ts from 'typescript'; +import type { TextRange } from '../types'; + +export enum BindingTypes { + NoUnref, + NeedUnref, + DirectAccess, +} + +export function parseBindings(ts: typeof import('typescript'), sourceFile: ts.SourceFile) { + const bindingRanges: TextRange[] = []; + // `bindingTypes` may include some bindings that are not in `bindingRanges`, such as `foo` in `defineProps({ foo: Number })` + const bindingTypes = new Map(); + + ts.forEachChild(sourceFile, node => { + if (ts.isVariableStatement(node)) { + for (const decl of node.declarationList.declarations) { + worker(decl.name, true); + + function worker(_node: ts.Node, root = false) { + if (ts.isIdentifier(_node)) { + const nodeText = _getNodeText(_node); + bindingRanges.push(_getStartEnd(_node)); + + if (root) { + if (decl.initializer && ts.isCallExpression(decl.initializer)) { + const callText = _getNodeText(decl.initializer.expression); + if (callText === 'ref') { + bindingTypes.set(nodeText, BindingTypes.NeedUnref); + } + // TODO: use vue compiler options + else if (callText === 'defineProps') { + bindingTypes.set(nodeText, BindingTypes.DirectAccess); + if (decl.initializer.typeArguments?.length === 1) { + const typeNode = decl.initializer.typeArguments[0]; + if (ts.isTypeLiteralNode(typeNode)) { + for (const prop of typeNode.members) { + if (ts.isPropertySignature(prop)) { + bindingTypes.set(prop.name.getText(), BindingTypes.NoUnref); + } + } + } + } + else if (decl.initializer.arguments.length === 1) { + const arg = decl.initializer.arguments[0]; + if (ts.isObjectLiteralExpression(arg)) { + for (const prop of arg.properties) { + if (ts.isPropertyAssignment(prop)) { + bindingTypes.set(prop.name.getText(), BindingTypes.NoUnref); + } + } + } + else if (ts.isArrayLiteralExpression(arg)) { + for (const prop of arg.elements) { + if (ts.isStringLiteral(prop)) { + bindingTypes.set(prop.text, BindingTypes.NoUnref); + } + } + } + } + } + } + else { + bindingTypes.set(nodeText, BindingTypes.NeedUnref); + } + } + } + // { ? } = ... + // [ ? ] = ... + else if (ts.isObjectBindingPattern(_node) || ts.isArrayBindingPattern(_node)) { + for (const property of _node.elements) { + if (ts.isBindingElement(property)) { + worker(property.name); + } + } + } + // { foo: ? } = ... + else if (ts.isPropertyAssignment(_node)) { + worker(_node.initializer); + } + // { foo } = ... + else if (ts.isShorthandPropertyAssignment(_node)) { + const nodeText = _getNodeText(_node.name); + bindingRanges.push(_getStartEnd(_node.name)); + bindingTypes.set(nodeText, BindingTypes.NeedUnref); + } + // { ...? } = ... + // [ ...? ] = ... + else if (ts.isSpreadAssignment(_node) || ts.isSpreadElement(_node)) { + worker(_node.expression); + } + } + } + } + else if (ts.isFunctionDeclaration(node)) { + if (node.name && ts.isIdentifier(node.name)) { + const nodeText = _getNodeText(node.name); + bindingRanges.push(_getStartEnd(node.name)); + bindingTypes.set(nodeText, BindingTypes.NoUnref); + } + } + else if (ts.isClassDeclaration(node)) { + if (node.name) { + const nodeText = _getNodeText(node.name); + bindingRanges.push(_getStartEnd(node.name)); + bindingTypes.set(nodeText, BindingTypes.NoUnref); + } + } + else if (ts.isEnumDeclaration(node)) { + const nodeText = _getNodeText(node.name); + bindingRanges.push(_getStartEnd(node.name)); + bindingTypes.set(nodeText, BindingTypes.NoUnref); + } + + if (ts.isImportDeclaration(node)) { + if (node.importClause && !node.importClause.isTypeOnly) { + if (node.importClause.name) { + const nodeText = _getNodeText(node.importClause.name); + bindingRanges.push(_getStartEnd(node.importClause.name)); + bindingTypes.set(nodeText, BindingTypes.NeedUnref); + } + if (node.importClause.namedBindings) { + if (ts.isNamedImports(node.importClause.namedBindings)) { + for (const element of node.importClause.namedBindings.elements) { + const nodeText = _getNodeText(element.name); + bindingRanges.push(_getStartEnd(element.name)); + bindingTypes.set(nodeText, BindingTypes.NeedUnref); + } + } + else if (ts.isNamespaceImport(node.importClause.namedBindings)) { + const nodeText = _getNodeText(node.importClause.namedBindings.name); + bindingRanges.push(_getStartEnd(node.importClause.namedBindings.name)); + bindingTypes.set(nodeText, BindingTypes.NoUnref); + } + } + } + } + }); + return { bindingRanges, bindingTypes }; + + function _getStartEnd(node: ts.Node) { + return getStartEnd(ts, node, sourceFile); + } + function _getNodeText(node: ts.Node) { + return getNodeText(ts, node, sourceFile); + } +} + + +export function getStartEnd( + ts: typeof import('typescript'), + node: ts.Node, + sourceFile: ts.SourceFile +) { + return { + start: (ts as any).getTokenPosOfNode(node, sourceFile) as number, + end: node.end, + }; +} + +export function getNodeText( + ts: typeof import('typescript'), + node: ts.Node, + sourceFile: ts.SourceFile +) { + const { start, end } = getStartEnd(ts, node, sourceFile); + return sourceFile.text.substring(start, end); +} + +// export function analyzeBindings( +// ts: typeof import('typescript'), +// ast: ts.SourceFile, +// vueCompilerOptions: VueCompilerOptions +// ) { +// const bindings = new Map(); + +// function worker(node: ts.Node) { +// if (ts.isVariableDeclaration(node)) { +// if (ts.isIdentifier(node.name)) { +// if (!node.initializer) { +// bindings.set(node.name.text, BindingTypes.NeedUnref); +// } +// else { +// if (ts.isCallExpression(node.initializer)) { +// const callText = node.initializer.expression.getText(); +// if (vueCompilerOptions.macros.defineProps.includes(callText)) { +// if (node.initializer.typeArguments?.length === 1) { +// const typeNode = node.initializer.typeArguments[0]; +// if (ts.isTypeLiteralNode(typeNode)) { +// for (const prop of typeNode.members) { +// if (ts.isPropertySignature(prop)) { +// bindings.set(prop.name.getText(), BindingTypes.NoUnref); +// } +// } +// } +// } +// else if (node.initializer.arguments.length === 1) { +// const arg = node.initializer.arguments[0]; +// if (ts.isObjectLiteralExpression(arg)) { +// for (const prop of arg.properties) { +// if (ts.isPropertyAssignment(prop)) { +// bindings.set(prop.name.getText(), BindingTypes.NoUnref); +// } +// } +// } +// else if (ts.isArrayLiteralExpression(arg)) { +// for (const prop of arg.elements) { +// if (ts.isStringLiteral(prop)) { +// bindings.set(prop.text, BindingTypes.NoUnref); +// } +// } +// } +// } +// } +// else if (callText === 'ref') { +// bindings.set(node.name.text, BindingTypes.NeedUnref); +// } +// } +// } +// } +// } +// else { +// ts.forEachChild(node, worker); +// } +// } + +// worker(ast); + +// return bindings; +// }; + +// const ast = ts.createSourceFile('a.ts', ` +// const a = ref() +// const b = defineProps({ +// foo: Number +// }) +// `, 99); + +// console.log(analyzeBindings(ts, ast, resolveVueCompilerOptions({}))); diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index 50aa71449e..6829a6122c 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -451,7 +451,7 @@ export function create( } } - for (const binding of scriptSetupRanges?.bindings ?? []) { + for (const binding of scriptSetupRanges?.bindings.bindingRanges ?? []) { const name = vueCode.sfc.scriptSetup!.content.substring(binding.start, binding.end); if (casing.tag === TagNameCasing.Kebab) { names.add(hyphenateTag(name)); @@ -501,8 +501,8 @@ export function create( return []; } let ctxVars = [ - ..._tsCodegen.scriptRanges()?.bindings.map(binding => vueCode.sfc.script!.content.substring(binding.start, binding.end)) ?? [], - ..._tsCodegen.scriptSetupRanges()?.bindings.map(binding => vueCode.sfc.scriptSetup!.content.substring(binding.start, binding.end)) ?? [], + ...[..._tsCodegen.scriptRanges()?.bindings.bindingRanges.values() ?? []].map(binding => vueCode.sfc.script!.content.substring(binding.start, binding.end)) ?? [], + ...[..._tsCodegen.scriptSetupRanges()?.bindings.bindingRanges.values() ?? []].map(binding => vueCode.sfc.scriptSetup!.content.substring(binding.start, binding.end)) ?? [], ...templateContextProps, ]; ctxVars = [...new Set(ctxVars)]; From 6bb8018ea873bb6e0870bf1905f373fe766a56a9 Mon Sep 17 00:00:00 2001 From: so1ve Date: Mon, 6 May 2024 16:17:49 +0800 Subject: [PATCH 02/17] spetial treatment for `.vue` default imports --- packages/language-core/lib/utils/parseBindings.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/language-core/lib/utils/parseBindings.ts b/packages/language-core/lib/utils/parseBindings.ts index 59b49cb1ee..ec439dab27 100644 --- a/packages/language-core/lib/utils/parseBindings.ts +++ b/packages/language-core/lib/utils/parseBindings.ts @@ -36,7 +36,7 @@ export function parseBindings(ts: typeof import('typescript'), sourceFile: ts.So if (ts.isTypeLiteralNode(typeNode)) { for (const prop of typeNode.members) { if (ts.isPropertySignature(prop)) { - bindingTypes.set(prop.name.getText(), BindingTypes.NoUnref); + bindingTypes.set(_getNodeText(prop.name), BindingTypes.NoUnref); } } } @@ -46,7 +46,7 @@ export function parseBindings(ts: typeof import('typescript'), sourceFile: ts.So if (ts.isObjectLiteralExpression(arg)) { for (const prop of arg.properties) { if (ts.isPropertyAssignment(prop)) { - bindingTypes.set(prop.name.getText(), BindingTypes.NoUnref); + bindingTypes.set(_getNodeText(prop.name), BindingTypes.NoUnref); } } } @@ -117,7 +117,12 @@ export function parseBindings(ts: typeof import('typescript'), sourceFile: ts.So if (node.importClause.name) { const nodeText = _getNodeText(node.importClause.name); bindingRanges.push(_getStartEnd(node.importClause.name)); - bindingTypes.set(nodeText, BindingTypes.NeedUnref); + if (ts.isStringLiteral(node.moduleSpecifier) && _getNodeText(node.moduleSpecifier).endsWith('.vue')) { + bindingTypes.set(nodeText, BindingTypes.NoUnref); + } + else { + bindingTypes.set(nodeText, BindingTypes.NeedUnref); + } } if (node.importClause.namedBindings) { if (ts.isNamedImports(node.importClause.namedBindings)) { @@ -189,7 +194,7 @@ export function getNodeText( // if (ts.isTypeLiteralNode(typeNode)) { // for (const prop of typeNode.members) { // if (ts.isPropertySignature(prop)) { -// bindings.set(prop.name.getText(), BindingTypes.NoUnref); +// bindings.set(_getNodeText(prop.name), BindingTypes.NoUnref); // } // } // } @@ -199,7 +204,7 @@ export function getNodeText( // if (ts.isObjectLiteralExpression(arg)) { // for (const prop of arg.properties) { // if (ts.isPropertyAssignment(prop)) { -// bindings.set(prop.name.getText(), BindingTypes.NoUnref); +// bindings.set(_getNodeText(prop.name), BindingTypes.NoUnref); // } // } // } From 915177d5a486b58820e536f3e907e75b7fd119b6 Mon Sep 17 00:00:00 2001 From: so1ve Date: Mon, 6 May 2024 16:19:31 +0800 Subject: [PATCH 03/17] remove test code --- .../language-core/lib/utils/parseBindings.ts | 71 ------------------- 1 file changed, 71 deletions(-) diff --git a/packages/language-core/lib/utils/parseBindings.ts b/packages/language-core/lib/utils/parseBindings.ts index ec439dab27..3901107fd3 100644 --- a/packages/language-core/lib/utils/parseBindings.ts +++ b/packages/language-core/lib/utils/parseBindings.ts @@ -171,74 +171,3 @@ export function getNodeText( const { start, end } = getStartEnd(ts, node, sourceFile); return sourceFile.text.substring(start, end); } - -// export function analyzeBindings( -// ts: typeof import('typescript'), -// ast: ts.SourceFile, -// vueCompilerOptions: VueCompilerOptions -// ) { -// const bindings = new Map(); - -// function worker(node: ts.Node) { -// if (ts.isVariableDeclaration(node)) { -// if (ts.isIdentifier(node.name)) { -// if (!node.initializer) { -// bindings.set(node.name.text, BindingTypes.NeedUnref); -// } -// else { -// if (ts.isCallExpression(node.initializer)) { -// const callText = node.initializer.expression.getText(); -// if (vueCompilerOptions.macros.defineProps.includes(callText)) { -// if (node.initializer.typeArguments?.length === 1) { -// const typeNode = node.initializer.typeArguments[0]; -// if (ts.isTypeLiteralNode(typeNode)) { -// for (const prop of typeNode.members) { -// if (ts.isPropertySignature(prop)) { -// bindings.set(_getNodeText(prop.name), BindingTypes.NoUnref); -// } -// } -// } -// } -// else if (node.initializer.arguments.length === 1) { -// const arg = node.initializer.arguments[0]; -// if (ts.isObjectLiteralExpression(arg)) { -// for (const prop of arg.properties) { -// if (ts.isPropertyAssignment(prop)) { -// bindings.set(_getNodeText(prop.name), BindingTypes.NoUnref); -// } -// } -// } -// else if (ts.isArrayLiteralExpression(arg)) { -// for (const prop of arg.elements) { -// if (ts.isStringLiteral(prop)) { -// bindings.set(prop.text, BindingTypes.NoUnref); -// } -// } -// } -// } -// } -// else if (callText === 'ref') { -// bindings.set(node.name.text, BindingTypes.NeedUnref); -// } -// } -// } -// } -// } -// else { -// ts.forEachChild(node, worker); -// } -// } - -// worker(ast); - -// return bindings; -// }; - -// const ast = ts.createSourceFile('a.ts', ` -// const a = ref() -// const b = defineProps({ -// foo: Number -// }) -// `, 99); - -// console.log(analyzeBindings(ts, ast, resolveVueCompilerOptions({}))); From 2b358498017d83c3fdb08e47e3e82f61dbfdfdac Mon Sep 17 00:00:00 2001 From: so1ve Date: Mon, 6 May 2024 17:10:28 +0800 Subject: [PATCH 04/17] wip --- .../lib/codegen/template/context.ts | 8 ++++ .../lib/codegen/template/index.ts | 5 +++ .../lib/codegen/template/interpolation.ts | 9 +++-- packages/language-core/lib/plugins/vue-tsx.ts | 2 + .../language-core/lib/utils/parseBindings.ts | 37 ++++++++++++++++++- 5 files changed, 56 insertions(+), 5 deletions(-) diff --git a/packages/language-core/lib/codegen/template/context.ts b/packages/language-core/lib/codegen/template/context.ts index e8673496f2..bb0911a240 100644 --- a/packages/language-core/lib/codegen/template/context.ts +++ b/packages/language-core/lib/codegen/template/context.ts @@ -1,5 +1,6 @@ import type * as CompilerDOM from '@vue/compiler-dom'; import type { Code, VueCodeInformation } from '../../types'; +import type { BindingTypes } from '../../utils/parseBindings'; import { endOfLine, newLine, wrapWith } from '../common'; const _codeFeatures = { @@ -96,6 +97,7 @@ export function createTemplateCodegenContext() { const blockConditions: string[] = []; const usedComponentCtxVars = new Set(); const scopedClasses: { className: string, offset: number; }[] = []; + let bindingTypes: Map | undefined; return { slots, @@ -106,6 +108,9 @@ export function createTemplateCodegenContext() { blockConditions, usedComponentCtxVars, scopedClasses, + get bindingTypes() { + return bindingTypes; + }, accessGlobalVariable(name: string, offset?: number) { let arr = accessGlobalVariables.get(name); if (!arr) { @@ -127,6 +132,9 @@ export function createTemplateCodegenContext() { getInternalVariable: () => { return `__VLS_${variableId++}`; }, + setBindingTypes: (types: Map) => { + bindingTypes = types; + }, ignoreError: function* (): Generator { if (!ignoredError) { ignoredError = true; diff --git a/packages/language-core/lib/codegen/template/index.ts b/packages/language-core/lib/codegen/template/index.ts index 413122413b..a876cbffca 100644 --- a/packages/language-core/lib/codegen/template/index.ts +++ b/packages/language-core/lib/codegen/template/index.ts @@ -1,6 +1,7 @@ import * as CompilerDOM from '@vue/compiler-dom'; import type * as ts from 'typescript'; import type { Code, Sfc, VueCompilerOptions } from '../../types'; +import type { BindingTypes } from '../../utils/parseBindings'; import { endOfLine, newLine, wrapWith } from '../common'; import { createTemplateCodegenContext } from './context'; import { getCanonicalComponentName, getPossibleOriginalComponentNames } from './element'; @@ -18,10 +19,14 @@ export interface TemplateCodegenOptions { hasDefineSlots?: boolean; slotsAssignName?: string; propsAssignName?: string; + bindingTypes?: Map; } export function* generateTemplate(options: TemplateCodegenOptions) { const ctx = createTemplateCodegenContext(); + if (options.bindingTypes) { + ctx.setBindingTypes(options.bindingTypes); + }; let hasSlot = false; diff --git a/packages/language-core/lib/codegen/template/interpolation.ts b/packages/language-core/lib/codegen/template/interpolation.ts index f349f03c55..1c6b5acf1a 100644 --- a/packages/language-core/lib/codegen/template/interpolation.ts +++ b/packages/language-core/lib/codegen/template/interpolation.ts @@ -1,6 +1,6 @@ -import { isGloballyWhitelisted } from '@vue/shared'; +import { isGloballyAllowed } from '@vue/shared'; import type * as ts from 'typescript'; -import { getNodeText, getStartEnd } from '../../utils/parseBindings'; +import { BindingTypes, getNodeText, getStartEnd } from '../../utils/parseBindings'; import type { Code, VueCodeInformation, VueCompilerOptions } from '../../types'; import { collectVars, createTsAst } from '../common'; import type { TemplateCodegenContext } from './context'; @@ -86,10 +86,13 @@ export function* forEachInterpolationSegment( const varCb = (id: ts.Identifier, isShorthand: boolean) => { const text = getNodeText(ts, id, ast); + if (text === 'record') { + console.log('binding:', text, BindingTypes[ctx.bindingTypes!.get(text)!]); + } if ( ctx.hasLocalVariable(text) || // https://github.com/vuejs/core/blob/245230e135152900189f13a4281302de45fdcfaa/packages/compiler-core/src/transforms/transformExpression.ts#L342-L352 - isGloballyWhitelisted(text) || + isGloballyAllowed(text) || text === 'require' || text.startsWith('__VLS_') ) { diff --git a/packages/language-core/lib/plugins/vue-tsx.ts b/packages/language-core/lib/plugins/vue-tsx.ts index db9870a87d..b64fe98cb6 100644 --- a/packages/language-core/lib/plugins/vue-tsx.ts +++ b/packages/language-core/lib/plugins/vue-tsx.ts @@ -122,6 +122,7 @@ function createTsx( hasDefineSlots: hasDefineSlots(), slotsAssignName: slotsAssignName(), propsAssignName: propsAssignName(), + bindingTypes: bindingTypes(), }); let current = codegen.next(); @@ -140,6 +141,7 @@ function createTsx( const hasDefineSlots = computed(() => !!scriptSetupRanges()?.slots.define); const slotsAssignName = computed(() => scriptSetupRanges()?.slots.name); const propsAssignName = computed(() => scriptSetupRanges()?.props.name); + const bindingTypes = computed(() => scriptSetupRanges()?.bindings.bindingTypes ?? scriptRanges()?.bindings.bindingTypes); const generatedScript = computed(() => { const codes: Code[] = []; const linkedCodeMappings: Mapping[] = []; diff --git a/packages/language-core/lib/utils/parseBindings.ts b/packages/language-core/lib/utils/parseBindings.ts index 3901107fd3..c93d5cc7c0 100644 --- a/packages/language-core/lib/utils/parseBindings.ts +++ b/packages/language-core/lib/utils/parseBindings.ts @@ -61,7 +61,18 @@ export function parseBindings(ts: typeof import('typescript'), sourceFile: ts.So } } else { - bindingTypes.set(nodeText, BindingTypes.NeedUnref); + // const a = 1; + if (decl.initializer) { + const innerExpression = getInnerExpression(decl.initializer); + _getNodeText(innerExpression).includes('record') && console.log(_getNodeText(innerExpression)); + if (isLiteral(innerExpression)) { + bindingTypes.set(nodeText, BindingTypes.NoUnref); + } + } + // const a = bar; + else { + bindingTypes.set(nodeText, BindingTypes.NeedUnref); + } } } } @@ -149,9 +160,31 @@ export function parseBindings(ts: typeof import('typescript'), sourceFile: ts.So function _getNodeText(node: ts.Node) { return getNodeText(ts, node, sourceFile); } + function getInnerExpression(node: ts.Node) { + if (isAsExpression(node) || ts.isSatisfiesExpression(node) || ts.isParenthesizedExpression(node)) { + return getInnerExpression(node.expression); + } + else if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.CommaToken) { + return getInnerExpression(node.right); + } + return node; + } + function isLiteral(node: ts.Node) { + return ts.isLiteralExpression(node) + || ts.isArrayLiteralExpression(node) + || ts.isObjectLiteralExpression(node) + || ts.isClassExpression(node) + || ts.isVoidExpression(node) + || ts.isArrowFunction(node) + || ts.isFunctionExpression(node) + || ts.isNewExpression(node); + } + // isAsExpression is missing in tsc + function isAsExpression(node: ts.Node): node is ts.AsExpression { + return node.kind === ts.SyntaxKind.AsExpression; + } } - export function getStartEnd( ts: typeof import('typescript'), node: ts.Node, From 498168714c2857ce06318954233582893636b181 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 6 May 2024 17:23:30 +0800 Subject: [PATCH 05/17] Update vue-template.ts --- packages/language-service/lib/plugins/vue-template.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index 6829a6122c..80b588711d 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -501,8 +501,8 @@ export function create( return []; } let ctxVars = [ - ...[..._tsCodegen.scriptRanges()?.bindings.bindingRanges.values() ?? []].map(binding => vueCode.sfc.script!.content.substring(binding.start, binding.end)) ?? [], - ...[..._tsCodegen.scriptSetupRanges()?.bindings.bindingRanges.values() ?? []].map(binding => vueCode.sfc.scriptSetup!.content.substring(binding.start, binding.end)) ?? [], + ..._tsCodegen.scriptRanges()?.bindings.bindingRange.map(binding => vueCode.sfc.script!.content.substring(binding.start, binding.end)) ?? [], + ..._tsCodegen.scriptSetupRanges()?.bindings.bindingRanges.map(binding => vueCode.sfc.scriptSetup!.content.substring(binding.start, binding.end)) ?? [], ...templateContextProps, ]; ctxVars = [...new Set(ctxVars)]; From 47b276d7327a20372546a8d3e6aeef9f2519dde1 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 6 May 2024 17:24:50 +0800 Subject: [PATCH 06/17] Update vue-template.ts --- packages/language-service/lib/plugins/vue-template.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/language-service/lib/plugins/vue-template.ts b/packages/language-service/lib/plugins/vue-template.ts index 80b588711d..e07c1e84a9 100644 --- a/packages/language-service/lib/plugins/vue-template.ts +++ b/packages/language-service/lib/plugins/vue-template.ts @@ -501,7 +501,7 @@ export function create( return []; } let ctxVars = [ - ..._tsCodegen.scriptRanges()?.bindings.bindingRange.map(binding => vueCode.sfc.script!.content.substring(binding.start, binding.end)) ?? [], + ..._tsCodegen.scriptRanges()?.bindings.bindingRanges.map(binding => vueCode.sfc.script!.content.substring(binding.start, binding.end)) ?? [], ..._tsCodegen.scriptSetupRanges()?.bindings.bindingRanges.map(binding => vueCode.sfc.scriptSetup!.content.substring(binding.start, binding.end)) ?? [], ...templateContextProps, ]; From 9732495c1e49792e64bca0600d9793569673ff8d Mon Sep 17 00:00:00 2001 From: so1ve Date: Tue, 7 May 2024 12:44:47 +0800 Subject: [PATCH 07/17] analyze let --- .../language-core/lib/utils/parseBindings.ts | 97 ++++++++++--------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/packages/language-core/lib/utils/parseBindings.ts b/packages/language-core/lib/utils/parseBindings.ts index c93d5cc7c0..af92a44b4f 100644 --- a/packages/language-core/lib/utils/parseBindings.ts +++ b/packages/language-core/lib/utils/parseBindings.ts @@ -15,65 +15,73 @@ export function parseBindings(ts: typeof import('typescript'), sourceFile: ts.So ts.forEachChild(sourceFile, node => { if (ts.isVariableStatement(node)) { for (const decl of node.declarationList.declarations) { - worker(decl.name, true); + const declList = node.declarationList; + worker(decl.name, true); function worker(_node: ts.Node, root = false) { if (ts.isIdentifier(_node)) { const nodeText = _getNodeText(_node); bindingRanges.push(_getStartEnd(_node)); - if (root) { - if (decl.initializer && ts.isCallExpression(decl.initializer)) { - const callText = _getNodeText(decl.initializer.expression); - if (callText === 'ref') { - bindingTypes.set(nodeText, BindingTypes.NeedUnref); - } - // TODO: use vue compiler options - else if (callText === 'defineProps') { - bindingTypes.set(nodeText, BindingTypes.DirectAccess); - if (decl.initializer.typeArguments?.length === 1) { - const typeNode = decl.initializer.typeArguments[0]; - if (ts.isTypeLiteralNode(typeNode)) { - for (const prop of typeNode.members) { - if (ts.isPropertySignature(prop)) { - bindingTypes.set(_getNodeText(prop.name), BindingTypes.NoUnref); - } - } + if (declList.flags & ts.NodeFlags.Const) { + if (decl.initializer) { + if (ts.isCallExpression(decl.initializer)) { + const callText = _getNodeText(decl.initializer.expression); + if (callText === 'ref') { + bindingTypes.set(nodeText, BindingTypes.NeedUnref); } - } - else if (decl.initializer.arguments.length === 1) { - const arg = decl.initializer.arguments[0]; - if (ts.isObjectLiteralExpression(arg)) { - for (const prop of arg.properties) { - if (ts.isPropertyAssignment(prop)) { - bindingTypes.set(_getNodeText(prop.name), BindingTypes.NoUnref); + // TODO: use vue compiler options + else if (callText === 'defineProps') { + bindingTypes.set(nodeText, BindingTypes.DirectAccess); + if (decl.initializer.typeArguments?.length === 1) { + const typeNode = decl.initializer.typeArguments[0]; + if (ts.isTypeLiteralNode(typeNode)) { + for (const prop of typeNode.members) { + if (ts.isPropertySignature(prop)) { + bindingTypes.set(_getNodeText(prop.name), BindingTypes.NoUnref); + } + } } } - } - else if (ts.isArrayLiteralExpression(arg)) { - for (const prop of arg.elements) { - if (ts.isStringLiteral(prop)) { - bindingTypes.set(prop.text, BindingTypes.NoUnref); + else if (decl.initializer.arguments.length === 1) { + const arg = decl.initializer.arguments[0]; + if (ts.isObjectLiteralExpression(arg)) { + for (const prop of arg.properties) { + if (ts.isPropertyAssignment(prop)) { + bindingTypes.set(_getNodeText(prop.name), BindingTypes.NoUnref); + } + } + } + else if (ts.isArrayLiteralExpression(arg)) { + for (const prop of arg.elements) { + if (ts.isStringLiteral(prop)) { + bindingTypes.set(prop.text, BindingTypes.NoUnref); + } + } } } } } - } - } - else { - // const a = 1; - if (decl.initializer) { - const innerExpression = getInnerExpression(decl.initializer); - _getNodeText(innerExpression).includes('record') && console.log(_getNodeText(innerExpression)); - if (isLiteral(innerExpression)) { - bindingTypes.set(nodeText, BindingTypes.NoUnref); + else { + const innerExpression = getInnerExpression(decl.initializer); + // const a = 1; + if (isLiteral(innerExpression)) { + bindingTypes.set(nodeText, BindingTypes.NoUnref); + } + // const a = bar; + else { + bindingTypes.set(nodeText, BindingTypes.NeedUnref); + } } } - // const a = bar; else { bindingTypes.set(nodeText, BindingTypes.NeedUnref); } } + // let a = 1; + else { + bindingTypes.set(nodeText, BindingTypes.NeedUnref); + } } } // { ? } = ... @@ -170,14 +178,7 @@ export function parseBindings(ts: typeof import('typescript'), sourceFile: ts.So return node; } function isLiteral(node: ts.Node) { - return ts.isLiteralExpression(node) - || ts.isArrayLiteralExpression(node) - || ts.isObjectLiteralExpression(node) - || ts.isClassExpression(node) - || ts.isVoidExpression(node) - || ts.isArrowFunction(node) - || ts.isFunctionExpression(node) - || ts.isNewExpression(node); + return !(ts.isIdentifier(node) || ts.isPropertyAccessExpression(node) || ts.isCallExpression(node)); } // isAsExpression is missing in tsc function isAsExpression(node: ts.Node): node is ts.AsExpression { From ced370f7b6995987aff7e4b188a55d13d004b58f Mon Sep 17 00:00:00 2001 From: so1ve Date: Tue, 7 May 2024 12:50:06 +0800 Subject: [PATCH 08/17] wip --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index d4fa2a380c..3e873b21de 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "private": true, + "packageManager": "pnpm@9.0.0", "scripts": { "build": "tsc -b", "watch": "npm run build && (npm run watch:base & npm run watch:vue)", From 2d41aa8ae30fb803f43d735166c41ea43c8c6aa8 Mon Sep 17 00:00:00 2001 From: so1ve Date: Tue, 7 May 2024 12:52:09 +0800 Subject: [PATCH 09/17] refactor --- .../language-core/lib/utils/parseBindings.ts | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/packages/language-core/lib/utils/parseBindings.ts b/packages/language-core/lib/utils/parseBindings.ts index af92a44b4f..64148d4da7 100644 --- a/packages/language-core/lib/utils/parseBindings.ts +++ b/packages/language-core/lib/utils/parseBindings.ts @@ -111,27 +111,18 @@ export function parseBindings(ts: typeof import('typescript'), sourceFile: ts.So } } } - else if (ts.isFunctionDeclaration(node)) { - if (node.name && ts.isIdentifier(node.name)) { - const nodeText = _getNodeText(node.name); - bindingRanges.push(_getStartEnd(node.name)); - bindingTypes.set(nodeText, BindingTypes.NoUnref); - } - } - else if (ts.isClassDeclaration(node)) { + else if ( + ts.isFunctionDeclaration(node) + || ts.isClassDeclaration(node) + || ts.isEnumDeclaration(node) + ) { if (node.name) { const nodeText = _getNodeText(node.name); bindingRanges.push(_getStartEnd(node.name)); bindingTypes.set(nodeText, BindingTypes.NoUnref); } } - else if (ts.isEnumDeclaration(node)) { - const nodeText = _getNodeText(node.name); - bindingRanges.push(_getStartEnd(node.name)); - bindingTypes.set(nodeText, BindingTypes.NoUnref); - } - - if (ts.isImportDeclaration(node)) { + else if (ts.isImportDeclaration(node)) { if (node.importClause && !node.importClause.isTypeOnly) { if (node.importClause.name) { const nodeText = _getNodeText(node.importClause.name); From f027ce5dce28b4276733e381c2dd27dcae8e1803 Mon Sep 17 00:00:00 2001 From: so1ve Date: Tue, 7 May 2024 12:57:56 +0800 Subject: [PATCH 10/17] refactor --- package.json | 2 +- .../language-core/lib/utils/parseBindings.ts | 20 ++++++++----------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 3e873b21de..0278f0fbb0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "packageManager": "pnpm@9.0.0", + "packageManager": "pnpm@9.1.0", "scripts": { "build": "tsc -b", "watch": "npm run build && (npm run watch:base & npm run watch:vue)", diff --git a/packages/language-core/lib/utils/parseBindings.ts b/packages/language-core/lib/utils/parseBindings.ts index 64148d4da7..08cb9496ef 100644 --- a/packages/language-core/lib/utils/parseBindings.ts +++ b/packages/language-core/lib/utils/parseBindings.ts @@ -99,9 +99,7 @@ export function parseBindings(ts: typeof import('typescript'), sourceFile: ts.So } // { foo } = ... else if (ts.isShorthandPropertyAssignment(_node)) { - const nodeText = _getNodeText(_node.name); - bindingRanges.push(_getStartEnd(_node.name)); - bindingTypes.set(nodeText, BindingTypes.NeedUnref); + addBinding(_node.name, BindingTypes.NeedUnref); } // { ...? } = ... // [ ...? ] = ... @@ -117,9 +115,7 @@ export function parseBindings(ts: typeof import('typescript'), sourceFile: ts.So || ts.isEnumDeclaration(node) ) { if (node.name) { - const nodeText = _getNodeText(node.name); - bindingRanges.push(_getStartEnd(node.name)); - bindingTypes.set(nodeText, BindingTypes.NoUnref); + addBinding(node.name, BindingTypes.NoUnref); } } else if (ts.isImportDeclaration(node)) { @@ -137,15 +133,11 @@ export function parseBindings(ts: typeof import('typescript'), sourceFile: ts.So if (node.importClause.namedBindings) { if (ts.isNamedImports(node.importClause.namedBindings)) { for (const element of node.importClause.namedBindings.elements) { - const nodeText = _getNodeText(element.name); - bindingRanges.push(_getStartEnd(element.name)); - bindingTypes.set(nodeText, BindingTypes.NeedUnref); + addBinding(element.name, BindingTypes.NeedUnref); } } else if (ts.isNamespaceImport(node.importClause.namedBindings)) { - const nodeText = _getNodeText(node.importClause.namedBindings.name); - bindingRanges.push(_getStartEnd(node.importClause.namedBindings.name)); - bindingTypes.set(nodeText, BindingTypes.NoUnref); + addBinding(node.importClause.namedBindings.name, BindingTypes.NoUnref); } } } @@ -159,6 +151,10 @@ export function parseBindings(ts: typeof import('typescript'), sourceFile: ts.So function _getNodeText(node: ts.Node) { return getNodeText(ts, node, sourceFile); } + function addBinding(node: ts.Node, bindingType: BindingTypes) { + bindingRanges.push(_getStartEnd(node)); + bindingTypes.set(_getNodeText(node), bindingType); + } function getInnerExpression(node: ts.Node) { if (isAsExpression(node) || ts.isSatisfiesExpression(node) || ts.isParenthesizedExpression(node)) { return getInnerExpression(node.expression); From c6f07963f96e34a3afb47c7e7579141c62135023 Mon Sep 17 00:00:00 2001 From: so1ve Date: Tue, 7 May 2024 13:56:53 +0800 Subject: [PATCH 11/17] wip --- .../lib/parsers/scriptSetupRanges.ts | 2 +- .../language-core/lib/utils/parseBindings.ts | 105 +++++++++++++++--- 2 files changed, 91 insertions(+), 16 deletions(-) diff --git a/packages/language-core/lib/parsers/scriptSetupRanges.ts b/packages/language-core/lib/parsers/scriptSetupRanges.ts index 3a9c111823..0ebdc7e930 100644 --- a/packages/language-core/lib/parsers/scriptSetupRanges.ts +++ b/packages/language-core/lib/parsers/scriptSetupRanges.ts @@ -47,7 +47,7 @@ export function parseScriptSetupRanges( required: boolean; isModel?: boolean; }[] = []; - const bindings = parseBindings(ts, ast); + const bindings = parseBindings(ts, ast, vueCompilerOptions); const text = ast.text; const leadingCommentEndOffset = ts.getLeadingCommentRanges(text, 0)?.reverse()[0].end ?? 0; diff --git a/packages/language-core/lib/utils/parseBindings.ts b/packages/language-core/lib/utils/parseBindings.ts index 08cb9496ef..185d34e8b0 100644 --- a/packages/language-core/lib/utils/parseBindings.ts +++ b/packages/language-core/lib/utils/parseBindings.ts @@ -1,5 +1,5 @@ import type * as ts from 'typescript'; -import type { TextRange } from '../types'; +import type { TextRange, VueCompilerOptions } from '../types'; export enum BindingTypes { NoUnref, @@ -7,10 +7,35 @@ export enum BindingTypes { DirectAccess, } -export function parseBindings(ts: typeof import('typescript'), sourceFile: ts.SourceFile) { + +export function parseBindings( + ts: typeof import('typescript'), + sourceFile: ts.SourceFile, + vueCompilerOptions?: VueCompilerOptions, +) { const bindingRanges: TextRange[] = []; // `bindingTypes` may include some bindings that are not in `bindingRanges`, such as `foo` in `defineProps({ foo: Number })` const bindingTypes = new Map(); + const vueImportAliases: Record = { + ref: 'ref', + reactive: 'reactive', + computed: 'computed', + shallowRef: 'shallowRef', + customRef: 'customRef', + toRefs: 'toRef', + }; + + ts.forEachChild(sourceFile, node => { + // TODO: User may use package name alias then the import specifier may not be `vue` + if (ts.isImportDeclaration(node) && _getNodeText(node.moduleSpecifier) === 'vue') { + const namedBindings = node.importClause?.namedBindings; + if (namedBindings && ts.isNamedImports(namedBindings)) { + for (const element of namedBindings.elements) { + vueImportAliases[_getNodeText(element.name)] = element.propertyName ? _getNodeText(element.propertyName) : _getNodeText(element.name); + } + } + } + }); ts.forEachChild(sourceFile, node => { if (ts.isVariableStatement(node)) { @@ -27,11 +52,10 @@ export function parseBindings(ts: typeof import('typescript'), sourceFile: ts.So if (decl.initializer) { if (ts.isCallExpression(decl.initializer)) { const callText = _getNodeText(decl.initializer.expression); - if (callText === 'ref') { + if (callText === vueImportAliases.ref) { bindingTypes.set(nodeText, BindingTypes.NeedUnref); } - // TODO: use vue compiler options - else if (callText === 'defineProps') { + else if (vueCompilerOptions?.macros.defineProps.includes(callText)) { bindingTypes.set(nodeText, BindingTypes.DirectAccess); if (decl.initializer.typeArguments?.length === 1) { const typeNode = decl.initializer.typeArguments[0]; @@ -63,9 +87,8 @@ export function parseBindings(ts: typeof import('typescript'), sourceFile: ts.So } } else { - const innerExpression = getInnerExpression(decl.initializer); // const a = 1; - if (isLiteral(innerExpression)) { + if (canNeverBeRefOrIsStatic(decl.initializer, vueImportAliases.reactive)) { bindingTypes.set(nodeText, BindingTypes.NoUnref); } // const a = bar; @@ -155,17 +178,69 @@ export function parseBindings(ts: typeof import('typescript'), sourceFile: ts.So bindingRanges.push(_getStartEnd(node)); bindingTypes.set(_getNodeText(node), bindingType); } - function getInnerExpression(node: ts.Node) { - if (isAsExpression(node) || ts.isSatisfiesExpression(node) || ts.isParenthesizedExpression(node)) { - return getInnerExpression(node.expression); - } - else if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.CommaToken) { - return getInnerExpression(node.right); + function unwrapTsNode(node: ts.Node) { + if ( + isAsExpression(node) + || ts.isSatisfiesExpression(node) + || ts.isTypeAssertionExpression(node) + || ts.isParenthesizedExpression(node) + || ts.isNonNullExpression(node) + || ts.isExpressionWithTypeArguments(node) + ) { + return unwrapTsNode(node.expression); } return node; } - function isLiteral(node: ts.Node) { - return !(ts.isIdentifier(node) || ts.isPropertyAccessExpression(node) || ts.isCallExpression(node)); + function canNeverBeRefOrIsStatic(node: ts.Node, userReactiveImport?: string): boolean { + node = unwrapTsNode(node); + + if (isCallOf(node, userReactiveImport)) { + return true; + } + else if (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) { + return canNeverBeRefOrIsStatic(node.operand, userReactiveImport); + } + else if (ts.isBinaryExpression(node)) { + return canNeverBeRefOrIsStatic(node.left, userReactiveImport) && canNeverBeRefOrIsStatic(node.right, userReactiveImport); + } + else if (ts.isConditionalExpression(node)) { + return ( + canNeverBeRefOrIsStatic(node.condition, userReactiveImport) && + canNeverBeRefOrIsStatic(node.whenTrue, userReactiveImport) && + canNeverBeRefOrIsStatic(node.whenFalse, userReactiveImport) + ); + } + else if (ts.isCommaListExpression(node)) { + return node.elements.every(expr => canNeverBeRefOrIsStatic(expr, userReactiveImport)); + } + else if (ts.isTemplateExpression(node)) { + return node.templateSpans.every(span => canNeverBeRefOrIsStatic(span.expression, userReactiveImport)); + } + else if (ts.isParenthesizedExpression(node)) { + return canNeverBeRefOrIsStatic(node.expression, userReactiveImport); + } + else if ( + ts.isStringLiteral(node) || + ts.isNumericLiteral(node) || + node.kind === ts.SyntaxKind.TrueKeyword || + node.kind === ts.SyntaxKind.FalseKeyword || + node.kind === ts.SyntaxKind.NullKeyword || + ts.isBigIntLiteral(node) + ) { + return true; + } + else if (ts.isLiteralExpression(node)) { + return true; + } + return false; + } + function isCallOf(node: ts.Node, userReactiveImport?: string): boolean { + if (ts.isCallExpression(node)) { + if (ts.isIdentifier(node.expression) && _getNodeText(node.expression) === userReactiveImport) { + return true; + } + } + return false; } // isAsExpression is missing in tsc function isAsExpression(node: ts.Node): node is ts.AsExpression { From 1d3c52e96d324cd785a65f8022cc85225e5fd662 Mon Sep 17 00:00:00 2001 From: so1ve Date: Tue, 7 May 2024 16:51:07 +0800 Subject: [PATCH 12/17] wip --- .../lib/codegen/template/interpolation.ts | 5 +- .../language-core/lib/parsers/scriptRanges.ts | 10 +-- .../language-core/lib/utils/parseBindings.ts | 83 ++++++++----------- .../language-core/lib/utils/tscApiShim.ts | 22 +++++ 4 files changed, 60 insertions(+), 60 deletions(-) create mode 100644 packages/language-core/lib/utils/tscApiShim.ts diff --git a/packages/language-core/lib/codegen/template/interpolation.ts b/packages/language-core/lib/codegen/template/interpolation.ts index 1c6b5acf1a..e9085cbeb5 100644 --- a/packages/language-core/lib/codegen/template/interpolation.ts +++ b/packages/language-core/lib/codegen/template/interpolation.ts @@ -1,6 +1,6 @@ import { isGloballyAllowed } from '@vue/shared'; import type * as ts from 'typescript'; -import { BindingTypes, getNodeText, getStartEnd } from '../../utils/parseBindings'; +import { getNodeText, getStartEnd } from '../../utils/parseBindings'; import type { Code, VueCodeInformation, VueCompilerOptions } from '../../types'; import { collectVars, createTsAst } from '../common'; import type { TemplateCodegenContext } from './context'; @@ -86,9 +86,6 @@ export function* forEachInterpolationSegment( const varCb = (id: ts.Identifier, isShorthand: boolean) => { const text = getNodeText(ts, id, ast); - if (text === 'record') { - console.log('binding:', text, BindingTypes[ctx.bindingTypes!.get(text)!]); - } if ( ctx.hasLocalVariable(text) || // https://github.com/vuejs/core/blob/245230e135152900189f13a4281302de45fdcfaa/packages/compiler-core/src/transforms/transformExpression.ts#L342-L352 diff --git a/packages/language-core/lib/parsers/scriptRanges.ts b/packages/language-core/lib/parsers/scriptRanges.ts index 085879c1e5..2b799e8204 100644 --- a/packages/language-core/lib/parsers/scriptRanges.ts +++ b/packages/language-core/lib/parsers/scriptRanges.ts @@ -1,11 +1,14 @@ import type { TextRange } from '../types'; import type * as ts from 'typescript'; import { BindingTypes, getNodeText, getStartEnd, parseBindings } from '../utils/parseBindings'; +import { createTscApiShim } from '../utils/tscApiShim'; export interface ScriptRanges extends ReturnType { } export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.SourceFile, hasScriptSetup: boolean, withNode: boolean) { + const tscApiShim = createTscApiShim(ts); + let exportDefault: (TextRange & { expression: TextRange, args: TextRange, @@ -24,7 +27,7 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc if (ts.isExportAssignment(raw)) { let node: ts.AsExpression | ts.ExportAssignment | ts.ParenthesizedExpression = raw; - while (isAsExpression(node.expression) || ts.isParenthesizedExpression(node.expression)) { // fix https://github.com/vuejs/language-tools/issues/1882 + while (tscApiShim.isAsExpression(node.expression) || ts.isParenthesizedExpression(node.expression)) { // fix https://github.com/vuejs/language-tools/issues/1882 node = node.expression; } @@ -73,9 +76,4 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc function _getStartEnd(node: ts.Node) { return getStartEnd(ts, node, ast); } - - // isAsExpression is missing in tsc - function isAsExpression(node: ts.Node): node is ts.AsExpression { - return node.kind === ts.SyntaxKind.AsExpression; - } } diff --git a/packages/language-core/lib/utils/parseBindings.ts b/packages/language-core/lib/utils/parseBindings.ts index 185d34e8b0..c9d4665c8a 100644 --- a/packages/language-core/lib/utils/parseBindings.ts +++ b/packages/language-core/lib/utils/parseBindings.ts @@ -1,5 +1,6 @@ import type * as ts from 'typescript'; import type { TextRange, VueCompilerOptions } from '../types'; +import { createTscApiShim } from './tscApiShim'; export enum BindingTypes { NoUnref, @@ -7,12 +8,12 @@ export enum BindingTypes { DirectAccess, } - export function parseBindings( ts: typeof import('typescript'), sourceFile: ts.SourceFile, vueCompilerOptions?: VueCompilerOptions, ) { + const tscApiShim = createTscApiShim(ts); const bindingRanges: TextRange[] = []; // `bindingTypes` may include some bindings that are not in `bindingRanges`, such as `foo` in `defineProps({ foo: Number })` const bindingTypes = new Map(); @@ -87,14 +88,10 @@ export function parseBindings( } } else { - // const a = 1; - if (canNeverBeRefOrIsStatic(decl.initializer, vueImportAliases.reactive)) { - bindingTypes.set(nodeText, BindingTypes.NoUnref); - } - // const a = bar; - else { - bindingTypes.set(nodeText, BindingTypes.NeedUnref); - } + bindingTypes.set( + nodeText, + canNeverBeRef(decl.initializer, vueImportAliases.reactive) ? BindingTypes.NoUnref : BindingTypes.NeedUnref + ); } } else { @@ -178,59 +175,49 @@ export function parseBindings( bindingRanges.push(_getStartEnd(node)); bindingTypes.set(_getNodeText(node), bindingType); } - function unwrapTsNode(node: ts.Node) { + function getValueNode(node: ts.Node) { if ( - isAsExpression(node) + tscApiShim.isAsExpression(node) || ts.isSatisfiesExpression(node) - || ts.isTypeAssertionExpression(node) + || tscApiShim.isTypeAssertionExpression(node) || ts.isParenthesizedExpression(node) || ts.isNonNullExpression(node) || ts.isExpressionWithTypeArguments(node) ) { - return unwrapTsNode(node.expression); + return getValueNode(node.expression); + } + else if (ts.isCommaListExpression(node)) { + return getValueNode(node.elements[node.elements.length - 1]); } return node; } - function canNeverBeRefOrIsStatic(node: ts.Node, userReactiveImport?: string): boolean { - node = unwrapTsNode(node); - + function canNeverBeRef(node: ts.Node, userReactiveImport?: string): boolean { + node = getValueNode(node); if (isCallOf(node, userReactiveImport)) { return true; } - else if (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) { - return canNeverBeRefOrIsStatic(node.operand, userReactiveImport); - } - else if (ts.isBinaryExpression(node)) { - return canNeverBeRefOrIsStatic(node.left, userReactiveImport) && canNeverBeRefOrIsStatic(node.right, userReactiveImport); - } - else if (ts.isConditionalExpression(node)) { - return ( - canNeverBeRefOrIsStatic(node.condition, userReactiveImport) && - canNeverBeRefOrIsStatic(node.whenTrue, userReactiveImport) && - canNeverBeRefOrIsStatic(node.whenFalse, userReactiveImport) - ); - } - else if (ts.isCommaListExpression(node)) { - return node.elements.every(expr => canNeverBeRefOrIsStatic(expr, userReactiveImport)); - } - else if (ts.isTemplateExpression(node)) { - return node.templateSpans.every(span => canNeverBeRefOrIsStatic(span.expression, userReactiveImport)); - } - else if (ts.isParenthesizedExpression(node)) { - return canNeverBeRefOrIsStatic(node.expression, userReactiveImport); - } else if ( - ts.isStringLiteral(node) || - ts.isNumericLiteral(node) || - node.kind === ts.SyntaxKind.TrueKeyword || - node.kind === ts.SyntaxKind.FalseKeyword || - node.kind === ts.SyntaxKind.NullKeyword || - ts.isBigIntLiteral(node) + ts.isPrefixUnaryExpression(node) + || ts.isPostfixUnaryExpression(node) + || ts.isArrayLiteralExpression(node) + || ts.isObjectLiteralExpression(node) + || ts.isFunctionExpression(node) + || ts.isArrowFunction(node) + || ts.isClassExpression(node) + || ts.isTaggedTemplateExpression(node) + || ts.isNoSubstitutionTemplateLiteral(node) + || ts.isLiteralExpression(node) + || node.kind === ts.SyntaxKind.TrueKeyword + || node.kind === ts.SyntaxKind.FalseKeyword + || node.kind === ts.SyntaxKind.NullKeyword ) { return true; } - else if (ts.isLiteralExpression(node)) { - return true; + else if (ts.isBinaryExpression(node)) { + return canNeverBeRef(node.left, userReactiveImport) && canNeverBeRef(node.right, userReactiveImport); + } + else if (ts.isConditionalExpression(node)) { + return canNeverBeRef(node.whenTrue, userReactiveImport) && canNeverBeRef(node.whenFalse, userReactiveImport); } return false; } @@ -242,10 +229,6 @@ export function parseBindings( } return false; } - // isAsExpression is missing in tsc - function isAsExpression(node: ts.Node): node is ts.AsExpression { - return node.kind === ts.SyntaxKind.AsExpression; - } } export function getStartEnd( diff --git a/packages/language-core/lib/utils/tscApiShim.ts b/packages/language-core/lib/utils/tscApiShim.ts new file mode 100644 index 0000000000..de0ac848f5 --- /dev/null +++ b/packages/language-core/lib/utils/tscApiShim.ts @@ -0,0 +1,22 @@ +import type * as ts from 'typescript'; + +/** + * Provide missing functions in tsc context. + */ +export function createTscApiShim(ts: typeof import('typescript')) { + function isAsExpression(node: ts.Node): node is ts.AsExpression { + return node.kind === ts.SyntaxKind.AsExpression; + } + function isTypeAssertionExpression(node: ts.Node): node is ts.TypeAssertion { + return node.kind === ts.SyntaxKind.TypeAssertionExpression; + } + function isTemplateExpression(node: ts.Node): node is ts.TemplateExpression { + return node.kind === ts.SyntaxKind.TemplateExpression; + } + + return { + isAsExpression, + isTypeAssertionExpression, + isTemplateExpression, + }; +}; \ No newline at end of file From 109e094dfca7996d65bb67277617a4aab86ff07d Mon Sep 17 00:00:00 2001 From: so1ve Date: Tue, 7 May 2024 17:02:35 +0800 Subject: [PATCH 13/17] update --- .../language-core/lib/utils/parseBindings.ts | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/packages/language-core/lib/utils/parseBindings.ts b/packages/language-core/lib/utils/parseBindings.ts index c9d4665c8a..c780526d64 100644 --- a/packages/language-core/lib/utils/parseBindings.ts +++ b/packages/language-core/lib/utils/parseBindings.ts @@ -19,11 +19,10 @@ export function parseBindings( const bindingTypes = new Map(); const vueImportAliases: Record = { ref: 'ref', - reactive: 'reactive', computed: 'computed', shallowRef: 'shallowRef', customRef: 'customRef', - toRefs: 'toRef', + toRef: 'toRef', }; ts.forEachChild(sourceFile, node => { @@ -46,18 +45,24 @@ export function parseBindings( worker(decl.name, true); function worker(_node: ts.Node, root = false) { if (ts.isIdentifier(_node)) { - const nodeText = _getNodeText(_node); + const name = _getNodeText(_node); bindingRanges.push(_getStartEnd(_node)); if (root) { if (declList.flags & ts.NodeFlags.Const) { if (decl.initializer) { if (ts.isCallExpression(decl.initializer)) { - const callText = _getNodeText(decl.initializer.expression); - if (callText === vueImportAliases.ref) { - bindingTypes.set(nodeText, BindingTypes.NeedUnref); + const callee = _getNodeText(decl.initializer.expression); + if ( + callee === vueImportAliases.ref + || callee === vueImportAliases.computed + || callee === vueImportAliases.shallowRef + || callee === vueImportAliases.customRef + || callee === vueImportAliases.toRef + ) { + bindingTypes.set(name, BindingTypes.NeedUnref); } - else if (vueCompilerOptions?.macros.defineProps.includes(callText)) { - bindingTypes.set(nodeText, BindingTypes.DirectAccess); + else if (vueCompilerOptions?.macros.defineProps.includes(callee)) { + bindingTypes.set(name, BindingTypes.DirectAccess); if (decl.initializer.typeArguments?.length === 1) { const typeNode = decl.initializer.typeArguments[0]; if (ts.isTypeLiteralNode(typeNode)) { @@ -80,7 +85,7 @@ export function parseBindings( else if (ts.isArrayLiteralExpression(arg)) { for (const prop of arg.elements) { if (ts.isStringLiteral(prop)) { - bindingTypes.set(prop.text, BindingTypes.NoUnref); + bindingTypes.set(_getNodeText(prop), BindingTypes.NoUnref); } } } @@ -89,18 +94,18 @@ export function parseBindings( } else { bindingTypes.set( - nodeText, + name, canNeverBeRef(decl.initializer, vueImportAliases.reactive) ? BindingTypes.NoUnref : BindingTypes.NeedUnref ); } } else { - bindingTypes.set(nodeText, BindingTypes.NeedUnref); + bindingTypes.set(name, BindingTypes.NeedUnref); } } // let a = 1; else { - bindingTypes.set(nodeText, BindingTypes.NeedUnref); + bindingTypes.set(name, BindingTypes.NeedUnref); } } } @@ -141,13 +146,13 @@ export function parseBindings( else if (ts.isImportDeclaration(node)) { if (node.importClause && !node.importClause.isTypeOnly) { if (node.importClause.name) { - const nodeText = _getNodeText(node.importClause.name); + const name = _getNodeText(node.importClause.name); bindingRanges.push(_getStartEnd(node.importClause.name)); if (ts.isStringLiteral(node.moduleSpecifier) && _getNodeText(node.moduleSpecifier).endsWith('.vue')) { - bindingTypes.set(nodeText, BindingTypes.NoUnref); + bindingTypes.set(name, BindingTypes.NoUnref); } else { - bindingTypes.set(nodeText, BindingTypes.NeedUnref); + bindingTypes.set(name, BindingTypes.NeedUnref); } } if (node.importClause.namedBindings) { From 6590f0c6ba8581c4dd4036b008790869278db061 Mon Sep 17 00:00:00 2001 From: so1ve Date: Tue, 7 May 2024 18:05:51 +0800 Subject: [PATCH 14/17] use proxy --- .../language-core/lib/parsers/scriptRanges.ts | 6 ++--- .../language-core/lib/utils/parseBindings.ts | 8 +++---- .../language-core/lib/utils/tscApiShim.ts | 24 +++++++++++++------ 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/packages/language-core/lib/parsers/scriptRanges.ts b/packages/language-core/lib/parsers/scriptRanges.ts index 2b799e8204..3a331d7683 100644 --- a/packages/language-core/lib/parsers/scriptRanges.ts +++ b/packages/language-core/lib/parsers/scriptRanges.ts @@ -1,13 +1,13 @@ import type { TextRange } from '../types'; import type * as ts from 'typescript'; import { BindingTypes, getNodeText, getStartEnd, parseBindings } from '../utils/parseBindings'; -import { createTscApiShim } from '../utils/tscApiShim'; +import { injectTscApiShim } from '../utils/tscApiShim'; export interface ScriptRanges extends ReturnType { } export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.SourceFile, hasScriptSetup: boolean, withNode: boolean) { - const tscApiShim = createTscApiShim(ts); + ts = injectTscApiShim(ts); let exportDefault: (TextRange & { expression: TextRange, @@ -27,7 +27,7 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc if (ts.isExportAssignment(raw)) { let node: ts.AsExpression | ts.ExportAssignment | ts.ParenthesizedExpression = raw; - while (tscApiShim.isAsExpression(node.expression) || ts.isParenthesizedExpression(node.expression)) { // fix https://github.com/vuejs/language-tools/issues/1882 + while (ts.isAsExpression(node.expression) || ts.isParenthesizedExpression(node.expression)) { // fix https://github.com/vuejs/language-tools/issues/1882 node = node.expression; } diff --git a/packages/language-core/lib/utils/parseBindings.ts b/packages/language-core/lib/utils/parseBindings.ts index c780526d64..12f86ed9ac 100644 --- a/packages/language-core/lib/utils/parseBindings.ts +++ b/packages/language-core/lib/utils/parseBindings.ts @@ -1,6 +1,6 @@ import type * as ts from 'typescript'; import type { TextRange, VueCompilerOptions } from '../types'; -import { createTscApiShim } from './tscApiShim'; +import { injectTscApiShim } from './tscApiShim'; export enum BindingTypes { NoUnref, @@ -13,7 +13,7 @@ export function parseBindings( sourceFile: ts.SourceFile, vueCompilerOptions?: VueCompilerOptions, ) { - const tscApiShim = createTscApiShim(ts); + ts = injectTscApiShim(ts); const bindingRanges: TextRange[] = []; // `bindingTypes` may include some bindings that are not in `bindingRanges`, such as `foo` in `defineProps({ foo: Number })` const bindingTypes = new Map(); @@ -182,9 +182,9 @@ export function parseBindings( } function getValueNode(node: ts.Node) { if ( - tscApiShim.isAsExpression(node) + ts.isAsExpression(node) || ts.isSatisfiesExpression(node) - || tscApiShim.isTypeAssertionExpression(node) + || ts.isTypeAssertionExpression(node) || ts.isParenthesizedExpression(node) || ts.isNonNullExpression(node) || ts.isExpressionWithTypeArguments(node) diff --git a/packages/language-core/lib/utils/tscApiShim.ts b/packages/language-core/lib/utils/tscApiShim.ts index de0ac848f5..3268e40bb8 100644 --- a/packages/language-core/lib/utils/tscApiShim.ts +++ b/packages/language-core/lib/utils/tscApiShim.ts @@ -3,7 +3,7 @@ import type * as ts from 'typescript'; /** * Provide missing functions in tsc context. */ -export function createTscApiShim(ts: typeof import('typescript')) { +export function injectTscApiShim(ts: typeof import('typescript')) { function isAsExpression(node: ts.Node): node is ts.AsExpression { return node.kind === ts.SyntaxKind.AsExpression; } @@ -14,9 +14,19 @@ export function createTscApiShim(ts: typeof import('typescript')) { return node.kind === ts.SyntaxKind.TemplateExpression; } - return { - isAsExpression, - isTypeAssertionExpression, - isTemplateExpression, - }; -}; \ No newline at end of file + return new Proxy(ts, { + get(target, key) { + if (key === 'isAsExpression') { + return isAsExpression; + } + else if (key === 'isTypeAssertionExpression') { + return isTypeAssertionExpression; + } + else if (key === 'isTemplateExpression') { + return isTemplateExpression; + } + + return target[key as keyof typeof target]; + } + }); +}; From ba6ef44f460637963c85c4ea61dbc56d610c113f Mon Sep 17 00:00:00 2001 From: so1ve Date: Wed, 8 May 2024 12:38:46 +0800 Subject: [PATCH 15/17] component --- packages/language-core/lib/utils/parseBindings.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/language-core/lib/utils/parseBindings.ts b/packages/language-core/lib/utils/parseBindings.ts index 12f86ed9ac..74124a564e 100644 --- a/packages/language-core/lib/utils/parseBindings.ts +++ b/packages/language-core/lib/utils/parseBindings.ts @@ -3,9 +3,10 @@ import type { TextRange, VueCompilerOptions } from '../types'; import { injectTscApiShim } from './tscApiShim'; export enum BindingTypes { - NoUnref, - NeedUnref, - DirectAccess, + NoUnref = 2 << 0, + NeedUnref = 2 << 1, + DirectAccess = 2 << 2, + Component = 2 << 3, } export function parseBindings( @@ -27,7 +28,7 @@ export function parseBindings( ts.forEachChild(sourceFile, node => { // TODO: User may use package name alias then the import specifier may not be `vue` - if (ts.isImportDeclaration(node) && _getNodeText(node.moduleSpecifier) === 'vue') { + if (ts.isImportDeclaration(node) && _getNodeText(node.moduleSpecifier) === vueCompilerOptions?.lib) { const namedBindings = node.importClause?.namedBindings; if (namedBindings && ts.isNamedImports(namedBindings)) { for (const element of namedBindings.elements) { @@ -148,8 +149,8 @@ export function parseBindings( if (node.importClause.name) { const name = _getNodeText(node.importClause.name); bindingRanges.push(_getStartEnd(node.importClause.name)); - if (ts.isStringLiteral(node.moduleSpecifier) && _getNodeText(node.moduleSpecifier).endsWith('.vue')) { - bindingTypes.set(name, BindingTypes.NoUnref); + if (ts.isStringLiteral(node.moduleSpecifier) && vueCompilerOptions?.extensions.some(ext => _getNodeText(node.moduleSpecifier).endsWith(ext))) { + bindingTypes.set(name, BindingTypes.NoUnref | BindingTypes.Component); } else { bindingTypes.set(name, BindingTypes.NeedUnref); From 09d076c091c66b12fa7dd49ef5b4acef830ad408 Mon Sep 17 00:00:00 2001 From: so1ve Date: Wed, 8 May 2024 13:18:15 +0800 Subject: [PATCH 16/17] merge master --- .../lib/codegen/template/element.ts | 8 +++++++- .../lib/codegen/template/index.ts | 1 - .../lib/parsers/scriptSetupRanges.ts | 13 ------------ packages/language-core/lib/plugins/vue-tsx.ts | 20 ------------------- .../language-core/lib/utils/parseBindings.ts | 6 +++++- 5 files changed, 12 insertions(+), 36 deletions(-) diff --git a/packages/language-core/lib/codegen/template/element.ts b/packages/language-core/lib/codegen/template/element.ts index e516c44d89..f3f77c8111 100644 --- a/packages/language-core/lib/codegen/template/element.ts +++ b/packages/language-core/lib/codegen/template/element.ts @@ -2,6 +2,7 @@ import * as CompilerDOM from '@vue/compiler-dom'; import { camelize, capitalize } from '@vue/shared'; import type { Code, VueCodeInformation } from '../../types'; import { hyphenateTag } from '../../utils/shared'; +import { BindingTypes } from '../../utils/parseBindings'; import { collectVars, createTsAst, endOfLine, newLine, variableNameRegex, wrapWith } from '../common'; import { generateCamelized } from './camelized'; import type { TemplateCodegenContext } from './context'; @@ -30,7 +31,12 @@ export function* generateComponent( : [startTagOffset]; const propsFailedExps: CompilerDOM.SimpleExpressionNode[] = []; const possibleOriginalNames = getPossibleOriginalComponentNames(node.tag, true); - const matchImportName = possibleOriginalNames.find(name => options.scriptSetupImportComponentNames.has(name)); + const matchImportName = possibleOriginalNames.find(name => { + const bindingType = ctx.bindingTypes?.get(name); + if (bindingType) { + return bindingType & BindingTypes.Component; + } + }); const var_originalComponent = matchImportName ?? ctx.getInternalVariable(); const var_functionalComponent = ctx.getInternalVariable(); const var_componentInstance = ctx.getInternalVariable(); diff --git a/packages/language-core/lib/codegen/template/index.ts b/packages/language-core/lib/codegen/template/index.ts index 553b6c5637..a876cbffca 100644 --- a/packages/language-core/lib/codegen/template/index.ts +++ b/packages/language-core/lib/codegen/template/index.ts @@ -16,7 +16,6 @@ export interface TemplateCodegenOptions { template: NonNullable; shouldGenerateScopedClasses?: boolean; stylesScopedClasses: Set; - scriptSetupImportComponentNames: Set; hasDefineSlots?: boolean; slotsAssignName?: string; propsAssignName?: string; diff --git a/packages/language-core/lib/parsers/scriptSetupRanges.ts b/packages/language-core/lib/parsers/scriptSetupRanges.ts index 9dfbef71ef..0ebdc7e930 100644 --- a/packages/language-core/lib/parsers/scriptSetupRanges.ts +++ b/packages/language-core/lib/parsers/scriptSetupRanges.ts @@ -50,7 +50,6 @@ export function parseScriptSetupRanges( const bindings = parseBindings(ts, ast, vueCompilerOptions); const text = ast.text; const leadingCommentEndOffset = ts.getLeadingCommentRanges(text, 0)?.reverse()[0].end ?? 0; - const importComponentNames = new Set(); ts.forEachChild(ast, node => { const isTypeExport = (ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) && node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword); @@ -72,17 +71,6 @@ export function parseScriptSetupRanges( } foundNonImportExportNode = true; } - - if ( - ts.isImportDeclaration(node) - && node.importClause?.name - && !node.importClause.isTypeOnly - ) { - const moduleName = getNodeText(ts, node.moduleSpecifier, ast).slice(1, -1); - if (vueCompilerOptions.extensions.some(ext => moduleName.endsWith(ext))) { - importComponentNames.add(getNodeText(ts, node.importClause.name, ast)); - } - } }); ts.forEachChild(ast, child => visitNode(child, [ast])); @@ -90,7 +78,6 @@ export function parseScriptSetupRanges( leadingCommentEndOffset, importSectionEndOffset, bindings, - importComponentNames, props, slots, emits, diff --git a/packages/language-core/lib/plugins/vue-tsx.ts b/packages/language-core/lib/plugins/vue-tsx.ts index 2bcc6f182b..b64fe98cb6 100644 --- a/packages/language-core/lib/plugins/vue-tsx.ts +++ b/packages/language-core/lib/plugins/vue-tsx.ts @@ -119,7 +119,6 @@ function createTsx( template: _sfc.template, shouldGenerateScopedClasses: shouldGenerateScopedClasses(), stylesScopedClasses: stylesScopedClasses(), - scriptSetupImportComponentNames: scriptSetupImportComponentNames(), hasDefineSlots: hasDefineSlots(), slotsAssignName: slotsAssignName(), propsAssignName: propsAssignName(), @@ -140,13 +139,6 @@ function createTsx( }; }); const hasDefineSlots = computed(() => !!scriptSetupRanges()?.slots.define); - const scriptSetupImportComponentNames = computed>(oldNames => { - const newNames = scriptSetupRanges()?.importComponentNames ?? new Set(); - if (newNames && oldNames && twoSetsEqual(newNames, oldNames)) { - return oldNames; - } - return newNames; - }); const slotsAssignName = computed(() => scriptSetupRanges()?.slots.name); const propsAssignName = computed(() => scriptSetupRanges()?.props.name); const bindingTypes = computed(() => scriptSetupRanges()?.bindings.bindingTypes ?? scriptRanges()?.bindings.bindingTypes); @@ -192,15 +184,3 @@ function createTsx( generatedTemplate, }; } - -function twoSetsEqual(a: Set, b: Set) { - if (a.size !== b.size) { - return false; - } - for (const file of a) { - if (!b.has(file)) { - return false; - } - } - return true; -} diff --git a/packages/language-core/lib/utils/parseBindings.ts b/packages/language-core/lib/utils/parseBindings.ts index 74124a564e..20834e0283 100644 --- a/packages/language-core/lib/utils/parseBindings.ts +++ b/packages/language-core/lib/utils/parseBindings.ts @@ -149,7 +149,11 @@ export function parseBindings( if (node.importClause.name) { const name = _getNodeText(node.importClause.name); bindingRanges.push(_getStartEnd(node.importClause.name)); - if (ts.isStringLiteral(node.moduleSpecifier) && vueCompilerOptions?.extensions.some(ext => _getNodeText(node.moduleSpecifier).endsWith(ext))) { + if ( + ts.isStringLiteral(node.moduleSpecifier) + && !node.importClause.isTypeOnly + && vueCompilerOptions?.extensions.some(ext => _getNodeText(node.moduleSpecifier).slice(1, -1).endsWith(ext)) + ) { bindingTypes.set(name, BindingTypes.NoUnref | BindingTypes.Component); } else { From d16616f6f70d7115b867b0bfaf53133bf27e5f36 Mon Sep 17 00:00:00 2001 From: so1ve Date: Wed, 8 May 2024 13:44:54 +0800 Subject: [PATCH 17/17] deprecated variables now have strike through --- packages/language-core/lib/codegen/template/interpolation.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/language-core/lib/codegen/template/interpolation.ts b/packages/language-core/lib/codegen/template/interpolation.ts index e9085cbeb5..134e12f701 100644 --- a/packages/language-core/lib/codegen/template/interpolation.ts +++ b/packages/language-core/lib/codegen/template/interpolation.ts @@ -1,6 +1,6 @@ import { isGloballyAllowed } from '@vue/shared'; import type * as ts from 'typescript'; -import { getNodeText, getStartEnd } from '../../utils/parseBindings'; +import { BindingTypes, getNodeText, getStartEnd } from '../../utils/parseBindings'; import type { Code, VueCodeInformation, VueCompilerOptions } from '../../types'; import { collectVars, createTsAst } from '../common'; import type { TemplateCodegenContext } from './context'; @@ -91,7 +91,8 @@ export function* forEachInterpolationSegment( // https://github.com/vuejs/core/blob/245230e135152900189f13a4281302de45fdcfaa/packages/compiler-core/src/transforms/transformExpression.ts#L342-L352 isGloballyAllowed(text) || text === 'require' || - text.startsWith('__VLS_') + text.startsWith('__VLS_') || + ((ctx.bindingTypes?.get(text) ?? 0) & BindingTypes.NoUnref) ) { // localVarOffsets.push(localVar.getStart(ast)); }