-
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
Improvements to tsc --watch #17669
Improvements to tsc --watch #17669
Changes from 40 commits
802e283
499fabc
273569f
94a589b
ef5935b
e068475
6237b22
9b18f7b
85b9254
69e5abd
c814d8e
2dd6aed
89c61e7
bb91b32
46e3d1c
031a637
0d5e6c9
2762232
65a6ee0
d55150c
8dc6248
7474ba7
6385f7e
d0a23bb
59d07dc
9895082
f1b1b12
136b091
6bf9133
a99c04e
b66b752
e639ceb
8deef58
60e2e68
3908325
7173da2
e500be2
6227a36
55931c4
e65df12
e711238
3b85f3f
ea95f3b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1202,6 +1202,13 @@ namespace ts { | |
return Array.isArray ? Array.isArray(value) : value instanceof Array; | ||
} | ||
|
||
/** | ||
* Tests whether a value is string | ||
*/ | ||
export function isString(text: any): text is string { | ||
return typeof text === "string"; | ||
} | ||
|
||
export function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined { | ||
return value !== undefined && test(value) ? value : undefined; | ||
} | ||
|
@@ -1212,7 +1219,10 @@ namespace ts { | |
} | ||
|
||
/** Does nothing. */ | ||
export function noop(): void {} | ||
export function noop(): void { } | ||
|
||
/** Do nothing and return false */ | ||
export function returnFalse(): false { return false; } | ||
|
||
/** Throws an error because a function is not implemented. */ | ||
export function notImplemented(): never { | ||
|
@@ -1455,16 +1465,16 @@ namespace ts { | |
function compareMessageText(text1: string | DiagnosticMessageChain, text2: string | DiagnosticMessageChain): Comparison { | ||
while (text1 && text2) { | ||
// We still have both chains. | ||
const string1 = typeof text1 === "string" ? text1 : text1.messageText; | ||
const string2 = typeof text2 === "string" ? text2 : text2.messageText; | ||
const string1 = isString(text1) ? text1 : text1.messageText; | ||
const string2 = isString(text2) ? text2 : text2.messageText; | ||
|
||
const res = compareValues(string1, string2); | ||
if (res) { | ||
return res; | ||
} | ||
|
||
text1 = typeof text1 === "string" ? undefined : text1.next; | ||
text2 = typeof text2 === "string" ? undefined : text2.next; | ||
text1 = isString(text1) ? undefined : text1.next; | ||
text2 = isString(text2) ? undefined : text2.next; | ||
} | ||
|
||
if (!text1 && !text2) { | ||
|
@@ -2066,8 +2076,8 @@ namespace ts { | |
} | ||
|
||
export interface FileSystemEntries { | ||
files: ReadonlyArray<string>; | ||
directories: ReadonlyArray<string>; | ||
readonly files: ReadonlyArray<string>; | ||
readonly directories: ReadonlyArray<string>; | ||
} | ||
|
||
export interface FileMatcherPatterns { | ||
|
@@ -2620,4 +2630,204 @@ namespace ts { | |
export function isCheckJsEnabledForFile(sourceFile: SourceFile, compilerOptions: CompilerOptions) { | ||
return sourceFile.checkJsDirective ? sourceFile.checkJsDirective.enabled : compilerOptions.checkJs; | ||
} | ||
|
||
export interface HostForCaching extends PartialSystem { | ||
useCaseSensitiveFileNames: boolean; | ||
} | ||
|
||
export interface CachedHost { | ||
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 comment as above. |
||
addOrDeleteFileOrFolder(fileOrFolder: string, fileOrFolderPath: Path): void; | ||
addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void; | ||
clearCache(): void; | ||
} | ||
|
||
export interface CachedPartialSystem extends PartialSystem, CachedHost { | ||
} | ||
|
||
interface MutableFileSystemEntries { | ||
readonly files: string[]; | ||
readonly directories: string[]; | ||
} | ||
|
||
export function createCachedPartialSystem(host: HostForCaching): CachedPartialSystem { | ||
const cachedReadDirectoryResult = createMap<MutableFileSystemEntries>(); | ||
const getCurrentDirectory = memoize(() => host.getCurrentDirectory()); | ||
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); | ||
return { | ||
writeFile, | ||
fileExists, | ||
directoryExists, | ||
createDirectory, | ||
getCurrentDirectory, | ||
getDirectories, | ||
readDirectory, | ||
addOrDeleteFileOrFolder, | ||
addOrDeleteFile, | ||
clearCache | ||
}; | ||
|
||
function toPath(fileName: string) { | ||
return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName); | ||
} | ||
|
||
function getCachedFileSystemEntries(rootDirPath: Path): MutableFileSystemEntries | undefined { | ||
return cachedReadDirectoryResult.get(rootDirPath); | ||
} | ||
|
||
function getCachedFileSystemEntriesForBaseDir(path: Path): MutableFileSystemEntries | undefined { | ||
return getCachedFileSystemEntries(getDirectoryPath(path)); | ||
} | ||
|
||
function getBaseNameOfFileName(fileName: string) { | ||
return getBaseFileName(normalizePath(fileName)); | ||
} | ||
|
||
function createCachedFileSystemEntries(rootDir: string, rootDirPath: Path) { | ||
const resultFromHost: MutableFileSystemEntries = { | ||
files: map(host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]), getBaseNameOfFileName) || [], | ||
directories: host.getDirectories(rootDir) || [] | ||
}; | ||
|
||
cachedReadDirectoryResult.set(rootDirPath, resultFromHost); | ||
return resultFromHost; | ||
} | ||
|
||
/** | ||
* If the readDirectory result was already cached, it returns that | ||
* Otherwise gets result from host and caches it. | ||
* The host request is done under try catch block to avoid caching incorrect result | ||
*/ | ||
function tryReadDirectory(rootDir: string, rootDirPath: Path): MutableFileSystemEntries | undefined { | ||
const cachedResult = getCachedFileSystemEntries(rootDirPath); | ||
if (cachedResult) { | ||
return cachedResult; | ||
} | ||
|
||
try { | ||
return createCachedFileSystemEntries(rootDir, rootDirPath); | ||
} | ||
catch (_e) { | ||
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. Include an assertion about the kind of exception you expected to catch. 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. It could be any exception, i have updated the comment 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. Wait, why do we want to catch any exception and ignore it? |
||
// If there is exception to read directories, dont cache the result and direct the calls to host | ||
Debug.assert(!cachedReadDirectoryResult.has(rootDirPath)); | ||
return undefined; | ||
} | ||
} | ||
|
||
function fileNameEqual(name1: string, name2: 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. I think it would be better to use canonical file names from the start, rather than to re-canonicalize them every time they are compared. 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 agree with that. This allows us to retain the casing (instead of always providing using lower case) of file names even if the host is case-insensitive |
||
return getCanonicalFileName(name1) === getCanonicalFileName(name2); | ||
} | ||
|
||
function hasEntry(entries: ReadonlyArray<string>, name: string) { | ||
return some(entries, file => fileNameEqual(file, name)); | ||
} | ||
|
||
function updateFileSystemEntry(entries: string[], baseName: string, isValid: boolean) { | ||
if (hasEntry(entries, baseName)) { | ||
if (!isValid) { | ||
return filterMutate(entries, entry => !fileNameEqual(entry, baseName)); | ||
} | ||
} | ||
else if (isValid) { | ||
return entries.push(baseName); | ||
} | ||
} | ||
|
||
function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { | ||
const path = toPath(fileName); | ||
const result = getCachedFileSystemEntriesForBaseDir(path); | ||
if (result) { | ||
updateFilesOfFileSystemEntry(result, getBaseNameOfFileName(fileName), /*fileExists*/ true); | ||
} | ||
return host.writeFile(fileName, data, writeByteOrderMark); | ||
} | ||
|
||
function fileExists(fileName: string): boolean { | ||
const path = toPath(fileName); | ||
const result = getCachedFileSystemEntriesForBaseDir(path); | ||
return result && hasEntry(result.files, getBaseNameOfFileName(fileName)) || | ||
host.fileExists(fileName); | ||
} | ||
|
||
function directoryExists(dirPath: string): boolean { | ||
const path = toPath(dirPath); | ||
return cachedReadDirectoryResult.has(path) || host.directoryExists(dirPath); | ||
} | ||
|
||
function createDirectory(dirPath: string) { | ||
const path = toPath(dirPath); | ||
const result = getCachedFileSystemEntriesForBaseDir(path); | ||
const baseFileName = getBaseNameOfFileName(dirPath); | ||
if (result) { | ||
updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true); | ||
} | ||
host.createDirectory(dirPath); | ||
} | ||
|
||
function getDirectories(rootDir: string): string[] { | ||
const rootDirPath = toPath(rootDir); | ||
const result = tryReadDirectory(rootDir, rootDirPath); | ||
if (result) { | ||
return result.directories.slice(); | ||
} | ||
return host.getDirectories(rootDir); | ||
} | ||
|
||
function readDirectory(rootDir: string, extensions?: ReadonlyArray<string>, excludes?: ReadonlyArray<string>, includes?: ReadonlyArray<string>, depth?: number): string[] { | ||
const rootDirPath = toPath(rootDir); | ||
const result = tryReadDirectory(rootDir, rootDirPath); | ||
if (result) { | ||
return matchFiles(rootDir, extensions, excludes, includes, host.useCaseSensitiveFileNames, getCurrentDirectory(), depth, getFileSystemEntries); | ||
} | ||
return host.readDirectory(rootDir, extensions, excludes, includes, depth); | ||
|
||
function getFileSystemEntries(dir: string) { | ||
const path = toPath(dir); | ||
if (path === rootDirPath) { | ||
return result; | ||
} | ||
return getCachedFileSystemEntries(path) || createCachedFileSystemEntries(dir, path); | ||
} | ||
} | ||
|
||
function addOrDeleteFileOrFolder(fileOrFolder: string, fileOrFolderPath: Path) { | ||
const existingResult = getCachedFileSystemEntries(fileOrFolderPath); | ||
if (existingResult) { | ||
// This was a folder already present, remove it if this doesnt exist any more | ||
if (!host.directoryExists(fileOrFolder)) { | ||
cachedReadDirectoryResult.delete(fileOrFolderPath); | ||
} | ||
} | ||
else { | ||
// This was earlier a file (hence not in cached directory contents) | ||
// or we never cached the directory containing it | ||
const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrFolderPath); | ||
if (parentResult) { | ||
const baseName = getBaseNameOfFileName(fileOrFolder); | ||
if (parentResult) { | ||
updateFilesOfFileSystemEntry(parentResult, baseName, host.fileExists(fileOrFolderPath)); | ||
updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrFolderPath)); | ||
} | ||
} | ||
} | ||
} | ||
|
||
function addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind) { | ||
if (eventKind === FileWatcherEventKind.Changed) { | ||
return; | ||
} | ||
|
||
const parentResult = getCachedFileSystemEntriesForBaseDir(filePath); | ||
if (parentResult) { | ||
updateFilesOfFileSystemEntry(parentResult, getBaseNameOfFileName(fileName), eventKind === FileWatcherEventKind.Created); | ||
} | ||
} | ||
|
||
function updateFilesOfFileSystemEntry(parentResult: MutableFileSystemEntries, baseName: string, fileExists: boolean) { | ||
updateFileSystemEntry(parentResult.files, baseName, fileExists); | ||
} | ||
|
||
function clearCache() { | ||
cachedReadDirectoryResult.clear(); | ||
} | ||
} | ||
} |
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.
It looks like this last argument is always provided?