Skip to content

Language Service Plugins with Proxies #11976

Closed
@RyanCavanaugh

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

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