import { dirname , resolve } from 'path' ; import ts from 'typescript' ; import { TextDocumentContentChangeEvent } from 'vscode-languageserver-protocol' ; import { getPackageInfo } from '../../importPackage' ; import { Document } from '../../lib/documents' ; import { configLoader } from '../../lib/documents/configLoader' ; import { Logger } from '../../logger' ; import { normalizePath } from '../../utils' ; import { DocumentSnapshot } from './DocumentSnapshot' ; import { createSvelteModuleLoader } from './module-loader' ; import { GlobalSnapshotsManager , ignoredBuildDirectories , SnapshotManager } from './SnapshotManager' ; import { ensureRealSvelteFilePath , findTsConfigPath , hasTsExtensions } from './utils' ; export interface LanguageServiceContainer { readonly tsconfigPath : string ; readonly compilerOptions : ts . CompilerOptions ; internal readonly snapshotManager : SnapshotManager ; getService ( ) : ts . LanguageService ; updateSnapshot ( documentOrFilePath ) : DocumentSnapshot ; deleteSnapshot ( filePath ) : void ; updateProjectFiles ( ) : void ; updateTsOrJsFile ( fileName , changes ? ) : void ; /**
     * Checks if a file is present in the project.
     * Unlike `fileBelongsToProject`, this doesn't run a file search on disk.
     */ hasFile ( filePath ) : boolean ; /**
     * Careful, don't call often, or it will hurt performance.
     * Only works for TS versions that have ScriptKind.Deferred
     */ fileBelongsToProject ( filePath ) : boolean ; } const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024 ; const services = new Map < string , Promise < LanguageServiceContainer > > ( ) ; const serviceSizeMap = new Map ( ) ; export interface LanguageServiceDocumentContext { ambientTypesSource : string ; transformOnTemplateError : boolean ; createDocument : ( fileName : string , content : string ) => Document ; globalSnapshotsManager : GlobalSnapshotsManager ; notifyExceedSizeLimit : ( ( ) => void ) | undefined ; } export async function getService ( path , workspaceUris , docContext ) { const tsconfigPath = findTsConfigPath ( path , workspaceUris ) ; return getServiceForTsconfig ( tsconfigPath , docContext ) ; } export async function forAllServices ( cb ) { for ( const service of services . values ( ) ) { cb ( await service ) ; } } param tsconfigPath param docContext export async function getServiceForTsconfig ( tsconfigPath , docContext ) { let service ; if ( services . has ( tsconfigPath ) ) { service = await services . get ( tsconfigPath ) ! ; } else { Logger . log ( 'Initialize new ts service at ' , tsconfigPath ) ; const newService = createLanguageService ( tsconfigPath , docContext ) ; services . set ( tsconfigPath , newService ) ; service = await newService ; } return service ; } async function createLanguageService ( tsconfigPath , docContext ) { const workspacePath = tsconfigPath ? dirname ( tsconfigPath ) : '' ; const { options : compilerOptions , fileNames : files , raw } = getParsedConfig ( ) ; const snapshotManager = new SnapshotManager ( docContext . globalSnapshotsManager , files , raw , workspacePath || process . cwd ( ) ) ; await configLoader . loadConfigs ( workspacePath ) ; const svelteModuleLoader = createSvelteModuleLoader ( getSnapshot , compilerOptions ) ; let svelteTsPath ; try { svelteTsPath = dirname ( require . resolve ( docContext . ambientTypesSource ) ) ; } catch ( e ) { svelteTsPath = __dirname ; } const svelteTsxFiles = [ './svelte-shims.d.ts' , './svelte-jsx.d.ts' , './svelte-native-jsx.d.ts' ] . map ( ( f ) => ts . sys . resolvePath ( resolve ( svelteTsPath , f ) ) ) ; let languageServiceReducedMode = false ; let projectVersion = 0 ; const host = { getCompilationSettings : ( ) => compilerOptions , getScriptFileNames : ( ) => Array . from ( new Set ( [ ... ( languageServiceReducedMode ? [ ] : snapshotManager . getProjectFileNames ( ) ) , ... snapshotManager . getFileNames ( ) , ... svelteTsxFiles ] ) ) , getScriptVersion : ( fileName ) => getSnapshot ( fileName ) . version . toString ( ) , getScriptSnapshot : getSnapshot , getCurrentDirectory : ( ) => workspacePath , getDefaultLibFileName : ts . getDefaultLibFilePath , fileExists : svelteModuleLoader . fileExists , readFile : svelteModuleLoader . readFile , resolveModuleNames : svelteModuleLoader . resolveModuleNames , readDirectory : svelteModuleLoader . readDirectory , getDirectories : ts . sys . getDirectories , useCaseSensitiveFileNames : ( ) => ts . sys . useCaseSensitiveFileNames , getScriptKind : ( fileName ) => getSnapshot ( fileName ) . scriptKind , getProjectVersion : ( ) => projectVersion . toString ( ) , getNewLine : ( ) => ts . sys . newLine } ; let languageService = ts . createLanguageService ( host ) ; const transformationConfig = { transformOnTemplateError : docContext . transformOnTemplateError } ; docContext . globalSnapshotsManager . onChange ( ( ) => { projectVersion ++ ; } ) ; reduceLanguageServiceCapabilityIfFileSizeTooBig ( ) ; return { tsconfigPath , compilerOptions , getService : ( ) => languageService , updateSnapshot , deleteSnapshot , updateProjectFiles , updateTsOrJsFile , hasFile , fileBelongsToProject , snapshotManager } ; function deleteSnapshot ( filePath ) { svelteModuleLoader . deleteFromModuleCache ( filePath ) ; snapshotManager . delete ( filePath ) ; } 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 ) { return prevSnapshot ; } if ( ! prevSnapshot ) { svelteModuleLoader . deleteUnresolvedResolutionsFromCache ( filePath ) ; } const newSnapshot = DocumentSnapshot . fromDocument ( document , transformationConfig ) ; snapshotManager . set ( filePath , newSnapshot ) ; if ( prevSnapshot && prevSnapshot . scriptKind !== newSnapshot . scriptKind ) { languageService . dispose ( ) ; languageService = ts . createLanguageService ( host ) ; } return newSnapshot ; } function updateSnapshotFromFilePath ( filePath ) { const prevSnapshot = snapshotManager . get ( filePath ) ; if ( prevSnapshot ) { return prevSnapshot ; } svelteModuleLoader . deleteUnresolvedResolutionsFromCache ( filePath ) ; const newSnapshot = DocumentSnapshot . fromFilePath ( filePath , docContext . createDocument , transformationConfig ) ; snapshotManager . set ( filePath , newSnapshot ) ; return newSnapshot ; } function getSnapshot ( fileName ) { fileName = ensureRealSvelteFilePath ( fileName ) ; let doc = snapshotManager . get ( fileName ) ; if ( doc ) { return doc ; } svelteModuleLoader . deleteUnresolvedResolutionsFromCache ( fileName ) ; doc = DocumentSnapshot . fromFilePath ( fileName , docContext . createDocument , transformationConfig ) ; snapshotManager . set ( fileName , doc ) ; return doc ; } function updateProjectFiles ( ) { projectVersion ++ ; const projectFileCountBefore = snapshotManager . getProjectFileNames ( ) . length ; snapshotManager . updateProjectFiles ( ) ; const projectFileCountAfter = snapshotManager . getProjectFileNames ( ) . length ; if ( projectFileCountAfter <= projectFileCountBefore ) { return ; } reduceLanguageServiceCapabilityIfFileSizeTooBig ( ) ; } function hasFile ( filePath ) { return snapshotManager . has ( filePath ) ; } function fileBelongsToProject ( filePath ) { filePath = normalizePath ( filePath ) ; return hasFile ( filePath ) || getParsedConfig ( ) . fileNames . includes ( filePath ) ; } function updateTsOrJsFile ( fileName , changes ? ) { if ( ! snapshotManager . has ( fileName ) ) { svelteModuleLoader . deleteUnresolvedResolutionsFromCache ( fileName ) ; } snapshotManager . updateTsOrJsFile ( fileName , changes ) ; } function getParsedConfig ( ) { const forcedCompilerOptions = { allowNonTsExtensions : true , target : ts . ScriptTarget . Latest , module : ts . ModuleKind . ESNext , moduleResolution : ts . ModuleResolutionKind . NodeJs , allowJs : true , noEmit : true , declaration : false , skipLibCheck : true , jsx : ts . JsxEmit . Preserve } ; let configJson = ( tsconfigPath && ts . readConfigFile ( tsconfigPath , ts . sys . readFile ) . config ) || getDefaultJsConfig ( ) ; if ( ! configJson . extends ) { configJson = Object . assign ( { exclude : getDefaultExclude ( ) } , configJson ) ; } const parsedConfig = ts . parseJsonConfigFileContent ( configJson , ts . sys , workspacePath , forcedCompilerOptions , tsconfigPath , undefined , [ { extension : 'svelte' , isMixedContent : true , scriptKind : ts . ScriptKind . Deferred ?? ts . ScriptKind . TSX } ] ) ; const compilerOptions = { ... parsedConfig . options , ... forcedCompilerOptions } ; if ( ! compilerOptions . jsxFactory || ! compilerOptions . jsxFactory . startsWith ( 'svelte' ) ) { compilerOptions . jsxFactory = 'svelte.createElement' ; if ( workspacePath ) { try { const svelteNativePkgInfo = getPackageInfo ( 'svelte-native' , workspacePath ) ; if ( svelteNativePkgInfo . path ) { compilerOptions . jsxFactory = 'svelteNative.createElement' ; } } catch ( e ) { } } } return { ... parsedConfig , fileNames : parsedConfig . fileNames . map ( normalizePath ) , options : compilerOptions } ; } /**
     * This should only be used when there's no jsconfig/tsconfig at all
     */ function getDefaultJsConfig ( ) { return { compilerOptions : { maxNodeModuleJsDepth : 2 , allowSyntheticDefaultImports : true } , include : [ ] } ; } function getDefaultExclude ( ) { return [ 'node_modules' , ... ignoredBuildDirectories ] ; } /**
     * 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 ) ) { languageService . cleanupSemanticCache ( ) ; languageServiceReducedMode = true ; docContext . notifyExceedSizeLimit ?. ( ) ; } } } /**
 * adopted from https://github.com/microsoft/TypeScript/blob/3c8e45b304b8572094c5d7fbb9cd768dbf6417c0/src/server/editorServices.ts#L1955
 */ function exceedsTotalSizeLimitForNonTsFiles ( compilerOptions , tsconfigPath , snapshotManager ) { 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 ( hasTsExtensions ( fileName ) ) { continue ; } totalNonTsFileSize += ts . sys . getFileSize ?. ( fileName ) ?? 0 ; if ( totalNonTsFileSize > availableSpace ) { const top5LargestFiles = fileNames . filter ( ( name ) => ! hasTsExtensions ( name ) ) . map ( ( name ) => ( { name , size : ts . sys . getFileSize ?. ( name ) ?? 0 } ) ) . sort ( ( a , b ) => b . size - a . size ) . slice ( 0 , 5 ) ; 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 ; }