-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Watch improvements in tsserver #17269
Changes from 42 commits
21ad26b
ae33ae8
75698a8
f154910
0e44367
2a63827
96ffd53
6bd42b8
df6f75b
9ff9476
62871cc
48c6513
19a6a00
68def1b
029b1f2
f338a70
8fedcf7
e568976
048e67c
0365901
404aa8f
71d79c6
f12980d
00011a5
0572b15
62663a1
dcbd7b1
62ef6b1
2439e7a
ff34a77
1155c37
ae87838
802e283
499fabc
273569f
94a589b
ef5935b
e068475
6237b22
9b18f7b
85b9254
69e5abd
c814d8e
2dd6aed
89c61e7
bb91b32
46e3d1c
031a637
0d5e6c9
2762232
65a6ee0
d55150c
8dc6248
7474ba7
6385f7e
65521bc
27988bf
02b8a7d
f723beb
b071a86
8db05c2
594482d
d0a23bb
59d07dc
9895082
f1b1b12
136b091
6bf9133
a99c04e
b66b752
da0d374
e639ceb
8deef58
c425128
d217bec
60e2e68
84b2e23
3908325
7173da2
e500be2
6227a36
55931c4
e65df12
e711238
3b85f3f
ea95f3b
9e570c3
4c79033
5aafd3f
17565d8
10ea5bf
254e393
a3b9467
16cf7c4
d7ce95d
345f36d
2b97b2c
8d5d4c2
9e5e20c
13aafa2
6c61293
7b2bab5
54f64a1
0ff160f
e6eede1
2a5d954
680994e
c8e711c
29e93c3
b179cd1
67f9533
de28d02
5739b68
fdb104b
aea8630
b536f9d
4f7c0e5
cf72f2a
23acff5
14febe2
38f3a2b
68d3605
9e08cae
835153b
898559b
7f969e8
4bb4711
8ac01d7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1401,28 +1401,30 @@ namespace ts { | |
Debug.assert((json === undefined && sourceFile !== undefined) || (json !== undefined && sourceFile === undefined)); | ||
const errors: Diagnostic[] = []; | ||
|
||
const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors); | ||
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); | ||
const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, getCanonicalFileName, resolutionStack, errors); | ||
const { raw } = parsedConfig; | ||
const options = extend(existingOptions, parsedConfig.options || {}); | ||
options.configFilePath = configFileName; | ||
setConfigFileInOptions(options, sourceFile); | ||
const { fileNames, wildcardDirectories } = getFileNames(); | ||
const { fileNames, wildcardDirectories, spec } = getFileNames(); | ||
return { | ||
options, | ||
fileNames, | ||
typeAcquisition: parsedConfig.typeAcquisition || getDefaultTypeAcquisition(), | ||
raw, | ||
errors, | ||
wildcardDirectories, | ||
compileOnSave: !!raw.compileOnSave | ||
compileOnSave: !!raw.compileOnSave, | ||
configFileSpecs: spec | ||
}; | ||
|
||
function getFileNames(): ExpandResult { | ||
let fileNames: ReadonlyArray<string>; | ||
let filesSpecs: ReadonlyArray<string>; | ||
if (hasProperty(raw, "files")) { | ||
if (isArray(raw["files"])) { | ||
fileNames = <ReadonlyArray<string>>raw["files"]; | ||
if (fileNames.length === 0) { | ||
filesSpecs = <ReadonlyArray<string>>raw["files"]; | ||
if (filesSpecs.length === 0) { | ||
createCompilerDiagnosticOnlyIfJson(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json"); | ||
} | ||
} | ||
|
@@ -1457,19 +1459,14 @@ namespace ts { | |
} | ||
} | ||
|
||
if (fileNames === undefined && includeSpecs === undefined) { | ||
if (filesSpecs === undefined && includeSpecs === undefined) { | ||
includeSpecs = ["**/*"]; | ||
} | ||
|
||
const result = matchFileNames(fileNames, includeSpecs, excludeSpecs, basePath, options, host, errors, extraFileExtensions, sourceFile); | ||
const result = matchFileNames(filesSpecs, includeSpecs, excludeSpecs, basePath, options, host, errors, extraFileExtensions, sourceFile); | ||
|
||
if (result.fileNames.length === 0 && !hasProperty(raw, "files") && resolutionStack.length === 0) { | ||
errors.push( | ||
createCompilerDiagnostic( | ||
Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, | ||
configFileName || "tsconfig.json", | ||
JSON.stringify(includeSpecs || []), | ||
JSON.stringify(excludeSpecs || []))); | ||
errors.push(getErrorForNoInputFiles(result.spec, configFileName)); | ||
} | ||
|
||
return result; | ||
|
@@ -1482,6 +1479,20 @@ namespace ts { | |
} | ||
} | ||
|
||
/*@internal*/ | ||
export function isErrorNoInputFiles(error: Diagnostic) { | ||
return error.code === Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code; | ||
} | ||
|
||
/*@internal*/ | ||
export function getErrorForNoInputFiles({ includeSpecs, excludeSpecs }: ConfigFileSpecs, configFileName: string | undefined) { | ||
return createCompilerDiagnostic( | ||
Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, | ||
configFileName || "tsconfig.json", | ||
JSON.stringify(includeSpecs || []), | ||
JSON.stringify(excludeSpecs || [])); | ||
} | ||
|
||
interface ParsedTsconfig { | ||
raw: any; | ||
options?: CompilerOptions; | ||
|
@@ -1503,11 +1514,11 @@ namespace ts { | |
host: ParseConfigHost, | ||
basePath: string, | ||
configFileName: string, | ||
getCanonicalFileName: (fileName: string) => string, | ||
resolutionStack: Path[], | ||
errors: Push<Diagnostic>, | ||
): ParsedTsconfig { | ||
basePath = normalizeSlashes(basePath); | ||
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); | ||
const resolvedPath = toPath(configFileName || "", basePath, getCanonicalFileName); | ||
|
||
if (resolutionStack.indexOf(resolvedPath) >= 0) { | ||
|
@@ -1689,7 +1700,7 @@ namespace ts { | |
|
||
const extendedDirname = getDirectoryPath(extendedConfigPath); | ||
const extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, extendedDirname, | ||
getBaseFileName(extendedConfigPath), resolutionStack, errors); | ||
getBaseFileName(extendedConfigPath), getCanonicalFileName, resolutionStack, errors); | ||
if (sourceFile) { | ||
sourceFile.extendedSourceFiles.push(...extendedResult.extendedSourceFiles); | ||
} | ||
|
@@ -1920,29 +1931,62 @@ namespace ts { | |
/** | ||
* Expands an array of file specifications. | ||
* | ||
* @param fileNames The literal file names to include. | ||
* @param include The wildcard file specifications to include. | ||
* @param exclude The wildcard file specifications to exclude. | ||
* @param filesSpecs The literal file names to include. | ||
* @param includeSpecs The wildcard file specifications to include. | ||
* @param excludeSpecs The wildcard file specifications to exclude. | ||
* @param basePath The base path for any relative file specifications. | ||
* @param options Compiler options. | ||
* @param host The host used to resolve files and directories. | ||
* @param errors An array for diagnostic reporting. | ||
*/ | ||
function matchFileNames( | ||
fileNames: ReadonlyArray<string>, | ||
include: ReadonlyArray<string>, | ||
exclude: ReadonlyArray<string>, | ||
filesSpecs: ReadonlyArray<string>, | ||
includeSpecs: ReadonlyArray<string>, | ||
excludeSpecs: ReadonlyArray<string>, | ||
basePath: string, | ||
options: CompilerOptions, | ||
host: ParseConfigHost, | ||
errors: Push<Diagnostic>, | ||
extraFileExtensions: ReadonlyArray<JsFileExtensionInfo>, | ||
jsonSourceFile: JsonSourceFile): ExpandResult { | ||
jsonSourceFile: JsonSourceFile | ||
): ExpandResult { | ||
basePath = normalizePath(basePath); | ||
let validatedIncludeSpecs: ReadonlyArray<string>, validatedExcludeSpecs: ReadonlyArray<string>; | ||
|
||
// The exclude spec list is converted into a regular expression, which allows us to quickly | ||
// test whether a file or directory should be excluded before recursively traversing the | ||
// file system. | ||
|
||
if (includeSpecs) { | ||
validatedIncludeSpecs = validateSpecs(includeSpecs, errors, /*allowTrailingRecursion*/ false, jsonSourceFile, "include"); | ||
} | ||
|
||
if (excludeSpecs) { | ||
validatedExcludeSpecs = validateSpecs(excludeSpecs, errors, /*allowTrailingRecursion*/ true, jsonSourceFile, "exclude"); | ||
} | ||
|
||
// Wildcard directories (provided as part of a wildcard path) are stored in a | ||
// file map that marks whether it was a regular wildcard match (with a `*` or `?` token), | ||
// or a recursive directory. This information is used by filesystem watchers to monitor for | ||
// new entries in these paths. | ||
const wildcardDirectories = getWildcardDirectories(validatedIncludeSpecs, validatedExcludeSpecs, basePath, host.useCaseSensitiveFileNames); | ||
|
||
const spec: ConfigFileSpecs = { filesSpecs, includeSpecs, excludeSpecs, validatedIncludeSpecs, validatedExcludeSpecs, wildcardDirectories }; | ||
return getFileNamesFromConfigSpecs(spec, basePath, options, host, extraFileExtensions); | ||
} | ||
|
||
/** | ||
* Gets the file names from the provided config file specs that contain, files, include, exclude and | ||
* other properties needed to resolve the file names | ||
* @param spec The config file specs extracted with file names to include, wildcards to include/exclude and other details | ||
* @param basePath The base path for any relative file specifications. | ||
* @param options Compiler options. | ||
* @param host The host used to resolve files and directories. | ||
* @param extraFileExtensions optionaly file extra file extension information from host | ||
*/ | ||
export function getFileNamesFromConfigSpecs(spec: ConfigFileSpecs, basePath: string, options: CompilerOptions, host: ParseConfigHost, extraFileExtensions: ReadonlyArray<JsFileExtensionInfo>): ExpandResult { | ||
basePath = normalizePath(basePath); | ||
|
||
const keyMapper = host.useCaseSensitiveFileNames ? caseSensitiveKeyMapper : caseInsensitiveKeyMapper; | ||
|
||
// Literal file names (provided via the "files" array in tsconfig.json) are stored in a | ||
|
@@ -1955,35 +1999,23 @@ namespace ts { | |
// via wildcard, and to handle extension priority. | ||
const wildcardFileMap = createMap<string>(); | ||
|
||
if (include) { | ||
include = validateSpecs(include, errors, /*allowTrailingRecursion*/ false, jsonSourceFile, "include"); | ||
} | ||
|
||
if (exclude) { | ||
exclude = validateSpecs(exclude, errors, /*allowTrailingRecursion*/ true, jsonSourceFile, "exclude"); | ||
} | ||
|
||
// Wildcard directories (provided as part of a wildcard path) are stored in a | ||
// file map that marks whether it was a regular wildcard match (with a `*` or `?` token), | ||
// or a recursive directory. This information is used by filesystem watchers to monitor for | ||
// new entries in these paths. | ||
const wildcardDirectories = getWildcardDirectories(include, exclude, basePath, host.useCaseSensitiveFileNames); | ||
const { filesSpecs, validatedIncludeSpecs, validatedExcludeSpecs, wildcardDirectories } = spec; | ||
|
||
// Rather than requery this for each file and filespec, we query the supported extensions | ||
// once and store it on the expansion context. | ||
const supportedExtensions = getSupportedExtensions(options, extraFileExtensions); | ||
|
||
// Literal files are always included verbatim. An "include" or "exclude" specification cannot | ||
// remove a literal file. | ||
if (fileNames) { | ||
for (const fileName of fileNames) { | ||
if (filesSpecs) { | ||
for (const fileName of filesSpecs) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should |
||
const file = combinePaths(basePath, fileName); | ||
literalFileMap.set(keyMapper(file), file); | ||
} | ||
} | ||
|
||
if (include && include.length > 0) { | ||
for (const file of host.readDirectory(basePath, supportedExtensions, exclude, include, /*depth*/ undefined)) { | ||
if (validatedIncludeSpecs && validatedIncludeSpecs.length > 0) { | ||
for (const file of host.readDirectory(basePath, supportedExtensions, validatedExcludeSpecs, validatedIncludeSpecs, /*depth*/ undefined)) { | ||
// If we have already included a literal or wildcard path with a | ||
// higher priority extension, we should skip this file. | ||
// | ||
|
@@ -2011,11 +2043,12 @@ namespace ts { | |
const wildcardFiles = arrayFrom(wildcardFileMap.values()); | ||
return { | ||
fileNames: literalFiles.concat(wildcardFiles), | ||
wildcardDirectories | ||
wildcardDirectories, | ||
spec | ||
}; | ||
} | ||
|
||
function validateSpecs(specs: ReadonlyArray<string>, errors: Push<Diagnostic>, allowTrailingRecursion: boolean, jsonSourceFile: JsonSourceFile, specKey: string) { | ||
function validateSpecs(specs: ReadonlyArray<string>, errors: Push<Diagnostic>, allowTrailingRecursion: boolean, jsonSourceFile: JsonSourceFile, specKey: string): ReadonlyArray<string> { | ||
return specs.filter(spec => { | ||
const diag = specToDiagnostic(spec, allowTrailingRecursion); | ||
if (diag !== undefined) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3738,16 +3738,33 @@ namespace ts { | |
errors: Diagnostic[]; | ||
wildcardDirectories?: MapLike<WatchDirectoryFlags>; | ||
compileOnSave?: boolean; | ||
configFileSpecs?: ConfigFileSpecs; | ||
} | ||
|
||
export const enum WatchDirectoryFlags { | ||
None = 0, | ||
Recursive = 1 << 0, | ||
} | ||
|
||
export interface ConfigFileSpecs { | ||
filesSpecs: ReadonlyArray<string>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be |
||
/** | ||
* Present to report errors (user specified specs), validatedIncludeSpecs are used for file name matching | ||
*/ | ||
includeSpecs: ReadonlyArray<string>; | ||
/** | ||
* Present to report errors (user specified specs), validatedExcludeSpecs are used for file name matching | ||
*/ | ||
excludeSpecs: ReadonlyArray<string>; | ||
validatedIncludeSpecs: ReadonlyArray<string>; | ||
validatedExcludeSpecs: ReadonlyArray<string>; | ||
wildcardDirectories: MapLike<WatchDirectoryFlags>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this be changed to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I want to keep this MapLike just the way json has to ensure that the conversion is done only on need basis |
||
} | ||
|
||
export interface ExpandResult { | ||
fileNames: string[]; | ||
wildcardDirectories: MapLike<WatchDirectoryFlags>; | ||
spec: ConfigFileSpecs; | ||
} | ||
|
||
/* @internal */ | ||
|
@@ -3995,9 +4012,11 @@ namespace ts { | |
} | ||
|
||
export interface ResolvedModuleWithFailedLookupLocations { | ||
resolvedModule: ResolvedModuleFull | undefined; | ||
readonly resolvedModule: ResolvedModuleFull | undefined; | ||
/* @internal */ | ||
failedLookupLocations: string[]; | ||
readonly failedLookupLocations: string[]; | ||
/*@internal*/ | ||
isInvalidated?: boolean; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't think this type should be mutable -- could you use a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To clarify, I meant to use a different type than There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, it looks like you did do that, but forgot to remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This property doesn't really belong here -- this is the return type of functions in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I dont think thats a good idea.. Already there are so many resolutions created for large projects, adding overhead of another object doesnt seem right. Instead I moved the invalidated definition to resolutionCache so shouldnt change anything in modulenameResolver |
||
} | ||
|
||
export interface ResolvedTypeReferenceDirective { | ||
|
@@ -4008,8 +4027,10 @@ namespace ts { | |
} | ||
|
||
export interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations { | ||
resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective; | ||
failedLookupLocations: string[]; | ||
readonly resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective; | ||
readonly failedLookupLocations: string[]; | ||
/*@internal*/ | ||
isInvalidated?: boolean; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above |
||
} | ||
|
||
export interface CompilerHost extends ModuleResolutionHost { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be
fileSpecs
instead offilesSpecs
? The extra "s" seems redundant.