diff --git a/src/Compilers/CSharp/Portable/SourceGeneration/CSharpSyntaxHelper.cs b/src/Compilers/CSharp/Portable/SourceGeneration/CSharpSyntaxHelper.cs index ea12b763d81a2..80136e313667c 100644 --- a/src/Compilers/CSharp/Portable/SourceGeneration/CSharpSyntaxHelper.cs +++ b/src/Compilers/CSharp/Portable/SourceGeneration/CSharpSyntaxHelper.cs @@ -21,6 +21,9 @@ private CSharpSyntaxHelper() public override bool IsCaseSensitive => true; + public override int AttributeListKind + => (int)SyntaxKind.AttributeList; + public override bool IsValidIdentifier(string name) => SyntaxFacts.IsValidIdentifier(name); @@ -60,38 +63,50 @@ public override bool IsLambdaExpression(SyntaxNode node) public override SyntaxToken GetUnqualifiedIdentifierOfName(SyntaxNode node) => ((NameSyntax)node).GetUnqualifiedName().Identifier; - public override void AddAliases(SyntaxNode node, ArrayBuilder<(string aliasName, string symbolName)> aliases, bool global) + public override void AddAliases(GreenNode node, ArrayBuilder<(string aliasName, string symbolName)> aliases, bool global) { - if (node is CompilationUnitSyntax compilationUnit) + if (node is Syntax.InternalSyntax.CompilationUnitSyntax compilationUnit) { AddAliases(compilationUnit.Usings, aliases, global); } - else if (node is BaseNamespaceDeclarationSyntax namespaceDeclaration) + else if (node is Syntax.InternalSyntax.BaseNamespaceDeclarationSyntax namespaceDeclaration) { AddAliases(namespaceDeclaration.Usings, aliases, global); } else { - throw ExceptionUtilities.UnexpectedValue(node.Kind()); + throw ExceptionUtilities.UnexpectedValue(node.KindText); } } - private static void AddAliases(SyntaxList usings, ArrayBuilder<(string aliasName, string symbolName)> aliases, bool global) + private static void AddAliases( + CodeAnalysis.Syntax.InternalSyntax.SyntaxList usings, + ArrayBuilder<(string aliasName, string symbolName)> aliases, + bool global) { foreach (var usingDirective in usings) { if (usingDirective.Alias is null) continue; - if (global != usingDirective.GlobalKeyword.Kind() is SyntaxKind.GlobalKeyword) + if (global != (usingDirective.GlobalKeyword != null)) continue; var aliasName = usingDirective.Alias.Name.Identifier.ValueText; - var symbolName = usingDirective.Name.GetUnqualifiedName().Identifier.ValueText; + var symbolName = GetUnqualifiedName(usingDirective.Name).Identifier.ValueText; aliases.Add((aliasName, symbolName)); } } + private static Syntax.InternalSyntax.SimpleNameSyntax GetUnqualifiedName(Syntax.InternalSyntax.NameSyntax name) + => name switch + { + Syntax.InternalSyntax.AliasQualifiedNameSyntax alias => alias.Name, + Syntax.InternalSyntax.QualifiedNameSyntax qualified => qualified.Right, + Syntax.InternalSyntax.SimpleNameSyntax simple => simple, + _ => throw ExceptionUtilities.UnexpectedValue(name.KindText), + }; + public override void AddAliases(CompilationOptions compilation, ArrayBuilder<(string aliasName, string symbolName)> aliases) { // C# doesn't have global aliases at the compilation level. diff --git a/src/Compilers/Core/Portable/SourceGeneration/ISyntaxHelper.cs b/src/Compilers/Core/Portable/SourceGeneration/ISyntaxHelper.cs index e505d313ada91..6eac7e1ad1f3f 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/ISyntaxHelper.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/ISyntaxHelper.cs @@ -13,6 +13,7 @@ namespace Microsoft.CodeAnalysis.SourceGeneration internal interface ISyntaxHelper { bool IsCaseSensitive { get; } + int AttributeListKind { get; } bool IsValidIdentifier(string name); @@ -33,13 +34,14 @@ internal interface ISyntaxHelper /// /// must be a compilation unit or namespace block. /// - void AddAliases(SyntaxNode node, ArrayBuilder<(string aliasName, string symbolName)> aliases, bool global); + void AddAliases(GreenNode node, ArrayBuilder<(string aliasName, string symbolName)> aliases, bool global); void AddAliases(CompilationOptions options, ArrayBuilder<(string aliasName, string symbolName)> aliases); } internal abstract class AbstractSyntaxHelper : ISyntaxHelper { public abstract bool IsCaseSensitive { get; } + public abstract int AttributeListKind { get; } public abstract bool IsValidIdentifier(string name); @@ -56,7 +58,7 @@ internal abstract class AbstractSyntaxHelper : ISyntaxHelper public abstract bool IsLambdaExpression(SyntaxNode node); - public abstract void AddAliases(SyntaxNode node, ArrayBuilder<(string aliasName, string symbolName)> aliases, bool global); + public abstract void AddAliases(GreenNode node, ArrayBuilder<(string aliasName, string symbolName)> aliases, bool global); public abstract void AddAliases(CompilationOptions options, ArrayBuilder<(string aliasName, string symbolName)> aliases); } } diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider_ForAttributeWithSimpleName.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider_ForAttributeWithSimpleName.cs index 9c3b9dad0afc9..b7050e3ccda71 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider_ForAttributeWithSimpleName.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider_ForAttributeWithSimpleName.cs @@ -104,7 +104,7 @@ static GlobalAliases getGlobalAliasesInCompilationUnit( Debug.Assert(compilationUnit is ICompilationUnitSyntax); var globalAliases = Aliases.GetInstance(); - syntaxHelper.AddAliases(compilationUnit, globalAliases, global: true); + syntaxHelper.AddAliases(compilationUnit.Green, globalAliases, global: true); return GlobalAliases.Create(globalAliases.ToImmutableAndFree()); } @@ -119,9 +119,12 @@ private static ImmutableArray GetMatchingNodes( CancellationToken cancellationToken) { var compilationUnit = syntaxTree.GetRoot(cancellationToken); - Debug.Assert(compilationUnit is ICompilationUnitSyntax); + // Walk the green node tree first to avoid allocating the entire red tree for files that have no attributes. + if (!ContainsAttributeList(compilationUnit.Green, syntaxHelper.AttributeListKind)) + return ImmutableArray.Empty; + var isCaseSensitive = syntaxHelper.IsCaseSensitive; var comparison = isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; @@ -158,14 +161,14 @@ void recurse(SyntaxNode node) if (node is ICompilationUnitSyntax) { - syntaxHelper.AddAliases(node, localAliases, global: false); + syntaxHelper.AddAliases(node.Green, localAliases, global: false); recurseChildren(node); } else if (syntaxHelper.IsAnyNamespaceBlock(node)) { var localAliasCount = localAliases.Count; - syntaxHelper.AddAliases(node, localAliases, global: false); + syntaxHelper.AddAliases(node.Green, localAliases, global: false); recurseChildren(node); @@ -285,4 +288,21 @@ bool matchesAttributeName(string currentAttributeName, bool withAttributeSuffix) return false; } } + + private static bool ContainsAttributeList(GreenNode node, int attributeListKind) + { + if (node.RawKind == attributeListKind) + return true; + + foreach (var child in node.ChildNodesAndTokens()) + { + if (node.IsToken) + return false; + + if (ContainsAttributeList(child, attributeListKind)) + return true; + } + + return false; + } } diff --git a/src/Compilers/VisualBasic/Portable/SourceGeneration/VisualBasicSyntaxHelper.vb b/src/Compilers/VisualBasic/Portable/SourceGeneration/VisualBasicSyntaxHelper.vb index 8aa7563127965..653224d769944 100644 --- a/src/Compilers/VisualBasic/Portable/SourceGeneration/VisualBasicSyntaxHelper.vb +++ b/src/Compilers/VisualBasic/Portable/SourceGeneration/VisualBasicSyntaxHelper.vb @@ -19,6 +19,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Public Overrides ReadOnly Property IsCaseSensitive As Boolean = False + Public Overrides ReadOnly Property AttributeListKind As Integer = SyntaxKind.AttributeList + Public Overrides Function IsValidIdentifier(name As String) As Boolean Return SyntaxFacts.IsValidIdentifier(name) End Function @@ -83,20 +85,34 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Throw ExceptionUtilities.UnexpectedValue(node.Kind()) End Function - Public Overrides Sub AddAliases(node As SyntaxNode, aliases As ArrayBuilder(Of (aliasName As String, symbolName As String)), [global] As Boolean) + Public Overloads Function GetUnqualifiedIdentifierOfName(name As InternalSyntax.NameSyntax) As InternalSyntax.IdentifierTokenSyntax + Dim qualifiedName = TryCast(name, InternalSyntax.QualifiedNameSyntax) + If qualifiedName IsNot Nothing Then + Return qualifiedName.Right.Identifier + End If + + Dim simpleName = TryCast(name, InternalSyntax.SimpleNameSyntax) + If simpleName IsNot Nothing Then + Return simpleName.Identifier + End If + + Throw ExceptionUtilities.UnexpectedValue(name.KindText) + End Function + + Public Overrides Sub AddAliases(node As GreenNode, aliases As ArrayBuilder(Of (aliasName As String, symbolName As String)), [global] As Boolean) ' VB does not have global aliases at the syntax level. If [global] Then Return End If - Dim compilationUnit = TryCast(node, CompilationUnitSyntax) + Dim compilationUnit = TryCast(node, InternalSyntax.CompilationUnitSyntax) If compilationUnit Is Nothing Then Return End If For Each importsStatement In compilationUnit.Imports - For Each importsClause In importsStatement.ImportsClauses - ProcessImportsClause(aliases, importsClause) + For i = 0 To importsStatement.ImportsClauses.Count - 1 + ProcessImportsClause(aliases, importsStatement.ImportsClauses(i)) Next Next End Sub @@ -106,12 +122,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic For Each globalImport In vbOptions.GlobalImports Dim clause = globalImport.Clause - ProcessImportsClause(aliases, clause) + ProcessImportsClause(aliases, DirectCast(clause.Green, InternalSyntax.ImportsClauseSyntax)) Next End Sub - Private Sub ProcessImportsClause(aliases As ArrayBuilder(Of (aliasName As String, symbolName As String)), clause As ImportsClauseSyntax) - Dim importsClause = TryCast(clause, SimpleImportsClauseSyntax) + Private Sub ProcessImportsClause(aliases As ArrayBuilder(Of (aliasName As String, symbolName As String)), clause As InternalSyntax.ImportsClauseSyntax) + Dim importsClause = TryCast(clause, InternalSyntax.SimpleImportsClauseSyntax) If importsClause?.Alias IsNot Nothing Then aliases.Add((importsClause.Alias.Identifier.ValueText, GetUnqualifiedIdentifierOfName(importsClause.Name).ValueText)) End If