Skip to content
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

.razor: implement the token type metrics #7924

Draft
wants to merge 15 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion analyzers/src/SonarAnalyzer.CFG/ShimLayer/SyntaxKindEx.cs
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
Expand Up @@ -28,6 +28,11 @@ public class SymbolReferenceAnalyzer : SymbolReferenceAnalyzerBase<SyntaxKind>
protected override SyntaxNode GetBindableParent(SyntaxToken token) =>
token.GetBindableParent();

protected override ImmutableSortedSet<LineDirectiveEntry> CalculateLineDirectiveMap(SyntaxTree syntaxTree) =>
syntaxTree.GetRoot().DescendantNodes(_ => true, true).Where(x => x.IsKind(SyntaxKind.LineDirectiveTrivia)
|| x.IsKind(SyntaxKindEx.LineSpanDirectiveTrivia))
.Select(x => new LineDirectiveEntry(x.GetLocation().GetLineSpan().StartLinePosition.Line, x)).ToImmutableSortedSet();

protected override ReferenceInfo[] CreateDeclarationReferenceInfo(SyntaxNode node, SemanticModel model) =>
node switch
{
Expand Down
Expand Up @@ -28,11 +28,16 @@ public class TokenTypeAnalyzer : TokenTypeAnalyzerBase<SyntaxKind>
{
protected override ILanguageFacade<SyntaxKind> Language { get; } = CSharpFacade.Instance;

protected override TokenClassifierBase GetTokenClassifier(SemanticModel semanticModel, bool skipIdentifierTokens) =>
new TokenClassifier(semanticModel, skipIdentifierTokens);
protected override TokenClassifierBase GetTokenClassifier(SemanticModel semanticModel, bool skipIdentifierTokens, string filePath, ImmutableSortedSet<LineDirectiveEntry> lineDirectiveMap) =>
new TokenClassifier(semanticModel, skipIdentifierTokens, filePath, lineDirectiveMap);

protected override TriviaClassifierBase GetTriviaClassifier() =>
new TriviaClassifier();
protected override TriviaClassifierBase GetTriviaClassifier(string filePath, ImmutableSortedSet<LineDirectiveEntry> lineDirectiveMap) =>
new TriviaClassifier(filePath, lineDirectiveMap);

protected override ImmutableSortedSet<LineDirectiveEntry> CalculateLineDirectiveMap(SyntaxTree syntaxTree) =>
syntaxTree.GetRoot().DescendantNodes(_ => true, true).Where(x => x.IsKind(SyntaxKind.LineDirectiveTrivia)
|| x.IsKind(SyntaxKindEx.LineSpanDirectiveTrivia))
.Select(x => new LineDirectiveEntry(x.GetLocation().GetLineSpan().StartLinePosition.Line, x)).ToImmutableSortedSet();

internal sealed class TokenClassifier : TokenClassifierBase
{
Expand All @@ -54,7 +59,8 @@ internal sealed class TokenClassifier : TokenClassifierBase
SyntaxKindEx.InterpolatedRawStringEndToken,
};

public TokenClassifier(SemanticModel semanticModel, bool skipIdentifiers) : base(semanticModel, skipIdentifiers) { }
public TokenClassifier(SemanticModel semanticModel, bool skipIdentifiers, string filePath, ImmutableSortedSet<LineDirectiveEntry> lineDirectiveMap)
: base(semanticModel, skipIdentifiers, filePath, lineDirectiveMap) { }

protected override SyntaxNode GetBindableParent(SyntaxToken token) =>
token.GetBindableParent();
Expand Down Expand Up @@ -343,6 +349,8 @@ internal sealed class TriviaClassifier : TriviaClassifierBase
SyntaxKind.MultiLineDocumentationCommentTrivia,
};

public TriviaClassifier(string filePath, ImmutableSortedSet<LineDirectiveEntry> lineDirectiveMap) : base(filePath, lineDirectiveMap) { }

protected override bool IsRegularComment(SyntaxTrivia trivia) =>
trivia.IsAnyKind(RegularCommentToken);

Expand Down
Expand Up @@ -18,12 +18,37 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using SonarAnalyzer.Rules;

namespace SonarAnalyzer.Extensions;

public static class LocationExtensions
{
public static FileLinePositionSpan GetMappedLineSpanIfAvailable(this Location location) =>
GeneratedCodeRecognizer.IsRazorGeneratedFile(location.SourceTree)
? location.GetMappedLineSpan()
: location.GetLineSpan();
public static FileLinePositionSpan GetMappedLineSpanIfAvailable(this Location location, ImmutableSortedSet<LineDirectiveEntry> lineDirectiveMap)
{
if (GeneratedCodeRecognizer.IsRazorGeneratedFile(location.SourceTree)
&& !lineDirectiveMap.IsEmpty)
{
var unmappedLocation = location.GetLineSpan().StartLinePosition.Line;
var lineSpanIndex = -1;
for (var i = 0; i < lineDirectiveMap.Count; i++)
{
if (lineDirectiveMap[i].LineNumber > unmappedLocation)
{
lineSpanIndex = i - 1;
break;
}
}

return lineSpanIndex != -1
&& LineSpanDirectiveTriviaSyntaxWrapper.IsInstance(lineDirectiveMap[lineSpanIndex].LineDirective)
&& (LineSpanDirectiveTriviaSyntaxWrapper)lineDirectiveMap[lineSpanIndex].LineDirective is var lineSpanDirective
&& lineSpanDirective.CharacterOffset.ValueText is var stringValue
&& int.TryParse(stringValue, out var numericValue)
&& numericValue >= location.GetLineSpan().Span.End.Character
? location.GetLineSpan()
: location.GetMappedLineSpan();
}
return location.GetLineSpan();
}
}
Expand Up @@ -41,7 +41,7 @@ public abstract class CopyPasteTokenAnalyzerBase<TSyntaxKind> : UtilityAnalyzerB
!GeneratedCodeRecognizer.IsRazorGeneratedFile(tree)
&& base.ShouldGenerateMetrics(tree, compilation);

protected sealed override CopyPasteTokenInfo CreateMessage(SyntaxTree tree, SemanticModel model)
protected sealed override CopyPasteTokenInfo CreateMessage(SyntaxTree tree, SemanticModel model, ImmutableSortedSet<LineDirectiveEntry> lineDirectiveMap)
{
var cpdTokenInfo = new CopyPasteTokenInfo { FilePath = tree.FilePath };
foreach (var token in tree.GetRoot().DescendantTokens(n => !IsUsingDirective(n)))
Expand Down
Expand Up @@ -32,13 +32,12 @@ public abstract class FileMetadataAnalyzerBase<TSyntaxKind> : UtilityAnalyzerBas
protected override bool AnalyzeGeneratedCode => true;

protected FileMetadataAnalyzerBase() : base(DiagnosticId, Title) { }

protected override bool ShouldGenerateMetrics(SyntaxTree tree, Compilation compilation) =>
!GeneratedCodeRecognizer.IsRazorGeneratedFile(tree)
&& base.ShouldGenerateMetrics(tree, compilation);

protected sealed override FileMetadataInfo CreateMessage(SyntaxTree tree, SemanticModel model) =>
new()
protected sealed override FileMetadataInfo CreateMessage(SyntaxTree tree, SemanticModel model, ImmutableSortedSet<LineDirectiveEntry> lineDirectiveMap) =>
new FileMetadataInfo
{
FilePath = tree.FilePath,
IsGenerated = Language.GeneratedCodeRecognizer.IsGenerated(tree),
Expand Down
@@ -0,0 +1,43 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2023 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

namespace SonarAnalyzer.Rules
{
public readonly struct LineDirectiveEntry : IComparable<LineDirectiveEntry>
{
public readonly int LineNumber;
public readonly SyntaxNode LineDirective;

public LineDirectiveEntry(int lineNumber)
{
LineNumber = lineNumber;
LineDirective = null;
}

public LineDirectiveEntry(int lineNumber, SyntaxNode lineDirective)
{
LineNumber = lineNumber;
LineDirective = lineDirective;
}

public int CompareTo(LineDirectiveEntry other) =>
LineNumber.CompareTo(other.LineNumber);
}
}
Expand Up @@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using Microsoft.CodeAnalysis;
using SonarAnalyzer.Protobuf;

namespace SonarAnalyzer.Rules
Expand All @@ -43,7 +44,7 @@ public abstract class LogAnalyzerBase<TSyntaxKind> : UtilityAnalyzerBase<TSyntax
new LogInfo { Severity = LogSeverity.Info, Text = "Concurrent execution: " + (IsConcurrentExecutionEnabled() ? "enabled" : "disabled") }
};

protected sealed override LogInfo CreateMessage(SyntaxTree tree, SemanticModel model) =>
protected sealed override LogInfo CreateMessage(SyntaxTree tree, SemanticModel model, ImmutableSortedSet<LineDirectiveEntry> lineDirectiveMap) =>
tree.IsGenerated(Language.GeneratedCodeRecognizer, model.Compilation)
? CreateMessage(tree)
: null;
Expand Down
Expand Up @@ -35,7 +35,7 @@ public abstract class MetricsAnalyzerBase<TSyntaxKind> : UtilityAnalyzerBase<TSy

protected MetricsAnalyzerBase() : base(DiagnosticId, Title) { }

protected sealed override MetricsInfo CreateMessage(SyntaxTree tree, SemanticModel model)
protected sealed override MetricsInfo CreateMessage(SyntaxTree tree, SemanticModel model, ImmutableSortedSet<LineDirectiveEntry> lineDirectiveMap)
{
var metrics = GetMetrics(tree, model);
var complexity = metrics.Complexity;
Expand Down
Expand Up @@ -39,14 +39,14 @@ public abstract class SymbolReferenceAnalyzerBase<TSyntaxKind> : UtilityAnalyzer

protected SymbolReferenceAnalyzerBase() : base(DiagnosticId, Title) { }

protected sealed override SymbolReferenceInfo CreateMessage(SyntaxTree tree, SemanticModel model)
protected sealed override SymbolReferenceInfo CreateMessage(SyntaxTree tree, SemanticModel model, ImmutableSortedSet<LineDirectiveEntry> lineDirectiveMap)
{
var filePath = GetFilePath(tree);
var symbolReferenceInfo = new SymbolReferenceInfo { FilePath = filePath };
var references = GetReferences(tree.GetRoot(), model);
foreach (var symbol in references.Keys)
{
if (GetSymbolReference(references[symbol], filePath) is { } reference)
if (GetSymbolReference(references[symbol], filePath, lineDirectiveMap) is { } reference)
{
symbolReferenceInfo.Reference.Add(reference);
}
Expand Down Expand Up @@ -111,9 +111,9 @@ protected sealed override SymbolReferenceInfo CreateMessage(SyntaxTree tree, Sem
var symbol => symbol
};

private static SymbolReferenceInfo.Types.SymbolReference GetSymbolReference(IReadOnlyList<ReferenceInfo> references, string filePath)
private static SymbolReferenceInfo.Types.SymbolReference GetSymbolReference(IReadOnlyList<ReferenceInfo> references, string filePath, ImmutableSortedSet<LineDirectiveEntry> lineDirectiveMap)
{
var declarationSpan = GetDeclarationSpan(references, filePath);
var declarationSpan = GetDeclarationSpan(references, filePath, lineDirectiveMap);
if (!declarationSpan.HasValue)
{
return null;
Expand All @@ -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(lineDirectiveMap) is var mappedLineSpan
&& string.Equals(mappedLineSpan.Path, filePath, StringComparison.OrdinalIgnoreCase))
{
symbolReference.Reference.Add(GetTextRange(mappedLineSpan));
Expand All @@ -135,12 +133,12 @@ private static SymbolReferenceInfo.Types.SymbolReference GetSymbolReference(IRea
return symbolReference;
}

private static FileLinePositionSpan? GetDeclarationSpan(IReadOnlyList<ReferenceInfo> references, string filePath)
private static FileLinePositionSpan? GetDeclarationSpan(IReadOnlyList<ReferenceInfo> references, string filePath, ImmutableSortedSet<LineDirectiveEntry> lineDirectiveMap)
{
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(lineDirectiveMap) is var mappedLineSpan
&& string.Equals(mappedLineSpan.Path, filePath, StringComparison.OrdinalIgnoreCase))
{
return mappedLineSpan;
Expand Down