Skip to content

Commit

Permalink
Add: add neo smart contract syntax analyzer (neo-project#839)
Browse files Browse the repository at this point in the history
* add neo smart contract syntax analyzer

* update ID

* formar

* fix errors of id

* fix unused

* update readme

* fix path, namespace, and float type check

* update diable warning

* diable warning for code fix

* remove nowarn as it also diables the error highlight

* don't run on github CI

* do not compile analyzer

* fix build

* add more analyzer and also add code fix for multiple analyzers

* add out keyword code fix

* fix float double and out keyword

* verify name conflict, showing confilct

* check notify name

* Update src/Neo.SmartContract.Analyzer/CollectionTypesUsageAnalyzer.cs

Co-authored-by: Shargon <[email protected]>

* Update src/Neo.SmartContract.Analyzer/CharMethodsUsageAnalyzer.cs

Co-authored-by: Shargon <[email protected]>

* Optimization and clean code

* remove sample from solution build

* also remove from release

* add analyzer to `_deploy` and `_initial`

* fix double detection

* also ban decimal usage.

* add NEPStandard analyzer

* use framework NEP

* Fix errors in Examples.cs

* fix analyzer and add unit tests

* delete examples as we have UT now

* fix standards and add ut

* fix analyzer and add UT

* remove keywords fix and update UT

* biginteger using analyzer and tests

* initialize analyzer and unit test

* initialize analyzer and unit test

* use analyzer in examples

---------

Co-authored-by: Shargon <[email protected]>
  • Loading branch information
Jim8y and shargon authored Mar 23, 2024
1 parent a47fb44 commit 905246d
Show file tree
Hide file tree
Showing 46 changed files with 3,795 additions and 5 deletions.
2 changes: 2 additions & 0 deletions examples/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Neo.SmartContract.Framework\Neo.SmartContract.Framework.csproj" />
<ProjectReference Include="..\..\src\Neo.Compiler.CSharp\Neo.Compiler.CSharp.csproj" PrivateAssets="All"/>
<ProjectReference Include="..\..\src\Neo.SmartContract.Analyzer\Neo.SmartContract.Analyzer.csproj"
OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup>

<Target Name="ExecuteBeforeBuild" BeforeTargets="PreBuildEvent">
Expand Down
22 changes: 18 additions & 4 deletions neo-devpack-dotnet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{79389FC0-C62
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{D5266066-0AFD-44D5-A83E-2F73668A63C8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.SmartContract.Analyzer", "src\Neo.SmartContract.Analyzer\Neo.SmartContract.Analyzer.csproj", "{F5862E0E-C8ED-4B99-B353-E1F35D86D4EF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Compiler.CSharp.TestContracts", "tests\Neo.Compiler.CSharp.TestContracts\Neo.Compiler.CSharp.TestContracts.csproj", "{8D67DD5A-D683-481F-915E-98683EA38791}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.SmartContract.Framework.TestContracts", "tests\Neo.SmartContract.Framework.TestContracts\Neo.SmartContract.Framework.TestContracts.csproj", "{A372F1D6-51FF-472C-9508-FDAF7E6FEB13}"
Expand Down Expand Up @@ -68,6 +70,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.SmartContract.Trans
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.SmartContract.ZKP", "examples\Example.SmartContract.ZKP\Example.SmartContract.ZKP.csproj", "{141AD5BA-1735-4583-93B6-145CF72721E5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.SmartContract.Analyzer.UnitTests", "tests\Neo.SmartContract.Analyzer.UnitTests\Neo.SmartContract.Analyzer.UnitTests.csproj", "{F30E2375-012A-4A38-985B-31CB7DBA4D28}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -94,6 +98,10 @@ Global
{93BEC5CC-BAFF-4389-89E7-84AAFF5D495D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{93BEC5CC-BAFF-4389-89E7-84AAFF5D495D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{93BEC5CC-BAFF-4389-89E7-84AAFF5D495D}.Release|Any CPU.Build.0 = Release|Any CPU
{F5862E0E-C8ED-4B99-B353-E1F35D86D4EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F5862E0E-C8ED-4B99-B353-E1F35D86D4EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F5862E0E-C8ED-4B99-B353-E1F35D86D4EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F5862E0E-C8ED-4B99-B353-E1F35D86D4EF}.Release|Any CPU.Build.0 = Release|Any CPU
{8D67DD5A-D683-481F-915E-98683EA38791}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8D67DD5A-D683-481F-915E-98683EA38791}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D67DD5A-D683-481F-915E-98683EA38791}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -190,6 +198,10 @@ Global
{141AD5BA-1735-4583-93B6-145CF72721E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{141AD5BA-1735-4583-93B6-145CF72721E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{141AD5BA-1735-4583-93B6-145CF72721E5}.Release|Any CPU.Build.0 = Release|Any CPU
{F30E2375-012A-4A38-985B-31CB7DBA4D28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F30E2375-012A-4A38-985B-31CB7DBA4D28}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F30E2375-012A-4A38-985B-31CB7DBA4D28}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F30E2375-012A-4A38-985B-31CB7DBA4D28}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -200,13 +212,10 @@ Global
{FC0CAF5A-30A7-4857-B1A4-486FAAA39E5A} = {79389FC0-C621-4CEA-AD2B-6074C32E7BCA}
{4C6B120B-99B5-4888-B8D5-45031458DD07} = {D5266066-0AFD-44D5-A83E-2F73668A63C8}
{93BEC5CC-BAFF-4389-89E7-84AAFF5D495D} = {D5266066-0AFD-44D5-A83E-2F73668A63C8}
{F5862E0E-C8ED-4B99-B353-E1F35D86D4EF} = {79389FC0-C621-4CEA-AD2B-6074C32E7BCA}
{8D67DD5A-D683-481F-915E-98683EA38791} = {D5266066-0AFD-44D5-A83E-2F73668A63C8}
{A372F1D6-51FF-472C-9508-FDAF7E6FEB13} = {D5266066-0AFD-44D5-A83E-2F73668A63C8}
{D0153204-6AEF-4D94-B0E1-8124C38C91D4} = {D5266066-0AFD-44D5-A83E-2F73668A63C8}
{73223FBD-C562-4FA0-9722-C7F1C382A9DE} = {49D5873D-7B38-48A5-B853-85146F032091}
{D541BCE9-65BC-475B-94E5-19B6BFFF2B8E} = {49D5873D-7B38-48A5-B853-85146F032091}
{35A34EBD-F2BF-4D83-A096-D5F007B12732} = {49D5873D-7B38-48A5-B853-85146F032091}
{D6D53889-5A10-46A4-BA66-E78B56EC1881} = {49D5873D-7B38-48A5-B853-85146F032091}
{648DCE6F-A0BA-4032-951B-20CF5BBFD998} = {79389FC0-C621-4CEA-AD2B-6074C32E7BCA}
{B772B8A9-9362-4C6F-A6D3-2A4138439B2C} = {D5266066-0AFD-44D5-A83E-2F73668A63C8}
{17F45E0B-AB1C-4796-8C99-E5212A5592F8} = {D5266066-0AFD-44D5-A83E-2F73668A63C8}
Expand All @@ -224,6 +233,11 @@ Global
{18A5C205-2102-4F4B-BB76-37660F74709C} = {C10F9957-077F-42C8-B350-8607D4899B7E}
{5319BE3E-A5E9-4AFF-94D6-A669DA214F24} = {C10F9957-077F-42C8-B350-8607D4899B7E}
{141AD5BA-1735-4583-93B6-145CF72721E5} = {C10F9957-077F-42C8-B350-8607D4899B7E}
{F30E2375-012A-4A38-985B-31CB7DBA4D28} = {D5266066-0AFD-44D5-A83E-2F73668A63C8}
{35A34EBD-F2BF-4D83-A096-D5F007B12732} = {49D5873D-7B38-48A5-B853-85146F032091}
{73223FBD-C562-4FA0-9722-C7F1C382A9DE} = {49D5873D-7B38-48A5-B853-85146F032091}
{D6D53889-5A10-46A4-BA66-E78B56EC1881} = {49D5873D-7B38-48A5-B853-85146F032091}
{D541BCE9-65BC-475B-94E5-19B6BFFF2B8E} = {49D5873D-7B38-48A5-B853-85146F032091}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6DA935E1-C674-4364-B087-F1B511B79215}
Expand Down
6 changes: 6 additions & 0 deletions src/Neo.SmartContract.Analyzer/AnalyzerReleases.Shipped.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## Release 1.0

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|------------------------------------------------
26 changes: 26 additions & 0 deletions src/Neo.SmartContract.Analyzer/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
NC4002 | Type | Error | FloatUsageAnalyzer
NC4003 | Type | Error | DecimalUsageAnalyzer
NC4004 | Type | Error | DoubleUsageAnalyzer
NC4005 | Method | Error | SystemMathUsageAnalyzer
NC4006 | Method | Error | BigIntegerUsageAnalyzer
NC4007 | Method | Error | StringMethodUsageAnalyzer
NC4008 | Usage | Error | BigIntegerCreationAnalyzer
NC4009 | Usage | Error | OutKeywordUsageAnalyzer
NC4010 | Usage | Warning | RefKeywordUsageAnalyzer
NC4011 | Usage | Error | LinqUsageAnalyzer
NC4012 | Method | Error | CharMethodsUsageAnalyzer
NC4013 | Type | Error | CollectionTypesUsageAnalyzer
NC4014 | Usage | Warning | VolatileKeywordUsageAnalyzer
NC4015 | Usage | Error | KeywordUsageAnalyzer
NC4016 | Usage | Error | AnonymousFunctionUsageAnalyzer
NC4017 | Usage | Error | BanCastMethodAnalyzer
NC4018 | Naming | Error | SmartContractMethodNamingAnalyzer
NC4019 | Usage | Error | NotifyEventNameAnalyzer
NC4020 | Naming | Warning | SmartContractMethodNamingAnalyzerUnderline
NC4021 | Usage | Warning | SupportedStandardsAnalyzer
NC4022 | Usage | Warning | BigIntegerUsingUsageAnalyzer
NC4023 | Usage | Error | StaticFieldInitializationAnalyzer
113 changes: 113 additions & 0 deletions src/Neo.SmartContract.Analyzer/AnonymousFunctionUsageAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Neo.SmartContract.Analyzer
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class AnonymousFunctionUsageAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "NC4016";
private static readonly string Title = "Anonymous Function usage";
private static readonly string MessageFormat = "Use of anonymous function is not allowed";
private static readonly string Description = "Anonymous functions are restricted in this project.";
private const string Category = "Usage";

private static readonly DiagnosticDescriptor Rule = new(
DiagnosticId,
Title,
MessageFormat,
Category,
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: Description);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.AnonymousMethodExpression, SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression);
}

private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
if (context.Node is not AnonymousMethodExpressionSyntax && context.Node is not LambdaExpressionSyntax) return;
var diagnostic = Diagnostic.Create(Rule, context.Node.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AnonymousFunctionToPrivateMethodCodeFixProvider))]
public class AnonymousFunctionToPrivateMethodCodeFixProvider : CodeFixProvider
{
// Generate a unique method name
private readonly string methodName = "GeneratedMethod";

public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(AnonymousFunctionUsageAnalyzer.DiagnosticId);

public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

var diagnostic = context.Diagnostics.First();
var diagnosticSpan = diagnostic.Location.SourceSpan;

var anonymousFunction = root?.FindToken(diagnosticSpan.Start).Parent?.AncestorsAndSelf().OfType<AnonymousFunctionExpressionSyntax>().First();
if (anonymousFunction is null) return;

context.RegisterCodeFix(
CodeAction.Create(
title: "Convert to private method",
createChangedDocument: c => ConvertToPrivateMethodAsync(context.Document, anonymousFunction, c),
equivalenceKey: "Convert to private method"),
diagnostic);
}

private async Task<Document> ConvertToPrivateMethodAsync(Document document, AnonymousFunctionExpressionSyntax anonymousFunction, CancellationToken cancellationToken)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken);

// Determine the return type and parameters of the lambda expression
var symbolInfo = semanticModel.GetSymbolInfo(anonymousFunction, cancellationToken);
if (symbolInfo.Symbol is not IMethodSymbol delegateType) return document;

var returnType = delegateType.ReturnType;
var parameters = delegateType.Parameters.Select(p =>
SyntaxFactory.Parameter(SyntaxFactory.Identifier(p.Name)).WithType(SyntaxFactory.ParseTypeName(p.Type.ToDisplayString())));

// Create a new method declaration
var newMethod = SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName(returnType.ToDisplayString()), methodName)
.WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)))
.WithParameterList(SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(parameters)))
.WithBody(anonymousFunction.Body as BlockSyntax ?? SyntaxFactory.Block());

// Insert the new method at the end of the class
var classDeclaration = anonymousFunction.FirstAncestorOrSelf<ClassDeclarationSyntax>();
if (classDeclaration is null) return document;

var editor = new SyntaxEditor(await document.GetSyntaxRootAsync(cancellationToken), document.Project.Solution.Workspace);
editor.InsertAfter(classDeclaration.Members.Last(), newMethod);

// Replace the anonymous function with a call to the new method
var invocationExpression = SyntaxFactory.InvocationExpression(SyntaxFactory.IdentifierName(methodName))
.WithArgumentList(SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(
delegateType.Parameters.Select(p => SyntaxFactory.Argument(SyntaxFactory.IdentifierName(p.Name))))));
editor.ReplaceNode(anonymousFunction, invocationExpression);

var newRoot = editor.GetChangedRoot();
return document.WithSyntaxRoot(newRoot);
}
}
}
47 changes: 47 additions & 0 deletions src/Neo.SmartContract.Analyzer/BanCastMethodAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;

namespace Neo.SmartContract.Analyzer
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class BanCastMethodAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "NC4017";
private const string Title = "Do not use .Cast<T>() method";
private const string MessageFormat = "Use of .Cast<T>() method is not allowed";
private const string Description = "Replace .Cast<T>() with direct type casting.";
private const string Category = "Usage";

private static readonly DiagnosticDescriptor Rule = new(
DiagnosticId,
Title,
MessageFormat,
Category,
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: Description);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InvocationExpression);
}

private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var invocationExpr = (InvocationExpressionSyntax)context.Node;
if (invocationExpr.Expression is MemberAccessExpressionSyntax memberAccessExpr &&
memberAccessExpr.Name.Identifier.ValueText == "Cast" &&
memberAccessExpr.Expression != null)
{
context.ReportDiagnostic(Diagnostic.Create(Rule, memberAccessExpr.Name.GetLocation()));
}
}
}
}
95 changes: 95 additions & 0 deletions src/Neo.SmartContract.Analyzer/BigIntegerCreationAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Neo.SmartContract.Analyzer
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class BigIntegerCreationAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "NC4008";

private static readonly DiagnosticDescriptor Rule = new(
DiagnosticId,
"Use of BigInteger constructor",
"Use of new BigInteger(int) is not allowed, please use BigInteger x = 0;",
"Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
=> ImmutableArray.Create(Rule);

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, SyntaxKind.ObjectCreationExpression);
}

private void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context)
{
var objectCreationExpression = (ObjectCreationExpressionSyntax)context.Node;

// Check if it is a BigInteger creation
if (context.SemanticModel.GetTypeInfo(objectCreationExpression).Type?.ToString() == "System.Numerics.BigInteger" &&
objectCreationExpression.ArgumentList?.Arguments.Count == 1 &&
context.SemanticModel.GetTypeInfo(objectCreationExpression.ArgumentList.Arguments[0].Expression).Type?.SpecialType == SpecialType.System_Int32)
{
var diagnostic = Diagnostic.Create(Rule, objectCreationExpression.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
}

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(BigIntegerCreationCodeFixProvider)), Shared]
public class BigIntegerCreationCodeFixProvider : CodeFixProvider
{
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(BigIntegerCreationAnalyzer.DiagnosticId);

public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var diagnostic = context.Diagnostics.First();
var diagnosticSpan = diagnostic.Location.SourceSpan;

var declaration = root?.FindToken(diagnosticSpan.Start).Parent?.AncestorsAndSelf().OfType<ObjectCreationExpressionSyntax>().First();
if (declaration is null) return;

context.RegisterCodeFix(
CodeAction.Create(
title: "Replace with direct assignment",
createChangedDocument: c => ReplaceWithDirectAssignment(context.Document, declaration, c),
equivalenceKey: "Replace with direct assignment"),
diagnostic);
}

private static async Task<Document> ReplaceWithDirectAssignment(Document document, ObjectCreationExpressionSyntax objectCreation, CancellationToken cancellationToken)
{
var argument = objectCreation.ArgumentList?.Arguments.First().Expression;
if (argument is null) return document;

var newExpression = SyntaxFactory.ParseExpression(argument.ToString())
.WithLeadingTrivia(objectCreation.GetLeadingTrivia())
.WithTrailingTrivia(objectCreation.GetTrailingTrivia());

var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var editor = new SyntaxEditor(root, document.Project.Solution.Workspace);
editor.ReplaceNode(objectCreation, newExpression);

var newRoot = editor.GetChangedRoot();
return document.WithSyntaxRoot(newRoot);
}
}
}
Loading

0 comments on commit 905246d

Please sign in to comment.