@@ -700,12 +700,54 @@ namespace ts {
700700      * @param  basePath A root directory to resolve relative path entries in the config 
701701      *    file to. e.g. outDir 
702702      */ 
703-     export  function  parseJsonConfigFileContent ( json : any ,  host : ParseConfigHost ,  basePath : string ,  existingOptions : CompilerOptions  =  { } ,  configFileName ?: string ) : ParsedCommandLine  { 
703+     export  function  parseJsonConfigFileContent ( json : any ,  host : ParseConfigHost ,  basePath : string ,  existingOptions : CompilerOptions  =  { } ,  configFileName ?: string ,   resolutionStack :  Path [ ]   =   [ ] ) : ParsedCommandLine  { 
704704        const  errors : Diagnostic [ ]  =  [ ] ; 
705-         const  compilerOptions : CompilerOptions  =  convertCompilerOptionsFromJsonWorker ( json [ "compilerOptions" ] ,  basePath ,  errors ,  configFileName ) ; 
706-         const  options  =  extend ( existingOptions ,  compilerOptions ) ; 
705+         const  getCanonicalFileName  =  createGetCanonicalFileName ( host . useCaseSensitiveFileNames ) ; 
706+         const  resolvedPath  =  toPath ( configFileName  ||  "" ,  basePath ,  getCanonicalFileName ) ; 
707+         if  ( resolutionStack . indexOf ( resolvedPath )  >=  0 )  { 
708+             return  { 
709+                 options : { } , 
710+                 fileNames : [ ] , 
711+                 typingOptions : { } , 
712+                 raw : json , 
713+                 errors : [ createCompilerDiagnostic ( Diagnostics . Circularity_detected_while_resolving_configuration_Colon_0 ,  [ ...resolutionStack ,  resolvedPath ] . join ( " -> " ) ) ] , 
714+                 wildcardDirectories : { } 
715+             } ; 
716+         } 
717+ 
718+         let  options : CompilerOptions  =  convertCompilerOptionsFromJsonWorker ( json [ "compilerOptions" ] ,  basePath ,  errors ,  configFileName ) ; 
707719        const  typingOptions : TypingOptions  =  convertTypingOptionsFromJsonWorker ( json [ "typingOptions" ] ,  basePath ,  errors ,  configFileName ) ; 
708720
721+         if  ( json [ "extends" ] )  { 
722+             let  [ include ,  exclude ,  files ,  baseOptions ] : [ string [ ] ,  string [ ] ,  string [ ] ,  CompilerOptions ]  =  [ undefined ,  undefined ,  undefined ,  { } ] ; 
723+             if  ( typeof  json [ "extends" ]  ===  "string" )  { 
724+                 [ include ,  exclude ,  files ,  baseOptions ]  =  ( tryExtendsName ( json [ "extends" ] )  ||  [ include ,  exclude ,  files ,  baseOptions ] ) ; 
725+             } 
726+             else  if  ( typeof  json [ "extends" ]  ===  "object"  &&  json [ "extends" ] . length )  { 
727+                 for  ( const  name  of  json [ "extends" ] )  { 
728+                     const  [ tempinclude ,  tempexclude ,  tempfiles ,  tempBase ] : [ string [ ] ,  string [ ] ,  string [ ] ,  CompilerOptions ]  =  ( tryExtendsName ( name )  ||  [ include ,  exclude ,  files ,  baseOptions ] ) ; 
729+                     include  =  tempinclude  ||  include ; 
730+                     exclude  =  tempexclude  ||  exclude ; 
731+                     files  =  tempfiles  ||  files ; 
732+                     baseOptions  =  assign ( { } ,  baseOptions ,  tempBase ) ; 
733+                 } 
734+             } 
735+             else  { 
736+                 errors . push ( createCompilerDiagnostic ( Diagnostics . Compiler_option_0_requires_a_value_of_type_1 ,  "extends" ,  "string or string[]" ) ) ; 
737+             } 
738+             if  ( include  &&  ! json [ "include" ] )  { 
739+                 json [ "include" ]  =  include ; 
740+             } 
741+             if  ( exclude  &&  ! json [ "exclude" ] )  { 
742+                 json [ "exclude" ]  =  exclude ; 
743+             } 
744+             if  ( files  &&  ! json [ "files" ] )  { 
745+                 json [ "files" ]  =  files ; 
746+             } 
747+             options  =  assign ( { } ,  baseOptions ,  options ) ; 
748+         } 
749+ 
750+         options  =  extend ( existingOptions ,  options ) ; 
709751        options . configFilePath  =  configFileName ; 
710752
711753        const  {  fileNames,  wildcardDirectories }  =  getFileNames ( errors ) ; 
@@ -719,6 +761,39 @@ namespace ts {
719761            wildcardDirectories
720762        } ; 
721763
764+         function  tryExtendsName ( extendedConfig : string ) : [ string [ ] ,  string [ ] ,  string [ ] ,  CompilerOptions ]  { 
765+             // If the path isn't a rooted or relative path, don't try to resolve it (we reserve the right to special case module-id like paths in the future) 
766+             if  ( ! ( isRootedDiskPath ( extendedConfig )  ||  startsWith ( normalizeSlashes ( extendedConfig ) ,  "./" )  ||  startsWith ( normalizeSlashes ( extendedConfig ) ,  "../" ) ) )  { 
767+                 errors . push ( createCompilerDiagnostic ( Diagnostics . The_path_in_an_extends_options_must_be_relative_or_rooted ) ) ; 
768+                 return ; 
769+             } 
770+             let  extendedConfigPath  =  toPath ( extendedConfig ,  basePath ,  getCanonicalFileName ) ; 
771+             if  ( ! host . fileExists ( extendedConfigPath )  &&  ! endsWith ( extendedConfigPath ,  ".json" ) )  { 
772+                 extendedConfigPath  =  `${ extendedConfigPath }   as  Path ; 
773+                 if  ( ! host . fileExists ( extendedConfigPath ) )  { 
774+                     errors . push ( createCompilerDiagnostic ( Diagnostics . File_0_does_not_exist ,  extendedConfig ) ) ; 
775+                     return ; 
776+                 } 
777+             } 
778+             const  extendedResult  =  readConfigFile ( extendedConfigPath ,  path  =>  host . readFile ( path ) ) ; 
779+             if  ( extendedResult . error )  { 
780+                 errors . push ( extendedResult . error ) ; 
781+                 return ; 
782+             } 
783+             const  extendedDirname  =  getDirectoryPath ( extendedConfigPath ) ; 
784+             const  relativeDifference  =  convertToRelativePath ( extendedDirname ,  basePath ,  getCanonicalFileName ) ; 
785+             const  updatePath : ( path : string )  =>  string  =  path  =>  isRootedDiskPath ( path )  ? path  : combinePaths ( relativeDifference ,  path ) ; 
786+             // Merge configs (copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios) 
787+             const  result  =  parseJsonConfigFileContent ( extendedResult . config ,  host ,  extendedDirname ,  /*existingOptions*/ undefined ,  getBaseFileName ( extendedConfigPath ) ,  resolutionStack . concat ( [ resolvedPath ] ) ) ; 
788+             errors . push ( ...result . errors ) ; 
789+             const  [ include ,  exclude ,  files ]  =  map ( [ "include" ,  "exclude" ,  "files" ] ,  key  =>  { 
790+                 if  ( ! json [ key ]  &&  extendedResult . config [ key ] )  { 
791+                     return  map ( extendedResult . config [ key ] ,  updatePath ) ; 
792+                 } 
793+             } ) ; 
794+             return  [ include ,  exclude ,  files ,  result . options ] ; 
795+         } 
796+ 
722797        function  getFileNames ( errors : Diagnostic [ ] ) : ExpandResult  { 
723798            let  fileNames : string [ ] ; 
724799            if  ( hasProperty ( json ,  "files" ) )  { 
0 commit comments