mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Instead of watching files in typing installer, forward it to projectService (#54028)
This commit is contained in:
@@ -692,6 +692,8 @@ export const WatchType: WatchTypeRegistry = {
|
||||
NoopConfigFileForInferredRoot: "Noop Config file for the inferred project root",
|
||||
MissingGeneratedFile: "Missing generated file",
|
||||
NodeModulesForModuleSpecifierCache: "node_modules for module specifier cache invalidation",
|
||||
TypingInstallerLocationFile: "File location for typing installer",
|
||||
TypingInstallerLocationDirectory: "Directory location for typing installer",
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
@@ -717,6 +719,8 @@ export interface WatchTypeRegistry {
|
||||
NoopConfigFileForInferredRoot: "Noop Config file for the inferred project root",
|
||||
MissingGeneratedFile: "Missing generated file",
|
||||
NodeModulesForModuleSpecifierCache: "node_modules for module specifier cache invalidation",
|
||||
TypingInstallerLocationFile: "File location for typing installer",
|
||||
TypingInstallerLocationDirectory: "Directory location for typing installer",
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
||||
@@ -10,6 +10,7 @@ export type EventTypesRegistry = "event::typesRegistry";
|
||||
export type EventBeginInstallTypes = "event::beginInstallTypes";
|
||||
export type EventEndInstallTypes = "event::endInstallTypes";
|
||||
export type EventInitializationFailed = "event::initializationFailed";
|
||||
export type ActionWatchTypingLocations = "action::watchTypingLocations";
|
||||
/** @internal */
|
||||
export const ActionSet: ActionSet = "action::set";
|
||||
/** @internal */
|
||||
@@ -24,6 +25,8 @@ export const EventBeginInstallTypes: EventBeginInstallTypes = "event::beginInsta
|
||||
export const EventEndInstallTypes: EventEndInstallTypes = "event::endInstallTypes";
|
||||
/** @internal */
|
||||
export const EventInitializationFailed: EventInitializationFailed = "event::initializationFailed";
|
||||
/** @internal */
|
||||
export const ActionWatchTypingLocations: ActionWatchTypingLocations = "action::watchTypingLocations";
|
||||
|
||||
/** @internal */
|
||||
export namespace Arguments {
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import {
|
||||
CompilerOptions,
|
||||
DirectoryWatcherCallback,
|
||||
FileWatcher,
|
||||
FileWatcherCallback,
|
||||
JsTyping,
|
||||
MapLike,
|
||||
Path,
|
||||
SortedReadonlyArray,
|
||||
TypeAcquisition,
|
||||
WatchOptions,
|
||||
} from "./_namespaces/ts";
|
||||
import {
|
||||
ActionInvalidate,
|
||||
ActionPackageInstalled,
|
||||
ActionSet,
|
||||
ActionWatchTypingLocations,
|
||||
EventBeginInstallTypes,
|
||||
EventEndInstallTypes,
|
||||
EventInitializationFailed,
|
||||
@@ -21,7 +18,7 @@ import {
|
||||
} from "./_namespaces/ts.server";
|
||||
|
||||
export interface TypingInstallerResponse {
|
||||
readonly kind: ActionSet | ActionInvalidate | EventTypesRegistry | ActionPackageInstalled | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed;
|
||||
readonly kind: ActionSet | ActionInvalidate | EventTypesRegistry | ActionPackageInstalled | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed | ActionWatchTypingLocations;
|
||||
}
|
||||
|
||||
export interface TypingInstallerRequestWithProjectName {
|
||||
@@ -35,7 +32,6 @@ export interface DiscoverTypings extends TypingInstallerRequestWithProjectName {
|
||||
readonly fileNames: string[];
|
||||
readonly projectRootPath: Path;
|
||||
readonly compilerOptions: CompilerOptions;
|
||||
readonly watchOptions?: WatchOptions;
|
||||
readonly typeAcquisition: TypeAcquisition;
|
||||
readonly unresolvedImports: SortedReadonlyArray<string>;
|
||||
readonly cachePath?: string;
|
||||
@@ -104,8 +100,6 @@ export interface InstallTypingHost extends JsTyping.TypingResolutionHost {
|
||||
writeFile(path: string, content: string): void;
|
||||
createDirectory(path: string): void;
|
||||
getCurrentDirectory?(): string;
|
||||
watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher;
|
||||
watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher;
|
||||
}
|
||||
|
||||
export interface SetTypings extends ProjectResponse {
|
||||
@@ -116,5 +110,11 @@ export interface SetTypings extends ProjectResponse {
|
||||
readonly kind: ActionSet;
|
||||
}
|
||||
|
||||
export interface WatchTypingLocations extends ProjectResponse {
|
||||
/** if files is undefined, retain same set of watchers */
|
||||
readonly files: readonly string[] | undefined;
|
||||
readonly kind: ActionWatchTypingLocations;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export type TypingInstallerResponseUnion = SetTypings | InvalidateCachedTypings | TypesRegistryResponse | PackageInstalledResponse | InstallTypes | InitializationFailedResponse;
|
||||
export type TypingInstallerResponseUnion = SetTypings | InvalidateCachedTypings | TypesRegistryResponse | PackageInstalledResponse | InstallTypes | InitializationFailedResponse | WatchTypingLocations;
|
||||
|
||||
@@ -176,6 +176,7 @@ import {
|
||||
ThrottledOperations,
|
||||
toNormalizedPath,
|
||||
TypingsCache,
|
||||
WatchTypingLocations,
|
||||
} from "./_namespaces/ts.server";
|
||||
import * as protocol from "./protocol";
|
||||
|
||||
@@ -1153,6 +1154,11 @@ export class ProjectService {
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
watchTypingLocations(response: WatchTypingLocations) {
|
||||
this.findProject(response.projectName)?.watchTypingLocations(response.files);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
delayEnsureProjectForOpenFiles() {
|
||||
if (!this.openFiles.size) return;
|
||||
|
||||
@@ -13,16 +13,19 @@ import {
|
||||
closeFileWatcher,
|
||||
closeFileWatcherOf,
|
||||
combinePaths,
|
||||
comparePaths,
|
||||
CompilerHost,
|
||||
CompilerOptions,
|
||||
concatenate,
|
||||
ConfigFileProgramReloadLevel,
|
||||
containsPath,
|
||||
createCacheableExportInfoMap,
|
||||
createLanguageService,
|
||||
createResolutionCache,
|
||||
createSymlinkCache,
|
||||
Debug,
|
||||
Diagnostic,
|
||||
directorySeparator,
|
||||
DirectoryStructureHost,
|
||||
DirectoryWatcherCallback,
|
||||
DocumentPositionMapper,
|
||||
@@ -45,6 +48,7 @@ import {
|
||||
generateDjb2Hash,
|
||||
getAllowJSCompilerOption,
|
||||
getAutomaticTypeDirectiveNames,
|
||||
getBaseFileName,
|
||||
GetCanonicalFileName,
|
||||
getDeclarationEmitOutputFilePathWorker,
|
||||
getDefaultCompilerOptions,
|
||||
@@ -126,6 +130,7 @@ import {
|
||||
WatchType,
|
||||
} from "./_namespaces/ts";
|
||||
import {
|
||||
ActionInvalidate,
|
||||
asNormalizedPath,
|
||||
createModuleSpecifierCache,
|
||||
emptyArray,
|
||||
@@ -287,6 +292,14 @@ export interface EmitResult {
|
||||
diagnostics: readonly Diagnostic[];
|
||||
}
|
||||
|
||||
const enum TypingWatcherType {
|
||||
FileWatcher = "FileWatcher",
|
||||
DirectoryWatcher = "DirectoryWatcher"
|
||||
}
|
||||
|
||||
type TypingWatchers = Map<Path, FileWatcher> & { isInvoked?: boolean; };
|
||||
|
||||
|
||||
export abstract class Project implements LanguageServiceHost, ModuleResolutionHost {
|
||||
private rootFiles: ScriptInfo[] = [];
|
||||
private rootFilesMap = new Map<string, ProjectRootFile>();
|
||||
@@ -370,6 +383,9 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
|
||||
/** @internal */
|
||||
typingFiles: SortedReadonlyArray<string> = emptyArray;
|
||||
|
||||
/** @internal */
|
||||
private typingWatchers: TypingWatchers | undefined;
|
||||
|
||||
/** @internal */
|
||||
originalConfiguredProjects: Set<NormalizedPath> | undefined;
|
||||
|
||||
@@ -1013,6 +1029,7 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
|
||||
|
||||
close() {
|
||||
this.projectService.typingsCache.onProjectClosed(this);
|
||||
this.closeWatchingTypingLocations();
|
||||
if (this.program) {
|
||||
// if we have a program - release all files that are enlisted in program but arent root
|
||||
// The releasing of the roots happens later
|
||||
@@ -1361,6 +1378,108 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
private closeWatchingTypingLocations() {
|
||||
if (this.typingWatchers) clearMap(this.typingWatchers, closeFileWatcher);
|
||||
this.typingWatchers = undefined;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
private onTypingInstallerWatchInvoke() {
|
||||
this.typingWatchers!.isInvoked = true;
|
||||
this.projectService.updateTypingsForProject({ projectName: this.getProjectName(), kind: ActionInvalidate });
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
watchTypingLocations(files: readonly string[] | undefined) {
|
||||
if (!files) {
|
||||
this.typingWatchers!.isInvoked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!files.length) {
|
||||
// shut down existing watchers
|
||||
this.closeWatchingTypingLocations();
|
||||
return;
|
||||
}
|
||||
|
||||
const toRemove = new Map(this.typingWatchers);
|
||||
if (!this.typingWatchers) this.typingWatchers = new Map();
|
||||
|
||||
// handler should be invoked once for the entire set of files since it will trigger full rediscovery of typings
|
||||
this.typingWatchers.isInvoked = false;
|
||||
const createProjectWatcher = (path: string, typingsWatcherType: TypingWatcherType) => {
|
||||
const canonicalPath = this.toPath(path);
|
||||
toRemove.delete(canonicalPath);
|
||||
if (!this.typingWatchers!.has(canonicalPath)) {
|
||||
this.typingWatchers!.set(canonicalPath, typingsWatcherType === TypingWatcherType.FileWatcher ?
|
||||
this.projectService.watchFactory.watchFile(
|
||||
path,
|
||||
() => !this.typingWatchers!.isInvoked ?
|
||||
this.onTypingInstallerWatchInvoke() :
|
||||
this.writeLog(`TypingWatchers already invoked`),
|
||||
PollingInterval.High,
|
||||
this.projectService.getWatchOptions(this),
|
||||
WatchType.TypingInstallerLocationFile,
|
||||
this,
|
||||
) :
|
||||
this.projectService.watchFactory.watchDirectory(
|
||||
path,
|
||||
f => {
|
||||
if (this.typingWatchers!.isInvoked) return this.writeLog(`TypingWatchers already invoked`);
|
||||
if (!fileExtensionIs(f, Extension.Json)) return this.writeLog(`Ignoring files that are not *.json`);
|
||||
if (comparePaths(f, combinePaths(this.projectService.typingsInstaller.globalTypingsCacheLocation!, "package.json"), !this.useCaseSensitiveFileNames())) return this.writeLog(`Ignoring package.json change at global typings location`);
|
||||
this.onTypingInstallerWatchInvoke();
|
||||
},
|
||||
WatchDirectoryFlags.Recursive,
|
||||
this.projectService.getWatchOptions(this),
|
||||
WatchType.TypingInstallerLocationDirectory,
|
||||
this,
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Create watches from list of files
|
||||
for (const file of files) {
|
||||
const basename = getBaseFileName(file);
|
||||
if (basename === "package.json" || basename === "bower.json") {
|
||||
// package.json or bower.json exists, watch the file to detect changes and update typings
|
||||
createProjectWatcher(file, TypingWatcherType.FileWatcher);
|
||||
continue;
|
||||
}
|
||||
|
||||
// path in projectRoot, watch project root
|
||||
if (containsPath(this.currentDirectory, file, this.currentDirectory, !this.useCaseSensitiveFileNames())) {
|
||||
const subDirectory = file.indexOf(directorySeparator, this.currentDirectory.length + 1);
|
||||
if (subDirectory !== -1) {
|
||||
// Watch subDirectory
|
||||
createProjectWatcher(file.substr(0, subDirectory), TypingWatcherType.DirectoryWatcher);
|
||||
}
|
||||
else {
|
||||
// Watch the directory itself
|
||||
createProjectWatcher(file, TypingWatcherType.DirectoryWatcher);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// path in global cache, watch global cache
|
||||
if (containsPath(this.projectService.typingsInstaller.globalTypingsCacheLocation!, file, this.currentDirectory, !this.useCaseSensitiveFileNames())) {
|
||||
createProjectWatcher(this.projectService.typingsInstaller.globalTypingsCacheLocation!, TypingWatcherType.DirectoryWatcher);
|
||||
continue;
|
||||
}
|
||||
|
||||
// watch node_modules or bower_components
|
||||
createProjectWatcher(file, TypingWatcherType.DirectoryWatcher);
|
||||
}
|
||||
|
||||
// Remove unused watches
|
||||
toRemove.forEach((watch, path) => {
|
||||
watch.close();
|
||||
this.typingWatchers!.delete(path);
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
getCurrentProgram(): Program | undefined {
|
||||
return this.program;
|
||||
|
||||
@@ -46,7 +46,6 @@ export function createInstallTypingsRequest(project: Project, typeAcquisition: T
|
||||
projectName: project.getProjectName(),
|
||||
fileNames: project.getFileNames(/*excludeFilesFromExternalLibraries*/ true, /*excludeConfigFiles*/ true).concat(project.getExcludedFiles() as NormalizedPath[]),
|
||||
compilerOptions: project.getCompilationSettings(),
|
||||
watchOptions: project.projectService.getWatchOptions(project),
|
||||
typeAcquisition,
|
||||
unresolvedImports,
|
||||
projectRootPath: project.getCurrentDirectory() as Path,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as Harness from "../../_namespaces/Harness";
|
||||
import * as ts from "../../_namespaces/ts";
|
||||
import { ActionWatchTypingLocations } from "../../_namespaces/ts.server";
|
||||
import { ensureErrorFreeBuild } from "./solutionBuilder";
|
||||
import {
|
||||
changeToHostTrackingWrittenFiles,
|
||||
@@ -280,11 +281,12 @@ export class TestTypingsInstallerWorker extends ts.server.typingsInstaller.Typin
|
||||
this.addPostExecAction("success", requestId, packageNames, cb);
|
||||
}
|
||||
|
||||
sendResponse(response: ts.server.SetTypings | ts.server.InvalidateCachedTypings) {
|
||||
sendResponse(response: ts.server.SetTypings | ts.server.InvalidateCachedTypings | ts.server.WatchTypingLocations) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Sending response:\n ${JSON.stringify(response)}`);
|
||||
}
|
||||
this.projectService.updateTypingsForProject(response);
|
||||
if (response.kind !== ActionWatchTypingLocations) this.projectService.updateTypingsForProject(response);
|
||||
else this.projectService.watchTypingLocations(response);
|
||||
}
|
||||
|
||||
enqueueInstallTypingsRequest(project: ts.server.Project, typeAcquisition: ts.TypeAcquisition, unresolvedImports: ts.SortedReadonlyArray<string>) {
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
ActionInvalidate,
|
||||
ActionPackageInstalled,
|
||||
ActionSet,
|
||||
ActionWatchTypingLocations,
|
||||
Arguments,
|
||||
BeginInstallTypes,
|
||||
createInstallTypingsRequest,
|
||||
@@ -669,7 +670,7 @@ function startNodeSession(options: StartSessionOptions, logger: Logger, cancella
|
||||
}
|
||||
}
|
||||
|
||||
private handleMessage(response: TypesRegistryResponse | PackageInstalledResponse | SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse) {
|
||||
private handleMessage(response: TypesRegistryResponse | PackageInstalledResponse | SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse | server.WatchTypingLocations) {
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`Received response:${stringifyIndented(response)}`);
|
||||
}
|
||||
@@ -765,6 +766,9 @@ function startNodeSession(options: StartSessionOptions, logger: Logger, cancella
|
||||
|
||||
break;
|
||||
}
|
||||
case ActionWatchTypingLocations:
|
||||
this.projectService.watchTypingLocations(response);
|
||||
break;
|
||||
default:
|
||||
assertType<never>(response);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
import {
|
||||
clearMap,
|
||||
closeFileWatcher,
|
||||
combinePaths,
|
||||
compareStringsCaseInsensitive,
|
||||
Comparison,
|
||||
containsPath,
|
||||
copyEntries,
|
||||
createGetCanonicalFileName,
|
||||
directorySeparator,
|
||||
Extension,
|
||||
fileExtensionIs,
|
||||
FileWatcher,
|
||||
forEachKey,
|
||||
getBaseFileName,
|
||||
GetCanonicalFileName,
|
||||
getDirectoryPath,
|
||||
getProperty,
|
||||
getWatchFactory,
|
||||
hasProperty,
|
||||
JsTyping,
|
||||
mangleScopedPackageName,
|
||||
@@ -24,20 +12,14 @@ import {
|
||||
ModuleResolutionKind,
|
||||
noop,
|
||||
Path,
|
||||
PollingInterval,
|
||||
resolveModuleName,
|
||||
Version,
|
||||
version,
|
||||
versionMajorMinor,
|
||||
WatchDirectoryFlags,
|
||||
WatchFactory,
|
||||
WatchFactoryHost,
|
||||
WatchLogLevel,
|
||||
WatchOptions,
|
||||
} from "./_namespaces/ts";
|
||||
import {
|
||||
ActionInvalidate,
|
||||
ActionSet,
|
||||
ActionWatchTypingLocations,
|
||||
BeginInstallTypes,
|
||||
CloseProject,
|
||||
DiscoverTypings,
|
||||
@@ -47,6 +29,7 @@ import {
|
||||
InstallTypingHost,
|
||||
InvalidateCachedTypings,
|
||||
SetTypings,
|
||||
WatchTypingLocations,
|
||||
} from "./_namespaces/ts.server";
|
||||
|
||||
interface NpmConfig {
|
||||
@@ -115,49 +98,19 @@ export interface PendingRequest {
|
||||
onRequestCompleted: RequestCompletedAction;
|
||||
}
|
||||
|
||||
function endsWith(str: string, suffix: string, caseSensitive: boolean): boolean {
|
||||
const expectedPos = str.length - suffix.length;
|
||||
return expectedPos >= 0 &&
|
||||
(str.indexOf(suffix, expectedPos) === expectedPos ||
|
||||
(!caseSensitive && compareStringsCaseInsensitive(str.substr(expectedPos), suffix) === Comparison.EqualTo));
|
||||
}
|
||||
|
||||
function isPackageOrBowerJson(fileName: string, caseSensitive: boolean) {
|
||||
return endsWith(fileName, "/package.json", caseSensitive) || endsWith(fileName, "/bower.json", caseSensitive);
|
||||
}
|
||||
|
||||
function sameFiles(a: string, b: string, caseSensitive: boolean) {
|
||||
return a === b || (!caseSensitive && compareStringsCaseInsensitive(a, b) === Comparison.EqualTo);
|
||||
}
|
||||
|
||||
const enum ProjectWatcherType {
|
||||
FileWatcher = "FileWatcher",
|
||||
DirectoryWatcher = "DirectoryWatcher"
|
||||
}
|
||||
|
||||
type ProjectWatchers = Map<string, FileWatcher> & { isInvoked?: boolean; };
|
||||
|
||||
function getDetailWatchInfo(projectName: string, watchers: ProjectWatchers | undefined) {
|
||||
return `Project: ${projectName} watcher already invoked: ${watchers?.isInvoked}`;
|
||||
}
|
||||
|
||||
export abstract class TypingsInstaller {
|
||||
private readonly packageNameToTypingLocation = new Map<string, JsTyping.CachedTyping>();
|
||||
private readonly missingTypingsSet = new Set<string>();
|
||||
private readonly knownCachesSet = new Set<string>();
|
||||
private readonly projectWatchers = new Map<string, ProjectWatchers>();
|
||||
private readonly projectWatchers = new Map<string, Set<string>>();
|
||||
private safeList: JsTyping.SafeList | undefined;
|
||||
/** @internal */
|
||||
readonly pendingRunRequests: PendingRequest[] = [];
|
||||
private readonly toCanonicalFileName: GetCanonicalFileName;
|
||||
private readonly globalCachePackageJsonPath: string;
|
||||
|
||||
private installRunCount = 1;
|
||||
private inFlightRequestCount = 0;
|
||||
|
||||
abstract readonly typesRegistry: Map<string, MapLike<string>>;
|
||||
/** @internal */
|
||||
private readonly watchFactory: WatchFactory<string, ProjectWatchers>;
|
||||
|
||||
constructor(
|
||||
protected readonly installTypingHost: InstallTypingHost,
|
||||
@@ -166,13 +119,10 @@ export abstract class TypingsInstaller {
|
||||
private readonly typesMapLocation: Path,
|
||||
private readonly throttleLimit: number,
|
||||
protected readonly log = nullLog) {
|
||||
this.toCanonicalFileName = createGetCanonicalFileName(installTypingHost.useCaseSensitiveFileNames);
|
||||
this.globalCachePackageJsonPath = combinePaths(globalCachePath, "package.json");
|
||||
const isLoggingEnabled = this.log.isEnabled();
|
||||
if (isLoggingEnabled) {
|
||||
this.log.writeLine(`Global cache location '${globalCachePath}', safe file path '${safeListPath}', types map path ${typesMapLocation}`);
|
||||
}
|
||||
this.watchFactory = getWatchFactory(this.installTypingHost as WatchFactoryHost, isLoggingEnabled ? WatchLogLevel.Verbose : WatchLogLevel.None, s => this.log.writeLine(s), getDetailWatchInfo);
|
||||
this.processCacheLocation(this.globalCachePath);
|
||||
}
|
||||
|
||||
@@ -191,14 +141,16 @@ export abstract class TypingsInstaller {
|
||||
}
|
||||
return;
|
||||
}
|
||||
clearMap(watchers, closeFileWatcher);
|
||||
// Close all the watchers
|
||||
this.projectWatchers.delete(projectName);
|
||||
this.sendResponse({ kind: ActionWatchTypingLocations, projectName, files: [] });
|
||||
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Closing file watchers for project '${projectName}' - done.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
install(req: DiscoverTypings) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Got install request ${JSON.stringify(req)}`);
|
||||
@@ -232,7 +184,7 @@ export abstract class TypingsInstaller {
|
||||
}
|
||||
|
||||
// start watching files
|
||||
this.watchFiles(req.projectName, discoverTypingsResult.filesToWatch, req.projectRootPath, req.watchOptions);
|
||||
this.watchFiles(req.projectName, discoverTypingsResult.filesToWatch);
|
||||
|
||||
// install typings
|
||||
if (discoverTypingsResult.newTypingNames.length) {
|
||||
@@ -456,101 +408,23 @@ export abstract class TypingsInstaller {
|
||||
}
|
||||
}
|
||||
|
||||
private watchFiles(projectName: string, files: string[], projectRootPath: Path, options: WatchOptions | undefined) {
|
||||
private watchFiles(projectName: string, files: string[]) {
|
||||
if (!files.length) {
|
||||
// shut down existing watchers
|
||||
this.closeWatchers(projectName);
|
||||
return;
|
||||
}
|
||||
|
||||
let watchers = this.projectWatchers.get(projectName)!;
|
||||
const toRemove = new Map<string, FileWatcher>();
|
||||
if (!watchers) {
|
||||
watchers = new Map();
|
||||
this.projectWatchers.set(projectName, watchers);
|
||||
const existing = this.projectWatchers.get(projectName);
|
||||
const newSet = new Set(files);
|
||||
if (!existing || forEachKey(newSet, s => !existing.has(s)) || forEachKey(existing, s => !newSet.has(s))) {
|
||||
this.projectWatchers.set(projectName, newSet);
|
||||
this.sendResponse({ kind: ActionWatchTypingLocations, projectName, files });
|
||||
}
|
||||
else {
|
||||
copyEntries(watchers, toRemove);
|
||||
// Keep same list of files
|
||||
this.sendResponse({ kind: ActionWatchTypingLocations, projectName, files: undefined });
|
||||
}
|
||||
|
||||
// handler should be invoked once for the entire set of files since it will trigger full rediscovery of typings
|
||||
watchers.isInvoked = false;
|
||||
|
||||
const isLoggingEnabled = this.log.isEnabled();
|
||||
const createProjectWatcher = (path: string, projectWatcherType: ProjectWatcherType) => {
|
||||
const canonicalPath = this.toCanonicalFileName(path);
|
||||
toRemove.delete(canonicalPath);
|
||||
if (watchers.has(canonicalPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLoggingEnabled) {
|
||||
this.log.writeLine(`${projectWatcherType}:: Added:: WatchInfo: ${path}`);
|
||||
}
|
||||
const watcher = projectWatcherType === ProjectWatcherType.FileWatcher ?
|
||||
this.watchFactory.watchFile(path, () => {
|
||||
if (!watchers.isInvoked) {
|
||||
watchers.isInvoked = true;
|
||||
this.sendResponse({ projectName, kind: ActionInvalidate });
|
||||
}
|
||||
}, PollingInterval.High, options, projectName, watchers) :
|
||||
this.watchFactory.watchDirectory(path, f => {
|
||||
if (watchers.isInvoked || !fileExtensionIs(f, Extension.Json)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPackageOrBowerJson(f, this.installTypingHost.useCaseSensitiveFileNames) &&
|
||||
!sameFiles(f, this.globalCachePackageJsonPath, this.installTypingHost.useCaseSensitiveFileNames)) {
|
||||
watchers.isInvoked = true;
|
||||
this.sendResponse({ projectName, kind: ActionInvalidate });
|
||||
}
|
||||
}, WatchDirectoryFlags.Recursive, options, projectName, watchers);
|
||||
|
||||
watchers.set(canonicalPath, isLoggingEnabled ? {
|
||||
close: () => {
|
||||
this.log.writeLine(`${projectWatcherType}:: Closed:: WatchInfo: ${path}`);
|
||||
watcher.close();
|
||||
}
|
||||
} : watcher);
|
||||
};
|
||||
|
||||
// Create watches from list of files
|
||||
for (const file of files) {
|
||||
if (file.endsWith("/package.json") || file.endsWith("/bower.json")) {
|
||||
// package.json or bower.json exists, watch the file to detect changes and update typings
|
||||
createProjectWatcher(file, ProjectWatcherType.FileWatcher);
|
||||
continue;
|
||||
}
|
||||
|
||||
// path in projectRoot, watch project root
|
||||
if (containsPath(projectRootPath, file, projectRootPath, !this.installTypingHost.useCaseSensitiveFileNames)) {
|
||||
const subDirectory = file.indexOf(directorySeparator, projectRootPath.length + 1);
|
||||
if (subDirectory !== -1) {
|
||||
// Watch subDirectory
|
||||
createProjectWatcher(file.substr(0, subDirectory), ProjectWatcherType.DirectoryWatcher);
|
||||
}
|
||||
else {
|
||||
// Watch the directory itself
|
||||
createProjectWatcher(file, ProjectWatcherType.DirectoryWatcher);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// path in global cache, watch global cache
|
||||
if (containsPath(this.globalCachePath, file, projectRootPath, !this.installTypingHost.useCaseSensitiveFileNames)) {
|
||||
createProjectWatcher(this.globalCachePath, ProjectWatcherType.DirectoryWatcher);
|
||||
continue;
|
||||
}
|
||||
|
||||
// watch node_modules or bower_components
|
||||
createProjectWatcher(file, ProjectWatcherType.DirectoryWatcher);
|
||||
}
|
||||
|
||||
// Remove unused watches
|
||||
toRemove.forEach((watch, path) => {
|
||||
watch.close();
|
||||
watchers.delete(path);
|
||||
});
|
||||
}
|
||||
|
||||
private createSetTypings(request: DiscoverTypings, typings: string[]): SetTypings {
|
||||
@@ -582,7 +456,7 @@ export abstract class TypingsInstaller {
|
||||
}
|
||||
|
||||
protected abstract installWorker(requestId: number, packageNames: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void;
|
||||
protected abstract sendResponse(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes): void;
|
||||
protected abstract sendResponse(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | WatchTypingLocations): void;
|
||||
|
||||
protected readonly latestDistTag = "latest";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user