Skip to content

Commit

Permalink
feat(language-core): introduce strictVModel option (#5229)
Browse files Browse the repository at this point in the history
  • Loading branch information
KazariEX authored Mar 2, 2025
1 parent d782f70 commit 1b74d13
Show file tree
Hide file tree
Showing 10 changed files with 77 additions and 31 deletions.
67 changes: 53 additions & 14 deletions packages/language-core/lib/codegen/template/elementEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ export function* generateElementEvents(
for (const prop of node.props) {
if (
prop.type === CompilerDOM.NodeTypes.DIRECTIVE
&& prop.name === 'on'
&& prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION
&& prop.arg.isStatic
&& (
prop.name === 'on'
&& (prop.arg?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && prop.arg.isStatic)
|| options.vueCompilerOptions.strictVModel
&& prop.name === 'model'
&& (!prop.arg || prop.arg.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION && prop.arg.isStatic)
)
) {
ctx.currentComponent!.used = true;
if (!emitVar) {
Expand All @@ -37,21 +41,31 @@ export function* generateElementEvents(
yield `let ${eventsVar}!: __VLS_NormalizeEmits<typeof ${emitVar}>${endOfLine}`;
yield `let ${propsVar}!: __VLS_FunctionalComponentProps<typeof ${componentFunctionalVar}, typeof ${componentVNodeVar}>${endOfLine}`;
}
let source = prop.arg.loc.source;
let start = prop.arg.loc.start.offset;
let propPrefix = 'on';
let source = prop.arg?.loc.source ?? 'model-value';
let start = prop.arg?.loc.start.offset;
let propPrefix = 'on-';
let emitPrefix = '';
if (source.startsWith('vue:')) {
if (prop.name === 'model') {
propPrefix = 'onUpdate:';
emitPrefix = 'update:';
}
else if (source.startsWith('vue:')) {
source = source.slice('vue:'.length);
start = start + 'vue:'.length;
propPrefix = 'onVnode';
start = start! + 'vue:'.length;
propPrefix = 'onVnode-';
emitPrefix = 'vnode-';
}
yield `const ${ctx.getInternalVariable()}: __VLS_NormalizeComponentEvent<typeof ${propsVar}, typeof ${eventsVar}, '${camelize(propPrefix + '-' + source)}', '${emitPrefix}${source}', '${camelize(emitPrefix + source)}'> = {${newLine}`;
yield* generateEventArg(ctx, source, start, propPrefix);
yield `: `;
yield* generateEventExpression(options, ctx, prop);
yield `}${endOfLine}`;
yield `(): __VLS_NormalizeComponentEvent<typeof ${propsVar}, typeof ${eventsVar}, '${camelize(propPrefix + source)}', '${emitPrefix + source}', '${camelize(emitPrefix + source)}'> => ({${newLine}`;
if (prop.name === 'on') {
yield* generateEventArg(ctx, source, start!, propPrefix.slice(0, -1));
yield `: `;
yield* generateEventExpression(options, ctx, prop);
}
else {
yield `'${camelize(propPrefix + source)}': `;
yield* generateModelEventExpression(options, ctx, prop);
}
yield `})${endOfLine}`;
}
}
}
Expand Down Expand Up @@ -156,6 +170,31 @@ export function* generateEventExpression(
}
}

export function* generateModelEventExpression(
options: TemplateCodegenOptions,
ctx: TemplateCodegenContext,
prop: CompilerDOM.DirectiveNode
): Generator<Code> {
if (prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) {
yield `(...[$event]) => {${newLine}`;
yield* ctx.generateConditionGuards();
yield* generateInterpolation(
options,
ctx,
'template',
ctx.codeFeatures.verification,
prop.exp.content,
prop.exp.loc.start.offset,
prop.exp.loc
);
yield ` = $event${endOfLine}`;
yield `}`;
}
else {
yield `() => {}`;
}
}

export function isCompoundExpression(ts: typeof import('typescript'), ast: ts.SourceFile) {
let result = true;
if (ast.statements.length === 0) {
Expand Down
1 change: 1 addition & 0 deletions packages/language-core/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface VueCompilerOptions {
vitePressExtensions: string[];
petiteVueExtensions: string[];
jsxSlots: boolean;
strictVModel: boolean;
checkUnknownProps: boolean;
checkUnknownEvents: boolean;
checkUnknownDirectives: boolean;
Expand Down
1 change: 1 addition & 0 deletions packages/language-core/lib/utils/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ export function getDefaultCompilerOptions(target = 99, lib = 'vue', strictTempla
vitePressExtensions: [],
petiteVueExtensions: [],
jsxSlots: false,
strictVModel: strictTemplates,
checkUnknownProps: strictTemplates,
checkUnknownEvents: strictTemplates,
checkUnknownDirectives: strictTemplates,
Expand Down
5 changes: 5 additions & 0 deletions packages/language-core/schemas/vue-tsconfig.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@
"default": false,
"markdownDescription": "Strict props, component type-checking in templates."
},
"strictVModel": {
"type": "boolean",
"default": false,
"markdownDescription": "Strict `v-model` type-checking. If not set, uses the 'strictTemplates' value."
},
"checkUnknownProps": {
"type": "boolean",
"default": false,
Expand Down
1 change: 0 additions & 1 deletion test-workspace/tsc/passedFixtures/vue2/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
"../vue3/#4822",
"../vue3/#4826",
"../vue3/#4828",
"../vue3/#5225",
"../vue3/attrs",
"../vue3/components",
"../vue3/defineEmits",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"exclude": [
"../vue3_strictTemplate/#3140",
"../vue3_strictTemplate/#3718",
"../vue3_strictTemplate/defineModel",
"../vue3_strictTemplate/intrinsicProps",
]
}
3 changes: 0 additions & 3 deletions test-workspace/tsc/passedFixtures/vue3/#5225/comp.vue

This file was deleted.

13 changes: 0 additions & 13 deletions test-workspace/tsc/passedFixtures/vue3/#5225/main.vue

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<script setup lang="ts">
defineModel<number>('foo');
defineModel<number>('bar', { required: true });
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script setup lang="ts">
import Comp from './comp.vue';
let foo!: number;
let bar!: number | undefined;
</script>

<template>
<!-- @vue-expect-error -->
<Comp :bar="0" v-model:foo="foo" />
<Comp v-if="bar" v-model:bar="bar" />
</template>

0 comments on commit 1b74d13

Please sign in to comment.