Skip to content

Commit

Permalink
fix: correct type inference of defineModel & defineEmits in generic
Browse files Browse the repository at this point in the history
  • Loading branch information
KazariEX committed Sep 7, 2024
1 parent 904167f commit 60b24a3
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 59 deletions.
9 changes: 4 additions & 5 deletions packages/language-core/lib/codegen/script/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function* generateComponent(
yield `}${endOfLine}`;
yield `},${newLine}`;
if (!ctx.bypassDefineComponent) {
const emitOptionCodes = [...generateEmitsOption(options, scriptSetup, scriptSetupRanges)];
const emitOptionCodes = [...generateEmitsOption(options, scriptSetupRanges)];
for (const code of emitOptionCodes) {
yield code;
}
Expand Down Expand Up @@ -64,7 +64,6 @@ export function* generateComponentSetupReturns(scriptSetupRanges: ScriptSetupRan

export function* generateEmitsOption(
options: ScriptCodegenOptions,
scriptSetup: NonNullable<Sfc['scriptSetup']>,
scriptSetupRanges: ScriptSetupRanges
): Generator<Code> {
const codes: {
Expand All @@ -75,16 +74,16 @@ export function* generateEmitsOption(
}[] = [];
if (scriptSetupRanges.defineProp.some(p => p.isModel)) {
codes.push({
optionExp: `{} as __VLS_NormalizeEmits<__VLS_ModelEmitsType>`,
typeOptionType: `__VLS_ModelEmitsType`,
optionExp: `{} as __VLS_NormalizeEmits<typeof __VLS_modelEmit>`,
typeOptionType: `__VLS_ModelEmit`,
});
}
if (scriptSetupRanges.emits.define) {
const { typeArg, hasUnionTypeArg } = scriptSetupRanges.emits.define;
codes.push({
optionExp: `{} as __VLS_NormalizeEmits<typeof ${scriptSetupRanges.emits.name ?? '__VLS_emit'}>`,
typeOptionType: typeArg && !hasUnionTypeArg
? scriptSetup.content.slice(typeArg.start, typeArg.end)
? `__VLS_Emit`
: undefined,
});
}
Expand Down
2 changes: 1 addition & 1 deletion packages/language-core/lib/codegen/script/componentSelf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function* generateComponentSelf(
yield `}${endOfLine}`; // return {
yield `},${newLine}`; // setup() {
if (options.sfc.scriptSetup && options.scriptSetupRanges && !ctx.bypassDefineComponent) {
const emitOptionCodes = [...generateEmitsOption(options, options.sfc.scriptSetup, options.scriptSetupRanges)];
const emitOptionCodes = [...generateEmitsOption(options, options.scriptSetupRanges)];
for (const code of emitOptionCodes) {
yield code;
}
Expand Down
96 changes: 61 additions & 35 deletions packages/language-core/lib/codegen/script/scriptSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function* generateScriptSetup(
emitTypes.push(`typeof ${scriptSetupRanges.emits.name ?? '__VLS_emit'}`);
}
if (scriptSetupRanges.defineProp.some(p => p.isModel)) {
emitTypes.push(`__VLS_ModelEmitsType`);
emitTypes.push(`typeof __VLS_modelEmit`);
}

yield ` return {} as {${newLine}`
Expand Down Expand Up @@ -132,31 +132,31 @@ function* generateSetupFunction(
let setupCodeModifies: [Code[], number, number][] = [];
const propsRange = scriptSetupRanges.props.withDefaults ?? scriptSetupRanges.props.define;
if (propsRange && scriptSetupRanges.props.define) {
const statement = scriptSetupRanges.props.define.statement;
if (scriptSetupRanges.props.define.typeArg) {
const { statement, typeArg } = scriptSetupRanges.props.define;
if (typeArg) {
setupCodeModifies.push([[
`let __VLS_typeProps!: `,
generateSfcBlockSection(scriptSetup, scriptSetupRanges.props.define.typeArg.start, scriptSetupRanges.props.define.typeArg.end, codeFeatures.all),
`type __VLS_Props = `,
generateSfcBlockSection(scriptSetup, typeArg.start, typeArg.end, codeFeatures.all),
endOfLine,
], statement.start, statement.start]);
setupCodeModifies.push([[`typeof __VLS_typeProps`], scriptSetupRanges.props.define.typeArg.start, scriptSetupRanges.props.define.typeArg.end]);
setupCodeModifies.push([[`__VLS_Props`], typeArg.start, typeArg.end]);
}
if (!scriptSetupRanges.props.name) {
if (statement.start === propsRange.start && statement.end === propsRange.end) {
setupCodeModifies.push([[`const __VLS_props = `], propsRange.start, propsRange.start]);
}
else {
if (scriptSetupRanges.props.define.typeArg) {
if (typeArg) {
setupCodeModifies.push([[
`const __VLS_props = `,
generateSfcBlockSection(scriptSetup, propsRange.start, scriptSetupRanges.props.define.typeArg.start, codeFeatures.all),
], statement.start, scriptSetupRanges.props.define.typeArg.start]);
generateSfcBlockSection(scriptSetup, propsRange.start, typeArg.start, codeFeatures.all),
], statement.start, typeArg.start]);
setupCodeModifies.push([[
generateSfcBlockSection(scriptSetup, scriptSetupRanges.props.define.typeArg.end, propsRange.end, codeFeatures.all),
generateSfcBlockSection(scriptSetup, typeArg.end, propsRange.end, codeFeatures.all),
`${endOfLine}`,
generateSfcBlockSection(scriptSetup, statement.start, propsRange.start, codeFeatures.all),
`__VLS_props`,
], scriptSetupRanges.props.define.typeArg.end, propsRange.end]);
], typeArg.end, propsRange.end]);
}
else {
setupCodeModifies.push([[
Expand All @@ -182,7 +182,44 @@ function* generateSetupFunction(
}
}
if (scriptSetupRanges.emits.define && !scriptSetupRanges.emits.name) {
setupCodeModifies.push([[`const __VLS_emit = `], scriptSetupRanges.emits.define.start, scriptSetupRanges.emits.define.start]);
const emitsRange = scriptSetupRanges.emits.define;
const { statement, typeArg } = emitsRange;
if (typeArg) {
setupCodeModifies.push([[
`type __VLS_Emit = `,
generateSfcBlockSection(scriptSetup, typeArg.start, typeArg.end, codeFeatures.all),
endOfLine,
], statement.start, statement.start]);
setupCodeModifies.push([[`__VLS_Emit`], typeArg.start, typeArg.end]);
}
if (!scriptSetupRanges.emits.name) {
if (statement.start === emitsRange.start && statement.end === emitsRange.end) {
setupCodeModifies.push([[`const __VLS_emit = `], emitsRange.start, emitsRange.start]);
}
else {
if (typeArg) {
setupCodeModifies.push([[
`const __VLS_emit = `,
generateSfcBlockSection(scriptSetup, emitsRange.start, typeArg.start, codeFeatures.all),
], statement.start, typeArg.start]);
setupCodeModifies.push([[
generateSfcBlockSection(scriptSetup, typeArg.end, emitsRange.end, codeFeatures.all),
`${endOfLine}`,
generateSfcBlockSection(scriptSetup, statement.start, emitsRange.start, codeFeatures.all),
`__VLS_emit`,
], typeArg.end, emitsRange.end]);
}
else {
setupCodeModifies.push([[
`const __VLS_emit = `,
generateSfcBlockSection(scriptSetup, emitsRange.start, emitsRange.end, codeFeatures.all),
`${endOfLine}`,
generateSfcBlockSection(scriptSetup, statement.start, emitsRange.start, codeFeatures.all),
`__VLS_emit`,
], statement.start, emitsRange.end]);
}
}
}
}
if (scriptSetupRanges.expose.define) {
if (scriptSetupRanges.expose.define?.typeArg) {
Expand Down Expand Up @@ -286,7 +323,7 @@ function* generateSetupFunction(
}

yield* generateComponentProps(options, ctx, scriptSetup, scriptSetupRanges, definePropMirrors);
yield* generateModelEmits(options, scriptSetup, scriptSetupRanges);
yield* generateModelEmit(scriptSetup, scriptSetupRanges);
yield `function __VLS_template() {${newLine}`;
const templateCodegenCtx = yield* generateTemplate(options, ctx, false);
yield `}${endOfLine}`;
Expand Down Expand Up @@ -324,7 +361,7 @@ function* generateComponentProps(
yield `,${newLine}`;
}

yield* generateEmitsOption(options, scriptSetup, scriptSetupRanges);
yield* generateEmitsOption(options, scriptSetupRanges);

yield `})${endOfLine}`;

Expand Down Expand Up @@ -420,40 +457,29 @@ function* generateComponentProps(
yield ` & `;
}
ctx.generatedPropsType = true;
yield `typeof __VLS_typeProps`;
yield `__VLS_Props`;
}
if (!ctx.generatedPropsType) {
yield `{}`;
}
yield endOfLine;
}

function* generateModelEmits(
options: ScriptCodegenOptions,
function* generateModelEmit(
scriptSetup: NonNullable<Sfc['scriptSetup']>,
scriptSetupRanges: ScriptSetupRanges
): Generator<Code> {
const defineModels = scriptSetupRanges.defineProp.filter(p => p.isModel);
if (defineModels.length) {
const generateDefineModels = function* () {
for (const defineModel of defineModels) {
const [propName, localName] = getPropAndLocalName(scriptSetup, defineModel);
yield `'update:${propName}': [${propName}:`;
yield* generateDefinePropType(scriptSetup, propName, localName, defineModel);
yield `]${endOfLine}`;
}
};
if (options.vueCompilerOptions.target >= 3.5) {
yield `type __VLS_ModelEmitsType = {${newLine}`;
yield* generateDefineModels();
yield `}${endOfLine}`;
}
else {
yield `const __VLS_modelEmitsType = (await import('${options.vueCompilerOptions.lib}')).defineEmits<{${newLine}`;
yield* generateDefineModels();
yield `}>()${endOfLine}`;
yield `type __VLS_ModelEmitsType = typeof __VLS_modelEmitsType${endOfLine}`;
yield `type __VLS_ModelEmit = {${newLine}`;
for (const defineModel of defineModels) {
const [propName, localName] = getPropAndLocalName(scriptSetup, defineModel);
yield `'update:${propName}': [${propName}:`;
yield* generateDefinePropType(scriptSetup, propName, localName, defineModel);
yield `]${endOfLine}`;
}
yield `}${endOfLine}`;
yield `const __VLS_modelEmit = defineEmits<__VLS_ModelEmit>()${endOfLine}`;
}
}

Expand Down
48 changes: 30 additions & 18 deletions packages/language-core/lib/parsers/scriptSetupRanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export function parseScriptSetupRanges(
const emits: {
name?: string;
define?: ReturnType<typeof parseDefineFunction> & {
statement: TextRange;
hasUnionTypeArg?: boolean;
};
} = {};
Expand Down Expand Up @@ -290,7 +291,10 @@ export function parseScriptSetupRanges(
}
}
else if (vueCompilerOptions.macros.defineEmits.includes(callText)) {
emits.define = parseDefineFunction(node);
emits.define = {
...parseDefineFunction(node),
statement: getStatementRange(ts, parents, node, ast)
};
if (ts.isVariableDeclaration(parent)) {
emits.name = getNodeText(ts, parent.name, ast);
}
Expand Down Expand Up @@ -326,25 +330,9 @@ export function parseScriptSetupRanges(
}
}

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(child);
statementRange ??= range;
statementRange.end = range.end;
});
break;
}
}
if (!statementRange) {
statementRange = _getStartEnd(node);
}

props.define = {
...parseDefineFunction(node),
statement: statementRange,
statement: getStatementRange(ts, parents, node, ast),
};

if (node.arguments.length) {
Expand Down Expand Up @@ -518,3 +506,27 @@ export function getNodeText(
const { start, end } = getStartEnd(ts, node, sourceFile);
return sourceFile.text.substring(start, end);
}

function getStatementRange(
ts: typeof import('typescript'),
parents: ts.Node[],
node: ts.Node,
sourceFile: 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);
statementRange ??= range;
statementRange.end = range.end;
});
break;
}
}
if (!statementRange) {
statementRange = getStartEnd(ts, node, sourceFile);
}
return statementRange;
}
1 change: 1 addition & 0 deletions test-workspace/tsc/passedFixtures/vue2/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"../vue3/#4649",
"../vue3/#4777",
"../vue3/#4820",
"../vue3/#4822",
"../vue3/components",
"../vue3/defineEmits",
"../vue3/defineModel",
Expand Down
8 changes: 8 additions & 0 deletions test-workspace/tsc/passedFixtures/vue3/#4822/comp.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script setup lang="ts" generic="T">
defineModel<T>();
defineEmits<{
(event: 'foo', arg: T): any;
}>();
</script>

<template></template>
19 changes: 19 additions & 0 deletions test-workspace/tsc/passedFixtures/vue3/#4822/main.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script lang="ts">
import { defineComponent } from 'vue';
import { exactType } from '../../shared';
import Comp from './comp.vue';
export default defineComponent({
components: {
Comp
}
});
</script>

<script setup lang="ts">
let foo!: number;
</script>

<template>
<Comp @foo="(arg) => exactType(arg, {} as number)" v-model="foo"></Comp>
</template>

0 comments on commit 60b24a3

Please sign in to comment.