296 lines
12 KiB
Plaintext
296 lines
12 KiB
Plaintext
|
"use strict";
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.createLanguageServiceHost = void 0;
|
||
|
const path = require("path-browserify");
|
||
|
const utilities_1 = require("./typescript/utilities");
|
||
|
const fileVersions = new Map();
|
||
|
function createLanguageServiceHost(ctx, ts, sys) {
|
||
|
let lastProjectVersion;
|
||
|
let tsProjectVersion = 0;
|
||
|
let tsFileNames = [];
|
||
|
let tsDirectories = new Set();
|
||
|
const _tsHost = {
|
||
|
...sys,
|
||
|
getCurrentDirectory: () => ctx.host.workspacePath,
|
||
|
getCompilationSettings: () => ctx.host.getCompilationSettings(),
|
||
|
getCancellationToken: ctx.host.getCancellationToken ? () => ctx.host.getCancellationToken() : undefined,
|
||
|
getLocalizedDiagnosticMessages: ctx.host.getLocalizedDiagnosticMessages ? () => ctx.host.getLocalizedDiagnosticMessages() : undefined,
|
||
|
getProjectReferences: ctx.host.getProjectReferences ? () => ctx.host.getProjectReferences() : undefined,
|
||
|
getDefaultLibFileName: (options) => {
|
||
|
try {
|
||
|
return ts.getDefaultLibFilePath(options);
|
||
|
}
|
||
|
catch {
|
||
|
// web
|
||
|
return `/node_modules/typescript/lib/${ts.getDefaultLibFileName(options)}`;
|
||
|
}
|
||
|
},
|
||
|
useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames,
|
||
|
getNewLine: () => sys.newLine,
|
||
|
readFile: fileName => {
|
||
|
const snapshot = getScriptSnapshot(fileName);
|
||
|
if (snapshot) {
|
||
|
return snapshot.getText(0, snapshot.getLength());
|
||
|
}
|
||
|
},
|
||
|
readDirectory,
|
||
|
getDirectories,
|
||
|
directoryExists,
|
||
|
fileExists,
|
||
|
getProjectVersion: () => {
|
||
|
return tsProjectVersion + ':' + sys.version;
|
||
|
},
|
||
|
getTypeRootsVersion: () => {
|
||
|
return sys.version ?? -1; // TODO: only update for /node_modules changes?
|
||
|
},
|
||
|
getScriptFileNames: () => tsFileNames,
|
||
|
getScriptVersion,
|
||
|
getScriptSnapshot,
|
||
|
getScriptKind(fileName) {
|
||
|
if (ts) {
|
||
|
if (ctx.virtualFiles.hasSource(fileName))
|
||
|
return ts.ScriptKind.Deferred;
|
||
|
switch (path.extname(fileName)) {
|
||
|
case '.js': return ts.ScriptKind.JS;
|
||
|
case '.cjs': return ts.ScriptKind.JS;
|
||
|
case '.mjs': return ts.ScriptKind.JS;
|
||
|
case '.jsx': return ts.ScriptKind.JSX;
|
||
|
case '.ts': return ts.ScriptKind.TS;
|
||
|
case '.cts': return ts.ScriptKind.TS;
|
||
|
case '.mts': return ts.ScriptKind.TS;
|
||
|
case '.tsx': return ts.ScriptKind.TSX;
|
||
|
case '.json': return ts.ScriptKind.JSON;
|
||
|
default: return ts.ScriptKind.Unknown;
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
},
|
||
|
};
|
||
|
const fsFileSnapshots = new Map();
|
||
|
if (ctx.host.resolveModuleName) {
|
||
|
// TODO: can this share between monorepo packages?
|
||
|
const moduleCache = ts.createModuleResolutionCache(_tsHost.getCurrentDirectory(), _tsHost.useCaseSensitiveFileNames ? s => s : s => s.toLowerCase(), _tsHost.getCompilationSettings());
|
||
|
let lastSysVersion = sys.version;
|
||
|
_tsHost.resolveModuleNameLiterals = (moduleLiterals, containingFile, redirectedReference, options, sourceFile) => {
|
||
|
if (lastSysVersion !== sys.version) {
|
||
|
lastSysVersion = sys.version;
|
||
|
moduleCache.clear();
|
||
|
}
|
||
|
return moduleLiterals.map((moduleLiteral) => {
|
||
|
let moduleName = moduleLiteral.text;
|
||
|
moduleName = ctx.host.resolveModuleName(moduleName, sourceFile.impliedNodeFormat);
|
||
|
return ts.resolveModuleName(moduleName, containingFile, options, _tsHost, moduleCache, redirectedReference, sourceFile.impliedNodeFormat);
|
||
|
});
|
||
|
};
|
||
|
_tsHost.resolveModuleNames = (moduleNames, containingFile, _reusedNames, redirectedReference, options, sourceFile) => {
|
||
|
if (lastSysVersion !== sys.version) {
|
||
|
lastSysVersion = sys.version;
|
||
|
moduleCache.clear();
|
||
|
}
|
||
|
return moduleNames.map((moduleName) => {
|
||
|
moduleName = ctx.host.resolveModuleName(moduleName, sourceFile?.impliedNodeFormat);
|
||
|
return ts.resolveModuleName(moduleName, containingFile, options, _tsHost, moduleCache, redirectedReference, sourceFile?.impliedNodeFormat).resolvedModule;
|
||
|
});
|
||
|
};
|
||
|
}
|
||
|
let oldTsVirtualFileSnapshots = new Set();
|
||
|
let oldOtherVirtualFileSnapshots = new Set();
|
||
|
return new Proxy(_tsHost, {
|
||
|
get: (target, property) => {
|
||
|
sync();
|
||
|
return target[property];
|
||
|
},
|
||
|
});
|
||
|
function sync() {
|
||
|
const newProjectVersion = ctx.host.getProjectVersion();
|
||
|
const shouldUpdate = newProjectVersion !== lastProjectVersion;
|
||
|
if (!shouldUpdate)
|
||
|
return;
|
||
|
lastProjectVersion = newProjectVersion;
|
||
|
const newTsVirtualFileSnapshots = new Set();
|
||
|
const newOtherVirtualFileSnapshots = new Set();
|
||
|
for (const { root } of ctx.virtualFiles.allSources()) {
|
||
|
forEachEmbeddedFile(root, embedded => {
|
||
|
if (embedded.kind === 1) {
|
||
|
newTsVirtualFileSnapshots.add(embedded.snapshot);
|
||
|
}
|
||
|
else {
|
||
|
newOtherVirtualFileSnapshots.add(embedded.snapshot);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
if (!setEquals(oldTsVirtualFileSnapshots, newTsVirtualFileSnapshots)) {
|
||
|
tsProjectVersion++;
|
||
|
}
|
||
|
else if (setEquals(oldOtherVirtualFileSnapshots, newOtherVirtualFileSnapshots)) {
|
||
|
// no any meta language files update, it mean project version was update by source files this time
|
||
|
tsProjectVersion++;
|
||
|
}
|
||
|
oldTsVirtualFileSnapshots = newTsVirtualFileSnapshots;
|
||
|
oldOtherVirtualFileSnapshots = newOtherVirtualFileSnapshots;
|
||
|
const tsFileNamesSet = new Set();
|
||
|
for (const { root } of ctx.virtualFiles.allSources()) {
|
||
|
forEachEmbeddedFile(root, embedded => {
|
||
|
if (embedded.kind === 1) {
|
||
|
tsFileNamesSet.add(embedded.fileName); // virtual .ts
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
for (const fileName of ctx.host.getScriptFileNames()) {
|
||
|
if (!ctx.virtualFiles.hasSource(fileName)) {
|
||
|
tsFileNamesSet.add(fileName); // .ts
|
||
|
}
|
||
|
}
|
||
|
tsFileNames = [...tsFileNamesSet];
|
||
|
// Update tsDirectories for `directoryExists()`
|
||
|
tsDirectories.clear();
|
||
|
for (const fileName of tsFileNames) {
|
||
|
tsDirectories.add(path.dirname(normalizePath(fileName)));
|
||
|
}
|
||
|
}
|
||
|
function readDirectory(dirName, extensions, excludes, includes, depth) {
|
||
|
let matches = (0, utilities_1.matchFiles)(dirName, extensions, excludes, includes, sys?.useCaseSensitiveFileNames ?? false, ctx.host.workspacePath, depth, (dirPath) => {
|
||
|
const files = [];
|
||
|
for (const fileName of tsFileNames) {
|
||
|
if (fileName.toLowerCase().startsWith(dirPath.toLowerCase())) {
|
||
|
const baseName = fileName.substring(dirPath.length);
|
||
|
if (baseName.indexOf('/') === -1) {
|
||
|
files.push(baseName);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return {
|
||
|
files,
|
||
|
directories: getVirtualFileDirectories(dirPath),
|
||
|
};
|
||
|
}, sys?.realpath ? (path => sys.realpath(path)) : (path => path));
|
||
|
matches = matches.map(match => {
|
||
|
const [_, source] = ctx.virtualFiles.getVirtualFile(match);
|
||
|
if (source) {
|
||
|
return source.fileName;
|
||
|
}
|
||
|
return match;
|
||
|
});
|
||
|
return [...new Set([
|
||
|
...matches,
|
||
|
...sys.readDirectory(dirName, extensions, excludes, includes, depth),
|
||
|
])];
|
||
|
}
|
||
|
function getDirectories(dirName) {
|
||
|
return [...new Set([
|
||
|
...getVirtualFileDirectories(dirName),
|
||
|
...sys.getDirectories(dirName),
|
||
|
])];
|
||
|
}
|
||
|
function getVirtualFileDirectories(dirName) {
|
||
|
const names = new Set();
|
||
|
for (const fileName of tsFileNames) {
|
||
|
if (fileName.toLowerCase().startsWith(dirName.toLowerCase())) {
|
||
|
const path = fileName.substring(dirName.length);
|
||
|
if (path.indexOf('/') >= 0) {
|
||
|
names.add(path.split('/')[0]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return [...names];
|
||
|
}
|
||
|
function getScriptSnapshot(fileName) {
|
||
|
// virtual files
|
||
|
const [virtualFile] = ctx.virtualFiles.getVirtualFile(fileName);
|
||
|
if (virtualFile) {
|
||
|
return virtualFile.snapshot;
|
||
|
}
|
||
|
// root files / opened files
|
||
|
const tsScript = ctx.host.getScriptSnapshot(fileName);
|
||
|
if (tsScript) {
|
||
|
return tsScript;
|
||
|
}
|
||
|
// fs files
|
||
|
const cache = fsFileSnapshots.get(fileName);
|
||
|
const modifiedTime = sys.getModifiedTime?.(fileName)?.valueOf();
|
||
|
if (!cache || cache[0] !== modifiedTime) {
|
||
|
if (sys.fileExists(fileName)) {
|
||
|
const text = sys.readFile(fileName);
|
||
|
const snapshot = text !== undefined ? ts.ScriptSnapshot.fromString(text) : undefined;
|
||
|
fsFileSnapshots.set(fileName, [modifiedTime, snapshot]);
|
||
|
}
|
||
|
else {
|
||
|
fsFileSnapshots.set(fileName, [modifiedTime, undefined]);
|
||
|
}
|
||
|
}
|
||
|
return fsFileSnapshots.get(fileName)?.[1];
|
||
|
}
|
||
|
function getScriptVersion(fileName) {
|
||
|
// virtual files / root files / opened files
|
||
|
const [virtualFile] = ctx.virtualFiles.getVirtualFile(fileName);
|
||
|
const snapshot = virtualFile?.snapshot ?? ctx.host.getScriptSnapshot(fileName);
|
||
|
if (snapshot) {
|
||
|
if (!fileVersions.has(fileName)) {
|
||
|
fileVersions.set(fileName, { lastVersion: 0, snapshotVersions: new WeakMap() });
|
||
|
}
|
||
|
const version = fileVersions.get(fileName);
|
||
|
if (!version.snapshotVersions.has(snapshot)) {
|
||
|
version.snapshotVersions.set(snapshot, version.lastVersion++);
|
||
|
}
|
||
|
return version.snapshotVersions.get(snapshot).toString();
|
||
|
}
|
||
|
// fs files
|
||
|
return sys.getModifiedTime?.(fileName)?.valueOf().toString() ?? '';
|
||
|
}
|
||
|
function directoryExists(dirName) {
|
||
|
return tsDirectories.has(normalizePath(dirName)) || sys.directoryExists(dirName);
|
||
|
}
|
||
|
function fileExists(fileName) {
|
||
|
// fill external virtual files
|
||
|
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 (!ctx.virtualFiles.hasSource(sourceFileName)) {
|
||
|
const scriptSnapshot = getScriptSnapshot(sourceFileName);
|
||
|
if (scriptSnapshot) {
|
||
|
ctx.virtualFiles.updateSource(sourceFileName, scriptSnapshot, ctx.host.getLanguageId?.(sourceFileName));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// virtual files
|
||
|
if (ctx.virtualFiles.hasVirtualFile(fileName)) {
|
||
|
return true;
|
||
|
}
|
||
|
// root files
|
||
|
if (ctx.host.getScriptSnapshot(fileName)) {
|
||
|
return true;
|
||
|
}
|
||
|
// fs files
|
||
|
return !!sys.fileExists(fileName);
|
||
|
}
|
||
|
}
|
||
|
exports.createLanguageServiceHost = createLanguageServiceHost;
|
||
|
function setEquals(a, b) {
|
||
|
if (a.size !== b.size)
|
||
|
return false;
|
||
|
for (const item of a) {
|
||
|
if (!b.has(item))
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
function forEachEmbeddedFile(file, cb) {
|
||
|
cb(file);
|
||
|
for (const embeddedFile of file.embeddedFiles) {
|
||
|
forEachEmbeddedFile(embeddedFile, cb);
|
||
|
}
|
||
|
}
|
||
|
function normalizePath(fileName) {
|
||
|
return fileName.replace(/\\/g, '/').toLowerCase();
|
||
|
}
|
||
|
//# sourceMappingURL=languageServiceHost.js.map
|