From 99d4c4a52161d14d4cf315649fe9e9589e4612bb Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sun, 31 Dec 2023 23:04:09 +0100 Subject: [PATCH] Add analyzer 'Declare explicit/implicit type' (#1335) --- ChangeLog.md | 9 + ...ExplicitTypeInsteadOfVarCodeFixProvider.cs | 2 + ...ypeInsteadOfVarInForEachCodeFixProvider.cs | 1 + ...VarInsteadOfExplicitTypeCodeFixProvider.cs | 1 + .../UseVarOrExplicitTypeCodeFixProvider.cs | 157 ++++++++ src/Analyzers.xml | 23 ++ ...plicitTypeInsteadOfVarInForEachAnalyzer.cs | 2 + ...nsteadOfVarWhenTypeIsNotObviousAnalyzer.cs | 2 + ...peInsteadOfVarWhenTypeIsObviousAnalyzer.cs | 2 + ...rInsteadOfExplicitTypeInForEachAnalyzer.cs | 2 + ...xplicitTypeWhenTypeIsNotObviousAnalyzer.cs | 2 + ...OfExplicitTypeWhenTypeIsObviousAnalyzer.cs | 2 + .../Analysis/UseVarOrExplicitTypeAnalyzer.cs | 197 ++++++++++ .../CSharp/DiagnosticIdentifiers.Generated.cs | 1 + .../CSharp/DiagnosticRules.Generated.cs | 64 ++-- src/CSharp/CSharp/CSharpTypeAnalysis.cs | 66 ++-- .../CSharp/Extensions/CodeStyleExtensions.cs | 23 ++ src/Common/ConfigOptionKeys.Generated.cs | 1 + src/Common/ConfigOptionValues.Generated.cs | 3 + src/Common/ConfigOptions.Generated.cs | 7 + .../EditorConfigCodeAnalysisConfigLoader.cs | 2 +- src/ConfigOptions.xml | 8 + .../CSharp/DiagnosticRules.Generated.cs | 10 +- ...eadOfExplicitTypeWhenTypeIsObviousTests.cs | 245 ------------ ...cs => RCS1264UseVarOrExplicitTypeTests.cs} | 78 ++-- ...s => RCS1264UseVarOrExplicitTypeTests2.cs} | 64 +++- .../RCS1264UseVarOrExplicitTypeTests3.cs | 358 ++++++++++++++++++ ...s => RCS1264UseVarOrExplicitTypeTests4.cs} | 76 ++-- ...s => RCS1264UseVarOrExplicitTypeTests5.cs} | 20 +- ...s => RCS1264UseVarOrExplicitTypeTests6.cs} | 54 ++- .../CSharp/DiagnosticRulesGenerator.cs | 7 +- .../src/configurationFiles.generated.ts | 25 +- .../CSharp/CodeActionFactory.cs | 12 +- .../CSharp/DocumentRefactoringFactory.cs | 2 +- .../CSharp/DocumentRefactorings.cs | 138 +++++-- 35 files changed, 1186 insertions(+), 480 deletions(-) create mode 100644 src/Analyzers.CodeFixes/CSharp/CodeFixes/UseVarOrExplicitTypeCodeFixProvider.cs create mode 100644 src/Analyzers/CSharp/Analysis/UseVarOrExplicitTypeAnalyzer.cs delete mode 100644 src/Tests/Analyzers.Tests/RCS1010UseVarInsteadOfExplicitTypeWhenTypeIsObviousTests.cs rename src/Tests/Analyzers.Tests/{RCS1176UseVarInsteadOfExplicitTypeWhenTypeIsNotObviousTests.cs => RCS1264UseVarOrExplicitTypeTests.cs} (65%) rename src/Tests/Analyzers.Tests/{RCS1012UseExplicitTypeInsteadOfVarWhenTypeIsObviousTests.cs => RCS1264UseVarOrExplicitTypeTests2.cs} (55%) create mode 100644 src/Tests/Analyzers.Tests/RCS1264UseVarOrExplicitTypeTests3.cs rename src/Tests/Analyzers.Tests/{RCS1008UseExplicitTypeInsteadOfVarWhenTypeIsNotObviousTests.cs => RCS1264UseVarOrExplicitTypeTests4.cs} (62%) rename src/Tests/Analyzers.Tests/{RCS1009UseExplicitTypeInsteadOfVarInForEachTests.cs => RCS1264UseVarOrExplicitTypeTests5.cs} (82%) rename src/Tests/Analyzers.Tests/{RCS1177UseVarInsteadOfExplicitTypeInForEachTests.cs => RCS1264UseVarOrExplicitTypeTests6.cs} (56%) diff --git a/ChangeLog.md b/ChangeLog.md index 0b7749795c..20886008e0 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -12,6 +12,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add analyzer "Add/remove blank line between switch sections" ([RCS0061](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0061)) ([PR](https://github.com/dotnet/roslynator/pull/1302)) - Option (required): `roslynator_blank_line_between_switch_sections = include|omit|omit_after_block` - Make analyzer [RCS0014](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0014) obsolete +- Add analyzer "Declare explicit/implicit type" ([RCS1264](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1264)) ([PR](https://github.com/dotnet/roslynator/pull/1335)) + - Required option: `roslynator_use_var = always | never | when_type_is_obvious` + - This analyzer consolidates following analyzers (which are made obsolete): + - [RCS1008](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1008) + - [RCS1009](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1009) + - [RCS1010](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1010) + - [RCS1012](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1012) + - [RCS1176](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1176) + - [RCS1177](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1177) - Add code fix "Declare as nullable" ([PR](https://github.com/dotnet/roslynator/pull/1333)) - Applicable to: `CS8600`, `CS8610`, `CS8765` and `CS8767` - Add option `roslynator_use_collection_expression = true|false` ([PR](https://github.com/dotnet/roslynator/pull/1325)) diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitTypeInsteadOfVarCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitTypeInsteadOfVarCodeFixProvider.cs index 924853a865..b0442f12f6 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitTypeInsteadOfVarCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitTypeInsteadOfVarCodeFixProvider.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 System.Composition; using System.Diagnostics; @@ -14,6 +15,7 @@ namespace Roslynator.CSharp.CodeFixes; +[Obsolete("Use code fix provider 'UseVarOrExplicitTypeCodeFixProvider' instead.")] [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseExplicitTypeInsteadOfVarCodeFixProvider))] [Shared] public sealed class UseExplicitTypeInsteadOfVarCodeFixProvider : BaseCodeFixProvider diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitTypeInsteadOfVarInForEachCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitTypeInsteadOfVarInForEachCodeFixProvider.cs index 64ad124858..d5a4f06dd6 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 'UseVarOrExplicitTypeCodeFixProvider' 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/UseVarInsteadOfExplicitTypeCodeFixProvider.cs index bb555259ac..4ab4984c1a 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseVarInsteadOfExplicitTypeCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseVarInsteadOfExplicitTypeCodeFixProvider.cs @@ -13,6 +13,7 @@ namespace Roslynator.CSharp.CodeFixes; +[Obsolete("Use code fix provider 'UseVarOrExplicitTypeCodeFixProvider' instead.")] [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseVarInsteadOfExplicitTypeCodeFixProvider))] [Shared] public sealed class UseVarInsteadOfExplicitTypeCodeFixProvider : BaseCodeFixProvider diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseVarOrExplicitTypeCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseVarOrExplicitTypeCodeFixProvider.cs new file mode 100644 index 0000000000..f2cf68dff3 --- /dev/null +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseVarOrExplicitTypeCodeFixProvider.cs @@ -0,0 +1,157 @@ +// 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 System.Composition; +using System.Diagnostics; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslynator.CodeFixes; +using Roslynator.CSharp; +using static Roslynator.CSharp.CodeActionFactory; + +namespace Roslynator.CSharp.CodeFixes; + +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseVarOrExplicitTypeCodeFixProvider))] +[Shared] +public sealed class UseVarOrExplicitTypeCodeFixProvider : BaseCodeFixProvider +{ + public override ImmutableArray FixableDiagnosticIds + { + get { return ImmutableArray.Create(DiagnosticIdentifiers.UseVarOrExplicitType); } + } + + 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 is TypeSyntax + || f.IsKind( + SyntaxKind.VariableDeclaration, + SyntaxKind.DeclarationExpression, + SyntaxKind.ForEachStatement, + SyntaxKind.ForEachVariableStatement, + SyntaxKind.TupleExpression))) + { + return; + } + + Document document = context.Document; + Diagnostic diagnostic = context.Diagnostics[0]; + + SemanticModel semanticModel = await context.GetSemanticModelAsync().ConfigureAwait(false); + + if (node is TypeSyntax type) + { + if (type.IsVar) + { + if (node.IsParentKind(SyntaxKind.VariableDeclaration, SyntaxKind.ForEachStatement)) + { + node = node.Parent; + } + else if (node.IsParentKind(SyntaxKind.DeclarationExpression)) + { + node = node.Parent; + + if (node.IsParentKind(SyntaxKind.ForEachVariableStatement)) + node = node.Parent; + } + } + else + { + CodeAction codeAction = ChangeTypeToVar(document, type, equivalenceKey: GetEquivalenceKey(diagnostic, "ToImplicit")); + context.RegisterCodeFix(codeAction, diagnostic); + return; + } + } + + switch (node) + { + case TupleExpressionSyntax tupleExpression: + { + CodeAction codeAction = ChangeTypeToVar(document, tupleExpression, equivalenceKey: GetEquivalenceKey(diagnostic, "ToImplicit")); + context.RegisterCodeFix(codeAction, diagnostic); + break; + } + case VariableDeclarationSyntax variableDeclaration: + { + ExpressionSyntax value = variableDeclaration.Variables[0].Initializer.Value; + ITypeSymbol typeSymbol = semanticModel.GetTypeSymbol(value, context.CancellationToken); + + if (typeSymbol is null) + { + var localSymbol = semanticModel.GetDeclaredSymbol(variableDeclaration.Variables[0], context.CancellationToken) as ILocalSymbol; + + if (localSymbol is not null) + { + typeSymbol = localSymbol.Type; + value = value.WalkDownParentheses(); + + Debug.Assert( + value.IsKind(SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression), + value.Kind().ToString()); + + if (value.IsKind(SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression)) + typeSymbol = typeSymbol.WithNullableAnnotation(NullableAnnotation.NotAnnotated); + } + else + { + SyntaxDebug.Fail(variableDeclaration.Variables[0]); + return; + } + } + + CodeAction codeAction = UseExplicitType(document, variableDeclaration.Type, typeSymbol, semanticModel, equivalenceKey: GetEquivalenceKey(diagnostic, "ToExplicit")); + context.RegisterCodeFix(codeAction, diagnostic); + break; + } + case DeclarationExpressionSyntax declarationExpression: + { + ITypeSymbol typeSymbol = null; + if (declarationExpression.Parent is AssignmentExpressionSyntax assignment) + { + typeSymbol = semanticModel.GetTypeSymbol(assignment.Right, context.CancellationToken); + } + else if (declarationExpression.Parent is ArgumentSyntax argument) + { + IParameterSymbol parameterSymbol = DetermineParameterHelper.DetermineParameter(argument, semanticModel, cancellationToken: context.CancellationToken); + typeSymbol = parameterSymbol?.Type; + } + + if (typeSymbol is null) + { + var localSymbol = semanticModel.GetDeclaredSymbol(declarationExpression.Designation, context.CancellationToken) as ILocalSymbol; + typeSymbol = (localSymbol?.Type) ?? semanticModel.GetTypeSymbol(declarationExpression, context.CancellationToken); + } + + CodeAction codeAction = UseExplicitType(document, declarationExpression.Type, typeSymbol, semanticModel, equivalenceKey: GetEquivalenceKey(diagnostic, "ToExplicit")); + context.RegisterCodeFix(codeAction, diagnostic); + break; + } + case ForEachStatementSyntax forEachStatement: + { + ITypeSymbol typeSymbol = semanticModel.GetForEachStatementInfo((CommonForEachStatementSyntax)node).ElementType; + + CodeAction codeAction = UseExplicitType(document, forEachStatement.Type, typeSymbol, semanticModel, equivalenceKey: GetEquivalenceKey(diagnostic, "ToExplicit")); + context.RegisterCodeFix(codeAction, diagnostic); + break; + } + case ForEachVariableStatementSyntax forEachVariableStatement: + { + var declarationExpression = (DeclarationExpressionSyntax)forEachVariableStatement.Variable; + ITypeSymbol typeSymbol = semanticModel.GetForEachStatementInfo((CommonForEachStatementSyntax)node).ElementType; + + CodeAction codeAction = UseExplicitType(document, declarationExpression.Type, typeSymbol, semanticModel, equivalenceKey: GetEquivalenceKey(diagnostic, "ToExplicit")); + context.RegisterCodeFix(codeAction, diagnostic); + break; + } + } + } +} diff --git a/src/Analyzers.xml b/src/Analyzers.xml index d3b2e49c3e..29ef7b5506 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 + UseVarOrExplicitType + Use 'var' or explicit type + {0} + Info + false + + + RCS9001 UsePatternMatching diff --git a/src/Analyzers/CSharp/Analysis/UseExplicitTypeInsteadOfVarInForEachAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UseExplicitTypeInsteadOfVarInForEachAnalyzer.cs index 46ec5e3e86..03d96629cf 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 'UseVarOrExplicitTypeAnalyzer' 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..71d350a6d5 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 'UseVarOrExplicitTypeAnalyzer' 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..ebe589a30d 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 'UseVarOrExplicitTypeAnalyzer' 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..d035815e65 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 'UseVarOrExplicitTypeAnalyzer' 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..5a4124260f 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 'UseVarOrExplicitTypeAnalyzer' 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..4a85ed18a1 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 'UseVarOrExplicitTypeAnalyzer' instead.")] [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseVarInsteadOfExplicitTypeWhenTypeIsObviousAnalyzer : BaseDiagnosticAnalyzer { diff --git a/src/Analyzers/CSharp/Analysis/UseVarOrExplicitTypeAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UseVarOrExplicitTypeAnalyzer.cs new file mode 100644 index 0000000000..39dac4c8a9 --- /dev/null +++ b/src/Analyzers/CSharp/Analysis/UseVarOrExplicitTypeAnalyzer.cs @@ -0,0 +1,197 @@ +// 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 UseVarOrExplicitTypeAnalyzer : BaseDiagnosticAnalyzer +{ + private static ImmutableArray _supportedDiagnostics; + + public override ImmutableArray SupportedDiagnostics + { + get + { + if (_supportedDiagnostics.IsDefault) + Immutable.InterlockedInitialize(ref _supportedDiagnostics, DiagnosticRules.UseVarOrExplicitType); + + 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 (!IsObvious(declarationExpression) + && !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); + } + + static bool IsObvious(DeclarationExpressionSyntax declarationExpression) + { + return declarationExpression.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.UseVarOrExplicitType, + node.GetLocation(), + "Use 'var' instead of explicit type"); + } + + private static void ReportImplicitToExplicit(SyntaxNodeAnalysisContext context, SyntaxNode node) + { + DiagnosticHelpers.ReportDiagnostic( + context, + DiagnosticRules.UseVarOrExplicitType, + node.GetLocation(), + "Use explicit type instead of 'var'"); + } + + private static bool IsPartOfTupleExpression(DeclarationExpressionSyntax declarationExpression) + { + return declarationExpression.IsParentKind(SyntaxKind.Argument) + && declarationExpression.Parent.IsParentKind(SyntaxKind.TupleExpression); + } +} diff --git a/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs b/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs index e5ee5b459d..39500cbec2 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 UseVarOrExplicitType = "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..c7d8521bf3 100644 --- a/src/Analyzers/CSharp/DiagnosticRules.Generated.cs +++ b/src/Analyzers/CSharp/DiagnosticRules.Generated.cs @@ -104,8 +104,8 @@ public static partial class DiagnosticRules /// RCS1008 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'", + title: "[deprecated] Use explicit type instead of 'var' (when the type is not obvious)", + messageFormat: "([deprecated] Use RCS1264 instead) Use explicit type instead of 'var'", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, isEnabledByDefault: false, @@ -116,8 +116,8 @@ public static partial class DiagnosticRules /// RCS1009 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'", + title: "[deprecated] Use explicit type instead of 'var' (foreach variable)", + messageFormat: "([deprecated] Use RCS1264 instead) Use explicit type instead of 'var'", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, isEnabledByDefault: false, @@ -128,8 +128,8 @@ public static partial class DiagnosticRules /// RCS1010 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", + title: "[deprecated] Use 'var' instead of explicit type (when the type is obvious)", + messageFormat: "([deprecated] Use RCS1264 instead) Use 'var' instead of explicit type", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, isEnabledByDefault: false, @@ -140,8 +140,8 @@ public static partial class DiagnosticRules /// RCS1012 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'", + title: "[deprecated] Use explicit type instead of 'var' (when the type is obvious)", + messageFormat: "([deprecated] Use RCS1264 instead) Use explicit type instead of 'var'", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, isEnabledByDefault: false, @@ -304,7 +304,7 @@ public static partial class DiagnosticRules /// RCS1035 public static readonly DiagnosticDescriptor RemoveRedundantCommaInInitializer = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.RemoveRedundantCommaInInitializer, - title: "Remove redundant comma in initializer", + title: "[deprecated] Remove redundant comma in initializer", messageFormat: "([deprecated] Use RCS1260 instead) Remove redundant comma in initializer", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, @@ -340,7 +340,7 @@ public static partial class DiagnosticRules /// RCS1038 public static readonly DiagnosticDescriptor RemoveEmptyStatement = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.RemoveEmptyStatement, - title: "Remove empty statement", + title: "[deprecated] Remove empty statement", messageFormat: "([deprecated] Use RCS1259 instead) Remove empty statement", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Info, @@ -364,7 +364,7 @@ public static partial class DiagnosticRules /// RCS1040 public static readonly DiagnosticDescriptor RemoveEmptyElseClause = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.RemoveEmptyElseClause, - title: "Remove empty 'else' clause", + title: "[deprecated] Remove empty 'else' clause", messageFormat: "([deprecated] Use RCS1259 instead) Remove empty 'else' clause", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, @@ -376,7 +376,7 @@ public static partial class DiagnosticRules /// RCS1041 public static readonly DiagnosticDescriptor RemoveEmptyInitializer = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.RemoveEmptyInitializer, - title: "Remove empty initializer", + title: "[deprecated] Remove empty initializer", messageFormat: "([deprecated] Use RCS1259 instead) Remove empty initializer", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Info, @@ -590,7 +590,7 @@ public static partial class DiagnosticRules /// RCS1063 public static readonly DiagnosticDescriptor AvoidUsageOfDoStatementToCreateInfiniteLoop = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.AvoidUsageOfDoStatementToCreateInfiniteLoop, - title: "Avoid usage of do statement to create an infinite loop", + title: "[deprecated] Avoid usage of do statement to create an infinite loop", messageFormat: "([deprecated] Use RCS1252 instead) Use while statement to create an infinite loop", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Info, @@ -602,7 +602,7 @@ public static partial class DiagnosticRules /// RCS1064 public static readonly DiagnosticDescriptor AvoidUsageOfForStatementToCreateInfiniteLoop = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.AvoidUsageOfForStatementToCreateInfiniteLoop, - title: "Avoid usage of for statement to create an infinite loop", + title: "[deprecated] Avoid usage of for statement to create an infinite loop", messageFormat: "([deprecated] Use RCS1252 instead) Use while statement to create an infinite loop", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Info, @@ -614,7 +614,7 @@ public static partial class DiagnosticRules /// RCS1065 public static readonly DiagnosticDescriptor AvoidUsageOfWhileStatementToCreateInfiniteLoop = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.AvoidUsageOfWhileStatementToCreateInfiniteLoop, - title: "Avoid usage of while statement to create an infinite loop", + title: "[deprecated] Avoid usage of while statement to create an infinite loop", messageFormat: "([deprecated] Use RCS1252 instead) Use for statement to create an infinite loop", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Info, @@ -626,7 +626,7 @@ public static partial class DiagnosticRules /// RCS1066 public static readonly DiagnosticDescriptor RemoveEmptyFinallyClause = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.RemoveEmptyFinallyClause, - title: "Remove empty 'finally' clause", + title: "[deprecated] Remove empty 'finally' clause", messageFormat: "([deprecated] Use RCS1259 instead) Remove empty 'finally' clause", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, @@ -688,7 +688,7 @@ public static partial class DiagnosticRules /// RCS1072 public static readonly DiagnosticDescriptor RemoveEmptyNamespaceDeclaration = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.RemoveEmptyNamespaceDeclaration, - title: "Remove empty namespace declaration", + title: "[deprecated] Remove empty namespace declaration", messageFormat: "([deprecated] Use RCS1259 instead) Remove empty namespace declaration", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Info, @@ -850,7 +850,7 @@ public static partial class DiagnosticRules /// RCS1091 public static readonly DiagnosticDescriptor RemoveEmptyRegion = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.RemoveEmptyRegion, - title: "Remove empty region", + title: "[deprecated] Remove empty region", messageFormat: "([deprecated] Use RCS1259 instead) Remove empty region", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, @@ -936,7 +936,7 @@ public static partial class DiagnosticRules /// RCS1100 public static readonly DiagnosticDescriptor FormatDocumentationSummaryOnSingleLine = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.FormatDocumentationSummaryOnSingleLine, - title: "Format documentation summary on a single line", + title: "[deprecated] Format documentation summary on a single line", messageFormat: "([deprecated] Use RCS1253 instead) Format documentation summary on a single line", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Info, @@ -948,7 +948,7 @@ public static partial class DiagnosticRules /// RCS1101 public static readonly DiagnosticDescriptor FormatDocumentationSummaryOnMultipleLines = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.FormatDocumentationSummaryOnMultipleLines, - title: "Format documentation summary on multiple lines", + title: "[deprecated] Format documentation summary on multiple lines", messageFormat: "([deprecated] Use RCS1253 instead) Format documentation summary on multiple lines", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Info, @@ -1008,7 +1008,7 @@ public static partial class DiagnosticRules /// RCS1106 public static readonly DiagnosticDescriptor RemoveEmptyDestructor = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.RemoveEmptyDestructor, - title: "Remove empty destructor", + title: "[deprecated] Remove empty destructor", messageFormat: "([deprecated] Use RCS1259 instead) Remove empty destructor", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Info, @@ -1616,8 +1616,8 @@ public static partial class DiagnosticRules /// RCS1176 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", + title: "[deprecated] Use 'var' instead of explicit type (when the type is not obvious)", + messageFormat: "([deprecated] Use RCS1264 instead) Use 'var' instead of explicit type", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, isEnabledByDefault: false, @@ -1628,8 +1628,8 @@ public static partial class DiagnosticRules /// RCS1177 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", + title: "[deprecated] Use 'var' instead of explicit type (in foreach)", + messageFormat: "([deprecated] Use RCS1264 instead) Use 'var' instead of explicit type", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, isEnabledByDefault: false, @@ -2294,7 +2294,7 @@ public static partial class DiagnosticRules /// RCS1237 public static readonly DiagnosticDescriptor UseBitShiftOperator = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.UseBitShiftOperator, - title: "Use bit shift operator", + title: "[deprecated] Use bit shift operator", messageFormat: "([deprecated] Use RCS1254 instead) Use bit shift operator", category: DiagnosticCategories.Roslynator, defaultSeverity: DiagnosticSeverity.Hidden, @@ -2603,5 +2603,17 @@ public static partial class DiagnosticRules helpLinkUri: DiagnosticIdentifiers.InvalidReferenceInDocumentationComment, customTags: Array.Empty()); + /// RCS1264 + public static readonly DiagnosticDescriptor UseVarOrExplicitType = DiagnosticDescriptorFactory.Create( + id: DiagnosticIdentifiers.UseVarOrExplicitType, + title: "Use 'var' or explicit type", + messageFormat: "{0}", + category: DiagnosticCategories.Roslynator, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: false, + description: null, + helpLinkUri: DiagnosticIdentifiers.UseVarOrExplicitType, + 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..f9cef68e80 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; @@ -444,6 +430,13 @@ public static bool IsImplicitThatCanBeExplicit( if (!type.IsVar) return false; + if (declarationExpression.Parent is AssignmentExpressionSyntax assignment) + { + ITypeSymbol? typeSymbol = semanticModel.GetTypeSymbol(assignment.Right, cancellationToken); + + return typeSymbol?.SupportsExplicitDeclaration() == true; + } + switch (declarationExpression.Designation) { case SingleVariableDesignationSyntax singleVariableDesignation: @@ -458,13 +451,32 @@ public static bool IsImplicitThatCanBeExplicit( { foreach (VariableDesignationSyntax variableDesignation in parenthesizedVariableDesignation.Variables) { - if (variableDesignation is not SingleVariableDesignationSyntax singleVariableDesignation2) + if (variableDesignation is not SingleVariableDesignationSyntax) return false; - if (!IsLocalThatSupportsExplicitDeclaration(singleVariableDesignation2)) + if (!IsLocalThatSupportsExplicitDeclaration(variableDesignation)) 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..48fafdc967 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.UseVar, out string rawValue)) + { + if (string.Equals(rawValue, ConfigOptionValues.UseVar_Always, StringComparison.OrdinalIgnoreCase)) + { + return TypeStyle.Implicit; + } + else if (string.Equals(rawValue, ConfigOptionValues.UseVar_Never, StringComparison.OrdinalIgnoreCase)) + { + return TypeStyle.Explicit; + } + else if (string.Equals(rawValue, ConfigOptionValues.UseVar_WhenTypeIsObvious, 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..224199ed2f 100644 --- a/src/Common/ConfigOptionKeys.Generated.cs +++ b/src/Common/ConfigOptionKeys.Generated.cs @@ -43,6 +43,7 @@ internal static partial class ConfigOptionKeys public const string UseBlockBodyWhenDeclarationSpansOverMultipleLines = "roslynator_use_block_body_when_declaration_spans_over_multiple_lines"; public const string UseBlockBodyWhenExpressionSpansOverMultipleLines = "roslynator_use_block_body_when_expression_spans_over_multiple_lines"; public const string UseCollectionExpression = "roslynator_use_collection_expression"; + public const string UseVar = "roslynator_use_var"; public const string UseVarInsteadOfImplicitObjectCreation = "roslynator_use_var_instead_of_implicit_object_creation"; } } \ No newline at end of file diff --git a/src/Common/ConfigOptionValues.Generated.cs b/src/Common/ConfigOptionValues.Generated.cs index f4adf13119..5de68fc012 100644 --- a/src/Common/ConfigOptionValues.Generated.cs +++ b/src/Common/ConfigOptionValues.Generated.cs @@ -57,5 +57,8 @@ internal static partial class ConfigOptionValues public const string TrailingCommaStyle_OmitWhenSingleLine = "omit_when_single_line"; public const string UseAnonymousFunctionOrMethodGroup_AnonymousFunction = "anonymous_function"; public const string UseAnonymousFunctionOrMethodGroup_MethodGroup = "method_group"; + public const string UseVar_Always = "always"; + public const string UseVar_Never = "never"; + public const string UseVar_WhenTypeIsObvious = "when_type_is_obvious"; } } \ No newline at end of file diff --git a/src/Common/ConfigOptions.Generated.cs b/src/Common/ConfigOptions.Generated.cs index d8dc899a49..2d7f2e57fd 100644 --- a/src/Common/ConfigOptions.Generated.cs +++ b/src/Common/ConfigOptions.Generated.cs @@ -232,6 +232,12 @@ public static partial class ConfigOptions defaultValuePlaceholder: "true|false", description: "Use collection expression for array/collection creation"); + public static readonly ConfigOptionDescriptor UseVar = new( + key: ConfigOptionKeys.UseVar, + defaultValue: null, + defaultValuePlaceholder: "always|never|when_type_is_obvious", + description: "Use 'var' insted of explicit type"); + public static readonly ConfigOptionDescriptor UseVarInsteadOfImplicitObjectCreation = new( key: ConfigOptionKeys.UseVarInsteadOfImplicitObjectCreation, 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.UseVar)); } } } \ No newline at end of file diff --git a/src/Common/Configuration/EditorConfigCodeAnalysisConfigLoader.cs b/src/Common/Configuration/EditorConfigCodeAnalysisConfigLoader.cs index 7551f62506..5387bb6d5f 100644 --- a/src/Common/Configuration/EditorConfigCodeAnalysisConfigLoader.cs +++ b/src/Common/Configuration/EditorConfigCodeAnalysisConfigLoader.cs @@ -147,7 +147,7 @@ private static EditorConfigData LoadFile(string path) string text = File.ReadAllText(path); - AnalyzerConfig config = AnalyzerConfig.Parse(text, path); + var config = AnalyzerConfig.Parse(text, path); AnalyzerConfigSet configSet = AnalyzerConfigSet.Create(ImmutableArray.Create(config), out ImmutableArray diagnostics); diff --git a/src/ConfigOptions.xml b/src/ConfigOptions.xml index 44aa41017e..cae280376b 100644 --- a/src/ConfigOptions.xml +++ b/src/ConfigOptions.xml @@ -229,6 +229,14 @@ true|false Use collection expression for array/collection creation +