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:
classConfiguredProject{init(){// psuedo-ish code of what happens using the above config fileletmyLanguageService=createLanguageService();// Normal LS creation// Literals here are actually loaded from config file, not hardcodedconstplugin=require("./my-plugin");// Pass in the entry from tsconfig so plugin can read its own config objectmyLanguageService=plugin.create(myLanguageService,this,{name: "myPlugin"});}}The implementation of myPlugin might look like this
exportfunctioncreate(oldLS,project,config){constnewLS=ts.createLanguageServiceProxy(oldLS);newLS.getQuickInfo=function(){constx=oldLS.getQuickInfo.apply(oldLS,arguments);// do something interesting with 'x' herereturnx;}returnnewLS;}/cc Chuck Jazdzewski (@chuckjaz)
Goals / non-goals
Architecture Overview
When an editor performs a language service operation, the following steps occur:
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-pluginpluginLoading
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
createmethod:The implementation of
myPluginmight look like this/cc Chuck Jazdzewski (@chuckjaz)