Skip to content

Language Service Plugins with Proxies #11976

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:

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)

Metadata

Metadata

Labels

DiscussionIssues which may not have code impact

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions