Skip to content

Commit e33e606

Browse files
committed
添加 LVGLSharp.Analyzers 项目及其引用
在解决方案中新增了 `LVGLSharp.Analyzers` 项目,并在 `LVGLSharp.sln` 中更新配置以包含该项目。在 `PictureBoxDemo.csproj`、`SerialPort.csproj` 和 `WinFormsDemo.csproj` 中添加了对该分析器项目的引用,指定其输出类型为 `Analyzer`。 新增了 `AnalyzerReleases.Shipped.md` 和 `AnalyzerReleases.Unshipped.md` 文件,用于记录分析器的发布信息和未发布的规则。添加了 `AnalyzerResources.cs` 和 `AnalyzerResources.resx` 文件,用于管理分析器的资源字符串。 新增了 `DemoLayoutAnalyzer.cs` 文件,定义了一个新的 Roslyn 分析器 `DemoLayoutAnalyzer`,用于检查和强制执行特定的布局模式,定义了两个规则:`LVGL001` 和 `LVGL002`。最后,新增了 `LVGLSharp.Analyzers.csproj` 文件,定义了项目的配置。
1 parent de6433d commit e33e606

File tree

10 files changed

+275
-0
lines changed

10 files changed

+275
-0
lines changed

LVGLSharp.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LVGLSharp.Native", "src\LVG
3131
EndProject
3232
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PictureBoxDemo", "src\Demos\PictureBoxDemo\PictureBoxDemo.csproj", "{F243F821-433D-713A-7637-8A06BF6B5DC5}"
3333
EndProject
34+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LVGLSharp.Analyzers", "src\LVGLSharp.Analyzers\LVGLSharp.Analyzers.csproj", "{C61A88A7-BF41-6130-E711-D80E32E5E733}"
35+
EndProject
3436
Global
3537
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3638
Debug|Any CPU = Debug|Any CPU
@@ -73,6 +75,10 @@ Global
7375
{F243F821-433D-713A-7637-8A06BF6B5DC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
7476
{F243F821-433D-713A-7637-8A06BF6B5DC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
7577
{F243F821-433D-713A-7637-8A06BF6B5DC5}.Release|Any CPU.Build.0 = Release|Any CPU
78+
{C61A88A7-BF41-6130-E711-D80E32E5E733}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
79+
{C61A88A7-BF41-6130-E711-D80E32E5E733}.Debug|Any CPU.Build.0 = Debug|Any CPU
80+
{C61A88A7-BF41-6130-E711-D80E32E5E733}.Release|Any CPU.ActiveCfg = Release|Any CPU
81+
{C61A88A7-BF41-6130-E711-D80E32E5E733}.Release|Any CPU.Build.0 = Release|Any CPU
7682
EndGlobalSection
7783
GlobalSection(SolutionProperties) = preSolution
7884
HideSolutionNode = FALSE

src/Demos/PictureBoxDemo/PictureBoxDemo.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
<Using Include="LVGLSharp.Darwing" />
2222
</ItemGroup>
2323
<ItemGroup>
24+
<ProjectReference Include="..\..\LVGLSharp.Analyzers\LVGLSharp.Analyzers.csproj"
25+
OutputItemType="Analyzer"
26+
ReferenceOutputAssembly="false" />
27+
2428
<Content Include="lvgl.png">
2529
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
2630
</Content>

src/Demos/SerialPort/SerialPort.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
<ProjectReference Include="..\..\LVGLSharp.Core\LVGLSharp.Core.csproj" />
2323
<ProjectReference Include="..\..\LVGLSharp.Windows\LVGLSharp.Runtime.Windows.csproj" />
2424
<ProjectReference Include="..\..\LVGLSharp.Runtime.Linux\LVGLSharp.Runtime.Linux.csproj" />
25+
<ProjectReference Include="..\..\LVGLSharp.Analyzers\LVGLSharp.Analyzers.csproj"
26+
OutputItemType="Analyzer"
27+
ReferenceOutputAssembly="false" />
2528
</ItemGroup>
2629

2730
</Project>

src/Demos/WinFormsDemo/WinFormsDemo.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@
2323
<Using Include="LVGLSharp.Forms" />
2424
<Using Include="LVGLSharp.Darwing" />
2525
</ItemGroup>
26+
<ItemGroup>
27+
<ProjectReference Include="..\..\LVGLSharp.Analyzers\LVGLSharp.Analyzers.csproj"
28+
OutputItemType="Analyzer"
29+
ReferenceOutputAssembly="false" />
30+
</ItemGroup>
2631
<ItemGroup>
2732
<None Remove="lvgl.png" />
2833
</ItemGroup>
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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
## Unshipped
2+
3+
### New Rules
4+
5+
Rule ID | Category | Severity | Notes
6+
--------|----------|----------|-----
7+
LVGL001 | Layout | Warning | Enforces WinFormsDemo-style FlowLayoutPanel rows inside demo TableLayoutPanel layouts.
8+
LVGL002 | Layout | Warning | Disallows percent-based RowStyle usage in demo designer layouts.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Resources;
2+
3+
namespace LVGLSharp.Analyzers;
4+
5+
internal static class AnalyzerResources
6+
{
7+
private static readonly ResourceManager s_resourceManager = new("LVGLSharp.Analyzers.AnalyzerResources", typeof(AnalyzerResources).Assembly);
8+
9+
internal static ResourceManager ResourceManager => s_resourceManager;
10+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<root>
3+
<resheader name="resmimetype">
4+
<value>text/microsoft-resx</value>
5+
</resheader>
6+
<resheader name="version">
7+
<value>2.0</value>
8+
</resheader>
9+
<resheader name="reader">
10+
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
11+
</resheader>
12+
<resheader name="writer">
13+
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
14+
</resheader>
15+
<data name="DirectChildRuleTitle" xml:space="preserve">
16+
<value>Demo layouts should use WinFormsDemo-style containers</value>
17+
</data>
18+
<data name="DirectChildRuleMessage" xml:space="preserve">
19+
<value>Add a FlowLayoutPanel to the demo TableLayoutPanel instead of '{0}'</value>
20+
</data>
21+
<data name="DirectChildRuleDescription" xml:space="preserve">
22+
<value>Demo layouts should add one FlowLayoutPanel per main table row and place the actual controls inside that flow container.</value>
23+
</data>
24+
<data name="AbsoluteRowRuleTitle" xml:space="preserve">
25+
<value>Demo layouts should use absolute table row heights</value>
26+
</data>
27+
<data name="AbsoluteRowRuleMessage" xml:space="preserve">
28+
<value>Use an absolute RowStyle height instead of SizeType.Percent in demo layouts</value>
29+
</data>
30+
<data name="AbsoluteRowRuleDescription" xml:space="preserve">
31+
<value>WinFormsDemo-style layout uses fixed row heights for the outer demo TableLayoutPanel to keep LVGL controls visible.</value>
32+
</data>
33+
</root>
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
using System.Collections.Immutable;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.CodeAnalysis.CSharp;
4+
using Microsoft.CodeAnalysis.CSharp.Syntax;
5+
using Microsoft.CodeAnalysis.Diagnostics;
6+
7+
namespace LVGLSharp.Analyzers;
8+
9+
/// <summary>
10+
/// Enforces the demo layout pattern that LVGL currently renders reliably.
11+
/// </summary>
12+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
13+
public sealed class DemoLayoutAnalyzer : DiagnosticAnalyzer
14+
{
15+
private const string Category = "Layout";
16+
private const string TableLayoutPanelMetadataName = "System.Windows.Forms.TableLayoutPanel";
17+
private const string LvglTableLayoutPanelMetadataName = "LVGLSharp.Forms.TableLayoutPanel";
18+
private const string FlowLayoutPanelMetadataName = "System.Windows.Forms.FlowLayoutPanel";
19+
private const string LvglFlowLayoutPanelMetadataName = "LVGLSharp.Forms.FlowLayoutPanel";
20+
private const string SizeTypeMetadataName = "System.Windows.Forms.SizeType";
21+
private const string LvglSizeTypeMetadataName = "LVGLSharp.Forms.SizeType";
22+
23+
private static readonly LocalizableString DirectChildTitle = new LocalizableResourceString("DirectChildRuleTitle", AnalyzerResources.ResourceManager, typeof(AnalyzerResources));
24+
private static readonly LocalizableString DirectChildMessage = new LocalizableResourceString("DirectChildRuleMessage", AnalyzerResources.ResourceManager, typeof(AnalyzerResources));
25+
private static readonly LocalizableString DirectChildDescription = new LocalizableResourceString("DirectChildRuleDescription", AnalyzerResources.ResourceManager, typeof(AnalyzerResources));
26+
private static readonly LocalizableString AbsoluteRowTitle = new LocalizableResourceString("AbsoluteRowRuleTitle", AnalyzerResources.ResourceManager, typeof(AnalyzerResources));
27+
private static readonly LocalizableString AbsoluteRowMessage = new LocalizableResourceString("AbsoluteRowRuleMessage", AnalyzerResources.ResourceManager, typeof(AnalyzerResources));
28+
private static readonly LocalizableString AbsoluteRowDescription = new LocalizableResourceString("AbsoluteRowRuleDescription", AnalyzerResources.ResourceManager, typeof(AnalyzerResources));
29+
30+
internal static readonly DiagnosticDescriptor DirectChildRule = new(
31+
id: "LVGL001",
32+
title: DirectChildTitle,
33+
messageFormat: DirectChildMessage,
34+
category: Category,
35+
defaultSeverity: DiagnosticSeverity.Warning,
36+
isEnabledByDefault: true,
37+
description: DirectChildDescription);
38+
39+
internal static readonly DiagnosticDescriptor AbsoluteRowRule = new(
40+
id: "LVGL002",
41+
title: AbsoluteRowTitle,
42+
messageFormat: AbsoluteRowMessage,
43+
category: Category,
44+
defaultSeverity: DiagnosticSeverity.Warning,
45+
isEnabledByDefault: true,
46+
description: AbsoluteRowDescription);
47+
48+
/// <inheritdoc />
49+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [DirectChildRule, AbsoluteRowRule];
50+
51+
/// <inheritdoc />
52+
public override void Initialize(AnalysisContext context)
53+
{
54+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
55+
context.EnableConcurrentExecution();
56+
context.RegisterSyntaxNodeAction(AnalyzeInvocation, SyntaxKind.InvocationExpression);
57+
}
58+
59+
private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context)
60+
{
61+
if (!IsDemoDesignerFile(context.Node.SyntaxTree))
62+
{
63+
return;
64+
}
65+
66+
if (context.Node is not InvocationExpressionSyntax invocation || invocation.Expression is not MemberAccessExpressionSyntax memberAccess)
67+
{
68+
return;
69+
}
70+
71+
if (memberAccess.Name.Identifier.ValueText != "Add" || memberAccess.Expression is not MemberAccessExpressionSyntax collectionAccess)
72+
{
73+
return;
74+
}
75+
76+
switch (collectionAccess.Name.Identifier.ValueText)
77+
{
78+
case "Controls":
79+
AnalyzeControlsAdd(context, invocation, collectionAccess);
80+
break;
81+
82+
case "RowStyles":
83+
AnalyzeRowStylesAdd(context, invocation, collectionAccess);
84+
break;
85+
}
86+
}
87+
88+
private static void AnalyzeControlsAdd(SyntaxNodeAnalysisContext context, InvocationExpressionSyntax invocation, MemberAccessExpressionSyntax collectionAccess)
89+
{
90+
if (!IsTableLayoutPanel(context.SemanticModel.GetTypeInfo(collectionAccess.Expression, context.CancellationToken).Type))
91+
{
92+
return;
93+
}
94+
95+
if (invocation.ArgumentList.Arguments.Count == 0)
96+
{
97+
return;
98+
}
99+
100+
var childExpression = invocation.ArgumentList.Arguments[0].Expression;
101+
var childType = context.SemanticModel.GetTypeInfo(childExpression, context.CancellationToken).Type;
102+
if (IsFlowLayoutPanel(childType))
103+
{
104+
return;
105+
}
106+
107+
var childTypeName = childType?.Name ?? childExpression.ToString();
108+
context.ReportDiagnostic(Diagnostic.Create(DirectChildRule, childExpression.GetLocation(), childTypeName));
109+
}
110+
111+
private static void AnalyzeRowStylesAdd(SyntaxNodeAnalysisContext context, InvocationExpressionSyntax invocation, MemberAccessExpressionSyntax collectionAccess)
112+
{
113+
if (!IsTableLayoutPanel(context.SemanticModel.GetTypeInfo(collectionAccess.Expression, context.CancellationToken).Type))
114+
{
115+
return;
116+
}
117+
118+
if (invocation.ArgumentList.Arguments.Count == 0)
119+
{
120+
return;
121+
}
122+
123+
if (invocation.ArgumentList.Arguments[0].Expression is not ObjectCreationExpressionSyntax objectCreation || objectCreation.ArgumentList is null)
124+
{
125+
return;
126+
}
127+
128+
if (objectCreation.ArgumentList.Arguments.Count == 0)
129+
{
130+
return;
131+
}
132+
133+
var sizeTypeExpression = objectCreation.ArgumentList.Arguments[0].Expression;
134+
if (!IsPercentSizeType(context.SemanticModel.GetSymbolInfo(sizeTypeExpression, context.CancellationToken).Symbol))
135+
{
136+
return;
137+
}
138+
139+
context.ReportDiagnostic(Diagnostic.Create(AbsoluteRowRule, sizeTypeExpression.GetLocation()));
140+
}
141+
142+
private static bool IsDemoDesignerFile(SyntaxTree syntaxTree)
143+
{
144+
var filePath = syntaxTree.FilePath;
145+
if (string.IsNullOrWhiteSpace(filePath))
146+
{
147+
return false;
148+
}
149+
150+
var normalizedPath = filePath.Replace('\\', '/');
151+
return normalizedPath.Contains("/src/Demos/", StringComparison.OrdinalIgnoreCase)
152+
&& normalizedPath.EndsWith(".Designer.cs", StringComparison.OrdinalIgnoreCase);
153+
}
154+
155+
private static bool IsTableLayoutPanel(ITypeSymbol? typeSymbol)
156+
=> IsNamedType(typeSymbol, TableLayoutPanelMetadataName) || IsNamedType(typeSymbol, LvglTableLayoutPanelMetadataName);
157+
158+
private static bool IsFlowLayoutPanel(ITypeSymbol? typeSymbol)
159+
=> IsNamedType(typeSymbol, FlowLayoutPanelMetadataName) || IsNamedType(typeSymbol, LvglFlowLayoutPanelMetadataName);
160+
161+
private static bool IsPercentSizeType(ISymbol? symbol)
162+
=> symbol is IFieldSymbol fieldSymbol
163+
&& fieldSymbol.Name == "Percent"
164+
&& (fieldSymbol.ContainingType.ToDisplayString() == SizeTypeMetadataName || fieldSymbol.ContainingType.ToDisplayString() == LvglSizeTypeMetadataName);
165+
166+
private static bool IsNamedType(ITypeSymbol? typeSymbol, string metadataName)
167+
{
168+
for (var current = typeSymbol; current is not null; current = current.BaseType)
169+
{
170+
if (current.ToDisplayString() == metadataName)
171+
{
172+
return true;
173+
}
174+
}
175+
176+
return false;
177+
}
178+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>netstandard2.0</TargetFramework>
4+
<Nullable>enable</Nullable>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<LangVersion>latest</LangVersion>
7+
<IsRoslynAnalyzer>true</IsRoslynAnalyzer>
8+
<IncludeBuildOutput>true</IncludeBuildOutput>
9+
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all" />
14+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" PrivateAssets="all" />
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<AdditionalFiles Include="AnalyzerReleases.Shipped.md" />
19+
<AdditionalFiles Include="AnalyzerReleases.Unshipped.md" />
20+
</ItemGroup>
21+
22+
</Project>

0 commit comments

Comments
 (0)