提交 d6b91d3c 编写于 作者: C CyrusNajmabadi

Initial work for the 'Use Object Initializer' analyzer.

上级 4c91e16f
......@@ -417,6 +417,8 @@
<Compile Include="SignatureHelp\ObjectCreationExpressionSignatureHelpProvider_NormalType.cs" />
<Compile Include="SignatureHelp\SignatureHelpUtilities.cs" />
<Compile Include="SolutionCrawler\CSharpDocumentDifferenceService.cs" />
<Compile Include="UseObjectInitializer\CSharpUseObjectInitializerCodeFixProvider.cs" />
<Compile Include="UseObjectInitializer\CSharpUseObjectInitializerDiagnosticAnalyzer.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="CSharpFeaturesResources.resx">
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.UseObjectInitializer
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseObjectInitializer), Shared]
internal class CSharpUseObjectInitializerCodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds
=> ImmutableArray.Create(IDEDiagnosticIds.UseObjectInitializerDiagnosticId);
public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
context.RegisterCodeFix(
new MyCodeAction(c => FixAsync(context.Document, context.Diagnostics.First(), c)),
context.Diagnostics);
return SpecializedTasks.EmptyTask;
}
private async Task<Document> FixAsync(
Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var objectCreation = (ObjectCreationExpressionSyntax)root.FindNode(diagnostic.AdditionalLocations[0].SourceSpan);
var matches = new Analyzer(objectCreation).Analyze();
var editor = new SyntaxEditor(root, document.Project.Solution.Workspace);
editor.ReplaceNode(objectCreation, GetNewObjectCreation(objectCreation, matches));
foreach(var match in matches)
{
editor.RemoveNode(match.ExpressionStatement);
}
var newRoot = editor.GetChangedRoot();
return document.WithSyntaxRoot(newRoot);
}
private ObjectCreationExpressionSyntax GetNewObjectCreation(
ObjectCreationExpressionSyntax objectCreation,
List<Match> matches)
{
var initializer = SyntaxFactory.InitializerExpression(
SyntaxKind.ObjectInitializerExpression,
SyntaxFactory.SeparatedList(
matches.Select(CreateAssignmentExpression)));
return objectCreation.WithInitializer(initializer)
.WithAdditionalAnnotations(Formatter.Annotation);
}
private ExpressionSyntax CreateAssignmentExpression(Match match)
{
return SyntaxFactory.AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
match.MemberAccessExpression.Name,
match.Initializer).WithLeadingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed);
}
private class MyCodeAction : CodeAction.DocumentChangeAction
{
public MyCodeAction(Func<CancellationToken, Task<Document>> createChangedDocument)
: base(FeaturesResources.Object_initialization_can_be_simplified, createChangedDocument)
{
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.CSharp.UseObjectInitializer
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class CSharpUseObjectInitializerDiagnosticAnalyzer : DiagnosticAnalyzer, IBuiltInAnalyzer
{
private static readonly string Id = IDEDiagnosticIds.UseObjectInitializerDiagnosticId;
private static readonly DiagnosticDescriptor s_descriptor =
CreateDescriptor(Id, DiagnosticSeverity.Hidden);
private static readonly DiagnosticDescriptor s_unnecessaryWithSuggestionDescriptor =
CreateDescriptor(Id, DiagnosticSeverity.Hidden, DiagnosticCustomTags.Unnecessary);
private static readonly DiagnosticDescriptor s_unnecessaryWithoutSuggestionDescriptor =
CreateDescriptor(Id + "WithoutSuggestion",
DiagnosticSeverity.Hidden, DiagnosticCustomTags.Unnecessary);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
=> ImmutableArray.Create(s_descriptor, s_unnecessaryWithoutSuggestionDescriptor, s_unnecessaryWithSuggestionDescriptor);
public bool OpenFileOnly(Workspace workspace) => false;
private static DiagnosticDescriptor CreateDescriptor(string id, DiagnosticSeverity severity, params string[] customTags)
=> new DiagnosticDescriptor(
id,
FeaturesResources.Object_initialization_can_be_simplified,
FeaturesResources.Object_initialization_can_be_simplified,
DiagnosticCategory.Style,
severity,
isEnabledByDefault: true,
customTags: customTags);
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ObjectCreationExpression);
}
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var optionSet = context.Options.GetOptionSet();
var option = optionSet.GetOption(CodeStyleOptions.PreferObjectInitializer, LanguageNames.CSharp);
if (!option.Value)
{
// not point in analyzing if the option is off.
return;
}
var objectCreationExpression = (ObjectCreationExpressionSyntax)context.Node;
var matches = new Analyzer(objectCreationExpression).Analyze();
if (matches == null)
{
return;
}
var locations = ImmutableArray.Create(objectCreationExpression.GetLocation());
var severity = option.Notification.Value;
context.ReportDiagnostic(Diagnostic.Create(
CreateDescriptor(Id, severity),
objectCreationExpression.GetLocation(),
additionalLocations: locations));
var syntaxTree = objectCreationExpression.SyntaxTree;
foreach (var match in matches)
{
var location1 = Location.Create(syntaxTree, TextSpan.FromBounds(
match.MemberAccessExpression.SpanStart, match.MemberAccessExpression.OperatorToken.Span.End));
context.ReportDiagnostic(Diagnostic.Create(
s_unnecessaryWithSuggestionDescriptor, location1, additionalLocations: locations));
context.ReportDiagnostic(Diagnostic.Create(
s_unnecessaryWithoutSuggestionDescriptor,
match.ExpressionStatement.SemicolonToken.GetLocation(),
additionalLocations: locations));
}
}
public DiagnosticAnalyzerCategory GetAnalyzerCategory()
{
return DiagnosticAnalyzerCategory.SemanticDocumentAnalysis;
}
}
internal struct Match
{
public readonly ExpressionStatementSyntax ExpressionStatement;
public readonly MemberAccessExpressionSyntax MemberAccessExpression;
public readonly ExpressionSyntax Initializer;
public Match(ExpressionStatementSyntax expressionStatement, MemberAccessExpressionSyntax memberAccessExpression, ExpressionSyntax initializer)
{
ExpressionStatement = expressionStatement;
MemberAccessExpression = memberAccessExpression;
Initializer = initializer;
}
}
internal struct Analyzer
{
private readonly ObjectCreationExpressionSyntax _objectCreationExpression;
private StatementSyntax _containingStatement;
private SyntaxNodeOrToken _valuePattern;
public Analyzer(ObjectCreationExpressionSyntax objectCreationExpression) : this()
{
_objectCreationExpression = objectCreationExpression;
}
internal List<Match> Analyze()
{
if (_objectCreationExpression.Initializer != null)
{
// Don't bother if this already has an initializer.
return null;
}
if (!TryInitializeVariableDeclarationCase() &&
!TryInitializeAssignmentCase())
{
return null;
}
var containingBlock = _containingStatement.Parent as BlockSyntax;
if (containingBlock == null)
{
return null;
}
List<Match> matches = null;
var statementIndex = containingBlock.Statements.IndexOf(_containingStatement);
for (var i = statementIndex + 1; i < containingBlock.Statements.Count; i++)
{
var expressionStatement = containingBlock.Statements[i] as ExpressionStatementSyntax;
if (expressionStatement == null)
{
break;
}
var assignExpression = expressionStatement.Expression as AssignmentExpressionSyntax;
if (assignExpression?.Kind() != SyntaxKind.SimpleAssignmentExpression)
{
break;
}
var leftMemberAccess = assignExpression.Left as MemberAccessExpressionSyntax;
if (leftMemberAccess?.Kind() != SyntaxKind.SimpleMemberAccessExpression)
{
break;
}
var expression = leftMemberAccess.Expression;
if (!ValuePatternMatches(expression))
{
break;
}
// found a match!
matches = matches ?? new List<Match>();
matches.Add(new Match(expressionStatement, leftMemberAccess, assignExpression.Right));
}
return matches;
}
private bool ValuePatternMatches(ExpressionSyntax expression)
{
if (_valuePattern.IsToken)
{
return expression.IsKind(SyntaxKind.IdentifierName) &&
SyntaxFactory.AreEquivalent(
_valuePattern.AsToken(),
((IdentifierNameSyntax)expression).Identifier);
}
else
{
return SyntaxFactory.AreEquivalent(
_valuePattern.AsNode(),
expression);
}
}
private bool TryInitializeAssignmentCase()
{
if (!IsRightSideOfAssignment())
{
return false;
}
_containingStatement = _objectCreationExpression.FirstAncestorOrSelf<StatementSyntax>();
_valuePattern = ((AssignmentExpressionSyntax)_objectCreationExpression.Parent).Left;
return true;
}
private bool IsRightSideOfAssignment()
{
return _objectCreationExpression.IsParentKind(SyntaxKind.SimpleAssignmentExpression) &&
((AssignmentExpressionSyntax)_objectCreationExpression.Parent).Right == _objectCreationExpression &&
_objectCreationExpression.Parent.IsParentKind(SyntaxKind.ExpressionStatement);
}
private bool TryInitializeVariableDeclarationCase()
{
if (!IsVariableDeclarationInitializer())
{
return false;
}
_containingStatement = _objectCreationExpression.FirstAncestorOrSelf<StatementSyntax>();
_valuePattern = ((VariableDeclaratorSyntax)_objectCreationExpression.Parent.Parent).Identifier;
return true;
}
private bool IsVariableDeclarationInitializer()
{
return
_objectCreationExpression.IsParentKind(SyntaxKind.EqualsValueClause) &&
_objectCreationExpression.Parent.IsParentKind(SyntaxKind.VariableDeclarator) &&
_objectCreationExpression.Parent.Parent.IsParentKind(SyntaxKind.VariableDeclaration) &&
_objectCreationExpression.Parent.Parent.Parent.IsParentKind(SyntaxKind.LocalDeclarationStatement);
}
}
}
......@@ -44,6 +44,7 @@ internal static class PredefinedCodeFixProviderNames
public const string AddNew = "Add new keyword to member";
public const string UseImplicitType = nameof(UseImplicitType);
public const string UseExplicitType = nameof(UseExplicitType);
public const string UseObjectInitializer = nameof(UseObjectInitializer);
public const string PreferFrameworkType = nameof(PreferFrameworkType);
}
}
......@@ -20,6 +20,8 @@ internal static class IDEDiagnosticIds
public const string PreferFrameworkTypeInDeclarationsDiagnosticId = "IDE0014";
public const string PreferFrameworkTypeInMemberAccessDiagnosticId = "IDE0015";
public const string UseObjectInitializerDiagnosticId = "IDE0017";
// Analyzer error Ids
public const string AnalyzerChangedId = "IDE1001";
public const string AnalyzerDependencyConflictId = "IDE1002";
......
......@@ -652,6 +652,7 @@
<Compile Include="Common\SymbolDisplayPartKindTags.cs" />
<Compile Include="UseAutoProperty\AbstractUseAutoPropertyCodeFixProvider.cs" />
<Compile Include="UseAutoProperty\AbstractUseAutoPropertyAnalyzer.cs" />
<Compile Include="UseObjectInitializer\AbstractUseObjectInitializerDiagnosticAnalyzer.cs" />
<Compile Include="Workspace\BackgroundCompiler.cs" />
<Compile Include="Workspace\BackgroundParser.cs" />
</ItemGroup>
......
......@@ -1948,6 +1948,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Object initialization can be simplified.
/// </summary>
internal static string Object_initialization_can_be_simplified {
get {
return ResourceManager.GetString("Object_initialization_can_be_simplified", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Only methods with a single argument, which is not an out variable declaration, can be replaced with a property..
/// </summary>
......
......@@ -1067,4 +1067,7 @@ This version used in: {2}</value>
<data name="Install_package_0" xml:space="preserve">
<value>Install package '{0}'</value>
</data>
<data name="Object_initialization_can_be_simplified" xml:space="preserve">
<value>Object initialization can be simplified</value>
</data>
</root>
\ No newline at end of file
//// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
//using System;
//using System.Collections.Generic;
//using System.Collections.Immutable;
//using System.Linq;
//using System.Text;
//using System.Threading.Tasks;
//using Microsoft.CodeAnalysis.Diagnostics;
//namespace Microsoft.CodeAnalysis.UseObjectInitializer
//{
// internal abstract class AbstractUseObjectInitializerDiagnosticAnalyzer<
// TObjectCreationExpression,
// TEqualsValueClause,
// TVariableDeclarator,
// TAssignmentExpression,
// TSyntaxKind>
// : DiagnosticAnalyzer
// where TObjectCreationExpression : SyntaxNode
// where TEqualsValueClause : SyntaxNode
// where TVariableDeclarator : SyntaxNode
// where TAssignmentExpression : SyntaxNode
// where TSyntaxKind : struct
// {
// public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
// {
// get
// {
// throw new NotImplementedException();
// }
// }
// public override void Initialize(AnalysisContext context)
// {
// context.RegisterSyntaxNodeAction<TSyntaxKind>(
// AnalyzeNode,
// ImmutableArray.Create(GetObjectCreationSyntaxKind()));
// }
// protected abstract TSyntaxKind GetObjectCreationSyntaxKind();
// private void AnalyzeNode(SyntaxNodeAnalysisContext obj)
// {
// var objectCreationNode = (TObjectCreationExpression)obj.Node;
// if (objectCreationNode.Parent is TEqualsValueClause &&
// objectCreationNode.Parent.Parent is TVariableDeclarator)
// {
// AnalyzeInVariableDeclarator(objectCreationNode);
// return;
// }
// if( objectCreationNode.Parent is TAssignmentExpression &&)
// }
// }
//}
......@@ -55,5 +55,11 @@ public class CodeStyleOptions
nameof(PreferThrowExpression),
defaultValue: trueWithSuggestionEnforcement,
storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.PreferThrowExpression"));
internal static readonly PerLanguageOption<CodeStyleOption<bool>> PreferObjectInitializer = new PerLanguageOption<CodeStyleOption<bool>>(
nameof(CodeStyleOptions),
nameof(PreferObjectInitializer),
defaultValue: trueWithSuggestionEnforcement,
storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.PreferObjectInitializer"));
}
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册