Repository navigation
[tsserver] custom command (ex: to collect Angular2 @Component/selector) #5730
Description
Activity
//cc:Bill Ticehurst (@billti)
Bill Ticehurst (@billti) is looking into the extensiblity portion, so i will let him comment on that. one thing to note, the connection should go both ways. i.e. the template LS worker needs to know about the TS files, but the TS LS worker needs to know about it also to be able to handle things like rename, and find all references.for your other question about extracting decorator information, here is a sample:
function visit(sourceFile: ts.SourceFile) { visitNode(sourceFile); function visitNode(node: ts.Node) { if (node.kind === ts.SyntaxKind.ClassDeclaration) { // only look in classes if (node.decorators) { node.decorators.forEach(decorator => { if (decorator.expression.kind === ts.SyntaxKind.CallExpression) { // decorators of the form doc({...}) let callExpression = <ts.CallExpression>decorator.expression; if (callExpression.expression.getFullText() === "Component") { // found a component if (callExpression.arguments && callExpression.arguments.length >= 1) { let firstArg = callExpression.arguments[0]; if (firstArg.kind === ts.SyntaxKind.ObjectLiteralExpression) { let object = <ts.ObjectLiteralExpression>firstArg; object.properties.forEach(p => { if (p.kind === ts.SyntaxKind.PropertyAssignment) { // key is selector if ((<ts.PropertyAssignment>p).name.getText() === "selector") { // found it! console.log((<ts.PropertyAssignment>p).initializer.getText()); } } }); } } } } }); } } ts.forEachChild(node, visitNode); } } // Parse let sourceFile = ts.createSourceFile("test.ts", ` import {Component} from 'angular2/angular2'; @Component({ selector: 'my-app' }) class AppComponent { } `, ts.ScriptTarget.ES6, /*setParentNodes */ true); visit(sourceFile);
Thank a lot Mohamed Hegazy (@mhegazy) for your help! I will study your suggestion.
I'm glad that Bill Ticehurst (@billti) works about this topic, very cool!
- addedQuestionAn issue which isn't directly actionable in codeAn issue which isn't directly actionable in codeDomain: APIRelates to the public API for TypeScriptRelates to the public API for TypeScript
on Nov 23, 2015 mihailik Bill Ticehurst (@billti) have you a road map to implement custom command? Otherwise I could start to develop something and try to do a PR with my idea.
Thanks for your help.
I actually spent a lot of time on this over the past couple weeks. This turns out to be quite a non-trivial problem, and I've hit a few dead ends and back-tracked. Part of the problem is to do anything meaningful with the template code, you need to synthesize a file with some generated content from the template and ask questions about that, yet the program used by the language service is immutable once created, so you end up needing another language service working with the synthesized program, which the actual language service the editor uses then delegates to. (You still need the original one also to ask some of the initial questions before creating/delegating the synthesized one).
I've been playing around with a few different ideas (my scratch pad for various prototypes is at https://github.com/billti/TypeScript/commits/ngml . I use a similar syntactic model to Mohamed above for now to detect components, but note this could be fooled by a similar shape class/decorator that wasn't actually an Angular component), but I don't think we're close to committing to any plugin model and timeline yet. Once you expose something like this for folks to start using, you've committed yourself to supporting it and maintaining compatibility, and this is too complex an area to rush out a design.
Bill Ticehurst (@billti) wow, I'm very impressed with your work. If I have understood you provide the capability to add plugin to support completion for template object property completion for ng attributes, for model, etc.
but I don't think we're close to committing to any plugin model and timeline yet
You mean that you have not planned to integrate your work in master TypeScript? Is there any chance that you do that in the future?
If it's possible, it should be cool if tsserver could load this plugin dynamicly (without compiling a custom tsserver)
My initial idea was to add a new command in tsserver at runtime to provide for instance a command which returns the angular model of a project (modules, directives) to display it in an outline like I have done for Angular1 https://github.com/angelozerr/angularjs-eclipse/wiki/Angular-Explorer-View
When angular model changes, teh command fires an event with the new change in order to the outline refresh as soon as model changes.
This command is added at runtime (no need to compile a custom version of tsserver). Your command is a npm module which starts with
tscmd-So you could have :
- node_modules
- typescript
- bin
- tsserver
- bin
- tscmd-ngml
- typescript
After you call your tsserver with parameters
-cmd ngmland with node require.resolve you load your custom command by searchingrequire.resove("tscmd-ngml").Thoses command could be hosted too inside
tsconfig.json. This idea follows the same idea than ternjs to load her plugins.- node_modules
May I suggest an alternative?
Your proposal is about introducing plugin framework to
tsserver. Instead you can achieve extensibility with composition.The routing/execution of the requests inside
tsservergoes through Session.executeCommand() and Session.handlers map. If you were able to interceptexecuteCommandor inject properties intohandlers, you have your extensibility.The idea is to have a wrapper script that fires the standard
tsserverin a customised way. The necessary change totsserveris minimal (see the actual patch):process.nextTick(() => { // <-- look over here const ioSession = new IOSession(ts.sys, logger); process.on("uncaughtException", function(err: Error) { ioSession.logError(err, "unknown"); }); // Start listening ioSession.listen(); });
Your wrapper script will go:
var ts = require('tsscript.js')- replace/modify
ts.server.Session.prototype.executeCommand - let it continue
Now your project is in charge of complexity, not TypeScript.
This pay-as-you-go model is important to flexibility and support cost. If
tsserverbecomes a plugin host, the actual hosting model may suit some use cases better than others. Whentsserveris used as a component, your app can take make all those decisions as suits your use case.Your custom request handler may recycle the instance of
tsserver, collect some data, spawn some crazy async logic — all that be hard withtsserveras an entry point and you as a plugin.Angelo (@angelozerr) The goal is to get this into master at some point, we just can't commit as to when exactly that point is right now, as we've just started exploring this space and trying to understand the scope of the work. There are a number of use-cases we need to think through, and ideally prototype, to ensure we make them practical/possible once we release and support this.
The goal is that any plug-ins or extensions would be written and compiled separately, referencing a supported API typing file. I'm just compiling my work as part of TypeScript for now as some of my experimentation also requires changes on the TypeScript side.
If I understand your requirements correctly, want you want might be something as simply as an event to your plugin whenever some structure in the code changes that you observe (in this case the Angular model), and you don't want to interfere with any of the existing compiler/language service functionality (such as completions, errors, compilation, etc.). Is this right?
One challenge with some of the suggestions above is that currently we can't assume that Node is the runtime and host for the language service or compiler. For example in Visual Studio or tsc.exe, there isn't a 'require' global method that has a standard resolution for locating and loading files. This is why we have abstractions for dealing with the host such as exist in https://github.com/Microsoft/TypeScript/blob/master/src/compiler/sys.ts or the
LanguageServiceHostinterface.mihailik While being able to monkey-patch the Session object would certainly be powerful, the approach doesn't really lend itself to a supported and strongly typed extensibilty model.
Bill Ticehurst (@billti) agree in long term!
My point is to leave the hosting logic to the outer application. Host application is best suited to coordinate lifetime, discovery, asynchrony. And
tsservershould focus on AST-related things.Monkey-patching of
executeCommandis just a trivial option. Even better would be to exposehandlerspublicly (it is strongly typed already). Then you canmodule.exports=ioSessionand give the caller an explicit typed instance of the session to deal with.(I've changed the PR accordingly)
After another brief discussion in the team, a further concern is about the other end of the "pipe". We can expose additional commands easily enough on the tsserver end, but then currently most of the editor hosts (e.g. VS, VS Code, Sublime, etc.), have no way of sharing their end of that stdin/stdout pipe to send the new commands. The part of the plugin living in the editor host could spin up it's own instance of tsserver to work with, but then that is duplicating a lot of cost (memory, file watchers, etc.) and gets expensive very quickly.
Maybe by definition any plugin that exposes additional commands REQUIRES editor host specific work anyway to call them (i.e. Python code for Sublime, JS for VSCode, Java for Eclipse, etc). But it is an additional wrinkle/challenge that'd we'd need to do work for each editor also to have a plugin model if we wanted a general extensibility story there, rather than baking support for certain plugins into the existing editor support.
You can't own the editor-side story whatever your best intentions. Brackets, vim, emacs, Cloud9 etc. have their own plans, so prescriptive model will create friction and cost, even if there were one good for Python, Sublime and Java.
Perhaps the least-prescriptive model today is to let the editor-side drive, and get out of their way?
Angelo (@angelozerr) The goal is to get this into master at some point, we just can't commit as to when exactly that point is right now, as we've just started exploring this space and trying to understand the scope of the work. There are a number of use-cases we need to think through, and ideally prototype, to ensure we make them practical/possible once we release and support this.
I understand. I'm very exciting with this plugien feature. Hope it will be available soon.
The goal is that any plug-ins or extensions would be written and compiled separately, referencing a supported API typing file.
Great!
I'm just compiling my work as part of TypeScript for now as some of my experimentation also requires changes on the TypeScript side.
Yes sure.
If I understand your requirements correctly, want you want might be something as simply as an event to your plugin whenever some structure in the code changes that you observe (in this case the Angular model),
Yes to provide an Angular2 Outline.
and you don't want to interfere with any of the existing compiler/language service functionality (such as completions, errors, compilation, etc.). Is this right?
No, I need that too!
To be honnest with you, I have already done those 2 features (Angular Outline and custom completion for ng template) for Angular1 by developping a tern plugin for angular1. So I have started to develop a tern plugin for angular2. But as Angular2 is based on TypeScript, I need to customize acorn parser to parse TypeScript to support decorator and additional syntax of ts (it starts working but it's a very big work). So if TypeScript with tsserver is able to provide the same feature than ternjs plugin, I will use tsserver to support Angular2 inside Eclipse.
More, if you provide plugin with tsserver, you will able to support completion inside string for a lot of JavaSCript framework. For instance you could support CSS selectors completions, validation etc like have done https://github.com/angelozerr/tern-browser-extension#css-selector
This CSS selectors features works too for jQuery $(""). To support that, I mark the AST node $ and document.querySelector as "CSS selectors". I have a tern plugin which use this information to provide completion, navigation, validation, hover. IMHO, I think you should do the same thing. Mark your node as template.
One challenge with some of the suggestions above is that currently we can't assume that Node is the runtime and host for the language service or compiler. For example in Visual Studio or tsc.exe, there isn't a 'require' global method that has a standard resolution for locating and loading files. This is why we have abstractions for dealing with the host such as exist in https://github.com/Microsoft/TypeScript/blob/master/src/compiler/sys.ts or the LanguageServiceHost interface.
Ok, require.resolve was just a suggestion (tern use that) to load plugin at runtime. But if you provide an another mean to host plugin, that's fine.
mihailik thanks for your PR. I will see it.
Bill Ticehurst (@billti) Mohamed Hegazy (@mhegazy) I know it's diffiicult for you to give me an answer because plugins features is a POC, but this feature doesn't appear in the Roadmap and I tell me when you could (have the intention to) provide this plugin features (in several weeks? months? years?).
I would like just know if I continue to integrate tsserver inside Eclipse to support Angular2 or if I develop an Angular2 tern plugin by waiting for your plugin features (I would prefer the first option with tsserver).
Many thanks for your answer!
Bill Ticehurst (@billti) can comment better on the timeline. i think he can also share his current work if you are interested in trying it out/helping.
The plan is to use this work as a proving grounds for an extensiblity model. once we have a clear idea of the work/API commitment involved, we should be able to publish the details of the new extensiblity API.
Thanks a lot Mohamed Hegazy (@mhegazy) for your answer! I'm waiting for answer of Bill Ticehurst (@billti) to know what I will do it.
Mohamed Hegazy (@mhegazy) Bill Ticehurst (@billti) is there some news about this issue? I post again the question because the feature of this issue seems not belong to RoadMap.
Bill Ticehurst (@billti) has a writeup of the current state in #6508, i think we are still on track for 2.0 for this work. will update the road map.
Many thanks Mohamed Hegazy (@mhegazy) for your answer! I'm very excited with the work of Bill Ticehurst (@billti) and very motivated to continue my integration of TypeScript inside Eclipse with https://github.com/angelozerr/typescript.java
closing this as it is already handled in #6508
- locked and limited conversation to collaborators
on Jun 19, 2018
At first I explain you what I would like to do with tsserver. Dirk Bäumer (@dbaeumer) I think you could be interested with this issue for VSCode and Angular2 support.
Take the sample https://angular.io/docs/ts/latest/quickstart.html with Angular2 & TypeScript. There is a ts file:
And the HTML:
The HTML contains
<my-app>which is binded with decorator @Component/selector property. My goal is to provide completion, navigation inside HTML (Eclipse) editor for<my-app>. In other words, I need to collect the whole @Component/selector node of the project to use it in a HTML editor. I tell me how I could do that:With ternjs it's possible to write a tern plugin which is able to do that (collect @Component/selector) because:
tsserver could do the same thing by searching inside node_modules with a pattern name like
tsserver-xxxxto provide a custom command. After that any editor will be able to consume it just by installing with `npm installl tsserver-angular2'Hope you will understand my issue.