Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

configurePlugins command for tsserver #28106

Merged
merged 3 commits into from
Oct 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/harness/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,10 @@ namespace ts.server {
return response.body!.map(entry => this.decodeSpan(entry, fileName)); // TODO: GH#18217
}

configurePlugin(pluginName: string, configuration: any): void {
this.processRequest<protocol.ConfigurePluginRequest>("configurePlugin", { pluginName, configuration });
}

getIndentationAtPosition(_fileName: string, _position: number, _options: EditorOptions): number {
return notImplemented();
}
Expand Down
18 changes: 16 additions & 2 deletions src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3400,6 +3400,10 @@ Actual: ${stringify(fullActual)}`);
}
}
}

public configurePlugin(pluginName: string, configuration: any): void {
(<ts.server.SessionClient>this.languageService).configurePlugin(pluginName, configuration);
}
}

function updateTextRangeForTextChanges({ pos, end }: ts.TextRange, textChanges: ReadonlyArray<ts.TextChange>): ts.TextRange {
Expand Down Expand Up @@ -3463,19 +3467,20 @@ Actual: ${stringify(fullActual)}`);
function runCode(code: string, state: TestState): void {
// Compile and execute the test
const wrappedCode =
`(function(test, goTo, verify, edit, debug, format, cancellation, classification, verifyOperationIsCancelled) {
`(function(test, goTo, plugins, verify, edit, debug, format, cancellation, classification, verifyOperationIsCancelled) {
${code}
})`;
try {
const test = new FourSlashInterface.Test(state);
const goTo = new FourSlashInterface.GoTo(state);
const plugins = new FourSlashInterface.Plugins(state);
const verify = new FourSlashInterface.Verify(state);
const edit = new FourSlashInterface.Edit(state);
const debug = new FourSlashInterface.Debug(state);
const format = new FourSlashInterface.Format(state);
const cancellation = new FourSlashInterface.Cancellation(state);
const f = eval(wrappedCode);
f(test, goTo, verify, edit, debug, format, cancellation, FourSlashInterface.Classification, verifyOperationIsCancelled);
f(test, goTo, plugins, verify, edit, debug, format, cancellation, FourSlashInterface.Classification, verifyOperationIsCancelled);
}
catch (err) {
throw err;
Expand Down Expand Up @@ -3975,6 +3980,15 @@ namespace FourSlashInterface {
}
}

export class Plugins {
constructor (private state: FourSlash.TestState) {
}

public configurePlugin(pluginName: string, configuration: any): void {
this.state.configurePlugin(pluginName, configuration);
}
}

export class GoTo {
constructor(private state: FourSlash.TestState) {
}
Expand Down
30 changes: 30 additions & 0 deletions src/harness/harnessLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,36 @@ namespace Harness.LanguageService {
error: undefined
};

// Accepts configurations
case "configurable-diagnostic-adder":
let customMessage = "default message";
return {
module: () => ({
create(info: ts.server.PluginCreateInfo) {
customMessage = info.config.message;
const proxy = makeDefaultProxy(info);
proxy.getSemanticDiagnostics = filename => {
const prev = info.languageService.getSemanticDiagnostics(filename);
const sourceFile: ts.SourceFile = info.project.getSourceFile(ts.toPath(filename, /*basePath*/ undefined, ts.createGetCanonicalFileName(info.serverHost.useCaseSensitiveFileNames)))!;
prev.push({
category: ts.DiagnosticCategory.Error,
file: sourceFile,
code: 9999,
length: 3,
messageText: customMessage,
start: 0
});
return prev;
};
return proxy;
},
onConfigurationChanged(config: any) {
customMessage = config.message;
}
}),
error: undefined
};

default:
return {
module: undefined,
Expand Down
16 changes: 14 additions & 2 deletions src/server/editorServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,8 @@ namespace ts.server {
public readonly globalPlugins: ReadonlyArray<string>;
public readonly pluginProbeLocations: ReadonlyArray<string>;
public readonly allowLocalPluginLoads: boolean;
private currentPluginConfigOverrides: Map<any> | undefined;

public readonly typesMapLocation: string | undefined;

public readonly syntaxOnly?: boolean;
Expand Down Expand Up @@ -1667,7 +1669,7 @@ namespace ts.server {
project.enableLanguageService();
project.watchWildcards(createMapFromTemplate(parsedCommandLine.wildcardDirectories!)); // TODO: GH#18217
}
project.enablePluginsWithOptions(compilerOptions);
project.enablePluginsWithOptions(compilerOptions, this.currentPluginConfigOverrides);
const filesToAdd = parsedCommandLine.fileNames.concat(project.getExternalFiles());
this.updateRootAndOptionsOfNonInferredProject(project, filesToAdd, fileNamePropertyReader, compilerOptions, parsedCommandLine.typeAcquisition!, parsedCommandLine.compileOnSave!); // TODO: GH#18217
}
Expand Down Expand Up @@ -1857,7 +1859,7 @@ namespace ts.server {

private createInferredProject(currentDirectory: string | undefined, isSingleInferredProject?: boolean, projectRootPath?: NormalizedPath): InferredProject {
const compilerOptions = projectRootPath && this.compilerOptionsForInferredProjectsPerProjectRoot.get(projectRootPath) || this.compilerOptionsForInferredProjects;
const project = new InferredProject(this, this.documentRegistry, compilerOptions, projectRootPath, currentDirectory);
const project = new InferredProject(this, this.documentRegistry, compilerOptions, projectRootPath, currentDirectory, this.currentPluginConfigOverrides);
if (isSingleInferredProject) {
this.inferredProjects.unshift(project);
}
Expand Down Expand Up @@ -2806,6 +2808,16 @@ namespace ts.server {

return false;
}

configurePlugin(args: protocol.ConfigurePluginRequestArguments) {
// For any projects that already have the plugin loaded, configure the plugin
this.forEachEnabledProject(project => project.onPluginConfigurationChanged(args.pluginName, args.configuration));

// Also save the current configuration to pass on to any projects that are yet to be loaded.
// If a plugin is configured twice, only the latest configuration will be remembered.
this.currentPluginConfigOverrides = this.currentPluginConfigOverrides || createMap();
this.currentPluginConfigOverrides.set(args.pluginName, args.configuration);
}
}

/* @internal */
Expand Down
63 changes: 42 additions & 21 deletions src/server/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ namespace ts.server {
export interface PluginModule {
create(createInfo: PluginCreateInfo): LanguageService;
getExternalFiles?(proj: Project): string[];
onConfigurationChanged?(config: any): void;
}

export interface PluginModuleWithName {
name: string;
module: PluginModule;
}

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

/*@internal*/
/**
Expand Down Expand Up @@ -549,9 +555,9 @@ namespace ts.server {

getExternalFiles(): SortedReadonlyArray<string> {
return toSortedArray(flatMap(this.plugins, plugin => {
if (typeof plugin.getExternalFiles !== "function") return;
if (typeof plugin.module.getExternalFiles !== "function") return;
try {
return plugin.getExternalFiles(this);
return plugin.module.getExternalFiles(this);
}
catch (e) {
this.projectService.logger.info(`A plugin threw an exception in getExternalFiles: ${e}`);
Expand Down Expand Up @@ -1105,7 +1111,7 @@ namespace ts.server {
this.rootFilesMap.delete(info.path);
}

protected enableGlobalPlugins(options: CompilerOptions) {
protected enableGlobalPlugins(options: CompilerOptions, pluginConfigOverrides: Map<any> | undefined) {
const host = this.projectService.host;

if (!host.require) {
Expand All @@ -1128,12 +1134,13 @@ namespace ts.server {

// Provide global: true so plugins can detect why they can't find their config
this.projectService.logger.info(`Loading global plugin ${globalPluginName}`);
this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths);

this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths, pluginConfigOverrides);
}
}
}

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

const log = (message: string) => {
Expand All @@ -1143,18 +1150,21 @@ namespace ts.server {
const resolvedModule = firstDefined(searchPaths, searchPath =>
<PluginModuleFactory | undefined>Project.resolveModule(pluginConfigEntry.name, searchPath, this.projectService.host, log));
if (resolvedModule) {
const configurationOverride = pluginConfigOverrides && pluginConfigOverrides.get(pluginConfigEntry.name);
if (configurationOverride) {
// Preserve the name property since it's immutable
const pluginName = pluginConfigEntry.name;
pluginConfigEntry = configurationOverride;
pluginConfigEntry.name = pluginName;
}

this.enableProxy(resolvedModule, pluginConfigEntry);
}
else {
this.projectService.logger.info(`Couldn't find ${pluginConfigEntry.name}`);
}
}

/** Starts a new check for diagnostics. Call this if some file has updated that would cause diagnostics to be changed. */
refreshDiagnostics() {
this.projectService.sendProjectsUpdatedInBackgroundEvent();
}

private enableProxy(pluginModuleFactory: PluginModuleFactory, configEntry: PluginImport) {
try {
if (typeof pluginModuleFactory !== "function") {
Expand All @@ -1180,12 +1190,26 @@ namespace ts.server {
}
this.projectService.logger.info(`Plugin validation succeded`);
this.languageService = newLS;
this.plugins.push(pluginModule);
this.plugins.push({ name: configEntry.name, module: pluginModule });
}
catch (e) {
this.projectService.logger.info(`Plugin activation failed: ${e}`);
}
}

/*@internal*/
onPluginConfigurationChanged(pluginName: string, configuration: any) {
minestarks marked this conversation as resolved.
Show resolved Hide resolved
this.plugins.filter(plugin => plugin.name === pluginName).forEach(plugin => {
if (plugin.module.onConfigurationChanged) {
plugin.module.onConfigurationChanged(configuration);
}
});
}

/** Starts a new check for diagnostics. Call this if some file has updated that would cause diagnostics to be changed. */
refreshDiagnostics() {
this.projectService.sendProjectsUpdatedInBackgroundEvent();
}
}

/**
Expand Down Expand Up @@ -1241,7 +1265,8 @@ namespace ts.server {
documentRegistry: DocumentRegistry,
compilerOptions: CompilerOptions,
projectRootPath: NormalizedPath | undefined,
currentDirectory: string | undefined) {
currentDirectory: string | undefined,
pluginConfigOverrides: Map<any> | undefined) {
super(InferredProject.newName(),
ProjectKind.Inferred,
projectService,
Expand All @@ -1257,7 +1282,7 @@ namespace ts.server {
if (!projectRootPath && !projectService.useSingleInferredProject) {
this.canonicalCurrentDirectory = projectService.toCanonicalFileName(this.currentDirectory);
}
this.enableGlobalPlugins(this.getCompilerOptions());
this.enableGlobalPlugins(this.getCompilerOptions(), pluginConfigOverrides);
}

addRoot(info: ScriptInfo) {
Expand Down Expand Up @@ -1402,12 +1427,8 @@ namespace ts.server {
return program && program.getResolvedProjectReferences();
}

enablePlugins() {
this.enablePluginsWithOptions(this.getCompilerOptions());
}

/*@internal*/
enablePluginsWithOptions(options: CompilerOptions) {
enablePluginsWithOptions(options: CompilerOptions, pluginConfigOverrides: Map<any> | undefined) {
const host = this.projectService.host;

if (!host.require) {
Expand All @@ -1428,11 +1449,11 @@ namespace ts.server {
// Enable tsconfig-specified plugins
if (options.plugins) {
for (const pluginConfigEntry of options.plugins) {
this.enablePlugin(pluginConfigEntry, searchPaths);
this.enablePlugin(pluginConfigEntry, searchPaths, pluginConfigOverrides);
}
}

this.enableGlobalPlugins(options);
this.enableGlobalPlugins(options, pluginConfigOverrides);
}

/**
Expand Down
11 changes: 11 additions & 0 deletions src/server/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ namespace ts.server.protocol {
GetEditsForFileRename = "getEditsForFileRename",
/* @internal */
GetEditsForFileRenameFull = "getEditsForFileRename-full",
ConfigurePlugin = "configurePlugin"

// NOTE: If updating this, be sure to also update `allCommandNames` in `harness/unittests/session.ts`.
}
Expand Down Expand Up @@ -1368,6 +1369,16 @@ namespace ts.server.protocol {
export interface ConfigureResponse extends Response {
}

export interface ConfigurePluginRequestArguments {
pluginName: string;
configuration: any;
}

export interface ConfigurePluginRequest extends Request {
command: CommandTypes.ConfigurePlugin;
arguments: ConfigurePluginRequestArguments;
}

/**
* Information found in an "open" request.
*/
Expand Down
8 changes: 8 additions & 0 deletions src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1946,6 +1946,10 @@ namespace ts.server {
this.updateErrorCheck(next, checkList, delay, /*requireOpen*/ false);
}

private configurePlugin(args: protocol.ConfigurePluginRequestArguments) {
this.projectService.configurePlugin(args);
}

getCanonicalFileName(fileName: string) {
const name = this.host.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
return normalizePath(name);
Expand Down Expand Up @@ -2267,6 +2271,10 @@ namespace ts.server {
[CommandNames.GetEditsForFileRenameFull]: (request: protocol.GetEditsForFileRenameRequest) => {
return this.requiredResponse(this.getEditsForFileRename(request.arguments, /*simplifiedResult*/ false));
},
[CommandNames.ConfigurePlugin]: (request: protocol.ConfigurePluginRequest) => {
this.configurePlugin(request.arguments);
return this.notRequired();
}
});

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