Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@ namespace Microsoft.NetCore.CSharp.Analyzers.Resources
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class CSharpMarkAssembliesWithNeutralResourcesLanguageAnalyzer : MarkAssembliesWithNeutralResourcesLanguageAnalyzer
{
protected override void RegisterAttributeAnalyzer(CompilationStartAnalysisContext context, Action onResourceFound, INamedTypeSymbol generatedCode)
protected override void RegisterAttributeAnalyzer(CompilationStartAnalysisContext context, Func<bool> shouldAnalyze, Action<SyntaxNodeAnalysisContext> onResourceFound, INamedTypeSymbol generatedCode)
{
context.RegisterSyntaxNodeAction(context =>
{
if (!shouldAnalyze())
{
return;
}

var attributeSyntax = (AttributeSyntax)context.Node;
if (!CheckAttribute(attributeSyntax))
{
Expand All @@ -30,7 +35,7 @@ protected override void RegisterAttributeAnalyzer(CompilationStartAnalysisContex
return;
}

onResourceFound();
onResourceFound(context);
}, SyntaxKind.Attribute);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
Expand Down Expand Up @@ -33,10 +34,9 @@ public abstract class MarkAssembliesWithNeutralResourcesLanguageAnalyzer : Diagn
RuleLevel.IdeSuggestion,
description: CreateLocalizableResourceString(nameof(MarkAssembliesWithNeutralResourcesLanguageDescription)),
isPortedFxCopRule: true,
isDataflowRule: false,
isReportedAtCompilationEnd: true);
isDataflowRule: false);

protected abstract void RegisterAttributeAnalyzer(CompilationStartAnalysisContext context, Action onResourceFound, INamedTypeSymbol generatedCode);
protected abstract void RegisterAttributeAnalyzer(CompilationStartAnalysisContext context, Func<bool> shouldAnalyze, Action<SyntaxNodeAnalysisContext> onResourceFound, INamedTypeSymbol generatedCode);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);

Expand Down Expand Up @@ -68,28 +68,48 @@ public override void Initialize(AnalysisContext context)
return;
}

var hasResource = false;
RegisterAttributeAnalyzer(context, () => hasResource = true, generatedCode);
var alreadyReportedDiagnostic = new StrongBox<bool>(false);
var @lock = new object();

context.RegisterCompilationEndAction(context =>
{
// there is nothing to do.
if (!hasResource)
RegisterAttributeAnalyzer(
context,
shouldAnalyze: () =>
{
return;
}

if (data != null &&
data.ApplicationSyntaxReference?.GetSyntax(context.CancellationToken) is { } attributeSyntax)
// This value can only change from false to true. So no need to lock if it's already true here.
if (alreadyReportedDiagnostic.Value)
{
return false;
}

lock (@lock)
{
return !alreadyReportedDiagnostic.Value;
}
},
onResourceFound: context =>
{
// we have the attribute but its doing it wrong.
context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(Rule));
return;
}

// attribute just don't exist
context.ReportNoLocationDiagnostic(Rule);
});
lock (@lock)
{
if (alreadyReportedDiagnostic.Value)
{
return;
}

alreadyReportedDiagnostic.Value = true;

if (data != null &&
data.ApplicationSyntaxReference?.GetSyntax(context.CancellationToken) is { } attributeSyntax)
{
// we have the attribute but its doing it wrong.
context.ReportDiagnostic(attributeSyntax.CreateDiagnostic(Rule));
Copy link

@sharwell sharwell Aug 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❗ This diagnostic (the one with a specific location) should always be reported. It can set alreadyReportedDiagnostic to true, but it should not return without reporting if alreadyReportedDiagnostic was already true.

Edit: It cannot set alreadyReportedDiagnostic to true because it would destabilize the no-location reporting.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Diagnostic with location will always be reported if we find a source assembly attribute here, otherwise a no-location diagnostic will always be reported - I believe the logic here is deterministic because data is computed at CompilationStart and does not alter during subsequent callbacks.

return;
}

// attribute just don't exist
context.ReportNoLocationDiagnostic(Rule);
}
},
generatedCode);
});
}

Expand Down
6 changes: 2 additions & 4 deletions src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,7 @@
"tags": [
"PortedFromFxCop",
"Telemetry",
"EnabledRuleInAggressiveMode",
"CompilationEnd"
"EnabledRuleInAggressiveMode"
]
}
},
Expand Down Expand Up @@ -6387,8 +6386,7 @@
"tags": [
"PortedFromFxCop",
"Telemetry",
"EnabledRuleInAggressiveMode",
"CompilationEnd"
"EnabledRuleInAggressiveMode"
]
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Testing;
Expand All @@ -18,13 +18,27 @@ public class MarkAssembliesWithNeutralResourcesLanguageTests
namespace DesignerFile {
[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""System.Resources.Tools.StronglyTypedResourceBuilder"", ""4.0.0.0"")]
internal class Resource1 { }

[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""System.Resources.Tools.StronglyTypedResourceBuilder"", ""4.0.0.0"")]
internal class Resource2 { }

[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""System.Resources.Tools.StronglyTypedResourceBuilder"", ""4.0.0.0"")]
internal class Resource3 { }
}";

private const string BasicDesignerFile = @"
Namespace My.Resources
<Global.System.CodeDom.Compiler.GeneratedCodeAttribute(""System.Resources.Tools.StronglyTypedResourceBuilder"", ""4.0.0.0"")> _
Friend Class Resource1
End Class

<Global.System.CodeDom.Compiler.GeneratedCodeAttribute(""System.Resources.Tools.StronglyTypedResourceBuilder"", ""4.0.0.0"")> _
Friend Class Resource2
End Class

<Global.System.CodeDom.Compiler.GeneratedCodeAttribute(""System.Resources.Tools.StronglyTypedResourceBuilder"", ""4.0.0.0"")> _
Friend Class Resource3
End Class
End Namespace";

[Fact]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

Imports Microsoft.NetCore.Analyzers.Resources
Imports Microsoft.CodeAnalysis
Expand All @@ -13,9 +13,13 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Resources
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
Public NotInheritable Class BasicMarkAssembliesWithNeutralResourcesLanguageAnalyzer
Inherits MarkAssembliesWithNeutralResourcesLanguageAnalyzer
Protected Overrides Sub RegisterAttributeAnalyzer(context As CompilationStartAnalysisContext, onResourceFound As Action, generatedCode As INamedTypeSymbol)
Protected Overrides Sub RegisterAttributeAnalyzer(context As CompilationStartAnalysisContext, shouldAnalyze As Func(Of Boolean), onResourceFound As Action(Of SyntaxNodeAnalysisContext), generatedCode As INamedTypeSymbol)
context.RegisterSyntaxNodeAction(
Sub(nc)
If Not shouldAnalyze() Then
Return
End If

Dim attributeSyntax = DirectCast(nc.Node, AttributeSyntax)
If Not CheckBasicAttribute(attributeSyntax) Then
Return
Expand All @@ -25,7 +29,7 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Resources
Return
End If

onResourceFound()
onResourceFound(nc)
End Sub, SyntaxKind.Attribute)
End Sub

Expand Down
6 changes: 6 additions & 0 deletions src/Utilities/Compiler/Extensions/DiagnosticExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,12 @@ public static void ReportNoLocationDiagnostic(
params object[] args)
=> context.Compilation.ReportNoLocationDiagnostic(rule, context.ReportDiagnostic, properties: null, args);

public static void ReportNoLocationDiagnostic(
this SyntaxNodeAnalysisContext context,
DiagnosticDescriptor rule,
params object[] args)
=> context.Compilation.ReportNoLocationDiagnostic(rule, context.ReportDiagnostic, properties: null, args);

public static void ReportNoLocationDiagnostic(
this Compilation compilation,
DiagnosticDescriptor rule,
Expand Down