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

Implement analyzer for unused resources #6338

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ Rule ID | Category | Severity | Notes
--------|----------|----------|-------
CA1514 | Maintainability | Info | AvoidLengthCheckWhenSlicingToEndAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1514)
CA1515 | Maintainability | Disabled | MakeTypesInternal, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1515)
CA1516 | Maintainability | Info | AvoidUnusedResources, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1516)
CA2262 | Usage | Info | ProvideHttpClientHandlerMaxResponseHeaderLengthValueCorrectly, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2262)
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;

namespace Microsoft.CodeQuality.Analyzers.Maintainability
{
using static MicrosoftCodeQualityAnalyzersResources;

[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public sealed class AvoidUnusedResources : DiagnosticAnalyzer
{
internal const string RuleId = "CA1516";
internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(
RuleId,
CreateLocalizableResourceString(nameof(AvoidUnusedResourcesTitle)),
CreateLocalizableResourceString(nameof(AvoidUnusedResourceMessage)),
DiagnosticCategory.Maintainability,
RuleLevel.IdeSuggestion,
description: null,
isPortedFxCopRule: false,
isDataflowRule: false,
isReportedAtCompilationEnd: true);

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

public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need to report diagnostics on generated code?

Copy link
Member Author

Choose a reason for hiding this comment

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

@mavasani I report the diagnostic in the C# code that is generated from resx. The other way I can think of is reporting a diagnostic with Location.None, but I think it's clearer to have a location.

context.RegisterCompilationStartAction(context =>
{
var resourceFileNames = context.Options.AdditionalFiles
.Where(f => Path.GetExtension(f.Path).Equals(".resx", StringComparison.Ordinal)).Select(f => Path.GetFileNameWithoutExtension(f.Path)).ToImmutableHashSet();

var resourceTypes = new ConcurrentBag<INamedTypeSymbol>();
var usedProperties = new ConcurrentBag<IPropertySymbol>();

context.RegisterSymbolAction(context =>
{
if (IsResourceType(context.Symbol, resourceFileNames))
{
resourceTypes.Add((INamedTypeSymbol)context.Symbol);
}
}, SymbolKind.NamedType);

context.RegisterOperationAction(context =>
{
var operation = (IPropertyReferenceOperation)context.Operation;
var property = operation.Property;
if (IsResourceType(property.ContainingType, resourceFileNames) && IsCandidateProperty(property))
{
usedProperties.Add(property);
}
}, OperationKind.PropertyReference);

context.RegisterCompilationEndAction(context =>
{
foreach (var resourceType in resourceTypes)
{
foreach (var member in resourceType.GetMembers())
{
if (member is not IPropertySymbol property || !IsCandidateProperty(property))
{
continue;
}

if (!usedProperties.Contains(property))
{
context.ReportDiagnostic(property.CreateDiagnostic(Rule, property.Name));
}
}
}
});
});
}

private static bool IsResourceType(ISymbol type, ImmutableHashSet<string> resourceFileNames)
=> resourceFileNames.Contains(type.Name) && type.ContainingType is null;

private static bool IsCandidateProperty(IPropertySymbol property)
=> property.IsStatic && property.Type.SpecialType == SpecialType.System_String;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1303,6 +1303,12 @@
<value>Because an application's API isn't typically referenced from outside the assembly, types can be made internal</value>
</data>
<data name="MakeTypesInternalTitle" xml:space="preserve">
<value>Consider making public types internal</value>
<value>Consider making public types internal</value>
</data>
</root>
<data name="AvoidUnusedResourceMessage" xml:space="preserve">
<value>Remove unused resources '{0}'</value>
</data>
<data name="AvoidUnusedResourcesTitle" xml:space="preserve">
<value>Remove unused resource</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@
<target state="translated">Vyhněte se výstupním parametrům</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourceMessage">
<source>Remove unused resources '{0}'</source>
<target state="new">Remove unused resources '{0}'</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourcesTitle">
<source>Remove unused resource</source>
<target state="new">Remove unused resource</target>
<note />
</trans-unit>
<trans-unit id="CollectionsShouldImplementGenericInterfaceMultipleMessage">
<source>Type '{0}' directly or indirectly inherits '{1}' without implementing any of '{2}'. Publicly-visible types should implement the generic version to broaden usability.</source>
<target state="translated">Typ {0} přímo nebo nepřímo dědí {1} bez implementace kterékoli položky {2}. Veřejně viditelné typy by kvůli větší využitelnosti měly implementovat obecnou verzi.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@
<target state="translated">out-Parameter vermeiden</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourceMessage">
<source>Remove unused resources '{0}'</source>
<target state="new">Remove unused resources '{0}'</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourcesTitle">
<source>Remove unused resource</source>
<target state="new">Remove unused resource</target>
<note />
</trans-unit>
<trans-unit id="CollectionsShouldImplementGenericInterfaceMultipleMessage">
<source>Type '{0}' directly or indirectly inherits '{1}' without implementing any of '{2}'. Publicly-visible types should implement the generic version to broaden usability.</source>
<target state="translated">Der Typ "{0}" erbt "{1}" direkt oder indirekt ohne Implementierung von "{2}". Öffentlich sichtbare Typen müssen die generische Version implementieren, um die Nutzbarkeit zu erweitern.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@
<target state="translated">Evitar parámetros out</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourceMessage">
<source>Remove unused resources '{0}'</source>
<target state="new">Remove unused resources '{0}'</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourcesTitle">
<source>Remove unused resource</source>
<target state="new">Remove unused resource</target>
<note />
</trans-unit>
<trans-unit id="CollectionsShouldImplementGenericInterfaceMultipleMessage">
<source>Type '{0}' directly or indirectly inherits '{1}' without implementing any of '{2}'. Publicly-visible types should implement the generic version to broaden usability.</source>
<target state="translated">El tipo "{0}" hereda "{1}" de manera directa o indirecta sin implementar cualquier "{2}". Los tipos visibles públicamente deben implementar la versión genérica para ampliar su capacidad de uso.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@
<target state="translated">Éviter les paramètres out</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourceMessage">
<source>Remove unused resources '{0}'</source>
<target state="new">Remove unused resources '{0}'</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourcesTitle">
<source>Remove unused resource</source>
<target state="new">Remove unused resource</target>
<note />
</trans-unit>
<trans-unit id="CollectionsShouldImplementGenericInterfaceMultipleMessage">
<source>Type '{0}' directly or indirectly inherits '{1}' without implementing any of '{2}'. Publicly-visible types should implement the generic version to broaden usability.</source>
<target state="translated">Le type '{0}' hérite directement ou indirectement de tout '{1}' sans implémenter '{2}'. Les types visibles de manière publique doivent implémenter la version générique pour étendre leur efficacité d'utilisation.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@
<target state="translated">Evitare parametri out</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourceMessage">
<source>Remove unused resources '{0}'</source>
<target state="new">Remove unused resources '{0}'</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourcesTitle">
<source>Remove unused resource</source>
<target state="new">Remove unused resource</target>
<note />
</trans-unit>
<trans-unit id="CollectionsShouldImplementGenericInterfaceMultipleMessage">
<source>Type '{0}' directly or indirectly inherits '{1}' without implementing any of '{2}'. Publicly-visible types should implement the generic version to broaden usability.</source>
<target state="translated">Il tipo '{0}' eredita direttamente o indirettamente '{1}' senza implementare '{2}'. I tipi visibili pubblicamente devono implementare la versione generica per ampliare l'usabilità.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@
<target state="translated">out パラメーターを使用しません</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourceMessage">
<source>Remove unused resources '{0}'</source>
<target state="new">Remove unused resources '{0}'</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourcesTitle">
<source>Remove unused resource</source>
<target state="new">Remove unused resource</target>
<note />
</trans-unit>
<trans-unit id="CollectionsShouldImplementGenericInterfaceMultipleMessage">
<source>Type '{0}' directly or indirectly inherits '{1}' without implementing any of '{2}'. Publicly-visible types should implement the generic version to broaden usability.</source>
<target state="translated">型 '{0}' は、'{2}' のいずれも実装することなく '{1}' を直接または間接的に継承します。公開されている型は、操作性の拡充のためジェネリック バージョンを実装する必要があります。</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@
<target state="translated">out 매개 변수를 사용하지 마십시오.</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourceMessage">
<source>Remove unused resources '{0}'</source>
<target state="new">Remove unused resources '{0}'</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourcesTitle">
<source>Remove unused resource</source>
<target state="new">Remove unused resource</target>
<note />
</trans-unit>
<trans-unit id="CollectionsShouldImplementGenericInterfaceMultipleMessage">
<source>Type '{0}' directly or indirectly inherits '{1}' without implementing any of '{2}'. Publicly-visible types should implement the generic version to broaden usability.</source>
<target state="translated">형식 '{0}'은(는) '{2}' 중 무엇도 구현하지 않고 직접 또는 간접적으로 '{1}'을(를) 상속합니다. 공개된 형식의 유용성을 확대하려면 제네릭 버전을 구현해야 합니다.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@
<target state="translated">Unikaj parametrów out</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourceMessage">
<source>Remove unused resources '{0}'</source>
<target state="new">Remove unused resources '{0}'</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourcesTitle">
<source>Remove unused resource</source>
<target state="new">Remove unused resource</target>
<note />
</trans-unit>
<trans-unit id="CollectionsShouldImplementGenericInterfaceMultipleMessage">
<source>Type '{0}' directly or indirectly inherits '{1}' without implementing any of '{2}'. Publicly-visible types should implement the generic version to broaden usability.</source>
<target state="translated">Typ „{0}” bezpośrednio lub pośrednio dziedziczy element „{1}”, bez implementowania żadnego z elementów „{2}”. Typy widoczne publicznie powinny implementować wersję ogólną, aby zwiększyć użyteczność.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@
<target state="translated">Evitar parâmetros out</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourceMessage">
<source>Remove unused resources '{0}'</source>
<target state="new">Remove unused resources '{0}'</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourcesTitle">
<source>Remove unused resource</source>
<target state="new">Remove unused resource</target>
<note />
</trans-unit>
<trans-unit id="CollectionsShouldImplementGenericInterfaceMultipleMessage">
<source>Type '{0}' directly or indirectly inherits '{1}' without implementing any of '{2}'. Publicly-visible types should implement the generic version to broaden usability.</source>
<target state="translated">O tipo '{0}' herda direta ou indiretamente '{1}' sem implementar nenhum dos '{2}'. Tipos publicamente visíveis devem implementar a versão genérica para ampliar a usabilidade.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@
<target state="translated">Не используйте параметры out</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourceMessage">
<source>Remove unused resources '{0}'</source>
<target state="new">Remove unused resources '{0}'</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourcesTitle">
<source>Remove unused resource</source>
<target state="new">Remove unused resource</target>
<note />
</trans-unit>
<trans-unit id="CollectionsShouldImplementGenericInterfaceMultipleMessage">
<source>Type '{0}' directly or indirectly inherits '{1}' without implementing any of '{2}'. Publicly-visible types should implement the generic version to broaden usability.</source>
<target state="translated">Тип "{0}" прямо или косвенно наследует "{1}" без реализации какого-либо из "{2}". Общедоступные типы должны реализовывать универсальную версию для повышения удобства использования.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@
<target state="translated">out parametrelerinden kaçının</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourceMessage">
<source>Remove unused resources '{0}'</source>
<target state="new">Remove unused resources '{0}'</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourcesTitle">
<source>Remove unused resource</source>
<target state="new">Remove unused resource</target>
<note />
</trans-unit>
<trans-unit id="CollectionsShouldImplementGenericInterfaceMultipleMessage">
<source>Type '{0}' directly or indirectly inherits '{1}' without implementing any of '{2}'. Publicly-visible types should implement the generic version to broaden usability.</source>
<target state="translated">'{0}' türü, herhangi bir '{2}' uygulamaksızın doğrudan veya dolaylı olarak şunu devralır: '{1}'. Herkese görünür türler, kullanılabilirliği genişletmek için genel sürümü uygulamalıdır.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@
<target state="translated">避免使用 out 参数</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourceMessage">
<source>Remove unused resources '{0}'</source>
<target state="new">Remove unused resources '{0}'</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourcesTitle">
<source>Remove unused resource</source>
<target state="new">Remove unused resource</target>
<note />
</trans-unit>
<trans-unit id="CollectionsShouldImplementGenericInterfaceMultipleMessage">
<source>Type '{0}' directly or indirectly inherits '{1}' without implementing any of '{2}'. Publicly-visible types should implement the generic version to broaden usability.</source>
<target state="translated">类型“{0}”会直接或间接继承“{1}”,而不实现任何“{2}”。公共可见的类型应实现通用版本以扩大可用性。</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@
<target state="translated">避免使用 out 參數</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourceMessage">
<source>Remove unused resources '{0}'</source>
<target state="new">Remove unused resources '{0}'</target>
<note />
</trans-unit>
<trans-unit id="AvoidUnusedResourcesTitle">
<source>Remove unused resource</source>
<target state="new">Remove unused resource</target>
<note />
</trans-unit>
<trans-unit id="CollectionsShouldImplementGenericInterfaceMultipleMessage">
<source>Type '{0}' directly or indirectly inherits '{1}' without implementing any of '{2}'. Publicly-visible types should implement the generic version to broaden usability.</source>
<target state="translated">類型 '{0}' 未實作任何 '{2}' 便直接或間接繼承 '{1}'。公開顯示的類型應實作一般版本以擴大可用性。</target>
Expand Down
12 changes: 12 additions & 0 deletions src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,18 @@ Unlike a class library, an application's API isn't typically referenced publicly
|CodeFix|True|
---

## [CA1516](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1516): Remove unused resource

Remove unused resources '{0}'

|Item|Value|
|-|-|
|Category|Maintainability|
|Enabled|True|
|Severity|Info|
|CodeFix|False|
---

## [CA1700](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1700): Do not name enum values 'Reserved'

This rule assumes that an enumeration member that has a name that contains "reserved" is not currently used but is a placeholder to be renamed or removed in a future version. Renaming or removing a member is a breaking change.
Expand Down