@@ -101,6 +101,10 @@ export interface TsConfigInfo {
101101 extendedConfigPaths ?: Set < string > ;
102102}
103103
104+ enum TsconfigSvelteDiagnostics {
105+ NO_SVELTE_INPUT = 100_001
106+ }
107+
104108const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024 ; // 20 MB
105109const services = new FileMap < Promise < LanguageServiceContainer > > ( ) ;
106110const serviceSizeMap = new FileMap < number > ( ) ;
@@ -173,12 +177,23 @@ export async function getService(
173177 return service ;
174178 }
175179
180+ // First try to find a service whose includes config matches our file
176181 const defaultService = await findDefaultServiceForFile ( service , triedTsConfig ) ;
177182 if ( defaultService ) {
178183 configFileForOpenFiles . set ( path , defaultService . tsconfigPath ) ;
179184 return defaultService ;
180185 }
181186
187+ // If no such service found, see if the file is part of any existing service indirectly.
188+ // This can happen if the includes doesn't match the file but it was imported from one of the included files.
189+ for ( const configPath of triedTsConfig ) {
190+ const service = await getConfiguredService ( configPath ) ;
191+ const ls = service . getService ( ) ;
192+ if ( ls . getProgram ( ) ?. getSourceFile ( path ) ) {
193+ return service ;
194+ }
195+ }
196+
182197 tsconfigPath = '' ;
183198 }
184199
@@ -217,6 +232,8 @@ export async function getService(
217232 return ;
218233 }
219234
235+ triedTsConfig . add ( service . tsconfigPath ) ;
236+
220237 // TODO: maybe add support for ts 5.6's ancestor searching
221238 return findDefaultFromProjectReferences ( service , triedTsConfig ) ;
222239 }
@@ -315,6 +332,8 @@ async function createLanguageService(
315332
316333 const projectConfig = getParsedConfig ( ) ;
317334 const { options : compilerOptions , raw, errors : configErrors } = projectConfig ;
335+ const allowJs = compilerOptions . allowJs ?? ! ! compilerOptions . checkJs ;
336+ const virtualDocuments = new FileMap < Document > ( tsSystem . useCaseSensitiveFileNames ) ;
318337
319338 const getCanonicalFileName = createGetCanonicalFileName ( tsSystem . useCaseSensitiveFileNames ) ;
320339 watchWildCardDirectories ( projectConfig ) ;
@@ -360,6 +379,7 @@ async function createLanguageService(
360379 let languageServiceReducedMode = false ;
361380 let projectVersion = 0 ;
362381 let dirty = projectConfig . fileNames . length > 0 ;
382+ let skipSvelteInputCheck = ! tsconfigPath ;
363383
364384 const host : ts . LanguageServiceHost = {
365385 log : ( message ) => Logger . debug ( `[ts] ${ message } ` ) ,
@@ -529,12 +549,19 @@ async function createLanguageService(
529549 return prevSnapshot ;
530550 }
531551
552+ const newSnapshot = DocumentSnapshot . fromDocument ( document , transformationConfig ) ;
553+
532554 if ( ! prevSnapshot ) {
533555 svelteModuleLoader . deleteUnresolvedResolutionsFromCache ( filePath ) ;
556+ if ( configFileForOpenFiles . get ( filePath ) === '' && services . size > 1 ) {
557+ configFileForOpenFiles . delete ( filePath ) ;
558+ }
559+ } else if ( prevSnapshot . scriptKind !== newSnapshot . scriptKind && ! allowJs ) {
560+ // if allowJs is false, we need to invalid the cache so that js svelte files can be loaded through module resolution
561+ svelteModuleLoader . deleteFromModuleCache ( filePath ) ;
562+ configFileForOpenFiles . delete ( filePath ) ;
534563 }
535564
536- const newSnapshot = DocumentSnapshot . fromDocument ( document , transformationConfig ) ;
537-
538565 snapshotManager . set ( filePath , newSnapshot ) ;
539566
540567 return newSnapshot ;
@@ -640,14 +667,22 @@ async function createLanguageService(
640667 : snapshotManager . getProjectFileNames ( ) ;
641668 const canonicalProjectFileNames = new Set ( projectFiles . map ( getCanonicalFileName ) ) ;
642669
670+ // We only assign project files (i.e. those found through includes config) and virtual files to getScriptFileNames.
671+ // We don't to include other client files otherwise they stay in the program and are never removed
672+ const clientFiles = tsconfigPath
673+ ? Array . from ( virtualDocuments . values ( ) )
674+ . map ( ( v ) => v . getFilePath ( ) )
675+ . filter ( isNotNullOrUndefined )
676+ : snapshotManager . getClientFileNames ( ) ;
677+
643678 return Array . from (
644679 new Set ( [
645680 ...projectFiles ,
646681 // project file is read from the file system so it's more likely to have
647682 // the correct casing
648- ...snapshotManager
649- . getClientFileNames ( )
650- . filter ( ( file ) => ! canonicalProjectFileNames . has ( getCanonicalFileName ( file ) ) ) ,
683+ ...clientFiles . filter (
684+ ( file ) => ! canonicalProjectFileNames . has ( getCanonicalFileName ( file ) )
685+ ) ,
651686 ...svelteTsxFiles
652687 ] )
653688 ) ;
@@ -736,20 +771,6 @@ async function createLanguageService(
736771 }
737772 }
738773
739- const svelteConfigDiagnostics = checkSvelteInput ( parsedConfig ) ;
740- if ( svelteConfigDiagnostics . length > 0 ) {
741- docContext . reportConfigError ?.( {
742- uri : pathToUrl ( tsconfigPath ) ,
743- diagnostics : svelteConfigDiagnostics . map ( ( d ) => ( {
744- message : d . messageText as string ,
745- range : { start : { line : 0 , character : 0 } , end : { line : 0 , character : 0 } } ,
746- severity : ts . DiagnosticCategory . Error ,
747- source : 'svelte'
748- } ) )
749- } ) ;
750- parsedConfig . errors . push ( ...svelteConfigDiagnostics ) ;
751- }
752-
753774 return {
754775 ...parsedConfig ,
755776 fileNames : parsedConfig . fileNames . map ( normalizePath ) ,
@@ -758,22 +779,32 @@ async function createLanguageService(
758779 } ;
759780 }
760781
761- function checkSvelteInput ( config : ts . ParsedCommandLine ) {
782+ function checkSvelteInput ( program : ts . Program | undefined , config : ts . ParsedCommandLine ) {
762783 if ( ! tsconfigPath || config . raw . references || config . raw . files ) {
763784 return [ ] ;
764785 }
765786
766- const svelteFiles = config . fileNames . filter ( isSvelteFilePath ) ;
767- if ( svelteFiles . length > 0 ) {
787+ const configFileName = basename ( tsconfigPath ) ;
788+ // Only report to possible nearest config file since referenced project might not be a svelte project
789+ if ( configFileName !== 'tsconfig.json' && configFileName !== 'jsconfig.json' ) {
790+ return [ ] ;
791+ }
792+
793+ const hasSvelteFiles =
794+ config . fileNames . some ( isSvelteFilePath ) ||
795+ program ?. getSourceFiles ( ) . some ( ( file ) => isSvelteFilePath ( file . fileName ) ) ;
796+
797+ if ( hasSvelteFiles ) {
768798 return [ ] ;
769799 }
800+
770801 const { include, exclude } = config . raw ;
771802 const inputText = JSON . stringify ( include ) ;
772803 const excludeText = JSON . stringify ( exclude ) ;
773804 const svelteConfigDiagnostics : ts . Diagnostic [ ] = [
774805 {
775806 category : ts . DiagnosticCategory . Error ,
776- code : 0 ,
807+ code : TsconfigSvelteDiagnostics . NO_SVELTE_INPUT ,
777808 file : undefined ,
778809 start : undefined ,
779810 length : undefined ,
@@ -933,6 +964,28 @@ async function createLanguageService(
933964 dirty = false ;
934965 compilerHost = undefined ;
935966
967+ if ( ! skipSvelteInputCheck ) {
968+ const svelteConfigDiagnostics = checkSvelteInput ( program , projectConfig ) ;
969+ const codes = svelteConfigDiagnostics . map ( ( d ) => d . code ) ;
970+ if ( ! svelteConfigDiagnostics . length ) {
971+ // stop checking once it passed once
972+ skipSvelteInputCheck = true ;
973+ }
974+ // report even if empty to clear previous diagnostics
975+ docContext . reportConfigError ?.( {
976+ uri : pathToUrl ( tsconfigPath ) ,
977+ diagnostics : svelteConfigDiagnostics . map ( ( d ) => ( {
978+ message : d . messageText as string ,
979+ range : { start : { line : 0 , character : 0 } , end : { line : 0 , character : 0 } } ,
980+ severity : ts . DiagnosticCategory . Error ,
981+ source : 'svelte'
982+ } ) )
983+ } ) ;
984+ projectConfig . errors = projectConfig . errors
985+ . filter ( ( e ) => ! codes . includes ( e . code ) )
986+ . concat ( svelteConfigDiagnostics ) ;
987+ }
988+
936989 // https://github.com/microsoft/TypeScript/blob/23faef92703556567ddbcb9afb893f4ba638fc20/src/server/project.ts#L1624
937990 // host.getCachedExportInfoMap will create the cache if it doesn't exist
938991 // so we need to check the property instead
@@ -1135,6 +1188,7 @@ async function createLanguageService(
11351188 if ( ! filePath ) {
11361189 return ;
11371190 }
1191+ virtualDocuments . set ( filePath , document ) ;
11381192 configFileForOpenFiles . set ( filePath , tsconfigPath || workspacePath ) ;
11391193 updateSnapshot ( document ) ;
11401194 scheduleUpdate ( filePath ) ;
0 commit comments