-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Smart Select language service API #31028
Smart Select language service API #31028
Conversation
…other kinds of bookended lists
Thanks for the PR! It looks like you've changed the TSServer protocol in some way. Please ensure that any changes here don't break consumers of the current TSServer API. For some extra review, we'll ping @sheetalkamat, @amcasey, @mjbvz, @minestarks for you. Feel free to loop in other consumers/maintainers if necessary |
Protocol changes look good. I'll give it another quick test locally and let you know if I run into an big issues |
Looks great. Just found a few odd results that cause VS Code to error: export function loadRepos() {
return {
type: /**/LOAD_REPOS,
};
} Top level returned text span is:
describe('thing', () => {
it('/**/', () => {});
}); Returned span is:
class HomePage {
componentDidMount() {
if (this.props.username/**/) { }
}
} Returned top level span is
|
Hm, I can see why the empty string one is doing that, but the others seem odd. I'll check and see what it thinks it's doing, but it looks like I just need to add a filter to reject zero-width spans. Will update first thing tomorrow. Thanks for testing! |
Ohhh. Those really strange ones were caused by a possible bug in I can no longer repro the bugs you gave me, but I thought I stopped using |
I agree the fourslash format looks like a disaster. The motivation, though, is that in general position-based tests end up being a lot less readable and maintainable than DSL-based formats, so we try to avoid the former as much as possible. It's impossible to look at these tests right now in the diff view, for example, and understand what the expected behavior is, and if something changes in the wire format, there's a bunch of updating that will have to happen. Updating the text is also very difficult - adding a single character in the middle of an existing test is going to require a lot of tedious math. Having a one-off DSL has generally paid for itself in terms of long-term test maintenance. For example, we could imagine having something like this (bikeshed a bunch): const test = `const { x, y: a, ...zs = {} } = {};`;
const expected = [
`const { x, y: a, ...zs = {} } = {};`,
` [a] `,
` [y: a] `,
` [{ x, y: a, ...zs = {} }] `,
]; Alternatively, this might be an appropriate place to use baselines that just render a human-readable format of what happened. Writing a one-off renderer to make an inspectable text file for what the selection spans were would be pretty easy, and lends itself to reviewers being able to see what the behavior is. |
👍🏽 I like the idea of a baseline. The assertions are difficult to write no matter what, but there are formats that are easy to read—eliminating the writing step seems like the best approach. |
Ryan just gave me a physical thumbs up on this in the room because his computer currently has a blue screen of death 😄 |
@andrewbranch yey on first big feature merge 👍 |
External observer question: why is
. . .
Given general brevity of the docs on ts APIs, adding odd-shaped nearly-identical calls can easily lead people astray. |
Good question. Almost every TSServer command has a I actually wrote up something about this for the wiki, I just haven’t added it yet. |
Sorry, I didn't realise it's already consistent with other calls. Makes total sense! |
I believe the primary difference between the |
Yep, that’s right, and that’s the same way that the compiler itself typically represents positions in files. Maybe Visual Studio doesn't use lines and columns much internally at all ¯_(ツ)_/¯ |
Closes #29071
Protocol for VS & VS Code
Also exposes a
selectionRange-full
tsserver command which accepts the same request format but responds with the language service response unchanged, which is the same except thatTextSpan
is ats.TextSpan
:Implementation notes
Selection ranges walk up the AST, with a few different types of exceptions to the rule.
Adding artificial depth to flat nodes
Some TypeScript nodes feel like they have subtrees but are actually quite flat. Keeping them flat results in a jump from one selection to the next that feels too big. To remedy this, when asking for the children of these nodes, we synthesize SyntaxLists as intermediate nodes that group children together more intuitively, sometimes multiple levels deep. The most complex example is for mapped types. Consider
The actual tree for the type in this declaration looks like
But this belies the intuition of the way things are grouped. The two tokens in
-readonly
go together, the two in-?
go together, the square brackets go with their contents, etc. The tree we actually want for selection purposes would look more likeThis results in a better selection experience, but it's totally fake which also makes it totally subjective. I used two main strategies to make decisions that are ultimately opinions:
Skipping redundant or uninteresting nodes
Identical selection ranges are deduped, but some others vary only in whitespace or semicolons and seemed to add little value. These nodes are skipped.
Adding JSDoc comments
An extra selection is added to nodes with JSDoc annotations so that the node can be selected both alone and with its comment.
Special handling for string literals
String literals don't separate their quotes from their contents in the AST, which is desirable for selection, so there’s special logic there. Template strings are especially unintuitive and have special handling to make sure you can get the whole string, just inside the quotes, around the outside of a substitution (
${x}
), and on the inside of a substitution.