"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createLanguageContext = void 0; const path_1 = require("path"); const virtualFiles_1 = require("./virtualFiles"); const types_1 = require("./types"); function createLanguageContext(host, modules, languageModules) { for (const languageModule of languageModules.reverse()) { if (languageModule.proxyLanguageServiceHost) { const proxyApis = languageModule.proxyLanguageServiceHost(host); host = new Proxy(host, { get(target, key) { if (key in proxyApis) { return proxyApis[key]; } return target[key]; }, }); } } let lastProjectVersion; let tsProjectVersion = 0; const virtualFiles = (0, virtualFiles_1.createVirtualFiles)(languageModules); const ts = modules.typescript; const scriptSnapshots = new Map(); const sourceTsFileVersions = new Map(); const sourceFileVersions = new Map(); const virtualFileVersions = new Map(); const _tsHost = { fileExists: host.fileExists ? fileName => { const ext = fileName.substring(fileName.lastIndexOf('.')); if (ext === '.js' || ext === '.ts' || ext === '.jsx' || ext === '.tsx') { /** * If try to access a external .vue file that outside of the project, * the file will not process by language service host, * so virtual file will not be created. * * We try to create virtual file here. */ const sourceFileName = fileName.substring(0, fileName.lastIndexOf('.')); if (!virtualFiles.hasSource(sourceFileName)) { const scriptSnapshot = host.getScriptSnapshot(sourceFileName); if (scriptSnapshot) { virtualFiles.updateSource(sourceFileName, scriptSnapshot, host.getScriptLanguageId?.(sourceFileName)); } } } if (virtualFiles.hasVirtualFile(fileName)) { return true; } return !!host.fileExists?.(fileName); } : undefined, getProjectVersion: () => { return tsProjectVersion.toString(); }, getScriptFileNames, getScriptVersion, getScriptSnapshot, readDirectory: (_path, extensions, exclude, include, depth) => { const result = host.readDirectory?.(_path, extensions, exclude, include, depth) ?? []; for (const { fileName } of virtualFiles.allSources()) { const vuePath2 = path_1.posix.join(_path, path_1.posix.basename(fileName)); if (path_1.posix.relative(_path.toLowerCase(), fileName.toLowerCase()).startsWith('..')) { continue; } if (!depth && fileName.toLowerCase() === vuePath2.toLowerCase()) { result.push(vuePath2); } else if (depth) { result.push(vuePath2); // TODO: depth num } } return result; }, getScriptKind(fileName) { if (ts) { if (virtualFiles.hasSource(fileName)) return ts.ScriptKind.Deferred; switch (path_1.posix.extname(fileName)) { case '.js': return ts.ScriptKind.JS; case '.jsx': return ts.ScriptKind.JSX; case '.ts': return ts.ScriptKind.TS; case '.tsx': return ts.ScriptKind.TSX; case '.json': return ts.ScriptKind.JSON; default: return ts.ScriptKind.Unknown; } } return 0; }, }; return { typescript: { languageServiceHost: new Proxy(_tsHost, { get: (target, property) => { update(); return target[property] || host[property]; }, }), }, virtualFiles: new Proxy(virtualFiles, { get: (target, property) => { update(); return target[property]; }, }), }; function update() { const newProjectVersion = host.getProjectVersion?.(); const shouldUpdate = newProjectVersion === undefined || newProjectVersion !== lastProjectVersion; lastProjectVersion = newProjectVersion; if (!shouldUpdate) return; let shouldUpdateTsProject = false; let virtualFilesUpdatedNum = 0; const remainRootFiles = new Set(host.getScriptFileNames()); // .vue for (const { fileName } of virtualFiles.allSources()) { remainRootFiles.delete(fileName); const snapshot = host.getScriptSnapshot(fileName); if (!snapshot) { // delete virtualFiles.deleteSource(fileName); shouldUpdateTsProject = true; virtualFilesUpdatedNum++; continue; } const newVersion = host.getScriptVersion(fileName); if (sourceFileVersions.get(fileName) !== newVersion) { // update sourceFileVersions.set(fileName, newVersion); virtualFiles.updateSource(fileName, snapshot, host.getScriptLanguageId?.(fileName)); virtualFilesUpdatedNum++; } } // no any vue file version change, it mean project version was update by ts file change at this time if (!virtualFilesUpdatedNum) { shouldUpdateTsProject = true; } // add for (const fileName of [...remainRootFiles]) { const snapshot = host.getScriptSnapshot(fileName); if (snapshot) { const virtualFile = virtualFiles.updateSource(fileName, snapshot, host.getScriptLanguageId?.(fileName)); if (virtualFile) { remainRootFiles.delete(fileName); } } } // .ts / .js / .d.ts / .json ... for (const [oldTsFileName, oldTsFileVersion] of [...sourceTsFileVersions]) { const newVersion = host.getScriptVersion(oldTsFileName); if (oldTsFileVersion !== newVersion) { if (!remainRootFiles.has(oldTsFileName) && !host.getScriptSnapshot(oldTsFileName)) { // delete sourceTsFileVersions.delete(oldTsFileName); } else { // update sourceTsFileVersions.set(oldTsFileName, newVersion); } shouldUpdateTsProject = true; } } for (const nowFileName of remainRootFiles) { if (!sourceTsFileVersions.has(nowFileName)) { // add const newVersion = host.getScriptVersion(nowFileName); sourceTsFileVersions.set(nowFileName, newVersion); shouldUpdateTsProject = true; } } for (const { root: rootVirtualFile } of virtualFiles.allSources()) { if (!shouldUpdateTsProject) { (0, virtualFiles_1.forEachEmbeddedFile)(rootVirtualFile, embedded => { if (embedded.kind === types_1.FileKind.TypeScriptHostFile) { if (virtualFileVersions.has(embedded.fileName) && virtualFileVersions.get(embedded.fileName)?.virtualFileSnapshot !== embedded.snapshot) { shouldUpdateTsProject = true; } } }); } } if (shouldUpdateTsProject) { tsProjectVersion++; } } function getScriptFileNames() { const tsFileNames = new Set(); for (const { root: rootVirtualFile } of virtualFiles.allSources()) { (0, virtualFiles_1.forEachEmbeddedFile)(rootVirtualFile, embedded => { if (embedded.kind === types_1.FileKind.TypeScriptHostFile) { tsFileNames.add(embedded.fileName); // virtual .ts } }); } for (const fileName of host.getScriptFileNames()) { if (!virtualFiles.hasSource(fileName)) { tsFileNames.add(fileName); // .ts } } return [...tsFileNames]; } function getScriptVersion(fileName) { let [virtualFile, source] = virtualFiles.getVirtualFile(fileName); if (virtualFile && source) { let version = virtualFileVersions.get(virtualFile.fileName); if (!version) { version = { value: 0, virtualFileSnapshot: virtualFile.snapshot, sourceFileSnapshot: source.snapshot, }; virtualFileVersions.set(virtualFile.fileName, version); } else if (version.virtualFileSnapshot !== virtualFile.snapshot || (host.isTsc && version.sourceFileSnapshot !== source.snapshot) // fix https://github.com/johnsoncodehk/volar/issues/1082 ) { version.value++; version.virtualFileSnapshot = virtualFile.snapshot; version.sourceFileSnapshot = source.snapshot; } return version.value.toString(); } return host.getScriptVersion(fileName); } function getScriptSnapshot(fileName) { const version = getScriptVersion(fileName); const cache = scriptSnapshots.get(fileName.toLowerCase()); if (cache && cache[0] === version) { return cache[1]; } const [virtualFile] = virtualFiles.getVirtualFile(fileName); if (virtualFile) { const snapshot = virtualFile.snapshot; scriptSnapshots.set(fileName.toLowerCase(), [version, snapshot]); return snapshot; } let tsScript = host.getScriptSnapshot(fileName); if (tsScript) { scriptSnapshots.set(fileName.toLowerCase(), [version, tsScript]); return tsScript; } } } exports.createLanguageContext = createLanguageContext; //# sourceMappingURL=languageContext.js.map