Skip to content

Commit 1a4c35b

Browse files
Jim8yajara87shargonWi1l-B0t
authored
Feature: run Neo analyzers during nccs compilation (#1905)
* Feature: run Neo analyzers during nccs compilation * Fix: preserve C# diagnostics with analyzer failures * Discover Neo analyzers by reflection --------- Co-authored-by: Alvaro <amjarag@gmail.com> Co-authored-by: Shargon <shargon@gmail.com> Co-authored-by: Will <201105916+Wi1l-B0t@users.noreply.github.com>
1 parent 1fa8625 commit 1a4c35b

15 files changed

Lines changed: 349 additions & 4 deletions

File tree

src/Neo.Compiler.CSharp/CompilationEngine/CompilationContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ private static void ValidateContractPermission(INamedTypeSymbol symbol, string c
165165

166166
internal void Compile()
167167
{
168+
_diagnostics.AddRange(_engine.AnalyzerDiagnostics);
169+
168170
HashSet<INamedTypeSymbol> processed = new(SymbolEqualityComparer.Default);
169171
foreach (SyntaxTree tree in _engine.Compilation!.SyntaxTrees)
170172
{

src/Neo.Compiler.CSharp/CompilationEngine/CompilationEngine.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414
using Microsoft.CodeAnalysis;
1515
using Microsoft.CodeAnalysis.CSharp;
1616
using Microsoft.CodeAnalysis.CSharp.Syntax;
17+
using Microsoft.CodeAnalysis.Diagnostics;
1718
using Neo.Json;
19+
using Neo.SmartContract.Analyzer;
1820
using System;
1921
using System.Collections.Concurrent;
2022
using System.Collections.Generic;
23+
using System.Collections.Immutable;
2124
using System.ComponentModel;
2225
using System.Diagnostics;
2326
using System.IO;
@@ -34,8 +37,10 @@ public class CompilationEngine(CompilationOptions options)
3437
{
3538
internal Compilation? Compilation;
3639
internal MetadataReference? FrameworkReference;
40+
internal ImmutableArray<Diagnostic> AnalyzerDiagnostics { get; private set; } = [];
3741
internal CompilationOptions Options { get; private set; } = options;
3842
private static readonly MetadataReference[] CommonReferences;
43+
private static readonly ImmutableArray<DiagnosticAnalyzer> NeoAnalyzers = NeoAnalyzerSuite.Create();
3944
private static readonly Guid FrameworkModuleVersionId = typeof(scfx::Neo.SmartContract.Framework.Attributes.SafeAttribute).Assembly.ManifestModule.ModuleVersionId;
4045
private const string FrameworkAssemblyName = "Neo.SmartContract.Framework";
4146
private static readonly StringComparison ProjectPathComparison = OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
@@ -154,6 +159,7 @@ internal List<CompilationContext> Compile(IEnumerable<string> sourceFiles, IEnum
154159

155160
FrameworkReference = frameworkReference ?? referenceArray.FirstOrDefault(IsTrustedFrameworkReference);
156161
Compilation = CSharpCompilation.Create(null, syntaxTrees, referenceArray, compilationOptions);
162+
AnalyzeCompilation();
157163
return CompileProjectContracts(Compilation);
158164
}
159165

@@ -224,6 +230,7 @@ private Compilation LoadProjectCompilation(string csproj, bool forceReload)
224230
ResetProjectState();
225231
Compilation = GetCompilation(projectPath);
226232
FrameworkReference = ResolveProjectFrameworkReference(Compilation);
233+
AnalyzeCompilation();
227234
ProjectPath = projectPath;
228235
return Compilation;
229236
}
@@ -232,6 +239,7 @@ private void ResetProjectState()
232239
{
233240
Compilation = null;
234241
FrameworkReference = null;
242+
AnalyzerDiagnostics = [];
235243
MetaReferences.Clear();
236244
PreparedProjectPath = null;
237245
ProjectPath = null;
@@ -272,6 +280,27 @@ internal static bool IsTrustedFrameworkReference(MetadataReference reference)
272280
}
273281
}
274282

283+
private void AnalyzeCompilation()
284+
{
285+
if (!Options.RunAnalyzers)
286+
{
287+
AnalyzerDiagnostics = [];
288+
return;
289+
}
290+
291+
AnalyzerDiagnostics = Compilation!
292+
.WithAnalyzers(NeoAnalyzers)
293+
.GetAnalyzerDiagnosticsAsync()
294+
.GetAwaiter()
295+
.GetResult()
296+
.Where(diagnostic => diagnostic.Severity != DiagnosticSeverity.Hidden)
297+
.OrderBy(diagnostic => diagnostic.Location.SourceTree?.FilePath ?? string.Empty, StringComparer.Ordinal)
298+
.ThenBy(diagnostic => diagnostic.Location.IsInSource ? diagnostic.Location.SourceSpan.Start : -1)
299+
.ThenBy(diagnostic => diagnostic.Id, StringComparer.Ordinal)
300+
.ThenBy(diagnostic => diagnostic.GetMessage(), StringComparer.Ordinal)
301+
.ToImmutableArray();
302+
}
303+
275304
public List<CompilationContext> CompileProject(string csproj, List<INamedTypeSymbol> sortedClasses, Dictionary<INamedTypeSymbol, List<INamedTypeSymbol>> classDependencies, List<INamedTypeSymbol?> allClassSymbols, string? targetContractName = null)
276305
{
277306
if (sortedClasses == null || classDependencies == null || allClassSymbols == null)

src/Neo.Compiler.CSharp/CompilationEngine/CompilationOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ public enum DebugType : byte
5858
private CSharpParseOptions? parseOptions = null;
5959
public bool SkipRestoreIfAssetsPresent { get; set; }
6060

61+
/// <summary>
62+
/// Gets or sets whether the Neo contract analyzer suite runs before contract conversion.
63+
/// </summary>
64+
public bool RunAnalyzers { get; set; }
65+
6166
public CSharpParseOptions GetParseOptions()
6267
{
6368
if (parseOptions is null)

src/Neo.Compiler.CSharp/Neo.Compiler.CSharp.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
</ItemGroup>
3535

3636
<ItemGroup>
37+
<ProjectReference Include="..\Neo.SmartContract.Analyzer\Neo.SmartContract.Analyzer.csproj" />
3738
<ProjectReference Include="..\Neo.SmartContract.Framework\Neo.SmartContract.Framework.csproj">
3839
<Aliases>scfx</Aliases>
3940
</ProjectReference>

src/Neo.Compiler.CSharp/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,8 @@ public static int Main(string[] args)
174174
NoInline = parseResult.GetValue(noInlineOption),
175175
AddressVersion = parseResult.GetValue(addressVersionOption),
176176
PrintAbi = parseResult.GetValue(printAbiOption),
177-
Debug = parseResult.GetValue(debugOption)
177+
Debug = parseResult.GetValue(debugOption),
178+
RunAnalyzers = true
178179
};
179180
return Handle(rootCommand, options, parseResult.GetValue(pathsArgument));
180181
});

src/Neo.Compiler.CSharp/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ The official C# compiler for Neo smart contracts. This compiler transforms C# co
55
## Features
66

77
- **C# to NeoVM Compilation**: Compile standard C# code to NeoVM bytecode
8+
- **Contract Diagnostics**: Run the Neo contract analyzer suite before bytecode generation
89
- **Smart Contract Support**: Full support for Neo smart contract development
910
- **Optimization**: Multiple optimization levels for bytecode efficiency
1011
- **Debug Information**: Generate debug info for testing and debugging

src/Neo.SmartContract.Analyzer/Neo.SmartContract.Analyzer.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2424
</PackageReference>
2525
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
26-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.14.0" />
26+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.14.0" PrivateAssets="all" />
2727
</ItemGroup>
2828

2929
<ItemGroup>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (C) 2015-2026 The Neo Project.
2+
//
3+
// NeoAnalyzerSuite.cs file belongs to the neo project and is free
4+
// software distributed under the MIT software license, see the
5+
// accompanying file LICENSE in the main directory of the
6+
// repository or http://www.opensource.org/licenses/mit-license.php
7+
// for more details.
8+
//
9+
// Redistribution and use in source and binary forms with or without
10+
// modifications are permitted.
11+
12+
using Microsoft.CodeAnalysis.Diagnostics;
13+
using System;
14+
using System.Collections.Generic;
15+
using System.Collections.Immutable;
16+
using System.Linq;
17+
using System.Reflection;
18+
19+
namespace Neo.SmartContract.Analyzer;
20+
21+
public static class NeoAnalyzerSuite
22+
{
23+
/// <summary>
24+
/// Creates the complete set of Neo smart contract analyzers.
25+
/// </summary>
26+
public static ImmutableArray<DiagnosticAnalyzer> Create() =>
27+
GetLoadableTypes()
28+
.Where(type => !type.IsAbstract && typeof(DiagnosticAnalyzer).IsAssignableFrom(type))
29+
.OrderBy(type => type.FullName, StringComparer.Ordinal)
30+
.Select(type => (DiagnosticAnalyzer)Activator.CreateInstance(type)!)
31+
.ToImmutableArray();
32+
33+
private static IEnumerable<Type> GetLoadableTypes()
34+
{
35+
try
36+
{
37+
return typeof(NeoAnalyzerSuite).Assembly.GetTypes();
38+
}
39+
catch (ReflectionTypeLoadException exception)
40+
{
41+
return exception.Types.OfType<Type>();
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)