diff --git a/src/Features/CSharp/Portable/CSharpFeatures.csproj b/src/Features/CSharp/Portable/CSharpFeatures.csproj
index f331aa7b134acafad43a82f097644f5f36be2608..fbce40c9b703df21dd4876d3883de557cafed78e 100644
--- a/src/Features/CSharp/Portable/CSharpFeatures.csproj
+++ b/src/Features/CSharp/Portable/CSharpFeatures.csproj
@@ -417,6 +417,8 @@
+
+
diff --git a/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs b/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3fa50f79e8e504cb7422bf8dc464c9756abab961
--- /dev/null
+++ b/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs
@@ -0,0 +1,82 @@
+// 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 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 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 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> createChangedDocument)
+ : base(FeaturesResources.Object_initialization_can_be_simplified, createChangedDocument)
+ {
+ }
+ }
+ }
+}
diff --git a/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerDiagnosticAnalyzer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..23d161c631534c6c1e34cf2601d6b926927cbe27
--- /dev/null
+++ b/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerDiagnosticAnalyzer.cs
@@ -0,0 +1,239 @@
+// 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 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 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 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();
+ 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();
+ _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();
+ _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);
+ }
+ }
+
+}
diff --git a/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs b/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs
index 619d6cbbe630a34dd0e9dd453d97057e25cae8d3..608a8445fa5382aa436a48f4e11079934cb67512 100644
--- a/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs
+++ b/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs
@@ -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);
}
}
diff --git a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs
index aa12579908e5da9588900fea154a38ace7e2df19..50540227fbeee129c288b6c4c1a1064e33267637 100644
--- a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs
+++ b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs
@@ -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";
diff --git a/src/Features/Core/Portable/Features.csproj b/src/Features/Core/Portable/Features.csproj
index 28ed5fc88e08c80e49dfe30604105c076169b547..61c116276b2b8795335ee0a1d0bbcd993cc0123b 100644
--- a/src/Features/Core/Portable/Features.csproj
+++ b/src/Features/Core/Portable/Features.csproj
@@ -652,6 +652,7 @@
+
diff --git a/src/Features/Core/Portable/FeaturesResources.Designer.cs b/src/Features/Core/Portable/FeaturesResources.Designer.cs
index b187fb4690aa5f58190057ebd796e9d8460fefa9..3f11afcfa2b7ed589a47e3cdd22c176137a6c5b5 100644
--- a/src/Features/Core/Portable/FeaturesResources.Designer.cs
+++ b/src/Features/Core/Portable/FeaturesResources.Designer.cs
@@ -1948,6 +1948,15 @@ internal class FeaturesResources {
}
}
+ ///
+ /// Looks up a localized string similar to Object initialization can be simplified.
+ ///
+ internal static string Object_initialization_can_be_simplified {
+ get {
+ return ResourceManager.GetString("Object_initialization_can_be_simplified", resourceCulture);
+ }
+ }
+
///
/// 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..
///
diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx
index 8efa54bfdfc5f5cbc88f5260b3f36649eaf42dbb..89fd32f252231df0a6626c091d2d4a09ac04062e 100644
--- a/src/Features/Core/Portable/FeaturesResources.resx
+++ b/src/Features/Core/Portable/FeaturesResources.resx
@@ -1067,4 +1067,7 @@ This version used in: {2}
Install package '{0}'
+
+ Object initialization can be simplified
+
\ No newline at end of file
diff --git a/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs b/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..76d96fec13324c19303ea77c7bbd5cd1213b329f
--- /dev/null
+++ b/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs
@@ -0,0 +1,56 @@
+//// 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 SupportedDiagnostics
+// {
+// get
+// {
+// throw new NotImplementedException();
+// }
+// }
+
+// public override void Initialize(AnalysisContext context)
+// {
+// context.RegisterSyntaxNodeAction(
+// 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 &&)
+// }
+// }
+//}
diff --git a/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOptions.cs b/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOptions.cs
index ac1862b4ab0556c60ceb1d7c9f4d99cecd2ef7e7..1c97ca4332a1039040d3c06558da15818693d790 100644
--- a/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOptions.cs
+++ b/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOptions.cs
@@ -55,5 +55,11 @@ public class CodeStyleOptions
nameof(PreferThrowExpression),
defaultValue: trueWithSuggestionEnforcement,
storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.PreferThrowExpression"));
+
+ internal static readonly PerLanguageOption> PreferObjectInitializer = new PerLanguageOption>(
+ nameof(CodeStyleOptions),
+ nameof(PreferObjectInitializer),
+ defaultValue: trueWithSuggestionEnforcement,
+ storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.PreferObjectInitializer"));
}
-}
+}
\ No newline at end of file