svelte-language-server
Version:
A language server for Svelte
287 lines • 13.2 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LSAndTSDocResolver = void 0;
const path_1 = require("path");
const typescript_1 = __importDefault(require("typescript"));
const utils_1 = require("../../utils");
const service_1 = require("./service");
const serviceCache_1 = require("./serviceCache");
const SnapshotManager_1 = require("./SnapshotManager");
const utils_2 = require("./utils");
const fileCollection_1 = require("../../lib/documents/fileCollection");
class LSAndTSDocResolver {
constructor(docManager, workspaceUris, configManager, options) {
this.docManager = docManager;
this.workspaceUris = workspaceUris;
this.configManager = configManager;
this.options = options;
/**
* Create a svelte document -> should only be invoked with svelte files.
*/
this.createDocument = (fileName, content) => {
const uri = (0, utils_1.pathToUrl)(fileName);
const document = this.docManager.openDocument({
text: content,
uri
},
/* openedByClient */ false);
this.docManager.lockDocument(uri);
return document;
};
this.extendedConfigCache = new Map();
docManager.on('documentChange', (0, utils_1.debounceSameArg)(this.updateSnapshot.bind(this), (newDoc, prevDoc) => newDoc.uri === prevDoc?.uri, 1000));
// New files would cause typescript to rebuild its type-checker.
// Open it immediately to reduce rebuilds in the startup
// where multiple files and their dependencies
// being loaded in a short period of times
docManager.on('documentOpen', (document) => {
if (document.openedByClient) {
this.getOrCreateSnapshot(document);
}
else {
this.updateSnapshot(document);
}
docManager.lockDocument(document.uri);
});
this.getCanonicalFileName = (0, utils_1.createGetCanonicalFileName)((options?.tsSystem ?? typescript_1.default.sys).useCaseSensitiveFileNames);
this.tsSystem = this.wrapWithPackageJsonMonitoring(this.options?.tsSystem ?? typescript_1.default.sys);
this.globalSnapshotsManager = new SnapshotManager_1.GlobalSnapshotsManager(this.tsSystem);
this.userPreferencesAccessor = { preferences: this.getTsUserPreferences() };
const projectService = (0, serviceCache_1.createProjectService)(this.tsSystem, this.userPreferencesAccessor);
configManager.onChange(() => {
const newPreferences = this.getTsUserPreferences();
const autoImportConfigChanged = newPreferences.includePackageJsonAutoImports !==
this.userPreferencesAccessor.preferences.includePackageJsonAutoImports;
this.userPreferencesAccessor.preferences = newPreferences;
if (autoImportConfigChanged) {
(0, service_1.forAllServices)((service) => {
service.onAutoImportProviderSettingsChanged();
});
}
});
this.packageJsonWatchers = new fileCollection_1.FileMap(this.tsSystem.useCaseSensitiveFileNames);
this.watchedDirectories = new fileCollection_1.FileSet(this.tsSystem.useCaseSensitiveFileNames);
// workspaceUris are already watched during initialization
for (const root of this.workspaceUris) {
const rootPath = (0, utils_1.urlToPath)(root);
if (rootPath) {
this.watchedDirectories.add(rootPath);
}
}
this.lsDocumentContext = {
isSvelteCheck: !!this.options?.isSvelteCheck,
ambientTypesSource: this.options?.isSvelteCheck ? 'svelte-check' : 'svelte2tsx',
createDocument: this.createDocument,
transformOnTemplateError: !this.options?.isSvelteCheck,
globalSnapshotsManager: this.globalSnapshotsManager,
notifyExceedSizeLimit: this.options?.notifyExceedSizeLimit,
extendedConfigCache: this.extendedConfigCache,
onProjectReloaded: this.options?.onProjectReloaded,
watchTsConfig: !!this.options?.watch,
tsSystem: this.tsSystem,
projectService,
watchDirectory: this.options?.watchDirectory
? this.watchDirectory.bind(this)
: undefined,
nonRecursiveWatchPattern: this.options?.nonRecursiveWatchPattern,
reportConfigError: this.options?.reportConfigError
};
}
async getLSAndTSDoc(document) {
const { tsDoc, lsContainer, userPreferences } = await this.getLSAndTSDocWorker(document);
return {
tsDoc,
lang: lsContainer.getService(),
userPreferences,
lsContainer
};
}
/**
* Retrieves the LS for operations that don't need cross-files information.
* can save some time by not synchronizing languageService program
*/
async getLsForSyntheticOperations(document) {
const { tsDoc, lsContainer, userPreferences } = await this.getLSAndTSDocWorker(document);
return { tsDoc, userPreferences, lang: lsContainer.getService(/* skipSynchronize */ true) };
}
async getLSAndTSDocWorker(document) {
const lsContainer = await this.getTSService(document.getFilePath() || '');
const tsDoc = await this.getOrCreateSnapshot(document);
const userPreferences = this.getUserPreferences(tsDoc);
return { tsDoc, lsContainer, userPreferences };
}
async getOrCreateSnapshot(pathOrDoc) {
const filePath = typeof pathOrDoc === 'string' ? pathOrDoc : pathOrDoc.getFilePath() || '';
const tsService = await this.getTSService(filePath);
return tsService.updateSnapshot(pathOrDoc);
}
async updateSnapshot(document) {
const filePath = document.getFilePath();
if (!filePath) {
return;
}
// ensure no new service is created
await this.updateExistingFile(filePath, (service) => service.updateSnapshot(document));
}
/**
* Updates snapshot path in all existing ts services and retrieves snapshot
*/
async updateSnapshotPath(oldPath, newPath) {
const document = this.docManager.get((0, utils_1.pathToUrl)(oldPath));
const isOpenedInClient = document?.openedByClient;
for (const snapshot of this.globalSnapshotsManager.getByPrefix(oldPath)) {
await this.deleteSnapshot(snapshot.filePath);
}
if (isOpenedInClient) {
this.docManager.openClientDocument({
uri: (0, utils_1.pathToUrl)(newPath),
text: document.getText()
});
}
else {
// This may not be a file but a directory, still try
await this.getOrCreateSnapshot(newPath);
}
}
/**
* Deletes snapshot in all existing ts services
*/
async deleteSnapshot(filePath) {
await (0, service_1.forAllServices)((service) => service.deleteSnapshot(filePath));
const uri = (0, utils_1.pathToUrl)(filePath);
if (this.docManager.get(uri)) {
// Guard this call, due to race conditions it may already have been closed;
// also this may not be a Svelte file
this.docManager.closeDocument(uri);
}
this.docManager.releaseDocument(uri);
}
async invalidateModuleCache(filePaths) {
await (0, service_1.forAllServices)((service) => service.invalidateModuleCache(filePaths));
}
/**
* Updates project files in all existing ts services
*/
async updateProjectFiles(watcherNewFiles) {
await (0, service_1.forAllServices)((service) => service.scheduleProjectFileUpdate(watcherNewFiles));
}
/**
* Updates file in all ts services where it exists
*/
async updateExistingTsOrJsFile(path, changes) {
await this.updateExistingFile(path, (service) => service.updateTsOrJsFile(path, changes));
}
async updateExistingSvelteFile(path) {
const newDocument = this.createDocument(path, this.tsSystem.readFile(path) ?? '');
await this.updateExistingFile(path, (service) => {
service.updateSnapshot(newDocument);
});
}
async updateExistingFile(path, cb) {
path = (0, utils_1.normalizePath)(path);
// Only update once because all snapshots are shared between
// services. Since we don't have a current version of TS/JS
// files, the operation wouldn't be idempotent.
let didUpdate = false;
await (0, service_1.forAllServices)((service) => {
if (service.hasFile(path) && !didUpdate) {
didUpdate = true;
cb(service);
}
});
}
async getTSService(filePath) {
if (this.options?.tsconfigPath) {
return this.getTSServiceByConfigPath(this.options.tsconfigPath, (0, path_1.dirname)(this.options.tsconfigPath));
}
if (!filePath) {
throw new Error('Cannot call getTSService without filePath and without tsconfigPath');
}
return (0, service_1.getService)(filePath, this.workspaceUris, this.lsDocumentContext);
}
async getTSServiceByConfigPath(tsconfigPath, workspacePath) {
return (0, service_1.getServiceForTsconfig)(tsconfigPath, workspacePath, this.lsDocumentContext);
}
getUserPreferences(tsDoc) {
const configLang = tsDoc.scriptKind === typescript_1.default.ScriptKind.TS || tsDoc.scriptKind === typescript_1.default.ScriptKind.TSX
? 'typescript'
: 'javascript';
const nearestWorkspaceUri = this.workspaceUris.find((workspaceUri) => (0, utils_2.isSubPath)(workspaceUri, tsDoc.filePath, this.getCanonicalFileName));
return this.configManager.getTsUserPreferences(configLang, nearestWorkspaceUri ? (0, utils_1.urlToPath)(nearestWorkspaceUri) : null);
}
getTsUserPreferences() {
return this.configManager.getTsUserPreferences('typescript', null);
}
wrapWithPackageJsonMonitoring(sys) {
if (!sys.watchFile || !this.options?.watch) {
return sys;
}
const watchFile = sys.watchFile;
return {
...sys,
readFile: (path, encoding) => {
if (path.endsWith('package.json') && !this.packageJsonWatchers.has(path)) {
this.packageJsonWatchers.set(path, watchFile(path, this.onPackageJsonWatchChange.bind(this), 3_000));
}
return sys.readFile(path, encoding);
}
};
}
onPackageJsonWatchChange(path, onWatchChange) {
const dir = (0, path_1.dirname)(path);
const projectService = this.lsDocumentContext.projectService;
const packageJsonCache = projectService?.packageJsonCache;
const normalizedPath = projectService?.toPath(path);
if (onWatchChange === typescript_1.default.FileWatcherEventKind.Deleted) {
this.packageJsonWatchers.get(path)?.close();
this.packageJsonWatchers.delete(path);
packageJsonCache?.delete(normalizedPath);
}
else {
packageJsonCache?.addOrUpdate(normalizedPath);
}
(0, service_1.forAllServices)((service) => {
service.onPackageJsonChange(path);
});
if (!path.includes('node_modules')) {
return;
}
setTimeout(() => {
this.updateSnapshotsInDirectory(dir);
const realPath = this.tsSystem.realpath &&
this.getCanonicalFileName((0, utils_1.normalizePath)(this.tsSystem.realpath?.(dir)));
// pnpm
if (realPath && realPath !== dir) {
this.updateSnapshotsInDirectory(realPath);
const realPkgPath = (0, path_1.join)(realPath, 'package.json');
(0, service_1.forAllServices)((service) => {
service.onPackageJsonChange(realPkgPath);
});
}
}, 500);
}
updateSnapshotsInDirectory(dir) {
this.globalSnapshotsManager.getByPrefix(dir).forEach((snapshot) => {
this.globalSnapshotsManager.updateTsOrJsFile(snapshot.filePath);
});
}
watchDirectory(patterns) {
if (!this.options?.watchDirectory || patterns.length === 0) {
return;
}
for (const pattern of patterns) {
const uri = typeof pattern.baseUri === 'string' ? pattern.baseUri : pattern.baseUri.uri;
for (const watched of this.watchedDirectories) {
if ((0, utils_2.isSubPath)(watched, uri, this.getCanonicalFileName)) {
return;
}
}
}
this.options.watchDirectory(patterns);
}
}
exports.LSAndTSDocResolver = LSAndTSDocResolver;
//# sourceMappingURL=LSAndTSDocResolver.js.map