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 { 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 < string , 'ignore' | 'error' > ; diagnosticSources ? : SvelteCheckDiagnosticSource [ ] ; /**
     * Path has to be absolute
     */ tsconfig ? : string ; } /**
 * Small wrapper around PluginHost's Diagnostic Capabilities
 * for svelte-check, without the overhead of the lsp.
 */ export class SvelteCheck { private docManager = new DocumentManager ( ( textDocument ) => new Document ( textDocument . uri , textDocument . text ) ) ; private configManager = new LSConfigManager ( ) ; private pluginHost = new PluginHost ( this . docManager ) ; private lsAndTSDocResolver ? : LSAndTSDocResolver ; constructor ( workspacePath , private options = { } ) { Logger . setLogErrorsOnly ( true ) ; this . initialize ( workspacePath , options ) ; } private async initialize ( workspacePath , options ) { if ( options . tsconfig && ! isAbsolute ( options . tsconfig ) ) { throw new Error ( 'tsconfigPath needs to be absolute, got ' + options . tsconfig ) ; } this . configManager . update ( { svelte : { compilerWarnings : options . compilerWarnings } } ) ; if ( shouldRegister ( 'svelte' ) ) { this . pluginHost . register ( new SveltePlugin ( this . configManager ) ) ; } if ( shouldRegister ( 'css' ) ) { this . pluginHost . register ( new CSSPlugin ( this . docManager , this . configManager ) ) ; } if ( shouldRegister ( 'js' ) || options . tsconfig ) { this . lsAndTSDocResolver = new LSAndTSDocResolver ( this . docManager , [ pathToUrl ( workspacePath ) ] , this . configManager , undefined , true , options . tsconfig ) ; this . pluginHost . register ( new TypeScriptPlugin ( this . configManager , this . lsAndTSDocResolver ) ) ; } function shouldRegister ( source ) { return ! options . diagnosticSources || options . diagnosticSources . includes ( source ) ; } } param doc param isNew async upsertDocument ( doc , isNew ) { 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 ( filePath , [ { range : Range . create ( Position . create ( 0 , 0 ) , Position . create ( Number . MAX_VALUE , Number . MAX_VALUE ) ) , text : doc . text } ] ) ; } else { this . docManager . openDocument ( { text : doc . text , uri : doc . uri } ) ; this . docManager . markAsOpenedInClient ( doc . uri ) ; } } param uri async removeDocument ( uri ) { 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 ) || '' ) ; } } /**
     * Gets the diagnostics for all currently open files.
     */ async getDiagnostics ( ) { 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 ; return await this . getDiagnosticsForFile ( uri ) ; } ) ) ; } private async getDiagnosticsForTsconfig ( tsconfigPath ) { const lsContainer = await this . getLSContainer ( tsconfigPath ) ; const lang = lsContainer . getService ( ) ; const files = lang . getProgram ( ) ?. getSourceFiles ( ) || [ ] ; const options = lang . getProgram ( ) ?. getCompilerOptions ( ) || { } ; 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 skipDiagnosticsForFile = ( options . skipLibCheck && file . isDeclarationFile ) || ( options . skipDefaultLibCheck && file . hasNoDefaultLib ) ; const diagnostics = skipDiagnosticsForFile ? [ ] : [ ... lang . getSyntacticDiagnostics ( file . fileName ) , ... lang . getSuggestionDiagnostics ( file . fileName ) , ... lang . getSemanticDiagnostics ( file . fileName ) ] . map < Diagnostic > ( ( 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 ) { const diagnostics = await this . pluginHost . getDiagnostics ( { uri } ) ; return { filePath : urlToPath ( uri ) || '' , text : this . docManager . get ( uri ) ?. getText ( ) || '' , diagnostics } ; } private getLSContainer ( tsconfigPath ) { if ( ! this . lsAndTSDocResolver ) { throw new Error ( 'Cannot run with tsconfig path without LS/TSdoc resolver' ) ; } return this . lsAndTSDocResolver . getTSService ( tsconfigPath ) ; } }