提交 286de33b 编写于 作者: C CyrusNajmabadi

initial stubs for 'Use Collection Initializer.

上级 9453dc35
......@@ -24,6 +24,7 @@ internal static class IDEDiagnosticIds
public const string InlineDeclarationDiagnosticId = "IDE0018";
public const string InlineAsTypeCheckId = "IDE0019";
public const string InlineIsTypeCheckId = "IDE0020";
public const string UseCollectionInitializerDiagnosticId = "IDE0021";
public const string UseExpressionBodyForConstructorsDiagnosticId = "IDE0020";
public const string UseExpressionBodyForMethodsDiagnosticId = "IDE0021";
......
......@@ -121,6 +121,8 @@
<Compile Include="Remote\RemoteArguments.cs" />
<Compile Include="Structure\BlockStructureOptions.cs" />
<Compile Include="AddImport\CodeActions\SymbolReference.SymbolReferenceCodeAction.cs" />
<Compile Include="UseCollectionInitializer\AbstractUseCollectionInitializerCodeFixProvider.cs" />
<Compile Include="UseCollectionInitializer\AbstractUseCollectionInitializerDiagnosticAnalyzer.cs" />
<Compile Include="UseThrowExpression\AbstractUseThrowExpressionDiagnosticAnalyzer.cs" />
<Compile Include="UseThrowExpression\UseThrowExpressionCodeFixProvider.cs" />
<Compile Include="UseThrowExpression\UseThrowExpressionCodeFixProvider.FixAllProvider.cs" />
......
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.UseCollectionInitializer
{
internal abstract class AbstractUseCollectionInitializerCodeFixProvider<
TExpressionSyntax,
TStatementSyntax,
TObjectCreationExpressionSyntax,
TMemberAccessExpressionSyntax,
TAssignmentStatementSyntax,
TVariableDeclarator>
: CodeFixProvider
where TExpressionSyntax : SyntaxNode
where TStatementSyntax : SyntaxNode
where TObjectCreationExpressionSyntax : TExpressionSyntax
where TMemberAccessExpressionSyntax : TExpressionSyntax
where TAssignmentStatementSyntax : TStatementSyntax
where TVariableDeclarator : SyntaxNode
{
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
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 = (TObjectCreationExpressionSyntax)root.FindNode(diagnostic.AdditionalLocations[0].SourceSpan);
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var analyzer = new Analyzer<TExpressionSyntax, TStatementSyntax, TObjectCreationExpressionSyntax, TMemberAccessExpressionSyntax, TAssignmentStatementSyntax, TVariableDeclarator>(
syntaxFacts, objectCreation);
var matches = analyzer.Analyze();
var editor = new SyntaxEditor(root, document.Project.Solution.Workspace);
var statement = objectCreation.FirstAncestorOrSelf<TStatementSyntax>();
var newStatement = statement.ReplaceNode(
objectCreation,
GetNewObjectCreation(objectCreation, matches)).WithAdditionalAnnotations(Formatter.Annotation);
editor.ReplaceNode(statement, newStatement);
foreach (var match in matches)
{
editor.RemoveNode(match.Statement);
}
var newRoot = editor.GetChangedRoot();
return document.WithSyntaxRoot(newRoot);
}
protected abstract TObjectCreationExpressionSyntax GetNewObjectCreation(
TObjectCreationExpressionSyntax objectCreation,
List<Match<TAssignmentStatementSyntax, TMemberAccessExpressionSyntax, TExpressionSyntax>> matches);
private class MyCodeAction : CodeAction.DocumentChangeAction
{
public MyCodeAction(Func<CancellationToken, Task<Document>> createChangedDocument)
: base(FeaturesResources.Object_initialization_can_be_simplified, createChangedDocument)
{
}
}
}
}
\ 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.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Options;
namespace Microsoft.CodeAnalysis.UseCollectionInitializer
{
internal abstract class AbstractUseCollectionInitializerDiagnosticAnalyzer<
TSyntaxKind,
TExpressionSyntax,
TStatementSyntax,
TObjectCreationExpressionSyntax,
TMemberAccessExpressionSyntax,
TAssignmentStatementSyntax,
TVariableDeclarator>
: AbstractCodeStyleDiagnosticAnalyzer, IBuiltInAnalyzer
where TSyntaxKind : struct
where TExpressionSyntax : SyntaxNode
where TStatementSyntax : SyntaxNode
where TObjectCreationExpressionSyntax : TExpressionSyntax
where TMemberAccessExpressionSyntax : TExpressionSyntax
where TAssignmentStatementSyntax : TStatementSyntax
where TVariableDeclarator : SyntaxNode
{
protected abstract bool FadeOutOperatorToken { get; }
public bool OpenFileOnly(Workspace workspace) => false;
protected AbstractUseCollectionInitializerDiagnosticAnalyzer()
: base(IDEDiagnosticIds.UseCollectionInitializerDiagnosticId,
new LocalizableResourceString(nameof(FeaturesResources.Object_initialization_can_be_simplified), FeaturesResources.ResourceManager, typeof(FeaturesResources)))
{
}
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(AnalyzeNode, GetObjectCreationSyntaxKind());
}
protected abstract TSyntaxKind GetObjectCreationSyntaxKind();
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var objectCreationExpression = (TObjectCreationExpressionSyntax)context.Node;
var language = objectCreationExpression.Language;
var optionSet = context.Options.GetOptionSet();
var option = optionSet.GetOption(CodeStyleOptions.PreferCollectionInitializer, language);
if (!option.Value)
{
// not point in analyzing if the option is off.
return;
}
var syntaxFacts = GetSyntaxFactsService();
var analyzer = new Analyzer<TExpressionSyntax, TStatementSyntax, TObjectCreationExpressionSyntax, TMemberAccessExpressionSyntax, TAssignmentStatementSyntax, TVariableDeclarator>(
syntaxFacts,
objectCreationExpression);
var matches = analyzer.Analyze();
if (matches == null)
{
return;
}
var locations = ImmutableArray.Create(objectCreationExpression.GetLocation());
var severity = option.Notification.Value;
context.ReportDiagnostic(Diagnostic.Create(
CreateDescriptor(DescriptorId, severity),
objectCreationExpression.GetLocation(),
additionalLocations: locations));
FadeOutCode(context, optionSet, matches, locations);
}
private void FadeOutCode(
SyntaxNodeAnalysisContext context,
OptionSet optionSet,
List<Match<TAssignmentStatementSyntax, TMemberAccessExpressionSyntax, TExpressionSyntax>> matches,
ImmutableArray<Location> locations)
{
var syntaxTree = context.Node.SyntaxTree;
var fadeOutCode = optionSet.GetOption(
CodeStyleOptions.PreferCollectionInitializer_FadeOutCode, context.Node.Language);
if (!fadeOutCode)
{
return;
}
var syntaxFacts = GetSyntaxFactsService();
foreach (var match in matches)
{
var end = this.FadeOutOperatorToken
? syntaxFacts.GetOperatorTokenOfMemberAccessExpression(match.MemberAccessExpression).Span.End
: syntaxFacts.GetExpressionOfMemberAccessExpression(match.MemberAccessExpression).Span.End;
var location1 = Location.Create(syntaxTree, TextSpan.FromBounds(
match.MemberAccessExpression.SpanStart, end));
context.ReportDiagnostic(Diagnostic.Create(
UnnecessaryWithSuggestionDescriptor, location1, additionalLocations: locations));
if (match.Statement.Span.End > match.Initializer.FullSpan.End)
{
context.ReportDiagnostic(Diagnostic.Create(
UnnecessaryWithoutSuggestionDescriptor,
Location.Create(syntaxTree, TextSpan.FromBounds(
match.Initializer.FullSpan.End,
match.Statement.Span.End)),
additionalLocations: locations));
}
}
}
protected abstract ISyntaxFactsService GetSyntaxFactsService();
public DiagnosticAnalyzerCategory GetAnalyzerCategory()
{
return DiagnosticAnalyzerCategory.SemanticDocumentAnalysis;
}
}
internal struct Match<TAssignmentStatementSyntax, TMemberAccessExpressionSyntax, TExpressionSyntax>
where TExpressionSyntax : SyntaxNode
where TMemberAccessExpressionSyntax : TExpressionSyntax
where TAssignmentStatementSyntax : SyntaxNode
{
public readonly TAssignmentStatementSyntax Statement;
public readonly TMemberAccessExpressionSyntax MemberAccessExpression;
public readonly TExpressionSyntax Initializer;
public Match(
TAssignmentStatementSyntax statement,
TMemberAccessExpressionSyntax memberAccessExpression,
TExpressionSyntax initializer)
{
Statement = statement;
MemberAccessExpression = memberAccessExpression;
Initializer = initializer;
}
}
internal struct Analyzer<
TExpressionSyntax,
TStatementSyntax,
TObjectCreationExpressionSyntax,
TMemberAccessExpressionSyntax,
TAssignmentStatementSyntax,
TVariableDeclaratorSyntax>
where TExpressionSyntax : SyntaxNode
where TStatementSyntax : SyntaxNode
where TObjectCreationExpressionSyntax : TExpressionSyntax
where TMemberAccessExpressionSyntax : TExpressionSyntax
where TAssignmentStatementSyntax : TStatementSyntax
where TVariableDeclaratorSyntax : SyntaxNode
{
private readonly ISyntaxFactsService _syntaxFacts;
private readonly TObjectCreationExpressionSyntax _objectCreationExpression;
private TStatementSyntax _containingStatement;
private SyntaxNodeOrToken _valuePattern;
public Analyzer(
ISyntaxFactsService syntaxFacts,
TObjectCreationExpressionSyntax objectCreationExpression) : this()
{
_syntaxFacts = syntaxFacts;
_objectCreationExpression = objectCreationExpression;
}
internal List<Match<TAssignmentStatementSyntax, TMemberAccessExpressionSyntax, TExpressionSyntax>> Analyze()
{
if (_syntaxFacts.GetObjectCreationInitializer(_objectCreationExpression) != null)
{
// Don't bother if this already has an initializer.
return null;
}
_containingStatement = _objectCreationExpression.FirstAncestorOrSelf<TStatementSyntax>();
if (_containingStatement == null)
{
return null;
}
if (!TryInitializeVariableDeclarationCase() &&
!TryInitializeAssignmentCase())
{
return null;
}
var containingBlock = _containingStatement.Parent;
var foundStatement = false;
List<Match<TAssignmentStatementSyntax, TMemberAccessExpressionSyntax, TExpressionSyntax>> matches = null;
HashSet<string> seenNames = null;
foreach (var child in containingBlock.ChildNodesAndTokens())
{
if (!foundStatement)
{
if (child == _containingStatement)
{
foundStatement = true;
}
continue;
}
if (child.IsToken)
{
break;
}
var statement = child.AsNode() as TAssignmentStatementSyntax;
if (statement == null)
{
break;
}
if (!_syntaxFacts.IsSimpleAssignmentStatement(statement))
{
break;
}
SyntaxNode left, right;
_syntaxFacts.GetPartsOfAssignmentStatement(statement, out left, out right);
var rightExpression = right as TExpressionSyntax;
var leftMemberAccess = left as TMemberAccessExpressionSyntax;
if (!_syntaxFacts.IsSimpleMemberAccessExpression(leftMemberAccess))
{
break;
}
var expression = (TExpressionSyntax)_syntaxFacts.GetExpressionOfMemberAccessExpression(leftMemberAccess);
if (!ValuePatternMatches(expression))
{
break;
}
// found a match!
seenNames = seenNames ?? new HashSet<string>();
matches = matches ?? new List<Match<TAssignmentStatementSyntax, TMemberAccessExpressionSyntax, TExpressionSyntax>>();
// If we see an assignment to the same property/field, we can't convert it
// to an initializer.
var name = _syntaxFacts.GetNameOfMemberAccessExpression(leftMemberAccess);
var identifier = _syntaxFacts.GetIdentifierOfSimpleName(name);
if (!seenNames.Add(identifier.ValueText))
{
break;
}
matches.Add(new Match<TAssignmentStatementSyntax, TMemberAccessExpressionSyntax, TExpressionSyntax>(
statement, leftMemberAccess, rightExpression));
}
return matches;
}
private bool ValuePatternMatches(TExpressionSyntax expression)
{
if (_valuePattern.IsToken)
{
return _syntaxFacts.IsIdentifierName(expression) &&
_syntaxFacts.AreEquivalent(
_valuePattern.AsToken(),
_syntaxFacts.GetIdentifierOfSimpleName(expression));
}
else
{
return _syntaxFacts.AreEquivalent(
_valuePattern.AsNode(), expression);
}
}
private bool TryInitializeAssignmentCase()
{
if (!_syntaxFacts.IsSimpleAssignmentStatement(_containingStatement))
{
return false;
}
SyntaxNode left, right;
_syntaxFacts.GetPartsOfAssignmentStatement(_containingStatement, out left, out right);
if (right != _objectCreationExpression)
{
return false;
}
_valuePattern = left;
return true;
}
private bool TryInitializeVariableDeclarationCase()
{
if (!_syntaxFacts.IsLocalDeclarationStatement(_containingStatement))
{
return false;
}
var containingDeclarator = _objectCreationExpression.FirstAncestorOrSelf<TVariableDeclaratorSyntax>();
if (containingDeclarator == null)
{
return false;
}
if (!_syntaxFacts.IsDeclaratorOfLocalDeclarationStatement(containingDeclarator, _containingStatement))
{
return false;
}
_valuePattern = _syntaxFacts.GetIdentifierOfVariableDeclarator(containingDeclarator);
return true;
}
}
}
\ No newline at end of file
......@@ -338,6 +338,34 @@ public Customer()
//]
}
}
";
private static readonly string s_preferCollectionInitializer = @"
using System.Collections.Generic;
class Customer
{
private int Age;
public Customer()
{
//[
// Prefer:
var list = new List<int>
{
1,
2,
3
};
// Over:
var list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);
//]
}
}
";
private static readonly string s_preferInlinedVariableDeclaration = @"
......@@ -589,6 +617,7 @@ internal StyleViewModel(OptionSet optionSet, IServiceProvider serviceProvider) :
// Expression preferences
CodeStyleItems.Add(new SimpleCodeStyleOptionViewModel(CodeStyleOptions.PreferObjectInitializer, ServicesVSResources.Prefer_object_initializer, s_preferObjectInitializer, s_preferObjectInitializer, this, optionSet, expressionPreferencesGroupTitle));
CodeStyleItems.Add(new SimpleCodeStyleOptionViewModel(CodeStyleOptions.PreferCollectionInitializer, ServicesVSResources.Prefer_collection_initializer, s_preferCollectionInitializer, s_preferCollectionInitializer, this, optionSet, expressionPreferencesGroupTitle));
CodeStyleItems.Add(new SimpleCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferPatternMatchingOverIsWithCastCheck, CSharpVSResources.Prefer_pattern_matching_over_is_with_cast_check, s_preferPatternMatchingOverIsWithCastCheck, s_preferPatternMatchingOverIsWithCastCheck, this, optionSet, expressionPreferencesGroupTitle));
CodeStyleItems.Add(new SimpleCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferPatternMatchingOverAsWithNullCheck, CSharpVSResources.Prefer_pattern_matching_over_as_with_null_check, s_preferPatternMatchingOverAsWithNullCheck, s_preferPatternMatchingOverAsWithNullCheck, this, optionSet, expressionPreferencesGroupTitle));
......
......@@ -1365,6 +1365,15 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to Prefer collection initializer.
/// </summary>
internal static string Prefer_collection_initializer {
get {
return ResourceManager.GetString("Prefer_collection_initializer", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Prefer framework type.
/// </summary>
......
......@@ -804,4 +804,7 @@ Additional information: {1}</value>
<data name="This_item_cannot_be_deleted_because_it_is_used_by_an_existing_Naming_Rule" xml:space="preserve">
<value>This item cannot be deleted because it is used by an existing Naming Rule.</value>
</data>
<data name="Prefer_collection_initializer" xml:space="preserve">
<value>Prefer collection initializer</value>
</data>
</root>
\ No newline at end of file
......@@ -165,6 +165,30 @@ Class Customer
End Sub
End Class"
Private Shared ReadOnly s_preferCollectionInitializer As String = "
Imports System.Collections.Generic
Class Customer
Private Age As Integer
Sub New()
//[
' Prefer:
Dim list = New List(Of Integer) From {
1,
2,
3
}
' Over:
Dim list = New List(Of Integer)()
list.Add(1)
list.Add(2)
list.Add(3)
//]
End Sub
End Class"
#End Region
Public Sub New(optionSet As OptionSet, serviceProvider As IServiceProvider)
......@@ -202,6 +226,7 @@ End Class"
' expression preferences
Me.CodeStyleItems.Add(New SimpleCodeStyleOptionViewModel(CodeStyleOptions.PreferObjectInitializer, ServicesVSResources.Prefer_object_initializer, s_preferObjectInitializer, s_preferObjectInitializer, Me, optionSet, expressionPreferencesGroupTitle))
Me.CodeStyleItems.Add(New SimpleCodeStyleOptionViewModel(CodeStyleOptions.PreferCollectionInitializer, ServicesVSResources.Prefer_collection_initializer, s_preferCollectionInitializer, s_preferCollectionInitializer, Me, optionSet, expressionPreferencesGroupTitle))
End Sub
End Class
......
......@@ -64,11 +64,23 @@ public class CodeStyleOptions
defaultValue: TrueWithSuggestionEnforcement,
storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.PreferObjectInitializer"));
internal static readonly PerLanguageOption<CodeStyleOption<bool>> PreferCollectionInitializer = new PerLanguageOption<CodeStyleOption<bool>>(
nameof(CodeStyleOptions),
nameof(PreferCollectionInitializer),
defaultValue: TrueWithSuggestionEnforcement,
storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.PreferCollectionInitializer"));
internal static readonly PerLanguageOption<bool> PreferObjectInitializer_FadeOutCode = new PerLanguageOption<bool>(
nameof(CodeStyleOptions),
nameof(PreferObjectInitializer_FadeOutCode),
defaultValue: false,
storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.PreferObjectInitializer"));
storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.PreferObjectInitializer_FadeOutCode"));
internal static readonly PerLanguageOption<bool> PreferCollectionInitializer_FadeOutCode = new PerLanguageOption<bool>(
nameof(CodeStyleOptions),
nameof(PreferCollectionInitializer_FadeOutCode),
defaultValue: false,
storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.PreferCollectionInitializer_FadeOutCode"));
internal static readonly PerLanguageOption<CodeStyleOption<bool>> PreferInlinedVariableDeclaration = new PerLanguageOption<CodeStyleOption<bool>>(
nameof(CodeStyleOptions),
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册