From 8fd808cd20b51bbfcbd6c500611d7880118feaa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=8Caba=20=C5=A0agi?= Date: Tue, 12 Sep 2023 12:58:40 +0200 Subject: [PATCH] Improve TokenType analyzer`s location mapping --- .../ShimLayer/SyntaxKindEx.cs | 3 +- .../Extensions/LocationExtensions.cs | 82 ++++++++++++++++++- .../Utilities/SymbolReferenceAnalyzerBase.cs | 6 +- .../Rules/Utilities/TokenTypeAnalyzerBase.cs | 4 +- .../Rules/Utilities/TokenTypeAnalyzerTest.cs | 32 ++++---- 5 files changed, 100 insertions(+), 27 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.CFG/ShimLayer/SyntaxKindEx.cs b/analyzers/src/SonarAnalyzer.CFG/ShimLayer/SyntaxKindEx.cs index 109e0bddaac..4d331896d4c 100644 --- a/analyzers/src/SonarAnalyzer.CFG/ShimLayer/SyntaxKindEx.cs +++ b/analyzers/src/SonarAnalyzer.CFG/ShimLayer/SyntaxKindEx.cs @@ -27,6 +27,7 @@ public static class SyntaxKindEx public const SyntaxKind Utf8StringLiteralToken = (SyntaxKind)8520; public const SyntaxKind Utf8SingleLineRawStringLiteralToken = (SyntaxKind)8521; public const SyntaxKind Utf8MultiLineRawStringLiteralToken = (SyntaxKind)8522; + public const SyntaxKind PragmaChecksumDirectiveTrivia = (SyntaxKind)8560; public const SyntaxKind ConflictMarkerTrivia = (SyntaxKind)8564; public const SyntaxKind IsPatternExpression = (SyntaxKind)8657; public const SyntaxKind RangeExpression = (SyntaxKind)8658; @@ -84,9 +85,9 @@ public static class SyntaxKindEx public const SyntaxKind FunctionPointerUnmanagedCallingConventionList = (SyntaxKind)9066; public const SyntaxKind FunctionPointerUnmanagedCallingConvention = (SyntaxKind)9067; public const SyntaxKind RecordStructDeclaration = (SyntaxKind)9068; + public const SyntaxKind LineSpanDirectiveTrivia = (SyntaxKind)9071; public const SyntaxKind InterpolatedSingleLineRawStringStartToken = (SyntaxKind)9072; public const SyntaxKind InterpolatedMultiLineRawStringStartToken = (SyntaxKind)9073; public const SyntaxKind InterpolatedRawStringEndToken = (SyntaxKind)9074; - public const SyntaxKind PragmaChecksumDirectiveTrivia = (SyntaxKind)8560; } } diff --git a/analyzers/src/SonarAnalyzer.Common/Extensions/LocationExtensions.cs b/analyzers/src/SonarAnalyzer.Common/Extensions/LocationExtensions.cs index 48d04d47153..1c695100be3 100644 --- a/analyzers/src/SonarAnalyzer.Common/Extensions/LocationExtensions.cs +++ b/analyzers/src/SonarAnalyzer.Common/Extensions/LocationExtensions.cs @@ -18,12 +18,88 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using Microsoft.CodeAnalysis.CSharp; + namespace SonarAnalyzer.Extensions; public static class LocationExtensions { public static FileLinePositionSpan GetMappedLineSpanIfAvailable(this Location location) => - GeneratedCodeRecognizer.IsRazorGeneratedFile(location.SourceTree) - ? location.GetMappedLineSpan() - : location.GetLineSpan(); + GeneratedCodeRecognizer.IsRazorGeneratedFile(location.SourceTree) ? location.GetMappedLineSpan() : location.GetLineSpan(); + + public static FileLinePositionSpan GetMappedLineSpanIfAvailable(this Location location, SyntaxToken token) + { + var node = token.Parent; + if (GeneratedCodeRecognizer.IsRazorGeneratedFile(location.SourceTree)) + { + var mappedLocation = location.GetMappedLineSpan(); + if (mappedLocation.HasMappedPath) + { + var lineDirective = FindLineDirecitive(node); + if (LineSpanDirectiveTriviaSyntaxWrapper.IsInstance(lineDirective) + && (LineSpanDirectiveTriviaSyntaxWrapper)lineDirective is var lineSpanDirective + && lineSpanDirective.CharacterOffset.ValueText is var stringValue + && int.TryParse(stringValue, out var numericValue) + && numericValue >= location.GetLineSpan().Span.End.Character) + { + return location.GetLineSpan(); + } + } + return mappedLocation; + } + return location.GetLineSpan(); + } + + private static SyntaxNode FindLineDirecitive(SyntaxNode node) + { + while (node != null) + { + if (LineDirective(node) is { } lineDirective) + { + return lineDirective; + } + var directive = FindLineDirectiveOnSameLevel(node); + + if (directive != null) + { + return directive; + } + + node = node.Parent; + } + return null; + } + + private static SyntaxNode FindLineDirectiveOnSameLevel(SyntaxNode node) + { + var childNodes = node.Parent.ChildNodes().ToArray(); + var index = -1; + for (var i = 0; i < childNodes.Count(); i++) + { + if (childNodes[i] == node) + { + index = i; + break; + } + } + + if (index == -1) + { + return null; + } + + for (var j = index; j >= 0; j--) + { + if (LineDirective(childNodes[j]) is { } lineDirective) + { + return lineDirective; + } + } + + return null; + } + + private static SyntaxNode LineDirective(SyntaxNode node) => + node.DescendantNodes(_ => true, true).FirstOrDefault(x => x.IsKind(SyntaxKind.LineDirectiveTrivia) + || x.IsKind(SyntaxKindEx.LineSpanDirectiveTrivia)); } diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/SymbolReferenceAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/SymbolReferenceAnalyzerBase.cs index 3e15cb533c9..870896b1bec 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/SymbolReferenceAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/SymbolReferenceAnalyzerBase.cs @@ -124,9 +124,7 @@ private static SymbolReferenceInfo.Types.SymbolReference GetSymbolReference(IRea { var reference = references[i]; if (!reference.IsDeclaration - && reference.Identifier.GetLocation().GetMappedLineSpanIfAvailable() is var mappedLineSpan - // Syntax tree can contain elements from external files (e.g. razor imports files) - // We need to make sure that we don't count these elements. + && reference.Identifier.GetLocation().GetMappedLineSpanIfAvailable(reference.Identifier) is var mappedLineSpan && string.Equals(mappedLineSpan.Path, filePath, StringComparison.OrdinalIgnoreCase)) { symbolReference.Reference.Add(GetTextRange(mappedLineSpan)); @@ -140,7 +138,7 @@ private static SymbolReferenceInfo.Types.SymbolReference GetSymbolReference(IRea for (var i = 0; i < references.Count; i++) { if (references[i].IsDeclaration - && references[i].Identifier.GetLocation().GetMappedLineSpanIfAvailable() is var mappedLineSpan + && references[i].Identifier.GetLocation().GetMappedLineSpanIfAvailable(references[i].Identifier) is var mappedLineSpan && string.Equals(mappedLineSpan.Path, filePath, StringComparison.OrdinalIgnoreCase)) { return mappedLineSpan; diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/TokenTypeAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/TokenTypeAnalyzerBase.cs index e727b873b53..84906df1dbe 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/TokenTypeAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/TokenTypeAnalyzerBase.cs @@ -140,7 +140,7 @@ token switch protected TokenInfo TokenInfo(SyntaxToken token, TokenType tokenType) { - var span = token.GetLocation().GetMappedLineSpanIfAvailable(); + var span = token.GetLocation().GetMappedLineSpanIfAvailable(token); return tokenType == TokenType.UnknownTokentype || (string.IsNullOrWhiteSpace(token.Text) && tokenType != TokenType.StringLiteral) || !(string.IsNullOrWhiteSpace(filePath) || string.Equals(span.Path, filePath, StringComparison.OrdinalIgnoreCase)) @@ -205,7 +205,7 @@ protected TriviaClassifierBase(string filePath) new() { TokenType = tokenType, - TextRange = GetTextRange(Location.Create(tree, span).GetLineSpan()) + TextRange = GetTextRange(Location.Create(tree, span).GetMappedLineSpanIfAvailable()) }; } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/TokenTypeAnalyzerTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/TokenTypeAnalyzerTest.cs index d1837a3c144..787d3ca9e41 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/TokenTypeAnalyzerTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/TokenTypeAnalyzerTest.cs @@ -102,27 +102,25 @@ public void Verify_Razor() .And.Contain((58, 58), "ks") .And.Contain((59, 59), "ksns") .And.Contain((60, 60), "ksnss") - .And.Contain((66, 66), "kkks") + .And.Contain((62, 66), "c") + .And.Contain((66, 66), "kkkcsc") .And.Contain((68, 68), "kktkkkkkts") - .And.Contain((84, 84), "nt") - .And.Contain((85, 85), "ntn") - .And.Contain((86, 86), "nknkk") - .And.Contain((87, 87), "nt") - .And.Contain((91, 91), "nnn") - .And.Contain((92, 92), "nnn") - .And.Contain((93, 93), "nttn") + .And.Contain((70, 70), "c") + .And.Contain((71, 73), "c") + .And.Contain((74, 74), "c") + .And.Contain((84, 84), "t") + .And.Contain((85, 85), "tn") + .And.Contain((86, 86), "knkk") + .And.Contain((87, 87), "t") + .And.Contain((91, 91), "nn") + .And.Contain((92, 92), "nn") + .And.Contain((93, 93), "ttn") .And.Contain((98, 98), "kn") .And.Contain((99, 99), "kn") .And.Contain((100, 100), "kn") - .And.Contain((215, 215), "c") - .And.Contain((216, 216), "c") - .And.Contain((217, 217), "c") - .And.Contain((246, 250), "c") - .And.Contain((246, 250), "c") - .And.Contain((250, 250), "cc") - .And.Contain((254, 254), "c") - .And.Contain((255, 257), "c") - .And.Contain((258, 258), "c"); + .And.Contain((106, 106), "c") + .And.Contain((107, 107), "c") + .And.Contain((108, 108), "c"); }); }