Skip to content

Commit

Permalink
Add analyzer 'Declare explicit/implicit type' (#1335)
Browse files Browse the repository at this point in the history
  • Loading branch information
josefpihrt authored Dec 31, 2023
1 parent c5b77c2 commit 99d4c4a
Show file tree
Hide file tree
Showing 35 changed files with 1,186 additions and 480 deletions.
9 changes: 9 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string> 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;
}
}
}
}
23 changes: 23 additions & 0 deletions src/Analyzers.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1799,6 +1799,8 @@ else if (condition2)
<Analyzer>
<Id>RCS1008</Id>
<Identifier>UseExplicitTypeInsteadOfVarWhenTypeIsNotObvious</Identifier>
<Status>Obsolete</Status>
<ObsoleteMessage>Use RCS1264 instead</ObsoleteMessage>
<Title>Use explicit type instead of 'var' (when the type is not obvious)</Title>
<MessageFormat>Use explicit type instead of 'var'</MessageFormat>
<DefaultSeverity>Hidden</DefaultSeverity>
Expand All @@ -1814,6 +1816,8 @@ else if (condition2)
<Id>RCS1009</Id>
<Identifier>UseExplicitTypeInsteadOfVarInForEach</Identifier>
<Title>Use explicit type instead of 'var' (foreach variable)</Title>
<Status>Obsolete</Status>
<ObsoleteMessage>Use RCS1264 instead</ObsoleteMessage>
<MessageFormat>Use explicit type instead of 'var'</MessageFormat>
<DefaultSeverity>Hidden</DefaultSeverity>
<IsEnabledByDefault>false</IsEnabledByDefault>
Expand All @@ -1835,6 +1839,8 @@ foreach (var item in items)
<Analyzer>
<Id>RCS1010</Id>
<Identifier>UseVarInsteadOfExplicitTypeWhenTypeIsObvious</Identifier>
<Status>Obsolete</Status>
<ObsoleteMessage>Use RCS1264 instead</ObsoleteMessage>
<Title>Use 'var' instead of explicit type (when the type is obvious)</Title>
<MessageFormat>Use 'var' instead of explicit type</MessageFormat>
<DefaultSeverity>Hidden</DefaultSeverity>
Expand All @@ -1850,6 +1856,8 @@ foreach (var item in items)
<Analyzer>
<Id>RCS1012</Id>
<Identifier>UseExplicitTypeInsteadOfVarWhenTypeIsObvious</Identifier>
<Status>Obsolete</Status>
<ObsoleteMessage>Use RCS1264 instead</ObsoleteMessage>
<Title>Use explicit type instead of 'var' (when the type is obvious)</Title>
<MessageFormat>Use explicit type instead of 'var'</MessageFormat>
<DefaultSeverity>Hidden</DefaultSeverity>
Expand Down Expand Up @@ -5411,6 +5419,8 @@ else
<Analyzer>
<Id>RCS1176</Id>
<Identifier>UseVarInsteadOfExplicitTypeWhenTypeIsNotObvious</Identifier>
<Status>Obsolete</Status>
<ObsoleteMessage>Use RCS1264 instead</ObsoleteMessage>
<Title>Use 'var' instead of explicit type (when the type is not obvious)</Title>
<MessageFormat>Use 'var' instead of explicit type</MessageFormat>
<DefaultSeverity>Hidden</DefaultSeverity>
Expand All @@ -5425,6 +5435,8 @@ else
<Analyzer>
<Id>RCS1177</Id>
<Identifier>UseVarInsteadOfExplicitTypeInForEach</Identifier>
<Status>Obsolete</Status>
<ObsoleteMessage>Use RCS1264 instead</ObsoleteMessage>
<Title>Use 'var' instead of explicit type (in foreach)</Title>
<MessageFormat>Use 'var' instead of explicit type</MessageFormat>
<DefaultSeverity>Hidden</DefaultSeverity>
Expand Down Expand Up @@ -7633,6 +7645,17 @@ public string Foo(string bar)
</Sample>
</Samples>
</Analyzer>
<Analyzer>
<Id>RCS1264</Id>
<Identifier>UseVarOrExplicitType</Identifier>
<Title>Use 'var' or explicit type</Title>
<MessageFormat>{0}</MessageFormat>
<DefaultSeverity>Info</DefaultSeverity>
<IsEnabledByDefault>false</IsEnabledByDefault>
<ConfigOptions>
<Option Key="use_var" IsRequired="true" />
</ConfigOptions>
</Analyzer>
<Analyzer>
<Id>RCS9001</Id>
<Identifier>UsePatternMatching</Identifier>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -8,6 +9,7 @@

namespace Roslynator.CSharp.Analysis;

[Obsolete("Use analyzer 'UseVarOrExplicitTypeAnalyzer' instead.")]
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class UseExplicitTypeInsteadOfVarInForEachAnalyzer : BaseDiagnosticAnalyzer
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -8,6 +9,7 @@

namespace Roslynator.CSharp.Analysis;

[Obsolete("Use analyzer 'UseVarOrExplicitTypeAnalyzer' instead.")]
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class UseExplicitTypeInsteadOfVarWhenTypeIsNotObviousAnalyzer : BaseDiagnosticAnalyzer
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -8,6 +9,7 @@

namespace Roslynator.CSharp.Analysis;

[Obsolete("Use analyzer 'UseVarOrExplicitTypeAnalyzer' instead.")]
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class UseExplicitTypeInsteadOfVarWhenTypeIsObviousAnalyzer : BaseDiagnosticAnalyzer
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -8,6 +9,7 @@

namespace Roslynator.CSharp.Analysis;

[Obsolete("Use analyzer 'UseVarOrExplicitTypeAnalyzer' instead.")]
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class UseVarInsteadOfExplicitTypeInForEachAnalyzer : BaseDiagnosticAnalyzer
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -8,6 +9,7 @@

namespace Roslynator.CSharp.Analysis;

[Obsolete("Use analyzer 'UseVarOrExplicitTypeAnalyzer' instead.")]
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class UseVarInsteadOfExplicitTypeWhenTypeIsNotObviousAnalyzer : BaseDiagnosticAnalyzer
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -8,6 +9,7 @@

namespace Roslynator.CSharp.Analysis;

[Obsolete("Use analyzer 'UseVarOrExplicitTypeAnalyzer' instead.")]
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class UseVarInsteadOfExplicitTypeWhenTypeIsObviousAnalyzer : BaseDiagnosticAnalyzer
{
Expand Down
Loading

0 comments on commit 99d4c4a

Please sign in to comment.