svelte-language-server
Version:
A language server for Svelte
1,025 lines • 47.5 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.__resetCache = __resetCache;
exports.getService = getService;
exports.forAllServices = forAllServices;
exports.getServiceForTsconfig = getServiceForTsconfig;
const path_1 = require("path");
const typescript_1 = __importDefault(require("typescript"));
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
const importPackage_1 = require("../../importPackage");
const configLoader_1 = require("../../lib/documents/configLoader");
const fileCollection_1 = require("../../lib/documents/fileCollection");
const logger_1 = require("../../logger");
const utils_1 = require("../../utils");
const DocumentSnapshot_1 = require("./DocumentSnapshot");
const module_loader_1 = require("./module-loader");
const SnapshotManager_1 = require("./SnapshotManager");
const utils_2 = require("./utils");
const serviceCache_1 = require("./serviceCache");
const svelte2tsx_1 = require("svelte2tsx");
var TsconfigSvelteDiagnostics;
(function (TsconfigSvelteDiagnostics) {
TsconfigSvelteDiagnostics[TsconfigSvelteDiagnostics["NO_SVELTE_INPUT"] = 100001] = "NO_SVELTE_INPUT";
})(TsconfigSvelteDiagnostics || (TsconfigSvelteDiagnostics = {}));
const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024; // 20 MB
const services = new fileCollection_1.FileMap();
const serviceSizeMap = new fileCollection_1.FileMap();
const configWatchers = new fileCollection_1.FileMap();
const dependedConfigWatchers = new fileCollection_1.FileMap();
const configPathToDependedProject = new fileCollection_1.FileMap();
const configFileModifiedTime = new fileCollection_1.FileMap();
const configFileForOpenFiles = new fileCollection_1.FileMap();
const pendingReloads = new fileCollection_1.FileSet();
const documentRegistries = new Map();
const pendingForAllServices = new Set();
const parsedTsConfigInfo = new fileCollection_1.FileMap();
/**
* For testing only: Reset the cache for services.
* Try to refactor this some day so that this file provides
* a setup function which creates all this nicely instead.
*/
function __resetCache() {
services.clear();
parsedTsConfigInfo.clear();
serviceSizeMap.clear();
configFileForOpenFiles.clear();
}
async function getService(path, workspaceUris, docContext) {
const getCanonicalFileName = (0, utils_1.createGetCanonicalFileName)(docContext.tsSystem.useCaseSensitiveFileNames);
const fileExistsWithCache = (fileName) => {
return ((parsedTsConfigInfo.has(fileName) && !pendingReloads.has(fileName)) ||
docContext.tsSystem.fileExists(fileName));
};
let tsconfigPath = configFileForOpenFiles.get(path) ??
(0, utils_2.findTsConfigPath)(path, workspaceUris, fileExistsWithCache, getCanonicalFileName);
if (tsconfigPath) {
/**
* Prevent infinite loop when the project reference is circular
*/
const triedTsConfig = new Set();
const needAssign = !configFileForOpenFiles.has(path);
let service = await getConfiguredService(tsconfigPath);
if (!needAssign) {
return service;
}
// First try to find a service whose includes config matches our file
const defaultService = await findDefaultServiceForFile(service, triedTsConfig);
if (defaultService) {
configFileForOpenFiles.set(path, defaultService.tsconfigPath);
return defaultService;
}
// If no such service found, see if the file is part of any existing service indirectly.
// This can happen if the includes doesn't match the file but it was imported from one of the included files.
for (const configPath of triedTsConfig) {
const service = await getConfiguredService(configPath);
const ls = service.getService();
if (ls.getProgram()?.getSourceFile(path)) {
return service;
}
}
tsconfigPath = '';
}
// Find closer boundary: workspace uri or node_modules
const nearestWorkspaceUri = (0, utils_2.getNearestWorkspaceUri)(workspaceUris, path, getCanonicalFileName);
const lastNodeModulesIdx = path.split('/').lastIndexOf('node_modules') + 2;
const nearestNodeModulesBoundary = lastNodeModulesIdx === 1
? undefined
: path.split('/').slice(0, lastNodeModulesIdx).join('/');
const nearestBoundary = (nearestNodeModulesBoundary?.length ?? 0) > (nearestWorkspaceUri?.length ?? 0)
? nearestNodeModulesBoundary
: nearestWorkspaceUri;
return getServiceForTsconfig(tsconfigPath, (nearestBoundary && (0, utils_1.urlToPath)(nearestBoundary)) ??
docContext.tsSystem.getCurrentDirectory(), docContext);
function getConfiguredService(tsconfigPath) {
return getServiceForTsconfig(tsconfigPath, (0, path_1.dirname)(tsconfigPath), docContext);
}
async function findDefaultServiceForFile(service, triedTsConfig) {
service.ensureProjectFileUpdates(path);
if (service.snapshotManager.isProjectFile(path)) {
return service;
}
if (triedTsConfig.has(service.tsconfigPath)) {
return;
}
triedTsConfig.add(service.tsconfigPath);
// TODO: maybe add support for ts 5.6's ancestor searching
return findDefaultFromProjectReferences(service, triedTsConfig);
}
async function findDefaultFromProjectReferences(service, triedTsConfig) {
const projectReferences = service.getResolvedProjectReferences();
if (projectReferences.length === 0) {
return undefined;
}
let possibleSubPaths = [];
for (const ref of projectReferences) {
if (ref.snapshotManager.isProjectFile(path)) {
return getConfiguredService(ref.configFilePath);
}
if (ref.parsedCommandLine.projectReferences?.length) {
possibleSubPaths.push(ref.configFilePath);
}
}
for (const ref of possibleSubPaths) {
const subService = await getConfiguredService(ref);
const defaultService = await findDefaultServiceForFile(subService, triedTsConfig);
if (defaultService) {
return defaultService;
}
}
}
}
async function forAllServices(cb) {
const promise = forAllServicesWorker(cb);
pendingForAllServices.add(promise);
await promise;
pendingForAllServices.delete(promise);
}
async function forAllServicesWorker(cb) {
for (const service of services.values()) {
cb(await service);
}
}
/**
* @param tsconfigPath has to be absolute
* @param docContext
*/
async function getServiceForTsconfig(tsconfigPath, workspacePath, docContext) {
if (tsconfigPath) {
tsconfigPath = (0, utils_1.normalizePath)(tsconfigPath);
}
const tsconfigPathOrWorkspacePath = tsconfigPath || workspacePath;
const reloading = pendingReloads.has(tsconfigPath);
let service;
if (reloading || !services.has(tsconfigPathOrWorkspacePath)) {
if (reloading) {
logger_1.Logger.log('Reloading ts service at ', tsconfigPath, ' due to config updated');
parsedTsConfigInfo.delete(tsconfigPath);
}
else {
logger_1.Logger.log('Initialize new ts service at ', tsconfigPath);
}
pendingReloads.delete(tsconfigPath);
const newService = createLanguageService(tsconfigPath, workspacePath, docContext);
services.set(tsconfigPathOrWorkspacePath, newService);
service = await newService;
}
else {
service = await services.get(tsconfigPathOrWorkspacePath);
}
if (pendingForAllServices.size > 0) {
await Promise.all(pendingForAllServices);
}
return service;
}
async function createLanguageService(tsconfigPath, workspacePath, docContext) {
const { tsSystem } = docContext;
const projectConfig = getParsedConfig();
const { options: compilerOptions, raw, errors: configErrors } = projectConfig;
const allowJs = compilerOptions.allowJs ?? !!compilerOptions.checkJs;
const virtualDocuments = new fileCollection_1.FileMap(tsSystem.useCaseSensitiveFileNames);
const getCanonicalFileName = (0, utils_1.createGetCanonicalFileName)(tsSystem.useCaseSensitiveFileNames);
watchWildCardDirectories(projectConfig);
const snapshotManager = createSnapshotManager(projectConfig, tsconfigPath);
// Load all configs within the tsconfig scope and the one above so that they are all loaded
// by the time they need to be accessed synchronously by DocumentSnapshots.
await configLoader_1.configLoader.loadConfigs(workspacePath);
const svelteModuleLoader = (0, module_loader_1.createSvelteModuleLoader)(getSnapshot, compilerOptions, tsSystem, typescript_1.default, () => host?.getCompilerHost?.());
let svelteTsPath;
/**
* set and clear during program creation, shouldn't not be cached elsewhere
*/
let compilerHost;
try {
// For when svelte2tsx/svelte-check is part of node_modules, for example VS Code extension
svelteTsPath = (0, path_1.dirname)(require.resolve(docContext.ambientTypesSource));
}
catch (e) {
// Fall back to dirname
svelteTsPath = __dirname;
}
const sveltePackageInfo = (0, importPackage_1.getPackageInfo)('svelte', tsconfigPath || workspacePath);
// Svelte 4 has some fixes with regards to parsing the generics attribute.
// Svelte 5 has new features, but we don't want to add the new compiler into language-tools. In the future it's probably
// best to shift more and more of this into user's node_modules for better handling of multiple Svelte versions.
const svelteCompiler = sveltePackageInfo.version.major >= 4
? (0, importPackage_1.importSvelte)(tsconfigPath || workspacePath)
: undefined;
const changedFilesForExportCache = new Set();
const svelteTsxFilesToOriginalCasing = getSvelteShimFiles();
let languageServiceReducedMode = false;
let projectVersion = 0;
let dirty = projectConfig.fileNames.length > 0;
let skipSvelteInputCheck = !tsconfigPath;
const host = {
log: (message) => logger_1.Logger.debug(`[ts] ${message}`),
getCompilationSettings: () => compilerOptions,
getScriptFileNames,
getScriptVersion: (fileName) => getSnapshotIfExists(fileName)?.version.toString() || '',
getScriptSnapshot: getSnapshotIfExists,
getCurrentDirectory: () => workspacePath,
getDefaultLibFileName: typescript_1.default.getDefaultLibFilePath,
fileExists: svelteModuleLoader.fileExists,
readFile: svelteModuleLoader.readFile,
resolveModuleNames: svelteModuleLoader.resolveModuleNames,
readDirectory: svelteModuleLoader.readDirectory,
realpath: tsSystem.realpath,
getDirectories: tsSystem.getDirectories,
getProjectReferences: () => projectConfig.projectReferences,
getParsedCommandLine,
useCaseSensitiveFileNames: () => tsSystem.useCaseSensitiveFileNames,
getScriptKind: (fileName) => getSnapshot(fileName).scriptKind,
getProjectVersion: () => projectVersion.toString(),
getNewLine: () => tsSystem.newLine,
resolveTypeReferenceDirectiveReferences: svelteModuleLoader.resolveTypeReferenceDirectiveReferences,
hasInvalidatedResolutions: svelteModuleLoader.mightHaveInvalidatedResolutions,
getModuleResolutionCache: svelteModuleLoader.getModuleResolutionCache,
useSourceOfProjectReferenceRedirect() {
return !languageServiceReducedMode;
},
setCompilerHost: (host) => (compilerHost = host),
getCompilerHost: () => compilerHost
};
const documentRegistry = getOrCreateDocumentRegistry(
// this should mostly be a singleton while host.getCurrentDirectory() might be the directory where the tsconfig is
tsSystem.getCurrentDirectory(), tsSystem.useCaseSensitiveFileNames);
const transformationConfig = {
parse: svelteCompiler?.parse,
version: svelteCompiler?.VERSION,
transformOnTemplateError: docContext.transformOnTemplateError,
typingsNamespace: raw?.svelteOptions?.namespace || 'svelteHTML'
};
const project = initLsCacheProject();
const languageService = typescript_1.default.createLanguageService(host, documentRegistry);
docContext.globalSnapshotsManager.onChange(scheduleUpdate);
reduceLanguageServiceCapabilityIfFileSizeTooBig();
watchConfigFiles(projectConfig.extendedConfigPaths, projectConfig);
return {
tsconfigPath,
compilerOptions,
configErrors,
getService,
updateSnapshot,
deleteSnapshot,
scheduleProjectFileUpdate,
updateTsOrJsFile,
ensureProjectFileUpdates,
hasFile,
fileBelongsToProject,
snapshotManager,
invalidateModuleCache,
onAutoImportProviderSettingsChanged,
onPackageJsonChange,
getTsConfigSvelteOptions,
getResolvedProjectReferences,
openVirtualDocument,
isShimFiles,
dispose
};
function createSnapshotManager(parsedCommandLine, configFileName) {
const cached = configFileName ? parsedTsConfigInfo.get(configFileName) : undefined;
if (cached?.snapshotManager) {
return cached.snapshotManager;
}
// raw is the tsconfig merged with extending config
// see: https://github.com/microsoft/TypeScript/blob/08e4f369fbb2a5f0c30dee973618d65e6f7f09f8/src/compiler/commandLineParser.ts#L2537
return new SnapshotManager_1.SnapshotManager(docContext.globalSnapshotsManager, parsedCommandLine.raw, configFileName ? (0, path_1.dirname)(configFileName) : workspacePath, tsSystem, parsedCommandLine.fileNames.map(utils_1.normalizePath), parsedCommandLine.wildcardDirectories);
}
function watchWildCardDirectories(parseCommandLine) {
const { wildcardDirectories } = parseCommandLine;
if (!wildcardDirectories || !docContext.watchDirectory) {
return;
}
const canonicalWorkspacePath = getCanonicalFileName(workspacePath);
const patterns = [];
Object.entries(wildcardDirectories).forEach(([dir, flags]) => {
if (
// already watched
getCanonicalFileName(dir).startsWith(canonicalWorkspacePath) ||
!tsSystem.directoryExists(dir)) {
return;
}
patterns.push({
baseUri: (0, utils_1.pathToUrl)(dir),
pattern: (flags & typescript_1.default.WatchDirectoryFlags.Recursive ? `**/` : '') +
docContext.nonRecursiveWatchPattern
});
});
docContext.watchDirectory?.(patterns);
}
function getService(skipSynchronize) {
ensureProjectFileUpdates();
if (!skipSynchronize) {
updateIfDirty();
}
return languageService;
}
function deleteSnapshot(filePath) {
svelteModuleLoader.deleteFromModuleCache(filePath);
snapshotManager.delete(filePath);
configFileForOpenFiles.delete(filePath);
}
function invalidateModuleCache(filePaths) {
for (const filePath of filePaths) {
const normalizedPath = (0, utils_1.normalizePath)(filePath);
svelteModuleLoader.deleteFromModuleCache(normalizedPath);
svelteModuleLoader.deleteUnresolvedResolutionsFromCache(normalizedPath);
scheduleUpdate(normalizedPath);
}
}
function updateSnapshot(documentOrFilePath) {
return typeof documentOrFilePath === 'string'
? updateSnapshotFromFilePath(documentOrFilePath)
: updateSnapshotFromDocument(documentOrFilePath);
}
function updateSnapshotFromDocument(document) {
const filePath = document.getFilePath() || '';
const prevSnapshot = snapshotManager.get(filePath);
if (prevSnapshot?.version === document.version &&
// In the test, there might be a new document instance with a different openedByClient
// In that case, Create a new snapshot otherwise the getClientFileNames won't include the new client file
prevSnapshot.isOpenedInClient() === document.openedByClient) {
return prevSnapshot;
}
const newSnapshot = DocumentSnapshot_1.DocumentSnapshot.fromDocument(document, transformationConfig);
if (!prevSnapshot) {
svelteModuleLoader.deleteUnresolvedResolutionsFromCache(filePath);
if (configFileForOpenFiles.get(filePath) === '' && services.size > 1) {
configFileForOpenFiles.delete(filePath);
}
}
else if (prevSnapshot.scriptKind !== newSnapshot.scriptKind && !allowJs) {
// if allowJs is false, we need to invalid the cache so that js svelte files can be loaded through module resolution
svelteModuleLoader.deleteFromModuleCache(filePath);
configFileForOpenFiles.delete(filePath);
}
snapshotManager.set(filePath, newSnapshot);
return newSnapshot;
}
function updateSnapshotFromFilePath(filePath) {
const prevSnapshot = snapshotManager.get(filePath);
if (prevSnapshot) {
return prevSnapshot;
}
return createSnapshot(filePath);
}
/**
* Deleted files will still be requested during the program update.
* Don't create snapshots for them.
* Otherwise, deleteUnresolvedResolutionsFromCache won't be called when the file is created again
*/
function getSnapshotIfExists(fileName) {
const svelteFileName = (0, utils_2.ensureRealSvelteFilePath)(fileName);
let doc = snapshotManager.get(fileName) ?? snapshotManager.get(svelteFileName);
if (doc) {
return doc;
}
if (!svelteModuleLoader.fileExists(fileName)) {
return undefined;
}
return createSnapshot(svelteModuleLoader.svelteFileExists(fileName) ? svelteFileName : fileName);
}
function getSnapshot(fileName) {
const svelteFileName = (0, utils_2.ensureRealSvelteFilePath)(fileName);
let doc = snapshotManager.get(fileName) ?? snapshotManager.get(svelteFileName);
if (doc) {
return doc;
}
return createSnapshot(fileName);
}
function createSnapshot(fileName) {
svelteModuleLoader.deleteUnresolvedResolutionsFromCache(fileName);
const doc = DocumentSnapshot_1.DocumentSnapshot.fromFilePath(fileName, docContext.createDocument, transformationConfig, tsSystem);
snapshotManager.set(fileName, doc);
return doc;
}
function scheduleProjectFileUpdate(watcherNewFiles) {
if (!snapshotManager.areIgnoredFromNewFileWatch(watcherNewFiles)) {
scheduleUpdate();
const info = parsedTsConfigInfo.get(tsconfigPath);
if (info) {
info.pendingProjectFileUpdate = true;
}
}
if (!projectConfig.projectReferences) {
return;
}
for (const ref of projectConfig.projectReferences) {
const config = parsedTsConfigInfo.get(ref.path);
if (config &&
// handled by the respective service
!services.has(config.configFilePath) &&
!config.snapshotManager.areIgnoredFromNewFileWatch(watcherNewFiles)) {
config.pendingProjectFileUpdate = true;
scheduleUpdate();
}
}
}
function ensureProjectFileUpdates(newFile) {
const info = parsedTsConfigInfo.get(tsconfigPath);
if (!info) {
return;
}
if (newFile &&
!info.pendingProjectFileUpdate &&
// no global snapshots yet when initial load pending
!snapshotManager.isProjectFile(newFile) &&
!docContext.globalSnapshotsManager.get(newFile)) {
scheduleProjectFileUpdate([newFile]);
}
if (!info.pendingProjectFileUpdate) {
return;
}
const projectFileCountBefore = snapshotManager.getProjectFileNames().length;
ensureFilesForConfigUpdates(info);
const projectFileCountAfter = snapshotManager.getProjectFileNames().length;
if (projectFileCountAfter > projectFileCountBefore) {
reduceLanguageServiceCapabilityIfFileSizeTooBig();
}
}
function getScriptFileNames() {
const projectFiles = languageServiceReducedMode
? []
: snapshotManager.getProjectFileNames();
const canonicalProjectFileNames = new Set(projectFiles.map(getCanonicalFileName));
// We only assign project files (i.e. those found through includes config) and virtual files to getScriptFileNames.
// We don't to include other client files otherwise they stay in the program and are never removed
const clientFiles = tsconfigPath
? Array.from(virtualDocuments.values())
.map((v) => v.getFilePath())
.filter(utils_1.isNotNullOrUndefined)
: snapshotManager.getClientFileNames();
return Array.from(new Set([
...projectFiles,
// project file is read from the file system so it's more likely to have
// the correct casing
...clientFiles.filter((file) => !canonicalProjectFileNames.has(getCanonicalFileName(file))),
// Use original casing here, too: people could have their VS Code extensions in a case insensitive
// folder but their project in a case sensitive one; and if we copy the shims into the case sensitive
// part it would break when canonicalizing it.
...svelteTsxFilesToOriginalCasing.values()
]));
}
function hasFile(filePath) {
return snapshotManager.has(filePath);
}
function fileBelongsToProject(filePath, isNew) {
filePath = (0, utils_1.normalizePath)(filePath);
if (hasFile(filePath)) {
return true;
}
if (!isNew) {
return false;
}
ensureProjectFileUpdates(filePath);
return snapshotManager.isProjectFile(filePath);
}
function updateTsOrJsFile(fileName, changes) {
if (!snapshotManager.has(fileName)) {
svelteModuleLoader.deleteUnresolvedResolutionsFromCache(fileName);
}
snapshotManager.updateTsOrJsFile(fileName, changes);
}
function getParsedConfig() {
let compilerOptions;
let parsedConfig;
let extendedConfigPaths;
if (tsconfigPath) {
const info = ensureTsConfigInfoUpToDate(tsconfigPath);
// tsconfig is either found from file-system or passed from svelte-check
// so this is already be validated to exist
if (!info) {
throw new Error('Failed to get tsconfig: ' + tsconfigPath);
}
compilerOptions = info.parsedCommandLine.options;
parsedConfig = info.parsedCommandLine;
extendedConfigPaths = info.extendedConfigPaths;
}
else {
const config = parseDefaultCompilerOptions();
compilerOptions = config.compilerOptions;
parsedConfig = config.parsedConfig;
}
if (!compilerOptions.moduleResolution ||
compilerOptions.moduleResolution === typescript_1.default.ModuleResolutionKind.Classic) {
compilerOptions.moduleResolution =
// NodeJS: up to 4.9, Node10: since 5.0
typescript_1.default.ModuleResolutionKind.NodeJs ?? typescript_1.default.ModuleResolutionKind.Node10;
}
if (!compilerOptions.module ||
[
typescript_1.default.ModuleKind.AMD,
typescript_1.default.ModuleKind.CommonJS,
typescript_1.default.ModuleKind.ES2015,
typescript_1.default.ModuleKind.None,
typescript_1.default.ModuleKind.System,
typescript_1.default.ModuleKind.UMD
].includes(compilerOptions.module)) {
compilerOptions.module = typescript_1.default.ModuleKind.ESNext;
}
if (!compilerOptions.target) {
compilerOptions.target = typescript_1.default.ScriptTarget.Latest;
}
else if (typescript_1.default.ScriptTarget.ES2015 > compilerOptions.target) {
compilerOptions.target = typescript_1.default.ScriptTarget.ES2015;
}
// detect which JSX namespace to use (svelte | svelteNative) if not specified or not compatible
if (!compilerOptions.jsxFactory || !compilerOptions.jsxFactory.startsWith('svelte')) {
//override if we detect svelte-native
if (workspacePath) {
try {
const svelteNativePkgInfo = (0, importPackage_1.getPackageInfo)('svelte-native', workspacePath);
if (svelteNativePkgInfo.path) {
// For backwards compatibility
parsedConfig.raw.svelteOptions = parsedConfig.raw.svelteOptions || {};
parsedConfig.raw.svelteOptions.namespace = 'svelteNative.JSX';
}
}
catch (e) {
//we stay regular svelte
}
}
}
return {
...parsedConfig,
fileNames: parsedConfig.fileNames.map(utils_1.normalizePath),
options: compilerOptions,
extendedConfigPaths
};
}
function checkSvelteInput(program, config) {
if (!tsconfigPath || config.raw.references || config.raw.files) {
return [];
}
const configFileName = (0, path_1.basename)(tsconfigPath);
// Only report to possible nearest config file since referenced project might not be a svelte project
if (configFileName !== 'tsconfig.json' && configFileName !== 'jsconfig.json') {
return [];
}
const hasSvelteFiles = config.fileNames.some(utils_2.isSvelteFilePath) ||
program?.getSourceFiles().some((file) => (0, utils_2.isSvelteFilePath)(file.fileName));
if (hasSvelteFiles) {
return [];
}
const { include, exclude } = config.raw;
const inputText = JSON.stringify(include);
const excludeText = JSON.stringify(exclude);
const svelteConfigDiagnostics = [
{
category: typescript_1.default.DiagnosticCategory.Warning,
code: TsconfigSvelteDiagnostics.NO_SVELTE_INPUT,
file: undefined,
start: undefined,
length: undefined,
messageText: `No svelte input files were found in config file '${tsconfigPath}'. ` +
`Did you forget to add svelte files to the 'include' in your ${(0, path_1.basename)(tsconfigPath)}? ` +
`Specified 'include' paths were '${inputText}' and 'exclude' paths were '${excludeText}'`,
source: 'svelte'
}
];
return svelteConfigDiagnostics;
}
function parseDefaultCompilerOptions() {
let configJson = {
compilerOptions: {
allowJs: true,
noEmit: true,
declaration: false,
skipLibCheck: true,
maxNodeModuleJsDepth: 2,
allowSyntheticDefaultImports: true
},
// Necessary to not flood the initial files
// with potentially completely unrelated .ts/.js files:
include: []
};
const parsedConfig = typescript_1.default.parseJsonConfigFileContent(configJson, tsSystem, workspacePath);
const compilerOptions = {
...parsedConfig.options,
target: typescript_1.default.ScriptTarget.Latest,
allowNonTsExtensions: true,
moduleResolution: typescript_1.default.ModuleResolutionKind.Node10
};
return { compilerOptions, parsedConfig };
}
/**
* Disable usage of project files.
* running language service in a reduced mode for
* large projects with improperly excluded tsconfig.
*/
function reduceLanguageServiceCapabilityIfFileSizeTooBig() {
if (exceedsTotalSizeLimitForNonTsFiles(compilerOptions, tsconfigPath, snapshotManager, tsSystem)) {
languageService.cleanupSemanticCache();
languageServiceReducedMode = true;
if (project) {
project.languageServiceEnabled = false;
}
docContext.notifyExceedSizeLimit?.();
}
}
function dispose() {
compilerHost = undefined;
languageService.dispose();
snapshotManager.dispose();
configWatchers.get(tsconfigPath)?.close();
configWatchers.delete(tsconfigPath);
configFileForOpenFiles.clear();
docContext.globalSnapshotsManager.removeChangeListener(scheduleUpdate);
}
function watchConfigFiles(extendedConfigPaths, parsedCommandLine) {
const tsconfigDependencies = Array.from(extendedConfigPaths ?? []).concat(parsedCommandLine.projectReferences?.map((r) => r.path) ?? []);
tsconfigDependencies.forEach((configPath) => {
let dependedTsConfig = configPathToDependedProject.get(configPath);
if (!dependedTsConfig) {
dependedTsConfig = new fileCollection_1.FileSet(tsSystem.useCaseSensitiveFileNames);
configPathToDependedProject.set(configPath, dependedTsConfig);
}
dependedTsConfig.add(tsconfigPath);
});
if (!tsSystem.watchFile || !docContext.watchTsConfig) {
return;
}
if (!configWatchers.has(tsconfigPath) && tsconfigPath) {
configFileModifiedTime.set(tsconfigPath, tsSystem.getModifiedTime?.(tsconfigPath));
configWatchers.set(tsconfigPath,
// for some reason setting the polling interval is necessary, else some error in TS is thrown
tsSystem.watchFile(tsconfigPath, watchConfigCallback, 1000));
}
for (const config of tsconfigDependencies) {
if (dependedConfigWatchers.has(config)) {
continue;
}
configFileModifiedTime.set(config, tsSystem.getModifiedTime?.(config));
dependedConfigWatchers.set(config,
// for some reason setting the polling interval is necessary, else some error in TS is thrown
tsSystem.watchFile(config, createWatchDependedConfigCallback(docContext), 1000));
}
}
async function watchConfigCallback(fileName, kind, modifiedTime) {
if (kind === typescript_1.default.FileWatcherEventKind.Changed &&
!configFileModified(fileName, modifiedTime ?? tsSystem.getModifiedTime?.(fileName))) {
return;
}
dispose();
if (kind === typescript_1.default.FileWatcherEventKind.Changed) {
scheduleReload(fileName);
}
else if (kind === typescript_1.default.FileWatcherEventKind.Deleted) {
services.delete(fileName);
configFileForOpenFiles.clear();
}
docContext.onProjectReloaded?.([fileName]);
docContext.reportConfigError?.({ uri: (0, utils_1.pathToUrl)(fileName), diagnostics: [] });
}
function updateIfDirty() {
if (!dirty) {
return;
}
svelteModuleLoader.invalidateFailedLocationResolution();
const oldProgram = project?.program;
let program;
try {
program = languageService.getProgram();
}
finally {
// mark as clean even if the update fails, at least we can still try again next time there is a change
dirty = false;
compilerHost = undefined;
svelteModuleLoader.clearPendingInvalidations();
}
if (project) {
project.program = program;
}
if (!skipSvelteInputCheck) {
const svelteConfigDiagnostics = checkSvelteInput(program, projectConfig);
const codes = svelteConfigDiagnostics.map((d) => d.code);
if (!svelteConfigDiagnostics.length) {
// stop checking once it passed once
skipSvelteInputCheck = true;
}
// report even if empty to clear previous diagnostics
docContext.reportConfigError?.({
uri: (0, utils_1.pathToUrl)(tsconfigPath),
diagnostics: svelteConfigDiagnostics.map((d) => ({
message: d.messageText,
range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } },
severity: vscode_languageserver_protocol_1.DiagnosticSeverity.Warning,
source: 'svelte'
}))
});
const new_errors = projectConfig.errors
.filter((e) => !codes.includes(e.code))
.concat(svelteConfigDiagnostics);
projectConfig.errors.splice(0, projectConfig.errors.length, ...new_errors);
}
// https://github.com/microsoft/TypeScript/blob/23faef92703556567ddbcb9afb893f4ba638fc20/src/server/project.ts#L1624
// host.getCachedExportInfoMap will create the cache if it doesn't exist
// so we need to check the property instead
const exportMapCache = project?.exportMapCache;
if (!oldProgram || !exportMapCache || exportMapCache.isEmpty()) {
changedFilesForExportCache.clear();
return;
}
exportMapCache.releaseSymbols();
// https://github.com/microsoft/TypeScript/blob/941d1543c201e40d87e63c9db04818493afdd9e7/src/server/project.ts#L1731
// if one file change results in clearing the cache
// don't continue to check other files, this will mark the cache as usable while it's empty
for (const fileName of changedFilesForExportCache) {
const oldFile = oldProgram.getSourceFile(fileName);
const newFile = program?.getSourceFile(fileName);
// file for another tsconfig
if (!oldFile && !newFile) {
continue;
}
if (!oldFile || !newFile) {
// new file or deleted file
exportMapCache.clear();
break;
}
const cleared = exportMapCache.onFileChanged?.(oldFile, newFile, false);
if (cleared) {
break;
}
}
changedFilesForExportCache.clear();
}
function scheduleUpdate(triggeredFile) {
if (triggeredFile) {
changedFilesForExportCache.add(triggeredFile);
}
if (dirty) {
return;
}
projectVersion++;
dirty = true;
}
function initLsCacheProject() {
const projectService = docContext.projectService;
if (!projectService) {
return;
}
// Used by typescript-auto-import-cache to create a lean language service for package.json auto-import.
const createLanguageServiceForAutoImportProvider = (host) => typescript_1.default.createLanguageService(host, documentRegistry);
return (0, serviceCache_1.createProject)(host, createLanguageServiceForAutoImportProvider, {
compilerOptions: compilerOptions,
projectService: projectService,
currentDirectory: workspacePath
});
}
function onAutoImportProviderSettingsChanged() {
project?.onAutoImportProviderSettingsChanged();
}
function onPackageJsonChange(packageJsonPath) {
if (!project) {
return;
}
if (project.packageJsonsForAutoImport?.has(packageJsonPath)) {
project.moduleSpecifierCache.clear();
if (project.autoImportProviderHost) {
project.autoImportProviderHost.markAsDirty();
}
}
if (packageJsonPath.includes('node_modules')) {
const dir = (0, path_1.dirname)(packageJsonPath);
const inProgram = project
.getCurrentProgram()
?.getSourceFiles()
.some((file) => file.fileName.includes(dir));
if (inProgram) {
host.getModuleSpecifierCache?.().clear();
}
}
}
function getTsConfigSvelteOptions() {
// if there's more options in the future, get it from raw.svelteOptions and normalize it
return {
namespace: transformationConfig.typingsNamespace
};
}
function ensureTsConfigInfoUpToDate(configFilePath) {
const cached = parsedTsConfigInfo.get(configFilePath);
if (cached !== undefined) {
ensureFilesForConfigUpdates(cached);
return cached;
}
const content = tsSystem.fileExists(configFilePath) && tsSystem.readFile(configFilePath);
if (!content) {
parsedTsConfigInfo.set(configFilePath, null);
return null;
}
const json = typescript_1.default.parseJsonText(configFilePath, content);
const extendedConfigPaths = new Set();
const { extendedConfigCache } = docContext;
const cacheMonitorProxy = {
...docContext.extendedConfigCache,
get(key) {
extendedConfigPaths.add(key);
return extendedConfigCache.get(key);
},
has(key) {
extendedConfigPaths.add(key);
return extendedConfigCache.has(key);
},
set(key, value) {
extendedConfigPaths.add(key);
return extendedConfigCache.set(key, value);
}
};
// TypeScript will throw if the parsedCommandLine doesn't include the sourceFile for the config file
// i.e. it must be directly parse from the json text instead of a javascript object like we do in getParsedConfig
const parsedCommandLine = typescript_1.default.parseJsonSourceFileConfigFileContent(json, tsSystem, (0, path_1.dirname)(configFilePath),
/*existingOptions*/ undefined, configFilePath,
/*resolutionStack*/ undefined, [
{
extension: 'svelte',
isMixedContent: true,
// Deferred was added in a later TS version, fall back to tsx
// If Deferred exists, this means that all Svelte files are included
// in parsedConfig.fileNames
scriptKind: typescript_1.default.ScriptKind.Deferred ?? typescript_1.default.ScriptKind.TS
}
], cacheMonitorProxy);
parsedCommandLine.options.allowNonTsExtensions = true;
const snapshotManager = createSnapshotManager(parsedCommandLine, configFilePath);
const tsconfigInfo = {
parsedCommandLine,
snapshotManager,
pendingProjectFileUpdate: false,
configFilePath,
extendedConfigPaths
};
parsedTsConfigInfo.set(configFilePath, tsconfigInfo);
watchConfigFiles(extendedConfigPaths, parsedCommandLine);
return tsconfigInfo;
}
function getParsedCommandLine(configFilePath) {
return ensureTsConfigInfoUpToDate(configFilePath)?.parsedCommandLine;
}
function ensureFilesForConfigUpdates(info) {
if (info?.pendingProjectFileUpdate) {
info.pendingProjectFileUpdate = false;
info.snapshotManager.updateProjectFiles();
info.parsedCommandLine.fileNames = info.snapshotManager.getProjectFileNames();
}
}
function getResolvedProjectReferences() {
if (!tsconfigPath || !projectConfig.projectReferences) {
return [];
}
return projectConfig.projectReferences
.map((ref) => ensureTsConfigInfoUpToDate((0, utils_1.normalizePath)(ref.path)))
.filter(utils_1.isNotNullOrUndefined);
}
function openVirtualDocument(document) {
const filePath = document.getFilePath();
if (!filePath) {
return;
}
virtualDocuments.set(filePath, document);
configFileForOpenFiles.set(filePath, tsconfigPath || workspacePath);
updateSnapshot(document);
scheduleUpdate(filePath);
}
function getSvelteShimFiles() {
const svelteTsxFiles = svelte2tsx_1.internalHelpers.get_global_types(tsSystem, sveltePackageInfo.version.major === 3, sveltePackageInfo.path, svelteTsPath, docContext.isSvelteCheck ? undefined : tsconfigPath || workspacePath);
const pathToOriginalCasing = new Map();
for (const file of svelteTsxFiles) {
const normalizedPath = (0, utils_1.normalizePath)(file);
pathToOriginalCasing.set(getCanonicalFileName(normalizedPath), normalizedPath);
}
return pathToOriginalCasing;
}
function isShimFiles(filePath) {
return svelteTsxFilesToOriginalCasing.has(getCanonicalFileName((0, utils_1.normalizePath)(filePath)));
}
}
/**
* adopted from https://github.com/microsoft/TypeScript/blob/3c8e45b304b8572094c5d7fbb9cd768dbf6417c0/src/server/editorServices.ts#L1955
*/
function exceedsTotalSizeLimitForNonTsFiles(compilerOptions, tsconfigPath, snapshotManager, tsSystem) {
if (compilerOptions.disableSizeLimit) {
return false;
}
let availableSpace = maxProgramSizeForNonTsFiles;
serviceSizeMap.set(tsconfigPath, 0);
serviceSizeMap.forEach((size) => {
availableSpace -= size;
});
let totalNonTsFileSize = 0;
const fileNames = snapshotManager.getProjectFileNames();
for (const fileName of fileNames) {
if ((0, utils_2.hasTsExtensions)(fileName)) {
continue;
}
totalNonTsFileSize += tsSystem.getFileSize?.(fileName) ?? 0;
if (totalNonTsFileSize > availableSpace) {
const top5LargestFiles = fileNames
.filter((name) => !(0, utils_2.hasTsExtensions)(name))
.map((name) => ({ name, size: tsSystem.getFileSize?.(name) ?? 0 }))
.sort((a, b) => b.size - a.size)
.slice(0, 5);
logger_1.Logger.log(`Non TS file size exceeded limit (${totalNonTsFileSize}). ` +
`Largest files: ${top5LargestFiles
.map((file) => `${file.name}:${file.size}`)
.join(', ')}`);
return true;
}
}
serviceSizeMap.set(tsconfigPath, totalNonTsFileSize);
return false;
}
/**
* shared watcher callback can't be within `createLanguageService`
* because it would reference the closure
* So that GC won't drop it and cause memory leaks
*/
function createWatchDependedConfigCallback(docContext) {
return async (fileName, kind, modifiedTime) => {
if (kind === typescript_1.default.FileWatcherEventKind.Changed &&
!configFileModified(fileName, modifiedTime ?? docContext.tsSystem.getModifiedTime?.(fileName))) {
return;
}
const getCanonicalFileName = (0, utils_1.createGetCanonicalFileName)(docContext.tsSystem.useCaseSensitiveFileNames);
docContext.extendedConfigCache.delete(getCanonicalFileName(fileName));
// rely on TypeScript internal behavior so delete both just in case
docContext.extendedConfigCache.delete(fileName);
const reloadingConfigs = [];
const promises = Array.from(configPathToDependedProject.get(fileName) ?? []).map(async (config) => {
reloadingConfigs.push(config);
const oldService = services.get(config);
scheduleReload(config);
(await oldService)?.dispose();
});
await Promise.all(promises);
docContext.onProjectReloaded?.(reloadingConfigs);
};
}
/**
* check if file content is modified instead of attributes changed
*/
function configFileModified(fileName, modifiedTime) {
const previousModifiedTime = configFileModifiedTime.get(fileName);
if (!modifiedTime || !previousModifiedTime) {
return true;
}
if (previousModifiedTime >= modifiedTime) {
return false;
}
configFileModifiedTime.set(fileName, modifiedTime);
return true;
}
/**
* schedule to the service reload to the next time the
* service in requested
* if there's still files opened it should be restarted
* in the onProjectReloaded hooks
*/
function scheduleReload(fileName) {
// don't delete service from map yet as it could result in a race condition
// where a file update is received before the service is reloaded, swallowing the update
pendingReloads.add(fileName);
}
function getOrCreateDocumentRegistry(currentDirectory, useCaseSensitiveFileNames) {
// unless it's a multi root workspace, there's only one registry
const key = [currentDirectory, useCaseSensitiveFileNames].join('|');
let registry = documentRegistries.get(key);
if (registry) {
return registry;
}
registry = typescript_1.default.createDocumentRegistry(useCaseSensitiveFileNames, currentDirectory);
const acquireDocumentWithKey = registry.acquireDocumentWithKey;
registry.acquireDocumentWithKey = (fileName, path, compilationSettingsOrHost, key, scriptSnapshot, version, scriptKind, sourceFileOptions) => {
ensureImpliedNodeFormat(compilationSettingsOrHost, fileName, sourceFileOptions);
return acquireDocumentWithKey(fileName, path, compilationSettingsOrHost, key, scriptSnapshot, version, scriptKind, sourceFileOptions);
};
const updateDocumentWithKey = registry.updateDocumentWithKey;
registry.updateDocumentWithKey = (fileName, path, compilationSettingsOrHost, key, scriptSnapshot, version, scriptKind, sourceFileOptions) => {
ensureImpliedNodeFormat(compilationSettingsOrHost, fileName, sourceFileOptions);
return updateDocumentWithKey(fileName, path, compilationSettingsOrHost, key, scriptSnapshot, version, scriptKind, sourceFileOptions);
};
documentRegistries.set(key, registry);
return registry;
function ensureImpliedNodeFormat(compilationSettingsOrHost, fileName, sourceFileOptions) {
const compilationSettings = getCompilationSettings(compilationSettingsOrHost);
const host = compilationSettingsOrHost === compilationSettings
? undefined
: compilationSettingsOrHost;
if (host &&
(0, utils_2.isSvelteFilePath)(fileName) &&
typeof sourceFileOptions === 'object' &&
!sourceFileOptions.impliedNodeFormat) {
const format = typescript_1.default.getImpliedNodeFormatForFile((0, utils_2.toVirtualSvelteFilePath)(fileName), host?.getCompilerHost?.()?.getModuleResolutionCache?.()?.getPackageJsonInfoCache(), host, compilationSettings);
sourceFileOptions.impliedNodeFormat = format;
}
}
function getCompilationSettings(settingsOrHost) {
if (typeof settingsOrHost.getCompilationSettings === 'function') {
return settingsOrHost.getCompilationSettings();
}
return settingsOrHost;
}
}
//# sourceMappingURL=service.js.map