@vue.ts/language
Version:
Vue 3 Language Helper
290 lines (284 loc) • 9 kB
JavaScript
;
const node_module = require('node:module');
const path = require('node:path');
const typescript = require('@volar/typescript');
const vue = require('@vue/language-core');
const common = require('@vue.ts/common');
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
function _interopNamespaceCompat(e) {
if (e && typeof e === 'object' && 'default' in e) return e;
const n = Object.create(null);
if (e) {
for (const k in e) {
n[k] = e[k];
}
}
n.default = e;
return n;
}
const path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
const vue__namespace = /*#__PURE__*/_interopNamespaceCompat(vue);
const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
function createLanguage(tsconfigPath, ts = require$1("typescript")) {
const tsconfig = common.normalizePath(tsconfigPath);
return createLanguageWorker(
() => vue__namespace.createParsedCommandLine(ts, ts.sys, tsconfigPath),
path__namespace.dirname(tsconfig),
tsconfig,
ts
);
}
function createLanguageWorker(loadParsedCommandLine, rootPath, configFileName, ts) {
let parsedCommandLine = loadParsedCommandLine();
let fileNames = parsedCommandLine.fileNames.map(common.normalizePath);
let projectVersion = 0;
const scriptSnapshots = /* @__PURE__ */ new Map();
const _host = {
// Commented to wait for volar 2.2.0-alpha.0
// ...ts.sys,
// configFileName,
getCurrentDirectory: () => rootPath,
getProjectVersion: () => projectVersion.toString(),
getCompilationSettings: () => parsedCommandLine.options,
getScriptFileNames: () => fileNames,
getProjectReferences: () => parsedCommandLine.projectReferences,
getScriptSnapshot: (fileName) => {
if (!scriptSnapshots.has(fileName)) {
const fileText = ts.sys.readFile(fileName);
if (fileText !== void 0) {
scriptSnapshots.set(fileName, ts.ScriptSnapshot.fromString(fileText));
}
}
return scriptSnapshots.get(fileName);
},
getLanguageId: (fileName) => {
if (parsedCommandLine.vueOptions.extensions.some(
(ext) => fileName.endsWith(ext)
)) {
return "vue";
}
return vue__namespace.resolveCommonLanguageId(fileName);
}
// scriptIdToFileName: (id) => id,
// fileNameToScriptId: (id) => id,
};
return {
...baseCreateLanguageWorker(
ts,
configFileName,
_host,
parsedCommandLine.vueOptions
),
updateFile(fileName, text) {
fileName = common.normalizePath(fileName);
scriptSnapshots.set(fileName, ts.ScriptSnapshot.fromString(text));
projectVersion++;
},
deleteFile(fileName) {
fileName = common.normalizePath(fileName);
fileNames = fileNames.filter((f) => f !== fileName);
projectVersion++;
},
reload() {
parsedCommandLine = loadParsedCommandLine();
fileNames = parsedCommandLine.fileNames.map(common.normalizePath);
this.clearCache();
},
clearCache() {
scriptSnapshots.clear();
projectVersion++;
}
};
}
function baseCreateLanguageWorker(ts, configFileName, host, vueCompilerOptions) {
const vueLanguagePlugin = vue__namespace.createVueLanguagePlugin(
ts,
(id) => id,
(fileName) => {
if (ts.sys.useCaseSensitiveFileNames) {
return host.getScriptFileNames().includes(fileName) ?? false;
} else {
const lowerFileName = fileName.toLowerCase();
for (const rootFile of host.getScriptFileNames()) {
if (rootFile.toLowerCase() === lowerFileName) {
return true;
}
}
return false;
}
},
host.getCompilationSettings(),
vueCompilerOptions
);
const language = typescript.createLanguage(
ts,
ts.sys,
[vueLanguagePlugin],
configFileName,
host,
{
fileIdToFileName: (id) => id,
fileNameToFileId: (id) => id
}
);
const { languageServiceHost } = language.typescript;
const tsLs = ts.createLanguageService(languageServiceHost);
const getScriptKind = languageServiceHost.getScriptKind?.bind(languageServiceHost);
languageServiceHost.getScriptKind = (fileName) => {
if (fileName.endsWith(".vue.js")) {
return ts.ScriptKind.TS;
}
if (fileName.endsWith(".vue.jsx")) {
return ts.ScriptKind.TSX;
}
return getScriptKind(fileName);
};
const program = tsLs.getProgram();
const typeChecker = program.getTypeChecker();
function getScriptSetupBlock(normalizedFilepath) {
const sourceFile = language.files.get(normalizedFilepath)?.generated?.code;
if (!(sourceFile instanceof vue__namespace.VueGeneratedCode)) {
return;
}
if (!sourceFile.sfc.scriptSetup) {
return;
}
const { lang } = sourceFile.sfc.scriptSetup;
if (lang !== "ts" && lang !== "tsx") {
return;
}
return sourceFile.sfc.scriptSetup;
}
function findNodeByRange(filepath, filter) {
filepath = common.normalizePath(filepath);
const scriptSetupBlock = getScriptSetupBlock(filepath);
if (!scriptSetupBlock) {
return;
}
const { startTagEnd: offset } = scriptSetupBlock;
const scriptSetupAst = getScriptSetupAst(filepath);
if (!scriptSetupAst) {
return;
}
const scriptSetupRanges = vue__namespace.parseScriptSetupRanges(
ts,
scriptSetupAst,
vueCompilerOptions
);
const virtualFileAst = getVirtualFileOrTsAst(filepath);
if (!virtualFileAst) {
return;
}
const virtualTsFileScriptSetupRanges = vue__namespace.parseScriptSetupRanges(
ts,
virtualFileAst,
vueCompilerOptions
);
const sourceRange = filter(scriptSetupRanges);
const virtualTsRange = filter(virtualTsFileScriptSetupRanges);
if (!sourceRange || !virtualTsRange) {
return;
}
let foundVirtualFileNode;
virtualFileAst.forEachChild(function traverse(node) {
if (node.getStart(virtualFileAst) === virtualTsRange.start && node.getEnd() === virtualTsRange.end) {
foundVirtualFileNode = node;
} else {
node.forEachChild(traverse);
}
});
let foundSetupNode;
scriptSetupAst.forEachChild(function traverse(node) {
if (node.getStart(scriptSetupAst) === sourceRange.start && node.getEnd() === sourceRange.end) {
foundSetupNode = node;
} else {
node.forEachChild(traverse);
}
});
const setupRange = {
start: offset + sourceRange.start,
end: offset + sourceRange.end
};
return {
virtualFileNode: foundVirtualFileNode,
scriptNode: foundSetupNode,
setupRange,
offset
};
}
function getScriptSetupAst(filepath) {
filepath = common.normalizePath(filepath);
const scriptSetupBlock = getScriptSetupBlock(filepath);
if (!scriptSetupBlock) {
return;
}
return scriptSetupBlock.ast;
}
function getVirtualFileOrTsAst(filepath) {
filepath = common.normalizePath(filepath);
let virtualFileOrTsAst = program.getSourceFile(filepath);
if (virtualFileOrTsAst) {
return virtualFileOrTsAst;
}
const scriptSetupBlock = getScriptSetupBlock(filepath);
if (!scriptSetupBlock) {
return;
}
const { lang } = scriptSetupBlock;
virtualFileOrTsAst = program.getSourceFile(`${filepath}.${lang}`);
return virtualFileOrTsAst;
}
function traverseAst(filepath, cb) {
filepath = common.normalizePath(filepath);
const scriptSetupAst = getScriptSetupAst(filepath);
const virtualFileOrTsAst = getVirtualFileOrTsAst(filepath);
if (!virtualFileOrTsAst) {
return;
}
if (scriptSetupAst) {
scriptSetupAst.forEachChild(function traverse(node) {
cb(node, {
scriptSetupAst,
virtualFileOrTsAst,
isVirtualOrTsFile: false
});
node.forEachChild(traverse);
});
}
if (virtualFileOrTsAst) {
virtualFileOrTsAst.forEachChild(function traverse(node) {
cb(node, {
scriptSetupAst,
virtualFileOrTsAst,
isVirtualOrTsFile: true
});
node.forEachChild(traverse);
});
}
}
return {
findNodeByRange,
getScriptSetupAst,
getVirtualFileAst: getVirtualFileOrTsAst,
traverseAst,
__internal__: {
tsLs,
program,
typeChecker
}
};
}
const LANGUAGE_GLOBAL_KEY = "__VUETS_LANGUAGE__";
function ensureLanguage(tsconfigPath) {
if (!globalThis[LANGUAGE_GLOBAL_KEY]) {
globalThis[LANGUAGE_GLOBAL_KEY] = createLanguage(tsconfigPath);
}
}
function getLanguage() {
if (!globalThis[LANGUAGE_GLOBAL_KEY]) {
throw new Error("[@vue.ts/language] Language not created!");
}
return globalThis[LANGUAGE_GLOBAL_KEY];
}
exports.ensureLanguage = ensureLanguage;
exports.getLanguage = getLanguage;