Skip to content

Better outlining spans for prototype methods #32782

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

Conversation

dragomirtitian
Copy link
Contributor

@dragomirtitian dragomirtitian commented Aug 9, 2019

Fixes #31516

Hi @DanielRosenwasser, @amcasey

I know it's been a while since i said I would have a look at this, but I finally got around to finishing the implementation for this.
First the good news, this is what an ES5 class looks like now:

image

Now for the problems:

  1. The implementation in VS code for outline view takes into account the node ranges returned when deciding how to display children. This means that the generated class node needs to encompass all the class members, otherwise the child nodes will not be displayed. This is fine if the members are contiguous, however it generates problems if there are other definitions between the members. The only ill effect I can see is that errors on these other definitions are displayed on the enclosing class.

image

I see some options:
a. Do nothing, the outline looks nice, although the bug report about this kind of writes itself
b. Create multiple additional spans for the class if the members are not contiguous. This is probably the best option but it can end up splitting up the class outline node:
image

c. Include any extra node in the class outline, maybe marking them in some distinctive way (although I am not sure what that would look like, and it would be kind of confusing)
d. Change the way errors are matched with outline nodes in VS-Code (probably a bigger change than we want to make for this feature)

  1. Class static members. I tried to add to the outline view assignments that occur directly on the constructor (ie. static members) but ran into a problem.

Given assignments of the form

A.v = 1;
A.a = function() { };

It is impossible to tell without using the type checker, if the assignments will need to become static members on a class named A or they are just regular assignments. One option would be to keep all such assignments and remove them from the outline during the merging of outline nodes. This could end up creating a lot of extra nodes. My solution to limit the number of generated nodes was to track any found classes in the current scope, and only add nodes for the assignments above if a class like definition A was found in the current scope. A is considered a class like definition if a function A is declared, or if a something was already assigned to the prototype of A :

function A() { }
A.v = 1;
A.a = function() { };

image

I do not search the scope before-hand, but rather mark the class when I find it, this does mean that if static members are assigned before I mark the class they do not appear in the outline as part of the class:
image

The alternative might be to not add these members at all.

  1. If two or more nodes can be merged a class will be generated, however if there is just one node there is a change in what is displayed but I think it's for the better:
    image

@amcasey
Copy link
Member

amcasey commented Aug 12, 2019

@dragomirtitian Thanks for looking into this! Regarding the problems you raised:

  1. I think an analogous problem might already exist for namespaces. Superficial experimentation suggests that it creates a separate entry for each (contiguous) chunk of the namespace (your (b)). If I understand correctly that errors are propagated through the outline based on these scopes, that might be the best option (unless, for some reason, splitting up an ES5 class declaration is common practice).
  2. I had independently spent some time thinking about how to fit expando objects into this model and was also unable to see a way to do it without semantic info. On the VS side, I'm pretty sure we're fine with this being a semantic operation, but VS Code might have different requirements. (To clarify, VS has no outline view, but the same code powers VS's Navigation Bars.)
  3. I'm not totally sure I understand this point. I think the difference is that the parent node has the purple icon, rather than the orange icon?

@dragomirtitian
Copy link
Contributor Author

dragomirtitian commented Aug 13, 2019

@amcasey Thank you for your feedback.

  1. I will add additional spans, this seemed like the best option to me too. (eta: End of week)

  2. As far as I can tell, VS Code starts two language services instances a syntax and a semantic one. The outline runs on the syntactic instance. Also currently navigationBar.ts does not use the type checker at all, my guess this is by design to keep outline generation reasonably quick. (@DanielRosenwasser, @mjbvz can you please validate the assumptions I made here. Thank you.)

  3. I was just referring to the fact that previously Something.prototype.method would show up as method->method now it shows up as Something->method. If there is a single prototype assignment Something is not promoted to a class node, it remains a method node as it did previously, just the name has changed.

image

@amcasey
Copy link
Member

amcasey commented Aug 13, 2019

  1. Cool
  2. Interesting. VS specifically uses the semantic server, even though it could use the syntactic server, because populating the navbars isn't important enough to block whatever else the syntactic server might be doing.
  3. If a solution jumps out at me, I'll let you know. Otherwise, your change seems like a net improvement and I'm fine dealing with that case later. @mjbvz may feel differently.

@amcasey
Copy link
Member

amcasey commented Aug 14, 2019

@jessetrinity is doing work in this area and is probably a better person to sign off on the actual code change.

@dragomirtitian
Copy link
Contributor Author

@amcasey @jessetrinity

I finished the changes for when ES5 classes are interwoven with other declarations. The outline is broken only if there is code that generates other outline nodes between class members:

image

If you think I should make any other changes let me know.

@jessetrinity
Copy link
Contributor

Aside from the one issue I noted it's looking pretty good so far in VS!

@jessetrinity
Copy link
Contributor

I finished the changes for when ES5 classes are interwoven with other declarations. The outline is broken only if there is code that generates other outline nodes between class members

I don't think we see that exact issue in VS, but it does look like our nav bar blows up when there is briefly an extra PropertyAccessExpression in there:

PRSplitClassBug

@jessetrinity
Copy link
Contributor

I would want a sign off from @mjbvz as well for how the changes affect VSCode.

@dragomirtitian
Copy link
Contributor Author

@jessetrinity Strange. I will check the spans returned for the outline nodes. I did not see similar behavior in VS Code 😕.

@RyanCavanaugh RyanCavanaugh requested a review from mjbvz August 16, 2019 21:41
@mjbvz
Copy link
Contributor

mjbvz commented Aug 16, 2019

The overall presentation looks reasonable. Adding @jrieken in case he has any specific feedback on the error matching in the outline panel.

For implementation, VS Code uses navtree requests to power a few different features:

  • Outline panel
  • Go to symbol in document
  • Breadcrumbs

These are important user facing features that should be available as soon as possible, so we run navtree against the syntax server in TS. Would that still be possible after these changes?

@jessetrinity
Copy link
Contributor

FYI I want to change the presentation of NavBar items with #33040. Updating the tests between both of these might be a little rough. If you don't get these changes in before that I would be happy to resolve any conflicts.

@dragomirtitian
Copy link
Contributor Author

@jessetrinity Thanks for the heads-up about this. It's been a hectic week and I haven't had time to finalize this. I will try to finish over the weekend.

@dragomirtitian
Copy link
Contributor Author

@jessetrinity I fixed the issue with multi-line values in the navigation item name. The reason was that previously I would put whatever was the target of the prototype access (ie for C.prototype.member i would use C) as the name of the node. This works fine if the target for the prototype access is a simple identifier, if however we have a nested target, any spaces or newlines would make it into the name (ie for C⮒B . A.prototype.member would end up in the name being C⮒B . A) which was the cause of the issues in VS. (VSCode replaced the newline with a return arrow character so it was less noticeable, but still an issue).

My solution was to walk up the prototype access target and add nodes for each identifier in the path. This will remove any spaces and should work fine (I did think about how to do this without generating the extra nodes, but I think this is the best solution, the other solution I considered was to keeping the current names and use a regex to remove unwanted characters, but this seemed hacky. The extra number of nodes should not be an issue in most situations, I expect most such classes are not nested in deep namespaces)

@dragomirtitian
Copy link
Contributor Author

For implementation, VS Code uses navtree requests to power a few different features:

  • Outline panel
  • Go to symbol in document
  • Breadcrumbs

@mjbvz I don't see any reason the changes in this PR would be incompatible with these features. I did try to be mindful towards performance while improving the outline, so users should not see any performance degradation in these features. Other than that I don't see any other potential issues, so I think it will work well 😊

Copy link
Contributor

@mjbvz mjbvz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change looks good as long as it can still run on the syntactic ts server

@jessetrinity
Copy link
Contributor

the other solution I considered was to keeping the current names and use a regex to remove unwanted characters, but this seemed hacky.

I think we just merged something similar to keep the navbar from breaking in these situations (it wouldn't keep us from getting into them in the first place, like this PR now does).

The extra number of nodes should not be an issue in most situations, I expect most such classes are not nested in deep namespaces

You would think that, but it turns out people do this quite extensively. Either way after some discussion we have decided that seeing all of the nested nodes is probably the best way to present it.

@jessetrinity jessetrinity merged commit fa9e0fa into microsoft:master Aug 29, 2019
timsuchanek pushed a commit to timsuchanek/TypeScript that referenced this pull request Sep 11, 2019
* Changed outlining to better outline ES5 classes (functions assigned to prototype)

* Changed outlining to better outline ES5 classes (properties assigned to functions)

* Fixed some small bugs when merging es5 class nodes. Added tests for new es5 class outline.

* Added support for interlaced ES5 classes (where an ES5 class's members are mixed with other declarations).

* Fixed crash in outline when assigning {} to the prototype.

* Added support for nested es5 declarations.

* Added support for prototype assignment for es5 classes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Better outlining spans for prototype method assignments
4 participants