You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
251 lines
10 KiB
251 lines
10 KiB
|
2 years ago
|
"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
|