Skip to content

Commit

Permalink
feat: support <script setup> types in template expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk committed Sep 19, 2021
1 parent ea96d6b commit 1aea6e6
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 48 deletions.
30 changes: 30 additions & 0 deletions packages/vscode-vue-languageservice/src/generators/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export function generate(
if (lsType === 'template') {
writeExportOptions();
writeConstNameOption();
writeExportTypes();
}

if (lsType === 'script' && scriptSetup) {
Expand Down Expand Up @@ -420,6 +421,35 @@ export function generate(
}
codeGen.addText(`};\n`);
}
function writeExportTypes() {

const bindingsArr: {
typeBindings: { start: number, end: number }[],
content: string,
}[] = [];

if (scriptSetupRanges && scriptSetup) {
bindingsArr.push({
typeBindings: scriptSetupRanges.typeBindings,
content: scriptSetup.content,
});
}
// if (scriptRanges && script) {
// bindingsArr.push({
// typeBindings: scriptRanges.typeBindings,
// content: script.content,
// });
// }

codeGen.addText('export {\n');
for (const bindings of bindingsArr) {
for (const typeBinding of bindings.typeBindings) {
const text = bindings.content.substring(typeBinding.start, typeBinding.end);
codeGen.addText(`${text} as __VLS_types_${text},\n`);
}
}
codeGen.addText('};\n');
}
function writeConstNameOption() {
codeGen.addText(`\n`);
if (script && scriptRanges?.exportDefault?.args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ export function generate(
tsCodeGen.addText(`}\n`);
}

tsCodeGen.addText(`declare const __VLS_slots:\n`);
tsCodeGen.addText(`declare var __VLS_slots:\n`);
for (const [exp, slot] of slotExps) {
tsCodeGen.addText(`Record<NonNullable<typeof ${exp}>, typeof ${slot.varName}> &\n`);
}
Expand All @@ -333,7 +333,6 @@ export function generate(
tsCodeGen.addText(`: typeof ${slot.varName},\n`);
}
tsCodeGen.addText(`};\n`);
tsCodeGen.addText(`export default __VLS_slots;\n`);

return {
codeGen: tsCodeGen,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function parseScriptRanges(ts: typeof import('typescript/lib/tsserverlibr
componentsOptionNode: ts.ObjectLiteralExpression | undefined,
}) | undefined;

const bindings = hasScriptSetup ? parseBindingRanges(ts, ast) : [];
const bindings = hasScriptSetup ? parseBindingRanges(ts, ast, false) : [];

ast.forEachChild(node => {
if (ts.isExportAssignment(node)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type EmitTypeArg = {
export function parseUnuseScriptSetupRanges(ts: typeof import('typescript/lib/tsserverlibrary'), ast: ts.SourceFile) {

const imports: TextRange[] = [];
const bindings = parseBindingRanges(ts, ast);
const bindings = parseBindingRanges(ts, ast, false);

let defineProps: {
range: TextRange,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ export function parseScriptSetupRanges(ts: typeof import('typescript/lib/tsserve
let emitsRuntimeArg: TextRange | undefined;
let emitsTypeArg: TextRange | undefined;

const bindings = parseBindingRanges(ts, ast);
const bindings = parseBindingRanges(ts, ast, false);
const typeBindings = parseBindingRanges(ts, ast, true);

ast.forEachChild(node => {
visitNode(node);
});

return {
bindings,
typeBindings,
withDefaultsArg,
propsRuntimeArg,
propsTypeArg,
Expand Down Expand Up @@ -66,24 +68,43 @@ export function parseScriptSetupRanges(ts: typeof import('typescript/lib/tsserve
}
}

export function parseBindingRanges(ts: typeof import('typescript/lib/tsserverlibrary'), sourceFile: ts.SourceFile) {
export function parseBindingRanges(ts: typeof import('typescript/lib/tsserverlibrary'), sourceFile: ts.SourceFile, isType: boolean) {
const bindings: TextRange[] = [];
sourceFile.forEachChild(node => {
if (ts.isVariableStatement(node)) {
for (const node_2 of node.declarationList.declarations) {
const vars = _findBindingVars(node_2.name);
for (const _var of vars) {
bindings.push(_var);
if (!isType) {
if (ts.isVariableStatement(node)) {
for (const node_2 of node.declarationList.declarations) {
const vars = _findBindingVars(node_2.name);
for (const _var of vars) {
bindings.push(_var);
}
}
}
else if (ts.isFunctionDeclaration(node)) {
if (node.name && ts.isIdentifier(node.name)) {
bindings.push(_getStartEnd(node.name));
}
}
else if (ts.isClassDeclaration(node)) {
if (node.name) {
bindings.push(_getStartEnd(node.name));
}
}
else if (ts.isEnumDeclaration(node)) {
bindings.push(_getStartEnd(node.name));
}
}
else if (ts.isFunctionDeclaration(node)) {
if (node.name && ts.isIdentifier(node.name)) {
else {
if (ts.isTypeAliasDeclaration(node)) {
bindings.push(_getStartEnd(node.name));
}
else if (ts.isInterfaceDeclaration(node)) {
bindings.push(_getStartEnd(node.name));
}
}
else if (ts.isImportDeclaration(node)) {
if (node.importClause && !node.importClause.isTypeOnly) {

if (ts.isImportDeclaration(node)) {
if (node.importClause && (isType || !node.importClause.isTypeOnly)) {
if (node.importClause.name) {
bindings.push(_getStartEnd(node.importClause.name));
}
Expand All @@ -99,14 +120,6 @@ export function parseBindingRanges(ts: typeof import('typescript/lib/tsserverlib
}
}
}
else if (ts.isClassDeclaration(node)) {
if (node.name) {
bindings.push(_getStartEnd(node.name));
}
}
else if (ts.isEnumDeclaration(node)) {
bindings.push(_getStartEnd(node.name));
}
});
return bindings;
function _getStartEnd(node: ts.Node) {
Expand Down
28 changes: 20 additions & 8 deletions packages/vscode-vue-languageservice/src/sourceFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { useSfcTemplateCompileResult } from './use/useSfcTemplateCompileResult';
import { useSfcTemplateScript } from './use/useSfcTemplateScript';
import { SearchTexts } from './utils/string';
import { untrack } from './utils/untrack';
import { parseScriptRanges } from './parsers/scriptRanges';
import { parseScriptSetupRanges } from './parsers/scriptSetupRanges';

export type SourceFile = ReturnType<typeof createSourceFile>;

Expand Down Expand Up @@ -103,26 +105,35 @@ export function createSourceFile(
computed(() => descriptor.scriptSetup),
context.modules.typescript,
);
const scriptRanges = computed(() =>
sfcScript.ast.value
? parseScriptRanges(context.modules.typescript, sfcScript.ast.value, !!descriptor.scriptSetup)
: undefined
);
const scriptSetupRanges = computed(() =>
sfcScriptSetup.ast.value
? parseScriptSetupRanges(context.modules.typescript, sfcScriptSetup.ast.value)
: undefined
);
const sfcScriptForTemplateLs = useSfcScriptGen(
'template',
context.modules.typescript,
uri,
document,
computed(() => descriptor.script),
computed(() => descriptor.scriptSetup),
computed(() => sfcScript.ast.value),
computed(() => sfcScriptSetup.ast.value),
computed(() => scriptRanges.value),
computed(() => scriptSetupRanges.value),
sfcTemplateCompileResult,
computed(() => sfcStyles.textDocuments.value),
);
const sfcScriptForScriptLs = useSfcScriptGen('script',
context.modules.typescript,
const sfcScriptForScriptLs = useSfcScriptGen(
'script',
uri,
document,
computed(() => descriptor.script),
computed(() => descriptor.scriptSetup),
computed(() => sfcScript.ast.value),
computed(() => sfcScriptSetup.ast.value),
computed(() => scriptRanges.value),
computed(() => scriptSetupRanges.value),
sfcTemplateCompileResult,
computed(() => sfcStyles.textDocuments.value),
);
Expand All @@ -139,6 +150,7 @@ export function createSourceFile(
document,
computed(() => descriptor.template),
computed(() => descriptor.scriptSetup),
computed(() => scriptSetupRanges.value),
computed(() => descriptor.styles),
templateScriptData,
sfcStyles.textDocuments,
Expand Down Expand Up @@ -212,7 +224,7 @@ export function createSourceFile(
getScriptAst: untrack(() => sfcScript.ast.value),
getScriptSetupAst: untrack(() => sfcScriptSetup.ast.value),
getVueHtmlDocument: untrack(() => vueHtmlDocument.value),
getScriptSetupData: untrack(() => sfcScriptForTemplateLs.scriptSetupRanges.value),
getScriptSetupData: untrack(() => scriptSetupRanges.value),
docLsScripts: untrack(() => ({
documents: [sfcScript.textDocument.value, sfcScriptSetup.textDocument.value].filter(shared.notEmpty),
sourceMaps: [sfcScript.sourceMap.value, sfcScriptSetup.sourceMap.value].filter(shared.notEmpty),
Expand Down
20 changes: 4 additions & 16 deletions packages/vscode-vue-languageservice/src/use/useSfcScriptGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,25 @@ import { TextDocument } from 'vscode-languageserver-textdocument';
import * as shared from '@volar/shared';
import { computed, Ref } from '@vue/reactivity';
import { TsSourceMap, TeleportSourceMap, TsMappingData, Range } from '../utils/sourceMaps';
import { parseScriptRanges } from '../parsers/scriptRanges';
import { parseScriptSetupRanges } from '../parsers/scriptSetupRanges';
import { generate as genScript } from '../generators/script';
import * as templateGen from '../generators/template_scriptSetup';
import type { parseScriptRanges } from '../parsers/scriptRanges';
import type { parseScriptSetupRanges } from '../parsers/scriptSetupRanges';

export function useSfcScriptGen(
lsType: 'template' | 'script',
ts: typeof import('typescript/lib/tsserverlibrary'),
vueUri: string,
vueDoc: Ref<TextDocument>,
script: Ref<shared.Sfc['script']>,
scriptSetup: Ref<shared.Sfc['scriptSetup']>,
scriptAst: Ref<ts.SourceFile | undefined>,
scriptSetupAst: Ref<ts.SourceFile | undefined>,
scriptRanges: Ref<ReturnType<typeof parseScriptRanges> | undefined>,
scriptSetupRanges: Ref<ReturnType<typeof parseScriptSetupRanges> | undefined>,
sfcTemplateCompileResult: ReturnType<(typeof import('./useSfcTemplateCompileResult'))['useSfcTemplateCompileResult']>,
sfcStyles: ReturnType<(typeof import('./useSfcStyles'))['useSfcStyles']>['textDocuments'],
) {

let version = 0;

const scriptRanges = computed(() =>
scriptAst.value
? parseScriptRanges(ts, scriptAst.value, !!scriptSetup.value)
: undefined
);
const scriptSetupRanges = computed(() =>
scriptSetupAst.value
? parseScriptSetupRanges(ts, scriptSetupAst.value)
: undefined
);
const htmlGen = computed(() => {
if (sfcTemplateCompileResult.value?.ast) {
return templateGen.generate(sfcTemplateCompileResult.value.ast);
Expand Down Expand Up @@ -105,7 +94,6 @@ export function useSfcScriptGen(

return {
lang,
scriptSetupRanges,
textDocument,
textDocumentTs,
sourceMap,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import * as templateGen from '../generators/template';
import * as cssClasses from '../parsers/cssClasses';
import { ITemplateScriptData, LanguageServiceContext } from '../types';
import * as SourceMaps from '../utils/sourceMaps';
import type { parseScriptRanges } from '../parsers/scriptRanges';
import type { parseScriptSetupRanges } from '../parsers/scriptSetupRanges';

export function useSfcTemplateScript(
vueUri: string,
vueDoc: Ref<TextDocument>,
template: Ref<shared.Sfc['template']>,
scriptSetup: Ref<shared.Sfc['scriptSetup']>,
scriptSetupRanges: Ref<ReturnType<typeof parseScriptSetupRanges> | undefined>,
styles: Ref<shared.Sfc['styles']>,
templateScriptData: ITemplateScriptData,
styleDocuments: Ref<{
Expand Down Expand Up @@ -69,6 +72,9 @@ export function useSfcTemplateScript(
const codeGen = createCodeGen<SourceMaps.TsMappingData>();

codeGen.addText(`import { __VLS_options, __VLS_name, __VLS_component } from './${vueFileName}';\n`);

writeImportTypes();

codeGen.addText(`declare var __VLS_ctxRaw: InstanceType<typeof __VLS_component>;\n`);
codeGen.addText(`declare var __VLS_ctx: __VLS_ExtractRawComponents<typeof __VLS_ctxRaw>;\n`);
codeGen.addText(`declare var __VLS_vmUnwrap: typeof __VLS_options & { components: { } };\n`);
Expand All @@ -93,6 +99,7 @@ export function useSfcTemplateScript(
codeGen.addText('declare var __VLS_styleScopedClasses: {\n');
const cssScopedMappings = writeCssClassProperties(cssScopedClasses.value, true);
codeGen.addText('};\n');
codeGen.addText(`{\n`);

/* Props */
codeGen.addText(`/* Props */\n`);
Expand All @@ -105,13 +112,45 @@ export function useSfcTemplateScript(
margeCodeGen(codeGen as CodeGen, templateCodeGens.value.codeGen as CodeGen);
}

codeGen.addText(`}\n`);
codeGen.addText(`export default __VLS_slots;\n`);

return {
...codeGen,
cssModuleMappingsArr,
cssScopedMappings,
ctxMappings,
};

function writeImportTypes() {

const bindingsArr: {
typeBindings: { start: number, end: number }[],
content: string,
}[] = [];

if (scriptSetupRanges.value && scriptSetup.value) {
bindingsArr.push({
typeBindings: scriptSetupRanges.value.typeBindings,
content: scriptSetup.value.content,
});
}
// if (scriptRanges.value && script.value) {
// bindingsArr.push({
// typeBindings: scriptRanges.value.typeBindings,
// content: script.value.content,
// });
// }

codeGen.addText('import {\n');
for (const bindings of bindingsArr) {
for (const typeBinding of bindings.typeBindings) {
const text = bindings.content.substring(typeBinding.start, typeBinding.end);
codeGen.addText(`__VLS_types_${text} as ${text},\n`);
}
}
codeGen.addText(`} from './${vueFileName}.__VLS_script';\n`);
}
function writeCssClassProperties(data: Map<string, Map<string, Set<[number, number]>>>, patchRename: boolean) {
const mappings = new Map<string, {
tsRange: {
Expand Down Expand Up @@ -163,7 +202,7 @@ export function useSfcTemplateScript(
const propsSet = new Set(templateScriptData.props);
const mappings: SourceMaps.Mapping<SourceMaps.TeleportMappingData>[] = [];
for (const propName of templateScriptData.context) {
codeGen.addText(`declare var `);
codeGen.addText(`declare let `);
const templateSideRange = codeGen.addText(propName);
codeGen.addText(`: typeof __VLS_ctx.`);
const scriptSideRange = codeGen.addText(propName);
Expand Down

0 comments on commit 1aea6e6

Please sign in to comment.