Skip to content

Commit 0879e16

Browse files
authored
Merge pull request #28212 from minestarks/configure-plugins-master
Merge pull request #28106 from minestarks/configure-plugins
2 parents 364d4bd + 1fbabd5 commit 0879e16

File tree

10 files changed

+172
-30
lines changed

10 files changed

+172
-30
lines changed

src/harness/client.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,10 @@ namespace ts.server {
694694
return response.body!.map(entry => this.decodeSpan(entry, fileName)); // TODO: GH#18217
695695
}
696696

697+
configurePlugin(pluginName: string, configuration: any): void {
698+
this.processRequest<protocol.ConfigurePluginRequest>("configurePlugin", { pluginName, configuration });
699+
}
700+
697701
getIndentationAtPosition(_fileName: string, _position: number, _options: EditorOptions): number {
698702
return notImplemented();
699703
}

src/harness/fourslash.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3076,6 +3076,10 @@ Actual: ${stringify(fullActual)}`);
30763076
}
30773077
}
30783078
}
3079+
3080+
public configurePlugin(pluginName: string, configuration: any): void {
3081+
(<ts.server.SessionClient>this.languageService).configurePlugin(pluginName, configuration);
3082+
}
30793083
}
30803084

30813085
function updateTextRangeForTextChanges({ pos, end }: ts.TextRange, textChanges: ReadonlyArray<ts.TextChange>): ts.TextRange {
@@ -3139,19 +3143,20 @@ Actual: ${stringify(fullActual)}`);
31393143
function runCode(code: string, state: TestState): void {
31403144
// Compile and execute the test
31413145
const wrappedCode =
3142-
`(function(test, goTo, verify, edit, debug, format, cancellation, classification, completion, verifyOperationIsCancelled) {
3146+
`(function(test, goTo, plugins, verify, edit, debug, format, cancellation, classification, completion, verifyOperationIsCancelled) {
31433147
${code}
31443148
})`;
31453149
try {
31463150
const test = new FourSlashInterface.Test(state);
31473151
const goTo = new FourSlashInterface.GoTo(state);
3152+
const plugins = new FourSlashInterface.Plugins(state);
31483153
const verify = new FourSlashInterface.Verify(state);
31493154
const edit = new FourSlashInterface.Edit(state);
31503155
const debug = new FourSlashInterface.Debug(state);
31513156
const format = new FourSlashInterface.Format(state);
31523157
const cancellation = new FourSlashInterface.Cancellation(state);
31533158
const f = eval(wrappedCode);
3154-
f(test, goTo, verify, edit, debug, format, cancellation, FourSlashInterface.Classification, FourSlashInterface.Completion, verifyOperationIsCancelled);
3159+
f(test, goTo, plugins, verify, edit, debug, format, cancellation, FourSlashInterface.Classification, FourSlashInterface.Completion, verifyOperationIsCancelled);
31553160
}
31563161
catch (err) {
31573162
throw err;
@@ -3651,6 +3656,15 @@ namespace FourSlashInterface {
36513656
}
36523657
}
36533658

3659+
export class Plugins {
3660+
constructor (private state: FourSlash.TestState) {
3661+
}
3662+
3663+
public configurePlugin(pluginName: string, configuration: any): void {
3664+
this.state.configurePlugin(pluginName, configuration);
3665+
}
3666+
}
3667+
36543668
export class GoTo {
36553669
constructor(private state: FourSlash.TestState) {
36563670
}

src/harness/harnessLanguageService.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,36 @@ namespace Harness.LanguageService {
833833
error: undefined
834834
};
835835

836+
// Accepts configurations
837+
case "configurable-diagnostic-adder":
838+
let customMessage = "default message";
839+
return {
840+
module: () => ({
841+
create(info: ts.server.PluginCreateInfo) {
842+
customMessage = info.config.message;
843+
const proxy = makeDefaultProxy(info);
844+
proxy.getSemanticDiagnostics = filename => {
845+
const prev = info.languageService.getSemanticDiagnostics(filename);
846+
const sourceFile: ts.SourceFile = info.project.getSourceFile(ts.toPath(filename, /*basePath*/ undefined, ts.createGetCanonicalFileName(info.serverHost.useCaseSensitiveFileNames)))!;
847+
prev.push({
848+
category: ts.DiagnosticCategory.Error,
849+
file: sourceFile,
850+
code: 9999,
851+
length: 3,
852+
messageText: customMessage,
853+
start: 0
854+
});
855+
return prev;
856+
};
857+
return proxy;
858+
},
859+
onConfigurationChanged(config: any) {
860+
customMessage = config.message;
861+
}
862+
}),
863+
error: undefined
864+
};
865+
836866
default:
837867
return {
838868
module: undefined,

src/server/editorServices.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,8 @@ namespace ts.server {
509509
public readonly globalPlugins: ReadonlyArray<string>;
510510
public readonly pluginProbeLocations: ReadonlyArray<string>;
511511
public readonly allowLocalPluginLoads: boolean;
512+
private currentPluginConfigOverrides: Map<any> | undefined;
513+
512514
public readonly typesMapLocation: string | undefined;
513515

514516
public readonly syntaxOnly?: boolean;
@@ -1742,7 +1744,7 @@ namespace ts.server {
17421744
project.enableLanguageService();
17431745
project.watchWildcards(createMapFromTemplate(parsedCommandLine.wildcardDirectories!)); // TODO: GH#18217
17441746
}
1745-
project.enablePluginsWithOptions(compilerOptions);
1747+
project.enablePluginsWithOptions(compilerOptions, this.currentPluginConfigOverrides);
17461748
const filesToAdd = parsedCommandLine.fileNames.concat(project.getExternalFiles());
17471749
this.updateRootAndOptionsOfNonInferredProject(project, filesToAdd, fileNamePropertyReader, compilerOptions, parsedCommandLine.typeAcquisition!, parsedCommandLine.compileOnSave!); // TODO: GH#18217
17481750
}
@@ -1932,7 +1934,7 @@ namespace ts.server {
19321934

19331935
private createInferredProject(currentDirectory: string | undefined, isSingleInferredProject?: boolean, projectRootPath?: NormalizedPath): InferredProject {
19341936
const compilerOptions = projectRootPath && this.compilerOptionsForInferredProjectsPerProjectRoot.get(projectRootPath) || this.compilerOptionsForInferredProjects;
1935-
const project = new InferredProject(this, this.documentRegistry, compilerOptions, projectRootPath, currentDirectory);
1937+
const project = new InferredProject(this, this.documentRegistry, compilerOptions, projectRootPath, currentDirectory, this.currentPluginConfigOverrides);
19361938
if (isSingleInferredProject) {
19371939
this.inferredProjects.unshift(project);
19381940
}
@@ -2880,6 +2882,16 @@ namespace ts.server {
28802882

28812883
return false;
28822884
}
2885+
2886+
configurePlugin(args: protocol.ConfigurePluginRequestArguments) {
2887+
// For any projects that already have the plugin loaded, configure the plugin
2888+
this.forEachEnabledProject(project => project.onPluginConfigurationChanged(args.pluginName, args.configuration));
2889+
2890+
// Also save the current configuration to pass on to any projects that are yet to be loaded.
2891+
// If a plugin is configured twice, only the latest configuration will be remembered.
2892+
this.currentPluginConfigOverrides = this.currentPluginConfigOverrides || createMap();
2893+
this.currentPluginConfigOverrides.set(args.pluginName, args.configuration);
2894+
}
28832895
}
28842896

28852897
/* @internal */

src/server/project.ts

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ namespace ts.server {
7272
export interface PluginModule {
7373
create(createInfo: PluginCreateInfo): LanguageService;
7474
getExternalFiles?(proj: Project): string[];
75+
onConfigurationChanged?(config: any): void;
76+
}
77+
78+
export interface PluginModuleWithName {
79+
name: string;
80+
module: PluginModule;
7581
}
7682

7783
export type PluginModuleFactory = (mod: { typescript: typeof ts }) => PluginModule;
@@ -92,7 +98,7 @@ namespace ts.server {
9298
private program: Program;
9399
private externalFiles: SortedReadonlyArray<string>;
94100
private missingFilesMap: Map<FileWatcher>;
95-
private plugins: PluginModule[] = [];
101+
private plugins: PluginModuleWithName[] = [];
96102

97103
/*@internal*/
98104
/**
@@ -549,9 +555,9 @@ namespace ts.server {
549555

550556
getExternalFiles(): SortedReadonlyArray<string> {
551557
return toSortedArray(flatMapToMutable(this.plugins, plugin => {
552-
if (typeof plugin.getExternalFiles !== "function") return;
558+
if (typeof plugin.module.getExternalFiles !== "function") return;
553559
try {
554-
return plugin.getExternalFiles(this);
560+
return plugin.module.getExternalFiles(this);
555561
}
556562
catch (e) {
557563
this.projectService.logger.info(`A plugin threw an exception in getExternalFiles: ${e}`);
@@ -1111,7 +1117,7 @@ namespace ts.server {
11111117
this.rootFilesMap.delete(info.path);
11121118
}
11131119

1114-
protected enableGlobalPlugins(options: CompilerOptions) {
1120+
protected enableGlobalPlugins(options: CompilerOptions, pluginConfigOverrides: Map<any> | undefined) {
11151121
const host = this.projectService.host;
11161122

11171123
if (!host.require) {
@@ -1134,12 +1140,13 @@ namespace ts.server {
11341140

11351141
// Provide global: true so plugins can detect why they can't find their config
11361142
this.projectService.logger.info(`Loading global plugin ${globalPluginName}`);
1137-
this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths);
1143+
1144+
this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths, pluginConfigOverrides);
11381145
}
11391146
}
11401147
}
11411148

1142-
protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[]) {
1149+
protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map<any> | undefined) {
11431150
this.projectService.logger.info(`Enabling plugin ${pluginConfigEntry.name} from candidate paths: ${searchPaths.join(",")}`);
11441151

11451152
const log = (message: string) => {
@@ -1149,18 +1156,21 @@ namespace ts.server {
11491156
const resolvedModule = firstDefined(searchPaths, searchPath =>
11501157
<PluginModuleFactory | undefined>Project.resolveModule(pluginConfigEntry.name, searchPath, this.projectService.host, log));
11511158
if (resolvedModule) {
1159+
const configurationOverride = pluginConfigOverrides && pluginConfigOverrides.get(pluginConfigEntry.name);
1160+
if (configurationOverride) {
1161+
// Preserve the name property since it's immutable
1162+
const pluginName = pluginConfigEntry.name;
1163+
pluginConfigEntry = configurationOverride;
1164+
pluginConfigEntry.name = pluginName;
1165+
}
1166+
11521167
this.enableProxy(resolvedModule, pluginConfigEntry);
11531168
}
11541169
else {
11551170
this.projectService.logger.info(`Couldn't find ${pluginConfigEntry.name}`);
11561171
}
11571172
}
11581173

1159-
/** Starts a new check for diagnostics. Call this if some file has updated that would cause diagnostics to be changed. */
1160-
refreshDiagnostics() {
1161-
this.projectService.sendProjectsUpdatedInBackgroundEvent();
1162-
}
1163-
11641174
private enableProxy(pluginModuleFactory: PluginModuleFactory, configEntry: PluginImport) {
11651175
try {
11661176
if (typeof pluginModuleFactory !== "function") {
@@ -1186,12 +1196,26 @@ namespace ts.server {
11861196
}
11871197
this.projectService.logger.info(`Plugin validation succeded`);
11881198
this.languageService = newLS;
1189-
this.plugins.push(pluginModule);
1199+
this.plugins.push({ name: configEntry.name, module: pluginModule });
11901200
}
11911201
catch (e) {
11921202
this.projectService.logger.info(`Plugin activation failed: ${e}`);
11931203
}
11941204
}
1205+
1206+
/*@internal*/
1207+
onPluginConfigurationChanged(pluginName: string, configuration: any) {
1208+
this.plugins.filter(plugin => plugin.name === pluginName).forEach(plugin => {
1209+
if (plugin.module.onConfigurationChanged) {
1210+
plugin.module.onConfigurationChanged(configuration);
1211+
}
1212+
});
1213+
}
1214+
1215+
/** Starts a new check for diagnostics. Call this if some file has updated that would cause diagnostics to be changed. */
1216+
refreshDiagnostics() {
1217+
this.projectService.sendProjectsUpdatedInBackgroundEvent();
1218+
}
11951219
}
11961220

11971221
/**
@@ -1247,7 +1271,8 @@ namespace ts.server {
12471271
documentRegistry: DocumentRegistry,
12481272
compilerOptions: CompilerOptions,
12491273
projectRootPath: NormalizedPath | undefined,
1250-
currentDirectory: string | undefined) {
1274+
currentDirectory: string | undefined,
1275+
pluginConfigOverrides: Map<any> | undefined) {
12511276
super(InferredProject.newName(),
12521277
ProjectKind.Inferred,
12531278
projectService,
@@ -1263,7 +1288,7 @@ namespace ts.server {
12631288
if (!projectRootPath && !projectService.useSingleInferredProject) {
12641289
this.canonicalCurrentDirectory = projectService.toCanonicalFileName(this.currentDirectory);
12651290
}
1266-
this.enableGlobalPlugins(this.getCompilerOptions());
1291+
this.enableGlobalPlugins(this.getCompilerOptions(), pluginConfigOverrides);
12671292
}
12681293

12691294
addRoot(info: ScriptInfo) {
@@ -1419,12 +1444,8 @@ namespace ts.server {
14191444
return program && program.forEachResolvedProjectReference(cb);
14201445
}
14211446

1422-
enablePlugins() {
1423-
this.enablePluginsWithOptions(this.getCompilerOptions());
1424-
}
1425-
14261447
/*@internal*/
1427-
enablePluginsWithOptions(options: CompilerOptions) {
1448+
enablePluginsWithOptions(options: CompilerOptions, pluginConfigOverrides: Map<any> | undefined) {
14281449
const host = this.projectService.host;
14291450

14301451
if (!host.require) {
@@ -1445,11 +1466,11 @@ namespace ts.server {
14451466
// Enable tsconfig-specified plugins
14461467
if (options.plugins) {
14471468
for (const pluginConfigEntry of options.plugins) {
1448-
this.enablePlugin(pluginConfigEntry, searchPaths);
1469+
this.enablePlugin(pluginConfigEntry, searchPaths, pluginConfigOverrides);
14491470
}
14501471
}
14511472

1452-
this.enableGlobalPlugins(options);
1473+
this.enableGlobalPlugins(options, pluginConfigOverrides);
14531474
}
14541475

14551476
/**

src/server/protocol.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ namespace ts.server.protocol {
129129
GetEditsForFileRename = "getEditsForFileRename",
130130
/* @internal */
131131
GetEditsForFileRenameFull = "getEditsForFileRename-full",
132+
ConfigurePlugin = "configurePlugin"
132133

133134
// NOTE: If updating this, be sure to also update `allCommandNames` in `harness/unittests/session.ts`.
134135
}
@@ -1375,6 +1376,16 @@ namespace ts.server.protocol {
13751376
export interface ConfigureResponse extends Response {
13761377
}
13771378

1379+
export interface ConfigurePluginRequestArguments {
1380+
pluginName: string;
1381+
configuration: any;
1382+
}
1383+
1384+
export interface ConfigurePluginRequest extends Request {
1385+
command: CommandTypes.ConfigurePlugin;
1386+
arguments: ConfigurePluginRequestArguments;
1387+
}
1388+
13781389
/**
13791390
* Information found in an "open" request.
13801391
*/

src/server/session.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1983,6 +1983,10 @@ namespace ts.server {
19831983
this.updateErrorCheck(next, checkList, delay, /*requireOpen*/ false);
19841984
}
19851985

1986+
private configurePlugin(args: protocol.ConfigurePluginRequestArguments) {
1987+
this.projectService.configurePlugin(args);
1988+
}
1989+
19861990
getCanonicalFileName(fileName: string) {
19871991
const name = this.host.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
19881992
return normalizePath(name);
@@ -2304,6 +2308,10 @@ namespace ts.server {
23042308
[CommandNames.GetEditsForFileRenameFull]: (request: protocol.GetEditsForFileRenameRequest) => {
23052309
return this.requiredResponse(this.getEditsForFileRename(request.arguments, /*simplifiedResult*/ false));
23062310
},
2311+
[CommandNames.ConfigurePlugin]: (request: protocol.ConfigurePluginRequest) => {
2312+
this.configurePlugin(request.arguments);
2313+
return this.notRequired();
2314+
}
23072315
});
23082316

23092317
public addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse) {

0 commit comments

Comments
 (0)