forked from neo-project/neo-devpack-dotnet
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add: add neo smart contract syntax analyzer (neo-project#839)
* 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
Showing
46 changed files
with
3,795 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Submodule neo
updated
11 files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
26
src/Neo.SmartContract.Analyzer/AnalyzerReleases.Unshipped.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
113
src/Neo.SmartContract.Analyzer/AnonymousFunctionUsageAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
95
src/Neo.SmartContract.Analyzer/BigIntegerCreationAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
Oops, something went wrong.