Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: attribute inheritance #4103

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 23 additions & 7 deletions packages/language-core/lib/generators/script.ts
Expand Up @@ -30,6 +30,7 @@ export function* generate(
tagNames: Set<string>;
accessedGlobalVariables: Set<string>;
hasSlot: boolean;
hasInheritedAttrs: boolean;
} | undefined,
compilerOptions: ts.CompilerOptions,
vueCompilerOptions: VueCompilerOptions,
Expand Down Expand Up @@ -60,6 +61,7 @@ export function* generate(
emits: {},
expose: {},
slots: {},
options: {},
defineProp: [],
importSectionEndOffset: 0,
leadingCommentEndOffset: 0,
Expand Down Expand Up @@ -162,6 +164,15 @@ export function* generate(
};`;
},
} satisfies HelperType as HelperType,
OmitIndexSignature: {
get name() {
this.usage = true;
return `__VLS_OmitIndexSignature`;
},
get code() {
return `type __VLS_OmitIndexSignature<T> = { [K in keyof T as {} extends Record<K, unknown> ? never : K]: T[K]; };`;
}
} satisfies HelperType as HelperType,
};

let generatedTemplate = false;
Expand Down Expand Up @@ -587,7 +598,7 @@ type __VLS_PrettifyGlobal<T> = { [K in keyof T]: T[K]; } & {};
+ ` props: ${helperTypes.Prettify.name}<${helperTypes.OmitKeepDiscriminatedUnion.name}<typeof __VLS_fnPropsDefineComponent & typeof __VLS_fnPropsTypeOnly, keyof typeof __VLS_defaultProps>> & typeof __VLS_fnPropsSlots & typeof __VLS_defaultProps,\n`
+ ` expose(exposed: import('${vueCompilerOptions.lib}').ShallowUnwrapRef<${scriptSetupRanges.expose.define ? 'typeof __VLS_exposed' : '{}'}>): void,\n`
+ ` attrs: any,\n`
+ ` slots: ReturnType<typeof __VLS_template>,\n`
+ ` slots: ReturnType<typeof __VLS_template>[0],\n`
+ ` emit: typeof ${scriptSetupRanges.emits.name ?? '__VLS_emit'} & typeof __VLS_modelEmitsType,\n`
+ ` };\n`);
yield _(` })(),\n`); // __VLS_setup = (async () => {
Expand Down Expand Up @@ -785,7 +796,7 @@ type __VLS_PrettifyGlobal<T> = { [K in keyof T]: T[K]; } & {};
yield* generateComponent(functional);
yield _(`;\n`);
yield _(mode === 'return' ? 'return ' : 'export default ');
yield _(`{} as ${helperTypes.WithTemplateSlots.name}<typeof __VLS_component, ReturnType<typeof __VLS_template>>;\n`);
yield _(`{} as ${helperTypes.WithTemplateSlots.name}<typeof __VLS_component, ReturnType<typeof __VLS_template>[0]>;\n`);
}
else {
yield _(mode === 'return' ? 'return ' : 'export default ');
Expand Down Expand Up @@ -816,10 +827,10 @@ type __VLS_PrettifyGlobal<T> = { [K in keyof T]: T[K]; } & {};
}
yield _(`};\n`);
yield _(`},\n`);
yield* generateComponentOptions(functional);
yield* generateComponentOptions(functional, true);
yield _(`})`);
}
function* generateComponentOptions(functional: boolean): Generator<CodeAndStack> {
function* generateComponentOptions(functional: boolean, inheritAttrs: boolean): Generator<CodeAndStack> {
if (scriptSetup && scriptSetupRanges && !bypassDefineComponent) {

const ranges = scriptSetupRanges;
Expand Down Expand Up @@ -861,6 +872,11 @@ type __VLS_PrettifyGlobal<T> = { [K in keyof T]: T[K]; } & {};
yield _(`__VLS_propsOption_defineProp`);
});
}
if (inheritAttrs && templateCodegen?.hasInheritedAttrs) {
propsCodegens.push(function* () {
yield _(`{} as ${helperTypes.TypePropsToOption.name}<__VLS_PickNotAny<${helperTypes.OmitIndexSignature.name}<ReturnType<typeof __VLS_template>[1]>, {}>>`);
});
}

if (propsCodegens.length === 1) {
yield _(`props: `);
Expand Down Expand Up @@ -923,7 +939,7 @@ type __VLS_PrettifyGlobal<T> = { [K in keyof T]: T[K]; } & {};
const templateUsageVars = [...getTemplateUsageVars()];
yield _(`// @ts-ignore\n`);
yield _(`[${templateUsageVars.join(', ')}]\n`);
yield _(`return {};\n`);
yield _(`return [{}, {}] as const;\n`);
yield _(`}\n`);
}
}
Expand Down Expand Up @@ -966,7 +982,7 @@ type __VLS_PrettifyGlobal<T> = { [K in keyof T]: T[K]; } & {};
}
yield _(`};\n`); // return {
yield _(`},\n`); // setup() {
yield* generateComponentOptions(functional);
yield* generateComponentOptions(functional, false);
yield _(`});\n`); // defineComponent {
}
else if (script) {
Expand Down Expand Up @@ -1085,7 +1101,7 @@ type __VLS_PrettifyGlobal<T> = { [K in keyof T]: T[K]; } & {};
}
}

yield _(`return ${scriptSetupRanges?.slots.name ?? '__VLS_slots'};\n`);
yield _(`return [${scriptSetupRanges?.slots.name ?? '__VLS_slots'}, __VLS_inheritedAttrs] as const;\n`);

}
function* generateCssClassProperty(
Expand Down
46 changes: 42 additions & 4 deletions packages/language-core/lib/generators/template.ts
Expand Up @@ -7,6 +7,8 @@ import type { Code, CodeAndStack, Sfc, VueCodeInformation, VueCompilerOptions }
import { hyphenateAttr, hyphenateTag } from '../utils/shared';
import { collectVars, eachInterpolationSegment } from '../utils/transform';
import { disableAllFeatures, enableAllFeatures, getStack, mergeFeatureSettings } from './utils';
import type { ScriptRanges } from '../parsers/scriptRanges';
import type { ScriptSetupRanges } from '../parsers/scriptSetupRanges';

const presetInfos = {
disabledAll: disableAllFeatures({}),
Expand Down Expand Up @@ -72,9 +74,8 @@ export function* generate(
template: NonNullable<Sfc['template']>,
shouldGenerateScopedClasses: boolean,
stylesScopedClasses: Set<string>,
hasScriptSetupSlots: boolean,
slotsAssignName: string | undefined,
propsAssignName: string | undefined,
scriptRanges: ScriptRanges | undefined,
scriptSetupRanges: ScriptSetupRanges | undefined,
codegenStack: boolean,
) {

Expand Down Expand Up @@ -115,13 +116,17 @@ export function* generate(
? (code: Code): _CodeAndStack => ['inlineCss', code, getStack()]
: (code: Code): _CodeAndStack => ['inlineCss', code, ''];
const nativeTags = new Set(vueCompilerOptions.nativeTags);
const hasScriptSetupSlots = scriptSetupRanges?.slots.define;
const slotsAssignName = scriptSetupRanges?.slots.name;
const propsAssignName = scriptSetupRanges?.props.name;
const slots = new Map<string, {
name?: string;
loc?: number;
tagRange: [number, number];
varName: string;
nodeLoc: any;
}>();
const inheritedAttrVars = new Set<string>();
const slotExps = new Map<string, { varName: string; }>();
const tagOffsetsMap = collectTagOffsets();
const localVars = new Map<string, number>();
Expand All @@ -141,6 +146,7 @@ export function* generate(
let expectErrorToken: { errors: number; } | undefined;
let expectedErrorNode: CompilerDOM.CommentNode | undefined;
let elementIndex = 0;
let singleRootNode: CompilerDOM.ElementNode | undefined;

if (slotsAssignName) {
localVars.set(slotsAssignName, 1);
Expand All @@ -164,12 +170,15 @@ export function* generate(
yield _ts(';\n');
}

yield* generateInheritedAttrs();

yield* generateExtraAutoImport();

return {
tagOffsetsMap,
accessedGlobalVariables,
hasSlot,
hasInheritedAttrs: inheritedAttrVars.size > 0,
};

function collectTagOffsets() {
Expand Down Expand Up @@ -286,6 +295,16 @@ export function* generate(
yield _ts(`}`);
}

function* generateInheritedAttrs(): Generator<_CodeAndStack> {
yield _ts('var __VLS_inheritedAttrs!: {}');
if (vueCompilerOptions.experimentalInheritAttrs) {
for (const varName of inheritedAttrVars) {
yield _ts(` & typeof ${varName}`);
}
}
yield _ts(';\n');
}

function* generateStyleScopedClasses(): Generator<_CodeAndStack> {
yield _ts(`if (typeof __VLS_styleScopedClasses === 'object' && !Array.isArray(__VLS_styleScopedClasses)) {\n`);
for (const { className, offset } of scopedClasses) {
Expand Down Expand Up @@ -419,8 +438,13 @@ export function* generate(
}
}

const shouldInheritRootNodeAttrs = (scriptSetupRanges?.options.inheritAttrs ?? scriptRanges?.exportDefault?.inheritAttrsOption) !== 'false';

if (node.type === CompilerDOM.NodeTypes.ROOT) {
let prev: CompilerDOM.TemplateChildNode | undefined;
if (shouldInheritRootNodeAttrs && node.children.length === 1 && node.children[0].type === CompilerDOM.NodeTypes.ELEMENT) {
singleRootNode = node.children[0];
}
for (const childNode of node.children) {
yield* generateAstNode(childNode, parentEl, prev, componentCtxVar);
prev = childNode;
Expand Down Expand Up @@ -758,7 +782,7 @@ export function* generate(
yield _ts(`, ...__VLS_functionalComponentArgsRest(${var_functionalComponent}));\n`);
}
else {
// without strictTemplates, this only for instacne type
// without strictTemplates, this only for instance type
yield _ts(`const ${var_componentInstance} = ${var_functionalComponent}(`);
yield _ts('{ ');
yield* generateProps(node, props, 'extraReferences');
Expand Down Expand Up @@ -985,6 +1009,13 @@ export function* generate(
yield _ts(`let ${componentEventsVar}!: __VLS_NormalizeEmits<typeof ${componentCtxVar}.emit>;\n`);
}

if (
node.props.some(prop => prop.type === CompilerDOM.NodeTypes.DIRECTIVE && prop.name === 'bind' && prop.exp?.loc.source === '$attrs')
|| node === singleRootNode
) {
yield* generateInheritAttrs(var_functionalComponent);
}

yield _ts(`}\n`);
}

Expand Down Expand Up @@ -1754,6 +1785,13 @@ export function* generate(
}
}

function* generateInheritAttrs(functionalComponent: string): Generator<_CodeAndStack> {

const varAttrs = `__VLS_${elementIndex++}`;
inheritedAttrVars.add(varAttrs);
yield _ts(`var ${varAttrs}!: Parameters<typeof ${functionalComponent}>[0];\n`);
}

function* generateExtraAutoImport(): Generator<_CodeAndStack> {

if (!tempVars.length) {
Expand Down
6 changes: 6 additions & 0 deletions packages/language-core/lib/parsers/scriptRanges.ts
Expand Up @@ -13,6 +13,7 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc
componentsOption: TextRange | undefined,
componentsOptionNode: ts.ObjectLiteralExpression | undefined,
nameOption: TextRange | undefined,
inheritAttrsOption: string | undefined,
}) | undefined;

const bindings = hasScriptSetup ? parseBindingRanges(ts, ast) : [];
Expand All @@ -39,6 +40,7 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc
if (obj) {
let componentsOptionNode: ts.ObjectLiteralExpression | undefined;
let nameOptionNode: ts.Expression | undefined;
let inheritAttrsOption: string | undefined;
ts.forEachChild(obj, node => {
if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name)) {
const name = getNodeText(ts, node.name, ast);
Expand All @@ -48,6 +50,9 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc
if (name === 'name') {
nameOptionNode = node.initializer;
}
if (name === 'inheritAttrs') {
inheritAttrsOption = getNodeText(ts, node.initializer, ast);
}
}
});
exportDefault = {
Expand All @@ -58,6 +63,7 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc
componentsOption: componentsOptionNode ? _getStartEnd(componentsOptionNode) : undefined,
componentsOptionNode: withNode ? componentsOptionNode : undefined,
nameOption: nameOptionNode ? _getStartEnd(nameOptionNode) : undefined,
inheritAttrsOption,
};
}
}
Expand Down
17 changes: 17 additions & 0 deletions packages/language-core/lib/parsers/scriptSetupRanges.ts
Expand Up @@ -33,6 +33,9 @@ export function parseScriptSetupRanges(
name?: string;
define?: ReturnType<typeof parseDefineFunction>;
} = {};
const options: {
inheritAttrs?: string;
} = {};

const definePropProposalA = vueCompilerOptions.experimentalDefinePropProposal === 'kevinEdition' || ast.text.trimStart().startsWith('// @experimentalDefinePropProposal=kevinEdition');
const definePropProposalB = vueCompilerOptions.experimentalDefinePropProposal === 'johnsonEdition' || ast.text.trimStart().startsWith('// @experimentalDefinePropProposal=johnsonEdition');
Expand Down Expand Up @@ -79,6 +82,7 @@ export function parseScriptSetupRanges(
slots,
emits,
expose,
options,
defineProp,
};

Expand Down Expand Up @@ -238,6 +242,19 @@ export function parseScriptSetupRanges(
props.name = getNodeText(ts, parent.name, ast);
}
}
else if (vueCompilerOptions.macros.defineOptions.includes(callText)) {
if (node.arguments.length && ts.isObjectLiteralExpression(node.arguments[0])) {
const obj = node.arguments[0];
ts.forEachChild(obj, node => {
if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name)) {
const name = getNodeText(ts, node.name, ast);
if (name === 'inheritAttrs') {
options.inheritAttrs = getNodeText(ts, node.initializer, ast);
}
}
});
}
}
}
ts.forEachChild(node, child => {
parents.push(node);
Expand Down
9 changes: 3 additions & 6 deletions packages/language-core/lib/plugins/vue-tsx.ts
Expand Up @@ -179,9 +179,8 @@ function createTsx(
_sfc.template,
shouldGenerateScopedClasses(),
stylesScopedClasses(),
hasScriptSetupSlots(),
slotsAssignName(),
propsAssignName(),
scriptRanges(),
scriptSetupRanges(),
ctx.codegenStack,
);

Expand Down Expand Up @@ -222,9 +221,6 @@ function createTsx(
cssCodeStacks: inlineCssCodegenStacks,
};
});
const hasScriptSetupSlots = computed(() => !!scriptSetupRanges()?.slots.define);
const slotsAssignName = computed(() => scriptSetupRanges()?.slots.name);
const propsAssignName = computed(() => scriptSetupRanges()?.props.name);
const generatedScript = computed(() => {
const codes: Code[] = [];
const codeStacks: StackNode[] = [];
Expand All @@ -246,6 +242,7 @@ function createTsx(
accessedGlobalVariables: _template.accessedGlobalVariables,
hasSlot: _template.hasSlot,
tagNames: new Set(_template.tagOffsetsMap.keys()),
hasInheritedAttrs: _template.hasInheritedAttrs,
} : undefined,
ctx.compilerOptions,
ctx.vueCompilerOptions,
Expand Down
1 change: 1 addition & 0 deletions packages/language-core/lib/types.ts
Expand Up @@ -55,6 +55,7 @@ export interface VueCompilerOptions {
experimentalResolveStyleCssClasses: 'scoped' | 'always' | 'never';
experimentalModelPropName: Record<string, Record<string, boolean | Record<string, string> | Record<string, string>[]>>;
experimentalUseElementAccessInTemplate: boolean;
experimentalInheritAttrs: boolean;
}

export const pluginVersion = 2;
Expand Down
1 change: 1 addition & 0 deletions packages/language-core/lib/utils/ts.ts
Expand Up @@ -279,5 +279,6 @@ export function resolveVueCompilerOptions(vueOptions: Partial<VueCompilerOptions
}
},
experimentalUseElementAccessInTemplate: vueOptions.experimentalUseElementAccessInTemplate ?? false,
experimentalInheritAttrs: vueOptions.experimentalInheritAttrs ?? false,
};
}
5 changes: 5 additions & 0 deletions packages/language-core/schemas/vue-tsconfig.schema.json
Expand Up @@ -113,6 +113,11 @@
"johnsonEdition",
false
]
},
"experimentalInheritAttrs": {
"type": "boolean",
"default": false,
"markdownDescription": "https://github.com/vuejs/language-tools/pull/4103"
}
}
}
Expand Down
1 change: 1 addition & 0 deletions test-workspace/tsc/vue2_strictTemplate/tsconfig.json
Expand Up @@ -11,5 +11,6 @@
"exclude": [
"../vue3_strictTemplate/#3140",
"../vue3_strictTemplate/intrinsicProps",
"../vue3_strictTemplate/inherit-attrs",
]
}
@@ -0,0 +1,7 @@
<script setup lang="ts">
import child from './child.vue';
</script>

<template>
<child />
</template>
@@ -0,0 +1,9 @@
<script setup lang="ts">
defineEmits<{
foo: [string];
}>();

defineProps<{
bar?: string;
}>();
</script>
@@ -0,0 +1,11 @@
<script setup lang="ts">
import child from './child.vue';

defineOptions({
inheritAttrs: false,
});
</script>

<template>
<child />
</template>