diff --git a/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs index abffa4590d59ece806d390e8ad3b2acae8e01ad8..8fc96f02ccab7ac3371aee02ca807e568950054f 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs @@ -186,56 +186,30 @@ public static NamespaceDeclarationSyntax GetInnermostNamespaceDeclarationWithUsi } } - // Matches the following: - // - // (whitespace* newline)+ - private static readonly Matcher s_oneOrMoreBlankLines; - - // Matches the following: - // - // (whitespace* (single-comment|multi-comment) whitespace* newline)+ OneOrMoreBlankLines - private static readonly Matcher s_bannerMatcher; - - // Used to match the following: - // - // (whitespace* (single-comment|multi-comment) whitespace* newline)+ blankLine* - private static readonly Matcher s_fileBannerMatcher; - - static SyntaxNodeExtensions() - { - var whitespace = Matcher.Repeat(Match(SyntaxKind.WhitespaceTrivia, "\\b")); - var endOfLine = Match(SyntaxKind.EndOfLineTrivia, "\\n"); - var singleBlankLine = Matcher.Sequence(whitespace, endOfLine); - - var shebangComment = Match(SyntaxKind.ShebangDirectiveTrivia, "#!"); - var singleLineComment = Match(SyntaxKind.SingleLineCommentTrivia, "//"); - var multiLineComment = Match(SyntaxKind.MultiLineCommentTrivia, "/**/"); - var anyCommentMatcher = Matcher.Choice(shebangComment, singleLineComment, multiLineComment); - - var commentLine = Matcher.Sequence(whitespace, anyCommentMatcher, whitespace, endOfLine); - - s_oneOrMoreBlankLines = Matcher.OneOrMore(singleBlankLine); - s_bannerMatcher = - Matcher.Sequence( - Matcher.OneOrMore(commentLine), - s_oneOrMoreBlankLines); - s_fileBannerMatcher = - Matcher.Sequence( - Matcher.OneOrMore(commentLine), - Matcher.Repeat(singleBlankLine)); - } - - private static Matcher Match(SyntaxKind kind, string description) - { - return Matcher.Single(t => t.Kind() == kind, description); - } - public static IEnumerable GetAllPrecedingTriviaToPreviousToken( this SyntaxNode node, SourceText sourceText = null, bool includePreviousTokenTrailingTriviaOnlyIfOnSameLine = false) => node.GetFirstToken().GetAllPrecedingTriviaToPreviousToken( sourceText, includePreviousTokenTrailingTriviaOnlyIfOnSameLine); + public static ImmutableArray GetLeadingBlankLines(this TSyntaxNode node) where TSyntaxNode : SyntaxNode + => CSharpSyntaxFactsService.Instance.GetLeadingBlankLines(node); + + public static TSyntaxNode GetNodeWithoutLeadingBlankLines(this TSyntaxNode node) where TSyntaxNode : SyntaxNode + => CSharpSyntaxFactsService.Instance.GetNodeWithoutLeadingBlankLines(node); + + public static TSyntaxNode GetNodeWithoutLeadingBlankLines(this TSyntaxNode node, out ImmutableArray strippedTrivia) where TSyntaxNode : SyntaxNode + => CSharpSyntaxFactsService.Instance.GetNodeWithoutLeadingBlankLines(node, out strippedTrivia); + + public static ImmutableArray GetLeadingBannerAndPreprocessorDirectives(this TSyntaxNode node) where TSyntaxNode : SyntaxNode + => CSharpSyntaxFactsService.Instance.GetLeadingBannerAndPreprocessorDirectives(node); + + public static TSyntaxNode GetNodeWithoutLeadingBannerAndPreprocessorDirectives(this TSyntaxNode node) where TSyntaxNode : SyntaxNode + => CSharpSyntaxFactsService.Instance.GetNodeWithoutLeadingBannerAndPreprocessorDirectives(node); + + public static TSyntaxNode GetNodeWithoutLeadingBannerAndPreprocessorDirectives(this TSyntaxNode node, out ImmutableArray strippedTrivia) where TSyntaxNode : SyntaxNode + => CSharpSyntaxFactsService.Instance.GetNodeWithoutLeadingBannerAndPreprocessorDirectives(node, out strippedTrivia); + /// /// Returns all of the trivia to the left of this token up to the previous token (concatenates /// the previous token's trailing trivia and this token's leading trivia). @@ -559,116 +533,6 @@ public static bool IsAnyLambdaOrAnonymousMethod(this SyntaxNode node) return result; } - public static IEnumerable GetLeadingBlankLines( - this TSyntaxNode node) - where TSyntaxNode : SyntaxNode - { - node.GetNodeWithoutLeadingBlankLines(out var blankLines); - return blankLines; - } - - public static TSyntaxNode GetNodeWithoutLeadingBlankLines( - this TSyntaxNode node) - where TSyntaxNode : SyntaxNode - { - return node.GetNodeWithoutLeadingBlankLines(out var blankLines); - } - - public static TSyntaxNode GetNodeWithoutLeadingBlankLines( - this TSyntaxNode node, out IEnumerable strippedTrivia) - where TSyntaxNode : SyntaxNode - { - var leadingTriviaToKeep = new List(node.GetLeadingTrivia()); - - var index = 0; - s_oneOrMoreBlankLines.TryMatch(leadingTriviaToKeep, ref index); - - strippedTrivia = new List(leadingTriviaToKeep.Take(index)); - - return node.WithLeadingTrivia(leadingTriviaToKeep.Skip(index)); - } - - public static IEnumerable GetLeadingBannerAndPreprocessorDirectives( - this TSyntaxNode node) - where TSyntaxNode : SyntaxNode - { - node.GetNodeWithoutLeadingBannerAndPreprocessorDirectives(out var leadingTrivia); - return leadingTrivia; - } - - public static TSyntaxNode GetNodeWithoutLeadingBannerAndPreprocessorDirectives( - this TSyntaxNode node) - where TSyntaxNode : SyntaxNode - { - return node.GetNodeWithoutLeadingBannerAndPreprocessorDirectives(out var strippedTrivia); - } - - public static TSyntaxNode GetNodeWithoutLeadingBannerAndPreprocessorDirectives( - this TSyntaxNode node, out IEnumerable strippedTrivia) - where TSyntaxNode : SyntaxNode - { - var leadingTrivia = node.GetLeadingTrivia(); - - // Rules for stripping trivia: - // 1) If there is a pp directive, then it (and all preceding trivia) *must* be stripped. - // This rule supersedes all other rules. - // 2) If there is a doc comment, it cannot be stripped. Even if there is a doc comment, - // followed by 5 new lines, then the doc comment still must stay with the node. This - // rule does *not* supersede rule 1. - // 3) Single line comments in a group (i.e. with no blank lines between them) belong to - // the node *iff* there is no blank line between it and the following trivia. - - List leadingTriviaToStrip, leadingTriviaToKeep; - - int ppIndex = -1; - for (int i = leadingTrivia.Count - 1; i >= 0; i--) - { - if (SyntaxFacts.IsPreprocessorDirective(leadingTrivia[i].Kind())) - { - ppIndex = i; - break; - } - } - - if (ppIndex != -1) - { - // We have a pp directive. it (and all previous trivia) must be stripped. - leadingTriviaToStrip = new List(leadingTrivia.Take(ppIndex + 1)); - leadingTriviaToKeep = new List(leadingTrivia.Skip(ppIndex + 1)); - } - else - { - leadingTriviaToKeep = new List(leadingTrivia); - leadingTriviaToStrip = new List(); - } - - // Now, consume as many banners as we can. s_fileBannerMatcher will only be matched at - // the start of the file. - var index = 0; - while ( - s_oneOrMoreBlankLines.TryMatch(leadingTriviaToKeep, ref index) || - s_bannerMatcher.TryMatch(leadingTriviaToKeep, ref index) || - (node.FullSpan.Start == 0 && s_fileBannerMatcher.TryMatch(leadingTriviaToKeep, ref index))) - { - } - - leadingTriviaToStrip.AddRange(leadingTriviaToKeep.Take(index)); - - strippedTrivia = leadingTriviaToStrip; - return node.WithLeadingTrivia(leadingTriviaToKeep.Skip(index)); - } - - public static ImmutableArray GetFileBanner(this SyntaxNode root) - { - Debug.Assert(root.FullSpan.Start == 0); - - var leadingTrivia = root.GetLeadingTrivia(); - var index = 0; - s_fileBannerMatcher.TryMatch(leadingTrivia.ToList(), ref index); - - return ImmutableArray.CreateRange(leadingTrivia.Take(index)); - } - public static bool IsAnyAssignExpression(this SyntaxNode node) { return SyntaxFacts.IsAssignmentExpression(node.Kind()); diff --git a/src/Workspaces/CSharp/Portable/Extensions/SyntaxTriviaExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/SyntaxTriviaExtensions.cs index 4a28bf071aedf5c7f1869a567be8c41b57c0c5a6..58c78b45c672c38f3b68b2128d0c969baca8488c 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/SyntaxTriviaExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/SyntaxTriviaExtensions.cs @@ -45,19 +45,13 @@ public static bool IsRegularOrDocComment(this SyntaxTrivia trivia) } public static bool IsSingleLineComment(this SyntaxTrivia trivia) - { - return trivia.Kind() == SyntaxKind.SingleLineCommentTrivia; - } + => trivia.Kind() == SyntaxKind.SingleLineCommentTrivia; public static bool IsMultiLineComment(this SyntaxTrivia trivia) - { - return trivia.Kind() == SyntaxKind.MultiLineCommentTrivia; - } + => trivia.Kind() == SyntaxKind.MultiLineCommentTrivia; public static bool IsShebangDirective(this SyntaxTrivia trivia) - { - return trivia.Kind() == SyntaxKind.ShebangDirectiveTrivia; - } + => trivia.Kind() == SyntaxKind.ShebangDirectiveTrivia; public static bool IsCompleteMultiLineComment(this SyntaxTrivia trivia) { diff --git a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs index c193cd0063fce3c8bab506d9c30593b044dfb3c2..678599c4ded2e58ff51b6a19e1300613c6280a51 100644 --- a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs +++ b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs @@ -1832,12 +1832,24 @@ public SyntaxNode GetOperandOfPrefixUnaryExpression(SyntaxNode node) public SyntaxNode GetNextExecutableStatement(SyntaxNode statement) => ((StatementSyntax)statement).GetNextStatement(); - public bool IsWhitespaceTrivia(SyntaxTrivia trivia) + public override bool IsWhitespaceTrivia(SyntaxTrivia trivia) => trivia.IsWhitespace(); - public bool IsEndOfLineTrivia(SyntaxTrivia trivia) + public override bool IsEndOfLineTrivia(SyntaxTrivia trivia) => trivia.IsEndOfLine(); + public override bool IsSingleLineCommentTrivia(SyntaxTrivia trivia) + => trivia.IsSingleLineComment(); + + public override bool IsMultiLineCommentTrivia(SyntaxTrivia trivia) + => trivia.IsMultiLineComment(); + + public override bool IsShebangDirectiveTrivia(SyntaxTrivia trivia) + => trivia.IsShebangDirective(); + + public override bool IsPreprocessorDirective(SyntaxTrivia trivia) + => SyntaxFacts.IsPreprocessorDirective(trivia.Kind()); + private class AddFirstMissingCloseBaceRewriter: CSharpSyntaxRewriter { private readonly SyntaxNode _contextNode; @@ -1979,8 +1991,5 @@ public bool IsBetweenTypeMembers(SourceText sourceText, SyntaxNode root, int pos public ImmutableArray GetSelectedMembers(SyntaxNode root, TextSpan textSpan) => ImmutableArray.CastUp(root.GetMembersInSpan(textSpan)); - - public ImmutableArray GetFileBanner(SyntaxNode root) - => root.GetFileBanner(); } } \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/AbstractSyntaxFactsService.cs b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/AbstractSyntaxFactsService.cs index 12fb8258c100b9780ae9b2ba2294b2d37c78d155..e34355e72ba029b3ebe745f4e57b42c0c59905f4 100644 --- a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/AbstractSyntaxFactsService.cs +++ b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/AbstractSyntaxFactsService.cs @@ -1,5 +1,9 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServices @@ -17,6 +21,53 @@ internal abstract class AbstractSyntaxFactsService private readonly static ObjectPool> s_aliasMapPool = new ObjectPool>(() => new Dictionary(StringComparer.OrdinalIgnoreCase)); + // Matches the following: + // + // (whitespace* newline)+ + private readonly Matcher _oneOrMoreBlankLines; + + // Matches the following: + // + // (whitespace* (single-comment|multi-comment) whitespace* newline)+ OneOrMoreBlankLines + private readonly Matcher _bannerMatcher; + + // Used to match the following: + // + // (whitespace* (single-comment|multi-comment) whitespace* newline)+ blankLine* + private readonly Matcher _fileBannerMatcher; + + protected AbstractSyntaxFactsService() + { + var whitespace = Matcher.Repeat( + Matcher.Single(IsWhitespaceTrivia, "\\b")); + var endOfLine = Matcher.Single(IsEndOfLineTrivia, "\\n"); + var singleBlankLine = Matcher.Sequence(whitespace, endOfLine); + + var shebangComment = Matcher.Single(IsShebangDirectiveTrivia, "#!"); + var singleLineComment = Matcher.Single(IsSingleLineCommentTrivia, "//"); + var multiLineComment = Matcher.Single(IsMultiLineCommentTrivia, "/**/"); + var anyCommentMatcher = Matcher.Choice(shebangComment, singleLineComment, multiLineComment); + + var commentLine = Matcher.Sequence(whitespace, anyCommentMatcher, whitespace, endOfLine); + + _oneOrMoreBlankLines = Matcher.OneOrMore(singleBlankLine); + _bannerMatcher = + Matcher.Sequence( + Matcher.OneOrMore(commentLine), + _oneOrMoreBlankLines); + _fileBannerMatcher = + Matcher.Sequence( + Matcher.OneOrMore(commentLine), + Matcher.Repeat(singleBlankLine)); + } + + public abstract bool IsWhitespaceTrivia(SyntaxTrivia trivia); + public abstract bool IsEndOfLineTrivia(SyntaxTrivia trivia); + public abstract bool IsSingleLineCommentTrivia(SyntaxTrivia trivia); + public abstract bool IsMultiLineCommentTrivia(SyntaxTrivia trivia); + public abstract bool IsShebangDirectiveTrivia(SyntaxTrivia trivia); + public abstract bool IsPreprocessorDirective(SyntaxTrivia trivia); + protected static List> AllocateAliasMapList() { return s_aliasMapListPool.Allocate(); @@ -47,5 +98,113 @@ protected static void FreeAliasMap(Dictionary aliasMap) { return s_aliasMapPool.Allocate(); } + + public ImmutableArray GetLeadingBlankLines(TSyntaxNode node) + where TSyntaxNode : SyntaxNode + { + GetNodeWithoutLeadingBlankLines(node, out var blankLines); + return blankLines; + } + + public TSyntaxNode GetNodeWithoutLeadingBlankLines(TSyntaxNode node) + where TSyntaxNode : SyntaxNode + { + return GetNodeWithoutLeadingBlankLines(node, out var blankLines); + } + + public TSyntaxNode GetNodeWithoutLeadingBlankLines( + TSyntaxNode node, out ImmutableArray strippedTrivia) + where TSyntaxNode : SyntaxNode + { + var leadingTriviaToKeep = new List(node.GetLeadingTrivia()); + + var index = 0; + _fileBannerMatcher.TryMatch(leadingTriviaToKeep, ref index); + + strippedTrivia = leadingTriviaToKeep.Take(index).ToImmutableArray(); + + return node.WithLeadingTrivia(leadingTriviaToKeep.Skip(index)); + } + + public ImmutableArray GetLeadingBannerAndPreprocessorDirectives(TSyntaxNode node) + where TSyntaxNode : SyntaxNode + { + GetNodeWithoutLeadingBannerAndPreprocessorDirectives(node, out var leadingTrivia); + return leadingTrivia; + } + + public TSyntaxNode GetNodeWithoutLeadingBannerAndPreprocessorDirectives( + TSyntaxNode node) + where TSyntaxNode : SyntaxNode + { + return GetNodeWithoutLeadingBannerAndPreprocessorDirectives(node, out var strippedTrivia); + } + + public TSyntaxNode GetNodeWithoutLeadingBannerAndPreprocessorDirectives( + TSyntaxNode node, out ImmutableArray strippedTrivia) + where TSyntaxNode : SyntaxNode + { + var leadingTrivia = node.GetLeadingTrivia(); + + // Rules for stripping trivia: + // 1) If there is a pp directive, then it (and all preceding trivia) *must* be stripped. + // This rule supersedes all other rules. + // 2) If there is a doc comment, it cannot be stripped. Even if there is a doc comment, + // followed by 5 new lines, then the doc comment still must stay with the node. This + // rule does *not* supersede rule 1. + // 3) Single line comments in a group (i.e. with no blank lines between them) belong to + // the node *iff* there is no blank line between it and the following trivia. + + List leadingTriviaToStrip, leadingTriviaToKeep; + + var ppIndex = -1; + for (var i = leadingTrivia.Count - 1; i >= 0; i--) + { + if (this.IsPreprocessorDirective(leadingTrivia[i])) + { + ppIndex = i; + break; + } + } + + if (ppIndex != -1) + { + // We have a pp directive. it (and all previous trivia) must be stripped. + leadingTriviaToStrip = new List(leadingTrivia.Take(ppIndex + 1)); + leadingTriviaToKeep = new List(leadingTrivia.Skip(ppIndex + 1)); + } + else + { + leadingTriviaToKeep = new List(leadingTrivia); + leadingTriviaToStrip = new List(); + } + + // Now, consume as many banners as we can. s_fileBannerMatcher will only be matched at + // the start of the file. + var index = 0; + + while ( + _oneOrMoreBlankLines.TryMatch(leadingTriviaToKeep, ref index) || + _bannerMatcher.TryMatch(leadingTriviaToKeep, ref index) || + (node.FullSpan.Start == 0 && _fileBannerMatcher.TryMatch(leadingTriviaToKeep, ref index))) + { + } + + leadingTriviaToStrip.AddRange(leadingTriviaToKeep.Take(index)); + + strippedTrivia = leadingTriviaToStrip.ToImmutableArray(); + return node.WithLeadingTrivia(leadingTriviaToKeep.Skip(index)); + } + + public ImmutableArray GetFileBanner(SyntaxNode root) + { + Debug.Assert(root.FullSpan.Start == 0); + + var leadingTrivia = root.GetLeadingTrivia(); + var index = 0; + _fileBannerMatcher.TryMatch(leadingTrivia.ToList(), ref index); + + return ImmutableArray.CreateRange(leadingTrivia.Take(index)); + } } -} +} \ No newline at end of file diff --git a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicCodeGenerationService.vb b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicCodeGenerationService.vb index 5f34913909f7eb1fc163407742132ffdb44eecad..045d7e60f80b4f75b1f3e749bc6404975f4bad1b 100644 --- a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicCodeGenerationService.vb +++ b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicCodeGenerationService.vb @@ -483,8 +483,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Dim statementArray = statements.OfType(Of StatementSyntax).ToArray() Dim newBlock As SyntaxNode If options.BeforeThisLocation IsNot Nothing Then - Dim strippedTrivia As IEnumerable(Of SyntaxTrivia) = Nothing - Dim newStatement = oldStatement.GetNodeWithoutLeadingBannerAndPreprocessorDirectives(strippedTrivia) + Dim strippedTrivia As ImmutableArray(Of SyntaxTrivia) = Nothing + Dim newStatement = VisualBasicSyntaxFactsService.Instance.GetNodeWithoutLeadingBannerAndPreprocessorDirectives( + oldStatement, strippedTrivia) statementArray(0) = statementArray(0).WithLeadingTrivia(strippedTrivia) diff --git a/src/Workspaces/VisualBasic/Portable/Extensions/SyntaxNodeExtensions.vb b/src/Workspaces/VisualBasic/Portable/Extensions/SyntaxNodeExtensions.vb index 50b068e3457c0a8c80ed10e701188e83e0ccfbf3..5c4ff14cd5ef332c522396e3af8cb8edf3a31bbc 100644 --- a/src/Workspaces/VisualBasic/Portable/Extensions/SyntaxNodeExtensions.vb +++ b/src/Workspaces/VisualBasic/Portable/Extensions/SyntaxNodeExtensions.vb @@ -188,44 +188,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions Return Contract.FailWithReturn(Of SyntaxList(Of StatementSyntax))("unknown statements container!") End Function - ' Matches the following: - ' - ' (whitespace* newline)+ - Private ReadOnly s_oneOrMoreBlankLines As Matcher(Of SyntaxTrivia) - - ' Matches the following: - ' - ' (whitespace* comment whitespace* newline)+ OneOrMoreBlankLines - Private ReadOnly s_bannerMatcher As Matcher(Of SyntaxTrivia) - - ' Used to match the following: - ' - ' (whitespace* comment whitespace* newline)+ blankLine* - Private ReadOnly s_fileBannerMatcher As Matcher(Of SyntaxTrivia) - - Sub New() - Dim whitespace = Matcher.Repeat(Match(SyntaxKind.WhitespaceTrivia, "\\b")) - Dim endOfLine = Match(SyntaxKind.EndOfLineTrivia, "\\n") - Dim singleBlankLine = Matcher.Sequence(whitespace, endOfLine) - - Dim comment = Match(SyntaxKind.CommentTrivia, "'") - Dim commentLine = Matcher.Sequence(whitespace, comment, whitespace, endOfLine) - - s_oneOrMoreBlankLines = Matcher.OneOrMore(singleBlankLine) - s_bannerMatcher = - Matcher.Sequence( - Matcher.OneOrMore(commentLine), - s_oneOrMoreBlankLines) - s_fileBannerMatcher = - Matcher.Sequence( - Matcher.OneOrMore(commentLine), - Matcher.Repeat(singleBlankLine)) - End Sub - - Private Function Match(kind As SyntaxKind, description As String) As Matcher(Of SyntaxTrivia) - Return Matcher.Single(Of SyntaxTrivia)(Function(t) t.Kind = kind, description) - End Function - Friend Function IsMultiLineLambda(node As SyntaxNode) As Boolean Return SyntaxFacts.IsMultiLineLambdaExpression(node.Kind()) @@ -448,106 +410,34 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions Return False End Function - - Public Function GetLeadingBlankLines(Of TSyntaxNode As SyntaxNode)(node As TSyntaxNode) As IEnumerable(Of SyntaxTrivia) - Dim blankLines As IEnumerable(Of SyntaxTrivia) = Nothing - node.GetNodeWithoutLeadingBlankLines(blankLines) - Return blankLines + Public Function GetLeadingBlankLines(Of TSyntaxNode As SyntaxNode)(node As TSyntaxNode) As ImmutableArray(Of SyntaxTrivia) + Return VisualBasicSyntaxFactsService.Instance.GetLeadingBlankLines(node) End Function Public Function GetNodeWithoutLeadingBlankLines(Of TSyntaxNode As SyntaxNode)(node As TSyntaxNode) As TSyntaxNode - Dim blankLines As IEnumerable(Of SyntaxTrivia) = Nothing - Return node.GetNodeWithoutLeadingBlankLines(blankLines) + Return VisualBasicSyntaxFactsService.Instance.GetNodeWithoutLeadingBlankLines(node) End Function - Public Function GetNodeWithoutLeadingBlankLines(Of TSyntaxNode As SyntaxNode)( - node As TSyntaxNode, ByRef strippedTrivia As IEnumerable(Of SyntaxTrivia)) As TSyntaxNode - - Dim leadingTriviaToKeep = New List(Of SyntaxTrivia)(node.GetLeadingTrivia()) - - Dim index = 0 - s_oneOrMoreBlankLines.TryMatch(leadingTriviaToKeep, index) - - strippedTrivia = New List(Of SyntaxTrivia)(leadingTriviaToKeep.Take(index)) - - Return DirectCast(node.WithLeadingTrivia(leadingTriviaToKeep.Skip(index)), TSyntaxNode) + Public Function GetNodeWithoutLeadingBlankLines(Of TSyntaxNode As SyntaxNode)(node As TSyntaxNode, ByRef strippedTrivia As ImmutableArray(Of SyntaxTrivia)) As TSyntaxNode + Return VisualBasicSyntaxFactsService.Instance.GetNodeWithoutLeadingBlankLines(node, strippedTrivia) End Function - Public Function GetLeadingBannerAndPreprocessorDirectives(Of TSyntaxNode As SyntaxNode)(node As TSyntaxNode) As IEnumerable(Of SyntaxTrivia) - Dim leadingTrivia As IEnumerable(Of SyntaxTrivia) = Nothing - node.GetNodeWithoutLeadingBannerAndPreprocessorDirectives(leadingTrivia) - Return leadingTrivia + Public Function GetLeadingBannerAndPreprocessorDirectives(Of TSyntaxNode As SyntaxNode)(node As TSyntaxNode) As ImmutableArray(Of SyntaxTrivia) + Return VisualBasicSyntaxFactsService.Instance.GetLeadingBannerAndPreprocessorDirectives(node) End Function Public Function GetNodeWithoutLeadingBannerAndPreprocessorDirectives(Of TSyntaxNode As SyntaxNode)(node As TSyntaxNode) As TSyntaxNode - Dim strippedTrivia As IEnumerable(Of SyntaxTrivia) = Nothing - Return node.GetNodeWithoutLeadingBannerAndPreprocessorDirectives(strippedTrivia) + Return VisualBasicSyntaxFactsService.Instance.GetNodeWithoutLeadingBannerAndPreprocessorDirectives(node) End Function - Public Function GetNodeWithoutLeadingBannerAndPreprocessorDirectives(Of TSyntaxNode As SyntaxNode)( - node As TSyntaxNode, - ByRef strippedTrivia As IEnumerable(Of SyntaxTrivia)) As TSyntaxNode - - Dim leadingTrivia = node.GetLeadingTrivia() - - ' Rules for stripping trivia: - ' 1) If there is a pp directive, then it (and all preceding trivia) *must* be stripped. - ' This rule supersedes all other rules. - ' 2) If there is a doc comment, it cannot be stripped. Even if there is a doc comment, - ' followed by 5 new lines, then the doc comment still must stay with the node. This - ' rule does *not* supersede rule 1. - ' 3) Single line comments in a group (i.e. with no blank lines between them) belong to - ' the node *iff* there is no blank line between it and the following trivia. - - Dim leadingTriviaToStrip, leadingTriviaToKeep As List(Of SyntaxTrivia) - - Dim ppIndex = -1 - For i = leadingTrivia.Count - 1 To 0 Step -1 - If SyntaxFacts.IsPreprocessorDirective(leadingTrivia(i).Kind) Then - ppIndex = i - Exit For - End If - Next - - If ppIndex <> -1 Then - ' We have a pp directive. it (and all previous trivia) must be stripped. - leadingTriviaToStrip = New List(Of SyntaxTrivia)(leadingTrivia.Take(ppIndex + 1)) - leadingTriviaToKeep = New List(Of SyntaxTrivia)(leadingTrivia.Skip(ppIndex + 1)) - Else - leadingTriviaToKeep = New List(Of SyntaxTrivia)(leadingTrivia) - leadingTriviaToStrip = New List(Of SyntaxTrivia)() - End If - - ' Now, consume as many banners as we can. s_fileBannerMatcher will only be matched at - ' the start of the file. - Dim index = 0 - While ( - s_oneOrMoreBlankLines.TryMatch(leadingTriviaToKeep, index) OrElse - s_bannerMatcher.TryMatch(leadingTriviaToKeep, index) OrElse - (node.FullSpan.Start = 0 AndAlso s_fileBannerMatcher.TryMatch(leadingTriviaToKeep, index))) - End While - - leadingTriviaToStrip.AddRange(leadingTriviaToKeep.Take(index)) - - strippedTrivia = leadingTriviaToStrip - Return DirectCast(node.WithLeadingTrivia(leadingTriviaToKeep.Skip(index)), TSyntaxNode) - End Function - - - Public Function GetFileBanner(root As SyntaxNode) As ImmutableArray(Of SyntaxTrivia) - Debug.Assert(root.FullSpan.Start = 0) - - Dim leadingTrivia = root.GetLeadingTrivia() - Dim index = 0 - s_fileBannerMatcher.TryMatch(leadingTrivia.ToList(), index) - - Return ImmutableArray.CreateRange(leadingTrivia.Take(index)) + Public Function GetNodeWithoutLeadingBannerAndPreprocessorDirectives(Of TSyntaxNode As SyntaxNode)(node As TSyntaxNode, ByRef strippedTrivia As ImmutableArray(Of SyntaxTrivia)) As TSyntaxNode + Return VisualBasicSyntaxFactsService.Instance.GetNodeWithoutLeadingBannerAndPreprocessorDirectives(node, strippedTrivia) End Function ''' diff --git a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb index 6f1a06a575501e36d7987350f478db91b370c98f..c430075ec99835259dedcc8443a95f4681d01a64 100644 --- a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb +++ b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb @@ -1659,14 +1659,32 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return DirectCast(statement, StatementSyntax).GetNextStatement()?.FirstAncestorOrSelf(Of ExecutableStatementSyntax) End Function - Public Function IsWhitespaceTrivia(trivia As SyntaxTrivia) As Boolean Implements ISyntaxFactsService.IsWhitespaceTrivia + Public Overrides Function IsWhitespaceTrivia(trivia As SyntaxTrivia) As Boolean Implements ISyntaxFactsService.IsWhitespaceTrivia Return trivia.IsWhitespace() End Function - Public Function IsEndOfLineTrivia(trivia As SyntaxTrivia) As Boolean Implements ISyntaxFactsService.IsEndOfLineTrivia + Public Overrides Function IsEndOfLineTrivia(trivia As SyntaxTrivia) As Boolean Implements ISyntaxFactsService.IsEndOfLineTrivia Return trivia.IsEndOfLine() End Function + Public Overrides Function IsSingleLineCommentTrivia(trivia As SyntaxTrivia) As Boolean + Return trivia.Kind = SyntaxKind.CommentTrivia + End Function + + Public Overrides Function IsMultiLineCommentTrivia(trivia As SyntaxTrivia) As Boolean + ' VB does not have multi-line comments. + Return False + End Function + + Public Overrides Function IsShebangDirectiveTrivia(trivia As SyntaxTrivia) As Boolean + ' VB does not have shebang directives. + Return False + End Function + + Public Overrides Function IsPreprocessorDirective(trivia As SyntaxTrivia) As Boolean + Return SyntaxFacts.IsPreprocessorDirective(trivia.Kind()) + End Function + Public Function IsRegularComment(trivia As SyntaxTrivia) As Boolean Implements ISyntaxFactsService.IsRegularComment Return trivia.Kind = SyntaxKind.CommentTrivia End Function @@ -1734,8 +1752,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return ImmutableArray(Of SyntaxNode).CastUp(root.GetMembersInSpan(textSpan)) End Function - Public Function GetFileBanner(root As SyntaxNode) As ImmutableArray(Of SyntaxTrivia) Implements ISyntaxFactsService.GetFileBanner - Return root.GetFileBanner() + Private Function ISyntaxFactsService_GetFileBanner(root As SyntaxNode) As ImmutableArray(Of SyntaxTrivia) Implements ISyntaxFactsService.GetFileBanner + Return GetFileBanner(root) End Function End Class End Namespace \ No newline at end of file diff --git a/src/Workspaces/VisualBasic/Portable/Utilities/ImportsOrganizer.vb b/src/Workspaces/VisualBasic/Portable/Utilities/ImportsOrganizer.vb index cac8d4bc8c651b76da471bbd6ad73f0f65ac1ec5..b90cb0182b1e922e8d776d143549785b793e6462 100644 --- a/src/Workspaces/VisualBasic/Portable/Utilities/ImportsOrganizer.vb +++ b/src/Workspaces/VisualBasic/Portable/Utilities/ImportsOrganizer.vb @@ -2,6 +2,7 @@ Imports System Imports System.Collections.Generic +Imports System.Collections.Immutable Imports System.Globalization Imports System.Linq Imports System.Text @@ -16,7 +17,7 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.CodeAnalysis.VisualBasic.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.Utilities - Friend Partial Class ImportsOrganizer + Partial Friend Class ImportsOrganizer Public Shared Function Organize([imports] As SyntaxList(Of ImportsStatementSyntax), placeSystemNamespaceFirst As Boolean) As SyntaxList(Of ImportsStatementSyntax) If [imports].Count > 1 Then @@ -24,7 +25,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Utilities If Not [imports].SpansPreprocessorDirective() Then ' If there is a banner comment that precedes the nodes, ' then remove it and store it for later. - Dim leadingTrivia As IEnumerable(Of SyntaxTrivia) = Nothing + Dim leadingTrivia As ImmutableArray(Of SyntaxTrivia) = Nothing initialList(0) = initialList(0).GetNodeWithoutLeadingBannerAndPreprocessorDirectives(leadingTrivia) Dim comparer = If(placeSystemNamespaceFirst, ImportsStatementComparer.SystemFirstInstance, ImportsStatementComparer.NormalInstance)