diff --git a/packages/language-server/src/plugins/index.ts b/packages/language-server/src/plugins/index.ts index e17717514..9b97b2911 100644 --- a/packages/language-server/src/plugins/index.ts +++ b/packages/language-server/src/plugins/index.ts @@ -1,5 +1,6 @@ export * from './css/CSSPlugin'; export * from './typescript/TypeScriptPlugin'; +export * from './typescript/LSAndTSDocResolver'; export * from './svelte/SveltePlugin'; export * from './html/HTMLPlugin'; export * from './PluginHost'; diff --git a/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts b/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts index 637da71c9..e353856b4 100644 --- a/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts +++ b/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts @@ -4,20 +4,27 @@ import { LSConfigManager } from '../../ls-config'; import { debounceSameArg, pathToUrl } from '../../utils'; import { DocumentSnapshot, SvelteDocumentSnapshot } from './DocumentSnapshot'; import { - getLanguageServiceForDocument, - getLanguageServiceForPath, getService, + getServiceForTsconfig, + hasServiceForFile, LanguageServiceContainer, LanguageServiceDocumentContext } from './service'; import { SnapshotManager } from './SnapshotManager'; export class LSAndTSDocResolver { + /** + * + * @param docManager + * @param workspaceUris + * @param configManager + * @param tsconfigPath This should only be set via svelte-check. Makes sure all documents are resolved to that tsconfig. Has to be absolute. + */ constructor( private readonly docManager: DocumentManager, private readonly workspaceUris: string[], private readonly configManager: LSConfigManager, - private readonly transformOnTemplateError = true + private readonly tsconfigPath?: string ) { const handleDocumentChange = (document: Document) => { // This refreshes the document in the ts language service @@ -55,12 +62,12 @@ export class LSAndTSDocResolver { private get lsDocumentContext(): LanguageServiceDocumentContext { return { createDocument: this.createDocument, - transformOnTemplateError: this.transformOnTemplateError + transformOnTemplateError: !this.tsconfigPath }; } async getLSForPath(path: string) { - return getLanguageServiceForPath(path, this.workspaceUris, this.lsDocumentContext); + return (await this.getTSService(path)).getService(); } async getLSAndTSDoc(document: Document): Promise<{ @@ -68,11 +75,7 @@ export class LSAndTSDocResolver { lang: ts.LanguageService; userPreferences: ts.UserPreferences; }> { - const lang = await getLanguageServiceForDocument( - document, - this.workspaceUris, - this.lsDocumentContext - ); + const lang = await this.getLSForPath(document.getFilePath() || ''); const tsDoc = await this.getSnapshot(document); const userPreferences = this.getUserPreferences(tsDoc.scriptKind); @@ -93,6 +96,11 @@ export class LSAndTSDocResolver { } async deleteSnapshot(filePath: string) { + if (!hasServiceForFile(filePath, this.workspaceUris)) { + // Don't initialize a service for a file that should be deleted + return; + } + (await this.getTSService(filePath)).deleteSnapshot(filePath); this.docManager.releaseDocument(pathToUrl(filePath)); } @@ -101,7 +109,13 @@ export class LSAndTSDocResolver { return (await this.getTSService(filePath)).snapshotManager; } - private getTSService(filePath: string): Promise { + async getTSService(filePath?: string): Promise { + if (this.tsconfigPath) { + return getServiceForTsconfig(this.tsconfigPath, this.lsDocumentContext); + } + if (!filePath) { + throw new Error('Cannot call getTSService without filePath and without tsconfigPath'); + } return getService(filePath, this.workspaceUris, this.lsDocumentContext); } diff --git a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts index 3d5caf707..a887afcc3 100644 --- a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts +++ b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts @@ -3,6 +3,7 @@ import { CodeAction, CodeActionContext, CompletionContext, + CompletionList, DefinitionLink, Diagnostic, FileChangeType, @@ -12,21 +13,15 @@ import { Position, Range, ReferenceContext, - SymbolInformation, - WorkspaceEdit, - CompletionList, SelectionRange, + SemanticTokens, SignatureHelp, SignatureHelpContext, - SemanticTokens, - TextDocumentContentChangeEvent + SymbolInformation, + TextDocumentContentChangeEvent, + WorkspaceEdit } from 'vscode-languageserver'; -import { - Document, - DocumentManager, - mapSymbolInformationToOriginal, - getTextInRange -} from '../../lib/documents'; +import { Document, getTextInRange, mapSymbolInformationToOriginal } from '../../lib/documents'; import { LSConfigManager, LSTypescriptConfig } from '../../ls-config'; import { isNotNullOrUndefined, pathToUrl } from '../../utils'; import { @@ -41,12 +36,12 @@ import { FindReferencesProvider, HoverProvider, OnWatchFileChanges, + OnWatchFileChangesPara, RenameProvider, SelectionRangeProvider, + SemanticTokensProvider, SignatureHelpProvider, UpdateImportsProvider, - OnWatchFileChangesPara, - SemanticTokensProvider, UpdateTsOrJsFile } from '../interfaces'; import { CodeActionsProviderImpl } from './features/CodeActionsProvider'; @@ -55,18 +50,18 @@ import { CompletionsProviderImpl } from './features/CompletionProvider'; import { DiagnosticsProviderImpl } from './features/DiagnosticsProvider'; +import { FindReferencesProviderImpl } from './features/FindReferencesProvider'; +import { getDirectiveCommentCompletions } from './features/getDirectiveCommentCompletions'; import { HoverProviderImpl } from './features/HoverProvider'; import { RenameProviderImpl } from './features/RenameProvider'; -import { UpdateImportsProviderImpl } from './features/UpdateImportsProvider'; -import { LSAndTSDocResolver } from './LSAndTSDocResolver'; -import { convertToLocationRange, getScriptKindFromFileName, symbolKindFromString } from './utils'; -import { getDirectiveCommentCompletions } from './features/getDirectiveCommentCompletions'; -import { FindReferencesProviderImpl } from './features/FindReferencesProvider'; import { SelectionRangeProviderImpl } from './features/SelectionRangeProvider'; -import { SignatureHelpProviderImpl } from './features/SignatureHelpProvider'; -import { ignoredBuildDirectories, SnapshotManager } from './SnapshotManager'; import { SemanticTokensProviderImpl } from './features/SemanticTokensProvider'; +import { SignatureHelpProviderImpl } from './features/SignatureHelpProvider'; +import { UpdateImportsProviderImpl } from './features/UpdateImportsProvider'; import { isNoTextSpanInGeneratedCode, SnapshotFragmentMap } from './features/utils'; +import { LSAndTSDocResolver } from './LSAndTSDocResolver'; +import { ignoredBuildDirectories, SnapshotManager } from './SnapshotManager'; +import { convertToLocationRange, getScriptKindFromFileName, symbolKindFromString } from './utils'; export class TypeScriptPlugin implements @@ -98,19 +93,9 @@ export class TypeScriptPlugin private readonly signatureHelpProvider: SignatureHelpProviderImpl; private readonly semanticTokensProvider: SemanticTokensProviderImpl; - constructor( - docManager: DocumentManager, - configManager: LSConfigManager, - workspaceUris: string[], - isEditor = true - ) { + constructor(configManager: LSConfigManager, lsAndTsDocResolver: LSAndTSDocResolver) { this.configManager = configManager; - this.lsAndTsDocResolver = new LSAndTSDocResolver( - docManager, - workspaceUris, - configManager, - /**transformOnTemplateError */ isEditor - ); + this.lsAndTsDocResolver = lsAndTsDocResolver; this.completionProvider = new CompletionsProviderImpl(this.lsAndTsDocResolver); this.codeActionsProvider = new CodeActionsProviderImpl( this.lsAndTsDocResolver, diff --git a/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts b/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts index e2272e9e2..6873484cd 100644 --- a/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts @@ -1,5 +1,5 @@ import ts from 'typescript'; -import { Diagnostic, DiagnosticSeverity, DiagnosticTag } from 'vscode-languageserver'; +import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver'; import { Document, mapObjWithRangeToOriginal, @@ -8,7 +8,7 @@ import { } from '../../../lib/documents'; import { DiagnosticsProvider } from '../../interfaces'; import { LSAndTSDocResolver } from '../LSAndTSDocResolver'; -import { convertRange, mapSeverity } from '../utils'; +import { convertRange, getDiagnosticTag, mapSeverity } from '../utils'; import { SvelteDocumentSnapshot } from '../DocumentSnapshot'; import { isInGeneratedCode } from './utils'; @@ -53,7 +53,7 @@ export class DiagnosticsProviderImpl implements DiagnosticsProvider { source: isTypescript ? 'ts' : 'js', message: ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'), code: diagnostic.code, - tags: this.getDiagnosticTag(diagnostic) + tags: getDiagnosticTag(diagnostic) })) .map((diagnostic) => mapObjWithRangeToOriginal(fragment, diagnostic)) .filter(hasNoNegativeLines) @@ -62,17 +62,6 @@ export class DiagnosticsProviderImpl implements DiagnosticsProvider { .map(swapRangeStartEndIfNecessary); } - private getDiagnosticTag(diagnostic: ts.Diagnostic) { - const tags: DiagnosticTag[] = []; - if (diagnostic.reportsUnnecessary) { - tags.push(DiagnosticTag.Unnecessary); - } - if (diagnostic.reportsDeprecated) { - tags.push(DiagnosticTag.Deprecated); - } - return tags; - } - private async getLSAndTSDoc(document: Document) { return this.lsAndTsDocResolver.getLSAndTSDoc(document); } diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index 06a5c2a51..a5d277278 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -16,6 +16,11 @@ export interface LanguageServiceContainer { getService(): ts.LanguageService; updateSnapshot(documentOrFilePath: Document | string): DocumentSnapshot; deleteSnapshot(filePath: string): void; + /** + * Careful, don't call often, or it will hurt performance. + * Only works for TS versions that have ScriptKind.Deferred + */ + fileBelongsToProject(filePath: string): boolean; } const services = new Map>(); @@ -25,29 +30,28 @@ export interface LanguageServiceDocumentContext { createDocument: (fileName: string, content: string) => Document; } -export async function getLanguageServiceForPath( +export async function getService( path: string, workspaceUris: string[], docContext: LanguageServiceDocumentContext -): Promise { - return (await getService(path, workspaceUris, docContext)).getService(); +): Promise { + const tsconfigPath = findTsConfigPath(path, workspaceUris); + return getServiceForTsconfig(tsconfigPath, docContext); } -export async function getLanguageServiceForDocument( - document: Document, - workspaceUris: string[], - docContext: LanguageServiceDocumentContext -): Promise { - return getLanguageServiceForPath(document.getFilePath() || '', workspaceUris, docContext); +export function hasServiceForFile(path: string, workspaceUris: string[]): boolean { + const tsconfigPath = findTsConfigPath(path, workspaceUris); + return services.has(tsconfigPath); } -export async function getService( - path: string, - workspaceUris: string[], +/** + * @param tsconfigPath has to be absolute + * @param docContext + */ +export async function getServiceForTsconfig( + tsconfigPath: string, docContext: LanguageServiceDocumentContext -) { - const tsconfigPath = findTsConfigPath(path, workspaceUris); - +): Promise { let service: LanguageServiceContainer; if (services.has(tsconfigPath)) { service = await services.get(tsconfigPath)!; @@ -127,6 +131,7 @@ async function createLanguageService( getService: () => languageService, updateSnapshot, deleteSnapshot, + fileBelongsToProject, snapshotManager }; @@ -192,6 +197,10 @@ async function createLanguageService( return doc; } + function fileBelongsToProject(filePath: string): boolean { + return snapshotManager.has(filePath) || getParsedConfig().fileNames.includes(filePath); + } + function getParsedConfig() { const forcedCompilerOptions: ts.CompilerOptions = { allowNonTsExtensions: true, diff --git a/packages/language-server/src/plugins/typescript/utils.ts b/packages/language-server/src/plugins/typescript/utils.ts index 8808d78ac..c6c120ef3 100644 --- a/packages/language-server/src/plugins/typescript/utils.ts +++ b/packages/language-server/src/plugins/typescript/utils.ts @@ -3,6 +3,7 @@ import ts from 'typescript'; import { CompletionItemKind, DiagnosticSeverity, + DiagnosticTag, Position, Range, SymbolKind @@ -301,3 +302,14 @@ export function convertToTextSpan(range: Range, fragment: SnapshotFragment): ts. export function isInScript(position: Position, fragment: SvelteSnapshotFragment | Document) { return isInTag(position, fragment.scriptInfo) || isInTag(position, fragment.moduleScriptInfo); } + +export function getDiagnosticTag(diagnostic: ts.Diagnostic): DiagnosticTag[] { + const tags: DiagnosticTag[] = []; + if (diagnostic.reportsUnnecessary) { + tags.push(DiagnosticTag.Unnecessary); + } + if (diagnostic.reportsDeprecated) { + tags.push(DiagnosticTag.Deprecated); + } + return tags; +} diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index fa6eccca8..2cc57f407 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -31,7 +31,8 @@ import { PluginHost, SveltePlugin, TypeScriptPlugin, - OnWatchFileChangesPara + OnWatchFileChangesPara, + LSAndTSDocResolver } from './plugins'; import { isNotNullOrUndefined, urlToPath } from './utils'; import { FallbackWatcher } from './lib/FallbackWatcher'; @@ -132,7 +133,12 @@ export function startServer(options?: LSOptions) { pluginHost.register((sveltePlugin = new SveltePlugin(configManager))); pluginHost.register(new HTMLPlugin(docManager, configManager)); pluginHost.register(new CSSPlugin(docManager, configManager)); - pluginHost.register(new TypeScriptPlugin(docManager, configManager, workspaceUris)); + pluginHost.register( + new TypeScriptPlugin( + configManager, + new LSAndTSDocResolver(docManager, workspaceUris, configManager) + ) + ); const clientSupportApplyEditCommand = !!evt.capabilities.workspace?.applyEdit; diff --git a/packages/language-server/src/svelte-check.ts b/packages/language-server/src/svelte-check.ts index 13eb2089c..838e78a2a 100644 --- a/packages/language-server/src/svelte-check.ts +++ b/packages/language-server/src/svelte-check.ts @@ -1,15 +1,28 @@ -import { Document, DocumentManager } from './lib/documents'; -import { LSConfigManager } from './ls-config'; -import { CSSPlugin, PluginHost, SveltePlugin, TypeScriptPlugin } from './plugins'; +import { isAbsolute } from 'path'; +import ts from 'typescript'; import { Diagnostic, Position, Range } from 'vscode-languageserver'; +import { Document, DocumentManager } from './lib/documents'; import { Logger } from './logger'; -import { urlToPath, pathToUrl } from './utils'; +import { LSConfigManager } from './ls-config'; +import { + CSSPlugin, + LSAndTSDocResolver, + PluginHost, + SveltePlugin, + TypeScriptPlugin +} from './plugins'; +import { convertRange, getDiagnosticTag, mapSeverity } from './plugins/typescript/utils'; +import { pathToUrl, urlToPath } from './utils'; export type SvelteCheckDiagnosticSource = 'js' | 'css' | 'svelte'; export interface SvelteCheckOptions { compilerWarnings?: Record; diagnosticSources?: SvelteCheckDiagnosticSource[]; + /** + * Path has to be absolute + */ + tsconfig?: string; } /** @@ -22,13 +35,18 @@ export class SvelteCheck { ); private configManager = new LSConfigManager(); private pluginHost = new PluginHost(this.docManager); + private lsAndTSDocResolver?: LSAndTSDocResolver; - constructor(workspacePath: string, options: SvelteCheckOptions = {}) { + constructor(workspacePath: string, private options: SvelteCheckOptions = {}) { Logger.setLogErrorsOnly(true); this.initialize(workspacePath, options); } - private initialize(workspacePath: string, options: SvelteCheckOptions) { + private async initialize(workspacePath: string, options: SvelteCheckOptions) { + if (options.tsconfig && !isAbsolute(options.tsconfig)) { + throw new Error('tsconfigPath needs to be absolute, got ' + options.tsconfig); + } + this.configManager.update({ svelte: { compilerWarnings: options.compilerWarnings @@ -41,14 +59,15 @@ export class SvelteCheck { if (shouldRegister('css')) { this.pluginHost.register(new CSSPlugin(this.docManager, this.configManager)); } - if (shouldRegister('js')) { + if (shouldRegister('js') || options.tsconfig) { + this.lsAndTSDocResolver = new LSAndTSDocResolver( + this.docManager, + [pathToUrl(workspacePath)], + this.configManager, + options.tsconfig + ); this.pluginHost.register( - new TypeScriptPlugin( - this.docManager, - this.configManager, - [pathToUrl(workspacePath)], - /**isEditor */ false - ) + new TypeScriptPlugin(this.configManager, this.lsAndTSDocResolver) ); } @@ -61,10 +80,20 @@ export class SvelteCheck { * Creates/updates given document * * @param doc Text and Uri of the document + * @param isNew Whether or not this is the creation of the document */ - upsertDocument(doc: { text: string; uri: string }) { + async upsertDocument(doc: { text: string; uri: string }, isNew: boolean): Promise { + const filePath = urlToPath(doc.uri) || ''; + + if (isNew && this.options.tsconfig) { + const lsContainer = await this.getLSContainer(this.options.tsconfig); + if (!lsContainer.fileBelongsToProject(filePath)) { + return; + } + } + if (doc.uri.endsWith('.ts') || doc.uri.endsWith('.js')) { - this.pluginHost.updateTsOrJsFile(urlToPath(doc.uri) || '', [ + this.pluginHost.updateTsOrJsFile(filePath, [ { range: Range.create( Position.create(0, 0), @@ -87,9 +116,17 @@ export class SvelteCheck { * * @param uri Uri of the document */ - removeDocument(uri: string) { + async removeDocument(uri: string): Promise { + if (!this.docManager.get(uri)) { + return; + } + this.docManager.closeDocument(uri); this.docManager.releaseDocument(uri); + if (this.options.tsconfig) { + const lsContainer = await this.getLSContainer(this.options.tsconfig); + lsContainer.deleteSnapshot(urlToPath(uri) || ''); + } } /** @@ -98,16 +135,68 @@ export class SvelteCheck { async getDiagnostics(): Promise< Array<{ filePath: string; text: string; diagnostics: Diagnostic[] }> > { + if (this.options.tsconfig) { + return this.getDiagnosticsForTsconfig(this.options.tsconfig); + } return await Promise.all( this.docManager.getAllOpenedByClient().map(async (doc) => { const uri = doc[1].uri; - const diagnostics = await this.pluginHost.getDiagnostics({ uri }); - return { - filePath: urlToPath(uri) || '', - text: this.docManager.get(uri)?.getText() || '', - diagnostics - }; + return await this.getDiagnosticsForFile(uri); + }) + ); + } + + private async getDiagnosticsForTsconfig(tsconfigPath: string) { + const lsContainer = await this.getLSContainer(tsconfigPath); + const lang = lsContainer.getService(); + const files = lang.getProgram()?.getSourceFiles() || []; + + return await Promise.all( + files.map((file) => { + const uri = pathToUrl(file.fileName); + const doc = this.docManager.get(uri); + if (doc) { + this.docManager.markAsOpenedInClient(uri); + return this.getDiagnosticsForFile(uri); + } else { + const diagnostics = [ + ...lang.getSyntacticDiagnostics(file.fileName), + ...lang.getSuggestionDiagnostics(file.fileName), + ...lang.getSemanticDiagnostics(file.fileName) + ].map((diagnostic) => ({ + range: convertRange( + { positionAt: file.getLineAndCharacterOfPosition.bind(file) }, + diagnostic + ), + severity: mapSeverity(diagnostic.category), + source: diagnostic.source, + message: ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'), + code: diagnostic.code, + tags: getDiagnosticTag(diagnostic) + })); + return { + filePath: file.fileName, + text: file.text, + diagnostics + }; + } }) ); } + + private async getDiagnosticsForFile(uri: string) { + const diagnostics = await this.pluginHost.getDiagnostics({ uri }); + return { + filePath: urlToPath(uri) || '', + text: this.docManager.get(uri)?.getText() || '', + diagnostics + }; + } + + private getLSContainer(tsconfigPath: string) { + if (!this.lsAndTSDocResolver) { + throw new Error('Cannot run with tsconfig path without LS/TSdoc resolver'); + } + return this.lsAndTSDocResolver.getTSService(tsconfigPath); + } } diff --git a/packages/language-server/test/plugins/typescript/TypescriptPlugin.test.ts b/packages/language-server/test/plugins/typescript/TypescriptPlugin.test.ts index c19f79ffc..0fa3fe27b 100644 --- a/packages/language-server/test/plugins/typescript/TypescriptPlugin.test.ts +++ b/packages/language-server/test/plugins/typescript/TypescriptPlugin.test.ts @@ -5,7 +5,7 @@ import fs from 'fs'; import { FileChangeType, Position, Range } from 'vscode-languageserver'; import { Document, DocumentManager } from '../../../src/lib/documents'; import { LSConfigManager } from '../../../src/ls-config'; -import { TypeScriptPlugin } from '../../../src/plugins'; +import { LSAndTSDocResolver, TypeScriptPlugin } from '../../../src/plugins'; import { INITIAL_VERSION } from '../../../src/plugins/typescript/DocumentSnapshot'; import { pathToUrl, urlToPath } from '../../../src/utils'; import { ignoredBuildDirectories } from '../../../src/plugins/typescript/SnapshotManager'; @@ -26,7 +26,10 @@ describe('TypescriptPlugin', () => { const filePath = path.join(testDir, filename); const document = new Document(pathToUrl(filePath), ts.sys.readFile(filePath) || ''); const pluginManager = new LSConfigManager(); - const plugin = new TypeScriptPlugin(docManager, pluginManager, [pathToUrl(testDir)]); + const plugin = new TypeScriptPlugin( + pluginManager, + new LSAndTSDocResolver(docManager, [pathToUrl(testDir)], pluginManager) + ); docManager.openDocument('some doc'); return { plugin, document }; } diff --git a/packages/language-server/test/plugins/typescript/typescript-performance.test.ts b/packages/language-server/test/plugins/typescript/typescript-performance.test.ts index e698aed3a..613ac7931 100644 --- a/packages/language-server/test/plugins/typescript/typescript-performance.test.ts +++ b/packages/language-server/test/plugins/typescript/typescript-performance.test.ts @@ -4,7 +4,7 @@ import ts from 'typescript'; import { Position, Range } from 'vscode-languageserver'; import { Document, DocumentManager } from '../../../src/lib/documents'; import { LSConfigManager } from '../../../src/ls-config'; -import { TypeScriptPlugin } from '../../../src/plugins'; +import { LSAndTSDocResolver, TypeScriptPlugin } from '../../../src/plugins'; import { pathToUrl } from '../../../src/utils'; describe('TypeScript Plugin Performance Tests', () => { @@ -15,7 +15,10 @@ describe('TypeScript Plugin Performance Tests', () => { const uri = pathToUrl(filePath); const document = new Document(uri, ts.sys.readFile(filePath) || ''); const pluginManager = new LSConfigManager(); - const plugin = new TypeScriptPlugin(docManager, pluginManager, [pathToUrl(testDir)]); + const plugin = new TypeScriptPlugin( + pluginManager, + new LSAndTSDocResolver(docManager, [pathToUrl(testDir)], pluginManager) + ); docManager.openDocument({ uri, text: document.getText() }); const append = (newText: string) => docManager.updateDocument({ uri, version: 1 }, [ diff --git a/packages/svelte-check/README.md b/packages/svelte-check/README.md index b39a569f5..8693f950f 100644 --- a/packages/svelte-check/README.md +++ b/packages/svelte-check/README.md @@ -55,6 +55,7 @@ Usage: | `--workspace ` | Path to your workspace. All subdirectories except node_modules and those listed in `--ignore` are checked | | `--output ` | | `--watch` | Will not exit after one pass but keep watching files for changes and rerun diagnostics | +| `--tsconfig ` | Pass a path to a tsconfig or jsconfig file. The path can be relative to the workspace path or absolute. Doing this means that only files matched by the files/include/exclude pattern of the config file are diagnosed. It also means that errors from TypeScript and JavaScript files are reported. | | `--ignore ` | Files/folders to ignore - relative to workspace root, comma-separated, inside quotes. Example: `--ignore "dist,build"` | | `--fail-on-warnings` | Will also exit with error code when there are warnings | | `--fail-on-hints` | Will also exit with error code when there are hints | diff --git a/packages/svelte-check/src/index.ts b/packages/svelte-check/src/index.ts index 87bd6a5f1..06e81ea04 100644 --- a/packages/svelte-check/src/index.ts +++ b/packages/svelte-check/src/index.ts @@ -52,10 +52,13 @@ async function openAllDocuments( for (const absFilePath of absFilePaths) { const text = fs.readFileSync(absFilePath, 'utf-8'); - svelteCheck.upsertDocument({ - uri: URI.file(absFilePath).toString(), - text - }); + svelteCheck.upsertDocument( + { + uri: URI.file(absFilePath).toString(), + text + }, + true + ); } resolve(); } @@ -119,26 +122,32 @@ class DiagnosticsWatcher { private workspaceUri: URI, private svelteCheck: SvelteCheck, private writer: Writer, - filePathsToIgnore: string[] + filePathsToIgnore: string[], + ignoreInitialAdd: boolean ) { watch(`${workspaceUri.fsPath}/**/*.{svelte,d.ts,ts,js}`, { ignored: ['node_modules'] .concat(filePathsToIgnore) - .map((ignore) => path.join(workspaceUri.fsPath, ignore)) + .map((ignore) => path.join(workspaceUri.fsPath, ignore)), + ignoreInitial: ignoreInitialAdd }) - .on('add', (path) => this.updateDocument(path)) + .on('add', (path) => this.updateDocument(path, true)) .on('unlink', (path) => this.removeDocument(path)) - .on('change', (path) => this.updateDocument(path)); + .on('change', (path) => this.updateDocument(path, false)); + + if (ignoreInitialAdd) { + this.scheduleDiagnostics(); + } } - private updateDocument(path: string) { + private async updateDocument(path: string, isNew: boolean) { const text = fs.readFileSync(path, 'utf-8'); - this.svelteCheck.upsertDocument({ text, uri: URI.file(path).toString() }); + await this.svelteCheck.upsertDocument({ text, uri: URI.file(path).toString() }, isNew); this.scheduleDiagnostics(); } - private removeDocument(path: string) { - this.svelteCheck.removeDocument(URI.file(path).toString()); + private async removeDocument(path: string) { + await this.svelteCheck.removeDocument(URI.file(path).toString()); this.scheduleDiagnostics(); } @@ -178,12 +187,18 @@ function instantiateWriter(myArgs: argv.ParsedArgs): Writer { } } -function getOptions(myArgs: argv.ParsedArgs): SvelteCheckOptions { +function getOptions(myArgs: argv.ParsedArgs, workspacePath: string): SvelteCheckOptions { + let tsconfig = myArgs['tsconfig']; + if (tsconfig && !path.isAbsolute(tsconfig)) { + tsconfig = path.join(workspacePath, tsconfig); + } + return { compilerWarnings: stringToObj(myArgs['compiler-warnings']), diagnosticSources: ( myArgs['diagnostic-sources']?.split(',')?.map((s: string) => s.trim()) - ) + ), + tsconfig }; function stringToObj(str = '') { @@ -217,13 +232,24 @@ function getOptions(myArgs: argv.ParsedArgs): SvelteCheckOptions { const writer = instantiateWriter(myArgs); - const svelteCheck = new SvelteCheck(workspaceUri.fsPath, getOptions(myArgs)); + const svelteCheck = new SvelteCheck( + workspaceUri.fsPath, + getOptions(myArgs, workspaceUri.fsPath) + ); const filePathsToIgnore = myArgs['ignore']?.split(',') || []; if (myArgs['watch']) { - new DiagnosticsWatcher(workspaceUri, svelteCheck, writer, filePathsToIgnore); + new DiagnosticsWatcher( + workspaceUri, + svelteCheck, + writer, + filePathsToIgnore, + !!myArgs['tsconfig'] + ); } else { - await openAllDocuments(workspaceUri, filePathsToIgnore, svelteCheck); + if (!myArgs['tsconfig']) { + await openAllDocuments(workspaceUri, filePathsToIgnore, svelteCheck); + } const result = await getDiagnostics(workspaceUri, writer, svelteCheck); if ( result &&