Skip to content

Commit 315e59f

Browse files
committed
Add searchPaths option so we know where to look for watchFactory from server which follows same locations as other Plugins
1 parent 6cfd2df commit 315e59f

File tree

28 files changed

+655
-613
lines changed

28 files changed

+655
-613
lines changed

src/compiler/sys.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,7 @@ export interface ImportPluginResult<T> {
858858
/** @internal */
859859
export function resolveModule<T = {}>(
860860
pluginConfigEntry: PluginImport,
861-
searchPaths: string[],
861+
searchPaths: readonly string[],
862862
host: Pick<System, "require" | "resolvePath">,
863863
log: (message: string) => void,
864864
): ImportPluginResult<T> {
@@ -957,9 +957,12 @@ export function createSystemWatchFunctions({
957957
sysLog(`Skipped loading watchFactory ${isString(options.watchFactory) ? options.watchFactory : JSON.stringify(options.watchFactory)} because it can be named with only package name`);
958958
return setUserWatchFactory(options, /*userWatchFactory*/ undefined);
959959
}
960-
const searchPaths = [
961-
combinePaths(system.getExecutingFilePath(), "../../..")
962-
];
960+
const host = options.getHost?.();
961+
const searchPaths = host ?
962+
host.searchPaths :
963+
[
964+
combinePaths(system.getExecutingFilePath(), "../../..")
965+
];
963966
sysLog(`Enabling watchFactory ${isString(options.watchFactory) ? options.watchFactory : JSON.stringify(options.watchFactory)} from candidate paths: ${searchPaths.join(",")}`);
964967
const { resolvedModule, errorLogs, pluginConfigEntry } = resolveModule<UserWatchFactoryModule>(
965968
isString(options.watchFactory) ? { name: options.watchFactory } : options.watchFactory,

src/compiler/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6787,6 +6787,10 @@ export interface UserWatchFactory {
67876787
watchDirectory?(fileName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher;
67886788
onConfigurationChanged?(config: any): void;
67896789
}
6790+
/**@internal*/
6791+
export interface WatchOptionsFactoryHost {
6792+
searchPaths: readonly string[];
6793+
}
67906794
export interface WatchOptions {
67916795
watchFile?: WatchFileKind;
67926796
watchDirectory?: WatchDirectoryKind;
@@ -6798,6 +6802,7 @@ export interface WatchOptions {
67986802

67996803
// All the internal properties are set as non enumerable and non configurable so that they arenot enumerated when checking if options have changed
68006804
/** @internal */ getResolvedWatchFactory?(): UserWatchFactory | undefined;
6805+
/** @internal */ getHost?(): WatchOptionsFactoryHost;
68016806

68026807
[option: string]: CompilerOptionsValue | Function | undefined;
68036808
}

src/server/editorServices.ts

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ import {
2424
getWatchFactory, hasExtension, hasProperty, hasTSFileExtension, HostCancellationToken, identity,
2525
IncompleteCompletionsCache, IndentStyle, isArray, isIgnoredFileFromWildCardWatching, isInsideNodeModules,
2626
isJsonEqual, isNodeModulesDirectory, isRootedDiskPath, isString, LanguageServiceMode, length, map,
27-
mapDefinedEntries, mapDefinedIterator, missingFileModifiedTime, MultiMap, noop, normalizePath, normalizeSlashes,
27+
mapDefinedEntries, mapDefinedIterator, memoize, missingFileModifiedTime, MultiMap, noop, normalizePath, normalizeSlashes,
2828
optionDeclarations, optionsForWatch, PackageJsonAutoImportPreference, ParsedCommandLine,
2929
parseJsonSourceFileConfigFileContent, parseJsonText, parsePackageName, Path, PerformanceEvent, PluginImport,
3030
PollingInterval, ProjectPackageJsonInfo, ProjectReference, ReadMapFile, ReadonlyCollection, removeFileExtension,
3131
removeIgnoredPath, removeMinAndVersionNumbers, ResolvedProjectReference, resolveModule, resolveProjectReferencePath,
32-
returnNoopFileWatcher, returnTrue, ScriptKind, SharedExtendedConfigFileWatcher, some, SourceFile, SourceFileLike, startsWith,
32+
returnNoopFileWatcher, returnTrue, ScriptKind, setWatchOptionInternalProperty, SharedExtendedConfigFileWatcher, some, SourceFile, SourceFileLike, startsWith,
3333
Ternary, TextChange, toFileNameLowerCase, toPath, tracing, tryAddToSet, tryReadFile, TsConfigSourceFile,
3434
TypeAcquisition, typeAcquisitionDeclarations, unorderedRemoveItem, updateSharedExtendedConfigFileWatcher,
3535
updateWatchingWildcardDirectories, UserPreferences, version, WatchDirectoryFlags, WatchFactory, WatchLogLevel,
@@ -1360,7 +1360,7 @@ export class ProjectService {
13601360
*
13611361
* @internal
13621362
*/
1363-
private watchWildcardDirectory(directory: Path, flags: WatchDirectoryFlags, configFileName: NormalizedPath, config: ParsedConfig) {
1363+
private watchWildcardDirectory(directory: Path, flags: WatchDirectoryFlags, configFileName: NormalizedPath, canonicalConfigFilePath: NormalizedPath, config: ParsedConfig) {
13641364
return this.watchFactory.watchDirectory(
13651365
directory,
13661366
fileOrDirectory => {
@@ -1419,7 +1419,7 @@ export class ProjectService {
14191419
});
14201420
},
14211421
flags,
1422-
this.getWatchOptionsFromProjectWatchOptions(config.parsedCommandLine!.watchOptions),
1422+
this.getWatchOptionsFromProjectWatchOptions(config.parsedCommandLine!.watchOptions, canonicalConfigFilePath),
14231423
WatchType.WildcardDirectory,
14241424
configFileName
14251425
);
@@ -1733,7 +1733,7 @@ export class ProjectService {
17331733
configFileName,
17341734
(_fileName, eventKind) => this.onConfigFileChanged(canonicalConfigFilePath, eventKind),
17351735
PollingInterval.High,
1736-
this.getWatchOptionsFromProjectWatchOptions(configFileExistenceInfo?.config?.parsedCommandLine?.watchOptions),
1736+
this.getWatchOptionsFromProjectWatchOptions(configFileExistenceInfo?.config?.parsedCommandLine?.watchOptions, canonicalConfigFilePath),
17371737
WatchType.ConfigFile,
17381738
forProject
17391739
);
@@ -2289,9 +2289,9 @@ export class ProjectService {
22892289
// If watch options different than older options when setting for the first time, update the config file watcher
22902290
if (!oldCommandLine && !isJsonEqual(
22912291
// Old options
2292-
this.getWatchOptionsFromProjectWatchOptions(/*projectOptions*/ undefined),
2292+
this.getWatchOptionsFromProjectWatchOptions(/*projectOptions*/ undefined, canonicalConfigFilePath),
22932293
// New options
2294-
this.getWatchOptionsFromProjectWatchOptions(parsedCommandLine.watchOptions)
2294+
this.getWatchOptionsFromProjectWatchOptions(parsedCommandLine.watchOptions, canonicalConfigFilePath)
22952295
)) {
22962296
// Reset the config file watcher
22972297
configFileExistenceInfo.watcher?.close();
@@ -2337,7 +2337,7 @@ export class ProjectService {
23372337
config!.watchedDirectories ||= new Map(),
23382338
new Map(getEntries(config!.parsedCommandLine!.wildcardDirectories!)),
23392339
// Create new directory watcher
2340-
(directory, flags) => this.watchWildcardDirectory(directory as Path, flags, configFileName, config!),
2340+
(directory, flags) => this.watchWildcardDirectory(directory as Path, flags, configFileName, forProject.canonicalConfigFilePath, config!),
23412341
);
23422342
}
23432343
else {
@@ -3100,6 +3100,9 @@ export class ProjectService {
31003100
if (args.watchOptions) {
31013101
const result = convertWatchOptions(args.watchOptions);
31023102
this.hostConfiguration.watchOptions = result?.watchOptions;
3103+
if (this.hostConfiguration.watchOptions?.watchFactory) {
3104+
this.setWatchOptionsFactoryHost(this.hostConfiguration.watchOptions, /*canonicalConfigFilePath*/ undefined);
3105+
}
31033106
this.projectWatchOptions.clear();
31043107
this.logger.info(`Host watch options changed to ${JSON.stringify(this.hostConfiguration.watchOptions)}, it will be take effect for next watches.`);
31053108
if (result?.errors?.length) {
@@ -3115,22 +3118,30 @@ export class ProjectService {
31153118
}
31163119

31173120
/** @internal */
3118-
getWatchOptions(project: Project) {
3119-
return this.getWatchOptionsFromProjectWatchOptions(project.getWatchOptions());
3121+
getWatchOptions(project: Project): WatchOptions | undefined {
3122+
return this.getWatchOptionsFromProjectWatchOptions(project.getWatchOptions(), isConfiguredProject(project) ? project.canonicalConfigFilePath : undefined);
31203123
}
31213124

31223125
/** @internal */
3123-
private getWatchOptionsFromProjectWatchOptions(projectOptions: WatchOptions | undefined) {
3126+
private getWatchOptionsFromProjectWatchOptions(projectOptions: WatchOptions | undefined, canonicalConfigFilePath: NormalizedPath | undefined) {
31243127
if (!projectOptions) return this.hostConfiguration.watchOptions;
31253128
let options = this.projectWatchOptions.get(projectOptions);
31263129
if (options) return options;
31273130
this.projectWatchOptions.set(projectOptions, options = this.hostConfiguration.watchOptions ?
31283131
{ ...this.hostConfiguration.watchOptions, ...projectOptions } :
31293132
projectOptions
31303133
);
3134+
this.setWatchOptionsFactoryHost(options, canonicalConfigFilePath);
31313135
return options;
31323136
}
31333137

3138+
/** @internal */
3139+
private setWatchOptionsFactoryHost(options: WatchOptions, canonicalConfigFilePath: NormalizedPath | undefined) {
3140+
setWatchOptionInternalProperty(options, "getHost", memoize(() => ({
3141+
searchPaths: this.getProjectPluginSearchPaths(canonicalConfigFilePath)
3142+
})));
3143+
}
3144+
31343145
/** @internal */
31353146
clearWatchOptionsFromProjectWatchOptions(projectOptions: WatchOptions | undefined) {
31363147
if (projectOptions) this.projectWatchOptions.delete(projectOptions);
@@ -4153,6 +4164,27 @@ export class ProjectService {
41534164
return false;
41544165
}
41554166

4167+
/** @internal */
4168+
getGlobalPluginSearchPaths() {
4169+
// Search any globally-specified probe paths, then our peer node_modules
4170+
return [
4171+
...this.pluginProbeLocations,
4172+
// ../../.. to walk from X/node_modules/typescript/lib/tsserver.js to X/node_modules/
4173+
combinePaths(this.getExecutingFilePath(), "../../.."),
4174+
];
4175+
}
4176+
4177+
/** @internal */
4178+
getProjectPluginSearchPaths(canonicalConfigFilePath: string | undefined) {
4179+
const searchPaths = this.getGlobalPluginSearchPaths();
4180+
if (canonicalConfigFilePath && this.allowLocalPluginLoads) {
4181+
const local = getDirectoryPath(canonicalConfigFilePath);
4182+
this.logger.info(`Local plugin loading enabled; adding ${local} to search paths`);
4183+
searchPaths.unshift(local);
4184+
}
4185+
return searchPaths;
4186+
}
4187+
41564188
/** @internal */
41574189
requestEnablePlugin(project: Project, pluginConfigEntry: PluginImport, searchPaths: string[]) {
41584190
if (!this.host.importPlugin && !this.host.require) {

src/server/project.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1644,12 +1644,7 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
16441644

16451645
/** @internal */
16461646
protected getGlobalPluginSearchPaths() {
1647-
// Search any globally-specified probe paths, then our peer node_modules
1648-
return [
1649-
...this.projectService.pluginProbeLocations,
1650-
// ../../.. to walk from X/node_modules/typescript/lib/tsserver.js to X/node_modules/
1651-
combinePaths(this.projectService.getExecutingFilePath(), "../../.."),
1652-
];
1647+
return this.projectService.getProjectPluginSearchPaths(/*canonicalConfigFilePath*/ undefined);
16531648
}
16541649

16551650
protected enableGlobalPlugins(options: CompilerOptions): void {
@@ -1662,7 +1657,7 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
16621657
}
16631658

16641659
// Enable global plugins with synthetic configuration entries
1665-
const searchPaths = this.getGlobalPluginSearchPaths();
1660+
const searchPaths = this.projectService.getGlobalPluginSearchPaths();
16661661
for (const globalPluginName of this.projectService.globalPlugins) {
16671662
// Skip empty names from odd commandline parses
16681663
if (!globalPluginName) continue;
@@ -2527,6 +2522,11 @@ export class ConfiguredProject extends Project {
25272522
return this.getCurrentProgram()?.forEachResolvedProjectReference(cb);
25282523
}
25292524

2525+
/** @internal */
2526+
protected getProjectPluginSearchPaths() {
2527+
return this.projectService.getProjectPluginSearchPaths(this.canonicalConfigFilePath);
2528+
}
2529+
25302530
/** @internal */
25312531
enablePluginsWithOptions(options: CompilerOptions): void {
25322532
this.plugins.length = 0;
@@ -2537,14 +2537,8 @@ export class ConfiguredProject extends Project {
25372537
return;
25382538
}
25392539

2540-
const searchPaths = this.getGlobalPluginSearchPaths();
2541-
if (this.projectService.allowLocalPluginLoads) {
2542-
const local = getDirectoryPath(this.canonicalConfigFilePath);
2543-
this.projectService.logger.info(`Local plugin loading enabled; adding ${local} to search paths`);
2544-
searchPaths.unshift(local);
2545-
}
2546-
25472540
// Enable tsconfig-specified plugins
2541+
const searchPaths = this.getProjectPluginSearchPaths();
25482542
if (options.plugins) {
25492543
for (const pluginConfigEntry of options.plugins) {
25502544
this.enablePlugin(pluginConfigEntry, searchPaths);

tests/baselines/reference/tsserver/watchEnvironment/watchFactory-as-configuration-of-host-allowLocalPluginLoads-object.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ Info 6 [00:00:29.000] Search path: /user/username/projects/myproject
9494
Info 7 [00:00:30.000] For info: /user/username/projects/myproject/a.ts :: Config file name: /user/username/projects/myproject/tsconfig.json
9595
Info 8 [00:00:31.000] Creating configuration project /user/username/projects/myproject/tsconfig.json
9696
Info 9 [00:00:32.000] FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/tsconfig.json 2000 {"watchFactory":{"name":"myplugin","myconfig":"somethingelse"}} Project: /user/username/projects/myproject/tsconfig.json WatchType: Config file
97-
CustomRequire:: Resolving myplugin from /a/lib/tsc.js/../../../node_modules
97+
CustomRequire:: Resolving myplugin from /a/pluginprobe1/node_modules
9898
Require:: Module myplugin created with config: {"name":"myplugin","myconfig":"somethingelse"} and options: {"watchFactory":{"name":"myplugin","myconfig":"somethingelse"}}
9999
Custom watchFile: /user/username/projects/myproject/tsconfig.json 2000 {"watchFactory":{"name":"myplugin","myconfig":"somethingelse"}}
100100
Info 10 [00:00:33.000] Config: /user/username/projects/myproject/tsconfig.json : {

tests/baselines/reference/tsserver/watchEnvironment/watchFactory-as-configuration-of-host-allowLocalPluginLoads.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ Info 6 [00:00:29.000] Search path: /user/username/projects/myproject
9191
Info 7 [00:00:30.000] For info: /user/username/projects/myproject/a.ts :: Config file name: /user/username/projects/myproject/tsconfig.json
9292
Info 8 [00:00:31.000] Creating configuration project /user/username/projects/myproject/tsconfig.json
9393
Info 9 [00:00:32.000] FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/tsconfig.json 2000 {"watchFactory":"myplugin"} Project: /user/username/projects/myproject/tsconfig.json WatchType: Config file
94-
CustomRequire:: Resolving myplugin from /a/lib/tsc.js/../../../node_modules
94+
CustomRequire:: Resolving myplugin from /a/pluginprobe1/node_modules
9595
Require:: Module myplugin created with config: {"name":"myplugin"} and options: {"watchFactory":"myplugin"}
9696
Custom watchFile: /user/username/projects/myproject/tsconfig.json 2000 {"watchFactory":"myplugin"}
9797
Info 10 [00:00:33.000] Config: /user/username/projects/myproject/tsconfig.json : {

tests/baselines/reference/tsserver/watchEnvironment/watchFactory-as-configuration-of-host-object.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ Info 6 [00:00:29.000] Search path: /user/username/projects/myproject
9494
Info 7 [00:00:30.000] For info: /user/username/projects/myproject/a.ts :: Config file name: /user/username/projects/myproject/tsconfig.json
9595
Info 8 [00:00:31.000] Creating configuration project /user/username/projects/myproject/tsconfig.json
9696
Info 9 [00:00:32.000] FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/tsconfig.json 2000 {"watchFactory":{"name":"myplugin","myconfig":"somethingelse"}} Project: /user/username/projects/myproject/tsconfig.json WatchType: Config file
97-
CustomRequire:: Resolving myplugin from /a/lib/tsc.js/../../../node_modules
97+
CustomRequire:: Resolving myplugin from /a/pluginprobe1/node_modules
9898
Require:: Module myplugin created with config: {"name":"myplugin","myconfig":"somethingelse"} and options: {"watchFactory":{"name":"myplugin","myconfig":"somethingelse"}}
9999
Custom watchFile: /user/username/projects/myproject/tsconfig.json 2000 {"watchFactory":{"name":"myplugin","myconfig":"somethingelse"}}
100100
Info 10 [00:00:33.000] Config: /user/username/projects/myproject/tsconfig.json : {

tests/baselines/reference/tsserver/watchEnvironment/watchFactory-as-configuration-of-host-with-pluginOverride-allowLocalPluginLoads-object.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ Info 9 [00:00:32.000] Search path: /user/username/projects/myproject
138138
Info 10 [00:00:33.000] For info: /user/username/projects/myproject/a.ts :: Config file name: /user/username/projects/myproject/tsconfig.json
139139
Info 11 [00:00:34.000] Creating configuration project /user/username/projects/myproject/tsconfig.json
140140
Info 12 [00:00:35.000] FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/tsconfig.json 2000 {"watchFactory":{"name":"myplugin","myconfig":"somethingelse"}} Project: /user/username/projects/myproject/tsconfig.json WatchType: Config file
141-
CustomRequire:: Resolving myplugin from /a/lib/tsc.js/../../../node_modules
141+
CustomRequire:: Resolving myplugin from /a/pluginprobe1/node_modules
142142
Require:: Module myplugin created with config: {"name":"myplugin","myconfig":"somethingelse"} and options: {"watchFactory":{"name":"myplugin","myconfig":"somethingelse"}}
143143
Custom watchFile: /user/username/projects/myproject/tsconfig.json 2000 {"watchFactory":{"name":"myplugin","myconfig":"somethingelse"}}
144144
Info 13 [00:00:36.000] Config: /user/username/projects/myproject/tsconfig.json : {

tests/baselines/reference/tsserver/watchEnvironment/watchFactory-as-configuration-of-host-with-pluginOverride-allowLocalPluginLoads.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ Info 9 [00:00:32.000] Search path: /user/username/projects/myproject
135135
Info 10 [00:00:33.000] For info: /user/username/projects/myproject/a.ts :: Config file name: /user/username/projects/myproject/tsconfig.json
136136
Info 11 [00:00:34.000] Creating configuration project /user/username/projects/myproject/tsconfig.json
137137
Info 12 [00:00:35.000] FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/tsconfig.json 2000 {"watchFactory":"myplugin"} Project: /user/username/projects/myproject/tsconfig.json WatchType: Config file
138-
CustomRequire:: Resolving myplugin from /a/lib/tsc.js/../../../node_modules
138+
CustomRequire:: Resolving myplugin from /a/pluginprobe1/node_modules
139139
Require:: Module myplugin created with config: {"name":"myplugin"} and options: {"watchFactory":"myplugin"}
140140
Custom watchFile: /user/username/projects/myproject/tsconfig.json 2000 {"watchFactory":"myplugin"}
141141
Info 13 [00:00:36.000] Config: /user/username/projects/myproject/tsconfig.json : {

tests/baselines/reference/tsserver/watchEnvironment/watchFactory-as-configuration-of-host-with-pluginOverride-object.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ Info 9 [00:00:32.000] Search path: /user/username/projects/myproject
138138
Info 10 [00:00:33.000] For info: /user/username/projects/myproject/a.ts :: Config file name: /user/username/projects/myproject/tsconfig.json
139139
Info 11 [00:00:34.000] Creating configuration project /user/username/projects/myproject/tsconfig.json
140140
Info 12 [00:00:35.000] FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/tsconfig.json 2000 {"watchFactory":{"name":"myplugin","myconfig":"somethingelse"}} Project: /user/username/projects/myproject/tsconfig.json WatchType: Config file
141-
CustomRequire:: Resolving myplugin from /a/lib/tsc.js/../../../node_modules
141+
CustomRequire:: Resolving myplugin from /a/pluginprobe1/node_modules
142142
Require:: Module myplugin created with config: {"name":"myplugin","myconfig":"somethingelse"} and options: {"watchFactory":{"name":"myplugin","myconfig":"somethingelse"}}
143143
Custom watchFile: /user/username/projects/myproject/tsconfig.json 2000 {"watchFactory":{"name":"myplugin","myconfig":"somethingelse"}}
144144
Info 13 [00:00:36.000] Config: /user/username/projects/myproject/tsconfig.json : {

0 commit comments

Comments
 (0)