typescript-svelte-plugin
Advanced tools
| export interface Configuration { | ||
| enable: boolean; | ||
| } | ||
| export declare class ConfigManager { | ||
| private emitter; | ||
| private config; | ||
| onConfigurationChanged(listener: (config: Configuration) => void): void; | ||
| updateConfigFromPluginConfig(config: Configuration): void; | ||
| getConfig(): Configuration; | ||
| } |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.ConfigManager = void 0; | ||
| const events_1 = require("events"); | ||
| const configurationEventName = 'configuration-changed'; | ||
| class ConfigManager { | ||
| constructor() { | ||
| this.emitter = new events_1.EventEmitter(); | ||
| this.config = { | ||
| enable: true | ||
| }; | ||
| } | ||
| onConfigurationChanged(listener) { | ||
| this.emitter.on(configurationEventName, listener); | ||
| } | ||
| updateConfigFromPluginConfig(config) { | ||
| this.config = { | ||
| ...this.config, | ||
| ...config | ||
| }; | ||
| this.emitter.emit(configurationEventName, config); | ||
| } | ||
| getConfig() { | ||
| return this.config; | ||
| } | ||
| } | ||
| exports.ConfigManager = ConfigManager; |
| import type ts from 'typescript/lib/tsserverlibrary'; | ||
| import { ConfigManager } from './config-manager'; | ||
| import { SvelteSnapshotManager } from './svelte-snapshots'; | ||
| export interface TsFilesSpec { | ||
| include?: readonly string[]; | ||
| exclude?: readonly string[]; | ||
| } | ||
| export declare class ProjectSvelteFilesManager { | ||
| private readonly typescript; | ||
| private readonly project; | ||
| private readonly serverHost; | ||
| private readonly snapshotManager; | ||
| private parsedCommandLine; | ||
| private files; | ||
| private directoryWatchers; | ||
| private static instances; | ||
| static getInstance(projectName: string): ProjectSvelteFilesManager | undefined; | ||
| constructor(typescript: typeof ts, project: ts.server.Project, serverHost: ts.server.ServerHost, snapshotManager: SvelteSnapshotManager, parsedCommandLine: ts.ParsedCommandLine, configManager: ConfigManager); | ||
| updateProjectConfig(serviceHost: ts.LanguageServiceHost): void; | ||
| getFiles(): string[]; | ||
| /** | ||
| * Create directory watcher for include and exclude | ||
| * The watcher in tsserver doesn't support svelte file | ||
| * It won't add new created svelte file to root | ||
| */ | ||
| private setupWatchers; | ||
| private watcherCallback; | ||
| private updateProjectSvelteFiles; | ||
| private addFileToProject; | ||
| private readProjectSvelteFilesFromFs; | ||
| private onConfigChanged; | ||
| private removeFileFromProject; | ||
| private disposeWatchersAndFiles; | ||
| dispose(): void; | ||
| } |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.ProjectSvelteFilesManager = void 0; | ||
| const utils_1 = require("./utils"); | ||
| class ProjectSvelteFilesManager { | ||
| constructor(typescript, project, serverHost, snapshotManager, parsedCommandLine, configManager) { | ||
| this.typescript = typescript; | ||
| this.project = project; | ||
| this.serverHost = serverHost; | ||
| this.snapshotManager = snapshotManager; | ||
| this.parsedCommandLine = parsedCommandLine; | ||
| this.files = new Set(); | ||
| this.directoryWatchers = new Set(); | ||
| if (configManager.getConfig().enable) { | ||
| this.setupWatchers(); | ||
| this.updateProjectSvelteFiles(); | ||
| } | ||
| configManager.onConfigurationChanged(this.onConfigChanged.bind(this)); | ||
| ProjectSvelteFilesManager.instances.set(project.getProjectName(), this); | ||
| } | ||
| static getInstance(projectName) { | ||
| return this.instances.get(projectName); | ||
| } | ||
| updateProjectConfig(serviceHost) { | ||
| var _a; | ||
| const parsedCommandLine = (_a = serviceHost.getParsedCommandLine) === null || _a === void 0 ? void 0 : _a.call(serviceHost, (0, utils_1.getConfigPathForProject)(this.project)); | ||
| if (!parsedCommandLine) { | ||
| return; | ||
| } | ||
| this.disposeWatchersAndFiles(); | ||
| this.parsedCommandLine = parsedCommandLine; | ||
| this.setupWatchers(); | ||
| this.updateProjectSvelteFiles(); | ||
| } | ||
| getFiles() { | ||
| return Array.from(this.files); | ||
| } | ||
| /** | ||
| * Create directory watcher for include and exclude | ||
| * The watcher in tsserver doesn't support svelte file | ||
| * It won't add new created svelte file to root | ||
| */ | ||
| setupWatchers() { | ||
| for (const directory in this.parsedCommandLine.wildcardDirectories) { | ||
| if (!Object.prototype.hasOwnProperty.call(this.parsedCommandLine.wildcardDirectories, directory)) { | ||
| continue; | ||
| } | ||
| const watchDirectoryFlags = this.parsedCommandLine.wildcardDirectories[directory]; | ||
| const watcher = this.serverHost.watchDirectory(directory, this.watcherCallback.bind(this), watchDirectoryFlags === this.typescript.WatchDirectoryFlags.Recursive, this.parsedCommandLine.watchOptions); | ||
| this.directoryWatchers.add(watcher); | ||
| } | ||
| } | ||
| watcherCallback(fileName) { | ||
| if (!(0, utils_1.isSvelteFilePath)(fileName)) { | ||
| return; | ||
| } | ||
| // We can't just add the file to the project directly, because | ||
| // - the casing of fileName is different | ||
| // - we don't know whether the file was added or deleted | ||
| this.updateProjectSvelteFiles(); | ||
| } | ||
| updateProjectSvelteFiles() { | ||
| const fileNamesAfter = this.readProjectSvelteFilesFromFs(); | ||
| const removedFiles = new Set(...this.files); | ||
| const newFiles = fileNamesAfter.filter((fileName) => { | ||
| const has = this.files.has(fileName); | ||
| if (has) { | ||
| removedFiles.delete(fileName); | ||
| } | ||
| return !has; | ||
| }); | ||
| for (const newFile of newFiles) { | ||
| this.addFileToProject(newFile); | ||
| this.files.add(newFile); | ||
| } | ||
| for (const removedFile of removedFiles) { | ||
| this.removeFileFromProject(removedFile, false); | ||
| this.files.delete(removedFile); | ||
| } | ||
| } | ||
| addFileToProject(newFile) { | ||
| this.snapshotManager.create(newFile); | ||
| const snapshot = this.project.projectService.getScriptInfo(newFile); | ||
| if (snapshot) { | ||
| this.project.addRoot(snapshot); | ||
| } | ||
| } | ||
| readProjectSvelteFilesFromFs() { | ||
| const fileSpec = this.parsedCommandLine.raw; | ||
| const { include, exclude } = fileSpec; | ||
| if ((include === null || include === void 0 ? void 0 : include.length) === 0) { | ||
| return []; | ||
| } | ||
| return this.typescript.sys | ||
| .readDirectory(this.project.getCurrentDirectory() || process.cwd(), ['.svelte'], exclude, include) | ||
| .map(this.typescript.server.toNormalizedPath); | ||
| } | ||
| onConfigChanged(config) { | ||
| this.disposeWatchersAndFiles(); | ||
| if (config.enable) { | ||
| this.setupWatchers(); | ||
| this.updateProjectSvelteFiles(); | ||
| } | ||
| } | ||
| removeFileFromProject(file, exists = true) { | ||
| const info = this.project.getScriptInfo(file); | ||
| if (info) { | ||
| this.project.removeFile(info, exists, true); | ||
| } | ||
| } | ||
| disposeWatchersAndFiles() { | ||
| this.directoryWatchers.forEach((watcher) => watcher.close()); | ||
| this.directoryWatchers.clear(); | ||
| this.files.forEach((file) => this.removeFileFromProject(file)); | ||
| this.files.clear(); | ||
| } | ||
| dispose() { | ||
| this.disposeWatchersAndFiles(); | ||
| ProjectSvelteFilesManager.instances.delete(this.project.getProjectName()); | ||
| } | ||
| } | ||
| exports.ProjectSvelteFilesManager = ProjectSvelteFilesManager; | ||
| ProjectSvelteFilesManager.instances = new Map(); |
| import type ts from 'typescript/lib/tsserverlibrary'; | ||
| declare function init(modules: { | ||
| typescript: typeof ts; | ||
| }): { | ||
| create: (info: ts.server.PluginCreateInfo) => ts.LanguageService; | ||
| getExternalFiles: (project: ts.server.ConfiguredProject) => string[]; | ||
| }; | ||
| }): ts.server.PluginModule; | ||
| export = init; |
+56
-10
@@ -7,3 +7,7 @@ "use strict"; | ||
| const svelte_snapshots_1 = require("./svelte-snapshots"); | ||
| const config_manager_1 = require("./config-manager"); | ||
| const project_svelte_files_1 = require("./project-svelte-files"); | ||
| const utils_1 = require("./utils"); | ||
| function init(modules) { | ||
| const configManager = new config_manager_1.ConfigManager(); | ||
| function create(info) { | ||
@@ -16,13 +20,43 @@ var _a, _b, _c, _d; | ||
| } | ||
| logger.log('Starting Svelte plugin'); | ||
| // If someone knows a better/more performant way to get svelteOptions, | ||
| // please tell us :) | ||
| const svelteOptions = ((_d = (_c = (_b = (_a = info.languageServiceHost).getParsedCommandLine) === null || _b === void 0 ? void 0 : _b.call(_a, info.project.getCompilerOptions().configFilePath)) === null || _c === void 0 ? void 0 : _c.raw) === null || _d === void 0 ? void 0 : _d.svelteOptions) || { namespace: 'svelteHTML' }; | ||
| if ((0, language_service_1.isPatched)(info.languageService)) { | ||
| logger.log('Already patched. Checking tsconfig updates.'); | ||
| (_a = project_svelte_files_1.ProjectSvelteFilesManager.getInstance(info.project.getProjectName())) === null || _a === void 0 ? void 0 : _a.updateProjectConfig(info.languageServiceHost); | ||
| return info.languageService; | ||
| } | ||
| configManager.updateConfigFromPluginConfig(info.config); | ||
| if (configManager.getConfig().enable) { | ||
| logger.log('Starting Svelte plugin'); | ||
| } | ||
| else { | ||
| logger.log('Svelte plugin disabled'); | ||
| logger.log(info.config); | ||
| } | ||
| // This call the ConfiguredProject.getParsedCommandLine | ||
| // where it'll try to load the cached version of the parsedCommandLine | ||
| const parsedCommandLine = (_c = (_b = info.languageServiceHost).getParsedCommandLine) === null || _c === void 0 ? void 0 : _c.call(_b, (0, utils_1.getConfigPathForProject)(info.project)); | ||
| const svelteOptions = ((_d = parsedCommandLine === null || parsedCommandLine === void 0 ? void 0 : parsedCommandLine.raw) === null || _d === void 0 ? void 0 : _d.svelteOptions) || { namespace: 'svelteHTML' }; | ||
| logger.log('svelteOptions:', svelteOptions); | ||
| const snapshotManager = new svelte_snapshots_1.SvelteSnapshotManager(modules.typescript, info.project.projectService, svelteOptions, logger); | ||
| (0, module_loader_1.patchModuleLoader)(logger, snapshotManager, modules.typescript, info.languageServiceHost, info.project); | ||
| return (0, language_service_1.decorateLanguageService)(info.languageService, snapshotManager, logger); | ||
| logger.debug(parsedCommandLine === null || parsedCommandLine === void 0 ? void 0 : parsedCommandLine.wildcardDirectories); | ||
| const snapshotManager = new svelte_snapshots_1.SvelteSnapshotManager(modules.typescript, info.project.projectService, svelteOptions, logger, configManager); | ||
| const projectSvelteFilesManager = parsedCommandLine | ||
| ? new project_svelte_files_1.ProjectSvelteFilesManager(modules.typescript, info.project, info.serverHost, snapshotManager, parsedCommandLine, configManager) | ||
| : undefined; | ||
| (0, module_loader_1.patchModuleLoader)(logger, snapshotManager, modules.typescript, info.languageServiceHost, info.project, configManager); | ||
| configManager.onConfigurationChanged(() => { | ||
| // enabling/disabling the plugin means TS has to recompute stuff | ||
| info.languageService.cleanupSemanticCache(); | ||
| info.project.markAsDirty(); | ||
| // updateGraph checks for new root files | ||
| // if there's no tsconfig there isn't root files to check | ||
| if (projectSvelteFilesManager) { | ||
| info.project.updateGraph(); | ||
| } | ||
| }); | ||
| return decorateLanguageServiceDispose((0, language_service_1.decorateLanguageService)(info.languageService, snapshotManager, logger, configManager), projectSvelteFilesManager !== null && projectSvelteFilesManager !== void 0 ? projectSvelteFilesManager : { | ||
| dispose() { } | ||
| }); | ||
| } | ||
| function getExternalFiles(project) { | ||
| if (!isSvelteProject(project.getCompilerOptions())) { | ||
| var _a, _b; | ||
| if (!isSvelteProject(project.getCompilerOptions()) || !configManager.getConfig().enable) { | ||
| return []; | ||
@@ -37,3 +71,4 @@ } | ||
| ].map((f) => modules.typescript.sys.resolvePath((0, path_1.resolve)(svelteTsPath, f))); | ||
| return svelteTsxFiles; | ||
| // let ts know project svelte files to do its optimization | ||
| return svelteTsxFiles.concat((_b = (_a = project_svelte_files_1.ProjectSvelteFilesManager.getInstance(project.getProjectName())) === null || _a === void 0 ? void 0 : _a.getFiles()) !== null && _b !== void 0 ? _b : []); | ||
| } | ||
@@ -52,4 +87,15 @@ function isSvelteProject(compilerOptions) { | ||
| } | ||
| return { create, getExternalFiles }; | ||
| function onConfigurationChanged(config) { | ||
| configManager.updateConfigFromPluginConfig(config); | ||
| } | ||
| function decorateLanguageServiceDispose(languageService, disposable) { | ||
| const dispose = languageService.dispose; | ||
| languageService.dispose = () => { | ||
| disposable.dispose(); | ||
| dispose(); | ||
| }; | ||
| return languageService; | ||
| } | ||
| return { create, getExternalFiles, onConfigurationChanged }; | ||
| } | ||
| module.exports = init; |
@@ -30,3 +30,12 @@ "use strict"; | ||
| const details = getCompletionEntryDetails(fileName, position, entryName, formatOptions, source, preferences, data); | ||
| if (details || !(0, utils_1.isSvelteFilePath)(source || '')) { | ||
| if (details) { | ||
| if ((0, utils_1.isSvelteFilePath)(source || '')) { | ||
| logger.debug('TS found Svelte Component import completion details'); | ||
| return (0, utils_1.replaceDeep)(details, componentPostfix, ''); | ||
| } | ||
| else { | ||
| return details; | ||
| } | ||
| } | ||
| if (!(0, utils_1.isSvelteFilePath)(source || '')) { | ||
| return details; | ||
@@ -33,0 +42,0 @@ } |
| import type ts from 'typescript/lib/tsserverlibrary'; | ||
| import { ConfigManager } from '../config-manager'; | ||
| import { Logger } from '../logger'; | ||
| import { SvelteSnapshotManager } from '../svelte-snapshots'; | ||
| export declare function decorateLanguageService(ls: ts.LanguageService, snapshotManager: SvelteSnapshotManager, logger: Logger): ts.LanguageService; | ||
| export declare function isPatched(ls: ts.LanguageService): boolean; | ||
| export declare function decorateLanguageService(ls: ts.LanguageService, snapshotManager: SvelteSnapshotManager, logger: Logger, configManager: ConfigManager): ts.LanguageService; |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.decorateLanguageService = void 0; | ||
| exports.decorateLanguageService = exports.isPatched = void 0; | ||
| const utils_1 = require("../utils"); | ||
@@ -11,3 +11,16 @@ const completions_1 = require("./completions"); | ||
| const rename_1 = require("./rename"); | ||
| function decorateLanguageService(ls, snapshotManager, logger) { | ||
| const sveltePluginPatchSymbol = Symbol('sveltePluginPatchSymbol'); | ||
| function isPatched(ls) { | ||
| return ls[sveltePluginPatchSymbol] === true; | ||
| } | ||
| exports.isPatched = isPatched; | ||
| function decorateLanguageService(ls, snapshotManager, logger, configManager) { | ||
| // Decorate using a proxy so we can dynamically enable/disable method | ||
| // patches depending on the enabled state of our config | ||
| const proxy = new Proxy(ls, createProxyHandler(configManager)); | ||
| decorateLanguageServiceInner(proxy, snapshotManager, logger); | ||
| return proxy; | ||
| } | ||
| exports.decorateLanguageService = decorateLanguageService; | ||
| function decorateLanguageServiceInner(ls, snapshotManager, logger) { | ||
| patchLineColumnOffset(ls, snapshotManager); | ||
@@ -22,3 +35,22 @@ (0, rename_1.decorateRename)(ls, snapshotManager, logger); | ||
| } | ||
| exports.decorateLanguageService = decorateLanguageService; | ||
| function createProxyHandler(configManager) { | ||
| const decorated = {}; | ||
| return { | ||
| get(target, p) { | ||
| var _a; | ||
| // always return patch symbol whether the plugin is enabled or not | ||
| if (p === sveltePluginPatchSymbol) { | ||
| return true; | ||
| } | ||
| if (!configManager.getConfig().enable || p === 'dispose') { | ||
| return target[p]; | ||
| } | ||
| return ((_a = decorated[p]) !== null && _a !== void 0 ? _a : target[p]); | ||
| }, | ||
| set(_, p, value) { | ||
| decorated[p] = value; | ||
| return true; | ||
| } | ||
| }; | ||
| } | ||
| function patchLineColumnOffset(ls, snapshotManager) { | ||
@@ -25,0 +57,0 @@ if (!ls.toLineColumnOffset) { |
| import type ts from 'typescript/lib/tsserverlibrary'; | ||
| import { ConfigManager } from './config-manager'; | ||
| import { Logger } from './logger'; | ||
@@ -13,2 +14,2 @@ import { SvelteSnapshotManager } from './svelte-snapshots'; | ||
| */ | ||
| export declare function patchModuleLoader(logger: Logger, snapshotManager: SvelteSnapshotManager, typescript: typeof ts, lsHost: ts.LanguageServiceHost, project: ts.server.Project): void; | ||
| export declare function patchModuleLoader(logger: Logger, snapshotManager: SvelteSnapshotManager, typescript: typeof ts, lsHost: ts.LanguageServiceHost, project: ts.server.Project, configManager: ConfigManager): void; |
@@ -39,2 +39,5 @@ "use strict"; | ||
| } | ||
| clear() { | ||
| this.cache.clear(); | ||
| } | ||
| getKey(moduleName, containingFile) { | ||
@@ -53,3 +56,3 @@ return containingFile + ':::' + (0, utils_1.ensureRealSvelteFilePath)(moduleName); | ||
| */ | ||
| function patchModuleLoader(logger, snapshotManager, typescript, lsHost, project) { | ||
| function patchModuleLoader(logger, snapshotManager, typescript, lsHost, project, configManager) { | ||
| var _a; | ||
@@ -66,2 +69,5 @@ const svelteSys = (0, svelte_sys_1.createSvelteSys)(logger); | ||
| }; | ||
| configManager.onConfigurationChanged(() => { | ||
| moduleCache.clear(); | ||
| }); | ||
| function resolveModuleNames(moduleNames, containingFile, reusedNames, redirectedReference, compilerOptions) { | ||
@@ -74,2 +80,5 @@ logger.log('Resolving modules names for ' + containingFile); | ||
| const resolved = (origResolveModuleNames === null || origResolveModuleNames === void 0 ? void 0 : origResolveModuleNames(moduleNames, containingFile, reusedNames, redirectedReference, compilerOptions)) || Array.from(Array(moduleNames.length)); | ||
| if (!configManager.getConfig().enable) { | ||
| return resolved; | ||
| } | ||
| return resolved.map((moduleName, idx) => { | ||
@@ -76,0 +85,0 @@ const fileName = moduleNames[idx]; |
| import type ts from 'typescript/lib/tsserverlibrary'; | ||
| import { ConfigManager } from './config-manager'; | ||
| import { Logger } from './logger'; | ||
@@ -41,6 +42,7 @@ import { SourceMapper } from './source-mapper'; | ||
| private logger; | ||
| private configManager; | ||
| private snapshots; | ||
| constructor(typescript: typeof ts, projectService: ts.server.ProjectService, svelteOptions: { | ||
| namespace: string; | ||
| }, logger: Logger); | ||
| }, logger: Logger, configManager: ConfigManager); | ||
| get(fileName: string): SvelteSnapshot | undefined; | ||
@@ -47,0 +49,0 @@ create(fileName: string): SvelteSnapshot | undefined; |
@@ -192,3 +192,3 @@ "use strict"; | ||
| class SvelteSnapshotManager { | ||
| constructor(typescript, projectService, svelteOptions, logger) { | ||
| constructor(typescript, projectService, svelteOptions, logger, configManager) { | ||
| this.typescript = typescript; | ||
@@ -198,2 +198,3 @@ this.projectService = projectService; | ||
| this.logger = logger; | ||
| this.configManager = configManager; | ||
| this.snapshots = new Map(); | ||
@@ -233,3 +234,3 @@ this.patchProjectServiceReadFile(); | ||
| this.projectService.host.readFile = (path) => { | ||
| if ((0, utils_1.isSvelteFilePath)(path)) { | ||
| if ((0, utils_1.isSvelteFilePath)(path) && this.configManager.getConfig().enable) { | ||
| this.logger.debug('Read Svelte file:', path); | ||
@@ -256,4 +257,7 @@ const svelteCode = readFile(path) || ''; | ||
| catch (e) { | ||
| this.logger.log('Error loading Svelte file:', path); | ||
| this.logger.log('Error loading Svelte file:', path, ' Using fallback.'); | ||
| this.logger.debug('Error:', e); | ||
| // Return something either way, else "X is not a module" errors will appear | ||
| // in the TS files that use this file. | ||
| return 'export default class extends Svelte2TsxComponent<any,any,any> {}'; | ||
| } | ||
@@ -260,0 +264,0 @@ } |
@@ -0,1 +1,2 @@ | ||
| import type ts from 'typescript/lib/tsserverlibrary'; | ||
| export declare function isSvelteFilePath(filePath: string): boolean; | ||
@@ -20,1 +21,2 @@ export declare function isVirtualSvelteFilePath(filePath: string): boolean; | ||
| export declare function replaceDeep<T extends Record<string, any>>(obj: T, searchStr: string | RegExp, replacementStr: string): T; | ||
| export declare function getConfigPathForProject(project: ts.server.Project): ts.server.NormalizedPath; |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.replaceDeep = exports.isNoTextSpanInGeneratedCode = exports.isInGeneratedCode = exports.isNotNullOrUndefined = exports.ensureRealSvelteFilePath = exports.toRealSvelteFilePath = exports.isVirtualSvelteFilePath = exports.isSvelteFilePath = void 0; | ||
| exports.getConfigPathForProject = exports.replaceDeep = exports.isNoTextSpanInGeneratedCode = exports.isInGeneratedCode = exports.isNotNullOrUndefined = exports.ensureRealSvelteFilePath = exports.toRealSvelteFilePath = exports.isVirtualSvelteFilePath = exports.isSvelteFilePath = void 0; | ||
| function isSvelteFilePath(filePath) { | ||
@@ -66,1 +66,6 @@ return filePath.endsWith('.svelte'); | ||
| exports.replaceDeep = replaceDeep; | ||
| function getConfigPathForProject(project) { | ||
| var _a; | ||
| return ((_a = project.canonicalConfigFilePath) !== null && _a !== void 0 ? _a : project.getCompilerOptions().configFilePath); | ||
| } | ||
| exports.getConfigPathForProject = getConfigPathForProject; |
+1
-1
| { | ||
| "name": "typescript-svelte-plugin", | ||
| "version": "0.3.0", | ||
| "version": "0.3.1", | ||
| "description": "A TypeScript Plugin providing Svelte intellisense", | ||
@@ -5,0 +5,0 @@ "main": "dist/src/index.js", |
60904
27.07%35
12.9%1400
27.74%