Skip to content

Commit 9da831f

Browse files
so1veA5rocksjohnsoncodehk
authored
feat(language-core): typed fallthrough attributes (#4103)
Co-authored-by: A5rocks <[email protected]> Co-authored-by: Johnson Chu <[email protected]>
1 parent 21e3b71 commit 9da831f

File tree

23 files changed

+264
-34
lines changed

23 files changed

+264
-34
lines changed

packages/language-core/lib/codegen/script/component.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export function* generateComponent(
3131
yield `}${endOfLine}`;
3232
yield `},${newLine}`;
3333
if (!ctx.bypassDefineComponent) {
34-
yield* generateScriptSetupOptions(options, ctx, scriptSetup, scriptSetupRanges);
34+
yield* generateScriptSetupOptions(options, ctx, scriptSetup, scriptSetupRanges, true);
3535
}
3636
if (options.sfc.script && options.scriptRanges) {
3737
yield* generateScriptOptions(options.sfc.script, options.scriptRanges);
@@ -65,24 +65,42 @@ export function* generateScriptSetupOptions(
6565
options: ScriptCodegenOptions,
6666
ctx: ScriptCodegenContext,
6767
scriptSetup: NonNullable<Sfc['scriptSetup']>,
68-
scriptSetupRanges: ScriptSetupRanges
68+
scriptSetupRanges: ScriptSetupRanges,
69+
inheritAttrs: boolean
6970
): Generator<Code> {
70-
yield* generatePropsOption(options, ctx, scriptSetup, scriptSetupRanges);
71+
yield* generatePropsOption(options, ctx, scriptSetup, scriptSetupRanges, inheritAttrs);
7172
yield* generateEmitsOption(options, scriptSetup, scriptSetupRanges);
7273
}
7374

7475
export function* generatePropsOption(
7576
options: ScriptCodegenOptions,
7677
ctx: ScriptCodegenContext,
7778
scriptSetup: NonNullable<Sfc['scriptSetup']>,
78-
scriptSetupRanges: ScriptSetupRanges
79+
scriptSetupRanges: ScriptSetupRanges,
80+
inheritAttrs: boolean
7981
) {
80-
if (options.vueCompilerOptions.target >= 3.5 && ctx.generatedPropsType) {
81-
yield `__typeProps: {} as __VLS_PublicProps,${newLine}`;
82+
83+
if (options.vueCompilerOptions.target >= 3.5) {
84+
const types = [];
85+
if (inheritAttrs && options.templateCodegen?.inheritedAttrVars.size) {
86+
types.push('typeof __VLS_template>[1]');
87+
}
88+
if (ctx.generatedPropsType) {
89+
types.push('{} as __VLS_PublicProps');
90+
}
91+
if (types.length) {
92+
yield `__typeProps: ${types.join(' & ')},${newLine}`;
93+
}
8294
}
8395
if (options.vueCompilerOptions.target < 3.5 || !ctx.generatedPropsType || scriptSetupRanges.props.withDefaults) {
8496
const codegens: (() => Generator<Code>)[] = [];
8597

98+
if (inheritAttrs && options.templateCodegen?.inheritedAttrVars.size) {
99+
codegens.push(function* () {
100+
yield `{} as ${ctx.helperTypes.TypePropsToOption.name}<__VLS_PickNotAny<${ctx.helperTypes.OmitIndexSignature.name}<ReturnType<typeof __VLS_template>[1]>, {}>>`;
101+
});
102+
}
103+
86104
if (ctx.generatedPropsType) {
87105
codegens.push(function* () {
88106
yield `{} as `;

packages/language-core/lib/codegen/script/context.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ export function createScriptCodegenContext(options: ScriptCodegenOptions) {
102102
};`;
103103
},
104104
} satisfies HelperType as HelperType,
105+
OmitIndexSignature: {
106+
get name() {
107+
this.used = true;
108+
return `__VLS_OmitIndexSignature`;
109+
},
110+
get code() {
111+
return `type __VLS_OmitIndexSignature<T> = { [K in keyof T as {} extends Record<K, unknown> ? never : K]: T[K]; };`;
112+
}
113+
} satisfies HelperType as HelperType,
105114
};
106115
const inlayHints: InlayHintInfo[] = [];
107116

packages/language-core/lib/codegen/script/internalComponent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export function* generateInternalComponent(
4949
yield `}${endOfLine}`; // return {
5050
yield `},${newLine}`; // setup() {
5151
if (options.sfc.scriptSetup && options.scriptSetupRanges && !ctx.bypassDefineComponent) {
52-
yield* generateScriptSetupOptions(options, ctx, options.sfc.scriptSetup, options.scriptSetupRanges);
52+
yield* generateScriptSetupOptions(options, ctx, options.sfc.scriptSetup, options.scriptSetupRanges, false);
5353
}
5454
if (options.sfc.script && options.scriptRanges) {
5555
yield* generateScriptOptions(options.sfc.script, options.scriptRanges);

packages/language-core/lib/codegen/script/scriptSetup.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export function* generateScriptSetup(
6767
+ ` props: ${ctx.helperTypes.Prettify.name}<typeof __VLS_functionalComponentProps & __VLS_PublicProps> & __VLS_BuiltInPublicProps,${newLine}`
6868
+ ` expose(exposed: import('${options.vueCompilerOptions.lib}').ShallowUnwrapRef<${scriptSetupRanges.expose.define ? 'typeof __VLS_exposed' : '{}'}>): void,${newLine}`
6969
+ ` attrs: any,${newLine}`
70-
+ ` slots: ReturnType<typeof __VLS_template>,${newLine}`
70+
+ ` slots: ReturnType<typeof __VLS_template>[0],${newLine}`
7171
+ ` emit: ${emitTypes.join(' & ')},${newLine}`
7272
+ ` }${endOfLine}`;
7373
yield ` })(),${newLine}`; // __VLS_setup = (async () => {
@@ -250,7 +250,7 @@ function* generateSetupFunction(
250250
yield* generateComponent(options, ctx, scriptSetup, scriptSetupRanges);
251251
yield endOfLine;
252252
yield `${syntax} `;
253-
yield `{} as ${ctx.helperTypes.WithTemplateSlots.name}<typeof __VLS_component, ReturnType<typeof __VLS_template>>${endOfLine}`;
253+
yield `{} as ${ctx.helperTypes.WithTemplateSlots.name}<typeof __VLS_component, ReturnType<typeof __VLS_template>[0]>${endOfLine}`;
254254
}
255255
else {
256256
yield `${syntax} `;

packages/language-core/lib/codegen/script/template.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function* generateTemplate(
3535
const templateUsageVars = [...getTemplateUsageVars(options, ctx)];
3636
yield `// @ts-ignore${newLine}`;
3737
yield `[${templateUsageVars.join(', ')}]${newLine}`;
38-
yield `return {}${endOfLine}`;
38+
yield `return [{}, {}] as const${endOfLine}`;
3939
yield `}${newLine}`;
4040
}
4141
}
@@ -154,10 +154,11 @@ function* generateTemplateContext(
154154
yield `// no template${newLine}`;
155155
if (!options.scriptSetupRanges?.slots.define) {
156156
yield `const __VLS_slots = {}${endOfLine}`;
157+
yield `const __VLS_inheritedAttrs = {}${endOfLine}`;
157158
}
158159
}
159160

160-
yield `return ${options.scriptSetupRanges?.slots.name ?? '__VLS_slots'}${endOfLine}`;
161+
yield `return [${options.scriptSetupRanges?.slots.name ?? '__VLS_slots'}, __VLS_inheritedAttrs] as const${endOfLine}`;
161162
}
162163

163164
function* generateCssClassProperty(

packages/language-core/lib/codegen/template/context.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ export function createTemplateCodegenContext(scriptSetupBindingNames: TemplateCo
125125
emptyClassOffsets,
126126
inlayHints,
127127
hasSlot: false,
128+
inheritedAttrVars: new Set(),
129+
singleRootNode: undefined as CompilerDOM.ElementNode | undefined,
128130
accessExternalVariable(name: string, offset?: number) {
129131
let arr = accessExternalVariables.get(name);
130132
if (!arr) {

packages/language-core/lib/codegen/template/element.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ export function* generateComponent(
224224
yield `, ...__VLS_functionalComponentArgsRest(${var_functionalComponent}))${endOfLine}`;
225225
}
226226
else {
227-
// without strictTemplates, this only for instacne type
227+
// without strictTemplates, this only for instance type
228228
yield `const ${var_componentInstance} = ${var_functionalComponent}({`;
229229
yield* generateElementProps(options, ctx, node, props, false);
230230
yield `}, ...__VLS_functionalComponentArgsRest(${var_functionalComponent}))${endOfLine}`;
@@ -271,6 +271,15 @@ export function* generateComponent(
271271
yield `let ${var_componentEvents}!: __VLS_NormalizeEmits<typeof ${var_componentEmit}>${endOfLine}`;
272272
}
273273

274+
if (
275+
node.props.some(prop => prop.type === CompilerDOM.NodeTypes.DIRECTIVE && prop.name === 'bind' && prop.exp?.loc.source === '$attrs')
276+
|| node === ctx.singleRootNode
277+
) {
278+
const varAttrs = ctx.getInternalVariable();
279+
ctx.inheritedAttrVars.add(varAttrs);
280+
yield `var ${varAttrs}!: Parameters<typeof ${var_functionalComponent}>[0];\n`;
281+
}
282+
274283
const slotDir = node.props.find(p => p.type === CompilerDOM.NodeTypes.DIRECTIVE && p.name === 'slot') as CompilerDOM.DirectiveNode;
275284
if (slotDir) {
276285
yield* generateComponentSlot(options, ctx, node, slotDir, currentComponent, componentCtxVar);
@@ -349,6 +358,13 @@ export function* generateElement(
349358
else {
350359
yield* generateElementChildren(options, ctx, node, currentComponent, componentCtxVar);
351360
}
361+
362+
if (
363+
node.props.some(prop => prop.type === CompilerDOM.NodeTypes.DIRECTIVE && prop.name === 'bind' && prop.exp?.loc.source === '$attrs')
364+
|| node === ctx.singleRootNode
365+
) {
366+
ctx.inheritedAttrVars.add(`__VLS_intrinsicElements.${node.tag}`);
367+
}
352368
}
353369

354370
function* generateVScope(

packages/language-core/lib/codegen/template/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface TemplateCodegenOptions {
1717
hasDefineSlots?: boolean;
1818
slotsAssignName?: string;
1919
propsAssignName?: string;
20+
inheritAttrs: boolean;
2021
}
2122

2223
export function* generateTemplate(options: TemplateCodegenOptions): Generator<Code, TemplateCodegenContext> {
@@ -43,6 +44,8 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator<Co
4344
yield endOfLine;
4445
}
4546

47+
yield* generateInheritedAttrs();
48+
4649
yield* ctx.generateAutoImportCompletion();
4750

4851
return ctx;
@@ -78,6 +81,14 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator<Co
7881
yield `}`;
7982
}
8083

84+
function* generateInheritedAttrs(): Generator<Code> {
85+
yield 'var __VLS_inheritedAttrs!: {}';
86+
for (const varName of ctx.inheritedAttrVars) {
87+
yield ` & typeof ${varName}`;
88+
}
89+
yield endOfLine;
90+
}
91+
8192
function* generateStyleScopedClasses(): Generator<Code> {
8293
yield `if (typeof __VLS_styleScopedClasses === 'object' && !Array.isArray(__VLS_styleScopedClasses)) {${newLine}`;
8394
for (const offset of ctx.emptyClassOffsets) {

packages/language-core/lib/codegen/template/templateChild.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,13 @@ export function* generateTemplateChild(
4747
}
4848
}
4949

50+
const shouldInheritRootNodeAttrs = options.inheritAttrs;
51+
5052
if (node.type === CompilerDOM.NodeTypes.ROOT) {
5153
let prev: CompilerDOM.TemplateChildNode | undefined;
54+
if (shouldInheritRootNodeAttrs && node.children.length === 1 && node.children[0].type === CompilerDOM.NodeTypes.ELEMENT) {
55+
ctx.singleRootNode = node.children[0];
56+
}
5257
for (const childNode of node.children) {
5358
yield* generateTemplateChild(options, ctx, childNode, currentComponent, prev, componentCtxVar);
5459
prev = childNode;

packages/language-core/lib/parsers/scriptRanges.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc
1313
componentsOption: TextRange | undefined,
1414
componentsOptionNode: ts.ObjectLiteralExpression | undefined,
1515
nameOption: TextRange | undefined,
16+
inheritAttrsOption: string | undefined,
1617
}) | undefined;
1718
let classBlockEnd: number | undefined;
1819

@@ -40,6 +41,7 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc
4041
if (obj) {
4142
let componentsOptionNode: ts.ObjectLiteralExpression | undefined;
4243
let nameOptionNode: ts.Expression | undefined;
44+
let inheritAttrsOption: string | undefined;
4345
ts.forEachChild(obj, node => {
4446
if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name)) {
4547
const name = getNodeText(ts, node.name, ast);
@@ -49,6 +51,9 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc
4951
if (name === 'name') {
5052
nameOptionNode = node.initializer;
5153
}
54+
if (name === 'inheritAttrs') {
55+
inheritAttrsOption = getNodeText(ts, node.initializer, ast);
56+
}
5257
}
5358
});
5459
exportDefault = {
@@ -59,6 +64,7 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc
5964
componentsOption: componentsOptionNode ? _getStartEnd(componentsOptionNode) : undefined,
6065
componentsOptionNode: withNode ? componentsOptionNode : undefined,
6166
nameOption: nameOptionNode ? _getStartEnd(nameOptionNode) : undefined,
67+
inheritAttrsOption,
6268
};
6369
}
6470
}

0 commit comments

Comments
 (0)