Skip to content

fix(language-core): use return values of useSlots and useAttrs directly in the template #5185

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

Merged
merged 1 commit into from
Feb 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/language-core/lib/codegen/template/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export interface TemplateCodegenOptions {
hasDefineSlots?: boolean;
slotsAssignName?: string;
propsAssignName?: string;
slotsReferenceNames: Set<string>;
attrsReferenceNames: Set<string>;
inheritAttrs: boolean;
selfComponentName?: string;
}
Expand All @@ -34,6 +36,12 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator<Co
if (options.propsAssignName) {
ctx.addLocalVariable(options.propsAssignName);
}
for (const name of options.slotsReferenceNames) {
ctx.addLocalVariable(name);
}
for (const name of options.attrsReferenceNames) {
ctx.addLocalVariable(name);
}
const slotsPropertyName = getSlotsPropertyName(options.vueCompilerOptions.target);
ctx.specialVars.add(slotsPropertyName);
ctx.specialVars.add('$attrs');
Expand Down
61 changes: 29 additions & 32 deletions packages/language-core/lib/parsers/scriptSetupRanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,15 @@ type DefineOptions = {
inheritAttrs?: string;
};

type UseAttrs = CallExpressionRange;
type UseAttrs = CallExpressionRange & {
name?: string;
};

type UseCssModule = CallExpressionRange;

type UseSlots = CallExpressionRange;
type UseSlots = CallExpressionRange & {
name?: string;
};

type UseTemplateRef = CallExpressionRange & {
name?: string;
Expand Down Expand Up @@ -284,26 +288,21 @@ export function parseScriptSetupRanges(
}
else if (vueCompilerOptions.macros.defineProps.includes(callText)) {
defineProps = {
...parseCallExpression(node),
...parseCallExpressionAssignment(node, parent),
statement: getStatementRange(ts, parents, node, ast),
argNode: node.arguments[0]
};
if (ts.isVariableDeclaration(parent)) {
if (ts.isObjectBindingPattern(parent.name)) {
defineProps.destructured = new Map();
const identifiers = collectIdentifiers(ts, parent.name, []);
for (const { id, isRest, initializer } of identifiers) {
const name = _getNodeText(id);
if (isRest) {
defineProps.destructuredRest = name;
}
else {
defineProps.destructured.set(name, initializer);
}
if (ts.isVariableDeclaration(parent) && ts.isObjectBindingPattern(parent.name)) {
defineProps.destructured = new Map();
const identifiers = collectIdentifiers(ts, parent.name, []);
for (const { id, isRest, initializer } of identifiers) {
const name = _getNodeText(id);
if (isRest) {
defineProps.destructuredRest = name;
}
else {
defineProps.destructured.set(name, initializer);
}
}
else {
defineProps.name = _getNodeText(parent.name);
}
}
else if (
Expand All @@ -327,12 +326,9 @@ export function parseScriptSetupRanges(
}
else if (vueCompilerOptions.macros.defineEmits.includes(callText)) {
defineEmits = {
...parseCallExpression(node),
...parseCallExpressionAssignment(node, parent),
statement: getStatementRange(ts, parents, node, ast)
};
if (ts.isVariableDeclaration(parent)) {
defineEmits.name = _getNodeText(parent.name);
}
if (node.typeArguments?.length && ts.isTypeLiteralNode(node.typeArguments[0])) {
for (const member of node.typeArguments[0].members) {
if (ts.isCallSignatureDeclaration(member)) {
Expand All @@ -347,12 +343,9 @@ export function parseScriptSetupRanges(
}
else if (vueCompilerOptions.macros.defineSlots.includes(callText)) {
defineSlots = {
...parseCallExpression(node),
...parseCallExpressionAssignment(node, parent),
statement: getStatementRange(ts, parents, node, ast)
};
if (ts.isVariableDeclaration(parent)) {
defineSlots.name = _getNodeText(parent.name);
}
}
else if (vueCompilerOptions.macros.defineExpose.includes(callText)) {
defineExpose = parseCallExpression(node);
Expand All @@ -377,22 +370,19 @@ export function parseScriptSetupRanges(
}
}
else if (vueCompilerOptions.composables.useAttrs.includes(callText)) {
useAttrs.push(parseCallExpression(node));
useAttrs.push(parseCallExpressionAssignment(node, parent));
}
else if (vueCompilerOptions.composables.useCssModule.includes(callText)) {
useCssModule.push(parseCallExpression(node));
}
else if (vueCompilerOptions.composables.useSlots.includes(callText)) {
useSlots.push(parseCallExpression(node));
useSlots.push(parseCallExpressionAssignment(node, parent));
}
else if (
vueCompilerOptions.composables.useTemplateRef.includes(callText)
&& !node.typeArguments?.length
) {
useTemplateRef.push({
name: ts.isVariableDeclaration(parent) ? _getNodeText(parent.name) : undefined,
...parseCallExpression(node)
});
useTemplateRef.push(parseCallExpressionAssignment(node, parent));
}
}

Expand All @@ -415,6 +405,13 @@ export function parseScriptSetupRanges(
};
}

function parseCallExpressionAssignment(node: ts.CallExpression, parent: ts.Node) {
return {
name: ts.isVariableDeclaration(parent) ? _getNodeText(parent.name) : undefined,
...parseCallExpression(node),
};
}

function _getStartEnd(node: ts.Node) {
return getStartEnd(ts, node, ast);
}
Expand Down
79 changes: 56 additions & 23 deletions packages/language-core/lib/plugins/vue-tsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,67 +69,74 @@ export default plugin;

function createTsx(
fileName: string,
_sfc: Sfc,
sfc: Sfc,
ctx: Parameters<VueLanguagePlugin>[0],
appendGlobalTypes: boolean
) {
const ts = ctx.modules.typescript;

const getLang = computed(() => {
return !_sfc.script && !_sfc.scriptSetup ? 'ts'
: _sfc.scriptSetup && _sfc.scriptSetup.lang !== 'js' ? _sfc.scriptSetup.lang
: _sfc.script && _sfc.script.lang !== 'js' ? _sfc.script.lang
return !sfc.script && !sfc.scriptSetup ? 'ts'
: sfc.scriptSetup && sfc.scriptSetup.lang !== 'js' ? sfc.scriptSetup.lang
: sfc.script && sfc.script.lang !== 'js' ? sfc.script.lang
: 'js';
});

const getResolvedOptions = computed(() => {
const options = parseVueCompilerOptions(_sfc.comments);
const options = parseVueCompilerOptions(sfc.comments);
if (options) {
const resolver = new CompilerOptionsResolver();
resolver.addConfig(options, path.dirname(fileName));
return resolver.build(ctx.vueCompilerOptions);
}
return ctx.vueCompilerOptions;
});

const getScriptRanges = computed(() =>
_sfc.script
? parseScriptRanges(ts, _sfc.script.ast, !!_sfc.scriptSetup, false)
sfc.script
? parseScriptRanges(ts, sfc.script.ast, !!sfc.scriptSetup, false)
: undefined
);

const getScriptSetupRanges = computed(() =>
_sfc.scriptSetup
? parseScriptSetupRanges(ts, _sfc.scriptSetup.ast, getResolvedOptions())
sfc.scriptSetup
? parseScriptSetupRanges(ts, sfc.scriptSetup.ast, getResolvedOptions())
: undefined
);

const getSetupBindingNames = computedSet(
computed(() => {
const newNames = new Set<string>();
const bindings = getScriptSetupRanges()?.bindings;
if (_sfc.scriptSetup && bindings) {
if (sfc.scriptSetup && bindings) {
for (const { range } of bindings) {
newNames.add(_sfc.scriptSetup.content.slice(range.start, range.end));
newNames.add(sfc.scriptSetup.content.slice(range.start, range.end));
}
}
return newNames;
})
);

const getSetupImportComponentNames = computedSet(
computed(() => {
const newNames = new Set<string>();
const bindings = getScriptSetupRanges()?.bindings;
if (_sfc.scriptSetup && bindings) {
if (sfc.scriptSetup && bindings) {
for (const { range, moduleName, isDefaultImport, isNamespace } of bindings) {
if (
moduleName
&& isDefaultImport
&& !isNamespace
&& ctx.vueCompilerOptions.extensions.some(ext => moduleName.endsWith(ext))
) {
newNames.add(_sfc.scriptSetup.content.slice(range.start, range.end));
newNames.add(sfc.scriptSetup.content.slice(range.start, range.end));
}
}
}
return newNames;
})
);

const getSetupDestructuredPropNames = computedSet(
computed(() => {
const newNames = new Set(getScriptSetupRanges()?.defineProps?.destructured?.keys());
Expand All @@ -140,6 +147,7 @@ function createTsx(
return newNames;
})
);

const getSetupTemplateRefNames = computedSet(
computed(() => {
const newNames = new Set(
Expand All @@ -150,29 +158,52 @@ function createTsx(
return newNames;
})
);

const setupHasDefineSlots = computed(() => !!getScriptSetupRanges()?.defineSlots);

const getSetupSlotsAssignName = computed(() => getScriptSetupRanges()?.defineSlots?.name);

const getSetupPropsAssignName = computed(() => getScriptSetupRanges()?.defineProps?.name);

const getSetupInheritAttrs = computed(() => {
const value = getScriptSetupRanges()?.defineOptions?.inheritAttrs ?? getScriptRanges()?.exportDefault?.inheritAttrsOption;
return value !== 'false';
});

const getSetupSlotsReferenceName = computedSet(
computed(() => {
const newNames = new Set(
getScriptSetupRanges()?.useSlots.map(({ name }) => name).filter(name => name !== undefined)
);
return newNames;
})
);

const getSetupAttrsReferenceName = computedSet(
computed(() => {
const newNames = new Set(
getScriptSetupRanges()?.useAttrs.map(({ name }) => name).filter(name => name !== undefined)
);
return newNames;
})
);

const getComponentSelfName = computed(() => {
const { exportDefault } = getScriptRanges() ?? {};
if (_sfc.script && exportDefault?.nameOption) {
if (sfc.script && exportDefault?.nameOption) {
const { nameOption } = exportDefault;
return _sfc.script.content.slice(nameOption.start + 1, nameOption.end - 1);
return sfc.script.content.slice(nameOption.start + 1, nameOption.end - 1);
}
const { defineOptions } = getScriptSetupRanges() ?? {};
if (_sfc.scriptSetup && defineOptions?.name) {
if (sfc.scriptSetup && defineOptions?.name) {
return defineOptions.name;
}
const baseName = path.basename(fileName);
return capitalize(camelize(baseName.slice(0, baseName.lastIndexOf('.'))));
});
const getGeneratedTemplate = computed(() => {

if (getResolvedOptions().skipTemplateCodegen || !_sfc.template) {
const getGeneratedTemplate = computed(() => {
if (getResolvedOptions().skipTemplateCodegen || !sfc.template) {
return;
}

Expand All @@ -181,7 +212,7 @@ function createTsx(
ts,
compilerOptions: ctx.compilerOptions,
vueCompilerOptions: getResolvedOptions(),
template: _sfc.template,
template: sfc.template,
edited: getResolvedOptions().__test || (fileEditTimes.get(fileName) ?? 0) >= 2,
scriptSetupBindingNames: getSetupBindingNames(),
scriptSetupImportComponentNames: getSetupImportComponentNames(),
Expand All @@ -190,12 +221,13 @@ function createTsx(
hasDefineSlots: setupHasDefineSlots(),
slotsAssignName: getSetupSlotsAssignName(),
propsAssignName: getSetupPropsAssignName(),
slotsReferenceNames: getSetupSlotsReferenceName(),
attrsReferenceNames: getSetupAttrsReferenceName(),
inheritAttrs: getSetupInheritAttrs(),
selfComponentName: getComponentSelfName(),
});

let current = codegen.next();

while (!current.done) {
const code = current.value;
codes.push(code);
Expand All @@ -207,15 +239,17 @@ function createTsx(
codes,
};
});

const getGeneratedScript = computed(() => {
const codes: Code[] = [];
const linkedCodeMappings: Mapping[] = [];
let generatedLength = 0;

const codes: Code[] = [];
const codegen = generateScript({
ts,
compilerOptions: ctx.compilerOptions,
vueCompilerOptions: getResolvedOptions(),
sfc: _sfc,
sfc: sfc,
edited: getResolvedOptions().__test || (fileEditTimes.get(fileName) ?? 0) >= 2,
fileName,
lang: getLang(),
Expand All @@ -231,7 +265,6 @@ function createTsx(
fileEditTimes.set(fileName, (fileEditTimes.get(fileName) ?? 0) + 1);

let current = codegen.next();

while (!current.done) {
const code = current.value;
codes.push(code);
Expand Down