From 740204154347d8d7c72336a09a71fb4e73487543 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sun, 10 Dec 2023 01:45:32 +0100 Subject: [PATCH 01/19] Add analyzer 'Declare explicit/implicit type' --- ...r.cs => UseExplicitTypeCodeFixProvider.cs} | 48 ++++- ...ypeInsteadOfVarInForEachCodeFixProvider.cs | 1 + ...r.cs => UseImplicitTypeCodeFixProvider.cs} | 7 +- src/Analyzers.xml | 23 +++ .../DeclareExplicitOrImplicitTypeAnalyzer.cs | 191 ++++++++++++++++++ ...plicitTypeInsteadOfVarInForEachAnalyzer.cs | 2 + ...nsteadOfVarWhenTypeIsNotObviousAnalyzer.cs | 2 + ...peInsteadOfVarWhenTypeIsObviousAnalyzer.cs | 2 + ...rInsteadOfExplicitTypeInForEachAnalyzer.cs | 2 + ...xplicitTypeWhenTypeIsNotObviousAnalyzer.cs | 2 + ...OfExplicitTypeWhenTypeIsObviousAnalyzer.cs | 2 + .../CSharp/DiagnosticIdentifiers.Generated.cs | 1 + .../CSharp/DiagnosticRules.Generated.cs | 24 ++- src/CSharp/CSharp/CSharpTypeAnalysis.cs | 55 ++--- .../CSharp/Extensions/CodeStyleExtensions.cs | 23 +++ src/Common/ConfigOptionKeys.Generated.cs | 1 + src/Common/ConfigOptionValues.Generated.cs | 3 + src/Common/ConfigOptions.Generated.cs | 7 + src/ConfigOptions.xml | 8 + ...1264DeclareExplicitOrImplicitTypeTests.cs} | 78 +++---- ...264DeclareExplicitOrImplicitTypeTests2.cs} | 32 +-- ...264DeclareExplicitOrImplicitTypeTests3.cs} | 49 +++-- ...264DeclareExplicitOrImplicitTypeTests4.cs} | 76 +++---- ...264DeclareExplicitOrImplicitTypeTests5.cs} | 20 +- ...264DeclareExplicitOrImplicitTypeTests6.cs} | 54 ++++- .../src/configurationFiles.generated.ts | 25 +-- 26 files changed, 533 insertions(+), 205 deletions(-) rename src/Analyzers.CodeFixes/CSharp/CodeFixes/{UseExplicitTypeInsteadOfVarCodeFixProvider.cs => UseExplicitTypeCodeFixProvider.cs} (65%) rename src/Analyzers.CodeFixes/CSharp/CodeFixes/{UseVarInsteadOfExplicitTypeCodeFixProvider.cs => UseImplicitTypeCodeFixProvider.cs} (90%) create mode 100644 src/Analyzers/CSharp/Analysis/DeclareExplicitOrImplicitTypeAnalyzer.cs rename src/Tests/Analyzers.Tests/{RCS1176UseVarInsteadOfExplicitTypeWhenTypeIsNotObviousTests.cs => RCS1264DeclareExplicitOrImplicitTypeTests.cs} (63%) rename src/Tests/Analyzers.Tests/{RCS1012UseExplicitTypeInsteadOfVarWhenTypeIsObviousTests.cs => RCS1264DeclareExplicitOrImplicitTypeTests2.cs} (63%) rename src/Tests/Analyzers.Tests/{RCS1010UseVarInsteadOfExplicitTypeWhenTypeIsObviousTests.cs => RCS1264DeclareExplicitOrImplicitTypeTests3.cs} (59%) rename src/Tests/Analyzers.Tests/{RCS1008UseExplicitTypeInsteadOfVarWhenTypeIsNotObviousTests.cs => RCS1264DeclareExplicitOrImplicitTypeTests4.cs} (59%) rename src/Tests/Analyzers.Tests/{RCS1009UseExplicitTypeInsteadOfVarInForEachTests.cs => RCS1264DeclareExplicitOrImplicitTypeTests5.cs} (81%) rename src/Tests/Analyzers.Tests/{RCS1177UseVarInsteadOfExplicitTypeInForEachTests.cs => RCS1264DeclareExplicitOrImplicitTypeTests6.cs} (55%) diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitTypeInsteadOfVarCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitTypeCodeFixProvider.cs similarity index 65% rename from src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitTypeInsteadOfVarCodeFixProvider.cs rename to src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitTypeCodeFixProvider.cs index 924853a865..e4fdbe8b54 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitTypeInsteadOfVarCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitTypeCodeFixProvider.cs @@ -14,9 +14,9 @@ namespace Roslynator.CSharp.CodeFixes; -[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseExplicitTypeInsteadOfVarCodeFixProvider))] +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseExplicitTypeCodeFixProvider))] [Shared] -public sealed class UseExplicitTypeInsteadOfVarCodeFixProvider : BaseCodeFixProvider +public sealed class UseExplicitTypeCodeFixProvider : BaseCodeFixProvider { public override ImmutableArray FixableDiagnosticIds { @@ -24,7 +24,8 @@ public override ImmutableArray FixableDiagnosticIds { return ImmutableArray.Create( DiagnosticIdentifiers.UseExplicitTypeInsteadOfVarWhenTypeIsNotObvious, - DiagnosticIdentifiers.UseExplicitTypeInsteadOfVarWhenTypeIsObvious); + DiagnosticIdentifiers.UseExplicitTypeInsteadOfVarWhenTypeIsObvious, + DiagnosticIdentifiers.DeclareExplicitOrImplicitType); } } @@ -32,8 +33,21 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) { SyntaxNode root = await context.GetSyntaxRootAsync().ConfigureAwait(false); - if (!TryFindFirstAncestorOrSelf(root, context.Span, out SyntaxNode node, predicate: f => f.IsKind(SyntaxKind.VariableDeclaration, SyntaxKind.DeclarationExpression))) + if (!TryFindFirstAncestorOrSelf( + root, + context.Span, + out SyntaxNode node, + predicate: f => f.IsKind( + SyntaxKind.VariableDeclaration, + SyntaxKind.DeclarationExpression, + SyntaxKind.ForEachStatement, + SyntaxKind.ForEachVariableStatement))) + { return; + } + + Document document = context.Document; + Diagnostic diagnostic = context.Diagnostics[0]; SemanticModel semanticModel = await context.GetSemanticModelAsync().ConfigureAwait(false); @@ -68,10 +82,8 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) RegisterCodeFix(context, variableDeclaration.Type, typeSymbol, semanticModel); } - else + else if (node is DeclarationExpressionSyntax declarationExpression) { - var declarationExpression = (DeclarationExpressionSyntax)node; - TypeSyntax type = declarationExpression.Type; var localSymbol = semanticModel.GetDeclaredSymbol(declarationExpression.Designation, context.CancellationToken) as ILocalSymbol; @@ -80,6 +92,28 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) RegisterCodeFix(context, type, typeSymbol, semanticModel); } + else if (node is ForEachStatementSyntax forEachStatement) + { + TypeSyntax type = forEachStatement.Type; + + ITypeSymbol typeSymbol = semanticModel.GetForEachStatementInfo((CommonForEachStatementSyntax)node).ElementType; + + CodeAction codeAction = CodeActionFactory.UseExplicitType(document, type, typeSymbol, semanticModel, equivalenceKey: GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + } + else if (node is ForEachVariableStatementSyntax forEachVariableStatement) + { + var declarationExpression2 = (DeclarationExpressionSyntax)forEachVariableStatement.Variable; + + TypeSyntax type = declarationExpression2.Type; + + ITypeSymbol typeSymbol = semanticModel.GetForEachStatementInfo((CommonForEachStatementSyntax)node).ElementType; + + CodeAction codeAction = CodeActionFactory.UseExplicitType(document, type, typeSymbol, semanticModel, equivalenceKey: GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + } } private static void RegisterCodeFix(CodeFixContext context, TypeSyntax type, ITypeSymbol typeSymbol, SemanticModel semanticModel) diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitTypeInsteadOfVarInForEachCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitTypeInsteadOfVarInForEachCodeFixProvider.cs index 64ad124858..b350ae9320 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitTypeInsteadOfVarInForEachCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitTypeInsteadOfVarInForEachCodeFixProvider.cs @@ -13,6 +13,7 @@ namespace Roslynator.CSharp.CodeFixes; +[Obsolete("Use code fix provider 'UseExplicitTypeCodeFixProvider' instead.")] [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseExplicitTypeInsteadOfVarInForEachCodeFixProvider))] [Shared] public sealed class UseExplicitTypeInsteadOfVarInForEachCodeFixProvider : BaseCodeFixProvider diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseVarInsteadOfExplicitTypeCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseImplicitTypeCodeFixProvider.cs similarity index 90% rename from src/Analyzers.CodeFixes/CSharp/CodeFixes/UseVarInsteadOfExplicitTypeCodeFixProvider.cs rename to src/Analyzers.CodeFixes/CSharp/CodeFixes/UseImplicitTypeCodeFixProvider.cs index bb555259ac..efcf8aa9f9 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseVarInsteadOfExplicitTypeCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseImplicitTypeCodeFixProvider.cs @@ -13,9 +13,9 @@ namespace Roslynator.CSharp.CodeFixes; -[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseVarInsteadOfExplicitTypeCodeFixProvider))] +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseImplicitTypeCodeFixProvider))] [Shared] -public sealed class UseVarInsteadOfExplicitTypeCodeFixProvider : BaseCodeFixProvider +public sealed class UseImplicitTypeCodeFixProvider : BaseCodeFixProvider { public override ImmutableArray FixableDiagnosticIds { @@ -24,7 +24,8 @@ public override ImmutableArray FixableDiagnosticIds return ImmutableArray.Create( DiagnosticIdentifiers.UseVarInsteadOfExplicitTypeWhenTypeIsObvious, DiagnosticIdentifiers.UseVarInsteadOfExplicitTypeWhenTypeIsNotObvious, - DiagnosticIdentifiers.UseVarInsteadOfExplicitTypeInForEach); + DiagnosticIdentifiers.UseVarInsteadOfExplicitTypeInForEach, + DiagnosticIdentifiers.DeclareExplicitOrImplicitType); } } diff --git a/src/Analyzers.xml b/src/Analyzers.xml index d3b2e49c3e..24471bcdf4 100644 --- a/src/Analyzers.xml +++ b/src/Analyzers.xml @@ -1799,6 +1799,8 @@ else if (condition2) RCS1008 UseExplicitTypeInsteadOfVarWhenTypeIsNotObvious + Obsolete + Use RCS1264 instead Use explicit type instead of 'var' (when the type is not obvious) Use explicit type instead of 'var' Hidden @@ -1814,6 +1816,8 @@ else if (condition2) RCS1009 UseExplicitTypeInsteadOfVarInForEach Use explicit type instead of 'var' (foreach variable) + Obsolete + Use RCS1264 instead Use explicit type instead of 'var' Hidden false @@ -1835,6 +1839,8 @@ foreach (var item in items) RCS1010 UseVarInsteadOfExplicitTypeWhenTypeIsObvious + Obsolete + Use RCS1264 instead Use 'var' instead of explicit type (when the type is obvious) Use 'var' instead of explicit type Hidden @@ -1850,6 +1856,8 @@ foreach (var item in items) RCS1012 UseExplicitTypeInsteadOfVarWhenTypeIsObvious + Obsolete + Use RCS1264 instead Use explicit type instead of 'var' (when the type is obvious) Use explicit type instead of 'var' Hidden @@ -5411,6 +5419,8 @@ else RCS1176 UseVarInsteadOfExplicitTypeWhenTypeIsNotObvious + Obsolete + Use RCS1264 instead Use 'var' instead of explicit type (when the type is not obvious) Use 'var' instead of explicit type Hidden @@ -5425,6 +5435,8 @@ else RCS1177 UseVarInsteadOfExplicitTypeInForEach + Obsolete + Use RCS1264 instead Use 'var' instead of explicit type (in foreach) Use 'var' instead of explicit type Hidden @@ -7633,6 +7645,17 @@ public string Foo(string bar) + + RCS1264 + DeclareExplicitOrImplicitType + Declare explicit/implicit type + Use {0} type + Info + false + + + RCS9001 UsePatternMatching diff --git a/src/Analyzers/CSharp/Analysis/DeclareExplicitOrImplicitTypeAnalyzer.cs b/src/Analyzers/CSharp/Analysis/DeclareExplicitOrImplicitTypeAnalyzer.cs new file mode 100644 index 0000000000..1da593a7fd --- /dev/null +++ b/src/Analyzers/CSharp/Analysis/DeclareExplicitOrImplicitTypeAnalyzer.cs @@ -0,0 +1,191 @@ +// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Roslynator.CSharp.CodeStyle; + +namespace Roslynator.CSharp.Analysis; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class DeclareExplicitOrImplicitTypeAnalyzer : BaseDiagnosticAnalyzer +{ + private static ImmutableArray _supportedDiagnostics; + + public override ImmutableArray SupportedDiagnostics + { + get + { + if (_supportedDiagnostics.IsDefault) + Immutable.InterlockedInitialize(ref _supportedDiagnostics, DiagnosticRules.DeclareExplicitOrImplicitType); + + return _supportedDiagnostics; + } + } + + public override void Initialize(AnalysisContext context) + { + base.Initialize(context); + + context.RegisterSyntaxNodeAction(f => AnalyzeVariableDeclaration(f), SyntaxKind.VariableDeclaration); + context.RegisterSyntaxNodeAction(f => AnalyzeDeclarationExpression(f), SyntaxKind.DeclarationExpression); + context.RegisterSyntaxNodeAction(f => AnalyzeTupleExpression(f), SyntaxKind.TupleExpression); + context.RegisterSyntaxNodeAction(f => AnalyzeForEachStatement(f), SyntaxKind.ForEachStatement); + } + + private static void AnalyzeVariableDeclaration(SyntaxNodeAnalysisContext context) + { + var variableDeclaration = (VariableDeclarationSyntax)context.Node; + + TypeStyle style = context.GetTypeStyle(); + + if (style == TypeStyle.Implicit) + { + if (CSharpTypeAnalysis.IsExplicitThatCanBeImplicit(variableDeclaration, context.SemanticModel, TypeAppearance.NotObvious, context.CancellationToken)) + ReportExplicitToImplicit(context, variableDeclaration.Type); + } + else if (style == TypeStyle.Explicit) + { + if (CSharpTypeAnalysis.IsImplicitThatCanBeExplicit(variableDeclaration, context.SemanticModel, TypeAppearance.Obvious, context.CancellationToken)) + ReportImplicitToExplicit(context, variableDeclaration.Type); + } + else if (style == TypeStyle.ImplicitWhenTypeIsObvious) + { + TypeAnalysis typeAnalysis = CSharpTypeAnalysis.AnalyzeType(variableDeclaration, context.SemanticModel, context.CancellationToken); + + if (typeAnalysis.IsExplicit) + { + if (typeAnalysis.IsTypeObvious + && typeAnalysis.SupportsImplicit) + { + ReportExplicitToImplicit(context, variableDeclaration.Type); + } + } + else if (typeAnalysis.IsImplicit) + { + if (!typeAnalysis.IsTypeObvious + && typeAnalysis.SupportsExplicit) + { + ReportImplicitToExplicit(context, variableDeclaration.Type); + } + } + } + } + + private static void AnalyzeDeclarationExpression(SyntaxNodeAnalysisContext context) + { + var declarationExpression = (DeclarationExpressionSyntax)context.Node; + + TypeStyle style = context.GetTypeStyle(); + + if (style == TypeStyle.Implicit) + { + if (!IsPartOfTupleExpression(declarationExpression) + && CSharpTypeAnalysis.IsExplicitThatCanBeImplicit(declarationExpression, context.SemanticModel, context.CancellationToken)) + { + ReportExplicitToImplicit(context, declarationExpression.Type); + } + } + else if (style == TypeStyle.Explicit) + { + if (CSharpTypeAnalysis.IsImplicitThatCanBeExplicit(declarationExpression, context.SemanticModel, context.CancellationToken)) + ReportImplicitToExplicit(context, declarationExpression.Type); + } + else if (style == TypeStyle.ImplicitWhenTypeIsObvious) + { + if (declarationExpression.Parent is ForEachVariableStatementSyntax forEachStatement) + { + if (CSharpTypeAnalysis.IsImplicitThatCanBeExplicit(forEachStatement, context.SemanticModel)) + ReportExplicitToImplicit(context, declarationExpression.Type); + } + else + { + if (!IsObviousTupleExpression(declarationExpression) + && CSharpTypeAnalysis.IsImplicitThatCanBeExplicit(declarationExpression, context.SemanticModel, TypeAppearance.Obvious, context.CancellationToken)) + { + ReportImplicitToExplicit(context, declarationExpression.Type); + } + + static bool IsObviousTupleExpression(DeclarationExpressionSyntax declarationExpression) + { + return IsPartOfTupleExpression(declarationExpression) + && declarationExpression.Parent.Parent.Parent is AssignmentExpressionSyntax assignmentExpression + && assignmentExpression.Right.IsKind(SyntaxKind.DefaultExpression); + } + } + } + } + + private static void AnalyzeTupleExpression(SyntaxNodeAnalysisContext context) + { + var tupleExpression = (TupleExpressionSyntax)context.Node; + + TypeStyle style = context.GetTypeStyle(); + + if (style == TypeStyle.Implicit) + { + if (CSharpTypeAnalysis.IsExplicitThatCanBeImplicit(tupleExpression, context.SemanticModel, context.CancellationToken)) + ReportExplicitToImplicit(context, tupleExpression); + } + else if (style == TypeStyle.ImplicitWhenTypeIsObvious) + { + if (tupleExpression.Parent is AssignmentExpressionSyntax assignmentExpression + && assignmentExpression.Right.IsKind(SyntaxKind.DefaultExpression) + && CSharpTypeAnalysis.IsExplicitThatCanBeImplicit(tupleExpression, context.SemanticModel, context.CancellationToken)) + { + ReportExplicitToImplicit(context, tupleExpression); + } + } + } + + private static void AnalyzeForEachStatement(SyntaxNodeAnalysisContext context) + { + var forEachStatement = (ForEachStatementSyntax)context.Node; + + TypeStyle style = context.GetTypeStyle(); + + if (style == TypeStyle.Implicit) + { + TypeAnalysis analysis = CSharpTypeAnalysis.AnalyzeType(forEachStatement, context.SemanticModel); + + if (analysis.IsExplicit + && analysis.SupportsImplicit) + { + ReportExplicitToImplicit(context, forEachStatement.Type); + } + } + else if (style == TypeStyle.Explicit + || style == TypeStyle.ImplicitWhenTypeIsObvious) + { + if (CSharpTypeAnalysis.IsImplicitThatCanBeExplicit(forEachStatement, context.SemanticModel)) + ReportImplicitToExplicit(context, forEachStatement.Type); + } + } + + private static void ReportExplicitToImplicit(SyntaxNodeAnalysisContext context, SyntaxNode node) + { + DiagnosticHelpers.ReportDiagnostic( + context, + DiagnosticRules.DeclareExplicitOrImplicitType, + node.GetLocation(), + "implicit"); + } + + private static void ReportImplicitToExplicit(SyntaxNodeAnalysisContext context, SyntaxNode node) + { + DiagnosticHelpers.ReportDiagnostic( + context, + DiagnosticRules.DeclareExplicitOrImplicitType, + node.GetLocation(), + "explicit"); + } + + private static bool IsPartOfTupleExpression(DeclarationExpressionSyntax declarationExpression) + { + return declarationExpression.IsParentKind(SyntaxKind.Argument) + && declarationExpression.Parent.IsParentKind(SyntaxKind.TupleExpression); + } + +} diff --git a/src/Analyzers/CSharp/Analysis/UseExplicitTypeInsteadOfVarInForEachAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UseExplicitTypeInsteadOfVarInForEachAnalyzer.cs index 46ec5e3e86..d07aece626 100644 --- a/src/Analyzers/CSharp/Analysis/UseExplicitTypeInsteadOfVarInForEachAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/UseExplicitTypeInsteadOfVarInForEachAnalyzer.cs @@ -1,5 +1,6 @@ // Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -8,6 +9,7 @@ namespace Roslynator.CSharp.Analysis; +[Obsolete("Use analyzer 'DeclareExplicitOrImplicitTypeAnalyzer' instead.")] [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseExplicitTypeInsteadOfVarInForEachAnalyzer : BaseDiagnosticAnalyzer { diff --git a/src/Analyzers/CSharp/Analysis/UseExplicitTypeInsteadOfVarWhenTypeIsNotObviousAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UseExplicitTypeInsteadOfVarWhenTypeIsNotObviousAnalyzer.cs index d11aacac74..69124361c4 100644 --- a/src/Analyzers/CSharp/Analysis/UseExplicitTypeInsteadOfVarWhenTypeIsNotObviousAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/UseExplicitTypeInsteadOfVarWhenTypeIsNotObviousAnalyzer.cs @@ -1,5 +1,6 @@ // Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -8,6 +9,7 @@ namespace Roslynator.CSharp.Analysis; +[Obsolete("Use analyzer 'DeclareExplicitOrImplicitTypeAnalyzer' instead.")] [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseExplicitTypeInsteadOfVarWhenTypeIsNotObviousAnalyzer : BaseDiagnosticAnalyzer { diff --git a/src/Analyzers/CSharp/Analysis/UseExplicitTypeInsteadOfVarWhenTypeIsObviousAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UseExplicitTypeInsteadOfVarWhenTypeIsObviousAnalyzer.cs index 02dff3f668..92c7b92160 100644 --- a/src/Analyzers/CSharp/Analysis/UseExplicitTypeInsteadOfVarWhenTypeIsObviousAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/UseExplicitTypeInsteadOfVarWhenTypeIsObviousAnalyzer.cs @@ -1,5 +1,6 @@ // Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -8,6 +9,7 @@ namespace Roslynator.CSharp.Analysis; +[Obsolete("Use analyzer 'DeclareExplicitOrImplicitTypeAnalyzer' instead.")] [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseExplicitTypeInsteadOfVarWhenTypeIsObviousAnalyzer : BaseDiagnosticAnalyzer { diff --git a/src/Analyzers/CSharp/Analysis/UseVarInsteadOfExplicitTypeInForEachAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UseVarInsteadOfExplicitTypeInForEachAnalyzer.cs index b809f2d84c..6b3ad1448f 100644 --- a/src/Analyzers/CSharp/Analysis/UseVarInsteadOfExplicitTypeInForEachAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/UseVarInsteadOfExplicitTypeInForEachAnalyzer.cs @@ -1,5 +1,6 @@ // Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -8,6 +9,7 @@ namespace Roslynator.CSharp.Analysis; +[Obsolete("Use analyzer 'DeclareExplicitOrImplicitTypeAnalyzer' instead.")] [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseVarInsteadOfExplicitTypeInForEachAnalyzer : BaseDiagnosticAnalyzer { diff --git a/src/Analyzers/CSharp/Analysis/UseVarInsteadOfExplicitTypeWhenTypeIsNotObviousAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UseVarInsteadOfExplicitTypeWhenTypeIsNotObviousAnalyzer.cs index 05af244467..66e45f45e2 100644 --- a/src/Analyzers/CSharp/Analysis/UseVarInsteadOfExplicitTypeWhenTypeIsNotObviousAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/UseVarInsteadOfExplicitTypeWhenTypeIsNotObviousAnalyzer.cs @@ -1,5 +1,6 @@ // Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -8,6 +9,7 @@ namespace Roslynator.CSharp.Analysis; +[Obsolete("Use analyzer 'DeclareExplicitOrImplicitTypeAnalyzer' instead.")] [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseVarInsteadOfExplicitTypeWhenTypeIsNotObviousAnalyzer : BaseDiagnosticAnalyzer { diff --git a/src/Analyzers/CSharp/Analysis/UseVarInsteadOfExplicitTypeWhenTypeIsObviousAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UseVarInsteadOfExplicitTypeWhenTypeIsObviousAnalyzer.cs index 3808be7387..6114b0ed25 100644 --- a/src/Analyzers/CSharp/Analysis/UseVarInsteadOfExplicitTypeWhenTypeIsObviousAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/UseVarInsteadOfExplicitTypeWhenTypeIsObviousAnalyzer.cs @@ -1,5 +1,6 @@ // Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -8,6 +9,7 @@ namespace Roslynator.CSharp.Analysis; +[Obsolete("Use analyzer 'DeclareExplicitOrImplicitTypeAnalyzer' instead.")] [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseVarInsteadOfExplicitTypeWhenTypeIsObviousAnalyzer : BaseDiagnosticAnalyzer { diff --git a/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs b/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs index e5ee5b459d..29febdd043 100644 --- a/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs +++ b/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs @@ -220,5 +220,6 @@ public static partial class DiagnosticIdentifiers public const string DisposeResourceAsynchronously = "RCS1261"; public const string UnnecessaryRawStringLiteral = "RCS1262"; public const string InvalidReferenceInDocumentationComment = "RCS1263"; + public const string DeclareExplicitOrImplicitType = "RCS1264"; } } \ No newline at end of file diff --git a/src/Analyzers/CSharp/DiagnosticRules.Generated.cs b/src/Analyzers/CSharp/DiagnosticRules.Generated.cs index 87ec5b2cb7..6ce0724709 100644 --- a/src/Analyzers/CSharp/DiagnosticRules.Generated.cs +++ b/src/Analyzers/CSharp/DiagnosticRules.Generated.cs @@ -105,7 +105,7 @@ public static partial class DiagnosticRules public static readonly DiagnosticDescriptor UseExplicitTypeInsteadOfVarWhenTypeIsNotObvious = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.UseExplicitTypeInsteadOfVarWhenTypeIsNotObvious, title: "Use explicit type instead of 'var' (when the type is not obvious)", - messageFormat: "Use explicit type instead of 'var'", + messageFormat: "([deprecated] Use RCS1264 instead) Use explicit type instead of 'var'", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, isEnabledByDefault: false, @@ -117,7 +117,7 @@ public static partial class DiagnosticRules public static readonly DiagnosticDescriptor UseExplicitTypeInsteadOfVarInForEach = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.UseExplicitTypeInsteadOfVarInForEach, title: "Use explicit type instead of 'var' (foreach variable)", - messageFormat: "Use explicit type instead of 'var'", + messageFormat: "([deprecated] Use RCS1264 instead) Use explicit type instead of 'var'", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, isEnabledByDefault: false, @@ -129,7 +129,7 @@ public static partial class DiagnosticRules public static readonly DiagnosticDescriptor UseVarInsteadOfExplicitTypeWhenTypeIsObvious = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.UseVarInsteadOfExplicitTypeWhenTypeIsObvious, title: "Use 'var' instead of explicit type (when the type is obvious)", - messageFormat: "Use 'var' instead of explicit type", + messageFormat: "([deprecated] Use RCS1264 instead) Use 'var' instead of explicit type", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, isEnabledByDefault: false, @@ -141,7 +141,7 @@ public static partial class DiagnosticRules public static readonly DiagnosticDescriptor UseExplicitTypeInsteadOfVarWhenTypeIsObvious = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.UseExplicitTypeInsteadOfVarWhenTypeIsObvious, title: "Use explicit type instead of 'var' (when the type is obvious)", - messageFormat: "Use explicit type instead of 'var'", + messageFormat: "([deprecated] Use RCS1264 instead) Use explicit type instead of 'var'", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, isEnabledByDefault: false, @@ -1617,7 +1617,7 @@ public static partial class DiagnosticRules public static readonly DiagnosticDescriptor UseVarInsteadOfExplicitTypeWhenTypeIsNotObvious = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.UseVarInsteadOfExplicitTypeWhenTypeIsNotObvious, title: "Use 'var' instead of explicit type (when the type is not obvious)", - messageFormat: "Use 'var' instead of explicit type", + messageFormat: "([deprecated] Use RCS1264 instead) Use 'var' instead of explicit type", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, isEnabledByDefault: false, @@ -1629,7 +1629,7 @@ public static partial class DiagnosticRules public static readonly DiagnosticDescriptor UseVarInsteadOfExplicitTypeInForEach = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.UseVarInsteadOfExplicitTypeInForEach, title: "Use 'var' instead of explicit type (in foreach)", - messageFormat: "Use 'var' instead of explicit type", + messageFormat: "([deprecated] Use RCS1264 instead) Use 'var' instead of explicit type", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, isEnabledByDefault: false, @@ -2603,5 +2603,17 @@ public static partial class DiagnosticRules helpLinkUri: DiagnosticIdentifiers.InvalidReferenceInDocumentationComment, customTags: Array.Empty()); + /// RCS1264 + public static readonly DiagnosticDescriptor DeclareExplicitOrImplicitType = DiagnosticDescriptorFactory.Create( + id: DiagnosticIdentifiers.DeclareExplicitOrImplicitType, + title: "Declare explicit/implicit type", + messageFormat: "Use {0} type", + category: DiagnosticCategories.Roslynator, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: false, + description: null, + helpLinkUri: DiagnosticIdentifiers.DeclareExplicitOrImplicitType, + customTags: Array.Empty()); + } } \ No newline at end of file diff --git a/src/CSharp/CSharp/CSharpTypeAnalysis.cs b/src/CSharp/CSharp/CSharpTypeAnalysis.cs index 2ea480bfb0..2538c613c1 100644 --- a/src/CSharp/CSharp/CSharpTypeAnalysis.cs +++ b/src/CSharp/CSharp/CSharpTypeAnalysis.cs @@ -76,31 +76,8 @@ public static TypeAnalysis AnalyzeType( } } - switch (expression.Kind()) - { - case SyntaxKind.ObjectCreationExpression: - case SyntaxKind.ArrayCreationExpression: - case SyntaxKind.CastExpression: - case SyntaxKind.AsExpression: - case SyntaxKind.ThisExpression: - case SyntaxKind.DefaultExpression: - { - flags |= TypeAnalysisFlags.TypeObvious; - break; - } - case SyntaxKind.SimpleMemberAccessExpression: - { - ISymbol? symbol = semanticModel.GetSymbol(expression, cancellationToken); - - if (symbol?.Kind == SymbolKind.Field - && symbol.ContainingType?.TypeKind == TypeKind.Enum) - { - flags |= TypeAnalysisFlags.TypeObvious; - } - - break; - } - } + if (IsTypeObvious(expression, typeSymbol, includeNullability: false, semanticModel, cancellationToken)) + flags |= TypeAnalysisFlags.TypeObvious; return new TypeAnalysis(typeSymbol, flags); } @@ -433,6 +410,15 @@ public static bool IsImplicitThatCanBeExplicit( DeclarationExpressionSyntax declarationExpression, SemanticModel semanticModel, CancellationToken cancellationToken = default) + { + return IsImplicitThatCanBeExplicit(declarationExpression, semanticModel, TypeAppearance.None, cancellationToken); + } + + public static bool IsImplicitThatCanBeExplicit( + DeclarationExpressionSyntax declarationExpression, + SemanticModel semanticModel, + TypeAppearance typeAppearance, + CancellationToken cancellationToken = default) { TypeSyntax type = declarationExpression.Type; @@ -465,6 +451,25 @@ public static bool IsImplicitThatCanBeExplicit( return false; } + if (declarationExpression.Parent is AssignmentExpressionSyntax assignmentExpression + && declarationExpression == assignmentExpression.Left) + { + ExpressionSyntax expression = assignmentExpression.Right; + + if (expression is not null) + { + switch (typeAppearance) + { + case TypeAppearance.Obvious: + return !IsTypeObvious(expression, semanticModel, cancellationToken); + case TypeAppearance.NotObvious: + return IsTypeObvious(expression, semanticModel, cancellationToken); + } + + Debug.Assert(typeAppearance == TypeAppearance.None, typeAppearance.ToString()); + } + } + return true; } default: diff --git a/src/Common/CSharp/Extensions/CodeStyleExtensions.cs b/src/Common/CSharp/Extensions/CodeStyleExtensions.cs index 97f07577d9..258a506714 100644 --- a/src/Common/CSharp/Extensions/CodeStyleExtensions.cs +++ b/src/Common/CSharp/Extensions/CodeStyleExtensions.cs @@ -445,6 +445,29 @@ public static TypeStyle GetObjectCreationTypeStyle(this SyntaxNodeAnalysisContex return TypeStyle.None; } + public static TypeStyle GetTypeStyle(this SyntaxNodeAnalysisContext context) + { + AnalyzerConfigOptions configOptions = context.GetConfigOptions(); + + if (ConfigOptions.TryGetValue(configOptions, ConfigOptions.TypeStyle, out string rawValue)) + { + if (string.Equals(rawValue, ConfigOptionValues.TypeStyle_Implicit, StringComparison.OrdinalIgnoreCase)) + { + return TypeStyle.Implicit; + } + else if (string.Equals(rawValue, ConfigOptionValues.TypeStyle_Explicit, StringComparison.OrdinalIgnoreCase)) + { + return TypeStyle.Explicit; + } + else if (string.Equals(rawValue, ConfigOptionValues.TypeStyle_ImplicitWhenTypeIsObvious, StringComparison.OrdinalIgnoreCase)) + { + return TypeStyle.ImplicitWhenTypeIsObvious; + } + } + + return TypeStyle.None; + } + public static bool? UseCollectionExpression(this SyntaxNodeAnalysisContext context) { return UseCollectionExpression(context.GetConfigOptions()); diff --git a/src/Common/ConfigOptionKeys.Generated.cs b/src/Common/ConfigOptionKeys.Generated.cs index 8c7505c31f..ac2912eb94 100644 --- a/src/Common/ConfigOptionKeys.Generated.cs +++ b/src/Common/ConfigOptionKeys.Generated.cs @@ -38,6 +38,7 @@ internal static partial class ConfigOptionKeys public const string SuppressUnityScriptMethods = "roslynator_suppress_unity_script_methods"; public const string TabLength = "roslynator_tab_length"; public const string TrailingCommaStyle = "roslynator_trailing_comma_style"; + public const string TypeStyle = "roslynator_type_style"; public const string UnityCodeAnalysisEnabled = "roslynator_unity_code_analysis.enabled"; public const string UseAnonymousFunctionOrMethodGroup = "roslynator_use_anonymous_function_or_method_group"; public const string UseBlockBodyWhenDeclarationSpansOverMultipleLines = "roslynator_use_block_body_when_declaration_spans_over_multiple_lines"; diff --git a/src/Common/ConfigOptionValues.Generated.cs b/src/Common/ConfigOptionValues.Generated.cs index f4adf13119..f2651b515f 100644 --- a/src/Common/ConfigOptionValues.Generated.cs +++ b/src/Common/ConfigOptionValues.Generated.cs @@ -55,6 +55,9 @@ internal static partial class ConfigOptionValues public const string TrailingCommaStyle_Include = "include"; public const string TrailingCommaStyle_Omit = "omit"; public const string TrailingCommaStyle_OmitWhenSingleLine = "omit_when_single_line"; + public const string TypeStyle_Explicit = "explicit"; + public const string TypeStyle_Implicit = "implicit"; + public const string TypeStyle_ImplicitWhenTypeIsObvious = "implicit_when_type_is_obvious"; public const string UseAnonymousFunctionOrMethodGroup_AnonymousFunction = "anonymous_function"; public const string UseAnonymousFunctionOrMethodGroup_MethodGroup = "method_group"; } diff --git a/src/Common/ConfigOptions.Generated.cs b/src/Common/ConfigOptions.Generated.cs index d8dc899a49..b87c1a8bdc 100644 --- a/src/Common/ConfigOptions.Generated.cs +++ b/src/Common/ConfigOptions.Generated.cs @@ -202,6 +202,12 @@ public static partial class ConfigOptions defaultValuePlaceholder: "include|omit|omit_when_single_line", description: "Include/omit trailing comma in initializer or enum"); + public static readonly ConfigOptionDescriptor TypeStyle = new( + key: ConfigOptionKeys.TypeStyle, + defaultValue: null, + defaultValuePlaceholder: "explicit|implicit|implicit_when_type_is_obvious", + description: "Prefer explicit/implicit type when declaring a variable"); + public static readonly ConfigOptionDescriptor UnityCodeAnalysisEnabled = new( key: ConfigOptionKeys.UnityCodeAnalysisEnabled, defaultValue: null, @@ -268,6 +274,7 @@ private static IEnumerable> GetRequiredOptions() yield return new KeyValuePair("RCS1253", JoinOptionKeys(ConfigOptionKeys.DocCommentSummaryStyle)); yield return new KeyValuePair("RCS1254", JoinOptionKeys(ConfigOptionKeys.EnumFlagValueStyle)); yield return new KeyValuePair("RCS1260", JoinOptionKeys(ConfigOptionKeys.TrailingCommaStyle)); + yield return new KeyValuePair("RCS1264", JoinOptionKeys(ConfigOptionKeys.TypeStyle)); } } } \ No newline at end of file diff --git a/src/ConfigOptions.xml b/src/ConfigOptions.xml index 44aa41017e..35508e4378 100644 --- a/src/ConfigOptions.xml +++ b/src/ConfigOptions.xml @@ -229,6 +229,14 @@ true|false Use collection expression for array/collection creation +