diff --git a/packages/language-core/lib/codegen/script/scriptSetup.ts b/packages/language-core/lib/codegen/script/scriptSetup.ts index 84fbeb991e..977edacf4b 100644 --- a/packages/language-core/lib/codegen/script/scriptSetup.ts +++ b/packages/language-core/lib/codegen/script/scriptSetup.ts @@ -1,5 +1,5 @@ import type { ScriptSetupRanges } from '../../parsers/scriptSetupRanges'; -import type { Code, Sfc } from '../../types'; +import type { Code, Sfc, TextRange } from '../../types'; import { endOfLine, generateSfcBlockSection, newLine } from '../common'; import { generateComponent, generateEmitsOption } from './component'; import type { ScriptCodegenContext } from './context'; @@ -88,16 +88,16 @@ export function* generateScriptSetup( if (ctx.scriptSetupGeneratedOffset !== undefined) { for (const defineProp of scriptSetupRanges.defineProp) { - if (!defineProp.name) { + if (!defineProp.localName) { continue; } - const propName = scriptSetup.content.substring(defineProp.name.start, defineProp.name.end); - const propMirror = definePropMirrors.get(propName); + const [_, localName] = getPropAndLocalName(scriptSetup, defineProp); + const propMirror = definePropMirrors.get(localName!); if (propMirror !== undefined) { options.linkedCodeMappings.push({ - sourceOffsets: [defineProp.name.start + ctx.scriptSetupGeneratedOffset], + sourceOffsets: [defineProp.localName.start + ctx.scriptSetupGeneratedOffset], generatedOffsets: [propMirror], - lengths: [defineProp.name.end - defineProp.name.start], + lengths: [defineProp.localName.end - defineProp.localName.start], data: undefined, }); } @@ -302,14 +302,19 @@ function* generateComponentProps( yield `const __VLS_defaults = {${newLine}`; for (const defineProp of scriptSetupRanges.defineProp) { if (defineProp.defaultValue) { - if (defineProp.name) { - yield scriptSetup.content.substring(defineProp.name.start, defineProp.name.end); + const [propName, localName] = getPropAndLocalName(scriptSetup, defineProp); + + if (defineProp.name || defineProp.isModel) { + yield propName!; + } + else if (defineProp.localName) { + yield localName!; } else { - yield `modelValue`; + continue; } yield `: `; - yield scriptSetup.content.substring(defineProp.defaultValue.start, defineProp.defaultValue.end); + yield getRangeName(scriptSetup, defineProp.defaultValue); yield `,${newLine}`; } } @@ -331,33 +336,35 @@ function* generateComponentProps( ctx.generatedPropsType = true; yield `{${newLine}`; for (const defineProp of scriptSetupRanges.defineProp) { - let propName = 'modelValue'; - if (defineProp.name && defineProp.nameIsString) { + const [propName, localName] = getPropAndLocalName(scriptSetup, defineProp); + + if (defineProp.isModel && !defineProp.name) { + yield propName!; + } + else if (defineProp.name) { // renaming support yield generateSfcBlockSection(scriptSetup, defineProp.name.start, defineProp.name.end, codeFeatures.navigation); - propName = scriptSetup.content.substring(defineProp.name.start, defineProp.name.end); - propName = propName.replace(/['"]+/g, ''); } - else if (defineProp.name) { - propName = scriptSetup.content.substring(defineProp.name.start, defineProp.name.end); - definePropMirrors.set(propName, options.getGeneratedLength()); - yield propName; + else if (defineProp.localName) { + definePropMirrors.set(localName!, options.getGeneratedLength()); + yield localName!; } else { - yield propName; + continue; } + yield defineProp.required ? `: ` : `?: `; - yield* generateDefinePropType(scriptSetup, propName, defineProp); + yield* generateDefinePropType(scriptSetup, propName, localName, defineProp); yield `,${newLine}`; if (defineProp.modifierType) { let propModifierName = 'modelModifiers'; if (defineProp.name) { - propModifierName = `${scriptSetup.content.substring(defineProp.name.start + 1, defineProp.name.end - 1)}Modifiers`; + propModifierName = `${getRangeName(scriptSetup, defineProp.name, true)}Modifiers`; } - const modifierType = scriptSetup.content.substring(defineProp.modifierType.start, defineProp.modifierType.end); + const modifierType = getRangeName(scriptSetup, defineProp.modifierType); definePropMirrors.set(propModifierName, options.getGeneratedLength()); yield `${propModifierName}?: Record<${modifierType}, true>,${endOfLine}`; } @@ -394,13 +401,10 @@ function* generateModelEmits( continue; } - let propName = 'modelValue'; - if (defineProp.name) { - propName = scriptSetup.content.substring(defineProp.name.start, defineProp.name.end); - propName = propName.replace(/['"]+/g, ''); - } + const [propName, localName] = getPropAndLocalName(scriptSetup, defineProp); + yield `'update:${propName}': [${propName}:`; - yield* generateDefinePropType(scriptSetup, propName, defineProp); + yield* generateDefinePropType(scriptSetup, propName, localName, defineProp); yield `]${endOfLine}`; } yield `}`; @@ -413,16 +417,21 @@ function* generateModelEmits( yield endOfLine; } -function* generateDefinePropType(scriptSetup: NonNullable, propName: string, defineProp: ScriptSetupRanges['defineProp'][number]) { +function* generateDefinePropType( + scriptSetup: NonNullable, + propName: string | undefined, + localName: string | undefined, + defineProp: ScriptSetupRanges['defineProp'][number] +) { if (defineProp.type) { // Infer from defineProp - yield scriptSetup.content.substring(defineProp.type.start, defineProp.type.end); + yield getRangeName(scriptSetup, defineProp.type); } - else if ((defineProp.name && defineProp.nameIsString) || !defineProp.nameIsString) { + else if (defineProp.runtimeType && localName) { // Infer from actual prop declaration code - yield `typeof ${propName}['value']`; + yield `typeof ${localName}['value']`; } - else if (defineProp.defaultValue) { + else if (defineProp.defaultValue && propName) { // Infer from defineProp({default: T}) yield `typeof __VLS_defaults['${propName}']`; } @@ -430,3 +439,30 @@ function* generateDefinePropType(scriptSetup: NonNullable, p yield `any`; } } + +function getPropAndLocalName( + scriptSetup: NonNullable, + defineProp: ScriptSetupRanges['defineProp'][number] +) { + const localName = defineProp.localName + ? getRangeName(scriptSetup, defineProp.localName) + : undefined; + let propName = defineProp.name + ? getRangeName(scriptSetup, defineProp.name) + : defineProp.isModel + ? 'modelValue' + : localName; + if (defineProp.name) { + propName = propName!.replace(/['"]+/g, '') + } + return [propName, localName]; +} + +function getRangeName( + scriptSetup: NonNullable, + range: TextRange, + unwrap = false +) { + const offset = unwrap ? 1 : 0; + return scriptSetup.content.substring(range.start + offset, range.end - offset); +} \ No newline at end of file diff --git a/packages/language-core/lib/parsers/scriptSetupRanges.ts b/packages/language-core/lib/parsers/scriptSetupRanges.ts index ef3858993c..d173e7da4d 100644 --- a/packages/language-core/lib/parsers/scriptSetupRanges.ts +++ b/packages/language-core/lib/parsers/scriptSetupRanges.ts @@ -50,10 +50,11 @@ 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 defineProp: { + localName: TextRange | undefined; name: TextRange | undefined; - nameIsString: boolean; type: TextRange | undefined; modifierType?: TextRange | undefined; + runtimeType: TextRange | undefined; defaultValue: TextRange | undefined; required: boolean; isModel?: boolean; @@ -134,81 +135,136 @@ export function parseScriptSetupRanges( ) { const callText = getNodeText(ts, node.expression, ast); if (vueCompilerOptions.macros.defineModel.includes(callText)) { - let name: TextRange | undefined; + let localName: TextRange | undefined; + let propName: TextRange | undefined; let options: ts.Node | undefined; + + if ( + ts.isVariableDeclaration(parent) && + ts.isIdentifier(parent.name) + ) { + localName = _getStartEnd(parent.name); + } + if (node.arguments.length >= 2) { - name = _getStartEnd(node.arguments[0]); + propName = _getStartEnd(node.arguments[0]); options = node.arguments[1]; } else if (node.arguments.length >= 1) { if (ts.isStringLiteral(node.arguments[0])) { - name = _getStartEnd(node.arguments[0]); + propName = _getStartEnd(node.arguments[0]); } else { options = node.arguments[0]; } } + + let runtimeType: TextRange | undefined; + let defaultValue: TextRange | undefined; let required = false; if (options && ts.isObjectLiteralExpression(options)) { for (const property of options.properties) { - if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name) && getNodeText(ts, property.name, ast) === 'required' && property.initializer.kind === ts.SyntaxKind.TrueKeyword) { + if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name)) { + continue; + } + const text = getNodeText(ts, property.name, ast); + if (text === 'type') { + runtimeType = _getStartEnd(property.initializer); + } + else if (text === 'default') { + defaultValue = _getStartEnd(property.initializer); + } + else if (text === 'required' && property.initializer.kind === ts.SyntaxKind.TrueKeyword) { required = true; - break; } } } defineProp.push({ - name, - nameIsString: true, + localName, + name: propName, type: node.typeArguments?.length ? _getStartEnd(node.typeArguments[0]) : undefined, modifierType: node.typeArguments && node.typeArguments?.length >= 2 ? _getStartEnd(node.typeArguments[1]) : undefined, - defaultValue: undefined, + runtimeType, + defaultValue, required, isModel: true, }); } else if (callText === 'defineProp') { + let localName: TextRange | undefined; + let propName: TextRange | undefined; + let options: ts.Node | undefined; + + if ( + ts.isVariableDeclaration(parent) && + ts.isIdentifier(parent.name) + ) { + localName = _getStartEnd(parent.name); + } + + let runtimeType: TextRange | undefined; + let defaultValue: TextRange | undefined; + let required = false; if (definePropProposalA) { - let required = false; if (node.arguments.length >= 2) { - const secondArg = node.arguments[1]; - if (ts.isObjectLiteralExpression(secondArg)) { - for (const property of secondArg.properties) { - if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name) && getNodeText(ts, property.name, ast) === 'required' && property.initializer.kind === ts.SyntaxKind.TrueKeyword) { - required = true; - break; - } + options = node.arguments[1]; + } + if (node.arguments.length >= 1) { + propName = _getStartEnd(node.arguments[0]); + } + + if (options && ts.isObjectLiteralExpression(options)) { + for (const property of options.properties) { + if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name)) { + continue; + } + const text = getNodeText(ts, property.name, ast); + if (text === 'type') { + runtimeType = _getStartEnd(property.initializer); + } + else if (text === 'default') { + defaultValue = _getStartEnd(property.initializer); + } + else if (text === 'required' && property.initializer.kind === ts.SyntaxKind.TrueKeyword) { + required = true; } } } + } + else if (definePropProposalB) { + if (node.arguments.length >= 3) { + options = node.arguments[2]; + } + if (node.arguments.length >= 2) { + if (node.arguments[1].kind === ts.SyntaxKind.TrueKeyword) { + required = true; + } + } if (node.arguments.length >= 1) { - defineProp.push({ - name: _getStartEnd(node.arguments[0]), - nameIsString: true, - type: node.typeArguments?.length ? _getStartEnd(node.typeArguments[0]) : undefined, - defaultValue: undefined, - required, - }); + defaultValue = _getStartEnd(node.arguments[0]); } - else if (ts.isVariableDeclaration(parent)) { - defineProp.push({ - name: _getStartEnd(parent.name), - nameIsString: false, - type: node.typeArguments?.length ? _getStartEnd(node.typeArguments[0]) : undefined, - defaultValue: undefined, - required, - }); + + if (options && ts.isObjectLiteralExpression(options)) { + for (const property of options.properties) { + if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name)) { + continue; + } + const text = getNodeText(ts, property.name, ast); + if (text === 'type') { + runtimeType = _getStartEnd(property.initializer); + } + } } } - else if (definePropProposalB && ts.isVariableDeclaration(parent)) { - defineProp.push({ - name: _getStartEnd(parent.name), - nameIsString: false, - defaultValue: node.arguments.length >= 1 ? _getStartEnd(node.arguments[0]) : undefined, - type: node.typeArguments?.length ? _getStartEnd(node.typeArguments[0]) : undefined, - required: node.arguments.length >= 2 && node.arguments[1].kind === ts.SyntaxKind.TrueKeyword, - }); - } + + defineProp.push({ + localName, + name: propName, + type: node.typeArguments?.length ? _getStartEnd(node.typeArguments[0]) : undefined, + runtimeType, + defaultValue, + required, + }); } else if (vueCompilerOptions.macros.defineSlots.includes(callText)) { slots.define = parseDefineFunction(node); diff --git a/test-workspace/tsc/passedFixtures/vue2/tsconfig.json b/test-workspace/tsc/passedFixtures/vue2/tsconfig.json index 61eb1dbaff..7c1913090d 100644 --- a/test-workspace/tsc/passedFixtures/vue2/tsconfig.json +++ b/test-workspace/tsc/passedFixtures/vue2/tsconfig.json @@ -22,6 +22,7 @@ "../vue3/#4512", "../vue3/#4540", "../vue3/#4646", + "../vue3/#4649", "../vue3/components", "../vue3/defineEmits", "../vue3/defineModel", diff --git a/test-workspace/tsc/passedFixtures/vue3/#4649/main.vue b/test-workspace/tsc/passedFixtures/vue3/#4649/main.vue new file mode 100644 index 0000000000..f4384a3425 --- /dev/null +++ b/test-workspace/tsc/passedFixtures/vue3/#4649/main.vue @@ -0,0 +1,24 @@ + + + diff --git a/test-workspace/tsc/passedFixtures/vue3/#4649/model-comp.vue b/test-workspace/tsc/passedFixtures/vue3/#4649/model-comp.vue new file mode 100644 index 0000000000..a9f4fb8ba5 --- /dev/null +++ b/test-workspace/tsc/passedFixtures/vue3/#4649/model-comp.vue @@ -0,0 +1,18 @@ + diff --git a/test-workspace/tsc/passedFixtures/vue3/#4649/prop-comp.vue b/test-workspace/tsc/passedFixtures/vue3/#4649/prop-comp.vue new file mode 100644 index 0000000000..3f483c1427 --- /dev/null +++ b/test-workspace/tsc/passedFixtures/vue3/#4649/prop-comp.vue @@ -0,0 +1,21 @@ +