Skip to content

Commit

Permalink
feat: fewer side effects codegen for <script setup> (#2582)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk committed Apr 4, 2023
1 parent f740e87 commit ce73539
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 135 deletions.
183 changes: 93 additions & 90 deletions packages/vue-language-core/src/generators/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ export function generate(
emitsTypeNums: 0,
exposeRuntimeArg: undefined,
importSectionEndOffset: 0,
notOnTopTypeExports: [],
defineProps: undefined,
propsAssignName: undefined,
propsRuntimeArg: undefined,
Expand Down Expand Up @@ -274,27 +273,42 @@ export function generate(
FileRangeCapabilities.full,
]);
}
function generateExportDefault() {
// fix https://github.com/johnsoncodehk/volar/issues/1127
codes.push([
'',
'scriptSetup',
0,
{ diagnostic: true },
]);
codes.push('export default ');
}
function generateExportDefaultEndMapping() {
if (!sfc.scriptSetup) {
return;
}
// fix https://github.com/johnsoncodehk/volar/issues/1127
codes.push([
'',
'scriptSetup',
sfc.scriptSetup.content.length,
{ diagnostic: true },
]);
codes.push(`\n`);
}
function generateScriptSetupAndTemplate() {

if (!sfc.scriptSetup || !scriptSetupRanges) {
return;
}

if (!scriptRanges?.exportDefault) {
// fix https://github.com/johnsoncodehk/volar/issues/1127
codes.push([
'',
'scriptSetup',
0,
{ diagnostic: true },
]);
codes.push('export default ');
}

const definePropMirrors: Record<string, [number, number]> = {};
let scriptSetupGeneratedOffset: number | undefined;

if (sfc.scriptSetup.generic) {
if (!scriptRanges?.exportDefault) {
generateExportDefault();
}
codes.push(`(<`);
codes.push([
sfc.scriptSetup.generic,
Expand All @@ -307,16 +321,31 @@ export function generate(
}
codes.push(`>`);
codes.push('(\n');
codes.push(`__VLS_props: typeof __VLS_setup['props'] & import('vue').VNodeProps,\n`);
codes.push(`__VLS_ctx: Pick<typeof __VLS_setup, 'expose' | 'attrs' | 'emit' | 'slots'>,\n`);
codes.push('__VLS_setup = (() => {\n');
scriptSetupGeneratedOffset = generateSetupFunction(true, 'none', definePropMirrors);

//#region exposed
codes.push(`const __VLS_exposed = `);
if (scriptSetupRanges.exposeRuntimeArg) {
addVirtualCode('scriptSetup', scriptSetupRanges.exposeRuntimeArg.start, scriptSetupRanges.exposeRuntimeArg.end);
}
else {
codes.push(`{}`);
}
codes.push(';\n');
//#endregion

//#region props
if (
(scriptSetupRanges.propsRuntimeArg && scriptSetupRanges.defineProps)
|| scriptSetupRanges.defineProp.length
) {
codes.push(`__VLS_props = (() => {\n`);
if (scriptSetupRanges.propsRuntimeArg && scriptSetupRanges.defineProps) {
codes.push(`const __VLS_return = (await import('vue')).`);
addVirtualCode('scriptSetup', scriptSetupRanges.defineProps.start, scriptSetupRanges.defineProps.end);
codes.push(`const __VLS_props = `);
addExtraReferenceVirtualCode('scriptSetup', scriptSetupRanges.defineProps.start, scriptSetupRanges.defineProps.end);
codes.push(`;\n`);
codes.push(`return {} as typeof __VLS_return`);
}
else if (scriptSetupRanges.defineProp.length) {
codes.push(`const __VLS_defaults = {\n`);
Expand All @@ -334,7 +363,7 @@ export function generate(
}
}
codes.push(`};\n`);
codes.push(`return {} as {\n`);
codes.push(`let __VLS_props!: {\n`);
for (const defineProp of scriptSetupRanges.defineProp) {
let propName = 'modelValue';
if (defineProp.name) {
Expand All @@ -356,70 +385,77 @@ export function generate(
}
codes.push(',\n');
}
codes.push(`}`);
codes.push(`};\n`);
}
codes.push(` & import('vue').VNodeProps`);;
if (scriptSetupRanges.slotsTypeArg) {
usedHelperTypes.ToTemplateSlots = true;
codes.push(` & { [K in keyof JSX.ElementChildrenAttribute]: __VLS_ToTemplateSlots<`);
addVirtualCode('scriptSetup', scriptSetupRanges.slotsTypeArg.start, scriptSetupRanges.slotsTypeArg.end);
addExtraReferenceVirtualCode('scriptSetup', scriptSetupRanges.slotsTypeArg.start, scriptSetupRanges.slotsTypeArg.end);
codes.push(`>; }`);
}
codes.push(`;\n`);
codes.push(`})()`);
}
else {
codes.push(`__VLS_props: import('vue').VNodeProps`);
codes.push(`const __VLS_props: {}`);
if (scriptSetupRanges.slotsTypeArg) {
usedHelperTypes.ToTemplateSlots = true;
codes.push(` & { [K in keyof JSX.ElementChildrenAttribute]: __VLS_ToTemplateSlots<`);
addVirtualCode('scriptSetup', scriptSetupRanges.slotsTypeArg.start, scriptSetupRanges.slotsTypeArg.end);
addExtraReferenceVirtualCode('scriptSetup', scriptSetupRanges.slotsTypeArg.start, scriptSetupRanges.slotsTypeArg.end);
codes.push(`>; }`);
}
if (scriptSetupRanges.propsTypeArg) {
codes.push(' & ');
addVirtualCode('scriptSetup', scriptSetupRanges.propsTypeArg.start, scriptSetupRanges.propsTypeArg.end);
addExtraReferenceVirtualCode('scriptSetup', scriptSetupRanges.propsTypeArg.start, scriptSetupRanges.propsTypeArg.end);
}
codes.push(`;\n`);
}
codes.push(',\n');
codes.push('__VLS_ctx = (() => {\n');
scriptSetupGeneratedOffset = generateSetupFunction(true, definePropMirrors);
codes.push('return {\n');
codes.push('attrs: {} as any,\n');
codes.push('slots: {} as typeof __VLS_setup extends () => Promise<{ slots: infer T }> ? T : never,\n');
//#endregion

//#region emit
codes.push('emit: ');
//#region emits
codes.push(`const __VLS_emit = `);
if (scriptSetupRanges.emitsTypeArg) {
codes.push('{} as ');
addVirtualCode('scriptSetup', scriptSetupRanges.emitsTypeArg.start, scriptSetupRanges.emitsTypeArg.end);
codes.push(',\n');
addExtraReferenceVirtualCode('scriptSetup', scriptSetupRanges.emitsTypeArg.start, scriptSetupRanges.emitsTypeArg.end);
codes.push(';\n');
}
else if (scriptSetupRanges.emitsRuntimeArg) {
codes.push(`(await import('vue')).defineEmits(`);
addVirtualCode('scriptSetup', scriptSetupRanges.emitsRuntimeArg.start, scriptSetupRanges.emitsRuntimeArg.end);
codes.push('),\n');
addExtraReferenceVirtualCode('scriptSetup', scriptSetupRanges.emitsRuntimeArg.start, scriptSetupRanges.emitsRuntimeArg.end);
codes.push(');\n');
}
else {
codes.push('{} as any,\n');
codes.push('{} as any;\n');
}
//#endregion

//#region expose
codes.push('expose(__VLS_exposed: typeof __VLS_setup extends () => Promise<{ exposed: infer T }> ? T : never) { },\n');
//#endregion

codes.push('return {} as {\n');
codes.push(`props: typeof __VLS_props,\n`);
codes.push('expose(exposed: typeof __VLS_exposed): void,\n');
codes.push('attrs: any,\n');
codes.push('slots: ReturnType<typeof __VLS_template>,\n');
codes.push('emit: typeof __VLS_emit');
codes.push('};\n');
codes.push('})(),\n');
codes.push(') => ({} as JSX.Element & { __ctx?: typeof __VLS_ctx, __props?: typeof __VLS_props }))');
codes.push(') => ({} as any))');
}
else if (!sfc.script) {
// no script block, generate script setup code at root
scriptSetupGeneratedOffset = generateSetupFunction(false, 'export', definePropMirrors);
}
else {
if (!scriptRanges?.exportDefault) {
generateExportDefault();
}
codes.push('(() => {\n');
scriptSetupGeneratedOffset = generateSetupFunction(false, definePropMirrors);
codes.push(`return {} as typeof __VLS_setup extends () => Promise<infer T> ? T : never;\n`);
scriptSetupGeneratedOffset = generateSetupFunction(false, 'return', definePropMirrors);
codes.push(`})()`);
}

if (scriptRanges?.exportDefault && scriptRanges.exportDefault.expression.end !== scriptRanges.exportDefault.end) {
addVirtualCode('script', scriptRanges.exportDefault.expression.end, scriptRanges.exportDefault.end);
}
generateExportDefaultEndMapping();

if (scriptSetupGeneratedOffset !== undefined) {
for (const defineProp of scriptSetupRanges.defineProp) {
if (!defineProp.name) {
Expand All @@ -439,27 +475,13 @@ export function generate(
}
}
}

if (scriptRanges?.exportDefault && scriptRanges.exportDefault.expression.end !== scriptRanges.exportDefault.end) {
addVirtualCode('script', scriptRanges.exportDefault.expression.end, scriptRanges.exportDefault.end);
}
codes.push(`;`);
// fix https://github.com/johnsoncodehk/volar/issues/1127
codes.push([
'',
'scriptSetup',
sfc.scriptSetup.content.length,
{ diagnostic: true },
]);
codes.push(`\n`);
}
function generateSetupFunction(functional: boolean, definePropMirrors: Record<string, [number, number]>) {
function generateSetupFunction(functional: boolean, mode: 'return' | 'export' | 'none', definePropMirrors: Record<string, [number, number]>) {

if (!scriptSetupRanges || !sfc.scriptSetup) {
return;
}


const definePropProposalA = sfc.scriptSetup.content.trimStart().startsWith('// @experimentalDefinePropProposal=kevinEdition') || vueCompilerOptions.experimentalDefinePropProposal === 'kevinEdition';
const definePropProposalB = sfc.scriptSetup.content.trimStart().startsWith('// @experimentalDefinePropProposal=johnsonEdition') || vueCompilerOptions.experimentalDefinePropProposal === 'johnsonEdition';

Expand All @@ -481,23 +503,9 @@ declare function defineProp<T>(value?: T | (() => T), required?: boolean, rest?:
`.trim() + '\n');
}

codes.push('const __VLS_setup = async () => {\n');

const scriptSetupGeneratedOffset = muggle.getLength(codes) - scriptSetupRanges.importSectionEndOffset;

if (sfc.scriptSetup.generic && scriptSetupRanges.propsRuntimeArg && scriptSetupRanges.defineProps) {
addVirtualCode('scriptSetup', scriptSetupRanges.importSectionEndOffset, scriptSetupRanges.defineProps.start);
codes.push('__VLS_props');
addVirtualCode('scriptSetup', scriptSetupRanges.defineProps.end);
}
else if (sfc.scriptSetup.generic && scriptSetupRanges.propsTypeArg) {
addVirtualCode('scriptSetup', scriptSetupRanges.importSectionEndOffset, scriptSetupRanges.propsTypeArg.start);
codes.push('typeof __VLS_props');
addVirtualCode('scriptSetup', scriptSetupRanges.propsTypeArg.end);
}
else {
addVirtualCode('scriptSetup', scriptSetupRanges.importSectionEndOffset);
}
addVirtualCode('scriptSetup', scriptSetupRanges.importSectionEndOffset);

if (scriptSetupRanges.propsTypeArg && scriptSetupRanges.withDefaultsArg) {
// fix https://github.com/johnsoncodehk/volar/issues/1187
Expand Down Expand Up @@ -642,29 +650,24 @@ declare function defineProp<T>(value?: T | (() => T), required?: boolean, rest?:

generateTemplate();

if (functional) {
codes.push('return {\n');
codes.push('slots: __VLS_template(),\n');
codes.push('exposed: ');
if (scriptSetupRanges.exposeRuntimeArg) {
addVirtualCode('scriptSetup', scriptSetupRanges.exposeRuntimeArg.start, scriptSetupRanges.exposeRuntimeArg.end);
}
else {
codes.push(`{}`);
}
codes.push(',\n');
codes.push('};\n');
if (mode === 'return') {
codes.push(`return `);
}
else {
else if (mode === 'export') {
generateExportDefault();
}
if (mode === 'return' || mode === 'export') {
if (!vueCompilerOptions.skipTemplateCodegen && (htmlGen?.hasSlot || scriptSetupRanges?.slotsTypeArg)) {
usedHelperTypes.WithTemplateSlots = true;
codes.push(`return {} as __VLS_WithTemplateSlots<typeof __VLS_publicComponent, ReturnType<typeof __VLS_template>>;\n`);
codes.push(`{} as __VLS_WithTemplateSlots<typeof __VLS_publicComponent, ReturnType<typeof __VLS_template>>;`);
}
else {
codes.push(`return {} as typeof __VLS_publicComponent;\n`);
codes.push(`{} as typeof __VLS_publicComponent;`);
}
}
codes.push(`};\n`);
if (mode === 'export') {
generateExportDefaultEndMapping();
}

return scriptSetupGeneratedOffset;
}
Expand Down
5 changes: 0 additions & 5 deletions packages/vue-language-core/src/parsers/scriptSetupRanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ export function parseScriptSetupRanges(
) {

let foundNonImportExportNode = false;
let notOnTopTypeExports: TextRange[] = [];
let importSectionEndOffset = 0;
let withDefaultsArg: TextRange | undefined;
let propsAssignName: string | undefined;
Expand Down Expand Up @@ -48,15 +47,11 @@ export function parseScriptSetupRanges(
importSectionEndOffset = node.getStart(ast, true);
foundNonImportExportNode = true;
}
else if (isTypeExport && foundNonImportExportNode) {
notOnTopTypeExports.push(_getStartEnd(node));
}
});
ast.forEachChild(child => visitNode(child, ast));

return {
importSectionEndOffset,
notOnTopTypeExports,
bindings,
withDefaultsArg,
defineProps,
Expand Down
2 changes: 1 addition & 1 deletion packages/vue-language-service/src/languageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ function resolvePlugins(
isSupportedDocument: (document) => document.languageId === 'jade',
vueCompilerOptions,
});
plugins.vue ??= createVuePlugin(vueCompilerOptions);
plugins.vue ??= createVuePlugin();
plugins.css ??= createCssPlugin();
plugins['pug-beautify'] ??= createPugFormatPlugin();
plugins.json ??= createJsonPlugin(settings?.json);
Expand Down
21 changes: 1 addition & 20 deletions packages/vue-language-service/src/plugins/vue.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { parseScriptSetupRanges } from '@volar/vue-language-core';
import { LanguageServicePlugin } from '@volar/language-service';
import * as html from 'vscode-html-languageservice';
import * as vscode from 'vscode-languageserver-protocol';
import { TextDocument } from 'vscode-languageserver-textdocument';
import createHtmlPlugin from '@volar-plugins/html';
import * as vue from '@volar/vue-language-core';
import { VueCompilerOptions } from '../types';
import { loadLanguageBlocks } from './data';

let sfcDataProvider: html.IHTMLDataProvider | undefined;

export default (vueCompilerOptions: VueCompilerOptions): LanguageServicePlugin => (context) => {
export default (): LanguageServicePlugin => (context) => {

const htmlPlugin = createHtmlPlugin({ validLang: 'vue', disableCustomData: true })(context);

Expand Down Expand Up @@ -46,23 +44,6 @@ export default (vueCompilerOptions: VueCompilerOptions): LanguageServicePlugin =

const result: vscode.Diagnostic[] = [];
const sfc = vueSourceFile.sfc;

if (sfc.scriptSetup && sfc.scriptSetupAst) {
const scriptSetupRanges = parseScriptSetupRanges(_ts.module, sfc.scriptSetupAst, vueCompilerOptions);
for (const range of scriptSetupRanges.notOnTopTypeExports) {
result.push(vscode.Diagnostic.create(
{
start: document.positionAt(range.start + sfc.scriptSetup.startTagEnd),
end: document.positionAt(range.end + sfc.scriptSetup.startTagEnd),
},
'type and interface export statements must be on the top in <script setup>',
vscode.DiagnosticSeverity.Warning,
undefined,
'volar',
));
}
}

const program = _ts.languageService.getProgram();

if (program && !program.getSourceFile(vueSourceFile.mainScriptName)) {
Expand Down

0 comments on commit ce73539

Please sign in to comment.