Description
Goals / non-goals
- Support plugins which can change completions, quick info, diagnostics, etc, returned from the language service
- Add new entry points as needed to checker
- Allow per-project configuration of plugins
- Automatically load and enable plugins with no extra coding from editor-side code
- Non-goal: Support new syntax, new typechecking behavior, etc (too complex)
- Non-goal: Commandline plugins (we may expand this model to tsc if it's successful)
- Non-goal: Scale to a large number of plugins
- Non-goal: Support projects not configured using tsconfig.json
Architecture Overview
When an editor performs a language service operation, the following steps occur:
- Editor sends a request to TS Server
- TS Server decodes the message and determines which function to invoke ("decode and dispatch")
- Each dispatching function, if needed, finds the project associated with the message ("find project")
- The dispatching function gets the language service from the project
- The method on the language service instance is invoked
- The response is encoded and sent back to the editor
send decode and find --- proxy inserted here
message dispatch project vvv
[editor] -> [TS Server] ----> getFormatting ----> Project A ----> Language Service A
\---> getCompletions ---> Project B ----> Language Service B
\---> getQuickInfo ---> Project C ----> Language Service C
\---> ...
The change here is to insert a proxy (more accurately a decorator) between the project and the language service. Projects backed by tsconfig.json files will, upon creation of their language service, wrap the LS instance by invoking a factory method on the plugins listed in the config file.
Configuration
A new "plugins" section is added to tsconfig.json
{
"compilerOptions": {
"strictNullChecks": false,
"plugins": [
{ "name": "myPlugin" }
]
},
"files": ["sample.ts"]
}
This configures the my-plugin
plugin
Loading
These plugins are loaded as node modules from the folder where the tsconfig.json file is.
Initialization
Immediately after creating its language service, a tsconfig.json-based project will wrap the language service in the plugin proxy by calling its create
method:
class ConfiguredProject {
init() {
// psuedo-ish code of what happens using the above config file
let myLanguageService = createLanguageService(); // Normal LS creation
// Literals here are actually loaded from config file, not hardcoded
const plugin = require("./my-plugin");
// Pass in the entry from tsconfig so plugin can read its own config object
myLanguageService = plugin.create(myLanguageService, this, { name: "myPlugin" });
}
}
The implementation of myPlugin
might look like this
export function create(oldLS, project, config) {
const newLS = ts.createLanguageServiceProxy(oldLS);
newLS.getQuickInfo = function() {
const x = oldLS.getQuickInfo.apply(oldLS, arguments);
// do something interesting with 'x' here
return x;
}
return newLS;
}
/cc @chuckjaz
Activity