Skip to content

Commit a0511b8

Browse files
feat(language-core): add options for fine-grained configuration of strictTemplates (#5138)
Co-authored-by: Johnson Chu <[email protected]>
1 parent 3f138d6 commit a0511b8

File tree

9 files changed

+111
-37
lines changed

9 files changed

+111
-37
lines changed

Diff for: packages/component-meta/lib/base.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,8 @@ export function baseCreate(
138138
const directoryExists = languageServiceHost.directoryExists?.bind(languageServiceHost);
139139
const fileExists = languageServiceHost.fileExists.bind(languageServiceHost);
140140
const getScriptSnapshot = languageServiceHost.getScriptSnapshot.bind(languageServiceHost);
141-
const globalTypesName = `${commandLine.vueOptions.lib}_${commandLine.vueOptions.target}_${commandLine.vueOptions.strictTemplates}.d.ts`;
142-
const globalTypesContents = `// @ts-nocheck\nexport {};\n` + vue.generateGlobalTypes(commandLine.vueOptions.lib, commandLine.vueOptions.target, commandLine.vueOptions.strictTemplates);
141+
const globalTypesName = vue.getGlobalTypesFileName(commandLine.vueOptions);
142+
const globalTypesContents = `// @ts-nocheck\nexport {};\n` + vue.generateGlobalTypes(commandLine.vueOptions);
143143
const globalTypesSnapshot: ts.IScriptSnapshot = {
144144
getText: (start, end) => globalTypesContents.slice(start, end),
145145
getLength: () => globalTypesContents.length,

Diff for: packages/language-core/lib/codegen/globalTypes.ts

+23-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
1+
import type { VueCompilerOptions } from '../types';
12
import { getSlotsPropertyName } from '../utils/shared';
23

3-
export function generateGlobalTypes(lib: string, target: number, strictTemplates: boolean) {
4-
const fnPropsType = `(K extends { $props: infer Props } ? Props : any)${strictTemplates ? '' : ' & Record<string, unknown>'}`;
4+
export function getGlobalTypesFileName(options: VueCompilerOptions) {
5+
return [
6+
options.lib,
7+
options.target,
8+
options.checkUnknownProps,
9+
options.checkUnknownEvents,
10+
options.checkUnknownComponents,
11+
].map(v => {
12+
if (typeof v === 'boolean') {
13+
return v ? 1 : 0;
14+
}
15+
return v;
16+
}).join('_') + '.d.ts';
17+
}
18+
19+
export function generateGlobalTypes(options: VueCompilerOptions) {
20+
const { lib, target, checkUnknownProps, checkUnknownEvents, checkUnknownComponents } = options;
21+
const fnPropsType = `(K extends { $props: infer Props } ? Props : any)${checkUnknownProps ? '' : ' & Record<string, unknown>'}`;
522
let text = ``;
623
if (target < 3.5) {
724
text += `
@@ -49,7 +66,7 @@ export function generateGlobalTypes(lib: string, target: number, strictTemplates
4966
N1 extends keyof __VLS_GlobalComponents ? N1 extends N0 ? Pick<__VLS_GlobalComponents, N0 extends keyof __VLS_GlobalComponents ? N0 : never> : { [K in N0]: __VLS_GlobalComponents[N1] } :
5067
N2 extends keyof __VLS_GlobalComponents ? N2 extends N0 ? Pick<__VLS_GlobalComponents, N0 extends keyof __VLS_GlobalComponents ? N0 : never> : { [K in N0]: __VLS_GlobalComponents[N2] } :
5168
N3 extends keyof __VLS_GlobalComponents ? N3 extends N0 ? Pick<__VLS_GlobalComponents, N0 extends keyof __VLS_GlobalComponents ? N0 : never> : { [K in N0]: __VLS_GlobalComponents[N3] } :
52-
${strictTemplates ? '{}' : '{ [K in N0]: unknown }'};
69+
${checkUnknownComponents ? '{}' : '{ [K in N0]: unknown }'};
5370
type __VLS_FunctionalComponentProps<T, K> =
5471
'__ctx' extends keyof __VLS_PickNotAny<K, {}> ? K extends { __ctx?: { props?: infer P } } ? NonNullable<P> : never
5572
: T extends (props: infer P, ...args: any) => any ? P :
@@ -69,7 +86,7 @@ export function generateGlobalTypes(lib: string, target: number, strictTemplates
6986
: __VLS_IsFunction<Events, CamelizedEvent> extends true
7087
? { [K in onEvent]?: Events[CamelizedEvent] }
7188
: Props
72-
)${strictTemplates ? '' : ' & Record<string, unknown>'};
89+
)${checkUnknownEvents ? '' : ' & Record<string, unknown>'};
7390
// fix https://github.com/vuejs/language-tools/issues/926
7491
type __VLS_UnionToIntersection<U> = (U extends unknown ? (arg: U) => unknown : never) extends ((arg: infer P) => unknown) ? P : never;
7592
type __VLS_OverloadUnionInner<T, U = unknown> = U & T extends (...args: infer A) => infer R
@@ -143,8 +160,8 @@ export function generateGlobalTypes(lib: string, target: number, strictTemplates
143160
} & { props?: ${fnPropsType}; expose?(exposed: K): void; } }
144161
: T extends () => any ? (props: {}, ctx?: any) => ReturnType<T>
145162
: T extends (...args: any) => any ? T
146-
: (_: {}${strictTemplates ? '' : ' & Record<string, unknown>'}, ctx?: any) => { __ctx?: { attrs?: any, expose?: any, slots?: any, emit?: any, props?: {}${strictTemplates ? '' : ' & Record<string, unknown>'} } };
147-
function __VLS_asFunctionalElement<T>(tag: T, endTag?: T): (_: T${strictTemplates ? '' : ' & Record<string, unknown>'}) => void;
163+
: (_: {}${checkUnknownProps ? '' : ' & Record<string, unknown>'}, ctx?: any) => { __ctx?: { attrs?: any, expose?: any, slots?: any, emit?: any, props?: {}${checkUnknownProps ? '' : ' & Record<string, unknown>'} } };
164+
function __VLS_asFunctionalElement<T>(tag: T, endTag?: T): (_: T${checkUnknownComponents ? '' : ' & Record<string, unknown>'}) => void;
148165
function __VLS_functionalComponentArgsRest<T extends (...args: any) => any>(t: T): 2 extends Parameters<T>['length'] ? [any] : [];
149166
function __VLS_normalizeSlot<S>(s: S): S extends () => infer R ? (props: {}) => R : S;
150167
function __VLS_tryAsConstant<const T>(t: T): T;

Diff for: packages/language-core/lib/codegen/script/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type * as ts from 'typescript';
44
import type { ScriptRanges } from '../../parsers/scriptRanges';
55
import type { ScriptSetupRanges } from '../../parsers/scriptSetupRanges';
66
import type { Code, Sfc, VueCodeInformation, VueCompilerOptions } from '../../types';
7-
import { generateGlobalTypes } from '../globalTypes';
7+
import { generateGlobalTypes, getGlobalTypesFileName } from '../globalTypes';
88
import type { TemplateCodegenContext } from '../template/context';
99
import { endOfLine, generateSfcBlockSection, newLine } from '../utils';
1010
import { generateComponentSelf } from './componentSelf';
@@ -68,7 +68,7 @@ export function* generateScript(options: ScriptCodegenOptions): Generator<Code,
6868
yield `/// <reference types="${relativePath}" />${newLine}`;
6969
}
7070
else {
71-
yield `/// <reference types=".vue-global-types/${options.vueCompilerOptions.lib}_${options.vueCompilerOptions.target}_${options.vueCompilerOptions.strictTemplates}.d.ts" />${newLine}`;
71+
yield `/// <reference types=".vue-global-types/${getGlobalTypesFileName(options.vueCompilerOptions)}" />${newLine}`;
7272
}
7373
}
7474
else {
@@ -163,7 +163,7 @@ export function* generateScript(options: ScriptCodegenOptions): Generator<Code,
163163
}
164164
yield* ctx.localTypes.generate([...ctx.localTypes.getUsedNames()]);
165165
if (options.appendGlobalTypes) {
166-
yield generateGlobalTypes(options.vueCompilerOptions.lib, options.vueCompilerOptions.target, options.vueCompilerOptions.strictTemplates);
166+
yield generateGlobalTypes(options.vueCompilerOptions);
167167
}
168168

169169
if (options.sfc.scriptSetup) {

Diff for: packages/language-core/lib/codegen/template/element.ts

+26-3
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,14 @@ export function* generateComponent(
213213

214214
yield `// @ts-ignore${newLine}`;
215215
yield `const ${var_functionalComponent} = __VLS_asFunctionalComponent(${var_originalComponent}, new ${var_originalComponent}({${newLine}`;
216-
yield* generateElementProps(options, ctx, node, props, options.vueCompilerOptions.strictTemplates, false);
216+
yield* generateElementProps(
217+
options,
218+
ctx,
219+
node,
220+
props,
221+
options.vueCompilerOptions.checkUnknownProps,
222+
false
223+
);
217224
yield `}))${endOfLine}`;
218225

219226
yield `const `;
@@ -237,7 +244,15 @@ export function* generateComponent(
237244
tagOffsets[0] + node.tag.length,
238245
ctx.codeFeatures.verification,
239246
`{${newLine}`,
240-
...generateElementProps(options, ctx, node, props, options.vueCompilerOptions.strictTemplates, true, failedPropExps),
247+
...generateElementProps(
248+
options,
249+
ctx,
250+
node,
251+
props,
252+
options.vueCompilerOptions.checkUnknownProps,
253+
true,
254+
failedPropExps
255+
),
241256
`}`
242257
);
243258
yield `, ...__VLS_functionalComponentArgsRest(${var_functionalComponent}))${endOfLine}`;
@@ -330,7 +345,15 @@ export function* generateElement(
330345
startTagOffset + node.tag.length,
331346
ctx.codeFeatures.verification,
332347
`{${newLine}`,
333-
...generateElementProps(options, ctx, node, node.props, options.vueCompilerOptions.strictTemplates, true, failedPropExps),
348+
...generateElementProps(
349+
options,
350+
ctx,
351+
node,
352+
node.props,
353+
options.vueCompilerOptions.checkUnknownProps,
354+
true,
355+
failedPropExps
356+
),
334357
`}`
335358
);
336359
yield `)${endOfLine}`;

Diff for: packages/language-core/lib/codegen/template/slotOutlet.ts

+16-2
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,28 @@ export function* generateSlotOutlet(
5353
startTagOffset + node.tag.length,
5454
ctx.codeFeatures.verification,
5555
`{${newLine}`,
56-
...generateElementProps(options, ctx, node, node.props.filter(prop => prop !== nameProp), true, true),
56+
...generateElementProps(
57+
options,
58+
ctx,
59+
node,
60+
node.props.filter(prop => prop !== nameProp),
61+
true,
62+
true
63+
),
5764
`}`
5865
);
5966
yield `)${endOfLine}`;
6067
}
6168
else {
6269
yield `var ${varSlot} = {${newLine}`;
63-
yield* generateElementProps(options, ctx, node, node.props.filter(prop => prop !== nameProp), options.vueCompilerOptions.strictTemplates, true);
70+
yield* generateElementProps(
71+
options,
72+
ctx,
73+
node,
74+
node.props.filter(prop => prop !== nameProp),
75+
options.vueCompilerOptions.checkUnknownProps,
76+
true
77+
);
6478
yield `}${endOfLine}`;
6579

6680
if (

Diff for: packages/language-core/lib/types.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export type { SFCParseResult } from '@vue/compiler-sfc';
1010
export { VueEmbeddedCode };
1111

1212
export type RawVueCompilerOptions = Partial<Omit<VueCompilerOptions, 'target' | 'plugins'>> & {
13+
strictTemplates?: boolean;
1314
target?: 'auto' | 2 | 2.7 | 3 | 3.3 | 3.5 | 99 | number;
1415
plugins?: string[];
1516
};
@@ -28,7 +29,9 @@ export interface VueCompilerOptions {
2829
vitePressExtensions: string[];
2930
petiteVueExtensions: string[];
3031
jsxSlots: boolean;
31-
strictTemplates: boolean;
32+
checkUnknownProps: boolean;
33+
checkUnknownEvents: boolean;
34+
checkUnknownComponents: boolean;
3235
skipTemplateCodegen: boolean;
3336
fallthroughAttributes: boolean;
3437
dataAttributes: string[];

Diff for: packages/language-core/lib/utils/ts.ts

+19-17
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { camelize } from '@vue/shared';
22
import { posix as path } from 'path-browserify';
33
import type * as ts from 'typescript';
4-
import { generateGlobalTypes } from '../codegen/globalTypes';
4+
import { generateGlobalTypes, getGlobalTypesFileName } from '../codegen/globalTypes';
55
import { getAllExtensions } from '../languagePlugin';
66
import type { RawVueCompilerOptions, VueCompilerOptions, VueLanguagePlugin } from '../types';
77

@@ -206,7 +206,7 @@ export class CompilerOptionsResolver {
206206

207207
build(defaults?: VueCompilerOptions): VueCompilerOptions {
208208
const target = this.target ?? this.fallbackTarget;
209-
defaults ??= getDefaultCompilerOptions(target, this.options.lib);
209+
defaults ??= getDefaultCompilerOptions(target, this.options.lib, this.options.strictTemplates);
210210
return {
211211
...defaults,
212212
...this.options,
@@ -222,16 +222,7 @@ export class CompilerOptionsResolver {
222222
// https://github.com/vuejs/vue-next/blob/master/packages/compiler-dom/src/transforms/vModel.ts#L49-L51
223223
// https://vuejs.org/guide/essentials/forms.html#form-input-bindings
224224
experimentalModelPropName: Object.fromEntries(Object.entries(
225-
this.options.experimentalModelPropName ?? defaults.experimentalModelPropName ?? {
226-
'': {
227-
input: true
228-
},
229-
value: {
230-
input: { type: 'text' },
231-
textarea: true,
232-
select: true
233-
}
234-
}
225+
this.options.experimentalModelPropName ?? defaults.experimentalModelPropName
235226
).map(([k, v]) => [camelize(k), v])),
236227
};
237228
}
@@ -263,15 +254,17 @@ function resolvePath(scriptPath: string, root: string) {
263254
}
264255
}
265256

266-
export function getDefaultCompilerOptions(target = 99, lib = 'vue'): VueCompilerOptions {
257+
export function getDefaultCompilerOptions(target = 99, lib = 'vue', strictTemplates = false): VueCompilerOptions {
267258
return {
268259
target,
269260
lib,
270261
extensions: ['.vue'],
271262
vitePressExtensions: [],
272263
petiteVueExtensions: [],
273264
jsxSlots: false,
274-
strictTemplates: false,
265+
checkUnknownProps: strictTemplates,
266+
checkUnknownEvents: strictTemplates,
267+
checkUnknownComponents: strictTemplates,
275268
skipTemplateCodegen: false,
276269
fallthroughAttributes: false,
277270
dataAttributes: [],
@@ -297,7 +290,16 @@ export function getDefaultCompilerOptions(target = 99, lib = 'vue'): VueCompiler
297290
plugins: [],
298291
experimentalDefinePropProposal: false,
299292
experimentalResolveStyleCssClasses: 'scoped',
300-
experimentalModelPropName: null!
293+
experimentalModelPropName: {
294+
'': {
295+
input: true
296+
},
297+
value: {
298+
input: { type: 'text' },
299+
textarea: true,
300+
select: true
301+
}
302+
},
301303
};
302304
}
303305

@@ -327,8 +329,8 @@ export function setupGlobalTypes(rootDir: string, vueOptions: VueCompilerOptions
327329
}
328330
dir = parentDir;
329331
}
330-
const globalTypesPath = path.join(dir, 'node_modules', '.vue-global-types', `${vueOptions.lib}_${vueOptions.target}_${vueOptions.strictTemplates}.d.ts`);
331-
const globalTypesContents = `// @ts-nocheck\nexport {};\n` + generateGlobalTypes(vueOptions.lib, vueOptions.target, vueOptions.strictTemplates);
332+
const globalTypesPath = path.join(dir, 'node_modules', '.vue-global-types', getGlobalTypesFileName(vueOptions));
333+
const globalTypesContents = `// @ts-nocheck\nexport {};\n` + generateGlobalTypes(vueOptions);
332334
host.writeFile(globalTypesPath, globalTypesContents);
333335
return { absolutePath: globalTypesPath };
334336
} catch { }

Diff for: packages/language-core/schemas/vue-tsconfig.schema.json

+15
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,21 @@
4545
"default": false,
4646
"markdownDescription": "Strict props, component type-checking in templates."
4747
},
48+
"checkUnknownProps": {
49+
"type": "boolean",
50+
"default": false,
51+
"markdownDescription": "Check unknown props. If not set, uses the 'strictTemplates' value."
52+
},
53+
"checkUnknownEvents": {
54+
"type": "boolean",
55+
"default": false,
56+
"markdownDescription": "Check unknown events. If not set, uses the 'strictTemplates' value."
57+
},
58+
"checkUnknownComponents": {
59+
"type": "boolean",
60+
"default": false,
61+
"markdownDescription": "Check unknown components. If not set, uses the 'strictTemplates' value."
62+
},
4863
"skipTemplateCodegen": {
4964
"type": "boolean",
5065
"default": false,

Diff for: packages/language-server/lib/initialize.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { LanguageServer } from '@volar/language-server';
22
import { createTypeScriptProject } from '@volar/language-server/node';
3-
import { createParsedCommandLine, createVueLanguagePlugin, generateGlobalTypes, getAllExtensions, getDefaultCompilerOptions, VueCompilerOptions } from '@vue/language-core';
3+
import { createParsedCommandLine, createVueLanguagePlugin, generateGlobalTypes, getAllExtensions, getDefaultCompilerOptions, getGlobalTypesFileName, VueCompilerOptions } from '@vue/language-core';
44
import { Disposable, getFullLanguageServicePlugins, InitializeParams } from '@vue/language-service';
55
import type * as ts from 'typescript';
66

@@ -55,8 +55,8 @@ export function initialize(
5555
const directoryExists = project.typescript.languageServiceHost.directoryExists?.bind(project.typescript.languageServiceHost);
5656
const fileExists = project.typescript.languageServiceHost.fileExists.bind(project.typescript.languageServiceHost);
5757
const getScriptSnapshot = project.typescript.languageServiceHost.getScriptSnapshot.bind(project.typescript.languageServiceHost);
58-
const globalTypesName = `${vueCompilerOptions.lib}_${vueCompilerOptions.target}_${vueCompilerOptions.strictTemplates}.d.ts`;
59-
const globalTypesContents = `// @ts-nocheck\nexport {};\n` + generateGlobalTypes(vueCompilerOptions.lib, vueCompilerOptions.target, vueCompilerOptions.strictTemplates);
58+
const globalTypesName = getGlobalTypesFileName(vueCompilerOptions);
59+
const globalTypesContents = `// @ts-nocheck\nexport {};\n` + generateGlobalTypes(vueCompilerOptions);
6060
const globalTypesSnapshot: ts.IScriptSnapshot = {
6161
getText: (start, end) => globalTypesContents.slice(start, end),
6262
getLength: () => globalTypesContents.length,

0 commit comments

Comments
 (0)