From 83b2a8989e6225bf2dd7766f3a4e3ef361e4746d Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 20 Sep 2016 01:55:33 -0700 Subject: [PATCH] Push logic into abstract class so we can share it with VB. --- .../CrefCompletionProviderTests.cs | 65 +++ .../CrefCompletionProviderTests.vb | 50 ++- ...harpUseObjectInitializerCodeFixProvider.cs | 70 +--- ...pUseObjectInitializerDiagnosticAnalyzer.cs | 250 +----------- src/Features/Core/Portable/Features.csproj | 1 + .../AbstractGenerateMemberService.cs | 2 +- ...ractUseObjectInitializerCodeFixProvider.cs | 105 +++++ ...tUseObjectInitializerDiagnosticAnalyzer.cs | 370 +++++++++++++++--- .../CompilationErrorDetailDiscoverer.cs | 2 +- .../CodeGeneration/CSharpSyntaxGenerator.cs | 21 + .../CSharpSyntaxFactsService.cs | 66 +++- .../Core/Portable/Editing/SyntaxGenerator.cs | 4 + .../SyntaxFactsService/ISyntaxFactsService.cs | 28 +- .../ISyntaxFactsServiceExtensions.cs | 2 +- .../VisualBasicSyntaxGenerator.vb | 16 + .../VisualBasicSyntaxFactsService.vb | 53 ++- 16 files changed, 742 insertions(+), 363 deletions(-) create mode 100644 src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerCodeFixProvider.cs diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs index 4b494cf53d1..c74a6802db8 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs @@ -1020,6 +1020,71 @@ public void AddFirstMissingCloseBrace(SyntaxNode root, SyntaxNode contextNode, o { throw new NotImplementedException(); } + + public SyntaxNode GetObjectCreationInitializer(SyntaxNode objectCreationExpression) + { + throw new NotImplementedException(); + } + + public bool IsSimpleAssignmentStatement(SyntaxNode statement) + { + throw new NotImplementedException(); + } + + public void GetPartsOfAssignmentStatement(SyntaxNode statement, out SyntaxNode left, out SyntaxNode right) + { + throw new NotImplementedException(); + } + + public SyntaxNode GetNameOfMemberAccessExpression(SyntaxNode memberAccessExpression) + { + throw new NotImplementedException(); + } + + public SyntaxToken GetOperatorTokenOfMemberAccessExpression(SyntaxNode memberAccessExpression) + { + throw new NotImplementedException(); + } + + public bool IsSimpleMemberAccessExpression(SyntaxNode node) + { + throw new NotImplementedException(); + } + + public SyntaxToken GetIdentifierOfSimpleName(SyntaxNode node) + { + throw new NotImplementedException(); + } + + public SyntaxToken GetIdentifierOfVariableDeclarator(SyntaxNode node) + { + throw new NotImplementedException(); + } + + public bool IsIdentifierName(SyntaxNode node) + { + throw new NotImplementedException(); + } + + public bool IsLocalDeclarationStatement(SyntaxNode node) + { + throw new NotImplementedException(); + } + + public bool IsDeclaratorOfLocalDeclarationStatement(SyntaxNode declator, SyntaxNode localDeclarationStatement) + { + throw new NotImplementedException(); + } + + public bool AreEquivalent(SyntaxToken token1, SyntaxToken token2) + { + throw new NotImplementedException(); + } + + public bool AreEquivalent(SyntaxNode node1, SyntaxNode node2) + { + throw new NotImplementedException(); + } } } } diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/CrefCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/CrefCompletionProviderTests.vb index 46106321e40..0ce5713d312 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/CrefCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/CrefCompletionProviderTests.vb @@ -660,7 +660,7 @@ End Class]]>.Value.NormalizeLineEndings() Throw New NotImplementedException() End Function - Public Function IsMemberAccessExpression(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsMemberAccessExpression + Public Function IsMemberAccessExpression(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsSimpleMemberAccessExpression Throw New NotImplementedException() End Function @@ -895,6 +895,54 @@ End Class]]>.Value.NormalizeLineEndings() Public Sub AddFirstMissingCloseBrace(root As SyntaxNode, contextNode As SyntaxNode, ByRef newRoot As SyntaxNode, ByRef newContextNode As SyntaxNode) Implements ISyntaxFactsService.AddFirstMissingCloseBrace Throw New NotImplementedException() End Sub + + Public Function GetObjectCreationInitializer(objectCreationExpression As SyntaxNode) As SyntaxNode Implements ISyntaxFactsService.GetObjectCreationInitializer + Throw New NotImplementedException() + End Function + + Public Function IsSimpleAssignmentStatement(statement As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsSimpleAssignmentStatement + Throw New NotImplementedException() + End Function + + Public Sub GetPartsOfAssignmentStatement(statement As SyntaxNode, ByRef left As SyntaxNode, ByRef right As SyntaxNode) Implements ISyntaxFactsService.GetPartsOfAssignmentStatement + Throw New NotImplementedException() + End Sub + + Public Function GetNameOfMemberAccessExpression(memberAccessExpression As SyntaxNode) As SyntaxNode Implements ISyntaxFactsService.GetNameOfMemberAccessExpression + Throw New NotImplementedException() + End Function + + Public Function GetOperatorTokenOfMemberAccessExpression(memberAccessExpression As SyntaxNode) As SyntaxToken Implements ISyntaxFactsService.GetOperatorTokenOfMemberAccessExpression + Throw New NotImplementedException() + End Function + + Public Function GetIdentifierOfSimpleName(node As SyntaxNode) As SyntaxToken Implements ISyntaxFactsService.GetIdentifierOfSimpleName + Throw New NotImplementedException() + End Function + + Public Function GetIdentifierOfVariableDeclarator(node As SyntaxNode) As SyntaxToken Implements ISyntaxFactsService.GetIdentifierOfVariableDeclarator + Throw New NotImplementedException() + End Function + + Public Function IsIdentifierName(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsIdentifierName + Throw New NotImplementedException() + End Function + + Public Function IsLocalDeclarationStatement(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsLocalDeclarationStatement + Throw New NotImplementedException() + End Function + + Public Function IsDeclaratorOfLocalDeclarationStatement(declator As SyntaxNode, localDeclarationStatement As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsDeclaratorOfLocalDeclarationStatement + Throw New NotImplementedException() + End Function + + Public Function AreEquivalent(token1 As SyntaxToken, token2 As SyntaxToken) As Boolean Implements ISyntaxFactsService.AreEquivalent + Throw New NotImplementedException() + End Function + + Public Function AreEquivalent(node1 As SyntaxNode, node2 As SyntaxNode) As Boolean Implements ISyntaxFactsService.AreEquivalent + Throw New NotImplementedException() + End Function End Class End Class End Namespace diff --git a/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs b/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs index b677626639b..82db40a3b52 100644 --- a/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs @@ -13,72 +13,24 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.UseObjectInitializer; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.UseObjectInitializer { [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseObjectInitializer), Shared] - internal class CSharpUseObjectInitializerCodeFixProvider : CodeFixProvider + internal class CSharpUseObjectInitializerCodeFixProvider : + AbstractUseObjectInitializerCodeFixProvider< + ExpressionSyntax, + StatementSyntax, + ObjectCreationExpressionSyntax, + MemberAccessExpressionSyntax, + VariableDeclaratorSyntax> { - public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - - 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 + protected override SyntaxTrivia GetNewLine() { - public MyCodeAction(Func> createChangedDocument) - : base(FeaturesResources.Object_initialization_can_be_simplified, createChangedDocument) - { - } + return SyntaxFactory.ElasticCarriageReturnLineFeed; } } } diff --git a/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerDiagnosticAnalyzer.cs index ea2ec25510a..f04dd671e71 100644 --- a/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerDiagnosticAnalyzer.cs +++ b/src/Features/CSharp/Portable/UseObjectInitializer/CSharpUseObjectInitializerDiagnosticAnalyzer.cs @@ -1,248 +1,24 @@ // 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; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.UseObjectInitializer; namespace Microsoft.CodeAnalysis.CSharp.UseObjectInitializer { [DiagnosticAnalyzer(LanguageNames.CSharp)] - internal class CSharpUseObjectInitializerDiagnosticAnalyzer : DiagnosticAnalyzer, IBuiltInAnalyzer + internal class CSharpUseObjectInitializerDiagnosticAnalyzer : + AbstractUseObjectInitializerDiagnosticAnalyzer< + SyntaxKind, + ExpressionSyntax, + StatementSyntax, + ObjectCreationExpressionSyntax, + MemberAccessExpressionSyntax, + VariableDeclaratorSyntax> { - private static readonly string Id = IDEDiagnosticIds.UseObjectInitializerDiagnosticId; + protected override SyntaxKind GetObjectCreationSyntaxKind() => SyntaxKind.ObjectCreationExpression; - 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; - HashSet seenNames = 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! - seenNames = seenNames ?? new HashSet(); - matches = matches ?? new List(); - - // If we see an assignment to the same property/field, we can't convert it - // to an initializer. - if (!seenNames.Add(leftMemberAccess.Name.Identifier.ValueText)) - { - break; - } - - 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); - } + protected override ISyntaxFactsService GetSyntaxFactsService() => CSharpSyntaxFactsService.Instance; } - -} +} \ No newline at end of file diff --git a/src/Features/Core/Portable/Features.csproj b/src/Features/Core/Portable/Features.csproj index 61c116276b2..3d9b2f2bae7 100644 --- a/src/Features/Core/Portable/Features.csproj +++ b/src/Features/Core/Portable/Features.csproj @@ -653,6 +653,7 @@ + diff --git a/src/Features/Core/Portable/GenerateMember/AbstractGenerateMemberService.cs b/src/Features/Core/Portable/GenerateMember/AbstractGenerateMemberService.cs index 51f5183354c..12fe9602d95 100644 --- a/src/Features/Core/Portable/GenerateMember/AbstractGenerateMemberService.cs +++ b/src/Features/Core/Portable/GenerateMember/AbstractGenerateMemberService.cs @@ -90,7 +90,7 @@ protected AbstractGenerateMemberService() var syntaxFacts = document.Project.LanguageServices.GetService(); var semanticModel = document.SemanticModel; - var isMemberAccessExpression = syntaxFacts.IsMemberAccessExpression(simpleNameOrMemberAccessExpression); + var isMemberAccessExpression = syntaxFacts.IsSimpleMemberAccessExpression(simpleNameOrMemberAccessExpression); if (isMemberAccessExpression || syntaxFacts.IsConditionalMemberAccessExpression(simpleNameOrMemberAccessExpression)) { diff --git a/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerCodeFixProvider.cs b/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerCodeFixProvider.cs new file mode 100644 index 00000000000..5c5bb044fe6 --- /dev/null +++ b/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerCodeFixProvider.cs @@ -0,0 +1,105 @@ +// 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.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.UseObjectInitializer +{ + internal abstract class AbstractUseObjectInitializerCodeFixProvider< + TExpressionSyntax, + TStatementSyntax, + TObjectCreationExpressionSyntax, + TMemberAccessExpressionSyntax, + TVariableDeclarator> + : CodeFixProvider + where TExpressionSyntax : SyntaxNode + where TObjectCreationExpressionSyntax : TExpressionSyntax + where TMemberAccessExpressionSyntax : TExpressionSyntax + where TStatementSyntax : SyntaxNode + where TVariableDeclarator : SyntaxNode + { + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + 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 = (TObjectCreationExpressionSyntax)root.FindNode(diagnostic.AdditionalLocations[0].SourceSpan); + + var syntaxFacts = document.GetLanguageService(); + var analyzer = new Analyzer( + syntaxFacts, objectCreation); + var matches = analyzer.Analyze(); + + var editor = new SyntaxEditor(root, document.Project.Solution.Workspace); + + editor.ReplaceNode(objectCreation, GetNewObjectCreation( + syntaxFacts, editor.Generator, objectCreation, matches)); + foreach (var match in matches) + { + editor.RemoveNode(match.Statement); + } + + var newRoot = editor.GetChangedRoot(); + return document.WithSyntaxRoot(newRoot); + } + + private TObjectCreationExpressionSyntax GetNewObjectCreation( + ISyntaxFactsService syntaxFacts, + SyntaxGenerator genarator, + TObjectCreationExpressionSyntax objectCreation, + List> matches) + { + var initializer = genarator.ObjectMemberInitializer( + matches.Select(m => CreateNamedFieldInitializer(syntaxFacts, genarator, m))); + + + return (TObjectCreationExpressionSyntax)genarator.WithObjectCreationInitializer(objectCreation, initializer) + .WithAdditionalAnnotations(Formatter.Annotation); + } + + private SyntaxNode CreateNamedFieldInitializer( + ISyntaxFactsService syntaxFacts, + SyntaxGenerator generator, + Match match) + { + return generator.NamedFieldInitializer( + syntaxFacts.GetNameOfMemberAccessExpression(match.MemberAccessExpression), + match.Initializer).WithLeadingTrivia(GetNewLine()); + } + + protected abstract SyntaxTrivia GetNewLine(); + + private class MyCodeAction : CodeAction.DocumentChangeAction + { + public MyCodeAction(Func> createChangedDocument) + : base(FeaturesResources.Object_initialization_can_be_simplified, createChangedDocument) + { + } + } + } +} + diff --git a/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs b/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs index 76d96fec133..44d09874656 100644 --- a/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs @@ -1,56 +1,314 @@ -//// 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 &&) -// } -// } -//} +// 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; + +namespace Microsoft.CodeAnalysis.UseObjectInitializer +{ + internal abstract class AbstractUseObjectInitializerDiagnosticAnalyzer< + TSyntaxKind, + TExpressionSyntax, + TStatementSyntax, + TObjectCreationExpressionSyntax, + TMemberAccessExpressionSyntax, + TVariableDeclarator> + : DiagnosticAnalyzer, IBuiltInAnalyzer + where TSyntaxKind : struct + where TExpressionSyntax : SyntaxNode + where TObjectCreationExpressionSyntax : TExpressionSyntax + where TMemberAccessExpressionSyntax : TExpressionSyntax + where TStatementSyntax : SyntaxNode + where TVariableDeclarator : SyntaxNode + { + 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, GetObjectCreationSyntaxKind()); + } + + protected abstract TSyntaxKind GetObjectCreationSyntaxKind(); + + 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 = (TObjectCreationExpressionSyntax)context.Node; + + var syntaxFacts = GetSyntaxFactsService(); + var analyzer = new Analyzer( + 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(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, + syntaxFacts.GetOperatorTokenOfMemberAccessExpression(match.MemberAccessExpression).Span.End)); + + context.ReportDiagnostic(Diagnostic.Create( + s_unnecessaryWithSuggestionDescriptor, location1, additionalLocations: locations)); + + if (match.Statement.Span.End > match.Initializer.FullSpan.End) + { + context.ReportDiagnostic(Diagnostic.Create( + s_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 + where TExpressionSyntax : SyntaxNode + where TMemberAccessExpressionSyntax : TExpressionSyntax + where TStatementSyntax : SyntaxNode + { + public readonly TStatementSyntax Statement; + public readonly TMemberAccessExpressionSyntax MemberAccessExpression; + public readonly TExpressionSyntax Initializer; + + public Match( + TStatementSyntax statement, + TMemberAccessExpressionSyntax memberAccessExpression, + TExpressionSyntax initializer) + { + Statement = statement; + MemberAccessExpression = memberAccessExpression; + Initializer = initializer; + } + } + + internal struct Analyzer< + TExpressionSyntax, + TStatementSyntax, + TObjectCreationExpressionSyntax, + TMemberAccessExpressionSyntax, + TVariableDeclaratorSyntax> + where TExpressionSyntax : SyntaxNode + where TStatementSyntax : SyntaxNode + where TObjectCreationExpressionSyntax : TExpressionSyntax + where TMemberAccessExpressionSyntax : TExpressionSyntax + 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> Analyze() + { + if (_syntaxFacts.GetObjectCreationInitializer(_objectCreationExpression) != null) + { + // Don't bother if this already has an initializer. + return null; + } + + _containingStatement = _objectCreationExpression.FirstAncestorOrSelf(); + if (_containingStatement == null) + { + return null; + } + + if (!TryInitializeVariableDeclarationCase() && + !TryInitializeAssignmentCase()) + { + return null; + } + + var containingBlock = _containingStatement.Parent; + var foundStatement = false; + + List> matches = null; + HashSet 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 TStatementSyntax; + 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(); + matches = matches ?? new List>(); + + // 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( + 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(); + if (containingDeclarator == null) + { + return false; + } + + if (!_syntaxFacts.IsDeclaratorOfLocalDeclarationStatement(containingDeclarator, _containingStatement)) + { + return false; + } + + _valuePattern = _syntaxFacts.GetIdentifierOfVariableDeclarator(containingDeclarator); + return true; + } + } +} \ No newline at end of file diff --git a/src/VisualStudio/CSharp/Impl/Telemetry/CompilationErrorDetailDiscoverer.cs b/src/VisualStudio/CSharp/Impl/Telemetry/CompilationErrorDetailDiscoverer.cs index 05ffa20dc14..2b526d8701d 100644 --- a/src/VisualStudio/CSharp/Impl/Telemetry/CompilationErrorDetailDiscoverer.cs +++ b/src/VisualStudio/CSharp/Impl/Telemetry/CompilationErrorDetailDiscoverer.cs @@ -192,7 +192,7 @@ public async Task> GetCompilationErrorDetails(Docu } } - if (syntaxFacts.IsMemberAccessExpression(node.Parent)) + if (syntaxFacts.IsSimpleMemberAccessExpression(node.Parent)) { var expression = node.Parent; diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs index 1b1e644829a..35ca3885894 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs @@ -4099,6 +4099,27 @@ internal override SyntaxNode NamedAnonymousObjectMemberDeclarator(SyntaxNode ide (ExpressionSyntax)expression); } + internal override SyntaxNode ObjectMemberInitializer(IEnumerable fieldInitializers) + { + return SyntaxFactory.InitializerExpression( + SyntaxKind.ObjectInitializerExpression, + SyntaxFactory.SeparatedList(fieldInitializers.Cast())); + } + + internal override SyntaxNode NamedFieldInitializer(SyntaxNode name, SyntaxNode value) + { + return SyntaxFactory.AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + (ExpressionSyntax)name, + (ExpressionSyntax)value); + } + + internal override SyntaxNode WithObjectCreationInitializer(SyntaxNode objectCreationExpression, SyntaxNode initializer) + { + return ((ObjectCreationExpressionSyntax)objectCreationExpression) + .WithInitializer((InitializerExpressionSyntax)initializer); + } + #endregion } } diff --git a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs index f5fd896439e..921c5ce1a9d 100644 --- a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs +++ b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs @@ -533,7 +533,7 @@ public bool IsBindableToken(SyntaxToken token) return false; } - public bool IsMemberAccessExpression(SyntaxNode node) + public bool IsSimpleMemberAccessExpression(SyntaxNode node) { return node is MemberAccessExpressionSyntax && ((MemberAccessExpressionSyntax)node).Kind() == SyntaxKind.SimpleMemberAccessExpression; @@ -1775,6 +1775,70 @@ public bool IsDeclaration(SyntaxNode node) newContextNode = newRoot.GetAnnotatedNodes(s_annotation).Single(); } + public SyntaxNode GetObjectCreationInitializer(SyntaxNode objectCreationExpression) + { + return ((ObjectCreationExpressionSyntax)objectCreationExpression).Initializer; + } + + public bool IsSimpleAssignmentStatement(SyntaxNode statement) + { + return statement.IsKind(SyntaxKind.ExpressionStatement) && + ((ExpressionStatementSyntax)statement).Expression.IsKind(SyntaxKind.SimpleAssignmentExpression); + } + + public void GetPartsOfAssignmentStatement(SyntaxNode statement, out SyntaxNode left, out SyntaxNode right) + { + var assignment = (AssignmentExpressionSyntax)((ExpressionStatementSyntax)statement).Expression; + left = assignment.Left; + right = assignment.Right; + } + + public SyntaxNode GetNameOfMemberAccessExpression(SyntaxNode memberAccessExpression) + { + return ((MemberAccessExpressionSyntax)memberAccessExpression).Name; + } + + public SyntaxToken GetOperatorTokenOfMemberAccessExpression(SyntaxNode memberAccessExpression) + { + return ((MemberAccessExpressionSyntax)memberAccessExpression).OperatorToken; + } + + public SyntaxToken GetIdentifierOfSimpleName(SyntaxNode node) + { + return ((SimpleNameSyntax)node).Identifier; + } + + public SyntaxToken GetIdentifierOfVariableDeclarator(SyntaxNode node) + { + return ((VariableDeclaratorSyntax)node).Identifier; + } + + public bool IsIdentifierName(SyntaxNode node) + { + return node.IsKind(SyntaxKind.IdentifierName); + } + + public bool IsLocalDeclarationStatement(SyntaxNode node) + { + return node.IsKind(SyntaxKind.LocalDeclarationStatement); + } + + public bool IsDeclaratorOfLocalDeclarationStatement(SyntaxNode declarator, SyntaxNode localDeclarationStatement) + { + return ((LocalDeclarationStatementSyntax)localDeclarationStatement).Declaration.Variables.Contains( + (VariableDeclaratorSyntax)declarator); + } + + public bool AreEquivalent(SyntaxToken token1, SyntaxToken token2) + { + return SyntaxFactory.AreEquivalent(token1, token2); + } + + public bool AreEquivalent(SyntaxNode node1, SyntaxNode node2) + { + return SyntaxFactory.AreEquivalent(node1, node2); + } + private class AddFirstMissingCloseBaceRewriter: CSharpSyntaxRewriter { private readonly SyntaxNode _contextNode; diff --git a/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs b/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs index 457750b9c63..c5e60681b32 100644 --- a/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs +++ b/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs @@ -118,6 +118,10 @@ public SyntaxNode FieldDeclaration(IFieldSymbol field, SyntaxNode initializer) initializer); } + internal abstract SyntaxNode ObjectMemberInitializer(IEnumerable fieldInitializers); + internal abstract SyntaxNode NamedFieldInitializer(SyntaxNode name, SyntaxNode value); + internal abstract SyntaxNode WithObjectCreationInitializer(SyntaxNode objectCreationExpression, SyntaxNode initializer); + /// /// Creates a method declaration. /// diff --git a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs index d6953b491df..1dc32bd5a2e 100644 --- a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs +++ b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs @@ -49,11 +49,16 @@ internal interface ISyntaxFactsService : ILanguageService bool IsObjectCreationExpressionType(SyntaxNode node); bool IsObjectCreationExpression(SyntaxNode node); + SyntaxNode GetObjectCreationInitializer(SyntaxNode objectCreationExpression); + bool IsInvocationExpression(SyntaxNode node); // Left side of = assignment. bool IsLeftSideOfAssignment(SyntaxNode node); + bool IsSimpleAssignmentStatement(SyntaxNode statement); + void GetPartsOfAssignmentStatement(SyntaxNode statement, out SyntaxNode left, out SyntaxNode right); + // Left side of any assignment (for example *= or += ) bool IsLeftSideOfAnyAssignment(SyntaxNode node); SyntaxNode GetRightHandSideOfAssignment(SyntaxNode node); @@ -66,26 +71,36 @@ internal interface ISyntaxFactsService : ILanguageService SyntaxNode GetRightSideOfDot(SyntaxNode node); bool IsRightSideOfQualifiedName(SyntaxNode node); + bool IsMemberAccessExpressionName(SyntaxNode node); + SyntaxNode GetNameOfMemberAccessExpression(SyntaxNode memberAccessExpression); + SyntaxNode GetExpressionOfMemberAccessExpression(SyntaxNode memberAccessExpression); + SyntaxToken GetOperatorTokenOfMemberAccessExpression(SyntaxNode memberAccessExpression); - bool IsMemberAccessExpression(SyntaxNode node); + bool IsSimpleMemberAccessExpression(SyntaxNode node); bool IsPointerMemberAccessExpression(SyntaxNode node); + bool IsNamedParameter(SyntaxNode node); bool IsSkippedTokensTrivia(SyntaxNode node); - SyntaxNode GetExpressionOfMemberAccessExpression(SyntaxNode node); SyntaxNode GetExpressionOfConditionalMemberAccessExpression(SyntaxNode node); SyntaxNode GetExpressionOfArgument(SyntaxNode node); SyntaxNode GetExpressionOfInterpolation(SyntaxNode node); bool IsConditionalMemberAccessExpression(SyntaxNode node); SyntaxNode GetNameOfAttribute(SyntaxNode node); + SyntaxToken GetIdentifierOfGenericName(SyntaxNode node); + SyntaxToken GetIdentifierOfSimpleName(SyntaxNode node); + SyntaxToken GetIdentifierOfVariableDeclarator(SyntaxNode node); + RefKind GetRefKindOfArgument(SyntaxNode node); void GetNameAndArityOfSimpleName(SyntaxNode node, out string name, out int arity); SyntaxList GetContentsOfInterpolatedString(SyntaxNode interpolatedString); SeparatedSyntaxList GetArgumentsForInvocationExpression(SyntaxNode invocationExpression); bool IsUsingDirectiveName(SyntaxNode node); + + bool IsIdentifierName(SyntaxNode node); bool IsGenericName(SyntaxNode node); bool IsAttribute(SyntaxNode node); @@ -100,6 +115,9 @@ internal interface ISyntaxFactsService : ILanguageService bool IsLockStatement(SyntaxNode node); bool IsUsingStatement(SyntaxNode node); + bool IsLocalDeclarationStatement(SyntaxNode node); + bool IsDeclaratorOfLocalDeclarationStatement(SyntaxNode declator, SyntaxNode localDeclarationStatement); + bool IsThisConstructorInitializer(SyntaxToken token); bool IsBaseConstructorInitializer(SyntaxToken token); bool IsQueryExpression(SyntaxNode node); @@ -135,6 +153,9 @@ internal interface ISyntaxFactsService : ILanguageService bool IsTopLevelNodeWithMembers(SyntaxNode node); bool HasIncompleteParentMember(SyntaxNode node); + bool AreEquivalent(SyntaxToken token1, SyntaxToken token2); + bool AreEquivalent(SyntaxNode node1, SyntaxNode node2); + bool TryGetDeclaredSymbolInfo(SyntaxNode node, out DeclaredSymbolInfo declaredSymbolInfo); string GetDisplayName(SyntaxNode node, DisplayNameOptions options, string rootNamespace = null); @@ -151,11 +172,9 @@ internal interface ISyntaxFactsService : ILanguageService SyntaxNode ConvertToSingleLine(SyntaxNode node, bool useElasticTrivia = false); SyntaxToken ToIdentifierToken(string name); - List GetMethodLevelMembers(SyntaxNode root); bool ContainsInMemberBody(SyntaxNode node, TextSpan span); - int GetMethodLevelMemberId(SyntaxNode root, SyntaxNode node); SyntaxNode GetMethodLevelMember(SyntaxNode root, int memberId); TextSpan GetInactiveRegionSpanAroundPosition(SyntaxTree tree, int position, CancellationToken cancellationToken); @@ -177,7 +196,6 @@ internal interface ISyntaxFactsService : ILanguageService SyntaxNode GetBindableParent(SyntaxToken token); IEnumerable GetConstructors(SyntaxNode root, CancellationToken cancellationToken); - bool TryGetCorrespondingOpenBrace(SyntaxToken token, out SyntaxToken openBrace); /// diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ISyntaxFactsServiceExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ISyntaxFactsServiceExtensions.cs index 51284a1df05..3b81a38a200 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ISyntaxFactsServiceExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ISyntaxFactsServiceExtensions.cs @@ -17,7 +17,7 @@ public static bool IsWord(this ISyntaxFactsService syntaxFacts, SyntaxToken toke public static bool IsAnyMemberAccessExpression( this ISyntaxFactsService syntaxFacts, SyntaxNode node) { - return syntaxFacts.IsMemberAccessExpression(node) || syntaxFacts.IsPointerMemberAccessExpression(node); + return syntaxFacts.IsSimpleMemberAccessExpression(node) || syntaxFacts.IsPointerMemberAccessExpression(node); } } } diff --git a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb index 5b9b36c7a6a..cf09f0e138f 100644 --- a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb +++ b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb @@ -3874,6 +3874,22 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration DirectCast(expression, ExpressionSyntax)) End Function + + Friend Overrides Function ObjectMemberInitializer(fieldInitializers As IEnumerable(Of SyntaxNode)) As SyntaxNode + Return SyntaxFactory.ObjectMemberInitializer( + fieldInitializers.Cast(Of FieldInitializerSyntax).ToArray()) + End Function + + Friend Overrides Function NamedFieldInitializer(name As SyntaxNode, value As SyntaxNode) As SyntaxNode + Return SyntaxFactory.NamedFieldInitializer( + DirectCast(name, IdentifierNameSyntax), + DirectCast(value, ExpressionSyntax)) + End Function + + Friend Overrides Function WithObjectCreationInitializer(objectCreationExpression As SyntaxNode, initializer As SyntaxNode) As SyntaxNode + Return DirectCast(objectCreationExpression, ObjectCreationExpressionSyntax). + WithInitializer(DirectCast(initializer, ObjectCreationInitializerSyntax)) + End Function #End Region End Class diff --git a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb index 40bb23e1d02..c53184b6636 100644 --- a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb +++ b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb @@ -436,7 +436,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Me.IsOperator(token) End Function - Public Function IsMemberAccessExpression(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsMemberAccessExpression + Public Function IsSimpleMemberAccessExpression(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsSimpleMemberAccessExpression Return TypeOf node Is MemberAccessExpressionSyntax AndAlso DirectCast(node, MemberAccessExpressionSyntax).Kind = SyntaxKind.SimpleMemberAccessExpression End Function @@ -1463,5 +1463,56 @@ Namespace Microsoft.CodeAnalysis.VisualBasic newRoot = root newContextNode = contextNode End Sub + + Public Function GetObjectCreationInitializer(objectCreationExpression As SyntaxNode) As SyntaxNode Implements ISyntaxFactsService.GetObjectCreationInitializer + Return DirectCast(objectCreationExpression, ObjectCreationExpressionSyntax).Initializer + End Function + + Public Function IsSimpleAssignmentStatement(statement As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsSimpleAssignmentStatement + Return statement.IsKind(SyntaxKind.SimpleAssignmentStatement) + End Function + + Public Sub GetPartsOfAssignmentStatement(statement As SyntaxNode, ByRef left As SyntaxNode, ByRef right As SyntaxNode) Implements ISyntaxFactsService.GetPartsOfAssignmentStatement + Dim assignment = DirectCast(statement, AssignmentStatementSyntax) + left = assignment.Left + right = assignment.Right + End Sub + + Public Function GetNameOfMemberAccessExpression(memberAccessExpression As SyntaxNode) As SyntaxNode Implements ISyntaxFactsService.GetNameOfMemberAccessExpression + Return DirectCast(memberAccessExpression, MemberAccessExpressionSyntax).Name + End Function + + Public Function GetOperatorTokenOfMemberAccessExpression(memberAccessExpression As SyntaxNode) As SyntaxToken Implements ISyntaxFactsService.GetOperatorTokenOfMemberAccessExpression + Return DirectCast(memberAccessExpression, MemberAccessExpressionSyntax).OperatorToken + End Function + + Public Function GetIdentifierOfSimpleName(node As SyntaxNode) As SyntaxToken Implements ISyntaxFactsService.GetIdentifierOfSimpleName + Return DirectCast(node, SimpleNameSyntax).Identifier + End Function + + Public Function GetIdentifierOfVariableDeclarator(node As SyntaxNode) As SyntaxToken Implements ISyntaxFactsService.GetIdentifierOfVariableDeclarator + Return DirectCast(node, VariableDeclaratorSyntax).Names.Last().Identifier + End Function + + Public Function IsIdentifierName(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsIdentifierName + Return node.IsKind(SyntaxKind.IdentifierName) + End Function + + Public Function IsLocalDeclarationStatement(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsLocalDeclarationStatement + Return node.IsKind(SyntaxKind.LocalDeclarationStatement) + End Function + + Public Function IsDeclaratorOfLocalDeclarationStatement(declarator As SyntaxNode, localDeclarationStatement As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsDeclaratorOfLocalDeclarationStatement + Return DirectCast(localDeclarationStatement, LocalDeclarationStatementSyntax).Declarators. + Contains(DirectCast(declarator, VariableDeclaratorSyntax)) + End Function + + Public Function AreEquivalent(token1 As SyntaxToken, token2 As SyntaxToken) As Boolean Implements ISyntaxFactsService.AreEquivalent + Return SyntaxFactory.AreEquivalent(token1, token2) + End Function + + Public Function AreEquivalent(node1 As SyntaxNode, node2 As SyntaxNode) As Boolean Implements ISyntaxFactsService.AreEquivalent + Return SyntaxFactory.AreEquivalent(node1, node2) + End Function End Class End Namespace \ No newline at end of file -- GitLab