Replace disk path operations to use fileNames instead of path (#56476)

Co-authored-by: Andrew Branch <andrewbranch@users.noreply.github.com>
This commit is contained in:
Sheetal Nandi
2023-11-27 22:21:29 -08:00
committed by GitHub
co-authored by Andrew Branch
parent c32ad95b96
commit b6121e400c
106 changed files with 976 additions and 659 deletions
+26 -14
View File
@@ -3771,7 +3771,7 @@ function specToDiagnostic(spec: CompilerOptionsValue, disallowTrailingRecursion?
/**
* Gets directories in a set of include patterns that should be watched for changes.
*/
function getWildcardDirectories({ validatedIncludeSpecs: include, validatedExcludeSpecs: exclude }: ConfigFileSpecs, path: string, useCaseSensitiveFileNames: boolean): MapLike<WatchDirectoryFlags> {
function getWildcardDirectories({ validatedIncludeSpecs: include, validatedExcludeSpecs: exclude }: ConfigFileSpecs, basePath: string, useCaseSensitiveFileNames: boolean): MapLike<WatchDirectoryFlags> {
// We watch a directory recursively if it contains a wildcard anywhere in a directory segment
// of the pattern:
//
@@ -3784,23 +3784,26 @@ function getWildcardDirectories({ validatedIncludeSpecs: include, validatedExclu
//
// /a/b/* - Watch /a/b directly to catch any new file
// /a/b/a?z - Watch /a/b directly to catch any new file matching a?z
const rawExcludeRegex = getRegularExpressionForWildcard(exclude, path, "exclude");
const rawExcludeRegex = getRegularExpressionForWildcard(exclude, basePath, "exclude");
const excludeRegex = rawExcludeRegex && new RegExp(rawExcludeRegex, useCaseSensitiveFileNames ? "" : "i");
const wildcardDirectories: MapLike<WatchDirectoryFlags> = {};
const wildCardKeyToPath = new Map<CanonicalKey, string>();
if (include !== undefined) {
const recursiveKeys: string[] = [];
const recursiveKeys: CanonicalKey[] = [];
for (const file of include) {
const spec = normalizePath(combinePaths(path, file));
const spec = normalizePath(combinePaths(basePath, file));
if (excludeRegex && excludeRegex.test(spec)) {
continue;
}
const match = getWildcardDirectoryFromSpec(spec, useCaseSensitiveFileNames);
if (match) {
const { key, flags } = match;
const existingFlags = wildcardDirectories[key];
const { key, path, flags } = match;
const existingPath = wildCardKeyToPath.get(key);
const existingFlags = existingPath !== undefined ? wildcardDirectories[existingPath] : undefined;
if (existingFlags === undefined || existingFlags < flags) {
wildcardDirectories[key] = flags;
wildcardDirectories[existingPath !== undefined ? existingPath : path] = flags;
if (existingPath === undefined) wildCardKeyToPath.set(key, path);
if (flags === WatchDirectoryFlags.Recursive) {
recursiveKeys.push(key);
}
@@ -3809,11 +3812,12 @@ function getWildcardDirectories({ validatedIncludeSpecs: include, validatedExclu
}
// Remove any subpaths under an existing recursively watched directory.
for (const key in wildcardDirectories) {
if (hasProperty(wildcardDirectories, key)) {
for (const path in wildcardDirectories) {
if (hasProperty(wildcardDirectories, path)) {
for (const recursiveKey of recursiveKeys) {
if (key !== recursiveKey && containsPath(recursiveKey, key, path, !useCaseSensitiveFileNames)) {
delete wildcardDirectories[key];
const key = toCanonicalKey(path, useCaseSensitiveFileNames);
if (key !== recursiveKey && containsPath(recursiveKey, key, basePath, !useCaseSensitiveFileNames)) {
delete wildcardDirectories[path];
}
}
}
@@ -3823,7 +3827,12 @@ function getWildcardDirectories({ validatedIncludeSpecs: include, validatedExclu
return wildcardDirectories;
}
function getWildcardDirectoryFromSpec(spec: string, useCaseSensitiveFileNames: boolean): { key: string; flags: WatchDirectoryFlags; } | undefined {
type CanonicalKey = string & { __canonicalKey: never; };
function toCanonicalKey(path: string, useCaseSensitiveFileNames: boolean): CanonicalKey {
return (useCaseSensitiveFileNames ? path : toFileNameLowerCase(path)) as CanonicalKey;
}
function getWildcardDirectoryFromSpec(spec: string, useCaseSensitiveFileNames: boolean): { key: CanonicalKey; path: string; flags: WatchDirectoryFlags; } | undefined {
const match = wildcardDirectoryPattern.exec(spec);
if (match) {
// We check this with a few `indexOf` calls because 3 `indexOf`/`lastIndexOf` calls is
@@ -3834,15 +3843,18 @@ function getWildcardDirectoryFromSpec(spec: string, useCaseSensitiveFileNames: b
const starWildcardIndex = spec.indexOf("*");
const lastDirectorySeperatorIndex = spec.lastIndexOf(directorySeparator);
return {
key: useCaseSensitiveFileNames ? match[0] : toFileNameLowerCase(match[0]),
key: toCanonicalKey(match[0], useCaseSensitiveFileNames),
path: match[0],
flags: (questionWildcardIndex !== -1 && questionWildcardIndex < lastDirectorySeperatorIndex)
|| (starWildcardIndex !== -1 && starWildcardIndex < lastDirectorySeperatorIndex)
? WatchDirectoryFlags.Recursive : WatchDirectoryFlags.None,
};
}
if (isImplicitGlob(spec.substring(spec.lastIndexOf(directorySeparator) + 1))) {
const path = removeTrailingDirectorySeparator(spec);
return {
key: removeTrailingDirectorySeparator(useCaseSensitiveFileNames ? spec : toFileNameLowerCase(spec)),
key: toCanonicalKey(path, useCaseSensitiveFileNames),
path,
flags: WatchDirectoryFlags.Recursive,
};
}
+23 -15
View File
@@ -1,7 +1,6 @@
import {
append,
appendIfUnique,
arrayFrom,
arrayIsEqualTo,
changeAnyExtension,
CharacterCodes,
@@ -901,11 +900,24 @@ export interface NonRelativeModuleNameResolutionCache extends NonRelativeNameRes
getOrCreateCacheForModuleName(nonRelativeModuleName: string, mode: ResolutionMode, redirectedReference?: ResolvedProjectReference): PerModuleNameCache;
}
/** @internal */
export interface MissingPackageJsonInfo {
packageDirectory: string;
directoryExists: boolean;
}
/** @internal */
export type PackageJsonInfoCacheEntry = PackageJsonInfo | MissingPackageJsonInfo;
/** @internal */
export function isPackageJsonInfo(entry: PackageJsonInfoCacheEntry | undefined): entry is PackageJsonInfo {
return !!(entry as PackageJsonInfo | undefined)?.contents;
}
export interface PackageJsonInfoCache {
/** @internal */ getPackageJsonInfo(packageJsonPath: string): PackageJsonInfo | boolean | undefined;
/** @internal */ setPackageJsonInfo(packageJsonPath: string, info: PackageJsonInfo | boolean): void;
/** @internal */ entries(): [Path, PackageJsonInfo | boolean][];
/** @internal */ getInternalMap(): Map<Path, PackageJsonInfo | boolean> | undefined;
/** @internal */ getPackageJsonInfo(packageJsonPath: string): PackageJsonInfoCacheEntry | undefined;
/** @internal */ setPackageJsonInfo(packageJsonPath: string, info: PackageJsonInfoCacheEntry): void;
/** @internal */ getInternalMap(): Map<Path, PackageJsonInfoCacheEntry> | undefined;
clear(): void;
/** @internal */ isReadonly?: boolean;
}
@@ -1021,21 +1033,17 @@ export function createCacheWithRedirects<K, V>(ownOptions: CompilerOptions | und
}
function createPackageJsonInfoCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): PackageJsonInfoCache {
let cache: Map<Path, PackageJsonInfo | boolean> | undefined;
return { getPackageJsonInfo, setPackageJsonInfo, clear, entries, getInternalMap };
let cache: Map<Path, PackageJsonInfoCacheEntry> | undefined;
return { getPackageJsonInfo, setPackageJsonInfo, clear, getInternalMap };
function getPackageJsonInfo(packageJsonPath: string) {
return cache?.get(toPath(packageJsonPath, currentDirectory, getCanonicalFileName));
}
function setPackageJsonInfo(packageJsonPath: string, info: PackageJsonInfo | boolean) {
function setPackageJsonInfo(packageJsonPath: string, info: PackageJsonInfoCacheEntry) {
(cache ||= new Map()).set(toPath(packageJsonPath, currentDirectory, getCanonicalFileName), info);
}
function clear() {
cache = undefined;
}
function entries() {
const iter = cache?.entries();
return iter ? arrayFrom(iter) : [];
}
function getInternalMap() {
return cache;
}
@@ -2391,7 +2399,7 @@ export function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures:
const existing = state.packageJsonInfoCache?.getPackageJsonInfo(packageJsonPath);
if (existing !== undefined) {
if (typeof existing !== "boolean") {
if (isPackageJsonInfo(existing)) {
if (traceEnabled) trace(host, Diagnostics.File_0_exists_according_to_earlier_cached_lookups, packageJsonPath);
state.affectingLocations?.push(packageJsonPath);
return existing.packageDirectory === packageDirectory ?
@@ -2399,7 +2407,7 @@ export function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures:
{ packageDirectory, contents: existing.contents };
}
else {
if (existing && traceEnabled) trace(host, Diagnostics.File_0_does_not_exist_according_to_earlier_cached_lookups, packageJsonPath);
if (existing.directoryExists && traceEnabled) trace(host, Diagnostics.File_0_does_not_exist_according_to_earlier_cached_lookups, packageJsonPath);
state.failedLookupLocations?.push(packageJsonPath);
return undefined;
}
@@ -2419,7 +2427,7 @@ export function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures:
if (directoryExists && traceEnabled) {
trace(host, Diagnostics.File_0_does_not_exist, packageJsonPath);
}
if (state.packageJsonInfoCache && !state.packageJsonInfoCache.isReadonly) state.packageJsonInfoCache.setPackageJsonInfo(packageJsonPath, directoryExists);
if (state.packageJsonInfoCache && !state.packageJsonInfoCache.isReadonly) state.packageJsonInfoCache.setPackageJsonInfo(packageJsonPath, { packageDirectory, directoryExists });
// record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results
state.failedLookupLocations?.push(packageJsonPath);
}
+36 -26
View File
@@ -64,6 +64,7 @@ import {
isModuleBlock,
isModuleDeclaration,
isNonGlobalAmbientModule,
isPackageJsonInfo,
isRootedDiskPath,
isSourceFile,
isString,
@@ -84,7 +85,6 @@ import {
NodeFlags,
NodeModulePathParts,
normalizePath,
Path,
pathContainsNodeModules,
pathIsBareSpecifier,
pathIsRelative,
@@ -200,7 +200,7 @@ function getPreferences(
export function updateModuleSpecifier(
compilerOptions: CompilerOptions,
importingSourceFile: SourceFile,
importingSourceFileName: Path,
importingSourceFileName: string,
toFileName: string,
host: ModuleSpecifierResolutionHost,
oldImportSpecifier: string,
@@ -221,7 +221,7 @@ export function updateModuleSpecifier(
export function getModuleSpecifier(
compilerOptions: CompilerOptions,
importingSourceFile: SourceFile,
importingSourceFileName: Path,
importingSourceFileName: string,
toFileName: string,
host: ModuleSpecifierResolutionHost,
options: ModuleSpecifierOptions = {},
@@ -238,15 +238,15 @@ export function getNodeModulesPackageName(
preferences: UserPreferences,
options: ModuleSpecifierOptions = {},
): string | undefined {
const info = getInfo(importingSourceFile.path, host);
const modulePaths = getAllModulePaths(importingSourceFile.path, nodeModulesFileName, host, preferences, options);
const info = getInfo(importingSourceFile.fileName, host);
const modulePaths = getAllModulePaths(info, nodeModulesFileName, host, preferences, options);
return firstDefined(modulePaths, modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, preferences, /*packageNameOnly*/ true, options.overrideImportMode));
}
function getModuleSpecifierWorker(
compilerOptions: CompilerOptions,
importingSourceFile: SourceFile,
importingSourceFileName: Path,
importingSourceFileName: string,
toFileName: string,
host: ModuleSpecifierResolutionHost,
preferences: Preferences,
@@ -254,7 +254,7 @@ function getModuleSpecifierWorker(
options: ModuleSpecifierOptions = {},
): string {
const info = getInfo(importingSourceFileName, host);
const modulePaths = getAllModulePaths(importingSourceFileName, toFileName, host, userPreferences, options);
const modulePaths = getAllModulePaths(info, toFileName, host, userPreferences, options);
return firstDefined(modulePaths, modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences, /*packageNameOnly*/ undefined, options.overrideImportMode)) ||
getLocalModuleSpecifier(toFileName, info, compilerOptions, host, options.overrideImportMode || importingSourceFile.impliedNodeFormat, preferences);
}
@@ -346,7 +346,7 @@ export function getModuleSpecifiersWithCacheInfo(
if (!moduleSourceFile) return { moduleSpecifiers: emptyArray, computedWithoutCache };
computedWithoutCache = true;
modulePaths ||= getAllModulePathsWorker(importingSourceFile.path, moduleSourceFile.originalFileName, host);
modulePaths ||= getAllModulePathsWorker(getInfo(importingSourceFile.fileName, host), moduleSourceFile.originalFileName, host);
const result = computeModuleSpecifiers(
modulePaths,
compilerOptions,
@@ -369,7 +369,7 @@ function computeModuleSpecifiers(
options: ModuleSpecifierOptions = {},
forAutoImport: boolean,
): readonly string[] {
const info = getInfo(importingSourceFile.path, host);
const info = getInfo(importingSourceFile.fileName, host);
const preferences = getPreferences(userPreferences, compilerOptions, importingSourceFile);
const existingSpecifier = forEach(modulePaths, modulePath =>
forEach(
@@ -455,14 +455,21 @@ function computeModuleSpecifiers(
interface Info {
readonly getCanonicalFileName: GetCanonicalFileName;
readonly importingSourceFileName: Path;
readonly sourceDirectory: Path;
readonly importingSourceFileName: string;
readonly sourceDirectory: string;
readonly canonicalSourceDirectory: string;
}
// importingSourceFileName is separate because getEditsForFileRename may need to specify an updated path
function getInfo(importingSourceFileName: Path, host: ModuleSpecifierResolutionHost): Info {
function getInfo(importingSourceFileName: string, host: ModuleSpecifierResolutionHost): Info {
importingSourceFileName = getNormalizedAbsolutePath(importingSourceFileName, host.getCurrentDirectory());
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : true);
const sourceDirectory = getDirectoryPath(importingSourceFileName);
return { getCanonicalFileName, importingSourceFileName, sourceDirectory };
return {
getCanonicalFileName,
importingSourceFileName,
sourceDirectory,
canonicalSourceDirectory: getCanonicalFileName(sourceDirectory),
};
}
function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOptions: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: ResolutionMode, preferences: Preferences): string;
@@ -473,7 +480,7 @@ function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOpt
return undefined;
}
const { sourceDirectory, getCanonicalFileName } = info;
const { sourceDirectory, canonicalSourceDirectory, getCanonicalFileName } = info;
const allowedEndings = getAllowedEndingsInPrefererredOrder(importMode);
const relativePath = rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName, allowedEndings, compilerOptions) ||
processEnding(ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, moduleFileName, getCanonicalFileName)), allowedEndings, compilerOptions);
@@ -506,7 +513,7 @@ function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOpt
toPath(getDirectoryPath(compilerOptions.configFilePath), host.getCurrentDirectory(), info.getCanonicalFileName) :
info.getCanonicalFileName(host.getCurrentDirectory());
const modulePath = toPath(moduleFileName, projectDirectory, getCanonicalFileName);
const sourceIsInternal = startsWith(sourceDirectory, projectDirectory);
const sourceIsInternal = startsWith(canonicalSourceDirectory, projectDirectory);
const targetIsInternal = startsWith(modulePath, projectDirectory);
if (sourceIsInternal && !targetIsInternal || !sourceIsInternal && targetIsInternal) {
// 1. The import path crosses the boundary of the tsconfig.json-containing directory.
@@ -623,37 +630,37 @@ export function forEachFileNameOfModule<T>(
* Symlinks will be returned first so they are preferred over the real path.
*/
function getAllModulePaths(
importingFilePath: Path,
info: Info,
importedFileName: string,
host: ModuleSpecifierResolutionHost,
preferences: UserPreferences,
options: ModuleSpecifierOptions = {},
) {
const importingFilePath = toPath(info.importingSourceFileName, host.getCurrentDirectory(), hostGetCanonicalFileName(host));
const importedFilePath = toPath(importedFileName, host.getCurrentDirectory(), hostGetCanonicalFileName(host));
const cache = host.getModuleSpecifierCache?.();
if (cache) {
const cached = cache.get(importingFilePath, importedFilePath, preferences, options);
if (cached?.modulePaths) return cached.modulePaths;
}
const modulePaths = getAllModulePathsWorker(importingFilePath, importedFileName, host);
const modulePaths = getAllModulePathsWorker(info, importedFileName, host);
if (cache) {
cache.setModulePaths(importingFilePath, importedFilePath, preferences, options, modulePaths);
}
return modulePaths;
}
function getAllModulePathsWorker(importingFileName: Path, importedFileName: string, host: ModuleSpecifierResolutionHost): readonly ModulePath[] {
const getCanonicalFileName = hostGetCanonicalFileName(host);
function getAllModulePathsWorker(info: Info, importedFileName: string, host: ModuleSpecifierResolutionHost): readonly ModulePath[] {
const allFileNames = new Map<string, { path: string; isRedirect: boolean; isInNodeModules: boolean; }>();
let importedFileFromNodeModules = false;
forEachFileNameOfModule(
importingFileName,
info.importingSourceFileName,
importedFileName,
host,
/*preferSymlinks*/ true,
(path, isRedirect) => {
const isInNodeModules = pathContainsNodeModules(path);
allFileNames.set(path, { path: getCanonicalFileName(path), isRedirect, isInNodeModules });
allFileNames.set(path, { path: info.getCanonicalFileName(path), isRedirect, isInNodeModules });
importedFileFromNodeModules = importedFileFromNodeModules || isInNodeModules;
// don't return value, so we collect everything
},
@@ -662,7 +669,7 @@ function getAllModulePathsWorker(importingFileName: Path, importedFileName: stri
// Sort by paths closest to importing file Name directory
const sortedPaths: ModulePath[] = [];
for (
let directory = getDirectoryPath(importingFileName);
let directory = info.canonicalSourceDirectory;
allFileNames.size !== 0;
) {
const directoryStart = ensureTrailingDirectorySeparator(directory);
@@ -684,7 +691,10 @@ function getAllModulePathsWorker(importingFileName: Path, importedFileName: stri
directory = newDirectory;
}
if (allFileNames.size) {
const remainingPaths = arrayFrom(allFileNames.values());
const remainingPaths = arrayFrom(
allFileNames.entries(),
([fileName, { isRedirect, isInNodeModules }]): ModulePath => ({ path: fileName, isRedirect, isInNodeModules }),
);
if (remainingPaths.length > 1) remainingPaths.sort(comparePathsByRedirectAndNumberOfDirectorySeparators);
sortedPaths.push(...remainingPaths);
}
@@ -914,7 +924,7 @@ function tryGetModuleNameFromRootDirs(rootDirs: readonly string[], moduleFileNam
return processEnding(shortest, allowedEndings, compilerOptions);
}
function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, importingSourceFile: SourceFile, host: ModuleSpecifierResolutionHost, options: CompilerOptions, userPreferences: UserPreferences, packageNameOnly?: boolean, overrideMode?: ResolutionMode): string | undefined {
function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, canonicalSourceDirectory }: Info, importingSourceFile: SourceFile, host: ModuleSpecifierResolutionHost, options: CompilerOptions, userPreferences: UserPreferences, packageNameOnly?: boolean, overrideMode?: ResolutionMode): string | undefined {
if (!host.fileExists || !host.readFile) {
return undefined;
}
@@ -967,7 +977,7 @@ function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCan
// Get a path that's relative to node_modules or the importing file's path
// if node_modules folder is in this folder or any of its parent folders, no need to keep it.
const pathToTopLevelNodeModules = getCanonicalFileName(moduleSpecifier.substring(0, parts.topLevelNodeModulesIndex));
if (!(startsWith(sourceDirectory, pathToTopLevelNodeModules) || globalTypingsCacheLocation && startsWith(getCanonicalFileName(globalTypingsCacheLocation), pathToTopLevelNodeModules))) {
if (!(startsWith(canonicalSourceDirectory, pathToTopLevelNodeModules) || globalTypingsCacheLocation && startsWith(getCanonicalFileName(globalTypingsCacheLocation), pathToTopLevelNodeModules))) {
return undefined;
}
@@ -983,7 +993,7 @@ function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCan
let moduleFileToTry = path;
let maybeBlockedByTypesVersions = false;
const cachedPackageJson = host.getPackageJsonInfoCache?.()?.getPackageJsonInfo(packageJsonPath);
if (typeof cachedPackageJson === "object" || cachedPackageJson === undefined && host.fileExists(packageJsonPath)) {
if (isPackageJsonInfo(cachedPackageJson) || cachedPackageJson === undefined && host.fileExists(packageJsonPath)) {
const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || JSON.parse(host.readFile!(packageJsonPath)!);
const importMode = overrideMode || importingSourceFile.impliedNodeFormat;
if (getResolvePackageJsonExports(options)) {
+23 -22
View File
@@ -4,7 +4,6 @@ import {
addRange,
addRelatedInfo,
append,
arrayFrom,
arrayIsEqualTo,
AsExpression,
BuilderProgram,
@@ -215,7 +214,6 @@ import {
LibResolution,
libs,
mapDefined,
mapDefinedIterator,
maybeBind,
memoize,
MethodDeclaration,
@@ -1238,7 +1236,8 @@ export function isProgramUptoDate(
if (program.getSourceFiles().some(sourceFileNotUptoDate)) return false;
// If any of the missing file paths are now created
if (program.getMissingFilePaths().some(fileExists)) return false;
const missingPaths = program.getMissingFilePaths();
if (missingPaths && forEachEntry(missingPaths, fileExists)) return false;
const currentOptions = program.getCompilerOptions();
// If the compilation settings do no match, then the program is not up-to-date
@@ -1683,8 +1682,8 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
* - false if sourceFile missing for source of project reference redirect
* - undefined otherwise
*/
const filesByName = new Map<string, SourceFile | false | undefined>();
let missingFilePaths: readonly Path[] | undefined;
const filesByName = new Map<Path, SourceFile | false | undefined>();
let missingFileNames = new Map<Path, string>();
// stores 'filename -> file association' ignoring case
// used to track cases when two file names differ only in casing
const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? new Map<string, SourceFile>() : undefined;
@@ -1801,14 +1800,11 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
}
}
missingFilePaths = arrayFrom(mapDefinedIterator(filesByName.entries(), ([path, file]) => file === undefined ? path as Path : undefined));
files = stableSort(processingDefaultLibFiles, compareDefaultLibFiles).concat(processingOtherFiles);
processingDefaultLibFiles = undefined;
processingOtherFiles = undefined;
}
Debug.assert(!!missingFilePaths);
// Release any files we have acquired in the old program but are
// not part of the new program.
if (oldProgram && host.onReleaseOldSourceFile) {
@@ -1858,7 +1854,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
getSourceFile,
getSourceFileByPath,
getSourceFiles: () => files,
getMissingFilePaths: () => missingFilePaths!, // TODO: GH#18217
getMissingFilePaths: () => missingFileNames,
getModuleResolutionCache: () => moduleResolutionCache,
getFilesByNameMap: () => filesByName,
getCompilerOptions: () => options,
@@ -2387,7 +2383,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
// If the missing file paths are now present, it can change the progam structure,
// and hence cant reuse the structure.
// This is same as how we dont reuse the structure if one of the file from old program is now missing
if (oldProgram.getMissingFilePaths().some(missingFilePath => host.fileExists(missingFilePath))) {
if (forEachEntry(oldProgram.getMissingFilePaths(), missingFileName => host.fileExists(missingFileName))) {
return StructureIsReused.Not;
}
@@ -2568,7 +2564,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
automaticTypeDirectiveNames = getAutomaticTypeDirectiveNames(options, host);
if (!arrayIsEqualTo(oldProgram.getAutomaticTypeDirectiveNames(), automaticTypeDirectiveNames)) return StructureIsReused.SafeModules;
}
missingFilePaths = oldProgram.getMissingFilePaths();
missingFileNames = oldProgram.getMissingFilePaths();
// update fileName -> file mapping
Debug.assert(newSourceFiles.length === oldProgram.getSourceFiles().length);
@@ -2632,7 +2628,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
// Use local caches
const path = toPath(f);
if (getSourceFileByPath(path)) return true;
if (contains(missingFilePaths, path)) return false;
if (missingFileNames.has(path)) return false;
// Before falling back to the host
return host.fileExists(f);
},
@@ -3591,7 +3587,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
const file = isString(source) ?
findSourceFile(source, isDefaultLib, ignoreNoDefaultLib, reason, packageId) :
undefined;
if (file) addFileToFilesByName(file, path, /*redirectedPath*/ undefined);
if (file) addFileToFilesByName(file, path, fileName, /*redirectedPath*/ undefined);
return file;
}
}
@@ -3677,7 +3673,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
// Instead of creating a duplicate, just redirect to the existing one.
const dupFile = createRedirectedSourceFile(fileFromPackageId, file!, fileName, path, toPath(fileName), originalFileName, sourceFileOptions);
redirectTargetsMap.add(fileFromPackageId.path, fileName);
addFileToFilesByName(dupFile, path, redirectedPath);
addFileToFilesByName(dupFile, path, fileName, redirectedPath);
addFileIncludeReason(dupFile, reason);
sourceFileToPackageName.set(path, packageIdToPackageName(packageId));
processingOtherFiles!.push(dupFile);
@@ -3689,7 +3685,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
sourceFileToPackageName.set(path, packageIdToPackageName(packageId));
}
}
addFileToFilesByName(file, path, redirectedPath);
addFileToFilesByName(file, path, fileName, redirectedPath);
if (file) {
sourceFilesFoundSearchingNodeModules.set(path, currentNodeModulesDepth > 0);
@@ -3740,15 +3736,20 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
if (file) fileReasons.add(file.path, reason);
}
function addFileToFilesByName(file: SourceFile | undefined, path: Path, redirectedPath: Path | undefined) {
function addFileToFilesByName(file: SourceFile | undefined, path: Path, fileName: string, redirectedPath: Path | undefined) {
if (redirectedPath) {
filesByName.set(redirectedPath, file);
filesByName.set(path, file || false);
updateFilesByNameMap(fileName, redirectedPath, file);
updateFilesByNameMap(fileName, path, file || false);
}
else {
filesByName.set(path, file);
updateFilesByNameMap(fileName, path, file);
}
}
function updateFilesByNameMap(fileName: string, path: Path, file: SourceFile | false | undefined) {
filesByName.set(path, file);
if (file !== undefined) missingFileNames.delete(path);
else missingFileNames.set(path, fileName);
}
function getProjectReferenceRedirect(fileName: string): string | undefined {
const referencedProject = getProjectReferenceRedirectProject(fileName);
@@ -4119,19 +4120,19 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
if (host.getParsedCommandLine) {
commandLine = host.getParsedCommandLine(refPath);
if (!commandLine) {
addFileToFilesByName(/*file*/ undefined, sourceFilePath, /*redirectedPath*/ undefined);
addFileToFilesByName(/*file*/ undefined, sourceFilePath, refPath, /*redirectedPath*/ undefined);
projectReferenceRedirects.set(sourceFilePath, false);
return undefined;
}
sourceFile = Debug.checkDefined(commandLine.options.configFile);
Debug.assert(!sourceFile.path || sourceFile.path === sourceFilePath);
addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined);
addFileToFilesByName(sourceFile, sourceFilePath, refPath, /*redirectedPath*/ undefined);
}
else {
// An absolute path pointing to the containing directory of the config file
const basePath = getNormalizedAbsolutePath(getDirectoryPath(refPath), currentDirectory);
sourceFile = host.getSourceFile(refPath, ScriptTarget.JSON) as JsonSourceFile | undefined;
addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined);
addFileToFilesByName(sourceFile, sourceFilePath, refPath, /*redirectedPath*/ undefined);
if (sourceFile === undefined) {
projectReferenceRedirects.set(sourceFilePath, false);
return undefined;
+6 -7
View File
@@ -1,5 +1,4 @@
import {
arrayToMap,
CachedDirectoryStructureHost,
clearMap,
closeFileWatcher,
@@ -54,7 +53,7 @@ import {
normalizePath,
PackageId,
packageIdToString,
PackageJsonInfo,
PackageJsonInfoCacheEntry,
parseNodeModuleFromPath,
Path,
PathPathComponents,
@@ -1179,7 +1178,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
}
}
function invalidateAffectingFileWatcher(path: string, packageJsonMap: Map<Path, PackageJsonInfo | boolean> | undefined) {
function invalidateAffectingFileWatcher(path: string, packageJsonMap: Map<Path, PackageJsonInfoCacheEntry> | undefined) {
const watcher = fileWatchesOfAffectingLocations.get(path);
if (watcher?.resolutions) (affectingPathChecks ??= new Set()).add(path);
if (watcher?.files) (affectingPathChecksForFile ??= new Set()).add(path);
@@ -1482,9 +1481,9 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
clearMap(typeRootsWatches, closeFileWatcher);
}
function createTypeRootsWatch(typeRootPath: Path, typeRoot: string): FileWatcher {
function createTypeRootsWatch(typeRoot: string): FileWatcher {
// Create new watch and recursive info
return canWatchTypeRootPath(typeRootPath) ?
return canWatchTypeRootPath(typeRoot) ?
resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => {
const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory);
if (cachedDirectoryStructureHost) {
@@ -1502,7 +1501,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
// So handle to failed lookup locations here as well to ensure we are invalidating resolutions
const dirPath = getDirectoryToWatchFailedLookupLocationFromTypeRoot(
typeRoot,
typeRootPath,
resolutionHost.toPath(typeRoot),
rootPath,
rootPathComponents,
getCurrentDirectory,
@@ -1534,7 +1533,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
if (typeRoots) {
mutateMap(
typeRootsWatches,
arrayToMap(typeRoots, tr => resolutionHost.toPath(tr)),
new Set(typeRoots),
{
createNewValue: createTypeRootsWatch,
onDeleteValue: closeFileWatcher,
+3 -3
View File
@@ -65,6 +65,7 @@ import {
getLeadingCommentRangesOfNode,
getLineAndCharacterOfPosition,
getNameOfDeclaration,
getNormalizedAbsolutePath,
getOriginalNodeId,
getOutputPathsFor,
getParseTreeNode,
@@ -208,7 +209,6 @@ import {
SymbolTracker,
SyntaxKind,
toFileNameLowerCase,
toPath,
TransformationContext,
transformNodes,
tryCast,
@@ -631,8 +631,8 @@ export function transformDeclarations(context: TransformationContext) {
const specifier = moduleSpecifiers.getModuleSpecifier(
options,
currentSourceFile,
toPath(outputFilePath, host.getCurrentDirectory(), host.getCanonicalFileName),
toPath(declFileName, host.getCurrentDirectory(), host.getCanonicalFileName),
getNormalizedAbsolutePath(outputFilePath, host.getCurrentDirectory()),
getNormalizedAbsolutePath(declFileName, host.getCurrentDirectory()),
host,
);
if (!pathIsRelative(specifier)) {
+27 -20
View File
@@ -1,6 +1,6 @@
import {
AffectedFileResult,
arrayToMap,
arrayFrom,
assertType,
BuilderProgram,
BuildInfo,
@@ -10,6 +10,7 @@ import {
clearMap,
closeFileWatcher,
closeFileWatcherOf,
combinePaths,
commonOptionsWithBuild,
CompilerHost,
CompilerOptions,
@@ -48,6 +49,7 @@ import {
findIndex,
flattenDiagnosticMessageText,
forEach,
forEachKey,
ForegroundColorEscapeSequences,
formatColorAndReset,
getAllProjectOutputs,
@@ -72,10 +74,10 @@ import {
isArray,
isIgnoredFileFromWildCardWatching,
isIncrementalCompilation,
isPackageJsonInfo,
isString,
listFiles,
loadWithModeAwareCache,
map,
maybeBind,
missingFileModifiedTime,
ModuleResolutionCache,
@@ -409,14 +411,14 @@ interface SolutionBuilderState<T extends BuilderProgram> extends WatchFactory<Wa
// Watch state
readonly watch: boolean;
readonly allWatchedWildcardDirectories: Map<ResolvedConfigFilePath, Map<string, WildcardDirectoryWatcher>>;
readonly allWatchedInputFiles: Map<ResolvedConfigFilePath, Map<Path, FileWatcher>>;
readonly allWatchedInputFiles: Map<ResolvedConfigFilePath, Map<string, FileWatcher>>;
readonly allWatchedConfigFiles: Map<ResolvedConfigFilePath, FileWatcher>;
readonly allWatchedExtendedConfigFiles: Map<Path, SharedExtendedConfigFileWatcher<ResolvedConfigFilePath>>;
readonly allWatchedPackageJsonFiles: Map<ResolvedConfigFilePath, Map<Path, FileWatcher>>;
readonly filesWatched: Map<Path, FileWatcherWithModifiedTime | Date>;
readonly outputTimeStamps: Map<ResolvedConfigFilePath, Map<Path, Date>>;
readonly lastCachedPackageJsonLookups: Map<ResolvedConfigFilePath, readonly (readonly [Path, object | boolean])[] | undefined>;
readonly lastCachedPackageJsonLookups: Map<ResolvedConfigFilePath, Set<string> | undefined>;
timerToBuildInvalidatedProject: any;
reportFileChangeDetected: boolean;
@@ -660,9 +662,9 @@ function createStateBuildOrder<T extends BuilderProgram>(state: SolutionBuilderS
state.resolvedConfigFilePaths.clear();
// TODO(rbuckton): Should be a `Set`, but that requires changing the code below that uses `mutateMapSkippingNewValues`
const currentProjects = new Map(
const currentProjects = new Set(
getBuildOrderFromAnyBuildOrder(buildOrder).map(
resolved => [toResolvedConfigFilePath(state, resolved), true as const],
resolved => toResolvedConfigFilePath(state, resolved),
),
);
@@ -676,6 +678,7 @@ function createStateBuildOrder<T extends BuilderProgram>(state: SolutionBuilderS
mutateMapSkippingNewValues(state.projectErrorsReported, currentProjects, noopOnDelete);
mutateMapSkippingNewValues(state.buildInfoCache, currentProjects, noopOnDelete);
mutateMapSkippingNewValues(state.outputTimeStamps, currentProjects, noopOnDelete);
mutateMapSkippingNewValues(state.lastCachedPackageJsonLookups, currentProjects, noopOnDelete);
// Remove watches for the program no longer in the solution
if (state.watch) {
@@ -1083,14 +1086,17 @@ function createBuildOrUpdateInvalidedProject<T extends BuilderProgram>(
config.projectReferences,
);
if (state.watch) {
const internalMap = state.moduleResolutionCache?.getPackageJsonInfoCache().getInternalMap();
state.lastCachedPackageJsonLookups.set(
projectPath,
state.moduleResolutionCache && map(
state.moduleResolutionCache.getPackageJsonInfoCache().entries(),
([path, data]) => ([state.host.realpath && data ? toPath(state, state.host.realpath(path)) : path, data] as const),
),
internalMap && new Set(arrayFrom(
internalMap.values(),
data =>
state.host.realpath && (isPackageJsonInfo(data) || data.directoryExists) ?
state.host.realpath(combinePaths(data.packageDirectory, "package.json")) :
combinePaths(data.packageDirectory, "package.json"),
)),
);
state.builderPrograms.set(projectPath, program);
}
step++;
@@ -1983,9 +1989,10 @@ function getUpToDateStatusWorker<T extends BuilderProgram>(state: SolutionBuilde
if (extendedConfigStatus) return extendedConfigStatus;
// Check package file time
const dependentPackageFileStatus = forEach(
state.lastCachedPackageJsonLookups.get(resolvedPath) || emptyArray,
([path]) => checkConfigFileUpToDateStatus(state, path, oldestOutputFileTime, oldestOutputFileName!),
const packageJsonLookups = state.lastCachedPackageJsonLookups.get(resolvedPath);
const dependentPackageFileStatus = packageJsonLookups && forEachKey(
packageJsonLookups,
path => checkConfigFileUpToDateStatus(state, path, oldestOutputFileTime, oldestOutputFileName!),
);
if (dependentPackageFileStatus) return dependentPackageFileStatus;
@@ -2376,7 +2383,7 @@ function watchWildCardDirectories<T extends BuilderProgram>(state: SolutionBuild
if (!state.watch) return;
updateWatchingWildcardDirectories(
getOrCreateValueMapFromConfigFileMap(state.allWatchedWildcardDirectories, resolvedPath),
new Map(Object.entries(parsed.wildcardDirectories!)),
parsed.wildcardDirectories,
(dir, flags) =>
state.watchDirectory(
dir,
@@ -2410,9 +2417,9 @@ function watchInputFiles<T extends BuilderProgram>(state: SolutionBuilderState<T
if (!state.watch) return;
mutateMap(
getOrCreateValueMapFromConfigFileMap(state.allWatchedInputFiles, resolvedPath),
arrayToMap(parsed.fileNames, fileName => toPath(state, fileName)),
new Set(parsed.fileNames),
{
createNewValue: (_path, input) =>
createNewValue: input =>
watchFile(
state,
input,
@@ -2431,12 +2438,12 @@ function watchPackageJsonFiles<T extends BuilderProgram>(state: SolutionBuilderS
if (!state.watch || !state.lastCachedPackageJsonLookups) return;
mutateMap(
getOrCreateValueMapFromConfigFileMap(state.allWatchedPackageJsonFiles, resolvedPath),
new Map(state.lastCachedPackageJsonLookups.get(resolvedPath)),
state.lastCachedPackageJsonLookups.get(resolvedPath),
{
createNewValue: (path, _input) =>
createNewValue: input =>
watchFile(
state,
path,
input,
() => invalidateProjectAndScheduleBuilds(state, resolvedPath, ProgramUpdateLevel.Update),
PollingInterval.High,
parsed?.watchOptions,
+2 -2
View File
@@ -4684,11 +4684,11 @@ export interface Program extends ScriptReferenceHost {
*
* @internal
*/
getMissingFilePaths(): readonly Path[];
getMissingFilePaths(): Map<Path, string>;
/** @internal */
getModuleResolutionCache(): ModuleResolutionCache | undefined;
/** @internal */
getFilesByNameMap(): Map<string, SourceFile | false | undefined>;
getFilesByNameMap(): Map<Path, SourceFile | false | undefined>;
/** @internal */
resolvedModules: Map<Path, ModeAwareCache<ResolvedModuleWithFailedLookupLocations>> | undefined;
+34 -10
View File
@@ -7829,9 +7829,12 @@ export function clearMap<K, T>(map: { forEach: Map<K, T>["forEach"]; clear: Map<
}
/** @internal */
export interface MutateMapSkippingNewValuesOptions<K, T, U> {
export interface MutateMapSkippingNewValuesDelete<K, T> {
onDeleteValue(existingValue: T, key: K): void;
}
/** @internal */
export interface MutateMapSkippingNewValuesOptions<K, T, U> extends MutateMapSkippingNewValuesDelete<K, T> {
/**
* If present this is called with the key when there is value for that key both in new map as well as existing map provided
* Caller can then decide to update or remove this key.
@@ -7846,47 +7849,68 @@ export interface MutateMapSkippingNewValuesOptions<K, T, U> {
*
* @internal
*/
export function mutateMapSkippingNewValues<K, T>(
map: Map<K, T>,
newMap: ReadonlySet<K> | undefined,
options: MutateMapSkippingNewValuesDelete<K, T>,
): void;
/** @internal */
export function mutateMapSkippingNewValues<K, T, U>(
map: Map<K, T>,
newMap: ReadonlyMap<K, U>,
newMap: ReadonlyMap<K, U> | undefined,
options: MutateMapSkippingNewValuesOptions<K, T, U>,
): void;
export function mutateMapSkippingNewValues<K, T, U>(
map: Map<K, T>,
newMap: ReadonlyMap<K, U> | ReadonlySet<K> | undefined,
options: MutateMapSkippingNewValuesOptions<K, T, U>,
) {
const { onDeleteValue, onExistingValue } = options;
// Needs update
map.forEach((existingValue, key) => {
const valueInNewMap = newMap.get(key);
// Not present any more in new map, remove it
if (valueInNewMap === undefined) {
if (!newMap?.has(key)) {
map.delete(key);
onDeleteValue(existingValue, key);
}
// If present notify about existing values
else if (onExistingValue) {
onExistingValue(existingValue, valueInNewMap, key);
onExistingValue(existingValue, (newMap as Map<K, U>).get?.(key)!, key);
}
});
}
/** @internal */
export interface MutateMapOptions<K, T, U> extends MutateMapSkippingNewValuesOptions<K, T, U> {
export interface MutateMapOptionsCreate<K, T, U> {
createNewValue(key: K, valueInNewMap: U): T;
}
/** @internal */
export interface MutateMapWithNewSetOptions<K, T> extends MutateMapSkippingNewValuesDelete<K, T>, MutateMapOptionsCreate<K, T, K> {
}
/** @internal */
export interface MutateMapOptions<K, T, U> extends MutateMapSkippingNewValuesOptions<K, T, U>, MutateMapOptionsCreate<K, T, U> {
}
/**
* Mutates the map with newMap such that keys in map will be same as newMap.
*
* @internal
*/
export function mutateMap<K, T, U>(map: Map<K, T>, newMap: ReadonlyMap<K, U>, options: MutateMapOptions<K, T, U>) {
export function mutateMap<K, T>(map: Map<K, T>, newMap: ReadonlySet<K> | undefined, options: MutateMapWithNewSetOptions<K, T>): void;
/** @internal */
export function mutateMap<K, T, U>(map: Map<K, T>, newMap: ReadonlyMap<K, U> | undefined, options: MutateMapOptions<K, T, U>): void;
export function mutateMap<K, T, U>(map: Map<K, T>, newMap: ReadonlyMap<K, U> | ReadonlySet<K> | undefined, options: MutateMapOptions<K, T, U>) {
// Needs update
mutateMapSkippingNewValues(map, newMap, options);
mutateMapSkippingNewValues(map, newMap as ReadonlyMap<K, U>, options);
const { createNewValue } = options;
// Add new values that are not already present
newMap.forEach((valueInNewMap, key) => {
newMap?.forEach((valueInNewMap, key) => {
if (!map.has(key)) {
// New values
map.set(key, createNewValue(key, valueInNewMap));
map.set(key, createNewValue(key, valueInNewMap as U & K));
}
});
}
+60 -60
View File
@@ -682,7 +682,11 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
resolutionCache.finishCachingPerDirectoryResolution(builderProgram.getProgram(), oldProgram);
// Update watches
updateMissingFilePathsWatch(builderProgram.getProgram(), missingFilesMap || (missingFilesMap = new Map()), watchMissingFilePath);
updateMissingFilePathsWatch(
builderProgram.getProgram(),
missingFilesMap || (missingFilesMap = new Map()),
watchMissingFilePath,
);
if (needsUpdateInTypeRootWatch) {
resolutionCache.updateTypeRootsWatch();
}
@@ -1053,11 +1057,18 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
}
}
function watchMissingFilePath(missingFilePath: Path) {
function watchMissingFilePath(missingFilePath: Path, missingFileName: string) {
// If watching missing referenced config file, we are already watching it so no need for separate watcher
return parsedConfigs?.has(missingFilePath) ?
noopFileWatcher :
watchFilePath(missingFilePath, missingFilePath, onMissingFileChange, PollingInterval.Medium, watchOptions, WatchType.MissingFile);
watchFilePath(
missingFilePath,
missingFileName,
onMissingFileChange,
PollingInterval.Medium,
watchOptions,
WatchType.MissingFile,
);
}
function onMissingFileChange(fileName: string, eventKind: FileWatcherEventKind, missingFilePath: Path) {
@@ -1076,16 +1087,11 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
}
function watchConfigFileWildCardDirectories() {
if (wildcardDirectories) {
updateWatchingWildcardDirectories(
watchedWildcardDirectories || (watchedWildcardDirectories = new Map()),
new Map(Object.entries(wildcardDirectories)),
watchWildcardDirectory,
);
}
else if (watchedWildcardDirectories) {
clearMap(watchedWildcardDirectories, closeFileWatcherOf);
}
updateWatchingWildcardDirectories(
watchedWildcardDirectories || (watchedWildcardDirectories = new Map()),
wildcardDirectories,
watchWildcardDirectory,
);
}
function watchWildcardDirectory(directory: string, flags: WatchDirectoryFlags) {
@@ -1187,56 +1193,50 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
WatchType.ConfigFileOfReferencedProject,
);
// Watch Wild card
if (commandLine.parsedCommandLine?.wildcardDirectories) {
updateWatchingWildcardDirectories(
commandLine.watchedDirectories ||= new Map(),
new Map(Object.entries(commandLine.parsedCommandLine?.wildcardDirectories)),
(directory, flags) =>
watchDirectory(
directory,
fileOrDirectory => {
const fileOrDirectoryPath = toPath(fileOrDirectory);
// Since the file existence changed, update the sourceFiles cache
if (cachedDirectoryStructureHost) {
cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
}
nextSourceFileVersion(fileOrDirectoryPath);
updateWatchingWildcardDirectories(
commandLine.watchedDirectories ||= new Map(),
commandLine.parsedCommandLine?.wildcardDirectories,
(directory, flags) =>
watchDirectory(
directory,
fileOrDirectory => {
const fileOrDirectoryPath = toPath(fileOrDirectory);
// Since the file existence changed, update the sourceFiles cache
if (cachedDirectoryStructureHost) {
cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
}
nextSourceFileVersion(fileOrDirectoryPath);
const config = parsedConfigs?.get(configPath);
if (!config?.parsedCommandLine) return;
if (
isIgnoredFileFromWildCardWatching({
watchedDirPath: toPath(directory),
fileOrDirectory,
fileOrDirectoryPath,
configFileName,
options: config.parsedCommandLine.options,
program: config.parsedCommandLine.fileNames,
currentDirectory,
useCaseSensitiveFileNames,
writeLog,
toPath,
})
) return;
const config = parsedConfigs?.get(configPath);
if (!config?.parsedCommandLine) return;
if (
isIgnoredFileFromWildCardWatching({
watchedDirPath: toPath(directory),
fileOrDirectory,
fileOrDirectoryPath,
configFileName,
options: config.parsedCommandLine.options,
program: config.parsedCommandLine.fileNames,
currentDirectory,
useCaseSensitiveFileNames,
writeLog,
toPath,
})
) return;
// Reload is pending, do the reload
if (config.updateLevel !== ProgramUpdateLevel.Full) {
config.updateLevel = ProgramUpdateLevel.RootNamesAndUpdate;
// Reload is pending, do the reload
if (config.updateLevel !== ProgramUpdateLevel.Full) {
config.updateLevel = ProgramUpdateLevel.RootNamesAndUpdate;
// Schedule Update the program
scheduleProgramUpdate();
}
},
flags,
commandLine.parsedCommandLine?.watchOptions || watchOptions,
WatchType.WildcardDirectoryOfReferencedProject,
),
);
}
else if (commandLine.watchedDirectories) {
clearMap(commandLine.watchedDirectories, closeFileWatcherOf);
commandLine.watchedDirectories = undefined;
}
// Schedule Update the program
scheduleProgramUpdate();
}
},
flags,
commandLine.parsedCommandLine?.watchOptions || watchOptions,
WatchType.WildcardDirectoryOfReferencedProject,
),
);
// Watch extended config files
updateExtendedConfigFilesWatches(
configPath,
+24 -42
View File
@@ -2,6 +2,7 @@ import {
arrayToMap,
binarySearch,
BuilderProgram,
clearMap,
closeFileWatcher,
compareStringsCaseSensitive,
CompilerOptions,
@@ -33,6 +34,7 @@ import {
isExcludedFile,
isSupportedSourceFileName,
map,
MapLike,
matchesExclude,
matchFiles,
mutateMap,
@@ -45,7 +47,6 @@ import {
removeFileExtension,
removeIgnoredPath,
returnNoopFileWatcher,
returnTrue,
ScriptKind,
setSysLog,
SortedArray,
@@ -315,8 +316,8 @@ export function createCachedDirectoryStructureHost(host: DirectoryStructureHost,
const baseName = getBaseNameOfFileName(fileOrDirectory);
const fsQueryResult: FileAndDirectoryExistence = {
fileExists: host.fileExists(fileOrDirectoryPath),
directoryExists: host.directoryExists(fileOrDirectoryPath),
fileExists: host.fileExists(fileOrDirectory),
directoryExists: host.directoryExists(fileOrDirectory),
};
if (fsQueryResult.directoryExists || hasEntry(parentResult.sortedAndCanonicalizedDirectories, getCanonicalFileName(baseName))) {
// Folder added or removed, clear the cache instead of updating the folder and its structure
@@ -460,27 +461,6 @@ export function cleanExtendedConfigCache(
});
}
/**
* Updates watchers based on the package json files used in module resolution
*
* @internal
*/
export function updatePackageJsonWatch(
lookups: readonly (readonly [Path, object | boolean])[],
packageJsonWatches: Map<Path, FileWatcher>,
createPackageJsonWatch: (packageJsonPath: Path, data: object | boolean) => FileWatcher,
) {
const newMap = new Map(lookups);
mutateMap(
packageJsonWatches,
newMap,
{
createNewValue: createPackageJsonWatch,
onDeleteValue: closeFileWatcher,
},
);
}
/**
* Updates the existing missing file watches with the new set of missing files after new program is created
*
@@ -489,15 +469,12 @@ export function updatePackageJsonWatch(
export function updateMissingFilePathsWatch(
program: Program,
missingFileWatches: Map<Path, FileWatcher>,
createMissingFileWatch: (missingFilePath: Path) => FileWatcher,
createMissingFileWatch: (missingFilePath: Path, missingFileName: string) => FileWatcher,
) {
const missingFilePaths = program.getMissingFilePaths();
// TODO(rbuckton): Should be a `Set` but that requires changing the below code that uses `mutateMap`
const newMissingFilePathMap = arrayToMap(missingFilePaths, identity, returnTrue);
// Update the missing file paths watcher
mutateMap(
missingFileWatches,
newMissingFilePathMap,
program.getMissingFilePaths(),
{
// Watch the missing files
createNewValue: createMissingFileWatch,
@@ -524,21 +501,26 @@ export interface WildcardDirectoryWatcher {
*/
export function updateWatchingWildcardDirectories(
existingWatchedForWildcards: Map<string, WildcardDirectoryWatcher>,
wildcardDirectories: Map<string, WatchDirectoryFlags>,
wildcardDirectories: MapLike<WatchDirectoryFlags> | undefined,
watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher,
) {
mutateMap(
existingWatchedForWildcards,
wildcardDirectories,
{
// Create new watch and recursive info
createNewValue: createWildcardDirectoryWatcher,
// Close existing watch thats not needed any more
onDeleteValue: closeFileWatcherOf,
// Close existing watch that doesnt match in the flags
onExistingValue: updateWildcardDirectoryWatcher,
},
);
if (wildcardDirectories) {
mutateMap(
existingWatchedForWildcards,
new Map(Object.entries(wildcardDirectories)),
{
// Create new watch and recursive info
createNewValue: createWildcardDirectoryWatcher,
// Close existing watch thats not needed any more
onDeleteValue: closeFileWatcherOf,
// Close existing watch that doesnt match in the flags
onExistingValue: updateWildcardDirectoryWatcher,
},
);
}
else {
clearMap(existingWatchedForWildcards, closeFileWatcherOf);
}
function createWildcardDirectoryWatcher(directory: string, flags: WatchDirectoryFlags): WildcardDirectoryWatcher {
// Create new watch and recursive info
+1 -1
View File
@@ -514,7 +514,7 @@ function verifyProgram(service: ts.server.ProjectService, project: ts.server.Pro
interface ResolveSingleModuleNameWithoutWatchingData {
resolutionToData: Map<ts.ResolutionWithFailedLookupLocations, Pick<ts.ResolvedModuleWithFailedLookupLocations, "failedLookupLocations" | "affectingLocations" | "resolutionDiagnostics">>;
packageJsonMap: Map<ts.Path, ts.PackageJsonInfo | boolean> | undefined;
packageJsonMap: Map<ts.Path, ts.PackageJsonInfoCacheEntry> | undefined;
}
function beforeResolveSingleModuleNameWithoutWatching(
+7 -7
View File
@@ -1650,7 +1650,7 @@ export class ProjectService {
*
* @internal
*/
private watchWildcardDirectory(directory: Path, flags: WatchDirectoryFlags, configFileName: NormalizedPath, config: ParsedConfig) {
private watchWildcardDirectory(directory: string, flags: WatchDirectoryFlags, configFileName: NormalizedPath, config: ParsedConfig) {
let watcher: FileWatcher | undefined = this.watchFactory.watchDirectory(
directory,
fileOrDirectory => {
@@ -1658,7 +1658,7 @@ export class ProjectService {
const fsResult = config.cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
if (
getBaseFileName(fileOrDirectoryPath) === "package.json" && !isInsideNodeModules(fileOrDirectoryPath) &&
(fsResult && fsResult.fileExists || !fsResult && this.host.fileExists(fileOrDirectoryPath))
(fsResult && fsResult.fileExists || !fsResult && this.host.fileExists(fileOrDirectory))
) {
const file = this.getNormalizedAbsolutePath(fileOrDirectory);
this.logger.info(`Config: ${configFileName} Detected new package.json: ${file}`);
@@ -1669,7 +1669,7 @@ export class ProjectService {
const configuredProjectForConfig = this.findConfiguredProjectByProjectName(configFileName);
if (
isIgnoredFileFromWildCardWatching({
watchedDirPath: directory,
watchedDirPath: this.toPath(directory),
fileOrDirectory,
fileOrDirectoryPath,
configFileName,
@@ -2666,9 +2666,9 @@ export class ProjectService {
config!.watchedDirectoriesStale = false;
updateWatchingWildcardDirectories(
config!.watchedDirectories ||= new Map(),
new Map(Object.entries(config!.parsedCommandLine!.wildcardDirectories!)),
config!.parsedCommandLine!.wildcardDirectories,
// Create new directory watcher
(directory, flags) => this.watchWildcardDirectory(directory as Path, flags, configFileName, config!),
(directory, flags) => this.watchWildcardDirectory(directory, flags, configFileName, config!),
);
}
else {
@@ -2759,7 +2759,7 @@ export class ProjectService {
projectRootFilesMap.forEach((value, path) => {
if (!newRootScriptInfoMap.has(path)) {
if (value.info) {
project.removeFile(value.info, project.fileExists(path), /*detachFromProject*/ true);
project.removeFile(value.info, project.fileExists(value.info.fileName), /*detachFromProject*/ true);
}
else {
projectRootFilesMap.delete(path);
@@ -3145,7 +3145,7 @@ export class ProjectService {
}
private getModifiedTime(info: ScriptInfo) {
return (this.host.getModifiedTime!(info.path) || missingFileModifiedTime).getTime();
return (this.host.getModifiedTime!(info.fileName) || missingFileModifiedTime).getTime();
}
private refreshScriptInfo(info: ScriptInfo) {
+3 -3
View File
@@ -1574,7 +1574,7 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
this.program,
this.missingFilesMap || (this.missingFilesMap = new Map()),
// Watch the missing files
missingFilePath => this.addMissingFileWatcher(missingFilePath),
(missingFilePath, missingFileName) => this.addMissingFileWatcher(missingFilePath, missingFileName),
);
if (this.generatedFilesMap) {
@@ -1699,14 +1699,14 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
}
}
private addMissingFileWatcher(missingFilePath: Path): FileWatcher {
private addMissingFileWatcher(missingFilePath: Path, missingFileName: string): FileWatcher {
if (isConfiguredProject(this)) {
// If this file is referenced config file, we are already watching it, no need to watch again
const configFileExistenceInfo = this.projectService.configFileExistenceInfoCache.get(missingFilePath as string as NormalizedPath);
if (configFileExistenceInfo?.config?.projects.has(this.canonicalConfigFilePath)) return noopFileWatcher;
}
const fileWatcher = this.projectService.watchFactory.watchFile(
missingFilePath,
getNormalizedAbsolutePath(missingFileName, this.currentDirectory),
(fileName, eventKind) => {
if (isConfiguredProject(this)) {
this.getCachedDirectoryStructureHost().addOrDeleteFile(fileName, missingFilePath, eventKind);
+1 -2
View File
@@ -35,7 +35,6 @@ import {
ModuleResolutionHost,
moduleSpecifiers,
normalizePath,
Path,
pathIsRelative,
Program,
PropertyAssignment,
@@ -214,7 +213,7 @@ function updateImports(
// Need an update if the imported file moved, or the importing file moved and was using a relative path.
return toImport !== undefined && (toImport.updated || (importingSourceFileMoved && pathIsRelative(importLiteral.text)))
? moduleSpecifiers.updateModuleSpecifier(program.getCompilerOptions(), sourceFile, getCanonicalFileName(newImportFromPath) as Path, toImport.newFileName, createModuleSpecifierResolutionHost(program, host), importLiteral.text)
? moduleSpecifiers.updateModuleSpecifier(program.getCompilerOptions(), sourceFile, newImportFromPath, toImport.newFileName, createModuleSpecifierResolutionHost(program, host), importLiteral.text)
: undefined;
});
}
+3 -3
View File
@@ -316,7 +316,7 @@ function getTargetFileImportsAndAddExportInOldFile(
const resolved = program.getResolvedModule(oldFile, moduleSpecifier.text, getModeForUsageLocation(oldFile, moduleSpecifier));
const fileName = resolved?.resolvedModule?.resolvedFileName;
if (fileName && targetSourceFile) {
const newModuleSpecifier = getModuleSpecifier(program.getCompilerOptions(), targetSourceFile, targetSourceFile.path, fileName, createModuleSpecifierResolutionHost(program, host));
const newModuleSpecifier = getModuleSpecifier(program.getCompilerOptions(), targetSourceFile, targetSourceFile.fileName, fileName, createModuleSpecifierResolutionHost(program, host));
append(copiedOldImports, filterImport(i, makeStringLiteral(newModuleSpecifier, quotePreference), name => importsToCopy.has(checker.getSymbolAtLocation(name)!)));
}
else {
@@ -418,7 +418,7 @@ export function updateImportsInOtherFiles(
deleteUnusedImports(sourceFile, importNode, changes, shouldMove); // These will be changed to imports from the new file
const pathToTargetFileWithExtension = resolvePath(getDirectoryPath(oldFile.path), targetFileName);
const newModuleSpecifier = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.path, pathToTargetFileWithExtension, createModuleSpecifierResolutionHost(program, host));
const newModuleSpecifier = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.fileName, pathToTargetFileWithExtension, createModuleSpecifierResolutionHost(program, host));
const newImportDeclaration = filterImport(importNode, makeStringLiteral(newModuleSpecifier, quotePreference), shouldMove);
if (newImportDeclaration) changes.insertNodeAfter(sourceFile, statement, newImportDeclaration);
@@ -570,7 +570,7 @@ export function makeImportOrRequire(
quotePreference: QuotePreference,
): AnyImportOrRequireStatement | undefined {
const pathToTargetFile = resolvePath(getDirectoryPath(sourceFile.path), targetFileNameWithExtension);
const pathToTargetFileWithCorrectExtension = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.path, pathToTargetFile, createModuleSpecifierResolutionHost(program, host));
const pathToTargetFileWithCorrectExtension = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.fileName, pathToTargetFile, createModuleSpecifierResolutionHost(program, host));
if (useEs6Imports) {
const specifiers = imports.map(i => factory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, factory.createIdentifier(i)));
+2 -2
View File
@@ -133,13 +133,13 @@ export function getSourceMapper(host: SourceMapperHost): SourceMapper {
const fileFromCache = sourceFileLike.get(path);
if (fileFromCache !== undefined) return fileFromCache ? fileFromCache : undefined;
if (!host.readFile || host.fileExists && !host.fileExists(path)) {
if (!host.readFile || host.fileExists && !host.fileExists(fileName)) {
sourceFileLike.set(path, false);
return undefined;
}
// And failing that, check the disk
const text = host.readFile(path);
const text = host.readFile(fileName);
const file = text ? createSourceFileLike(text) : false;
sourceFileLike.set(path, file);
return file ? file : undefined;
+6 -5
View File
@@ -7,8 +7,9 @@ import {
jsonToReadableText,
} from "./helpers";
function verifyMissingFilePaths(missingPaths: readonly ts.Path[], expected: readonly string[]) {
assert.isDefined(missingPaths);
function verifyMissingFilePaths(missing: ReturnType<ts.Program["getMissingFilePaths"]>, expected: readonly string[]) {
assert.isDefined(missing);
const missingPaths = ts.arrayFrom(missing.keys());
const map = new Set(expected);
for (const missing of missingPaths) {
const value = map.has(missing);
@@ -82,8 +83,8 @@ describe("unittests:: programApi:: Program.getMissingFilePaths", () => {
it("normalizes file paths", () => {
const program0 = ts.createProgram(["./nonexistent.ts", "./NONEXISTENT.ts"], options, testCompilerHost);
const program1 = ts.createProgram(["./NONEXISTENT.ts", "./nonexistent.ts"], options, testCompilerHost);
const missing0 = program0.getMissingFilePaths();
const missing1 = program1.getMissingFilePaths();
const missing0 = ts.arrayFrom(program0.getMissingFilePaths().keys());
const missing1 = ts.arrayFrom(program1.getMissingFilePaths().keys());
assert.equal(missing0.length, 1);
assert.deepEqual(missing0, missing1);
});
@@ -138,7 +139,7 @@ describe("unittests:: programApi:: Program.getMissingFilePaths", () => {
const program = ts.createProgram(["test.ts"], { module: ts.ModuleKind.ES2015 }, host);
assert(program.getSourceFiles().length === 1, "expected 'getSourceFiles' length to be 1");
assert(program.getMissingFilePaths().length === 0, "expected 'getMissingFilePaths' length to be 0");
assert(program.getMissingFilePaths().size === 0, "expected 'getMissingFilePaths' length to be 0");
assert((program.getFileProcessingDiagnostics()?.length || 0) === 0, "expected 'getFileProcessingDiagnostics' length to be 0");
});
});
@@ -53,7 +53,7 @@ describe("unittests:: Reuse program structure:: General", () => {
host.getTrace().forEach(trace => baselines.push(Utils.sanitizeTraceResolutionLogEntry(trace)));
host.clearTrace();
baselines.push("");
baselines.push(`MissingPaths:: ${jsonToReadableText(program.getMissingFilePaths())}`);
baselines.push(`MissingPaths:: ${jsonToReadableText(ts.arrayFrom(program.getMissingFilePaths().values()))}`);
baselines.push("");
baselines.push(ts.formatDiagnostics(program.getSemanticDiagnostics(), {
getCurrentDirectory: () => program.getCurrentDirectory(),
@@ -119,91 +119,95 @@ describe("unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem Watche
verifyWatchedDirectories("files not at root", "c:/", /*useProjectAtRoot*/ false);
});
it(`unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem recursive watch directory implementation does not watch files/directories in node_modules starting with "."`, () => {
const projectFolder = "/a/username/project";
const projectSrcFolder = `${projectFolder}/src`;
const configFile: File = {
path: `${projectFolder}/tsconfig.json`,
content: "{}",
};
const index: File = {
path: `${projectSrcFolder}/index.ts`,
content: `import {} from "file"`,
};
const file1: File = {
path: `${projectSrcFolder}/file1.ts`,
content: "",
};
const nodeModulesExistingUnusedFile: File = {
path: `${projectFolder}/node_modules/someFile.d.ts`,
content: "",
};
const environmentVariables = new Map<string, string>();
environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory);
const host = createServerHost([index, file1, configFile, libFile, nodeModulesExistingUnusedFile], { environmentVariables });
const session = new TestSession(host);
openFilesForSession([index], session);
describe("unittests:: tsserver:: watchEnvironment:: recursiveWatchDirectory", () => {
it(`unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem recursive watch directory implementation does not watch files/directories in node_modules starting with "."`, () => {
const projectFolder = "/a/username/project";
const projectSrcFolder = `${projectFolder}/src`;
const configFile: File = {
path: `${projectFolder}/tsconfig.json`,
content: "{}",
};
const index: File = {
path: `${projectSrcFolder}/index.ts`,
content: `import {} from "file"`,
};
const file1: File = {
path: `${projectSrcFolder}/file1.ts`,
content: "",
};
const nodeModulesExistingUnusedFile: File = {
path: `${projectFolder}/node_modules/someFile.d.ts`,
content: "",
};
const environmentVariables = new Map<string, string>();
environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory);
const host = createServerHost([index, file1, configFile, libFile, nodeModulesExistingUnusedFile], { environmentVariables });
const session = new TestSession(host);
openFilesForSession([index], session);
const nodeModulesIgnoredFileFromIgnoreDirectory: File = {
path: `${projectFolder}/node_modules/.cache/someFile.d.ts`,
content: "",
};
const nodeModulesIgnoredFileFromIgnoreDirectory: File = {
path: `${projectFolder}/node_modules/.cache/someFile.d.ts`,
content: "",
};
const nodeModulesIgnoredFile: File = {
path: `${projectFolder}/node_modules/.cacheFile.ts`,
content: "",
};
const nodeModulesIgnoredFile: File = {
path: `${projectFolder}/node_modules/.cacheFile.ts`,
content: "",
};
const gitIgnoredFileFromIgnoreDirectory: File = {
path: `${projectFolder}/.git/someFile.d.ts`,
content: "",
};
const gitIgnoredFileFromIgnoreDirectory: File = {
path: `${projectFolder}/.git/someFile.d.ts`,
content: "",
};
const gitIgnoredFile: File = {
path: `${projectFolder}/.gitCache.d.ts`,
content: "",
};
const emacsIgnoredFileFromIgnoreDirectory: File = {
path: `${projectFolder}/src/.#field.ts`,
content: "",
};
const gitIgnoredFile: File = {
path: `${projectFolder}/.gitCache.d.ts`,
content: "",
};
const emacsIgnoredFileFromIgnoreDirectory: File = {
path: `${projectFolder}/src/.#field.ts`,
content: "",
};
[
nodeModulesIgnoredFileFromIgnoreDirectory,
nodeModulesIgnoredFile,
gitIgnoredFileFromIgnoreDirectory,
gitIgnoredFile,
emacsIgnoredFileFromIgnoreDirectory,
].forEach(ignoredEntity => {
host.ensureFileOrFolder(ignoredEntity);
session.host.baselineHost("After writing ignored file or folder");
[
nodeModulesIgnoredFileFromIgnoreDirectory,
nodeModulesIgnoredFile,
gitIgnoredFileFromIgnoreDirectory,
gitIgnoredFile,
emacsIgnoredFileFromIgnoreDirectory,
].forEach(ignoredEntity => {
host.ensureFileOrFolder(ignoredEntity);
session.host.baselineHost("After writing ignored file or folder");
});
baselineTsserverLogs("watchEnvironment", `recursive directory does not watch files starting with dot in node_modules`, session);
});
baselineTsserverLogs("watchEnvironment", `recursive directory does not watch files starting with dot in node_modules`, session);
});
it("unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem watching files with network style paths", () => {
const logger = createLoggerWithInMemoryLogs(/*host*/ undefined!); // Special handling to ensure same logger is used
verifyFilePathStyle("c:/myprojects/project/x.js", logger);
verifyFilePathStyle("//vda1cs4850/myprojects/project/x.js", logger);
verifyFilePathStyle("//vda1cs4850/c$/myprojects/project/x.js", logger);
verifyFilePathStyle("c:/users/username/myprojects/project/x.js", logger);
verifyFilePathStyle("//vda1cs4850/c$/users/username/myprojects/project/x.js", logger);
baselineTsserverLogs("watchEnvironment", `watching files with network style paths`, { logger });
describe("unittests:: tsserver:: watchEnvironment:: networkStylePaths", () => {
it("unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem watching files with network style paths", () => {
const logger = createLoggerWithInMemoryLogs(/*host*/ undefined!); // Special handling to ensure same logger is used
verifyFilePathStyle("c:/myprojects/project/x.js", logger);
verifyFilePathStyle("//vda1cs4850/myprojects/project/x.js", logger);
verifyFilePathStyle("//vda1cs4850/c$/myprojects/project/x.js", logger);
verifyFilePathStyle("c:/users/username/myprojects/project/x.js", logger);
verifyFilePathStyle("//vda1cs4850/c$/users/username/myprojects/project/x.js", logger);
baselineTsserverLogs("watchEnvironment", `watching files with network style paths`, { logger });
function verifyFilePathStyle(path: string, logger: LoggerWithInMemoryLogs) {
const windowsStyleRoot = path.substring(0, ts.getRootLength(path));
const file: File = { path, content: "const x = 10" };
const host = createServerHost(
[libFile, file],
{ windowsStyleRoot },
);
logger.host = host;
logger.info(`For files of style ${path}`);
logger.log(`currentDirectory:: ${host.getCurrentDirectory()} useCaseSensitiveFileNames: ${host.useCaseSensitiveFileNames}`);
const session = new TestSession({ host, logger });
openFilesForSession([file], session);
}
function verifyFilePathStyle(path: string, logger: LoggerWithInMemoryLogs) {
const windowsStyleRoot = path.substring(0, ts.getRootLength(path));
const file: File = { path, content: "const x = 10" };
const host = createServerHost(
[libFile, file],
{ windowsStyleRoot },
);
logger.host = host;
logger.info(`For files of style ${path}`);
logger.log(`currentDirectory:: ${host.getCurrentDirectory()} useCaseSensitiveFileNames: ${host.useCaseSensitiveFileNames}`);
const session = new TestSession({ host, logger });
openFilesForSession([file], session);
}
});
});
describe("unittests:: tsserver:: watchEnvironment:: handles watch compiler options", () => {
@@ -527,3 +531,19 @@ describe("unittests:: tsserver:: watchEnvironment:: watching at workspaces codes
baselineTsserverLogs("watchEnvironment", "watching npm install in codespaces where workspaces folder is hosted at root", session);
});
});
describe("unittests:: tsserver:: watchEnvironment:: perVolumeCasing", () => {
it("new file addition", () => {
const host = createServerHost([libFile]);
// Make /Volumes case sensitive
host.getCanonicalFileName = s => ts.startsWith(s, "/Volumes/") ? s : ts.toFileNameLowerCase(s);
host.ensureFileOrFolder({ path: "/Volumes/git/projects/project/foo.ts", content: `export const foo = "foo";` });
host.writeFile("/Volumes/git/projects/project/tsconfig.json", "{ }");
host.writeFile("/Volumes/git/projects/project/package.json", jsonToReadableText({ name: "project", version: "1.0.0" }));
const session = new TestSession(host);
openFilesForSession(["/Volumes/git/projects/project/foo.ts"], session);
host.writeFile("/Volumes/git/projects/project/Bar.ts", `export const bar = "bar";`);
host.runQueuedTimeoutCallbacks();
baselineTsserverLogs("watchEnvironment", "perVolumeCasing and new file addition", session);
});
});