Skip to content

Commit c25a4d5

Browse files
committed
Merge branch 'v2_develop' into v2_4050-Activate
2 parents b7a7f1c + 62fd462 commit c25a4d5

15 files changed

+700
-4
lines changed

Directory.Packages.props

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,20 @@
55
<ItemGroup>
66
<!-- Enable Nuget Source Link for github -->
77
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
8+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.11.0" />
9+
<PackageVersion Include="Microsoft.CodeAnalysis.Features" Version="4.11.0" />
10+
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic.Workspaces" Version="4.11.0" />
11+
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.11.0" />
12+
<PackageVersion Include="Microsoft.Net.Compilers.Toolset" Version="4.11.0" />
813
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="[8,9)" />
914
<PackageVersion Include="ColorHelper" Version="[1.8.1,2)" />
1015
<PackageVersion Include="JetBrains.Annotations" Version="[2024.3.0,)" />
11-
<PackageVersion Include="Microsoft.CodeAnalysis" Version="[4.11,4.12)" />
12-
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="[4.11,4.12)" />
16+
<PackageVersion Include="Microsoft.CodeAnalysis" Version="4.11.0" />
17+
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.11.0" />
1318
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" />
19+
20+
<PackageVersion Include="Microsoft.Net.Compilers.Toolset" Version="4.11.0" />
21+
1422
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="[9.0.2,10)" />
1523
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.6" />
1624
<PackageVersion Include="System.IO.Abstractions" Version="[22.0.11,23)" />
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using Terminal.Gui.Input;
2+
using Terminal.Gui.Views;
3+
4+
namespace Terminal.Gui.Analyzers.Tests;
5+
6+
public class HandledEventArgsAnalyzerTests
7+
{
8+
[Theory]
9+
[InlineData("e")]
10+
[InlineData ("args")]
11+
public async Task Should_ReportDiagnostic_When_EHandledNotSet_Lambda (string paramName)
12+
{
13+
var originalCode = $$"""
14+
using Terminal.Gui.Views;
15+
16+
class TestClass
17+
{
18+
void Setup()
19+
{
20+
var b = new Button();
21+
b.Accepting += (s, {{paramName}}) =>
22+
{
23+
// Forgot {{paramName}}.Handled = true;
24+
};
25+
}
26+
}
27+
""";
28+
await new ProjectBuilder ()
29+
.WithSourceCode (originalCode)
30+
.WithAnalyzer (new HandledEventArgsAnalyzer ())
31+
.ValidateAsync ();
32+
}
33+
34+
[Theory]
35+
[InlineData ("e")]
36+
[InlineData ("args")]
37+
public async Task Should_ReportDiagnostic_When_EHandledNotSet_Method (string paramName)
38+
{
39+
var originalCode = $$"""
40+
using Terminal.Gui.Views;
41+
using Terminal.Gui.Input;
42+
43+
class TestClass
44+
{
45+
void Setup()
46+
{
47+
var b = new Button();
48+
b.Accepting += BOnAccepting;
49+
}
50+
private void BOnAccepting (object? sender, CommandEventArgs {{paramName}})
51+
{
52+
53+
}
54+
}
55+
""";
56+
await new ProjectBuilder ()
57+
.WithSourceCode (originalCode)
58+
.WithAnalyzer (new HandledEventArgsAnalyzer ())
59+
.ValidateAsync ();
60+
}
61+
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp;
3+
using Microsoft.CodeAnalysis.Text;
4+
using Microsoft.CodeAnalysis.CodeFixes;
5+
using Microsoft.CodeAnalysis.Diagnostics;
6+
using System.Collections.Immutable;
7+
using System.Collections.ObjectModel;
8+
using System.ComponentModel;
9+
using System.Drawing;
10+
using Microsoft.CodeAnalysis.CodeActions;
11+
using Terminal.Gui.ViewBase;
12+
using Terminal.Gui.Views;
13+
using Document = Microsoft.CodeAnalysis.Document;
14+
using Formatter = Microsoft.CodeAnalysis.Formatting.Formatter;
15+
using System.Reflection;
16+
using JetBrains.Annotations;
17+
18+
public sealed class ProjectBuilder
19+
{
20+
private string _sourceCode;
21+
private string _expectedFixedCode;
22+
private DiagnosticAnalyzer _analyzer;
23+
private CodeFixProvider _codeFix;
24+
25+
public ProjectBuilder WithSourceCode (string source)
26+
{
27+
_sourceCode = source;
28+
return this;
29+
}
30+
31+
public ProjectBuilder ShouldFixCodeWith (string expected)
32+
{
33+
_expectedFixedCode = expected;
34+
return this;
35+
}
36+
37+
public ProjectBuilder WithAnalyzer (DiagnosticAnalyzer analyzer)
38+
{
39+
_analyzer = analyzer;
40+
return this;
41+
}
42+
43+
public ProjectBuilder WithCodeFix (CodeFixProvider codeFix)
44+
{
45+
_codeFix = codeFix;
46+
return this;
47+
}
48+
49+
public async Task ValidateAsync ()
50+
{
51+
if (_sourceCode == null)
52+
{
53+
throw new InvalidOperationException ("Source code not set.");
54+
}
55+
56+
if (_analyzer == null)
57+
{
58+
throw new InvalidOperationException ("Analyzer not set.");
59+
}
60+
61+
// Parse original document
62+
var document = CreateDocument (_sourceCode);
63+
var compilation = await document.Project.GetCompilationAsync ();
64+
65+
var diagnostics = compilation.GetDiagnostics ();
66+
var errors = diagnostics.Where (d => d.Severity == DiagnosticSeverity.Error);
67+
68+
if (errors.Any ())
69+
{
70+
var errorMessages = string.Join (Environment.NewLine, errors.Select (e => e.ToString ()));
71+
throw new Exception ("Compilation failed with errors:" + Environment.NewLine + errorMessages);
72+
}
73+
74+
// Run analyzer
75+
var analyzerDiagnostics = await GetAnalyzerDiagnosticsAsync (compilation, _analyzer);
76+
77+
Assert.NotEmpty (analyzerDiagnostics);
78+
79+
if (_expectedFixedCode != null)
80+
{
81+
if (_codeFix == null)
82+
{
83+
throw new InvalidOperationException ("Expected code fix but none was set.");
84+
}
85+
86+
var fixedDocument = await ApplyCodeFixAsync (document, analyzerDiagnostics.First (), _codeFix);
87+
88+
var formattedDocument = await Formatter.FormatAsync (fixedDocument);
89+
var fixedSource = (await formattedDocument.GetTextAsync ()).ToString ();
90+
91+
Assert.Equal (_expectedFixedCode, fixedSource);
92+
}
93+
}
94+
95+
private static Document CreateDocument (string source)
96+
{
97+
var dd = typeof (Enumerable).GetTypeInfo ().Assembly.Location;
98+
var coreDir = Directory.GetParent (dd) ?? throw new Exception ($"Could not find parent directory of dotnet sdk. Sdk directory was {dd}");
99+
100+
var workspace = new AdhocWorkspace ();
101+
var projectId = ProjectId.CreateNewId ();
102+
var documentId = DocumentId.CreateNewId (projectId);
103+
104+
var references = new List<MetadataReference> ()
105+
{
106+
MetadataReference.CreateFromFile(typeof(Button).Assembly.Location),
107+
MetadataReference.CreateFromFile(typeof(View).Assembly.Location),
108+
MetadataReference.CreateFromFile(typeof(System.IO.FileSystemInfo).Assembly.Location),
109+
MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location),
110+
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
111+
MetadataReference.CreateFromFile(typeof(MarshalByValueComponent).Assembly.Location),
112+
MetadataReference.CreateFromFile(typeof(ObservableCollection<string>).Assembly.Location),
113+
114+
// New assemblies required by Terminal.Gui version 2
115+
MetadataReference.CreateFromFile(typeof(Size).Assembly.Location),
116+
MetadataReference.CreateFromFile(typeof(CanBeNullAttribute).Assembly.Location),
117+
118+
119+
MetadataReference.CreateFromFile(Path.Combine(coreDir.FullName, "mscorlib.dll")),
120+
MetadataReference.CreateFromFile(Path.Combine(coreDir.FullName, "System.Runtime.dll")),
121+
MetadataReference.CreateFromFile(Path.Combine(coreDir.FullName, "System.Collections.dll")),
122+
MetadataReference.CreateFromFile(Path.Combine(coreDir.FullName, "System.Data.Common.dll")),
123+
// Add more as necessary
124+
};
125+
126+
127+
var projectInfo = ProjectInfo.Create (
128+
projectId,
129+
VersionStamp.Create (),
130+
"TestProject",
131+
"TestAssembly",
132+
LanguageNames.CSharp,
133+
compilationOptions: new CSharpCompilationOptions (OutputKind.DynamicallyLinkedLibrary),
134+
metadataReferences: references);
135+
136+
var solution = workspace.CurrentSolution
137+
.AddProject (projectInfo)
138+
.AddDocument (documentId, "Test.cs", SourceText.From (source));
139+
140+
return solution.GetDocument (documentId)!;
141+
}
142+
143+
private static async Task<ImmutableArray<Diagnostic>> GetAnalyzerDiagnosticsAsync (Compilation compilation, DiagnosticAnalyzer analyzer)
144+
{
145+
var compilationWithAnalyzers = compilation.WithAnalyzers (ImmutableArray.Create (analyzer));
146+
return await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync ();
147+
}
148+
149+
private static async Task<Document> ApplyCodeFixAsync (Document document, Diagnostic diagnostic, CodeFixProvider codeFix)
150+
{
151+
CodeAction _codeAction = null;
152+
var context = new CodeFixContext ((TextDocument)document, diagnostic, (action, _) => _codeAction = action, CancellationToken.None);
153+
154+
await codeFix.RegisterCodeFixesAsync (context);
155+
156+
if (_codeAction == null)
157+
{
158+
throw new InvalidOperationException ("Code fix did not register a fix.");
159+
}
160+
161+
var operations = await _codeAction.GetOperationsAsync (CancellationToken.None);
162+
var solution = operations.OfType<ApplyChangesOperation> ().First ().ChangedSolution;
163+
return solution.GetDocument (document.Id);
164+
}
165+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<Nullable>enable</Nullable>
5+
<IsPackable>false</IsPackable>
6+
<IsTestProject>true</IsTestProject>
7+
<DefineConstants>$(DefineConstants);JETBRAINS_ANNOTATIONS;CONTRACTS_FULL</DefineConstants>
8+
<DebugType>portable</DebugType>
9+
<ImplicitUsings>enable</ImplicitUsings>
10+
<NoLogo>true</NoLogo>
11+
<SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
12+
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
13+
</PropertyGroup>
14+
<ItemGroup>
15+
<PackageReference Include="coverlet.collector" />
16+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
17+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" />
18+
<PackageReference Include="Microsoft.CodeAnalysis.Features" />
19+
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic.Workspaces" />
20+
<PackageReference Include="Microsoft.NET.Test.Sdk" />
21+
<PackageReference Include="xunit" />
22+
<PackageReference Include="xunit.runner.visualstudio" />
23+
</ItemGroup>
24+
<ItemGroup>
25+
<ProjectReference Include="..\Terminal.Gui.Analyzers\Terminal.Gui.Analyzers.csproj" />
26+
<ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
27+
</ItemGroup>
28+
<ItemGroup>
29+
<Using Include="Xunit" />
30+
</ItemGroup>
31+
<ItemGroup>
32+
<None Update="xunit.runner.json">
33+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
34+
</None>
35+
</ItemGroup>
36+
</Project>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
## Release 1.0.0
2+
3+
### New Rules
4+
5+
Rule ID | Category | Severity | Notes
6+
--------|----------|----------|--------------------
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
### New Rules
2+
3+
Rule ID | Category | Severity | Notes
4+
--------|----------|----------|--------------------
5+
TGUI001 | Reliability | Warning | HandledEventArgsAnalyzer, [Documentation](./TGUI001.md)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
namespace Terminal.Gui.Analyzers;
2+
3+
/// <summary>
4+
/// Categories commonly used for diagnostic analyzers, inspired by FxCop and .NET analyzers conventions.
5+
/// </summary>
6+
internal enum DiagnosticCategory
7+
{
8+
/// <summary>
9+
/// Issues related to naming conventions and identifiers.
10+
/// </summary>
11+
Naming,
12+
13+
/// <summary>
14+
/// API design, class structure, inheritance, etc.
15+
/// </summary>
16+
Design,
17+
18+
/// <summary>
19+
/// How code uses APIs or language features incorrectly or suboptimally.
20+
/// </summary>
21+
Usage,
22+
23+
/// <summary>
24+
/// Patterns that cause poor runtime performance.
25+
/// </summary>
26+
Performance,
27+
28+
/// <summary>
29+
/// Vulnerabilities or insecure coding patterns.
30+
/// </summary>
31+
Security,
32+
33+
/// <summary>
34+
/// Code patterns that can cause bugs, crashes, or unpredictable behavior.
35+
/// </summary>
36+
Reliability,
37+
38+
/// <summary>
39+
/// Code readability, complexity, or future-proofing concerns.
40+
/// </summary>
41+
Maintainability,
42+
43+
/// <summary>
44+
/// Code patterns that may not work on all platforms or frameworks.
45+
/// </summary>
46+
Portability,
47+
48+
/// <summary>
49+
/// Issues with culture, localization, or globalization support.
50+
/// </summary>
51+
Globalization,
52+
53+
/// <summary>
54+
/// Problems when working with COM, P/Invoke, or other interop scenarios.
55+
/// </summary>
56+
Interoperability,
57+
58+
/// <summary>
59+
/// Issues with missing or incorrect XML doc comments.
60+
/// </summary>
61+
Documentation,
62+
63+
/// <summary>
64+
/// Purely stylistic issues not affecting semantics (e.g., whitespace, order).
65+
/// </summary>
66+
Style
67+
}

0 commit comments

Comments
 (0)