-
Notifications
You must be signed in to change notification settings - Fork 213
Remove RazorCompletionItem.Items property #11360
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
Remove RazorCompletionItem.Items property #11360
Conversation
Every RazorCompletionItem creates an instance of ItemCollection and exposes it via an Items property. The Items property constructs the ItemCollection lazily, but that doesn't matter since it accessed immediately after each RazorCompletionItem constructor call to add a "description info" object (used to build completion description tooltips). No other data is stored in the ItemCollection. This is an unnecessary amount of overhead to store a single object per RazorCompletionItem. Internally, ItemCollection is implemented with a ConcurrentDictionary, so its definitely not cheap! The change removes the Items property altogether and provides a DescriptionInfo property that is set via the constructor.
src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Completion/RazorCompletionItem.cs
Show resolved
Hide resolved
return _items; | ||
} | ||
} | ||
public static RazorCompletionItem CreateMarkupTransition( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was doing review commit-by-commit - looking at the last commit, I see we added all factory methods to the completion item itself. I personally would've added them in the corresponding provider types as those are already specific to those more specific item instances, but don't have very strong feelings either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The purpose of these factory methods was to ensure that a RazorCompletionItem
is always created correctly. To support that goal, I want to enforce that RazorCompletionItem
can only be constructed via factory methods. If the factory methods aren't on RazorCompletionItem
, the constructor would have to be public and there wouldn't be any enforcement. Plus, it is far more typical from a discoverability perspective for "Create" factory methods for a type to appear on a either the type that is being created or a dedicated factory type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally would've preferred this added to MarkupTransitionCompletionItemProvider since that class is already specific to MarkupTransition.
FYI that I appreciate you explaining your thinking and that's a good place to start logically. For me, when I look at RazorCompletionItem
, I see the logic for MarkupTransition already split across other related areas, such as RazorCompletionItemResolver
. So, making RazorCompletionItem
a bit more knowledgeable about the RazorCompletionItemKinds
that it's constructed with seemed reasonable, and the encapsulation win helps avoid future bugs.
insertText: SyntaxConstants.TextTagName, | ||
descriptionInfo: new(CodeAnalysisResources.MarkupTransition_Description), | ||
commitCharacters: RazorCommitCharacter.CreateArray([">"])); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I that we replaced these checks with ArgHelper.ThrowIfNull in some cases but not in others. Curious about reasoning behind the choices, e.g. why not use ArgHelper.ThrowIfNull here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, there isn't a need to use ArgHelper.ThrowIfNull
in internal code called exclusively by our code if the calls won't compile when null is passed.
using Microsoft.AspNetCore.Razor.PooledObjects; | ||
using Microsoft.CodeAnalysis.Razor.Tooltip; | ||
using Microsoft.VisualStudio.Editor.Razor; | ||
using RazorSyntaxNode = Microsoft.AspNetCore.Razor.Language.Syntax.SyntaxNode; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved this to the top of the file because C#'s Add Using code action adds new using directives in the wrong place if there are usings at the top of the file and inside the namespace.
Yes, we had a discussion, but I don't remember agreeing to alias SyntaxNode
in this way, so there may have been confusion about the outcome. (FWIW, most cases like this in Razor tooling alias SyntaxNode
as RazorSyntaxNode
.)
RazorSyntaaxNode
is actually a superfluous type in the compiler. It sits in the inheritance hierarchy between SyntaxNode
and all Razor syntax node types. I suspect that it exists to as a built in "alias" for SyntaxNode
to avoid exactly the problem that this using type directive is trying to avoid. In fact, I'm pretty sure we could delete all using RazorSyntaxNode =
and using SyntaxNode =
directives from Razor tooling code, replace SyntaxNode
with RazorSyntaxNode
everywhere, and everything would work fine. 😄
There are actually only six types that inherit from SyntaxNode
directly rather than RazorSyntaxNode
: SyntaxTrivia
, SyntaxList
(+ 3 variations), and RazorSyntaxNode
itself. Given that tooling doesn't ever care about processing these types as syntax nodes, it seems like all of the effort to add using aliases is probably unnecessary. If we just use the real RazorSyntaxNode
throughout, there'd be more consistency and that would alleviate any human reader confusion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RazorCompletionItem implements value equality, but that equality doesn't appear to be used in production code. Looking back through the history of `RazorCompletionItem.cs` it seems that `IEquatable` was added for testing in a8a91e2 when it was a much simpler type. Removing the value equality does not break tests, so I'm removing the Equals override to avoid confusion and subtle equality bugs that might crop up in the future.
`ItemCollection` is essentially a property bag that results in dictionaries being used instead of plain ol' fields, and it's been overused in Razor. I've been trying to get rid of it for nearly a year. #10720, #10764, #11360, and #11931 each represent changes that remove a use of `ItemCollection`. This pull request finally gets rid of `ItemCollection` completely. The last use of `ItemCollection` is the `RazorCodeDocument.Items`, which is used by the compiler to store various objects during compilation, and tooling uses it to cache some expensive-to-create data. This change stores all of the compiler objects on `RazorCodeDocument` directly. The data cached by tooling is stored on the side in a `ConditionalWeakTable`. As part of this, I've refactoring the `RazorCodeDocument.TryComputeNamespace` extension method to be more understandable and maintainable. ---- CI Build: https://dev.azure.com/dnceng/internal/_build/results?buildId=2728842&view=results Test Insertion: https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/643246 Toolset Run: https://dev.azure.com/dnceng/internal/_build/results?buildId=2728843&view=results
Every
RazorCompletionItem
creates an instance ofItemCollection
and exposes it via anItems
property. The property lazily constructs theItemCollection
, but that doesn't really matter much because the property is always accessed. Every time theRazorCompletionItem
constructor is called a "description info" object (used to build completion description tooltips) is immediately added to theItems
property. No other data is stored in theItemCollection
.It is unnecessary overhead to store a single object per
RazorCompletionItem
in anItemCollection
. Internally,ItemCollection
is implemented with aConcurrentDictionary
, so it's definitely not cheap!This change removes the
Items
property altogether and provides aDescriptionInfo
property that is set via the constructor. I've also done a fair amount of clean up in separate commits. The first commit contains the meat of removing theItems
property.