mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
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:
co-authored by
Andrew Branch
parent
c32ad95b96
commit
b6121e400c
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user