2016-06-22 02:31:54 +02:00
namespace ts . server {
2016-06-24 23:30:45 +02:00
2016-06-22 02:31:54 +02:00
export enum ProjectKind {
Inferred ,
Configured ,
External
}
2018-05-09 16:51:46 +02:00
/* @internal */
export type Mutable < T > = { - readonly [ K in keyof T ] : T [ K ] ; } ;
2017-05-25 22:30:27 +02:00
/* @internal */
export function countEachFileTypes ( infos : ScriptInfo [ ] ) : FileStats {
2018-05-11 03:08:36 +02:00
const result : Mutable < FileStats > = { js : 0 , jsx : 0 , ts : 0 , tsx : 0 , dts : 0 , deferred : 0 } ;
2016-10-12 04:19:40 +02:00
for ( const info of infos ) {
switch ( info . scriptKind ) {
case ScriptKind . JS :
result . js += 1 ;
break ;
case ScriptKind . JSX :
result . jsx += 1 ;
break ;
case ScriptKind . TS :
2017-06-10 04:32:44 +02:00
fileExtensionIs ( info . fileName , Extension . Dts )
2016-10-12 04:19:40 +02:00
? result . dts += 1
: result . ts += 1 ;
break ;
case ScriptKind . TSX :
result . tsx += 1 ;
break ;
2018-05-11 03:08:36 +02:00
case ScriptKind . Deferred :
result . deferred += 1 ;
break ;
2016-10-12 04:19:40 +02:00
}
}
return result ;
}
function hasOneOrMoreJsAndNoTsFiles ( project : Project ) {
const counts = countEachFileTypes ( project . getScriptInfos ( ) ) ;
return counts . js > 0 && counts . ts === 0 && counts . tsx === 0 ;
2016-09-02 00:17:38 +02:00
}
2016-08-24 22:37:03 +02:00
export function allRootFilesAreJsOrDts ( project : Project ) : boolean {
2016-10-12 04:19:40 +02:00
const counts = countEachFileTypes ( project . getRootScriptInfos ( ) ) ;
return counts . ts === 0 && counts . tsx === 0 ;
2016-09-02 00:17:38 +02:00
}
export function allFilesAreJsOrDts ( project : Project ) : boolean {
2016-10-12 04:19:40 +02:00
const counts = countEachFileTypes ( project . getScriptInfos ( ) ) ;
return counts . ts === 0 && counts . tsx === 0 ;
2016-08-24 00:15:12 +02:00
}
2017-11-16 21:37:40 +01:00
/* @internal */
export function hasNoTypeScriptSource ( fileNames : string [ ] ) : boolean {
return ! fileNames . some ( fileName = > ( fileExtensionIs ( fileName , Extension . Ts ) && ! fileExtensionIs ( fileName , Extension . Dts ) ) || fileExtensionIs ( fileName , Extension . Tsx ) ) ;
}
2017-01-05 00:56:16 +01:00
/* @internal */
2016-08-26 23:37:49 +02:00
export interface ProjectFilesWithTSDiagnostics extends protocol . ProjectFiles {
2017-08-09 22:46:47 +02:00
projectErrors : ReadonlyArray < Diagnostic > ;
2016-08-26 23:37:49 +02:00
}
2017-02-14 22:35:16 +01:00
export interface PluginCreateInfo {
project : Project ;
languageService : LanguageService ;
languageServiceHost : LanguageServiceHost ;
serverHost : ServerHost ;
config : any ;
}
export interface PluginModule {
create ( createInfo : PluginCreateInfo ) : LanguageService ;
getExternalFiles ? ( proj : Project ) : string [ ] ;
}
2017-11-03 01:16:09 +01:00
export type PluginModuleFactory = ( mod : { typescript : typeof ts } ) = > PluginModule ;
2017-02-14 22:35:16 +01:00
2017-07-06 03:03:11 +02:00
/ * *
* The project root can be script info - if root is present ,
* or it could be just normalized path if root wasnt present on the host ( only for non inferred project )
* /
export type ProjectRoot = ScriptInfo | NormalizedPath ;
/* @internal */
export function isScriptInfo ( value : ProjectRoot ) : value is ScriptInfo {
return value instanceof ScriptInfo ;
}
2017-08-24 20:50:27 +02:00
export abstract class Project implements LanguageServiceHost , ModuleResolutionHost {
2016-06-28 00:29:04 +02:00
private rootFiles : ScriptInfo [ ] = [ ] ;
2017-07-06 03:03:11 +02:00
private rootFilesMap : Map < ProjectRoot > = createMap < ProjectRoot > ( ) ;
2017-07-13 22:08:59 +02:00
private program : Program ;
2017-04-22 00:20:55 +02:00
private externalFiles : SortedReadonlyArray < string > ;
2017-07-14 05:08:57 +02:00
private missingFilesMap : Map < FileWatcher > ;
2017-12-22 02:04:27 +01:00
private plugins : PluginModule [ ] = [ ] ;
2016-06-22 02:31:54 +02:00
2018-04-12 23:55:22 +02:00
/*@internal*/
2018-04-13 01:47:40 +02:00
/ * *
* This is map from files to unresolved imports in it
* Maop does not contain entries for files that do not have unresolved imports
* This helps in containing the set of files to invalidate
* /
2018-04-12 23:55:22 +02:00
cachedUnresolvedImportsPerFile = createMap < ReadonlyArray < string > > ( ) ;
2018-04-13 01:47:40 +02:00
2018-04-12 23:55:22 +02:00
/*@internal*/
2018-05-22 23:46:57 +02:00
lastCachedUnresolvedImportsList : SortedReadonlyArray < string > | undefined ;
2018-04-13 00:21:20 +02:00
/*@internal*/
2018-04-18 20:05:56 +02:00
private hasAddedorRemovedFiles = false ;
2016-10-26 00:24:21 +02:00
2018-02-07 02:11:59 +01:00
private lastFileExceededProgramSize : string | undefined ;
2017-02-14 22:35:16 +01:00
// wrapper over the real language service that will suppress all semantic operations
protected languageService : LanguageService ;
2016-11-12 02:35:11 +01:00
2018-04-03 22:40:48 +02:00
public languageServiceEnabled : boolean ;
2016-11-12 02:35:11 +01:00
2017-08-24 20:50:27 +02:00
readonly trace ? : ( s : string ) = > void ;
readonly realpath ? : ( path : string ) = > string ;
2017-08-04 10:14:54 +02:00
/*@internal*/
2017-08-24 20:50:27 +02:00
hasInvalidatedResolution : HasInvalidatedResolution ;
2017-08-04 10:14:54 +02:00
2017-07-12 01:10:44 +02:00
/*@internal*/
2017-08-24 20:50:27 +02:00
resolutionCache : ResolutionCache ;
2017-02-14 22:35:16 +01:00
2017-12-07 23:04:40 +01:00
private builderState : BuilderState | undefined ;
2016-12-09 02:56:08 +01:00
/ * *
* Set of files names that were updated since the last call to getChangesSinceVersion .
* /
2018-05-22 23:46:57 +02:00
private updatedFileNames : Map < true > | undefined ;
2016-06-23 01:51:09 +02:00
/ * *
* Set of files that was returned from the last call to getChangesSinceVersion .
* /
2017-07-07 19:34:36 +02:00
private lastReportedFileNames : Map < true > ;
2016-06-23 01:51:09 +02:00
/ * *
* Last version that was reported .
* /
private lastReportedVersion = 0 ;
/ * *
2018-04-17 23:17:15 +02:00
* Current project ' s program version . ( incremented everytime new program is created that is not complete reuse from the old one )
2016-06-23 01:51:09 +02:00
* This property is changed in 'updateGraph' based on the set of files in program
* /
2018-04-17 23:17:15 +02:00
private projectProgramVersion = 0 ;
2016-06-23 01:51:09 +02:00
/ * *
* Current version of the project state . It is changed when :
* - new root file was added / removed
* - edit happen in some file that is currently included in the project .
* This property is different from projectStructureVersion since in most cases edits don ' t affect set of files in the project
* /
private projectStateVersion = 0 ;
2018-02-12 23:55:58 +01:00
/*@internal*/
dirty = false ;
2017-09-26 22:34:56 +02:00
/*@internal*/
hasChangedAutomaticTypeDirectiveNames = false ;
2016-08-12 20:04:43 +02:00
2018-04-13 01:47:40 +02:00
/*@internal*/
typingFiles : SortedReadonlyArray < string > = emptyArray ;
2016-09-21 01:52:34 +02:00
2017-10-04 01:32:59 +02:00
private readonly cancellationToken : ThrottledCancellationToken ;
2016-10-12 04:19:40 +02:00
public isNonTsProject() {
2016-09-02 00:17:38 +02:00
this . updateGraph ( ) ;
return allFilesAreJsOrDts ( this ) ;
}
2016-10-12 04:19:40 +02:00
public isJsOnlyProject() {
this . updateGraph ( ) ;
return hasOneOrMoreJsAndNoTsFiles ( this ) ;
}
2018-05-07 21:38:38 +02:00
public static resolveModule ( module Name : string , initialDir : string , host : ServerHost , log : ( message : string ) = > void ) : { } | undefined {
2017-02-14 22:35:16 +01:00
const resolvedPath = normalizeSlashes ( host . resolvePath ( combinePaths ( initialDir , "node_modules" ) ) ) ;
log ( ` Loading ${ module Name } from ${ initialDir } (resolved to ${ resolvedPath } ) ` ) ;
2018-05-22 23:46:57 +02:00
const result = host . require ! ( resolvedPath , module Name ) ; // TODO: GH#18217
2017-02-14 22:35:16 +01:00
if ( result . error ) {
2017-08-08 16:54:08 +02:00
const err = result . error . stack || result . error . message || JSON . stringify ( result . error ) ;
log ( ` Failed to load module ' ${ module Name } ': ${ err } ` ) ;
2017-02-14 22:35:16 +01:00
return undefined ;
}
return result . module ;
}
2017-09-27 02:29:53 +02:00
/*@internal*/
readonly currentDirectory : string ;
2017-12-06 05:23:14 +01:00
/*@internal*/
public directoryStructureHost : DirectoryStructureHost ;
2018-02-22 20:09:23 +01:00
/*@internal*/
public readonly getCanonicalFileName : GetCanonicalFileName ;
2017-10-03 01:14:42 +02:00
/*@internal*/
2016-06-22 02:31:54 +02:00
constructor (
2017-08-24 20:50:27 +02:00
/*@internal*/ readonly projectName : string ,
2016-06-22 02:31:54 +02:00
readonly projectKind : ProjectKind ,
readonly projectService : ProjectService ,
2017-07-13 22:08:59 +02:00
private documentRegistry : DocumentRegistry ,
2016-06-22 02:31:54 +02:00
hasExplicitListOfFiles : boolean ,
2018-02-07 02:11:59 +01:00
lastFileExceededProgramSize : string | undefined ,
2016-08-24 01:11:52 +02:00
private compilerOptions : CompilerOptions ,
2017-07-12 01:10:44 +02:00
public compileOnSaveEnabled : boolean ,
2017-12-06 05:23:14 +01:00
directoryStructureHost : DirectoryStructureHost ,
2017-09-27 02:29:53 +02:00
currentDirectory : string | undefined ) {
2017-12-06 05:23:14 +01:00
this . directoryStructureHost = directoryStructureHost ;
2017-09-27 02:29:53 +02:00
this . currentDirectory = this . projectService . getNormalizedAbsolutePath ( currentDirectory || "" ) ;
2018-02-22 20:09:23 +01:00
this . getCanonicalFileName = this . projectService . toCanonicalFileName ;
2016-06-22 02:31:54 +02:00
2017-10-04 01:32:59 +02:00
this . cancellationToken = new ThrottledCancellationToken ( this . projectService . cancellationToken , this . projectService . throttleWaitMilliseconds ) ;
2016-06-22 02:31:54 +02:00
if ( ! this . compilerOptions ) {
2017-07-13 22:08:59 +02:00
this . compilerOptions = getDefaultCompilerOptions ( ) ;
2016-06-22 02:31:54 +02:00
this . compilerOptions . allowNonTsExtensions = true ;
this . compilerOptions . allowJs = true ;
}
2018-04-06 02:30:04 +02:00
else if ( hasExplicitListOfFiles || this . compilerOptions . allowJs || this . projectService . hasDeferredExtension ( ) ) {
2017-02-10 02:06:47 +01:00
// If files are listed explicitly or allowJs is specified, allow all extensions
2016-06-22 02:31:54 +02:00
this . compilerOptions . allowNonTsExtensions = true ;
}
2018-04-03 22:40:48 +02:00
this . languageServiceEnabled = ! projectService . syntaxOnly ;
2016-11-30 08:30:14 +01:00
this . setInternalCompilerOptionsForEmittingJsFiles ( ) ;
2017-08-24 20:50:27 +02:00
const host = this . projectService . host ;
2018-02-21 21:40:36 +01:00
if ( this . projectService . logger . loggingEnabled ( ) ) {
this . trace = s = > this . writeLog ( s ) ;
}
else if ( host . trace ) {
2018-05-22 23:46:57 +02:00
this . trace = s = > host . trace ! ( s ) ;
2017-08-24 20:50:27 +02:00
}
2016-11-04 05:13:41 +01:00
2017-08-24 20:50:27 +02:00
if ( host . realpath ) {
2018-05-22 23:46:57 +02:00
this . realpath = path = > host . realpath ! ( path ) ;
2017-08-24 20:50:27 +02:00
}
2016-11-12 02:35:11 +01:00
2017-10-05 03:45:59 +02:00
// Use the current directory as resolution root only if the project created using current directory string
2017-12-05 00:08:56 +01:00
this . resolutionCache = createResolutionCache ( this , currentDirectory && this . currentDirectory , /*logChangesWhenResolvingModule*/ true ) ;
2018-04-03 22:40:48 +02:00
this . languageService = createLanguageService ( this , this . documentRegistry , projectService . syntaxOnly ) ;
2018-02-07 02:11:59 +01:00
if ( lastFileExceededProgramSize ) {
this . disableLanguageService ( lastFileExceededProgramSize ) ;
2016-06-22 02:31:54 +02:00
}
this . markAsDirty ( ) ;
2018-02-12 23:55:58 +01:00
this . projectService . pendingEnsureProjectForOpenFiles = true ;
2016-06-22 02:31:54 +02:00
}
2017-10-18 00:04:09 +02:00
isKnownTypesPackageName ( name : string ) : boolean {
return this . typingsCache . isKnownTypesPackageName ( name ) ;
}
2017-10-19 01:04:42 +02:00
installPackage ( options : InstallPackageOptions ) : Promise < ApplyCodeActionCommandResult > {
2017-11-22 00:05:21 +01:00
return this . typingsCache . installPackage ( { . . . options , projectName : this.projectName , projectRootPath : this.toPath ( this . currentDirectory ) } ) ;
2017-10-18 00:04:09 +02:00
}
private get typingsCache ( ) : TypingsCache {
return this . projectService . typingsCache ;
}
2017-10-16 22:06:15 +02:00
// Method of LanguageServiceHost
2017-08-24 20:50:27 +02:00
getCompilationSettings() {
return this . compilerOptions ;
}
2017-10-16 22:06:15 +02:00
// Method to support public API
2017-10-16 20:22:49 +02:00
getCompilerOptions() {
2017-10-16 22:06:15 +02:00
return this . getCompilationSettings ( ) ;
2017-10-16 20:22:49 +02:00
}
2017-08-24 20:50:27 +02:00
getNewLine() {
2017-12-05 03:17:25 +01:00
return this . projectService . host . newLine ;
2017-08-24 20:50:27 +02:00
}
getProjectVersion() {
return this . projectStateVersion . toString ( ) ;
}
2018-07-06 00:39:03 +02:00
getProjectReferences ( ) : ReadonlyArray < ProjectReference > {
return emptyArray ;
2018-05-08 00:12:50 +02:00
}
2017-08-24 20:50:27 +02:00
getScriptFileNames() {
2017-09-26 22:34:56 +02:00
if ( ! this . rootFiles ) {
return ts . emptyArray ;
2017-08-24 20:50:27 +02:00
}
2017-09-26 22:34:56 +02:00
let result : string [ ] | undefined ;
this . rootFilesMap . forEach ( value = > {
if ( this . languageServiceEnabled || ( isScriptInfo ( value ) && value . isScriptOpen ( ) ) ) {
// if language service is disabled - process only files that are open
( result || ( result = [ ] ) ) . push ( isScriptInfo ( value ) ? value.fileName : value ) ;
}
} ) ;
return addRange ( result , this . typingFiles ) || ts . emptyArray ;
2017-08-24 20:50:27 +02:00
}
2017-09-26 22:34:56 +02:00
private getOrCreateScriptInfoAndAttachToProject ( fileName : string ) {
2017-11-03 23:28:28 +01:00
const scriptInfo = this . projectService . getOrCreateScriptInfoNotOpenedByClient ( fileName , this . currentDirectory , this . directoryStructureHost ) ;
2017-08-24 20:50:27 +02:00
if ( scriptInfo ) {
const existingValue = this . rootFilesMap . get ( scriptInfo . path ) ;
2017-09-26 22:34:56 +02:00
if ( existingValue !== scriptInfo && existingValue !== undefined ) {
2017-08-24 20:50:27 +02:00
// This was missing path earlier but now the file exists. Update the root
this . rootFiles . push ( scriptInfo ) ;
this . rootFilesMap . set ( scriptInfo . path , scriptInfo ) ;
}
scriptInfo . attachToProject ( this ) ;
}
return scriptInfo ;
}
getScriptKind ( fileName : string ) {
2017-09-26 22:34:56 +02:00
const info = this . getOrCreateScriptInfoAndAttachToProject ( fileName ) ;
2018-05-22 23:46:57 +02:00
return ( info && info . scriptKind ) ! ; // TODO: GH#18217
2017-08-24 20:50:27 +02:00
}
getScriptVersion ( filename : string ) {
2017-09-26 22:34:56 +02:00
const info = this . getOrCreateScriptInfoAndAttachToProject ( filename ) ;
2018-05-22 23:46:57 +02:00
return ( info && info . getLatestVersion ( ) ) ! ; // TODO: GH#18217
2017-08-24 20:50:27 +02:00
}
2018-05-22 23:46:57 +02:00
getScriptSnapshot ( filename : string ) : IScriptSnapshot | undefined {
2017-09-26 22:34:56 +02:00
const scriptInfo = this . getOrCreateScriptInfoAndAttachToProject ( filename ) ;
2017-08-24 20:50:27 +02:00
if ( scriptInfo ) {
return scriptInfo . getSnapshot ( ) ;
}
}
2017-10-05 01:03:16 +02:00
getCancellationToken ( ) : HostCancellationToken {
2017-10-04 01:32:59 +02:00
return this . cancellationToken ;
2017-08-24 20:50:27 +02:00
}
getCurrentDirectory ( ) : string {
2017-10-05 03:45:59 +02:00
return this . currentDirectory ;
2017-08-24 20:50:27 +02:00
}
getDefaultLibFileName() {
2017-10-05 03:45:59 +02:00
const nodeModuleBinDir = getDirectoryPath ( normalizePath ( this . projectService . getExecutingFilePath ( ) ) ) ;
2017-08-24 20:50:27 +02:00
return combinePaths ( nodeModuleBinDir , getDefaultLibFileName ( this . compilerOptions ) ) ;
}
useCaseSensitiveFileNames() {
2017-12-06 01:34:17 +01:00
return this . projectService . host . useCaseSensitiveFileNames ;
2017-08-24 20:50:27 +02:00
}
readDirectory ( path : string , extensions? : ReadonlyArray < string > , exclude? : ReadonlyArray < string > , include? : ReadonlyArray < string > , depth? : number ) : string [ ] {
2018-05-22 23:46:57 +02:00
return this . directoryStructureHost . readDirectory ! ( path , extensions , exclude , include , depth ) ;
2017-08-24 20:50:27 +02:00
}
readFile ( fileName : string ) : string | undefined {
2017-12-06 01:34:17 +01:00
return this . projectService . host . readFile ( fileName ) ;
2017-08-24 20:50:27 +02:00
}
fileExists ( file : string ) : boolean {
// As an optimization, don't hit the disks for files we already know don't exist
// (because we're watching for their creation).
const path = this . toPath ( file ) ;
2017-09-26 20:05:52 +02:00
return ! this . isWatchedMissingFile ( path ) && this . directoryStructureHost . fileExists ( file ) ;
2017-08-24 20:50:27 +02:00
}
2017-09-12 21:09:06 +02:00
resolveModuleNames ( module Names : string [ ] , containingFile : string , reusedNames? : string [ ] ) : ResolvedModuleFull [ ] {
2017-12-05 00:08:56 +01:00
return this . resolutionCache . resolveModuleNames ( module Names , containingFile , reusedNames ) ;
2017-08-24 20:50:27 +02:00
}
2018-05-22 23:46:57 +02:00
getResolvedModuleWithFailedLookupLocationsFromCache ( module Name : string , containingFile : string ) : ResolvedModuleWithFailedLookupLocations | undefined {
2018-05-19 01:42:42 +02:00
return this . resolutionCache . getResolvedModuleWithFailedLookupLocationsFromCache ( module Name , containingFile ) ;
}
2017-08-24 20:50:27 +02:00
resolveTypeReferenceDirectives ( typeDirectiveNames : string [ ] , containingFile : string ) : ResolvedTypeReferenceDirective [ ] {
return this . resolutionCache . resolveTypeReferenceDirectives ( typeDirectiveNames , containingFile ) ;
}
directoryExists ( path : string ) : boolean {
2018-05-22 23:46:57 +02:00
return this . directoryStructureHost . directoryExists ! ( path ) ; // TODO: GH#18217
2017-08-24 20:50:27 +02:00
}
getDirectories ( path : string ) : string [ ] {
2018-05-22 23:46:57 +02:00
return this . directoryStructureHost . getDirectories ! ( path ) ; // TODO: GH#18217
2017-08-24 20:50:27 +02:00
}
2017-12-06 01:34:17 +01:00
/*@internal*/
getCachedDirectoryStructureHost ( ) : CachedDirectoryStructureHost {
2018-05-22 23:46:57 +02:00
return undefined ! ; // TODO: GH#18217
2017-12-06 01:34:17 +01:00
}
2017-08-24 20:50:27 +02:00
/*@internal*/
toPath ( fileName : string ) {
2017-11-03 23:28:28 +01:00
return toPath ( fileName , this . currentDirectory , this . projectService . toCanonicalFileName ) ;
2017-08-24 20:50:27 +02:00
}
/*@internal*/
2017-08-28 21:34:58 +02:00
watchDirectoryOfFailedLookupLocation ( directory : string , cb : DirectoryWatcherCallback , flags : WatchDirectoryFlags ) {
2017-12-18 22:20:42 +01:00
return this . projectService . watchFactory . watchDirectory (
2017-08-23 22:34:41 +02:00
this . projectService . host ,
2017-08-23 22:34:41 +02:00
directory ,
2017-08-24 20:50:27 +02:00
cb ,
2017-08-28 21:34:58 +02:00
flags ,
2017-08-23 22:34:41 +02:00
WatchType . FailedLookupLocation ,
this
) ;
}
2017-08-24 20:50:27 +02:00
/*@internal*/
onInvalidatedResolution() {
2018-02-12 23:55:58 +01:00
this . projectService . delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles ( this ) ;
2017-08-24 20:50:27 +02:00
}
2017-08-23 22:34:41 +02:00
2017-08-30 20:49:58 +02:00
/*@internal*/
watchTypeRootsDirectory ( directory : string , cb : DirectoryWatcherCallback , flags : WatchDirectoryFlags ) {
2017-12-18 22:20:42 +01:00
return this . projectService . watchFactory . watchDirectory (
2017-08-30 20:49:58 +02:00
this . projectService . host ,
directory ,
cb ,
flags ,
WatchType . TypeRoots ,
this
) ;
}
/*@internal*/
onChangedAutomaticTypeDirectiveNames() {
2017-09-26 22:34:56 +02:00
this . hasChangedAutomaticTypeDirectiveNames = true ;
2018-02-12 23:55:58 +01:00
this . projectService . delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles ( this ) ;
2017-08-30 20:49:58 +02:00
}
2017-08-24 20:50:27 +02:00
/*@internal*/
getGlobalCache() {
return this . getTypeAcquisition ( ) . enable ? this . projectService.typingsInstaller.globalTypingsCacheLocation : undefined ;
2017-08-05 11:27:27 +02:00
}
2017-08-29 02:09:07 +02:00
/*@internal*/
writeLog ( s : string ) {
this . projectService . logger . info ( s ) ;
}
2018-02-05 20:42:03 +01:00
log ( s : string ) {
this . writeLog ( s ) ;
}
error ( s : string ) {
this . projectService . logger . msg ( s , Msg . Err ) ;
}
2016-11-30 08:30:14 +01:00
private setInternalCompilerOptionsForEmittingJsFiles() {
if ( this . projectKind === ProjectKind . Inferred || this . projectKind === ProjectKind . External ) {
this . compilerOptions . noEmitForJsFiles = true ;
}
}
2017-05-24 00:38:03 +02:00
/ * *
* Get the errors that dont have any file name associated
* /
2017-08-18 20:55:47 +02:00
getGlobalProjectErrors ( ) : ReadonlyArray < Diagnostic > {
return emptyArray ;
2017-05-24 00:38:03 +02:00
}
2017-08-18 20:55:47 +02:00
getAllProjectErrors ( ) : ReadonlyArray < Diagnostic > {
return emptyArray ;
2016-08-26 23:37:49 +02:00
}
2016-08-12 20:04:43 +02:00
getLanguageService ( ensureSynchronized = true ) : LanguageService {
2016-07-23 01:01:54 +02:00
if ( ensureSynchronized ) {
this . updateGraph ( ) ;
}
2016-12-13 22:21:32 +01:00
return this . languageService ;
2016-07-23 01:01:54 +02:00
}
2018-07-06 00:39:03 +02:00
/** @internal */
getSourceMapper ( ) : SourceMapper {
return this . getLanguageService ( ) . getSourceMapper ( ) ;
}
2017-10-04 00:06:56 +02:00
private shouldEmitFile ( scriptInfo : ScriptInfo ) {
return scriptInfo && ! scriptInfo . isDynamicOrHasMixedContent ( ) ;
2017-10-18 06:18:27 +02:00
}
2016-08-24 01:11:52 +02:00
getCompileOnSaveAffectedFileList ( scriptInfo : ScriptInfo ) : string [ ] {
if ( ! this . languageServiceEnabled ) {
return [ ] ;
}
this . updateGraph ( ) ;
2017-12-08 01:46:36 +01:00
this . builderState = BuilderState . create ( this . program , this . projectService . toCanonicalFileName , this . builderState ) ;
2018-05-22 23:46:57 +02:00
return mapDefined ( BuilderState . getFilesAffectedBy ( this . builderState , this . program , scriptInfo . path , this . cancellationToken , data = > this . projectService . host . createHash ! ( data ) ) , // TODO: GH#18217
sourceFile = > this . shouldEmitFile ( this . projectService . getScriptInfoForPath ( sourceFile . path ) ! ) ? sourceFile.fileName : undefined ) ;
2017-07-25 01:57:49 +02:00
}
/ * *
* Returns true if emit was conducted
* /
emitFile ( scriptInfo : ScriptInfo , writeFile : ( path : string , data : string , writeByteOrderMark? : boolean ) = > void ) : boolean {
2017-10-04 00:06:56 +02:00
if ( ! this . languageServiceEnabled || ! this . shouldEmitFile ( scriptInfo ) ) {
return false ;
}
const { emitSkipped , outputFiles } = this . getLanguageService ( /*ensureSynchronized*/ false ) . getEmitOutput ( scriptInfo . fileName ) ;
2017-07-25 01:57:49 +02:00
if ( ! emitSkipped ) {
for ( const outputFile of outputFiles ) {
2017-10-05 03:45:59 +02:00
const outputFileAbsoluteFileName = getNormalizedAbsolutePath ( outputFile . name , this . currentDirectory ) ;
2017-07-25 01:57:49 +02:00
writeFile ( outputFileAbsoluteFileName , outputFile . text , outputFile . writeByteOrderMark ) ;
}
}
return ! emitSkipped ;
2016-08-24 01:11:52 +02:00
}
2016-06-22 02:31:54 +02:00
enableLanguageService() {
2018-04-03 22:40:48 +02:00
if ( this . languageServiceEnabled || this . projectService . syntaxOnly ) {
2016-11-12 02:35:11 +01:00
return ;
}
2016-06-22 02:31:54 +02:00
this . languageServiceEnabled = true ;
2018-02-07 02:11:59 +01:00
this . lastFileExceededProgramSize = undefined ;
2016-11-12 02:35:11 +01:00
this . projectService . onUpdateLanguageServiceStateForProject ( this , /*languageServiceEnabled*/ true ) ;
2016-06-22 02:31:54 +02:00
}
2018-02-07 02:11:59 +01:00
disableLanguageService ( lastFileExceededProgramSize? : string ) {
2016-11-12 02:35:11 +01:00
if ( ! this . languageServiceEnabled ) {
return ;
}
2018-04-03 22:40:48 +02:00
Debug . assert ( ! this . projectService . syntaxOnly ) ;
2016-11-12 02:35:11 +01:00
this . languageService . cleanupSemanticCache ( ) ;
2016-06-22 02:31:54 +02:00
this . languageServiceEnabled = false ;
2018-02-07 02:11:59 +01:00
this . lastFileExceededProgramSize = lastFileExceededProgramSize ;
2017-12-07 23:04:40 +01:00
this . builderState = undefined ;
2017-08-30 20:49:58 +02:00
this . resolutionCache . closeTypeRootsWatch ( ) ;
2016-11-12 02:35:11 +01:00
this . projectService . onUpdateLanguageServiceStateForProject ( this , /*languageServiceEnabled*/ false ) ;
2016-06-22 02:31:54 +02:00
}
2016-11-18 00:12:32 +01:00
getProjectName() {
return this . projectName ;
}
2016-11-19 02:46:06 +01:00
abstract getTypeAcquisition ( ) : TypeAcquisition ;
2016-06-22 02:31:54 +02:00
2017-12-06 22:17:21 +01:00
protected removeLocalTypingsFromTypeAcquisition ( newTypeAcquisition : TypeAcquisition ) : TypeAcquisition {
if ( ! newTypeAcquisition || ! newTypeAcquisition . include ) {
// Nothing to filter out, so just return as-is
return newTypeAcquisition ;
}
return { . . . newTypeAcquisition , include : this.removeExistingTypings ( newTypeAcquisition . include ) } ;
}
2017-04-22 00:20:55 +02:00
getExternalFiles ( ) : SortedReadonlyArray < string > {
2017-12-22 02:04:27 +01:00
return toSortedArray ( flatMap ( this . plugins , plugin = > {
if ( typeof plugin . getExternalFiles !== "function" ) return ;
try {
return plugin . getExternalFiles ( this ) ;
}
catch ( e ) {
this . projectService . logger . info ( ` A plugin threw an exception in getExternalFiles: ${ e } ` ) ;
if ( e . stack ) {
this . projectService . logger . info ( e . stack ) ;
}
}
} ) ) ;
2017-02-14 22:35:16 +01:00
}
2016-08-24 01:11:52 +02:00
getSourceFile ( path : Path ) {
if ( ! this . program ) {
return undefined ;
}
return this . program . getSourceFileByPath ( path ) ;
}
2018-06-07 23:30:19 +02:00
/* @internal */
getSourceFileOrConfigFile ( path : Path ) : SourceFile | undefined {
const options = this . program . getCompilerOptions ( ) ;
return path === options . configFilePath ? options.configFile : this.getSourceFile ( path ) ;
}
2016-06-22 02:31:54 +02:00
close() {
2016-06-28 00:29:04 +02:00
if ( this . program ) {
2017-10-10 05:12:53 +02:00
// if we have a program - release all files that are enlisted in program but arent root
// The releasing of the roots happens later
// The project could have pending update remaining and hence the info could be in the files but not in program graph
2016-06-28 00:29:04 +02:00
for ( const f of this . program . getSourceFiles ( ) ) {
2017-10-10 05:12:53 +02:00
this . detachScriptInfoIfNotRoot ( f . fileName ) ;
2016-06-28 00:29:04 +02:00
}
2016-06-22 02:31:54 +02:00
}
2017-10-10 05:12:53 +02:00
// Release external files
forEach ( this . externalFiles , externalFile = > this . detachScriptInfoIfNotRoot ( externalFile ) ) ;
// Always remove root files from the project
for ( const root of this . rootFiles ) {
root . detachFromProject ( this ) ;
2016-06-28 00:29:04 +02:00
}
2018-02-12 23:55:58 +01:00
this . projectService . pendingEnsureProjectForOpenFiles = true ;
2017-10-10 00:24:00 +02:00
2018-05-22 23:46:57 +02:00
this . rootFiles = undefined ! ;
this . rootFilesMap = undefined ! ;
this . externalFiles = undefined ! ;
this . program = undefined ! ;
this . builderState = undefined ! ;
2017-08-05 08:09:11 +02:00
this . resolutionCache . clear ( ) ;
2018-05-22 23:46:57 +02:00
this . resolutionCache = undefined ! ;
this . cachedUnresolvedImportsPerFile = undefined ! ;
this . directoryStructureHost = undefined ! ;
2016-06-28 00:29:04 +02:00
2017-06-21 01:30:33 +02:00
// Clean up file watchers waiting for missing files
2017-08-07 23:47:32 +02:00
if ( this . missingFilesMap ) {
2017-08-23 22:34:41 +02:00
clearMap ( this . missingFilesMap , closeFileWatcher ) ;
2018-05-22 23:46:57 +02:00
this . missingFilesMap = undefined ! ;
2017-08-07 23:47:32 +02:00
}
2017-06-21 01:30:33 +02:00
2016-06-28 00:29:04 +02:00
// signal language service to release source files acquired from document registry
2016-06-22 02:31:54 +02:00
this . languageService . dispose ( ) ;
2018-05-22 23:46:57 +02:00
this . languageService = undefined ! ;
2016-06-22 02:31:54 +02:00
}
2017-10-10 05:12:53 +02:00
private detachScriptInfoIfNotRoot ( uncheckedFilename : string ) {
2017-10-10 00:24:00 +02:00
const info = this . projectService . getScriptInfo ( uncheckedFilename ) ;
// We might not find the script info in case its not associated with the project any more
// and project graph was not updated (eg delayed update graph in case of files changed/deleted on the disk)
2017-10-10 05:12:53 +02:00
if ( info && ! this . isRoot ( info ) ) {
2017-10-10 00:24:00 +02:00
info . detachFromProject ( this ) ;
}
}
2017-08-15 23:50:52 +02:00
isClosed() {
2017-08-24 20:50:27 +02:00
return this . rootFiles === undefined ;
2016-06-22 02:31:54 +02:00
}
2016-06-27 23:25:43 +02:00
hasRoots() {
2016-06-29 02:45:29 +02:00
return this . rootFiles && this . rootFiles . length > 0 ;
2016-06-27 23:25:43 +02:00
}
2018-04-20 22:38:15 +02:00
/*@internal*/
isOrphan() {
return false ;
}
2016-06-22 02:31:54 +02:00
getRootFiles() {
2016-06-29 02:45:29 +02:00
return this . rootFiles && this . rootFiles . map ( info = > info . fileName ) ;
2016-06-22 02:31:54 +02:00
}
2017-08-18 20:55:47 +02:00
/*@internal*/
2017-07-06 03:03:11 +02:00
getRootFilesMap() {
return this . rootFilesMap ;
}
2016-07-06 00:51:39 +02:00
getRootScriptInfos() {
return this . rootFiles ;
}
2018-05-22 23:46:57 +02:00
getScriptInfos ( ) : ScriptInfo [ ] {
2016-12-13 22:21:32 +01:00
if ( ! this . languageServiceEnabled ) {
// if language service is not enabled - return just root files
return this . rootFiles ;
}
2016-09-30 22:19:54 +02:00
return map ( this . program . getSourceFiles ( ) , sourceFile = > {
2018-06-01 22:12:57 +02:00
const scriptInfo = this . projectService . getScriptInfoForPath ( sourceFile . resolvedPath || sourceFile . path ) ;
Debug . assert ( ! ! scriptInfo , "getScriptInfo" , ( ) = > ` scriptInfo for a file ' ${ sourceFile . fileName } ' Path: ' ${ sourceFile . path } ' / ' ${ sourceFile . resolvedPath } ' is missing. ` ) ;
2018-05-22 23:46:57 +02:00
return scriptInfo ! ;
2016-09-30 22:19:54 +02:00
} ) ;
2016-08-24 01:11:52 +02:00
}
2017-07-28 01:07:50 +02:00
getExcludedFiles ( ) : ReadonlyArray < NormalizedPath > {
return emptyArray ;
}
2017-05-15 22:13:00 +02:00
getFileNames ( excludeFilesFromExternalLibraries? : boolean , excludeConfigFiles? : boolean ) {
2016-06-23 01:51:09 +02:00
if ( ! this . program ) {
return [ ] ;
}
2016-06-22 02:31:54 +02:00
if ( ! this . languageServiceEnabled ) {
// if language service is disabled assume that all files in program are root files + default library
let rootFiles = this . getRootFiles ( ) ;
if ( this . compilerOptions ) {
const defaultLibrary = getDefaultLibFilePath ( this . compilerOptions ) ;
if ( defaultLibrary ) {
( rootFiles || ( rootFiles = [ ] ) ) . push ( asNormalizedPath ( defaultLibrary ) ) ;
}
}
return rootFiles ;
}
2016-11-08 06:13:11 +01:00
const result : NormalizedPath [ ] = [ ] ;
for ( const f of this . program . getSourceFiles ( ) ) {
if ( excludeFilesFromExternalLibraries && this . program . isSourceFileFromExternalLibrary ( f ) ) {
continue ;
}
result . push ( asNormalizedPath ( f . fileName ) ) ;
}
2017-05-15 22:13:00 +02:00
if ( ! excludeConfigFiles ) {
const configFile = this . program . getCompilerOptions ( ) . configFile ;
if ( configFile ) {
result . push ( asNormalizedPath ( configFile . fileName ) ) ;
if ( configFile . extendedSourceFiles ) {
for ( const f of configFile . extendedSourceFiles ) {
result . push ( asNormalizedPath ( f ) ) ;
}
}
}
}
2016-11-08 06:13:11 +01:00
return result ;
2016-06-22 02:31:54 +02:00
}
2017-05-24 00:38:03 +02:00
hasConfigFile ( configFilePath : NormalizedPath ) {
if ( this . program && this . languageServiceEnabled ) {
const configFile = this . program . getCompilerOptions ( ) . configFile ;
if ( configFile ) {
if ( configFilePath === asNormalizedPath ( configFile . fileName ) ) {
return true ;
}
if ( configFile . extendedSourceFiles ) {
for ( const f of configFile . extendedSourceFiles ) {
if ( configFilePath === asNormalizedPath ( f ) ) {
return true ;
}
}
}
}
}
return false ;
}
2016-06-22 02:31:54 +02:00
containsScriptInfo ( info : ScriptInfo ) : boolean {
2016-06-28 00:29:04 +02:00
return this . isRoot ( info ) || ( this . program && this . program . getSourceFileByPath ( info . path ) !== undefined ) ;
2016-06-22 02:31:54 +02:00
}
2018-05-22 23:46:57 +02:00
containsFile ( filename : NormalizedPath , requireOpen? : boolean ) : boolean {
2018-07-27 21:21:30 +02:00
const info = this . projectService . getScriptInfoForNormalizedPath ( filename ) ;
2016-12-09 01:17:42 +01:00
if ( info && ( info . isScriptOpen ( ) || ! requireOpen ) ) {
2016-06-24 23:30:45 +02:00
return this . containsScriptInfo ( info ) ;
2016-06-22 02:31:54 +02:00
}
2018-05-22 23:46:57 +02:00
return false ;
2016-06-22 02:31:54 +02:00
}
isRoot ( info : ScriptInfo ) {
2017-07-06 03:03:11 +02:00
return this . rootFilesMap && this . rootFilesMap . get ( info . path ) === info ;
2016-06-22 02:31:54 +02:00
}
// add a root file to project
addRoot ( info : ScriptInfo ) {
2017-08-07 23:47:32 +02:00
Debug . assert ( ! this . isRoot ( info ) ) ;
this . rootFiles . push ( info ) ;
this . rootFilesMap . set ( info . path , info ) ;
info . attachToProject ( this ) ;
2016-06-24 23:55:12 +02:00
2017-08-07 23:47:32 +02:00
this . markAsDirty ( ) ;
2016-06-22 02:31:54 +02:00
}
2017-08-18 20:55:47 +02:00
// add a root file that doesnt exist on host
2017-07-06 03:03:11 +02:00
addMissingFileRoot ( fileName : NormalizedPath ) {
2017-07-15 07:35:07 +02:00
const path = this . projectService . toPath ( fileName ) ;
2017-07-06 03:03:11 +02:00
this . rootFilesMap . set ( path , fileName ) ;
this . markAsDirty ( ) ;
}
2017-09-08 02:27:00 +02:00
removeFile ( info : ScriptInfo , fileExists : boolean , detachFromProject : boolean ) {
2016-12-30 02:00:04 +01:00
if ( this . isRoot ( info ) ) {
this . removeRoot ( info ) ;
}
2017-09-08 02:27:00 +02:00
if ( fileExists ) {
// If file is present, just remove the resolutions for the file
this . resolutionCache . removeResolutionsOfFile ( info . path ) ;
}
else {
this . resolutionCache . invalidateResolutionOfFile ( info . path ) ;
}
2018-04-12 23:55:22 +02:00
this . cachedUnresolvedImportsPerFile . delete ( info . path ) ;
2016-06-24 23:30:45 +02:00
2016-06-23 01:51:09 +02:00
if ( detachFromProject ) {
info . detachFromProject ( this ) ;
}
2016-06-24 23:30:45 +02:00
2016-06-22 02:31:54 +02:00
this . markAsDirty ( ) ;
}
2016-12-09 02:56:08 +01:00
registerFileUpdate ( fileName : string ) {
2017-07-07 19:34:36 +02:00
( this . updatedFileNames || ( this . updatedFileNames = createMap < true > ( ) ) ) . set ( fileName , true ) ;
2016-12-09 02:56:08 +01:00
}
2016-06-22 02:31:54 +02:00
markAsDirty() {
2018-02-12 23:55:58 +01:00
if ( ! this . dirty ) {
this . projectStateVersion ++ ;
this . dirty = true ;
}
2016-06-22 02:31:54 +02:00
}
2017-12-06 22:17:21 +01:00
/* @internal */
2018-04-17 23:17:15 +02:00
private extractUnresolvedImportsFromSourceFile ( file : SourceFile , ambientModules : string [ ] ) : ReadonlyArray < string > {
2016-10-26 00:24:21 +02:00
const cached = this . cachedUnresolvedImportsPerFile . get ( file . path ) ;
if ( cached ) {
2018-04-17 23:17:15 +02:00
// found cached result, return
return cached ;
2016-10-26 00:24:21 +02:00
}
2018-04-13 01:47:40 +02:00
let unresolvedImports : string [ ] | undefined ;
2016-10-26 00:24:21 +02:00
if ( file . resolvedModules ) {
2016-12-05 23:13:32 +01:00
file . resolvedModules . forEach ( ( resolvedModule , name ) = > {
2016-10-26 00:24:21 +02:00
// pick unresolved non-relative names
2017-12-06 22:17:21 +01:00
if ( ! resolvedModule && ! isExternalModuleNameRelative ( name ) && ! isAmbientlyDeclaredModule ( name ) ) {
2016-10-26 00:24:21 +02:00
// for non-scoped names extract part up-to the first slash
// for scoped names - extract up to the second slash
let trimmed = name . trim ( ) ;
let i = trimmed . indexOf ( "/" ) ;
if ( i !== - 1 && trimmed . charCodeAt ( 0 ) === CharacterCodes . at ) {
i = trimmed . indexOf ( "/" , i + 1 ) ;
}
if ( i !== - 1 ) {
trimmed = trimmed . substr ( 0 , i ) ;
}
( unresolvedImports || ( unresolvedImports = [ ] ) ) . push ( trimmed ) ;
}
2016-12-05 23:13:32 +01:00
} ) ;
2016-10-26 00:24:21 +02:00
}
2018-04-17 23:17:15 +02:00
this . cachedUnresolvedImportsPerFile . set ( file . path , unresolvedImports || emptyArray ) ;
return unresolvedImports || emptyArray ;
2017-12-06 22:17:21 +01:00
function isAmbientlyDeclaredModule ( name : string ) {
return ambientModules . some ( m = > m === name ) ;
}
2016-10-26 00:24:21 +02:00
}
2018-04-17 23:17:15 +02:00
/* @internal */
2018-04-18 20:05:56 +02:00
onFileAddedOrRemoved() {
this . hasAddedorRemovedFiles = true ;
2018-04-17 23:17:15 +02:00
}
2016-06-30 22:23:55 +02:00
/ * *
* Updates set of files that contribute to this project
* @returns : true if set of files in the project stays the same and false - otherwise .
* /
2016-06-29 02:45:29 +02:00
updateGraph ( ) : boolean {
2017-08-04 10:14:54 +02:00
this . resolutionCache . startRecordingFilesWithChangedResolutions ( ) ;
2016-10-26 00:24:21 +02:00
2018-04-17 23:17:15 +02:00
const hasNewProgram = this . updateGraphWorker ( ) ;
2018-04-18 20:05:56 +02:00
const hasAddedorRemovedFiles = this . hasAddedorRemovedFiles ;
this . hasAddedorRemovedFiles = false ;
2016-10-26 00:24:21 +02:00
2017-08-04 10:14:54 +02:00
const changedFiles : ReadonlyArray < Path > = this . resolutionCache . finishRecordingFilesWithChangedResolutions ( ) || emptyArray ;
2016-10-26 00:24:21 +02:00
for ( const file of changedFiles ) {
// delete cached information for changed files
2018-04-12 23:55:22 +02:00
this . cachedUnresolvedImportsPerFile . delete ( file ) ;
2016-10-26 00:24:21 +02:00
}
2016-12-13 22:21:32 +01:00
// update builder only if language service is enabled
// otherwise tell it to drop its internal state
if ( this . languageServiceEnabled ) {
2017-09-07 19:58:50 +02:00
// 1. no changes in structure, no changes in unresolved imports - do nothing
// 2. no changes in structure, unresolved imports were changed - collect unresolved imports for all files
// (can reuse cached imports for files that were not changed)
// 3. new files were added/removed, but compilation settings stays the same - collect unresolved imports for all new/modified files
// (can reuse cached imports for files that were not changed)
// 4. compilation settings were changed in the way that might affect module resolution - drop all caches and collect all data from the scratch
2018-04-17 23:17:15 +02:00
if ( hasNewProgram || changedFiles . length ) {
2018-04-13 01:47:40 +02:00
let result : string [ ] | undefined ;
2017-12-06 22:17:21 +01:00
const ambientModules = this . program . getTypeChecker ( ) . getAmbientModules ( ) . map ( mod = > stripQuotes ( mod . getName ( ) ) ) ;
2017-09-07 19:58:50 +02:00
for ( const sourceFile of this . program . getSourceFiles ( ) ) {
2018-04-17 23:17:15 +02:00
const unResolved = this . extractUnresolvedImportsFromSourceFile ( sourceFile , ambientModules ) ;
if ( unResolved !== emptyArray ) {
( result || ( result = [ ] ) ) . push ( . . . unResolved ) ;
}
2017-09-07 19:58:50 +02:00
}
2018-04-13 01:47:40 +02:00
this . lastCachedUnresolvedImportsList = result ? toDeduplicatedSortedArray ( result ) : emptyArray ;
2017-09-07 19:58:50 +02:00
}
2018-04-18 20:05:56 +02:00
this . projectService . typingsCache . enqueueInstallTypingsForProject ( this , this . lastCachedUnresolvedImportsList , hasAddedorRemovedFiles ) ;
2016-12-13 22:21:32 +01:00
}
else {
2017-09-07 19:58:50 +02:00
this . lastCachedUnresolvedImportsList = undefined ;
2016-12-13 22:21:32 +01:00
}
2018-04-17 23:17:15 +02:00
if ( hasNewProgram ) {
this . projectProgramVersion ++ ;
2016-08-12 20:04:43 +02:00
}
2018-04-17 23:17:15 +02:00
return ! hasNewProgram ;
2016-08-12 20:04:43 +02:00
}
2018-04-13 01:47:40 +02:00
/*@internal*/
updateTypingFiles ( typingFiles : SortedReadonlyArray < string > ) {
2018-04-19 19:59:23 +02:00
enumerateInsertsAndDeletes ( typingFiles , this . typingFiles , getStringComparer ( ! this . useCaseSensitiveFileNames ( ) ) ,
/*inserted*/ noop ,
removed = > this . detachScriptInfoFromProject ( removed )
) ;
2018-04-13 01:47:40 +02:00
this . typingFiles = typingFiles ;
// Invalidate files with unresolved imports
this . resolutionCache . setFilesWithInvalidatedNonRelativeUnresolvedImports ( this . cachedUnresolvedImportsPerFile ) ;
}
2018-01-11 20:52:46 +01:00
/* @internal */
getCurrentProgram() {
return this . program ;
}
2017-12-06 22:17:21 +01:00
protected removeExistingTypings ( include : string [ ] ) : string [ ] {
2018-03-01 23:20:18 +01:00
const existing = getAutomaticTypeDirectiveNames ( this . getCompilerOptions ( ) , this . directoryStructureHost ) ;
2017-12-06 22:17:21 +01:00
return include . filter ( i = > existing . indexOf ( i ) < 0 ) ;
}
2016-08-12 20:04:43 +02:00
private updateGraphWorker() {
2016-06-23 01:51:09 +02:00
const oldProgram = this . program ;
2017-10-10 00:24:00 +02:00
Debug . assert ( ! this . isClosed ( ) , "Called update graph worker of closed project" ) ;
2017-09-06 23:43:34 +02:00
this . writeLog ( ` Starting updateGraphWorker: Project: ${ this . getProjectName ( ) } ` ) ;
const start = timestamp ( ) ;
2017-10-09 23:25:48 +02:00
this . hasInvalidatedResolution = this . resolutionCache . createHasInvalidatedResolution ( ) ;
2017-08-29 02:09:07 +02:00
this . resolutionCache . startCachingPerDirectoryResolution ( ) ;
2018-05-22 23:46:57 +02:00
this . program = this . languageService . getProgram ( ) ! ; // TODO: GH#18217
2018-02-12 23:55:58 +01:00
this . dirty = false ;
2017-08-29 02:09:07 +02:00
this . resolutionCache . finishCachingPerDirectoryResolution ( ) ;
2016-06-23 01:51:09 +02:00
2018-04-03 22:40:48 +02:00
Debug . assert ( oldProgram === undefined || this . program !== undefined ) ;
2016-06-23 01:51:09 +02:00
// bump up the version if
// - oldProgram is not set - this is a first time updateGraph is called
// - newProgram is different from the old program and structure of the old program was not reused.
2018-05-22 23:46:57 +02:00
const hasNewProgram = this . program && ( ! oldProgram || ( this . program !== oldProgram && ! ( oldProgram . structureIsReused ! & StructureIsReused . Completely ) ) ) ;
2017-09-26 22:34:56 +02:00
this . hasChangedAutomaticTypeDirectiveNames = false ;
2018-04-17 23:17:15 +02:00
if ( hasNewProgram ) {
2016-06-24 23:30:45 +02:00
if ( oldProgram ) {
for ( const f of oldProgram . getSourceFiles ( ) ) {
2016-06-29 02:45:29 +02:00
if ( this . program . getSourceFileByPath ( f . path ) ) {
continue ;
}
// new program does not contain this file - detach it from the project
2017-09-08 02:27:00 +02:00
this . detachScriptInfoFromProject ( f . fileName ) ;
2016-06-24 23:30:45 +02:00
}
}
2017-04-22 00:20:55 +02:00
2017-07-14 05:08:57 +02:00
// Update the missing file paths watcher
2017-08-13 00:31:19 +02:00
updateMissingFilePathsWatch (
this . program ,
2017-08-07 23:47:32 +02:00
this . missingFilesMap || ( this . missingFilesMap = createMap ( ) ) ,
2017-07-14 05:08:57 +02:00
// Watch the missing files
2017-08-23 22:34:41 +02:00
missingFilePath = > this . addMissingFileWatcher ( missingFilePath )
2017-07-14 05:08:57 +02:00
) ;
2017-08-29 02:09:07 +02:00
// Watch the type locations that would be added to program as part of automatic type resolutions
2017-08-30 20:49:58 +02:00
if ( this . languageServiceEnabled ) {
this . resolutionCache . updateTypeRootsWatch ( ) ;
}
2017-06-21 00:54:43 +02:00
}
2017-04-22 00:20:55 +02:00
const oldExternalFiles = this . externalFiles || emptyArray as SortedReadonlyArray < string > ;
this . externalFiles = this . getExternalFiles ( ) ;
2018-04-19 19:59:23 +02:00
enumerateInsertsAndDeletes ( this . externalFiles , oldExternalFiles , getStringComparer ( ! this . useCaseSensitiveFileNames ( ) ) ,
2017-04-22 00:20:55 +02:00
// Ensure a ScriptInfo is created for new external files. This is performed indirectly
// by the LSHost for files in the program when the program is retrieved above but
// the program doesn't contain external files so this must be done explicitly.
inserted = > {
2018-05-22 23:46:57 +02:00
const scriptInfo = this . projectService . getOrCreateScriptInfoNotOpenedByClient ( inserted , this . currentDirectory , this . directoryStructureHost ) ! ;
2017-04-22 00:20:55 +02:00
scriptInfo . attachToProject ( this ) ;
} ,
2018-01-17 01:35:34 +01:00
removed = > this . detachScriptInfoFromProject ( removed )
2017-09-08 02:27:00 +02:00
) ;
2017-09-06 23:43:34 +02:00
const elapsed = timestamp ( ) - start ;
2018-04-17 23:17:15 +02:00
this . writeLog ( ` Finishing updateGraphWorker: Project: ${ this . getProjectName ( ) } Version: ${ this . getProjectVersion ( ) } structureChanged: ${ hasNewProgram } Elapsed: ${ elapsed } ms ` ) ;
return hasNewProgram ;
2016-06-22 02:31:54 +02:00
}
2017-09-08 02:27:00 +02:00
private detachScriptInfoFromProject ( uncheckedFileName : string ) {
const scriptInfoToDetach = this . projectService . getScriptInfo ( uncheckedFileName ) ;
if ( scriptInfoToDetach ) {
scriptInfoToDetach . detachFromProject ( this ) ;
this . resolutionCache . removeResolutionsOfFile ( scriptInfoToDetach . path ) ;
}
}
2017-07-25 01:57:49 +02:00
private addMissingFileWatcher ( missingFilePath : Path ) {
2017-12-18 22:20:42 +01:00
const fileWatcher = this . projectService . watchFactory . watchFile (
2017-08-23 22:34:41 +02:00
this . projectService . host ,
missingFilePath ,
2017-08-14 23:59:51 +02:00
( fileName , eventKind ) = > {
if ( this . projectKind === ProjectKind . Configured ) {
2017-12-06 01:34:17 +01:00
this . getCachedDirectoryStructureHost ( ) . addOrDeleteFile ( fileName , missingFilePath , eventKind ) ;
2017-08-14 23:59:51 +02:00
}
2017-07-25 01:57:49 +02:00
if ( eventKind === FileWatcherEventKind . Created && this . missingFilesMap . has ( missingFilePath ) ) {
this . missingFilesMap . delete ( missingFilePath ) ;
2017-08-23 22:34:41 +02:00
fileWatcher . close ( ) ;
2017-07-25 01:57:49 +02:00
// When a missing file is created, we should update the graph.
2018-02-12 23:55:58 +01:00
this . projectService . delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles ( this ) ;
2017-07-25 01:57:49 +02:00
}
2017-08-23 22:34:41 +02:00
} ,
2018-01-16 22:15:39 +01:00
PollingInterval . Medium ,
2017-08-23 22:34:41 +02:00
WatchType . MissingFilePath ,
this
2017-07-25 01:57:49 +02:00
) ;
return fileWatcher ;
}
2017-08-24 20:50:27 +02:00
private isWatchedMissingFile ( path : Path ) {
2017-07-14 05:08:57 +02:00
return this . missingFilesMap && this . missingFilesMap . has ( path ) ;
2017-06-21 00:54:43 +02:00
}
2018-05-15 22:55:26 +02:00
getScriptInfoForNormalizedPath ( fileName : NormalizedPath ) : ScriptInfo | undefined {
2017-11-03 23:28:28 +01:00
const scriptInfo = this . projectService . getScriptInfoForPath ( this . toPath ( fileName ) ) ;
2016-08-26 23:37:49 +02:00
if ( scriptInfo && ! scriptInfo . isAttached ( this ) ) {
return Errors . ThrowProjectDoesNotContainDocument ( fileName , this ) ;
}
2016-06-22 02:31:54 +02:00
return scriptInfo ;
}
2016-06-23 01:51:09 +02:00
getScriptInfo ( uncheckedFileName : string ) {
2017-10-12 20:48:25 +02:00
return this . projectService . getScriptInfo ( uncheckedFileName ) ;
2016-06-23 01:51:09 +02:00
}
2017-09-05 23:19:40 +02:00
filesToString ( writeProjectFileNames : boolean ) {
2016-06-22 02:31:54 +02:00
if ( ! this . program ) {
2017-08-31 20:35:55 +02:00
return "\tFiles (0)\n" ;
2016-06-22 02:31:54 +02:00
}
2017-08-31 20:35:55 +02:00
const sourceFiles = this . program . getSourceFiles ( ) ;
let strBuilder = ` \ tFiles ( ${ sourceFiles . length } ) \ n ` ;
2017-09-05 23:19:40 +02:00
if ( writeProjectFileNames ) {
for ( const file of sourceFiles ) {
strBuilder += ` \ t ${ file . fileName } \ n ` ;
}
2016-06-22 02:31:54 +02:00
}
return strBuilder ;
}
setCompilerOptions ( compilerOptions : CompilerOptions ) {
if ( compilerOptions ) {
compilerOptions . allowNonTsExtensions = true ;
2017-08-04 10:14:54 +02:00
const oldOptions = this . compilerOptions ;
2016-06-22 02:31:54 +02:00
this . compilerOptions = compilerOptions ;
2016-11-30 08:30:14 +01:00
this . setInternalCompilerOptionsForEmittingJsFiles ( ) ;
2017-08-04 10:14:54 +02:00
if ( changesAffectModuleResolution ( oldOptions , compilerOptions ) ) {
2018-04-13 01:47:40 +02:00
// reset cached unresolved imports if changes in compiler options affected module resolution
this . cachedUnresolvedImportsPerFile . clear ( ) ;
this . lastCachedUnresolvedImportsList = undefined ;
2017-08-04 10:14:54 +02:00
this . resolutionCache . clear ( ) ;
}
2016-06-22 02:31:54 +02:00
this . markAsDirty ( ) ;
}
}
2017-01-05 00:56:16 +01:00
/* @internal */
2016-08-26 23:37:49 +02:00
getChangesSinceVersion ( lastKnownVersion? : number ) : ProjectFilesWithTSDiagnostics {
2016-07-23 01:01:54 +02:00
this . updateGraph ( ) ;
2018-02-06 04:25:07 +01:00
const info : protocol.ProjectVersionInfo = {
2016-06-22 02:31:54 +02:00
projectName : this.getProjectName ( ) ,
2018-04-17 23:17:15 +02:00
version : this.projectProgramVersion ,
2016-08-02 02:12:15 +02:00
isInferred : this.projectKind === ProjectKind . Inferred ,
2017-08-24 20:50:27 +02:00
options : this.getCompilationSettings ( ) ,
2018-02-07 02:11:59 +01:00
languageServiceDisabled : ! this . languageServiceEnabled ,
lastFileExceededProgramSize : this.lastFileExceededProgramSize
2016-06-22 02:31:54 +02:00
} ;
2016-12-09 23:44:08 +01:00
const updatedFileNames = this . updatedFileNames ;
this . updatedFileNames = undefined ;
2016-06-23 01:51:09 +02:00
// check if requested version is the same that we have reported last time
2016-06-22 02:31:54 +02:00
if ( this . lastReportedFileNames && lastKnownVersion === this . lastReportedVersion ) {
2016-12-09 23:44:08 +01:00
// if current structure version is the same - return info without any changes
2018-04-17 23:17:15 +02:00
if ( this . projectProgramVersion === this . lastReportedVersion && ! updatedFileNames ) {
2017-05-24 00:38:03 +02:00
return { info , projectErrors : this.getGlobalProjectErrors ( ) } ;
2016-06-22 02:31:54 +02:00
}
2016-06-23 01:51:09 +02:00
// compute and return the difference
2016-06-22 02:31:54 +02:00
const lastReportedFileNames = this . lastReportedFileNames ;
2017-08-29 19:22:36 +02:00
const externalFiles = this . getExternalFiles ( ) . map ( f = > toNormalizedPath ( f ) ) ;
const currentFiles = arrayToSet ( this . getFileNames ( ) . concat ( externalFiles ) ) ;
2016-06-22 02:31:54 +02:00
const added : string [ ] = [ ] ;
const removed : string [ ] = [ ] ;
2017-05-25 23:00:11 +02:00
const updated : string [ ] = updatedFileNames ? arrayFrom ( updatedFileNames . keys ( ) ) : [ ] ;
2016-12-12 16:50:09 +01:00
2016-12-28 18:16:38 +01:00
forEachKey ( currentFiles , id = > {
2016-12-05 23:13:32 +01:00
if ( ! lastReportedFileNames . has ( id ) ) {
2016-06-22 02:31:54 +02:00
added . push ( id ) ;
}
2016-12-05 23:13:32 +01:00
} ) ;
2016-12-28 18:16:38 +01:00
forEachKey ( lastReportedFileNames , id = > {
2016-12-05 23:13:32 +01:00
if ( ! currentFiles . has ( id ) ) {
2016-06-22 02:31:54 +02:00
removed . push ( id ) ;
}
2016-12-05 23:13:32 +01:00
} ) ;
2016-06-22 02:31:54 +02:00
this . lastReportedFileNames = currentFiles ;
2018-04-17 23:17:15 +02:00
this . lastReportedVersion = this . projectProgramVersion ;
2017-05-24 00:38:03 +02:00
return { info , changes : { added , removed , updated } , projectErrors : this.getGlobalProjectErrors ( ) } ;
2016-06-22 02:31:54 +02:00
}
else {
// unknown version - return everything
const projectFileNames = this . getFileNames ( ) ;
2017-08-29 19:22:36 +02:00
const externalFiles = this . getExternalFiles ( ) . map ( f = > toNormalizedPath ( f ) ) ;
2017-09-20 19:52:56 +02:00
const allFiles = projectFileNames . concat ( externalFiles ) ;
this . lastReportedFileNames = arrayToSet ( allFiles ) ;
2018-04-17 23:17:15 +02:00
this . lastReportedVersion = this . projectProgramVersion ;
2017-09-20 19:52:56 +02:00
return { info , files : allFiles , projectErrors : this.getGlobalProjectErrors ( ) } ;
2016-06-22 02:31:54 +02:00
}
}
2016-06-23 02:02:08 +02:00
// remove a root file from project
2016-12-30 02:00:04 +01:00
protected removeRoot ( info : ScriptInfo ) : void {
2017-05-24 17:42:02 +02:00
orderedRemoveItem ( this . rootFiles , info ) ;
2017-06-28 22:15:34 +02:00
this . rootFilesMap . delete ( info . path ) ;
2016-06-23 02:02:08 +02:00
}
2017-12-22 02:04:27 +01:00
protected enableGlobalPlugins() {
const host = this . projectService . host ;
const options = this . getCompilationSettings ( ) ;
if ( ! host . require ) {
this . projectService . logger . info ( "Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded" ) ;
return ;
}
// Search our peer node_modules, then any globally-specified probe paths
// ../../.. to walk from X/node_modules/typescript/lib/tsserver.js to X/node_modules/
const searchPaths = [ combinePaths ( this . projectService . getExecutingFilePath ( ) , "../../.." ) , . . . this . projectService . pluginProbeLocations ] ;
if ( this . projectService . globalPlugins ) {
// Enable global plugins with synthetic configuration entries
for ( const globalPluginName of this . projectService . globalPlugins ) {
// Skip empty names from odd commandline parses
if ( ! globalPluginName ) continue ;
// Skip already-locally-loaded plugins
if ( options . plugins && options . plugins . some ( p = > p . name === globalPluginName ) ) continue ;
// Provide global: true so plugins can detect why they can't find their config
this . projectService . logger . info ( ` Loading global plugin ${ globalPluginName } ` ) ;
this . enablePlugin ( { name : globalPluginName , global : true } as PluginImport , searchPaths ) ;
}
}
}
protected enablePlugin ( pluginConfigEntry : PluginImport , searchPaths : string [ ] ) {
this . projectService . logger . info ( ` Enabling plugin ${ pluginConfigEntry . name } from candidate paths: ${ searchPaths . join ( "," ) } ` ) ;
const log = ( message : string ) = > {
this . projectService . logger . info ( message ) ;
} ;
2018-05-03 00:37:22 +02:00
const resolvedModule = firstDefined ( searchPaths , searchPath = >
< PluginModuleFactory | undefined > Project . resolveModule ( pluginConfigEntry . name , searchPath , this . projectService . host , log ) ) ;
if ( resolvedModule ) {
this . enableProxy ( resolvedModule , pluginConfigEntry ) ;
}
else {
this . projectService . logger . info ( ` Couldn't find ${ pluginConfigEntry . name } ` ) ;
2017-12-22 02:04:27 +01:00
}
}
2018-05-07 21:38:38 +02:00
/** Starts a new check for diagnostics. Call this if some file has updated that would cause diagnostics to be changed. */
refreshDiagnostics() {
this . projectService . sendProjectsUpdatedInBackgroundEvent ( ) ;
}
2017-12-22 02:04:27 +01:00
private enableProxy ( pluginModuleFactory : PluginModuleFactory , configEntry : PluginImport ) {
try {
if ( typeof pluginModuleFactory !== "function" ) {
this . projectService . logger . info ( ` Skipped loading plugin ${ configEntry . name } because it did expose a proper factory function ` ) ;
return ;
}
const info : PluginCreateInfo = {
config : configEntry ,
project : this ,
languageService : this.languageService ,
languageServiceHost : this ,
serverHost : this.projectService.host
} ;
const pluginModule = pluginModuleFactory ( { typescript : ts } ) ;
const newLS = pluginModule . create ( info ) ;
for ( const k of Object . keys ( this . languageService ) ) {
if ( ! ( k in newLS ) ) {
this . projectService . logger . info ( ` Plugin activation warning: Missing proxied method ${ k } in created LS. Patching. ` ) ;
( newLS as any ) [ k ] = ( this . languageService as any ) [ k ] ;
}
}
this . projectService . logger . info ( ` Plugin validation succeded ` ) ;
this . languageService = newLS ;
this . plugins . push ( pluginModule ) ;
}
catch ( e ) {
this . projectService . logger . info ( ` Plugin activation failed: ${ e } ` ) ;
}
}
2016-06-22 02:31:54 +02:00
}
2017-05-25 22:30:27 +02:00
/ * *
* If a file is opened and no tsconfig ( or jsconfig ) is found ,
* the file and its imports / references are put into an InferredProject .
* /
2016-06-23 01:51:09 +02:00
export class InferredProject extends Project {
2017-07-20 17:54:47 +02:00
private static readonly newName = ( ( ) = > {
2016-11-18 00:12:32 +01:00
let nextId = 1 ;
return ( ) = > {
const id = nextId ;
nextId ++ ;
return makeInferredProjectName ( id ) ;
2017-02-10 21:24:10 +01:00
} ;
2016-11-18 00:12:32 +01:00
} ) ( ) ;
2016-06-23 01:51:09 +02:00
2016-12-28 23:46:58 +01:00
private _isJsInferredProject = false ;
2016-12-29 19:26:34 +01:00
2016-12-30 02:00:04 +01:00
toggleJsInferredProject ( isJsInferredProject : boolean ) {
if ( isJsInferredProject !== this . _isJsInferredProject ) {
this . _isJsInferredProject = isJsInferredProject ;
this . setCompilerOptions ( ) ;
}
2016-12-28 23:46:58 +01:00
}
2016-12-30 02:00:04 +01:00
setCompilerOptions ( options? : CompilerOptions ) {
// Avoid manipulating the given options directly
2017-08-24 20:50:27 +02:00
const newOptions = options ? cloneCompilerOptions ( options ) : this . getCompilationSettings ( ) ;
2016-12-28 23:46:58 +01:00
if ( ! newOptions ) {
return ;
}
if ( this . _isJsInferredProject && typeof newOptions . maxNodeModuleJsDepth !== "number" ) {
newOptions . maxNodeModuleJsDepth = 2 ;
}
2016-12-30 02:00:04 +01:00
else if ( ! this . _isJsInferredProject ) {
newOptions . maxNodeModuleJsDepth = undefined ;
}
2016-12-28 23:46:58 +01:00
newOptions . allowJs = true ;
super . setCompilerOptions ( newOptions ) ;
}
2017-10-11 23:21:09 +02:00
/** this is canonical project root path */
readonly projectRootPath : string | undefined ;
2018-04-20 22:38:15 +02:00
/*@internal*/
/** stored only if their is no projectRootPath and this isnt single inferred project */
readonly canonicalCurrentDirectory : string | undefined ;
2017-10-03 01:14:42 +02:00
/*@internal*/
2017-09-26 22:34:56 +02:00
constructor (
projectService : ProjectService ,
documentRegistry : DocumentRegistry ,
compilerOptions : CompilerOptions ,
2017-10-24 23:03:58 +02:00
projectRootPath : NormalizedPath | undefined ,
2017-10-05 03:45:59 +02:00
currentDirectory : string | undefined ) {
2016-11-18 00:12:32 +01:00
super ( InferredProject . newName ( ) ,
ProjectKind . Inferred ,
2016-06-23 01:51:09 +02:00
projectService ,
documentRegistry ,
2018-05-22 23:46:57 +02:00
// TODO: GH#18217
/*files*/ undefined ! ,
2018-02-07 02:11:59 +01:00
/*lastFileExceededProgramSize*/ undefined ,
2016-08-24 01:11:52 +02:00
compilerOptions ,
2017-07-12 01:10:44 +02:00
/*compileOnSaveEnabled*/ false ,
2017-09-26 22:34:56 +02:00
projectService . host ,
2017-09-27 02:29:53 +02:00
currentDirectory ) ;
2017-10-11 23:21:09 +02:00
this . projectRootPath = projectRootPath && projectService . toCanonicalFileName ( projectRootPath ) ;
2018-04-20 22:38:15 +02:00
if ( ! projectRootPath && ! projectService . useSingleInferredProject ) {
this . canonicalCurrentDirectory = projectService . toCanonicalFileName ( this . currentDirectory ) ;
}
2017-12-22 02:04:27 +01:00
this . enableGlobalPlugins ( ) ;
2016-06-23 01:51:09 +02:00
}
2016-12-30 02:00:04 +01:00
addRoot ( info : ScriptInfo ) {
2017-10-24 23:03:58 +02:00
Debug . assert ( info . isScriptOpen ( ) ) ;
2018-04-18 00:23:20 +02:00
this . projectService . startWatchingConfigFilesForInferredProjectRoot ( info ) ;
2016-12-30 02:00:04 +01:00
if ( ! this . _isJsInferredProject && info . isJavaScript ( ) ) {
this . toggleJsInferredProject ( /*isJsInferredProject*/ true ) ;
}
super . addRoot ( info ) ;
}
removeRoot ( info : ScriptInfo ) {
2017-08-23 22:34:41 +02:00
this . projectService . stopWatchingConfigFilesForInferredProjectRoot ( info ) ;
2017-07-06 03:03:11 +02:00
super . removeRoot ( info ) ;
2016-12-30 02:00:04 +01:00
if ( this . _isJsInferredProject && info . isJavaScript ( ) ) {
2017-08-21 20:39:04 +02:00
if ( every ( this . getRootScriptInfos ( ) , rootInfo = > ! rootInfo . isJavaScript ( ) ) ) {
2016-12-30 02:00:04 +01:00
this . toggleJsInferredProject ( /*isJsInferredProject*/ false ) ;
}
}
}
2018-04-20 22:38:15 +02:00
/*@internal*/
isOrphan() {
return ! this . hasRoots ( ) ;
}
2017-07-15 07:35:07 +02:00
isProjectWithSingleRoot() {
2017-08-12 05:07:47 +02:00
// - when useSingleInferredProject is not set and projectRootPath is not set,
// we can guarantee that this will be the only root
2017-07-15 07:35:07 +02:00
// - other wise it has single root if it has single root script info
2017-08-12 05:07:47 +02:00
return ( ! this . projectRootPath && ! this . projectService . useSingleInferredProject ) ||
this . getRootScriptInfos ( ) . length === 1 ;
2017-07-15 07:35:07 +02:00
}
2016-06-23 01:51:09 +02:00
close() {
2017-08-23 22:34:41 +02:00
forEach ( this . getRootScriptInfos ( ) , info = > this . projectService . stopWatchingConfigFilesForInferredProjectRoot ( info ) ) ;
2016-06-23 01:51:09 +02:00
super . close ( ) ;
}
2016-08-24 00:15:12 +02:00
2016-11-19 02:46:06 +01:00
getTypeAcquisition ( ) : TypeAcquisition {
2016-08-24 00:15:12 +02:00
return {
2016-11-19 02:46:06 +01:00
enable : allRootFilesAreJsOrDts ( this ) ,
2016-08-24 00:15:12 +02:00
include : [ ] ,
exclude : [ ]
} ;
}
2016-06-23 01:51:09 +02:00
}
2017-05-25 22:30:27 +02:00
/ * *
* If a file is opened , the server will look for a tsconfig ( or jsconfig )
* and if successfull create a ConfiguredProject for it .
* Otherwise it will create an InferredProject .
* /
2016-06-23 01:51:09 +02:00
export class ConfiguredProject extends Project {
2016-11-19 02:46:06 +01:00
private typeAcquisition : TypeAcquisition ;
2017-07-14 05:08:57 +02:00
/* @internal */
2018-05-22 23:46:57 +02:00
configFileWatcher : FileWatcher | undefined ;
2017-08-07 23:47:32 +02:00
private directoriesWatchedForWildcards : Map < WildcardDirectoryWatcher > | undefined ;
2016-11-23 21:34:00 +01:00
readonly canonicalConfigFilePath : NormalizedPath ;
2016-09-02 00:17:38 +02:00
2017-07-13 08:15:03 +02:00
/* @internal */
2017-11-06 23:39:23 +01:00
pendingReload : ConfigFileProgramReloadLevel ;
2017-07-13 08:15:03 +02:00
2017-07-11 22:38:12 +02:00
/*@internal*/
2018-05-22 23:46:57 +02:00
configFileSpecs : ConfigFileSpecs | undefined ;
2017-07-11 22:38:12 +02:00
2017-10-12 20:48:25 +02:00
/** Ref count to the project when opened from external project */
private externalProjectRefCount = 0 ;
2016-06-22 02:31:54 +02:00
2018-05-22 23:46:57 +02:00
private projectErrors : Diagnostic [ ] | undefined ;
2017-08-18 20:55:47 +02:00
2017-10-03 01:14:42 +02:00
/*@internal*/
2017-04-14 01:16:57 +02:00
constructor ( configFileName : NormalizedPath ,
2016-06-22 02:31:54 +02:00
projectService : ProjectService ,
2017-07-13 22:08:59 +02:00
documentRegistry : DocumentRegistry ,
2016-06-22 02:31:54 +02:00
hasExplicitListOfFiles : boolean ,
compilerOptions : CompilerOptions ,
2018-02-07 02:11:59 +01:00
lastFileExceededProgramSize : string | undefined ,
2017-07-12 01:10:44 +02:00
public compileOnSaveEnabled : boolean ,
2018-05-08 00:12:50 +02:00
cachedDirectoryStructureHost : CachedDirectoryStructureHost ,
private projectReferences : ReadonlyArray < ProjectReference > | undefined ) {
2017-09-26 22:34:56 +02:00
super ( configFileName ,
ProjectKind . Configured ,
projectService ,
documentRegistry ,
hasExplicitListOfFiles ,
2018-02-07 02:11:59 +01:00
lastFileExceededProgramSize ,
2017-09-26 22:34:56 +02:00
compilerOptions ,
compileOnSaveEnabled ,
cachedDirectoryStructureHost ,
getDirectoryPath ( configFileName ) ) ;
2016-11-23 21:34:00 +01:00
this . canonicalConfigFilePath = asNormalizedPath ( projectService . toCanonicalFileName ( configFileName ) ) ;
2017-02-14 22:35:16 +01:00
this . enablePlugins ( ) ;
2016-11-18 00:12:32 +01:00
}
2017-07-13 08:15:03 +02:00
/ * *
2017-08-18 20:55:47 +02:00
* If the project has reload from disk pending , it reloads ( and then updates graph as part of that ) instead of just updating the graph
2017-07-13 08:15:03 +02:00
* @returns : true if set of files in the project stays the same and false - otherwise .
* /
updateGraph ( ) : boolean {
2017-11-06 23:39:23 +01:00
const reloadLevel = this . pendingReload ;
this . pendingReload = ConfigFileProgramReloadLevel . None ;
switch ( reloadLevel ) {
case ConfigFileProgramReloadLevel . Partial :
return this . projectService . reloadFileNamesOfConfiguredProject ( this ) ;
case ConfigFileProgramReloadLevel . Full :
this . projectService . reloadConfiguredProject ( this ) ;
return true ;
default :
return super . updateGraph ( ) ;
2017-07-13 08:15:03 +02:00
}
}
2017-08-14 23:59:51 +02:00
/*@internal*/
2017-09-26 20:05:52 +02:00
getCachedDirectoryStructureHost() {
return this . directoryStructureHost as CachedDirectoryStructureHost ;
2017-07-12 01:10:44 +02:00
}
2016-11-18 00:12:32 +01:00
getConfigFilePath() {
2017-07-14 05:08:57 +02:00
return asNormalizedPath ( this . getProjectName ( ) ) ;
2016-06-22 02:31:54 +02:00
}
2018-07-06 00:39:03 +02:00
getProjectReferences ( ) : ReadonlyArray < ProjectReference > {
return this . projectReferences || emptyArray ;
2018-05-08 00:12:50 +02:00
}
updateReferences ( refs : ReadonlyArray < ProjectReference > | undefined ) {
this . projectReferences = refs ;
}
2017-02-14 22:35:16 +01:00
enablePlugins() {
const host = this . projectService . host ;
2017-08-24 20:50:27 +02:00
const options = this . getCompilationSettings ( ) ;
2017-02-14 22:35:16 +01:00
if ( ! host . require ) {
this . projectService . logger . info ( "Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded" ) ;
return ;
}
2017-04-14 01:16:57 +02:00
// Search our peer node_modules, then any globally-specified probe paths
// ../../.. to walk from X/node_modules/typescript/lib/tsserver.js to X/node_modules/
2017-09-27 02:29:53 +02:00
const searchPaths = [ combinePaths ( this . projectService . getExecutingFilePath ( ) , "../../.." ) , . . . this . projectService . pluginProbeLocations ] ;
2017-04-14 01:16:57 +02:00
2017-05-18 04:52:57 +02:00
if ( this . projectService . allowLocalPluginLoads ) {
const local = getDirectoryPath ( this . canonicalConfigFilePath ) ;
this . projectService . logger . info ( ` Local plugin loading enabled; adding ${ local } to search paths ` ) ;
searchPaths . unshift ( local ) ;
}
2017-04-14 01:16:57 +02:00
// Enable tsconfig-specified plugins
2017-04-14 01:02:24 +02:00
if ( options . plugins ) {
for ( const pluginConfigEntry of options . plugins ) {
this . enablePlugin ( pluginConfigEntry , searchPaths ) ;
}
}
2017-12-22 02:04:27 +01:00
this . enableGlobalPlugins ( ) ;
2017-02-14 22:35:16 +01:00
}
2017-08-18 20:55:47 +02:00
/ * *
* Get the errors that dont have any file name associated
* /
getGlobalProjectErrors ( ) : ReadonlyArray < Diagnostic > {
2017-10-04 22:25:56 +02:00
return filter ( this . projectErrors , diagnostic = > ! diagnostic . file ) || emptyArray ;
2017-08-18 20:55:47 +02:00
}
/ * *
* Get all the project errors
* /
getAllProjectErrors ( ) : ReadonlyArray < Diagnostic > {
2017-10-04 22:25:56 +02:00
return this . projectErrors || emptyArray ;
2017-08-18 20:55:47 +02:00
}
2016-08-26 23:37:49 +02:00
setProjectErrors ( projectErrors : Diagnostic [ ] ) {
this . projectErrors = projectErrors ;
}
2016-11-19 02:46:06 +01:00
setTypeAcquisition ( newTypeAcquisition : TypeAcquisition ) : void {
2017-12-06 22:17:21 +01:00
this . typeAcquisition = this . removeLocalTypingsFromTypeAcquisition ( newTypeAcquisition ) ;
2016-08-24 00:15:12 +02:00
}
2016-11-19 02:46:06 +01:00
getTypeAcquisition() {
return this . typeAcquisition ;
2016-08-12 20:04:43 +02:00
}
2017-08-18 20:55:47 +02:00
/*@internal*/
2017-07-07 23:43:00 +02:00
watchWildcards ( wildcardDirectories : Map < WatchDirectoryFlags > ) {
2017-08-13 00:31:19 +02:00
updateWatchingWildcardDirectories (
2017-08-07 23:47:32 +02:00
this . directoriesWatchedForWildcards || ( this . directoriesWatchedForWildcards = createMap ( ) ) ,
2017-07-25 01:57:49 +02:00
wildcardDirectories ,
// Create new directory watcher
2017-08-24 20:50:27 +02:00
( directory , flags ) = > this . projectService . watchWildcardDirectory ( directory as Path , flags , this ) ,
2017-07-14 05:08:57 +02:00
) ;
}
2017-08-18 20:55:47 +02:00
/*@internal*/
2017-08-23 22:34:41 +02:00
stopWatchingWildCards() {
2017-08-07 23:47:32 +02:00
if ( this . directoriesWatchedForWildcards ) {
2017-08-23 22:34:41 +02:00
clearMap ( this . directoriesWatchedForWildcards , closeFileWatcherOf ) ;
2017-08-07 23:47:32 +02:00
this . directoriesWatchedForWildcards = undefined ;
}
2017-07-14 05:08:57 +02:00
}
2016-06-22 02:31:54 +02:00
close() {
2017-07-07 23:43:00 +02:00
if ( this . configFileWatcher ) {
2017-08-23 22:34:41 +02:00
this . configFileWatcher . close ( ) ;
2017-07-07 23:43:00 +02:00
this . configFileWatcher = undefined ;
2016-06-22 02:31:54 +02:00
}
2017-08-23 22:34:41 +02:00
this . stopWatchingWildCards ( ) ;
2017-10-04 22:25:56 +02:00
this . projectErrors = undefined ;
this . configFileSpecs = undefined ;
2017-10-09 23:25:48 +02:00
super . close ( ) ;
2016-06-22 02:31:54 +02:00
}
2017-10-12 20:48:25 +02:00
/* @internal */
addExternalProjectReference() {
this . externalProjectRefCount ++ ;
2016-06-22 02:31:54 +02:00
}
2017-10-12 20:48:25 +02:00
/* @internal */
deleteExternalProjectReference() {
this . externalProjectRefCount -- ;
2016-06-22 02:31:54 +02:00
}
2016-09-20 01:47:15 +02:00
2017-10-12 20:48:25 +02:00
/** Returns true if the project is needed by any of the open script info/external project */
/* @internal */
2017-09-01 02:58:09 +02:00
hasOpenRef() {
2017-10-12 20:48:25 +02:00
if ( ! ! this . externalProjectRefCount ) {
return true ;
}
// Closed project doesnt have any reference
if ( this . isClosed ( ) ) {
return false ;
}
const configFileExistenceInfo = this . projectService . getConfigFileExistenceInfo ( this ) ;
if ( this . projectService . hasPendingProjectUpdate ( this ) ) {
// If there is pending update for this project,
// we dont know if this project would be needed by any of the open files impacted by this config file
// In that case keep the project alive if there are open files impacted by this project
return ! ! configFileExistenceInfo . openFilesImpactedByConfigFile . size ;
}
// If there is no pending update for this project,
// We know exact set of open files that get impacted by this configured project as the files in the project
// The project is referenced only if open files impacted by this project are present in this project
return forEachEntry (
configFileExistenceInfo . openFilesImpactedByConfigFile ,
2018-05-22 23:46:57 +02:00
( _value , infoPath ) = > this . containsScriptInfo ( this . projectService . getScriptInfoForPath ( infoPath as Path ) ! )
2017-10-12 20:48:25 +02:00
) || false ;
2017-09-01 02:58:09 +02:00
}
2016-09-20 01:47:15 +02:00
getEffectiveTypeRoots() {
2017-09-26 20:05:52 +02:00
return getEffectiveTypeRoots ( this . getCompilationSettings ( ) , this . directoryStructureHost ) || [ ] ;
2016-09-20 01:47:15 +02:00
}
2017-08-07 23:47:32 +02:00
/*@internal*/
updateErrorOnNoInputFiles ( hasFileNames : boolean ) {
if ( hasFileNames ) {
2018-05-22 23:46:57 +02:00
filterMutate ( this . projectErrors ! , error = > ! isErrorNoInputFiles ( error ) ) ; // TODO: GH#18217
2017-08-07 23:47:32 +02:00
}
2018-05-22 23:46:57 +02:00
else if ( ! this . configFileSpecs ! . filesSpecs && ! some ( this . projectErrors , isErrorNoInputFiles ) ) { // TODO: GH#18217
this . projectErrors ! . push ( getErrorForNoInputFiles ( this . configFileSpecs ! , this . getConfigFilePath ( ) ) ) ;
2017-08-07 23:47:32 +02:00
}
}
2016-06-22 02:31:54 +02:00
}
2017-05-25 22:30:27 +02:00
/ * *
* Project whose configuration is handled externally , such as in a '.csproj' .
* These are created only if a host explicitly calls ` openExternalProject ` .
* /
2016-06-23 01:51:09 +02:00
export class ExternalProject extends Project {
2017-07-28 01:07:50 +02:00
excludedFiles : ReadonlyArray < NormalizedPath > = [ ] ;
2016-11-19 02:46:06 +01:00
private typeAcquisition : TypeAcquisition ;
2017-10-03 01:14:42 +02:00
/*@internal*/
2017-03-17 20:56:31 +01:00
constructor ( public externalProjectName : string ,
2016-06-22 02:31:54 +02:00
projectService : ProjectService ,
2017-07-13 22:08:59 +02:00
documentRegistry : DocumentRegistry ,
2016-06-22 02:31:54 +02:00
compilerOptions : CompilerOptions ,
2018-02-07 02:11:59 +01:00
lastFileExceededProgramSize : string | undefined ,
2016-09-14 01:20:42 +02:00
public compileOnSaveEnabled : boolean ,
2017-09-27 02:29:53 +02:00
projectFilePath? : string ) {
2017-09-26 22:34:56 +02:00
super ( externalProjectName ,
ProjectKind . External ,
projectService ,
documentRegistry ,
/*hasExplicitListOfFiles*/ true ,
2018-02-07 02:11:59 +01:00
lastFileExceededProgramSize ,
2017-09-27 02:29:53 +02:00
compilerOptions ,
2017-09-26 22:34:56 +02:00
compileOnSaveEnabled ,
projectService . host ,
getDirectoryPath ( projectFilePath || normalizeSlashes ( externalProjectName ) ) ) ;
2016-08-24 00:15:12 +02:00
}
2017-07-28 01:07:50 +02:00
getExcludedFiles() {
return this . excludedFiles ;
2016-08-24 00:15:12 +02:00
}
2016-11-19 02:46:06 +01:00
getTypeAcquisition() {
return this . typeAcquisition ;
2016-08-24 00:15:12 +02:00
}
2016-11-19 02:46:06 +01:00
setTypeAcquisition ( newTypeAcquisition : TypeAcquisition ) : void {
2017-11-16 21:37:40 +01:00
Debug . assert ( ! ! newTypeAcquisition , "newTypeAcquisition may not be null/undefined" ) ;
Debug . assert ( ! ! newTypeAcquisition . include , "newTypeAcquisition.include may not be null/undefined" ) ;
Debug . assert ( ! ! newTypeAcquisition . exclude , "newTypeAcquisition.exclude may not be null/undefined" ) ;
Debug . assert ( typeof newTypeAcquisition . enable === "boolean" , "newTypeAcquisition.enable may not be null/undefined" ) ;
2017-12-06 22:17:21 +01:00
this . typeAcquisition = this . removeLocalTypingsFromTypeAcquisition ( newTypeAcquisition ) ;
2016-06-22 02:31:54 +02:00
}
}
2017-05-24 00:38:03 +02:00
}