镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
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:

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 Chuck Jazdzewski (@chuckjaz)

Activity

  1. weswigham commented on Nov 1, 2016

    @weswigham
    Member

    If I'm not mistaken, the wrapped/decorated language service model was already how Chuck Jazdzewski (@chuckjaz)'s proposal worked (and what my prototype implemented), though the API exposed here asks for monkeypatching rather than exposing hooks, and uses the single-registration-function plugin shape rather than expected-export-shape API.

  2. weswigham commented on Nov 1, 2016

    @weswigham
    Member

    Ryan Cavanaugh (@RyanCavanaugh) How do multiple language service plugins stack up in this model? It's unclear on the order they are constructed/executed in and the precedence of their return values.

  3. RyanCavanaugh commented on Nov 1, 2016

    @RyanCavanaugh
    MemberAuthor

    Wesley Wigham (@weswigham) they'd wrap to arbitrary depth. For ordering, I guess this does have to be an array, though it makes me sad. Updating.

  4. angelozerr commented on Feb 3, 2017

    @angelozerr

    It's perhaps out of scope of this issue but I tell me how to extends tsserver commands to add it with plugin. For instance I would like to have a new tsserver command to return a list of Angular2 modules that I would like to use with a Angular2 wizard to generate an angular-cli generate command which waits a module. In this wizard I would like to display a list with available modules that I could retrieve with tsserver command. See angelozerr/angular-eclipse#34

  5. angelozerr commented on Feb 13, 2017

    @angelozerr

    Just for your information, I have created https://github.com/angelozerr/tsserver-plugins to consume language service proxy (like angular2 and tslint) with any version of TypeScript tsserver.

  6. RyanCavanaugh commented on Aug 12, 2017

    @RyanCavanaugh
    MemberAuthor

    Closing for housekeeping purposes

  7. locked and limited conversation to collaborators on Jun 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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