mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-13 06:04:30 +08:00
Compare commits
23 Commits
1a9e8f64bd
...
7e45197698
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e45197698 | ||
|
|
a439fb8f4e | ||
|
|
25d33d0bf9 | ||
|
|
b710f31b86 | ||
|
|
a98d1cb8d0 | ||
|
|
77e332fd44 | ||
|
|
091b872c86 | ||
|
|
a0ce04b185 | ||
|
|
e692ed3e43 | ||
|
|
136b139312 | ||
|
|
833a95f7f3 | ||
|
|
2de57f5fde | ||
|
|
a3501c9b91 | ||
|
|
fdccfc4448 | ||
|
|
fa2488c108 | ||
|
|
06f8db2efd | ||
|
|
a7bd213044 | ||
|
|
c578910b40 | ||
|
|
45b25f429f | ||
|
|
8a117201d4 | ||
|
|
a4bb041b0d | ||
|
|
148cfe14b0 | ||
|
|
59fe63bba6 |
@ -46,9 +46,22 @@
|
||||
/gframework-batch-boot <task-or-stop-condition>
|
||||
```
|
||||
|
||||
批处理阈值速记:
|
||||
|
||||
```bash
|
||||
/gframework-batch-boot 75
|
||||
/gframework-batch-boot 75 2000
|
||||
```
|
||||
|
||||
- 单个数字默认表示“当前分支全部提交相对远程 `origin/main` 接近多少个文件变更时停止”
|
||||
- 两个数字默认表示“当前分支全部提交相对远程 `origin/main` 的 `文件数 OR 变更行数`”,顺序固定为 `<files> <lines>`
|
||||
- 不推荐写 `/gframework-batch-boot 75 | 2000`,因为 `|` 很像 shell pipe;若用户这样写,也应按 OR 语义理解并在后续说明中归一化成无 `|` 版本
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
/gframework-batch-boot 75
|
||||
/gframework-batch-boot 75 2000
|
||||
/gframework-batch-boot continue analyzer warning reduction until branch diff vs origin/main approaches 75 files
|
||||
/gframework-batch-boot keep refactoring repetitive source-generator tests in bounded batches
|
||||
```
|
||||
|
||||
@ -50,6 +50,19 @@ For changed-file limits, measure branch-wide scope against the chosen baseline,
|
||||
- use `git diff --name-only <baseline>...HEAD`
|
||||
- do not confuse branch diff size with `git status --short`
|
||||
|
||||
For changed-line limits, also measure branch-wide scope against the chosen baseline:
|
||||
|
||||
- prefer `git diff --numstat <baseline>...HEAD`
|
||||
- treat "changed lines" as `added + deleted` summed across the branch diff
|
||||
- do not use working-tree-only line counts as a substitute for branch-wide scope
|
||||
|
||||
For shorthand numeric thresholds, use a fixed default baseline:
|
||||
|
||||
- compare the current branch's cumulative diff against remote `origin/main`
|
||||
- include all commits reachable from `HEAD` that are not already in `origin/main`
|
||||
- do not reinterpret shorthand thresholds as "this batch only" or "current unstaged changes only"
|
||||
- only use another baseline when the user explicitly names it in the prompt
|
||||
|
||||
## Stop Conditions
|
||||
|
||||
Choose one primary stop condition before the first batch and restate it to the user.
|
||||
@ -63,6 +76,32 @@ Common stop conditions:
|
||||
|
||||
If multiple stop conditions exist, rank them and treat one as primary.
|
||||
|
||||
## Shorthand Stop-Condition Syntax
|
||||
|
||||
`gframework-batch-boot` may be invoked with shorthand numeric thresholds when the user clearly wants a branch-size stop
|
||||
condition instead of a long natural-language prompt.
|
||||
|
||||
Interpret shorthand as follows:
|
||||
|
||||
- `$gframework-batch-boot 75`
|
||||
- means: stop when the current branch's cumulative diff vs remote `origin/main` approaches `75` changed files
|
||||
- `$gframework-batch-boot 75 2000`
|
||||
- means: stop when the current branch's cumulative diff vs remote `origin/main` approaches `75` changed files OR
|
||||
`2000` changed lines
|
||||
- default positional meaning is `<files> <lines>`
|
||||
- `$gframework-batch-boot 75 | 2000`
|
||||
- may be interpreted as the same OR shorthand in plain-language chat
|
||||
- when restating, planning, or documenting the command, normalize it to `$gframework-batch-boot 75 2000`
|
||||
- prefer the no-pipe form because `|` is easy to confuse with a shell pipeline
|
||||
|
||||
When shorthand is used:
|
||||
|
||||
- report the resolved thresholds explicitly before the first batch
|
||||
- report that the baseline is remote `origin/main`, unless the user explicitly overrides it
|
||||
- if two numeric thresholds are present, treat file count as the default primary metric for status reporting unless the
|
||||
user says otherwise
|
||||
- stop when either threshold is reached or exceeded, even if the other threshold still has headroom
|
||||
|
||||
## Batch Loop
|
||||
|
||||
1. Inspect the current state before the first batch:
|
||||
@ -134,6 +173,8 @@ When stopping, report:
|
||||
|
||||
## Example Triggers
|
||||
|
||||
- `Use $gframework-batch-boot 75 to keep reducing analyzer warnings until the branch diff vs baseline approaches 75 files.`
|
||||
- `Use $gframework-batch-boot 75 2000 to keep reducing warnings until the branch diff approaches 75 files or 2000 changed lines.`
|
||||
- `Use $gframework-batch-boot and keep reducing analyzer warnings until the branch diff vs origin/main approaches 75 files.`
|
||||
- `Use $gframework-batch-boot to continue this repetitive test refactor in bounded batches until the warning count drops below 10.`
|
||||
- `Use $gframework-batch-boot and refresh module docs in waves without asking me to trigger every round.`
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -63,7 +63,7 @@ jobs:
|
||||
# 使用 TruffleHog 工具扫描代码库中的敏感信息泄露,如API密钥、密码等
|
||||
# 该步骤会比较基础分支和当前提交之间的差异,检测新增内容中是否包含敏感数据
|
||||
- name: TruffleHog OSS
|
||||
uses: trufflesecurity/trufflehog@v3.94.3
|
||||
uses: trufflesecurity/trufflehog@v3.95.2
|
||||
with:
|
||||
# 扫描路径,. 表示扫描整个仓库
|
||||
path: .
|
||||
|
||||
@ -29,6 +29,10 @@ All AI agents and contributors must follow these rules when writing, reviewing,
|
||||
## Git Workflow Rules
|
||||
|
||||
- Every completed task MUST pass at least one build validation before it is considered done.
|
||||
- When the goal is to inspect or reduce warnings printed during project build, contributors MUST establish the warning
|
||||
baseline from a non-incremental repository-root build by running `dotnet clean` and then `dotnet build`.
|
||||
- Contributors MUST NOT treat a repeated incremental `dotnet build` result as authoritative for warning inspection when
|
||||
a clean baseline has not been captured in the same round.
|
||||
- If the task changes multiple projects or shared abstractions, prefer a solution-level or affected-project
|
||||
`dotnet build ... -c Release`; otherwise use the smallest build command that still proves the result compiles.
|
||||
- When a task adds a feature or modifies code, contributors MUST run a Release build for every directly affected
|
||||
@ -233,6 +237,10 @@ All generated or modified code MUST include clear and meaningful comments where
|
||||
Use the smallest command set that proves the change, then expand if the change is cross-cutting.
|
||||
|
||||
```bash
|
||||
# Check warnings from the default repository build entrypoint
|
||||
dotnet clean
|
||||
dotnet build
|
||||
|
||||
# Build the full solution
|
||||
dotnet build GFramework.sln -c Release
|
||||
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
<Project>
|
||||
<!-- Keep repository-wide analyzer behavior consistent while allowing only selected projects to opt into polyfills. -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Meziantou.Analyzer" Version="3.0.48">
|
||||
<PackageReference Include="Meziantou.Analyzer" Version="3.0.52">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Update="Meziantou.Polyfill" Version="1.0.110">
|
||||
<PackageReference Update="Meziantou.Polyfill" Version="1.0.116">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@ -21,6 +21,6 @@
|
||||
<ProjectReference Include="..\GFramework.Cqrs.Abstractions\GFramework.Cqrs.Abstractions.csproj"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.6"/>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.7"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<WarningLevel>0</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.4.0"/>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
<ProjectReference Include="..\$(AssemblyName).Abstractions\$(AssemblyName).Abstractions.csproj"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.6"/>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.7"/>
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@ -283,15 +283,21 @@ public class UnifiedSettingsDataRepository(
|
||||
/// 复制当前统一文件快照,确保未提交修改不会污染内存中的已提交状态。
|
||||
/// </summary>
|
||||
/// <param name="source">要复制的统一文件快照。</param>
|
||||
/// <returns>包含独立 section 字典的新快照。</returns>
|
||||
/// <returns>包含独立 section 映射副本的新快照。</returns>
|
||||
private static UnifiedSettingsFile CloneFile(UnifiedSettingsFile source)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(source);
|
||||
|
||||
// 反序列化后的运行时类型可能只是 IDictionary 实现;若底层仍是 Dictionary,则保留其 comparer,
|
||||
// 否则退回到按当前内容复制,避免因为 API 抽象化而改变持久化前后的键比较语义。
|
||||
var sections = source.Sections is Dictionary<string, string> dictionary
|
||||
? new Dictionary<string, string>(dictionary, dictionary.Comparer)
|
||||
: new Dictionary<string, string>(source.Sections);
|
||||
|
||||
return new UnifiedSettingsFile
|
||||
{
|
||||
Version = source.Version,
|
||||
Sections = new Dictionary<string, string>(source.Sections, source.Sections.Comparer)
|
||||
Sections = sections
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Core.Abstractions.Versioning;
|
||||
|
||||
namespace GFramework.Game.Data;
|
||||
@ -22,13 +23,16 @@ namespace GFramework.Game.Data;
|
||||
internal sealed class UnifiedSettingsFile : IVersioned
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置节集合,存储不同类型的配置数据
|
||||
/// 键为配置节名称,值为配置对象
|
||||
/// 配置节映射,存储不同类型的配置数据。
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Sections { get; set; } = new();
|
||||
/// <remarks>
|
||||
/// 这里公开为 <see cref="IDictionary{TKey,TValue}" /> 而不是具体的 <see cref="Dictionary{TKey,TValue}" />,
|
||||
/// 以避免暴露可替换的具体集合实现,同时继续兼容 Newtonsoft.Json 对字典对象的序列化与反序列化。
|
||||
/// </remarks>
|
||||
public IDictionary<string, string> Sections { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// 配置文件版本号,用于版本控制和兼容性检查
|
||||
/// </summary>
|
||||
public int Version { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,57 +6,61 @@ namespace GFramework.Godot.SourceGenerators.Tests.Behavior;
|
||||
[TestFixture]
|
||||
public class AutoSceneGeneratorTests
|
||||
{
|
||||
private const string AutoSceneAttributeWithKeyDeclaration = """
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoSceneAttribute : Attribute
|
||||
{
|
||||
public AutoSceneAttribute(string key) { }
|
||||
}
|
||||
""";
|
||||
|
||||
private const string AutoSceneAttributeWithoutKeyDeclaration = """
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoSceneAttribute : Attribute
|
||||
{
|
||||
public AutoSceneAttribute() { }
|
||||
}
|
||||
""";
|
||||
|
||||
private const string NodeTypes = """
|
||||
public class Node { }
|
||||
public class Node2D : Node { }
|
||||
""";
|
||||
|
||||
private const string SceneBehaviorInfrastructure = """
|
||||
namespace GFramework.Game.Abstractions.Scene
|
||||
{
|
||||
public interface ISceneBehavior { }
|
||||
}
|
||||
|
||||
namespace GFramework.Godot.Scene
|
||||
{
|
||||
using GFramework.Game.Abstractions.Scene;
|
||||
using Godot;
|
||||
|
||||
public static class SceneBehaviorFactory
|
||||
{
|
||||
public static ISceneBehavior Create<T>(T owner, string key)
|
||||
where T : Node
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
[Test]
|
||||
public async Task Generates_Scene_Behavior_Boilerplate()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoSceneAttribute : Attribute
|
||||
{
|
||||
public AutoSceneAttribute(string key) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
public class Node { }
|
||||
public class Node2D : Node { }
|
||||
}
|
||||
|
||||
namespace GFramework.Game.Abstractions.Scene
|
||||
{
|
||||
public interface ISceneBehavior { }
|
||||
}
|
||||
|
||||
namespace GFramework.Godot.Scene
|
||||
{
|
||||
using GFramework.Game.Abstractions.Scene;
|
||||
using Godot;
|
||||
|
||||
public static class SceneBehaviorFactory
|
||||
{
|
||||
public static ISceneBehavior Create<T>(T owner, string key)
|
||||
where T : Node
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
[AutoScene("Gameplay")]
|
||||
public partial class GameplayRoot : Node2D
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
string source = CreateAutoSceneSource(
|
||||
AutoSceneAttributeWithKeyDeclaration,
|
||||
"""
|
||||
[AutoScene("Gameplay")]
|
||||
public partial class GameplayRoot : Node2D
|
||||
{
|
||||
}
|
||||
""",
|
||||
includeBehaviorInfrastructure: true);
|
||||
|
||||
const string expected = """
|
||||
// <auto-generated />
|
||||
@ -80,40 +84,20 @@ public class AutoSceneGeneratorTests
|
||||
|
||||
await GeneratorTest<AutoSceneGenerator>.RunAsync(
|
||||
source,
|
||||
("TestApp_GameplayRoot.AutoScene.g.cs", expected));
|
||||
("TestApp_GameplayRoot.AutoScene.g.cs", expected)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Reports_Diagnostic_When_AutoScene_Arguments_Are_Invalid()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoSceneAttribute : Attribute
|
||||
{
|
||||
public AutoSceneAttribute() { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
public class Node { }
|
||||
public class Node2D : Node { }
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
[{|#0:AutoScene|}]
|
||||
public partial class GameplayRoot : Node2D
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
string source = CreateAutoSceneSource(
|
||||
AutoSceneAttributeWithoutKeyDeclaration,
|
||||
"""
|
||||
[{|#0:AutoScene|}]
|
||||
public partial class GameplayRoot : Node2D
|
||||
{
|
||||
}
|
||||
""");
|
||||
|
||||
var test = new CSharpSourceGeneratorTest<AutoSceneGenerator, DefaultVerifier>
|
||||
{
|
||||
@ -128,65 +112,26 @@ public class AutoSceneGeneratorTests
|
||||
.WithLocation(0)
|
||||
.WithArguments("AutoSceneAttribute", "GameplayRoot", "a single string scene key argument"));
|
||||
|
||||
await test.RunAsync();
|
||||
await test.RunAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Generates_Type_Constraints_For_Nullable_Reference_NotNull_And_Unmanaged_Parameters()
|
||||
{
|
||||
const string source = """
|
||||
#nullable enable
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoSceneAttribute : Attribute
|
||||
{
|
||||
public AutoSceneAttribute(string key) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
public class Node { }
|
||||
public class Node2D : Node { }
|
||||
}
|
||||
|
||||
namespace GFramework.Game.Abstractions.Scene
|
||||
{
|
||||
public interface ISceneBehavior { }
|
||||
}
|
||||
|
||||
namespace GFramework.Godot.Scene
|
||||
{
|
||||
using GFramework.Game.Abstractions.Scene;
|
||||
using Godot;
|
||||
|
||||
public static class SceneBehaviorFactory
|
||||
{
|
||||
public static ISceneBehavior Create<T>(T owner, string key)
|
||||
where T : Node
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
[AutoScene("Gameplay")]
|
||||
public partial class GameplayRoot<TReference, TNotNull, TValue, TUnmanaged> : Node2D
|
||||
where TReference : class?
|
||||
where TNotNull : notnull
|
||||
where TValue : struct
|
||||
where TUnmanaged : unmanaged
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
string source = CreateAutoSceneSource(
|
||||
AutoSceneAttributeWithKeyDeclaration,
|
||||
"""
|
||||
[AutoScene("Gameplay")]
|
||||
public partial class GameplayRoot<TReference, TNotNull, TValue, TUnmanaged> : Node2D
|
||||
where TReference : class?
|
||||
where TNotNull : notnull
|
||||
where TValue : struct
|
||||
where TUnmanaged : unmanaged
|
||||
{
|
||||
}
|
||||
""",
|
||||
includeBehaviorInfrastructure: true,
|
||||
nullableEnabled: true);
|
||||
|
||||
const string expected = """
|
||||
// <auto-generated />
|
||||
@ -214,7 +159,7 @@ public class AutoSceneGeneratorTests
|
||||
|
||||
await GeneratorTest<AutoSceneGenerator>.RunAsync(
|
||||
source,
|
||||
("TestApp_GameplayRoot.AutoScene.g.cs", expected));
|
||||
("TestApp_GameplayRoot.AutoScene.g.cs", expected)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -267,7 +212,7 @@ public class AutoSceneGeneratorTests
|
||||
.WithLocation(0)
|
||||
.WithArguments("GameplayRoot", "SceneKeyStr"));
|
||||
|
||||
await test.RunAsync();
|
||||
await test.RunAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -326,6 +271,39 @@ public class AutoSceneGeneratorTests
|
||||
.WithLocation(0)
|
||||
.WithArguments("GameplayRoot", "__autoSceneBehavior_Generated"));
|
||||
|
||||
await test.RunAsync();
|
||||
await test.RunAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static string CreateAutoSceneSource(
|
||||
string attributeDeclaration,
|
||||
string testAppSource,
|
||||
bool includeBehaviorInfrastructure = false,
|
||||
bool nullableEnabled = false)
|
||||
{
|
||||
string nullableDirective = nullableEnabled ? "#nullable enable\n" : string.Empty;
|
||||
string infrastructure = includeBehaviorInfrastructure
|
||||
? $"{Environment.NewLine}{Environment.NewLine}{SceneBehaviorInfrastructure}"
|
||||
: string.Empty;
|
||||
|
||||
return $$"""
|
||||
{{nullableDirective}}using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
{{attributeDeclaration}}
|
||||
}
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
{{NodeTypes}}
|
||||
}{{infrastructure}}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
{{testAppSource}}
|
||||
}
|
||||
""";
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,69 +6,85 @@ namespace GFramework.Godot.SourceGenerators.Tests.Behavior;
|
||||
[TestFixture]
|
||||
public class AutoUiPageGeneratorTests
|
||||
{
|
||||
private const string AutoUiPageAttributeWithLayerDeclaration = """
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoUiPageAttribute : Attribute
|
||||
{
|
||||
public AutoUiPageAttribute(string key, string layerName) { }
|
||||
}
|
||||
""";
|
||||
|
||||
private const string AutoUiPageAttributeWithoutLayerDeclaration = """
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoUiPageAttribute : Attribute
|
||||
{
|
||||
public AutoUiPageAttribute(string key) { }
|
||||
}
|
||||
""";
|
||||
|
||||
private const string CanvasNodeTypes = """
|
||||
public class Node { }
|
||||
public class CanvasItem : Node { }
|
||||
public class Control : CanvasItem { }
|
||||
""";
|
||||
|
||||
private const string UiLayerFullEnum = """
|
||||
namespace GFramework.Game.Abstractions.Enums
|
||||
{
|
||||
public enum UiLayer
|
||||
{
|
||||
Page,
|
||||
Overlay,
|
||||
Modal
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string UiLayerPageOnlyEnum = """
|
||||
namespace GFramework.Game.Abstractions.Enums
|
||||
{
|
||||
public enum UiLayer
|
||||
{
|
||||
Page
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string UiBehaviorInfrastructure = """
|
||||
namespace GFramework.Game.Abstractions.UI
|
||||
{
|
||||
public interface IUiPageBehavior { }
|
||||
}
|
||||
|
||||
namespace GFramework.Godot.UI
|
||||
{
|
||||
using GFramework.Game.Abstractions.Enums;
|
||||
using GFramework.Game.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
public static class UiPageBehaviorFactory
|
||||
{
|
||||
public static IUiPageBehavior Create<T>(T owner, string key, UiLayer layer)
|
||||
where T : CanvasItem
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
[Test]
|
||||
public async Task Generates_Ui_Page_Behavior_Boilerplate()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoUiPageAttribute : Attribute
|
||||
{
|
||||
public AutoUiPageAttribute(string key, string layerName) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
public class Node { }
|
||||
public class CanvasItem : Node { }
|
||||
public class Control : CanvasItem { }
|
||||
}
|
||||
|
||||
namespace GFramework.Game.Abstractions.Enums
|
||||
{
|
||||
public enum UiLayer
|
||||
{
|
||||
Page,
|
||||
Overlay,
|
||||
Modal
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Game.Abstractions.UI
|
||||
{
|
||||
public interface IUiPageBehavior { }
|
||||
}
|
||||
|
||||
namespace GFramework.Godot.UI
|
||||
{
|
||||
using GFramework.Game.Abstractions.Enums;
|
||||
using GFramework.Game.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
public static class UiPageBehaviorFactory
|
||||
{
|
||||
public static IUiPageBehavior Create<T>(T owner, string key, UiLayer layer)
|
||||
where T : CanvasItem
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
[AutoUiPage("MainMenu", "Page")]
|
||||
public partial class MainMenu : Control
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
string source = CreateAutoUiPageSource(
|
||||
AutoUiPageAttributeWithLayerDeclaration,
|
||||
UiLayerFullEnum,
|
||||
"""
|
||||
[AutoUiPage("MainMenu", "Page")]
|
||||
public partial class MainMenu : Control
|
||||
{
|
||||
}
|
||||
""");
|
||||
|
||||
const string expected = """
|
||||
// <auto-generated />
|
||||
@ -92,70 +108,21 @@ public class AutoUiPageGeneratorTests
|
||||
|
||||
await GeneratorTest<AutoUiPageGenerator>.RunAsync(
|
||||
source,
|
||||
("TestApp_MainMenu.AutoUiPage.g.cs", expected));
|
||||
("TestApp_MainMenu.AutoUiPage.g.cs", expected)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Reports_Diagnostic_When_AutoUiPage_Attribute_Arguments_Are_Invalid()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoUiPageAttribute : Attribute
|
||||
{
|
||||
public AutoUiPageAttribute(string key) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
public class Node { }
|
||||
public class CanvasItem : Node { }
|
||||
public class Control : CanvasItem { }
|
||||
}
|
||||
|
||||
namespace GFramework.Game.Abstractions.Enums
|
||||
{
|
||||
public enum UiLayer
|
||||
{
|
||||
Page
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Game.Abstractions.UI
|
||||
{
|
||||
public interface IUiPageBehavior { }
|
||||
}
|
||||
|
||||
namespace GFramework.Godot.UI
|
||||
{
|
||||
using GFramework.Game.Abstractions.Enums;
|
||||
using GFramework.Game.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
public static class UiPageBehaviorFactory
|
||||
{
|
||||
public static IUiPageBehavior Create<T>(T owner, string key, UiLayer layer)
|
||||
where T : CanvasItem
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
[{|#0:AutoUiPage("MainMenu")|}]
|
||||
public partial class MainMenu : Control
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
string source = CreateAutoUiPageSource(
|
||||
AutoUiPageAttributeWithoutLayerDeclaration,
|
||||
UiLayerPageOnlyEnum,
|
||||
"""
|
||||
[{|#0:AutoUiPage("MainMenu")|}]
|
||||
public partial class MainMenu : Control
|
||||
{
|
||||
}
|
||||
""");
|
||||
|
||||
var test = new CSharpSourceGeneratorTest<AutoUiPageGenerator, DefaultVerifier>
|
||||
{
|
||||
@ -174,74 +141,25 @@ public class AutoUiPageGeneratorTests
|
||||
"MainMenu",
|
||||
"a string key argument and a string UiLayer name argument"));
|
||||
|
||||
await test.RunAsync();
|
||||
await test.RunAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Generates_Type_Constraints_For_ClassNullable_NotNull_And_Unmanaged()
|
||||
{
|
||||
const string source = """
|
||||
#nullable enable
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoUiPageAttribute : Attribute
|
||||
{
|
||||
public AutoUiPageAttribute(string key, string layerName) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
public class Node { }
|
||||
public class CanvasItem : Node { }
|
||||
public class Control : CanvasItem { }
|
||||
}
|
||||
|
||||
namespace GFramework.Game.Abstractions.Enums
|
||||
{
|
||||
public enum UiLayer
|
||||
{
|
||||
Page
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Game.Abstractions.UI
|
||||
{
|
||||
public interface IUiPageBehavior { }
|
||||
}
|
||||
|
||||
namespace GFramework.Godot.UI
|
||||
{
|
||||
using GFramework.Game.Abstractions.Enums;
|
||||
using GFramework.Game.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
public static class UiPageBehaviorFactory
|
||||
{
|
||||
public static IUiPageBehavior Create<T>(T owner, string key, UiLayer layer)
|
||||
where T : CanvasItem
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
[AutoUiPage("MainMenu", "Page")]
|
||||
public partial class MainMenu<TReference, TNotNull, TUnmanaged> : Control
|
||||
where TReference : class?
|
||||
where TNotNull : notnull
|
||||
where TUnmanaged : unmanaged
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
string source = CreateAutoUiPageSource(
|
||||
AutoUiPageAttributeWithLayerDeclaration,
|
||||
UiLayerPageOnlyEnum,
|
||||
"""
|
||||
[AutoUiPage("MainMenu", "Page")]
|
||||
public partial class MainMenu<TReference, TNotNull, TUnmanaged> : Control
|
||||
where TReference : class?
|
||||
where TNotNull : notnull
|
||||
where TUnmanaged : unmanaged
|
||||
{
|
||||
}
|
||||
""",
|
||||
nullableEnabled: true);
|
||||
|
||||
const string expected = """
|
||||
// <auto-generated />
|
||||
@ -268,6 +186,40 @@ public class AutoUiPageGeneratorTests
|
||||
|
||||
await GeneratorTest<AutoUiPageGenerator>.RunAsync(
|
||||
source,
|
||||
("TestApp_MainMenu.AutoUiPage.g.cs", expected));
|
||||
("TestApp_MainMenu.AutoUiPage.g.cs", expected)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static string CreateAutoUiPageSource(
|
||||
string attributeDeclaration,
|
||||
string uiLayerDeclaration,
|
||||
string testAppSource,
|
||||
bool nullableEnabled = false)
|
||||
{
|
||||
string nullableDirective = nullableEnabled ? "#nullable enable\n" : string.Empty;
|
||||
|
||||
return $$"""
|
||||
{{nullableDirective}}using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
{{attributeDeclaration}}
|
||||
}
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
{{CanvasNodeTypes}}
|
||||
}
|
||||
|
||||
{{uiLayerDeclaration}}
|
||||
|
||||
{{UiBehaviorInfrastructure}}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
{{testAppSource}}
|
||||
}
|
||||
""";
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,93 +8,103 @@ namespace GFramework.Godot.SourceGenerators.Tests.BindNodeSignal;
|
||||
[TestFixture]
|
||||
public class BindNodeSignalGeneratorTests
|
||||
{
|
||||
private const string BindNodeSignalAttributeDeclaration = """
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class BindNodeSignalAttribute : Attribute
|
||||
{
|
||||
public BindNodeSignalAttribute(string nodeFieldName, string signalName)
|
||||
{
|
||||
NodeFieldName = nodeFieldName;
|
||||
SignalName = signalName;
|
||||
}
|
||||
|
||||
public string NodeFieldName { get; }
|
||||
|
||||
public string SignalName { get; }
|
||||
}
|
||||
""";
|
||||
|
||||
private const string GetNodeAttributeDeclaration = """
|
||||
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class GetNodeAttribute : Attribute
|
||||
{
|
||||
}
|
||||
""";
|
||||
|
||||
private const string EmptyNodeType = """
|
||||
public class Node
|
||||
{
|
||||
}
|
||||
""";
|
||||
|
||||
private const string LifecycleNodeType = """
|
||||
public class Node
|
||||
{
|
||||
public virtual void _Ready() {}
|
||||
|
||||
public virtual void _ExitTree() {}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string ButtonType = """
|
||||
public class Button : Node
|
||||
{
|
||||
public event Action? Pressed
|
||||
{
|
||||
add {}
|
||||
remove {}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string SpinBoxType = """
|
||||
public class SpinBox : Node
|
||||
{
|
||||
public delegate void ValueChangedEventHandler(double value);
|
||||
|
||||
public event ValueChangedEventHandler? ValueChanged
|
||||
{
|
||||
add {}
|
||||
remove {}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
/// <summary>
|
||||
/// 验证生成器会为已有生命周期调用生成成对的绑定与解绑方法。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Generates_Bind_And_Unbind_Methods_For_Existing_Lifecycle_Hooks()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using Godot;
|
||||
string source = CreateHudSource(
|
||||
CreateAbstractionsSource(BindNodeSignalAttributeDeclaration),
|
||||
"""
|
||||
private Button _startButton = null!;
|
||||
private SpinBox _startOreSpinBox = null!;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class BindNodeSignalAttribute : Attribute
|
||||
{
|
||||
public BindNodeSignalAttribute(string nodeFieldName, string signalName)
|
||||
{
|
||||
NodeFieldName = nodeFieldName;
|
||||
SignalName = signalName;
|
||||
}
|
||||
[BindNodeSignal(nameof(_startButton), nameof(Button.Pressed))]
|
||||
private void OnStartButtonPressed()
|
||||
{
|
||||
}
|
||||
|
||||
public string NodeFieldName { get; }
|
||||
[BindNodeSignal(nameof(_startOreSpinBox), nameof(SpinBox.ValueChanged))]
|
||||
private void OnStartOreValueChanged(double value)
|
||||
{
|
||||
}
|
||||
|
||||
public string SignalName { get; }
|
||||
}
|
||||
}
|
||||
public override void _Ready()
|
||||
{
|
||||
__BindNodeSignals_Generated();
|
||||
}
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
public class Node
|
||||
{
|
||||
public virtual void _Ready() {}
|
||||
|
||||
public virtual void _ExitTree() {}
|
||||
}
|
||||
|
||||
public class Button : Node
|
||||
{
|
||||
public event Action? Pressed
|
||||
{
|
||||
add {}
|
||||
remove {}
|
||||
}
|
||||
}
|
||||
|
||||
public class SpinBox : Node
|
||||
{
|
||||
public delegate void ValueChangedEventHandler(double value);
|
||||
|
||||
public event ValueChangedEventHandler? ValueChanged
|
||||
{
|
||||
add {}
|
||||
remove {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
public partial class Hud : Node
|
||||
{
|
||||
private Button _startButton = null!;
|
||||
private SpinBox _startOreSpinBox = null!;
|
||||
|
||||
[BindNodeSignal(nameof(_startButton), nameof(Button.Pressed))]
|
||||
private void OnStartButtonPressed()
|
||||
{
|
||||
}
|
||||
|
||||
[BindNodeSignal(nameof(_startOreSpinBox), nameof(SpinBox.ValueChanged))]
|
||||
private void OnStartOreValueChanged(double value)
|
||||
{
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
__BindNodeSignals_Generated();
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
__UnbindNodeSignals_Generated();
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
public override void _ExitTree()
|
||||
{
|
||||
__UnbindNodeSignals_Generated();
|
||||
}
|
||||
""",
|
||||
LifecycleNodeType,
|
||||
ButtonType,
|
||||
SpinBoxType);
|
||||
|
||||
const string expected = """
|
||||
// <auto-generated />
|
||||
@ -121,7 +131,7 @@ public class BindNodeSignalGeneratorTests
|
||||
|
||||
await GeneratorTest<BindNodeSignalGenerator>.RunAsync(
|
||||
source,
|
||||
("TestApp_Hud.BindNodeSignal.g.cs", expected));
|
||||
("TestApp_Hud.BindNodeSignal.g.cs", expected)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -130,70 +140,23 @@ public class BindNodeSignalGeneratorTests
|
||||
[Test]
|
||||
public async Task Generates_Multiple_Subscriptions_For_The_Same_Handler_And_Coexists_With_GetNode()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using Godot;
|
||||
string source = CreateHudSource(
|
||||
CreateAbstractionsSource(BindNodeSignalAttributeDeclaration, GetNodeAttributeDeclaration),
|
||||
"""
|
||||
[GetNode]
|
||||
private Button _startButton = null!;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class BindNodeSignalAttribute : Attribute
|
||||
{
|
||||
public BindNodeSignalAttribute(string nodeFieldName, string signalName)
|
||||
{
|
||||
NodeFieldName = nodeFieldName;
|
||||
SignalName = signalName;
|
||||
}
|
||||
[GetNode]
|
||||
private Button _cancelButton = null!;
|
||||
|
||||
public string NodeFieldName { get; }
|
||||
|
||||
public string SignalName { get; }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class GetNodeAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
public class Node
|
||||
{
|
||||
public virtual void _Ready() {}
|
||||
|
||||
public virtual void _ExitTree() {}
|
||||
}
|
||||
|
||||
public class Button : Node
|
||||
{
|
||||
public event Action? Pressed
|
||||
{
|
||||
add {}
|
||||
remove {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
public partial class Hud : Node
|
||||
{
|
||||
[GetNode]
|
||||
private Button _startButton = null!;
|
||||
|
||||
[GetNode]
|
||||
private Button _cancelButton = null!;
|
||||
|
||||
[BindNodeSignal(nameof(_startButton), nameof(Button.Pressed))]
|
||||
[BindNodeSignal(nameof(_cancelButton), nameof(Button.Pressed))]
|
||||
private void OnAnyButtonPressed()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
[BindNodeSignal(nameof(_startButton), nameof(Button.Pressed))]
|
||||
[BindNodeSignal(nameof(_cancelButton), nameof(Button.Pressed))]
|
||||
private void OnAnyButtonPressed()
|
||||
{
|
||||
}
|
||||
""",
|
||||
LifecycleNodeType,
|
||||
ButtonType);
|
||||
|
||||
const string expected = """
|
||||
// <auto-generated />
|
||||
@ -220,7 +183,7 @@ public class BindNodeSignalGeneratorTests
|
||||
|
||||
await GeneratorTest<BindNodeSignalGenerator>.RunAsync(
|
||||
source,
|
||||
("TestApp_Hud.BindNodeSignal.g.cs", expected));
|
||||
("TestApp_Hud.BindNodeSignal.g.cs", expected)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -229,73 +192,24 @@ public class BindNodeSignalGeneratorTests
|
||||
[Test]
|
||||
public async Task Reports_Diagnostic_When_Signal_Does_Not_Exist()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using Godot;
|
||||
string source = CreateHudSource(
|
||||
CreateAbstractionsSource(BindNodeSignalAttributeDeclaration),
|
||||
"""
|
||||
private Button _startButton = null!;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class BindNodeSignalAttribute : Attribute
|
||||
{
|
||||
public BindNodeSignalAttribute(string nodeFieldName, string signalName)
|
||||
{
|
||||
NodeFieldName = nodeFieldName;
|
||||
SignalName = signalName;
|
||||
}
|
||||
[{|#0:BindNodeSignal(nameof(_startButton), "Released")|}]
|
||||
private void OnStartButtonPressed()
|
||||
{
|
||||
}
|
||||
""",
|
||||
EmptyNodeType,
|
||||
ButtonType);
|
||||
|
||||
public string NodeFieldName { get; }
|
||||
|
||||
public string SignalName { get; }
|
||||
}
|
||||
}
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
public class Node
|
||||
{
|
||||
}
|
||||
|
||||
public class Button : Node
|
||||
{
|
||||
public event Action? Pressed
|
||||
{
|
||||
add {}
|
||||
remove {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
public partial class Hud : Node
|
||||
{
|
||||
private Button _startButton = null!;
|
||||
|
||||
[{|#0:BindNodeSignal(nameof(_startButton), "Released")|}]
|
||||
private void OnStartButtonPressed()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
var test = new CSharpSourceGeneratorTest<BindNodeSignalGenerator, DefaultVerifier>
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { source }
|
||||
},
|
||||
DisabledDiagnostics = { "GF_Common_Trace_001" },
|
||||
TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
|
||||
};
|
||||
|
||||
test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_Godot_BindNodeSignal_006", DiagnosticSeverity.Error)
|
||||
.WithLocation(0)
|
||||
.WithArguments("_startButton", "Released"));
|
||||
|
||||
await test.RunAsync();
|
||||
await VerifyDiagnosticsAsync(
|
||||
source,
|
||||
new DiagnosticResult("GF_Godot_BindNodeSignal_006", DiagnosticSeverity.Error)
|
||||
.WithLocation(0)
|
||||
.WithArguments("_startButton", "Released")).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -304,75 +218,24 @@ public class BindNodeSignalGeneratorTests
|
||||
[Test]
|
||||
public async Task Reports_Diagnostic_When_Method_Signature_Does_Not_Match_Event()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using Godot;
|
||||
string source = CreateHudSource(
|
||||
CreateAbstractionsSource(BindNodeSignalAttributeDeclaration),
|
||||
"""
|
||||
private SpinBox _startOreSpinBox = null!;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class BindNodeSignalAttribute : Attribute
|
||||
{
|
||||
public BindNodeSignalAttribute(string nodeFieldName, string signalName)
|
||||
{
|
||||
NodeFieldName = nodeFieldName;
|
||||
SignalName = signalName;
|
||||
}
|
||||
[{|#0:BindNodeSignal(nameof(_startOreSpinBox), nameof(SpinBox.ValueChanged))|}]
|
||||
private void OnStartOreValueChanged()
|
||||
{
|
||||
}
|
||||
""",
|
||||
EmptyNodeType,
|
||||
SpinBoxType);
|
||||
|
||||
public string NodeFieldName { get; }
|
||||
|
||||
public string SignalName { get; }
|
||||
}
|
||||
}
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
public class Node
|
||||
{
|
||||
}
|
||||
|
||||
public class SpinBox : Node
|
||||
{
|
||||
public delegate void ValueChangedEventHandler(double value);
|
||||
|
||||
public event ValueChangedEventHandler? ValueChanged
|
||||
{
|
||||
add {}
|
||||
remove {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
public partial class Hud : Node
|
||||
{
|
||||
private SpinBox _startOreSpinBox = null!;
|
||||
|
||||
[{|#0:BindNodeSignal(nameof(_startOreSpinBox), nameof(SpinBox.ValueChanged))|}]
|
||||
private void OnStartOreValueChanged()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
var test = new CSharpSourceGeneratorTest<BindNodeSignalGenerator, DefaultVerifier>
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { source }
|
||||
},
|
||||
DisabledDiagnostics = { "GF_Common_Trace_001" },
|
||||
TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
|
||||
};
|
||||
|
||||
test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_Godot_BindNodeSignal_007", DiagnosticSeverity.Error)
|
||||
.WithLocation(0)
|
||||
.WithArguments("OnStartOreValueChanged", "ValueChanged", "_startOreSpinBox"));
|
||||
|
||||
await test.RunAsync();
|
||||
await VerifyDiagnosticsAsync(
|
||||
source,
|
||||
new DiagnosticResult("GF_Godot_BindNodeSignal_007", DiagnosticSeverity.Error)
|
||||
.WithLocation(0)
|
||||
.WithArguments("OnStartOreValueChanged", "ValueChanged", "_startOreSpinBox")).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -381,73 +244,24 @@ public class BindNodeSignalGeneratorTests
|
||||
[Test]
|
||||
public async Task Reports_Diagnostic_When_Constructor_Argument_Is_Empty()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using Godot;
|
||||
string source = CreateHudSource(
|
||||
CreateAbstractionsSource(BindNodeSignalAttributeDeclaration),
|
||||
"""
|
||||
private Button _startButton = null!;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class BindNodeSignalAttribute : Attribute
|
||||
{
|
||||
public BindNodeSignalAttribute(string nodeFieldName, string signalName)
|
||||
{
|
||||
NodeFieldName = nodeFieldName;
|
||||
SignalName = signalName;
|
||||
}
|
||||
[{|#0:BindNodeSignal(nameof(_startButton), "")|}]
|
||||
private void OnStartButtonPressed()
|
||||
{
|
||||
}
|
||||
""",
|
||||
EmptyNodeType,
|
||||
ButtonType);
|
||||
|
||||
public string NodeFieldName { get; }
|
||||
|
||||
public string SignalName { get; }
|
||||
}
|
||||
}
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
public class Node
|
||||
{
|
||||
}
|
||||
|
||||
public class Button : Node
|
||||
{
|
||||
public event Action? Pressed
|
||||
{
|
||||
add {}
|
||||
remove {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
public partial class Hud : Node
|
||||
{
|
||||
private Button _startButton = null!;
|
||||
|
||||
[{|#0:BindNodeSignal(nameof(_startButton), "")|}]
|
||||
private void OnStartButtonPressed()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
var test = new CSharpSourceGeneratorTest<BindNodeSignalGenerator, DefaultVerifier>
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { source }
|
||||
},
|
||||
DisabledDiagnostics = { "GF_Common_Trace_001" },
|
||||
TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
|
||||
};
|
||||
|
||||
test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_Godot_BindNodeSignal_010", DiagnosticSeverity.Error)
|
||||
.WithLocation(0)
|
||||
.WithArguments("OnStartButtonPressed", "signalName"));
|
||||
|
||||
await test.RunAsync();
|
||||
await VerifyDiagnosticsAsync(
|
||||
source,
|
||||
new DiagnosticResult("GF_Godot_BindNodeSignal_010", DiagnosticSeverity.Error)
|
||||
.WithLocation(0)
|
||||
.WithArguments("OnStartButtonPressed", "signalName")).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -456,85 +270,35 @@ public class BindNodeSignalGeneratorTests
|
||||
[Test]
|
||||
public async Task Reports_Diagnostic_When_Generated_Method_Names_Already_Exist()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using Godot;
|
||||
string source = CreateHudSource(
|
||||
CreateAbstractionsSource(BindNodeSignalAttributeDeclaration),
|
||||
"""
|
||||
private Button _startButton = null!;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class BindNodeSignalAttribute : Attribute
|
||||
{
|
||||
public BindNodeSignalAttribute(string nodeFieldName, string signalName)
|
||||
{
|
||||
NodeFieldName = nodeFieldName;
|
||||
SignalName = signalName;
|
||||
}
|
||||
[BindNodeSignal(nameof(_startButton), nameof(Button.Pressed))]
|
||||
private void OnStartButtonPressed()
|
||||
{
|
||||
}
|
||||
|
||||
public string NodeFieldName { get; }
|
||||
private void {|#0:__BindNodeSignals_Generated|}()
|
||||
{
|
||||
}
|
||||
|
||||
public string SignalName { get; }
|
||||
}
|
||||
}
|
||||
private void {|#1:__UnbindNodeSignals_Generated|}()
|
||||
{
|
||||
}
|
||||
""",
|
||||
EmptyNodeType,
|
||||
ButtonType);
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
public class Node
|
||||
{
|
||||
}
|
||||
|
||||
public class Button : Node
|
||||
{
|
||||
public event Action? Pressed
|
||||
{
|
||||
add {}
|
||||
remove {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
public partial class Hud : Node
|
||||
{
|
||||
private Button _startButton = null!;
|
||||
|
||||
[BindNodeSignal(nameof(_startButton), nameof(Button.Pressed))]
|
||||
private void OnStartButtonPressed()
|
||||
{
|
||||
}
|
||||
|
||||
private void {|#0:__BindNodeSignals_Generated|}()
|
||||
{
|
||||
}
|
||||
|
||||
private void {|#1:__UnbindNodeSignals_Generated|}()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
var test = new CSharpSourceGeneratorTest<BindNodeSignalGenerator, DefaultVerifier>
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { source }
|
||||
},
|
||||
DisabledDiagnostics = { "GF_Common_Trace_001" },
|
||||
TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
|
||||
};
|
||||
|
||||
test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_Common_Class_002", DiagnosticSeverity.Error)
|
||||
.WithLocation(0)
|
||||
.WithArguments("Hud", "__BindNodeSignals_Generated"));
|
||||
|
||||
test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_Common_Class_002", DiagnosticSeverity.Error)
|
||||
.WithLocation(1)
|
||||
.WithArguments("Hud", "__UnbindNodeSignals_Generated"));
|
||||
|
||||
await test.RunAsync();
|
||||
await VerifyDiagnosticsAsync(
|
||||
source,
|
||||
new DiagnosticResult("GF_Common_Class_002", DiagnosticSeverity.Error)
|
||||
.WithLocation(0)
|
||||
.WithArguments("Hud", "__BindNodeSignals_Generated"),
|
||||
new DiagnosticResult("GF_Common_Class_002", DiagnosticSeverity.Error)
|
||||
.WithLocation(1)
|
||||
.WithArguments("Hud", "__UnbindNodeSignals_Generated")).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -543,69 +307,80 @@ public class BindNodeSignalGeneratorTests
|
||||
[Test]
|
||||
public async Task Reports_Warnings_When_Lifecycle_Methods_Do_Not_Call_Generated_Methods()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using Godot;
|
||||
string source = CreateHudSource(
|
||||
CreateAbstractionsSource(BindNodeSignalAttributeDeclaration),
|
||||
"""
|
||||
private Button _startButton = null!;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class BindNodeSignalAttribute : Attribute
|
||||
{
|
||||
public BindNodeSignalAttribute(string nodeFieldName, string signalName)
|
||||
{
|
||||
NodeFieldName = nodeFieldName;
|
||||
SignalName = signalName;
|
||||
}
|
||||
[BindNodeSignal(nameof(_startButton), nameof(Button.Pressed))]
|
||||
private void OnStartButtonPressed()
|
||||
{
|
||||
}
|
||||
|
||||
public string NodeFieldName { get; }
|
||||
public override void {|#0:_Ready|}()
|
||||
{
|
||||
}
|
||||
|
||||
public string SignalName { get; }
|
||||
}
|
||||
}
|
||||
public override void {|#1:_ExitTree|}()
|
||||
{
|
||||
}
|
||||
""",
|
||||
LifecycleNodeType,
|
||||
ButtonType);
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
public class Node
|
||||
{
|
||||
public virtual void _Ready() {}
|
||||
await VerifyDiagnosticsAsync(
|
||||
source,
|
||||
new DiagnosticResult("GF_Godot_BindNodeSignal_008", DiagnosticSeverity.Warning)
|
||||
.WithLocation(0)
|
||||
.WithArguments("Hud"),
|
||||
new DiagnosticResult("GF_Godot_BindNodeSignal_009", DiagnosticSeverity.Warning)
|
||||
.WithLocation(1)
|
||||
.WithArguments("Hud")).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public virtual void _ExitTree() {}
|
||||
}
|
||||
private static string CreateAbstractionsSource(params string[] attributeDeclarations)
|
||||
{
|
||||
string declarations = string.Join($"{Environment.NewLine}{Environment.NewLine}", attributeDeclarations);
|
||||
|
||||
public class Button : Node
|
||||
{
|
||||
public event Action? Pressed
|
||||
{
|
||||
add {}
|
||||
remove {}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $$"""
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
{
|
||||
{{declarations}}
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
public partial class Hud : Node
|
||||
{
|
||||
private Button _startButton = null!;
|
||||
private static string CreateHudSource(
|
||||
string abstractionsSource,
|
||||
string hudMembers,
|
||||
params string[] godotTypes)
|
||||
{
|
||||
string godotSource = string.Join($"{Environment.NewLine}{Environment.NewLine}", godotTypes);
|
||||
|
||||
[BindNodeSignal(nameof(_startButton), nameof(Button.Pressed))]
|
||||
private void OnStartButtonPressed()
|
||||
{
|
||||
}
|
||||
return $$"""
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using Godot;
|
||||
|
||||
public override void {|#0:_Ready|}()
|
||||
{
|
||||
}
|
||||
{{abstractionsSource}}
|
||||
|
||||
public override void {|#1:_ExitTree|}()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
namespace Godot
|
||||
{
|
||||
{{godotSource}}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
public partial class Hud : Node
|
||||
{
|
||||
{{hudMembers}}
|
||||
}
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
private static Task VerifyDiagnosticsAsync(string source, params DiagnosticResult[] expectedDiagnostics)
|
||||
{
|
||||
var test = new CSharpSourceGeneratorTest<BindNodeSignalGenerator, DefaultVerifier>
|
||||
{
|
||||
TestState =
|
||||
@ -616,14 +391,11 @@ public class BindNodeSignalGeneratorTests
|
||||
TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
|
||||
};
|
||||
|
||||
test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_Godot_BindNodeSignal_008", DiagnosticSeverity.Warning)
|
||||
.WithLocation(0)
|
||||
.WithArguments("Hud"));
|
||||
foreach (DiagnosticResult expectedDiagnostic in expectedDiagnostics)
|
||||
{
|
||||
test.ExpectedDiagnostics.Add(expectedDiagnostic);
|
||||
}
|
||||
|
||||
test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_Godot_BindNodeSignal_009", DiagnosticSeverity.Warning)
|
||||
.WithLocation(1)
|
||||
.WithArguments("Hud"));
|
||||
|
||||
await test.RunAsync();
|
||||
return test.RunAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ public static class GeneratorTest<TGenerator>
|
||||
test.TestState.GeneratedSources.Add(
|
||||
(typeof(TGenerator), filename, NormalizeLineEndings(content)));
|
||||
|
||||
await test.RunAsync();
|
||||
await test.RunAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -44,4 +44,4 @@ public static class GeneratorTest<TGenerator>
|
||||
.Replace("\r", "\n", StringComparison.Ordinal)
|
||||
.Replace("\n", Environment.NewLine, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,61 +5,88 @@ namespace GFramework.Godot.SourceGenerators.Tests.GetNode;
|
||||
[TestFixture]
|
||||
public class GetNodeGeneratorTests
|
||||
{
|
||||
private const string FullGetNodeAttributeDeclaration = """
|
||||
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class GetNodeAttribute : Attribute
|
||||
{
|
||||
public GetNodeAttribute() {}
|
||||
public GetNodeAttribute(string path) { Path = path; }
|
||||
public string? Path { get; set; }
|
||||
public bool Required { get; set; } = true;
|
||||
public NodeLookupMode Lookup { get; set; } = NodeLookupMode.Auto;
|
||||
}
|
||||
|
||||
public enum NodeLookupMode
|
||||
{
|
||||
Auto = 0,
|
||||
UniqueName = 1,
|
||||
RelativePath = 2,
|
||||
AbsolutePath = 3
|
||||
}
|
||||
""";
|
||||
|
||||
private const string MinimalGetNodeAttributeDeclaration = """
|
||||
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class GetNodeAttribute : Attribute
|
||||
{
|
||||
public GetNodeAttribute() {}
|
||||
}
|
||||
|
||||
public enum NodeLookupMode
|
||||
{
|
||||
Auto = 0
|
||||
}
|
||||
""";
|
||||
|
||||
private const string PropertyOnlyGetNodeAttributeDeclaration = """
|
||||
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class GetNodeAttribute : Attribute
|
||||
{
|
||||
public string? Path { get; set; }
|
||||
public bool Required { get; set; } = true;
|
||||
public NodeLookupMode Lookup { get; set; } = NodeLookupMode.Auto;
|
||||
}
|
||||
|
||||
public enum NodeLookupMode
|
||||
{
|
||||
Auto = 0,
|
||||
UniqueName = 1,
|
||||
RelativePath = 2,
|
||||
AbsolutePath = 3
|
||||
}
|
||||
""";
|
||||
|
||||
private const string NodeWithReadyAndLookupMethods = """
|
||||
public class Node
|
||||
{
|
||||
public virtual void _Ready() {}
|
||||
public T GetNode<T>(string path) where T : Node => throw new InvalidOperationException(path);
|
||||
public T? GetNodeOrNull<T>(string path) where T : Node => default;
|
||||
}
|
||||
""";
|
||||
|
||||
private const string HBoxContainerType = """
|
||||
public class HBoxContainer : Node
|
||||
{
|
||||
}
|
||||
""";
|
||||
|
||||
[Test]
|
||||
public async Task Generates_InferredUniqueNameBindings_And_ReadyHook_WhenReadyIsMissing()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using Godot;
|
||||
string source = CreateGetNodeSource(
|
||||
FullGetNodeAttributeDeclaration,
|
||||
"""
|
||||
public partial class TopBar : HBoxContainer
|
||||
{
|
||||
[GetNode]
|
||||
private HBoxContainer _leftContainer = null!;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class GetNodeAttribute : Attribute
|
||||
{
|
||||
public GetNodeAttribute() {}
|
||||
public GetNodeAttribute(string path) { Path = path; }
|
||||
public string? Path { get; set; }
|
||||
public bool Required { get; set; } = true;
|
||||
public NodeLookupMode Lookup { get; set; } = NodeLookupMode.Auto;
|
||||
}
|
||||
|
||||
public enum NodeLookupMode
|
||||
{
|
||||
Auto = 0,
|
||||
UniqueName = 1,
|
||||
RelativePath = 2,
|
||||
AbsolutePath = 3
|
||||
}
|
||||
}
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
public class Node
|
||||
{
|
||||
public virtual void _Ready() {}
|
||||
public T GetNode<T>(string path) where T : Node => throw new InvalidOperationException(path);
|
||||
public T? GetNodeOrNull<T>(string path) where T : Node => default;
|
||||
}
|
||||
|
||||
public class HBoxContainer : Node
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
public partial class TopBar : HBoxContainer
|
||||
{
|
||||
[GetNode]
|
||||
private HBoxContainer _leftContainer = null!;
|
||||
|
||||
[GetNode]
|
||||
private HBoxContainer m_rightContainer = null!;
|
||||
}
|
||||
}
|
||||
""";
|
||||
[GetNode]
|
||||
private HBoxContainer m_rightContainer = null!;
|
||||
}
|
||||
""",
|
||||
HBoxContainerType);
|
||||
|
||||
const string expected = """
|
||||
// <auto-generated />
|
||||
@ -88,69 +115,30 @@ public class GetNodeGeneratorTests
|
||||
|
||||
await GeneratorTest<GetNodeGenerator>.RunAsync(
|
||||
source,
|
||||
("TestApp_TopBar.GetNode.g.cs", expected));
|
||||
("TestApp_TopBar.GetNode.g.cs", expected)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Generates_ManualInjectionOnly_WhenReadyAlreadyExists()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using Godot;
|
||||
string source = CreateGetNodeSource(
|
||||
FullGetNodeAttributeDeclaration,
|
||||
"""
|
||||
public partial class TopBar : HBoxContainer
|
||||
{
|
||||
[GetNode("%LeftContainer")]
|
||||
private HBoxContainer _leftContainer = null!;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class GetNodeAttribute : Attribute
|
||||
{
|
||||
public GetNodeAttribute() {}
|
||||
public GetNodeAttribute(string path) { Path = path; }
|
||||
public string? Path { get; set; }
|
||||
public bool Required { get; set; } = true;
|
||||
public NodeLookupMode Lookup { get; set; } = NodeLookupMode.Auto;
|
||||
}
|
||||
[GetNode(Required = false, Lookup = NodeLookupMode.RelativePath)]
|
||||
private HBoxContainer? _rightContainer;
|
||||
|
||||
public enum NodeLookupMode
|
||||
{
|
||||
Auto = 0,
|
||||
UniqueName = 1,
|
||||
RelativePath = 2,
|
||||
AbsolutePath = 3
|
||||
}
|
||||
}
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
public class Node
|
||||
{
|
||||
public virtual void _Ready() {}
|
||||
public T GetNode<T>(string path) where T : Node => throw new InvalidOperationException(path);
|
||||
public T? GetNodeOrNull<T>(string path) where T : Node => default;
|
||||
}
|
||||
|
||||
public class HBoxContainer : Node
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
public partial class TopBar : HBoxContainer
|
||||
{
|
||||
[GetNode("%LeftContainer")]
|
||||
private HBoxContainer _leftContainer = null!;
|
||||
|
||||
[GetNode(Required = false, Lookup = NodeLookupMode.RelativePath)]
|
||||
private HBoxContainer? _rightContainer;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
__InjectGetNodes_Generated();
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
public override void _Ready()
|
||||
{
|
||||
__InjectGetNodes_Generated();
|
||||
}
|
||||
}
|
||||
""",
|
||||
HBoxContainerType);
|
||||
|
||||
const string expected = """
|
||||
// <auto-generated />
|
||||
@ -171,7 +159,7 @@ public class GetNodeGeneratorTests
|
||||
|
||||
await GeneratorTest<GetNodeGenerator>.RunAsync(
|
||||
source,
|
||||
("TestApp_TopBar.GetNode.g.cs", expected));
|
||||
("TestApp_TopBar.GetNode.g.cs", expected)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -234,58 +222,26 @@ public class GetNodeGeneratorTests
|
||||
.WithSpan(39, 24, 39, 38)
|
||||
.WithArguments("_leftContainer"));
|
||||
|
||||
await test.RunAsync();
|
||||
await test.RunAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Reports_Diagnostic_When_Generated_Injection_Method_Name_Already_Exists()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using Godot;
|
||||
string source = CreateGetNodeSource(
|
||||
MinimalGetNodeAttributeDeclaration,
|
||||
"""
|
||||
public partial class TopBar : HBoxContainer
|
||||
{
|
||||
[GetNode]
|
||||
private HBoxContainer _leftContainer = null!;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class GetNodeAttribute : Attribute
|
||||
{
|
||||
public GetNodeAttribute() {}
|
||||
}
|
||||
|
||||
public enum NodeLookupMode
|
||||
{
|
||||
Auto = 0
|
||||
}
|
||||
}
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
public class Node
|
||||
{
|
||||
public virtual void _Ready() {}
|
||||
public T GetNode<T>(string path) where T : Node => throw new InvalidOperationException(path);
|
||||
public T? GetNodeOrNull<T>(string path) where T : Node => default;
|
||||
}
|
||||
|
||||
public class HBoxContainer : Node
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
public partial class TopBar : HBoxContainer
|
||||
{
|
||||
[GetNode]
|
||||
private HBoxContainer _leftContainer = null!;
|
||||
|
||||
private void {|#0:__InjectGetNodes_Generated|}()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
private void {|#0:__InjectGetNodes_Generated|}()
|
||||
{
|
||||
}
|
||||
}
|
||||
""",
|
||||
HBoxContainerType);
|
||||
|
||||
var test = new CSharpSourceGeneratorTest<GetNodeGenerator, DefaultVerifier>
|
||||
{
|
||||
@ -301,6 +257,39 @@ public class GetNodeGeneratorTests
|
||||
.WithLocation(0)
|
||||
.WithArguments("TopBar", "__InjectGetNodes_Generated"));
|
||||
|
||||
await test.RunAsync();
|
||||
await test.RunAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static string CreateGetNodeSource(
|
||||
string attributeDeclaration,
|
||||
string testAppSource,
|
||||
params string[] godotTypes)
|
||||
{
|
||||
string[] allGodotTypes = new string[godotTypes.Length + 1];
|
||||
allGodotTypes[0] = NodeWithReadyAndLookupMethods;
|
||||
Array.Copy(godotTypes, 0, allGodotTypes, 1, godotTypes.Length);
|
||||
|
||||
string godotSource = string.Join($"{Environment.NewLine}{Environment.NewLine}", allGodotTypes);
|
||||
|
||||
return $$"""
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
{
|
||||
{{attributeDeclaration}}
|
||||
}
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
{{godotSource}}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
{{testAppSource}}
|
||||
}
|
||||
""";
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,131 @@ namespace GFramework.Godot.SourceGenerators.Tests.Project;
|
||||
[TestFixture]
|
||||
public class GodotProjectMetadataGeneratorTests
|
||||
{
|
||||
private const string AutoLoadProjectFile = """
|
||||
[autoload]
|
||||
GameServices="*res://autoload/game_services.tscn"
|
||||
AudioBus="*res://autoload/audio_bus.gd"
|
||||
""";
|
||||
|
||||
private const string InputActionsProjectFile = """
|
||||
[input]
|
||||
move_up={
|
||||
"deadzone": 0.5
|
||||
}
|
||||
ui_cancel={
|
||||
"deadzone": 0.5
|
||||
}
|
||||
""";
|
||||
|
||||
private const string ExpectedAutoLoads = """
|
||||
// <auto-generated />
|
||||
#nullable enable
|
||||
|
||||
namespace GFramework.Godot.Generated;
|
||||
|
||||
/// <summary>
|
||||
/// 提供 project.godot 中 AutoLoad 单例的强类型访问入口。
|
||||
/// </summary>
|
||||
public static partial class AutoLoads
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取 AutoLoad <c>GameServices</c>。
|
||||
/// </summary>
|
||||
public static global::TestApp.GameServices GameServices => GetRequiredNode<global::TestApp.GameServices>("GameServices");
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取 AutoLoad <c>GameServices</c>。
|
||||
/// </summary>
|
||||
public static bool TryGetGameServices(out global::TestApp.GameServices? value)
|
||||
{
|
||||
return TryGetNode("GameServices", out value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 AutoLoad <c>AudioBus</c>。
|
||||
/// </summary>
|
||||
public static global::Godot.Node AudioBus => GetRequiredNode<global::Godot.Node>("AudioBus");
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取 AutoLoad <c>AudioBus</c>。
|
||||
/// </summary>
|
||||
public static bool TryGetAudioBus(out global::Godot.Node? value)
|
||||
{
|
||||
return TryGetNode("AudioBus", out value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个必填的 AutoLoad 节点;缺失时抛出异常。
|
||||
/// </summary>
|
||||
/// <typeparam name="TNode">节点类型。</typeparam>
|
||||
/// <param name="autoLoadName">AutoLoad 名称。</param>
|
||||
/// <returns>已解析的 AutoLoad 节点。</returns>
|
||||
private static TNode GetRequiredNode<TNode>(string autoLoadName)
|
||||
where TNode : global::Godot.Node
|
||||
{
|
||||
if (TryGetNode(autoLoadName, out TNode? value))
|
||||
{
|
||||
return value!;
|
||||
}
|
||||
|
||||
throw new global::System.InvalidOperationException($"AutoLoad '{autoLoadName}' is not available on the active SceneTree root.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从当前 SceneTree 根节点解析 AutoLoad。
|
||||
/// </summary>
|
||||
/// <typeparam name="TNode">节点类型。</typeparam>
|
||||
/// <param name="autoLoadName">AutoLoad 名称。</param>
|
||||
/// <param name="value">解析到的节点实例。</param>
|
||||
/// <returns>若当前进程存在 SceneTree 且根节点中能解析到该 AutoLoad,则返回 <c>true</c>。</returns>
|
||||
private static bool TryGetNode<TNode>(string autoLoadName, out TNode? value)
|
||||
where TNode : global::Godot.Node
|
||||
{
|
||||
value = default;
|
||||
|
||||
if (global::Godot.Engine.GetMainLoop() is not global::Godot.SceneTree sceneTree)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var root = sceneTree.Root;
|
||||
if (root is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value = root.GetNodeOrNull<TNode>($"/root/{autoLoadName}");
|
||||
return value is not null;
|
||||
}
|
||||
}
|
||||
|
||||
""";
|
||||
|
||||
private const string ExpectedInputActions = """
|
||||
// <auto-generated />
|
||||
#nullable enable
|
||||
|
||||
namespace GFramework.Godot.Generated;
|
||||
|
||||
/// <summary>
|
||||
/// 提供 project.godot 中 Input Action 名称的强类型常量。
|
||||
/// </summary>
|
||||
public static partial class InputActions
|
||||
{
|
||||
/// <summary>
|
||||
/// Input Action <c>move_up</c> 的稳定名称。
|
||||
/// </summary>
|
||||
public const string MoveUp = "move_up";
|
||||
|
||||
/// <summary>
|
||||
/// Input Action <c>ui_cancel</c> 的稳定名称。
|
||||
/// </summary>
|
||||
public const string UiCancel = "ui_cancel";
|
||||
|
||||
}
|
||||
|
||||
""";
|
||||
|
||||
/// <summary>
|
||||
/// 验证会根据 AutoLoad 与 Input Action 生成稳定的强类型入口。
|
||||
/// </summary>
|
||||
@ -29,142 +154,19 @@ public class GodotProjectMetadataGeneratorTests
|
||||
""",
|
||||
includeAutoLoadAttribute: true);
|
||||
|
||||
const string projectFile = """
|
||||
[autoload]
|
||||
GameServices="*res://autoload/game_services.tscn"
|
||||
AudioBus="*res://autoload/audio_bus.gd"
|
||||
|
||||
[input]
|
||||
move_up={
|
||||
"deadzone": 0.5
|
||||
}
|
||||
ui_cancel={
|
||||
"deadzone": 0.5
|
||||
}
|
||||
""";
|
||||
|
||||
const string expectedAutoLoads = """
|
||||
// <auto-generated />
|
||||
#nullable enable
|
||||
|
||||
namespace GFramework.Godot.Generated;
|
||||
|
||||
/// <summary>
|
||||
/// 提供 project.godot 中 AutoLoad 单例的强类型访问入口。
|
||||
/// </summary>
|
||||
public static partial class AutoLoads
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取 AutoLoad <c>GameServices</c>。
|
||||
/// </summary>
|
||||
public static global::TestApp.GameServices GameServices => GetRequiredNode<global::TestApp.GameServices>("GameServices");
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取 AutoLoad <c>GameServices</c>。
|
||||
/// </summary>
|
||||
public static bool TryGetGameServices(out global::TestApp.GameServices? value)
|
||||
{
|
||||
return TryGetNode("GameServices", out value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 AutoLoad <c>AudioBus</c>。
|
||||
/// </summary>
|
||||
public static global::Godot.Node AudioBus => GetRequiredNode<global::Godot.Node>("AudioBus");
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取 AutoLoad <c>AudioBus</c>。
|
||||
/// </summary>
|
||||
public static bool TryGetAudioBus(out global::Godot.Node? value)
|
||||
{
|
||||
return TryGetNode("AudioBus", out value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个必填的 AutoLoad 节点;缺失时抛出异常。
|
||||
/// </summary>
|
||||
/// <typeparam name="TNode">节点类型。</typeparam>
|
||||
/// <param name="autoLoadName">AutoLoad 名称。</param>
|
||||
/// <returns>已解析的 AutoLoad 节点。</returns>
|
||||
private static TNode GetRequiredNode<TNode>(string autoLoadName)
|
||||
where TNode : global::Godot.Node
|
||||
{
|
||||
if (TryGetNode(autoLoadName, out TNode? value))
|
||||
{
|
||||
return value!;
|
||||
}
|
||||
|
||||
throw new global::System.InvalidOperationException($"AutoLoad '{autoLoadName}' is not available on the active SceneTree root.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从当前 SceneTree 根节点解析 AutoLoad。
|
||||
/// </summary>
|
||||
/// <typeparam name="TNode">节点类型。</typeparam>
|
||||
/// <param name="autoLoadName">AutoLoad 名称。</param>
|
||||
/// <param name="value">解析到的节点实例。</param>
|
||||
/// <returns>若当前进程存在 SceneTree 且根节点中能解析到该 AutoLoad,则返回 <c>true</c>。</returns>
|
||||
private static bool TryGetNode<TNode>(string autoLoadName, out TNode? value)
|
||||
where TNode : global::Godot.Node
|
||||
{
|
||||
value = default;
|
||||
|
||||
if (global::Godot.Engine.GetMainLoop() is not global::Godot.SceneTree sceneTree)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var root = sceneTree.Root;
|
||||
if (root is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value = root.GetNodeOrNull<TNode>($"/root/{autoLoadName}");
|
||||
return value is not null;
|
||||
}
|
||||
}
|
||||
|
||||
""";
|
||||
|
||||
const string expectedInputActions = """
|
||||
// <auto-generated />
|
||||
#nullable enable
|
||||
|
||||
namespace GFramework.Godot.Generated;
|
||||
|
||||
/// <summary>
|
||||
/// 提供 project.godot 中 Input Action 名称的强类型常量。
|
||||
/// </summary>
|
||||
public static partial class InputActions
|
||||
{
|
||||
/// <summary>
|
||||
/// Input Action <c>move_up</c> 的稳定名称。
|
||||
/// </summary>
|
||||
public const string MoveUp = "move_up";
|
||||
|
||||
/// <summary>
|
||||
/// Input Action <c>ui_cancel</c> 的稳定名称。
|
||||
/// </summary>
|
||||
public const string UiCancel = "ui_cancel";
|
||||
|
||||
}
|
||||
|
||||
""";
|
||||
|
||||
var result = AdditionalTextGeneratorTestDriver.Run<GodotProjectMetadataGenerator>(
|
||||
source,
|
||||
("project.godot", projectFile));
|
||||
("project.godot", $"{AutoLoadProjectFile}\n\n{InputActionsProjectFile}"));
|
||||
|
||||
var generatedSources = AdditionalTextGeneratorTestDriver.ToGeneratedSourceMap(result);
|
||||
|
||||
Assert.That(result.Results.Single().Diagnostics, Is.Empty);
|
||||
Assert.That(
|
||||
generatedSources["GFramework_Godot_Generated_AutoLoads.g.cs"],
|
||||
Is.EqualTo(AdditionalTextGeneratorTestDriver.NormalizeLineEndings(expectedAutoLoads)));
|
||||
Is.EqualTo(AdditionalTextGeneratorTestDriver.NormalizeLineEndings(ExpectedAutoLoads)));
|
||||
Assert.That(
|
||||
generatedSources["GFramework_Godot_Generated_InputActions.g.cs"],
|
||||
Is.EqualTo(AdditionalTextGeneratorTestDriver.NormalizeLineEndings(expectedInputActions)));
|
||||
Is.EqualTo(AdditionalTextGeneratorTestDriver.NormalizeLineEndings(ExpectedInputActions)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -6,48 +6,52 @@ namespace GFramework.Godot.SourceGenerators.Tests.Registration;
|
||||
[TestFixture]
|
||||
public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
{
|
||||
private const string StandardAttributeDeclarations = """
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class RegisterExportedCollectionAttribute : Attribute
|
||||
{
|
||||
public RegisterExportedCollectionAttribute(string registryMemberName, string registerMethodName) { }
|
||||
}
|
||||
""";
|
||||
|
||||
private const string MultiDeclarationAttributeDeclarations = """
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class RegisterExportedCollectionAttribute : Attribute
|
||||
{
|
||||
public RegisterExportedCollectionAttribute(string registryMemberName, string registerMethodName) { }
|
||||
}
|
||||
""";
|
||||
|
||||
[Test]
|
||||
public async Task Generates_Batch_Registration_Method_For_Annotated_Collections()
|
||||
{
|
||||
const string source = """
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
string source = CreateSource(
|
||||
"""
|
||||
public sealed class IntRegistry
|
||||
{
|
||||
public void Register(int value) { }
|
||||
}
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
[AutoRegisterExportedCollections]
|
||||
public partial class Bootstrapper<TReference, TNotNull, TValue, TUnmanaged>
|
||||
where TReference : class?
|
||||
where TNotNull : notnull
|
||||
where TValue : struct
|
||||
where TUnmanaged : unmanaged
|
||||
{
|
||||
private readonly IntRegistry? _registry = new();
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class RegisterExportedCollectionAttribute : Attribute
|
||||
{
|
||||
public RegisterExportedCollectionAttribute(string registryMemberName, string registerMethodName) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
public sealed class IntRegistry
|
||||
{
|
||||
public void Register(int value) { }
|
||||
}
|
||||
|
||||
[AutoRegisterExportedCollections]
|
||||
public partial class Bootstrapper<TReference, TNotNull, TValue, TUnmanaged>
|
||||
where TReference : class?
|
||||
where TNotNull : notnull
|
||||
where TValue : struct
|
||||
where TUnmanaged : unmanaged
|
||||
{
|
||||
private readonly IntRegistry? _registry = new();
|
||||
|
||||
[RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
|
||||
public List<int>? Values { get; } = new();
|
||||
}
|
||||
}
|
||||
""";
|
||||
[RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
|
||||
public List<int>? Values { get; } = new();
|
||||
}
|
||||
""",
|
||||
nullableEnabled: true);
|
||||
|
||||
const string expected = """
|
||||
// <auto-generated />
|
||||
@ -77,7 +81,7 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
|
||||
await GeneratorTest<AutoRegisterExportedCollectionsGenerator>.RunAsync(
|
||||
source,
|
||||
("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected));
|
||||
("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -137,41 +141,23 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
[Test]
|
||||
public async Task Generates_Batch_Registration_Method_When_Register_Method_Uses_Array_Parameter()
|
||||
{
|
||||
const string source = """
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
string source = CreateSource(
|
||||
"""
|
||||
public sealed class ArrayRegistry
|
||||
{
|
||||
public void Register(int[] value) { }
|
||||
}
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
[AutoRegisterExportedCollections]
|
||||
public partial class Bootstrapper
|
||||
{
|
||||
private readonly ArrayRegistry _registry = new();
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class RegisterExportedCollectionAttribute : Attribute
|
||||
{
|
||||
public RegisterExportedCollectionAttribute(string registryMemberName, string registerMethodName) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
public sealed class ArrayRegistry
|
||||
{
|
||||
public void Register(int[] value) { }
|
||||
}
|
||||
|
||||
[AutoRegisterExportedCollections]
|
||||
public partial class Bootstrapper
|
||||
{
|
||||
private readonly ArrayRegistry _registry = new();
|
||||
|
||||
[RegisterExportedCollection(nameof(_registry), nameof(ArrayRegistry.Register))]
|
||||
public List<int[]> Values { get; } = new();
|
||||
}
|
||||
}
|
||||
""";
|
||||
[RegisterExportedCollection(nameof(_registry), nameof(ArrayRegistry.Register))]
|
||||
public List<int[]> Values { get; } = new();
|
||||
}
|
||||
""",
|
||||
nullableEnabled: true);
|
||||
|
||||
const string expected = """
|
||||
// <auto-generated />
|
||||
@ -197,59 +183,41 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
|
||||
await GeneratorTest<AutoRegisterExportedCollectionsGenerator>.RunAsync(
|
||||
source,
|
||||
("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected));
|
||||
("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Generates_Batch_Registration_Method_When_Register_Method_Comes_From_Inherited_Interface()
|
||||
{
|
||||
const string source = """
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
string source = CreateSource(
|
||||
"""
|
||||
public interface IKeyValue<TKey, TValue>
|
||||
{
|
||||
}
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
public interface IRegistry<TKey, TValue>
|
||||
{
|
||||
void Registry(IKeyValue<TKey, TValue> mapping);
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class RegisterExportedCollectionAttribute : Attribute
|
||||
{
|
||||
public RegisterExportedCollectionAttribute(string registryMemberName, string registerMethodName) { }
|
||||
}
|
||||
}
|
||||
public interface IAssetRegistry<TValue> : IRegistry<string, TValue>
|
||||
{
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
public interface IKeyValue<TKey, TValue>
|
||||
{
|
||||
}
|
||||
public sealed class IntConfig : IKeyValue<string, int>
|
||||
{
|
||||
}
|
||||
|
||||
public interface IRegistry<TKey, TValue>
|
||||
{
|
||||
void Registry(IKeyValue<TKey, TValue> mapping);
|
||||
}
|
||||
[AutoRegisterExportedCollections]
|
||||
public partial class Bootstrapper
|
||||
{
|
||||
private readonly IAssetRegistry<int>? _registry = null;
|
||||
|
||||
public interface IAssetRegistry<TValue> : IRegistry<string, TValue>
|
||||
{
|
||||
}
|
||||
|
||||
public sealed class IntConfig : IKeyValue<string, int>
|
||||
{
|
||||
}
|
||||
|
||||
[AutoRegisterExportedCollections]
|
||||
public partial class Bootstrapper
|
||||
{
|
||||
private readonly IAssetRegistry<int>? _registry = null;
|
||||
|
||||
[RegisterExportedCollection(nameof(_registry), "Registry")]
|
||||
public List<IntConfig>? Values { get; } = new();
|
||||
}
|
||||
}
|
||||
""";
|
||||
[RegisterExportedCollection(nameof(_registry), "Registry")]
|
||||
public List<IntConfig>? Values { get; } = new();
|
||||
}
|
||||
""",
|
||||
nullableEnabled: true);
|
||||
|
||||
const string expected = """
|
||||
// <auto-generated />
|
||||
@ -275,7 +243,7 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
|
||||
await GeneratorTest<AutoRegisterExportedCollectionsGenerator>.RunAsync(
|
||||
source,
|
||||
("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected));
|
||||
("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -340,45 +308,27 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
[Test]
|
||||
public async Task Generates_Batch_Registration_Method_When_Register_Method_Comes_From_Base_Class()
|
||||
{
|
||||
const string source = """
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
string source = CreateSource(
|
||||
"""
|
||||
public class BaseRegistry
|
||||
{
|
||||
public void Register(int value) { }
|
||||
}
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
public sealed class DerivedRegistry : BaseRegistry
|
||||
{
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class RegisterExportedCollectionAttribute : Attribute
|
||||
{
|
||||
public RegisterExportedCollectionAttribute(string registryMemberName, string registerMethodName) { }
|
||||
}
|
||||
}
|
||||
[AutoRegisterExportedCollections]
|
||||
public partial class Bootstrapper
|
||||
{
|
||||
private readonly DerivedRegistry? _registry = new();
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
public class BaseRegistry
|
||||
{
|
||||
public void Register(int value) { }
|
||||
}
|
||||
|
||||
public sealed class DerivedRegistry : BaseRegistry
|
||||
{
|
||||
}
|
||||
|
||||
[AutoRegisterExportedCollections]
|
||||
public partial class Bootstrapper
|
||||
{
|
||||
private readonly DerivedRegistry? _registry = new();
|
||||
|
||||
[RegisterExportedCollection(nameof(_registry), nameof(BaseRegistry.Register))]
|
||||
public List<int>? Values { get; } = new();
|
||||
}
|
||||
}
|
||||
""";
|
||||
[RegisterExportedCollection(nameof(_registry), nameof(BaseRegistry.Register))]
|
||||
public List<int>? Values { get; } = new();
|
||||
}
|
||||
""",
|
||||
nullableEnabled: true);
|
||||
|
||||
const string expected = """
|
||||
// <auto-generated />
|
||||
@ -404,50 +354,32 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
|
||||
await GeneratorTest<AutoRegisterExportedCollectionsGenerator>.RunAsync(
|
||||
source,
|
||||
("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected));
|
||||
("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Generates_Batch_Registration_Method_When_Registry_Member_Comes_From_Base_Class()
|
||||
{
|
||||
const string source = """
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
string source = CreateSource(
|
||||
"""
|
||||
public sealed class IntRegistry
|
||||
{
|
||||
public void Register(int value) { }
|
||||
}
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
public abstract class BootstrapperBase
|
||||
{
|
||||
protected readonly IntRegistry? _registry = new();
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class RegisterExportedCollectionAttribute : Attribute
|
||||
{
|
||||
public RegisterExportedCollectionAttribute(string registryMemberName, string registerMethodName) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
public sealed class IntRegistry
|
||||
{
|
||||
public void Register(int value) { }
|
||||
}
|
||||
|
||||
public abstract class BootstrapperBase
|
||||
{
|
||||
protected readonly IntRegistry? _registry = new();
|
||||
}
|
||||
|
||||
[AutoRegisterExportedCollections]
|
||||
public partial class Bootstrapper : BootstrapperBase
|
||||
{
|
||||
[RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
|
||||
public List<int>? Values { get; } = new();
|
||||
}
|
||||
}
|
||||
""";
|
||||
[AutoRegisterExportedCollections]
|
||||
public partial class Bootstrapper : BootstrapperBase
|
||||
{
|
||||
[RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
|
||||
public List<int>? Values { get; } = new();
|
||||
}
|
||||
""",
|
||||
nullableEnabled: true);
|
||||
|
||||
const string expected = """
|
||||
// <auto-generated />
|
||||
@ -473,74 +405,47 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
|
||||
await GeneratorTest<AutoRegisterExportedCollectionsGenerator>.RunAsync(
|
||||
source,
|
||||
("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected));
|
||||
("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Reports_Diagnostic_When_Collection_Member_Is_Not_Instance_Readable()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
string source = CreateSource(
|
||||
"""
|
||||
public sealed class IntRegistry
|
||||
{
|
||||
public void Register(int value) { }
|
||||
}
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
[AutoRegisterExportedCollections]
|
||||
public partial class Bootstrapper
|
||||
{
|
||||
private readonly IntRegistry _registry = new();
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class RegisterExportedCollectionAttribute : Attribute
|
||||
{
|
||||
public RegisterExportedCollectionAttribute(string registryMemberName, string registerMethodName) { }
|
||||
}
|
||||
}
|
||||
[RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
|
||||
public static List<int> {|#0:StaticValues|} = new();
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
public sealed class IntRegistry
|
||||
{
|
||||
public void Register(int value) { }
|
||||
}
|
||||
[RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
|
||||
public static List<int> {|#1:StaticPropertyValues|} { get; } = new();
|
||||
|
||||
[AutoRegisterExportedCollections]
|
||||
public partial class Bootstrapper
|
||||
{
|
||||
private readonly IntRegistry _registry = new();
|
||||
[RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
|
||||
public List<int> {|#2:WriteOnlyValues|} { set { } }
|
||||
}
|
||||
""");
|
||||
|
||||
[RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
|
||||
public static List<int> {|#0:StaticValues|} = new();
|
||||
|
||||
[RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
|
||||
public static List<int> {|#1:StaticPropertyValues|} { get; } = new();
|
||||
|
||||
[RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
|
||||
public List<int> {|#2:WriteOnlyValues|} { set { } }
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
var test = new CSharpSourceGeneratorTest<AutoRegisterExportedCollectionsGenerator, DefaultVerifier>
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { source }
|
||||
},
|
||||
DisabledDiagnostics = { "GF_Common_Trace_001" },
|
||||
TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
|
||||
};
|
||||
|
||||
test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_AutoExport_006", DiagnosticSeverity.Error)
|
||||
.WithLocation(0)
|
||||
.WithArguments("StaticValues"));
|
||||
test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_AutoExport_006", DiagnosticSeverity.Error)
|
||||
.WithLocation(1)
|
||||
.WithArguments("StaticPropertyValues"));
|
||||
test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_AutoExport_006", DiagnosticSeverity.Error)
|
||||
.WithLocation(2)
|
||||
.WithArguments("WriteOnlyValues"));
|
||||
|
||||
await test.RunAsync();
|
||||
await VerifyDiagnosticsAsync(
|
||||
source,
|
||||
skipGeneratedSourcesCheck: true,
|
||||
new DiagnosticResult("GF_AutoExport_006", DiagnosticSeverity.Error)
|
||||
.WithLocation(0)
|
||||
.WithArguments("StaticValues"),
|
||||
new DiagnosticResult("GF_AutoExport_006", DiagnosticSeverity.Error)
|
||||
.WithLocation(1)
|
||||
.WithArguments("StaticPropertyValues"),
|
||||
new DiagnosticResult("GF_AutoExport_006", DiagnosticSeverity.Error)
|
||||
.WithLocation(2)
|
||||
.WithArguments("WriteOnlyValues")).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -711,45 +616,28 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
[Test]
|
||||
public async Task Generates_Only_One_Source_When_Multiple_Partial_Declarations_Are_Annotated()
|
||||
{
|
||||
const string source = """
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
string source = CreateSource(
|
||||
"""
|
||||
public sealed class IntRegistry
|
||||
{
|
||||
public void Register(int value) { }
|
||||
}
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
[AutoRegisterExportedCollections]
|
||||
public partial class Bootstrapper
|
||||
{
|
||||
private readonly IntRegistry? _registry = new();
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class RegisterExportedCollectionAttribute : Attribute
|
||||
{
|
||||
public RegisterExportedCollectionAttribute(string registryMemberName, string registerMethodName) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
public sealed class IntRegistry
|
||||
{
|
||||
public void Register(int value) { }
|
||||
}
|
||||
|
||||
[AutoRegisterExportedCollections]
|
||||
public partial class Bootstrapper
|
||||
{
|
||||
private readonly IntRegistry? _registry = new();
|
||||
}
|
||||
|
||||
[AutoRegisterExportedCollections]
|
||||
public partial class Bootstrapper
|
||||
{
|
||||
[RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
|
||||
public List<int>? Values { get; } = new();
|
||||
}
|
||||
}
|
||||
""";
|
||||
[AutoRegisterExportedCollections]
|
||||
public partial class Bootstrapper
|
||||
{
|
||||
[RegisterExportedCollection(nameof(_registry), nameof(IntRegistry.Register))]
|
||||
public List<int>? Values { get; } = new();
|
||||
}
|
||||
""",
|
||||
nullableEnabled: true,
|
||||
allowMultipleDeclarations: true);
|
||||
|
||||
const string expected = """
|
||||
// <auto-generated />
|
||||
@ -775,6 +663,61 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
|
||||
await GeneratorTest<AutoRegisterExportedCollectionsGenerator>.RunAsync(
|
||||
source,
|
||||
("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected));
|
||||
("TestApp_Bootstrapper.AutoRegisterExportedCollections.g.cs", expected)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static string CreateSource(
|
||||
string applicationSource,
|
||||
bool nullableEnabled = false,
|
||||
bool allowMultipleDeclarations = false)
|
||||
{
|
||||
string nullableDirective = nullableEnabled ? "#nullable enable\n" : string.Empty;
|
||||
string attributeDeclarations = allowMultipleDeclarations
|
||||
? MultiDeclarationAttributeDeclarations
|
||||
: StandardAttributeDeclarations;
|
||||
|
||||
return $$"""
|
||||
{{nullableDirective}}using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
{{attributeDeclarations}}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
{{applicationSource}}
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
private static Task VerifyDiagnosticsAsync(
|
||||
string source,
|
||||
bool skipGeneratedSourcesCheck = false,
|
||||
params DiagnosticResult[] expectedDiagnostics)
|
||||
{
|
||||
var test = new CSharpSourceGeneratorTest<AutoRegisterExportedCollectionsGenerator, DefaultVerifier>
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { source }
|
||||
},
|
||||
DisabledDiagnostics = { "GF_Common_Trace_001" }
|
||||
};
|
||||
|
||||
if (skipGeneratedSourcesCheck)
|
||||
{
|
||||
test.TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck;
|
||||
}
|
||||
|
||||
foreach (DiagnosticResult expectedDiagnostic in expectedDiagnostics)
|
||||
{
|
||||
test.ExpectedDiagnostics.Add(expectedDiagnostic);
|
||||
}
|
||||
|
||||
return test.RunAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,19 +72,8 @@ public sealed class BindNodeSignalGenerator : IIncrementalGenerator
|
||||
if (bindNodeSignalAttribute is null || godotNodeSymbol is null)
|
||||
return;
|
||||
|
||||
// 缓存每个方法上已解析的特性,避免在筛选和生成阶段重复做语义查询。
|
||||
var methodAttributes = candidates
|
||||
.Where(static candidate => candidate is not null)
|
||||
.Select(static candidate => candidate!)
|
||||
.ToDictionary(
|
||||
static candidate => candidate,
|
||||
candidate => ResolveAttributes(candidate.MethodSymbol, bindNodeSignalAttribute),
|
||||
ReferenceEqualityComparer.Instance);
|
||||
|
||||
var methodCandidates = methodAttributes
|
||||
.Where(static pair => pair.Value.Count > 0)
|
||||
.Select(static pair => pair.Key)
|
||||
.ToList();
|
||||
var methodAttributes = BuildMethodAttributeMap(candidates, bindNodeSignalAttribute);
|
||||
var methodCandidates = CollectMethodCandidates(methodAttributes);
|
||||
|
||||
foreach (var group in GroupByContainingType(methodCandidates))
|
||||
{
|
||||
@ -99,19 +88,7 @@ public sealed class BindNodeSignalGenerator : IIncrementalGenerator
|
||||
UnbindMethodName))
|
||||
continue;
|
||||
|
||||
var bindings = new List<SignalBindingInfo>();
|
||||
|
||||
foreach (var candidate in group.Methods)
|
||||
{
|
||||
foreach (var attribute in methodAttributes[candidate])
|
||||
{
|
||||
if (!TryCreateBinding(context, candidate, attribute, godotNodeSymbol, out var binding))
|
||||
continue;
|
||||
|
||||
bindings.Add(binding);
|
||||
}
|
||||
}
|
||||
|
||||
var bindings = CollectBindings(context, group, methodAttributes, godotNodeSymbol);
|
||||
if (bindings.Count == 0)
|
||||
continue;
|
||||
|
||||
@ -171,99 +148,22 @@ public sealed class BindNodeSignalGenerator : IIncrementalGenerator
|
||||
|
||||
if (candidate.MethodSymbol.IsStatic)
|
||||
{
|
||||
ReportMethodDiagnostic(
|
||||
context,
|
||||
BindNodeSignalDiagnostics.StaticMethodNotSupported,
|
||||
candidate,
|
||||
attribute,
|
||||
candidate.MethodSymbol.Name);
|
||||
ReportStaticMethodDiagnostic(context, candidate, attribute);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryResolveCtorString(attribute, 0, out var nodeFieldName))
|
||||
{
|
||||
ReportMethodDiagnostic(
|
||||
context,
|
||||
BindNodeSignalDiagnostics.InvalidConstructorArgument,
|
||||
candidate,
|
||||
attribute,
|
||||
candidate.MethodSymbol.Name,
|
||||
"nodeFieldName");
|
||||
if (!TryResolveBindingTargetNames(context, candidate, attribute, out var nodeFieldName, out var signalName))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryResolveCtorString(attribute, 1, out var signalName))
|
||||
{
|
||||
ReportMethodDiagnostic(
|
||||
context,
|
||||
BindNodeSignalDiagnostics.InvalidConstructorArgument,
|
||||
candidate,
|
||||
attribute,
|
||||
candidate.MethodSymbol.Name,
|
||||
"signalName");
|
||||
if (!TryFindCompatibleField(context, candidate, attribute, godotNodeSymbol, nodeFieldName, out var fieldSymbol))
|
||||
return false;
|
||||
}
|
||||
|
||||
var fieldSymbol = FindField(candidate.MethodSymbol.ContainingType, nodeFieldName);
|
||||
if (fieldSymbol is null)
|
||||
{
|
||||
ReportMethodDiagnostic(
|
||||
context,
|
||||
BindNodeSignalDiagnostics.NodeFieldNotFound,
|
||||
candidate,
|
||||
attribute,
|
||||
candidate.MethodSymbol.Name,
|
||||
nodeFieldName,
|
||||
candidate.MethodSymbol.ContainingType.Name);
|
||||
if (!TryFindCompatibleEvent(context, candidate, attribute, fieldSymbol, signalName, out var eventSymbol))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fieldSymbol.IsStatic)
|
||||
{
|
||||
ReportMethodDiagnostic(
|
||||
context,
|
||||
BindNodeSignalDiagnostics.NodeFieldMustBeInstanceField,
|
||||
candidate,
|
||||
attribute,
|
||||
candidate.MethodSymbol.Name,
|
||||
fieldSymbol.Name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fieldSymbol.Type.IsAssignableTo(godotNodeSymbol))
|
||||
{
|
||||
ReportMethodDiagnostic(
|
||||
context,
|
||||
BindNodeSignalDiagnostics.FieldTypeMustDeriveFromNode,
|
||||
candidate,
|
||||
attribute,
|
||||
fieldSymbol.Name);
|
||||
return false;
|
||||
}
|
||||
|
||||
var eventSymbol = FindEvent(fieldSymbol.Type, signalName);
|
||||
if (eventSymbol is null)
|
||||
{
|
||||
ReportMethodDiagnostic(
|
||||
context,
|
||||
BindNodeSignalDiagnostics.SignalNotFound,
|
||||
candidate,
|
||||
attribute,
|
||||
fieldSymbol.Name,
|
||||
signalName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsMethodCompatibleWithEvent(candidate.MethodSymbol, eventSymbol))
|
||||
{
|
||||
ReportMethodDiagnostic(
|
||||
context,
|
||||
BindNodeSignalDiagnostics.MethodSignatureNotCompatible,
|
||||
candidate,
|
||||
attribute,
|
||||
candidate.MethodSymbol.Name,
|
||||
eventSymbol.Name,
|
||||
fieldSymbol.Name);
|
||||
ReportIncompatibleSignatureDiagnostic(context, candidate, attribute, eventSymbol, fieldSymbol);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -271,6 +171,235 @@ public sealed class BindNodeSignalGenerator : IIncrementalGenerator
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Dictionary<MethodCandidate, IReadOnlyList<AttributeData>> BuildMethodAttributeMap(
|
||||
ImmutableArray<MethodCandidate?> candidates,
|
||||
INamedTypeSymbol bindNodeSignalAttribute)
|
||||
{
|
||||
return candidates
|
||||
.Where(static candidate => candidate is not null)
|
||||
.Select(static candidate => candidate!)
|
||||
.ToDictionary(
|
||||
static candidate => candidate,
|
||||
candidate => ResolveAttributes(candidate.MethodSymbol, bindNodeSignalAttribute),
|
||||
ReferenceEqualityComparer.Instance);
|
||||
}
|
||||
|
||||
private static List<MethodCandidate> CollectMethodCandidates(
|
||||
IReadOnlyDictionary<MethodCandidate, IReadOnlyList<AttributeData>> methodAttributes)
|
||||
{
|
||||
return methodAttributes
|
||||
.Where(static pair => pair.Value.Count > 0)
|
||||
.Select(static pair => pair.Key)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static List<SignalBindingInfo> CollectBindings(
|
||||
SourceProductionContext context,
|
||||
TypeGroup group,
|
||||
IReadOnlyDictionary<MethodCandidate, IReadOnlyList<AttributeData>> methodAttributes,
|
||||
INamedTypeSymbol godotNodeSymbol)
|
||||
{
|
||||
var bindings = new List<SignalBindingInfo>();
|
||||
|
||||
foreach (var candidate in group.Methods)
|
||||
{
|
||||
foreach (var attribute in methodAttributes[candidate])
|
||||
{
|
||||
if (!TryCreateBinding(context, candidate, attribute, godotNodeSymbol, out var binding))
|
||||
continue;
|
||||
|
||||
bindings.Add(binding);
|
||||
}
|
||||
}
|
||||
|
||||
return bindings;
|
||||
}
|
||||
|
||||
private static void ReportStaticMethodDiagnostic(
|
||||
SourceProductionContext context,
|
||||
MethodCandidate candidate,
|
||||
AttributeData attribute)
|
||||
{
|
||||
ReportMethodDiagnostic(
|
||||
context,
|
||||
BindNodeSignalDiagnostics.StaticMethodNotSupported,
|
||||
candidate,
|
||||
attribute,
|
||||
candidate.MethodSymbol.Name);
|
||||
}
|
||||
|
||||
private static bool TryResolveBindingTargetNames(
|
||||
SourceProductionContext context,
|
||||
MethodCandidate candidate,
|
||||
AttributeData attribute,
|
||||
out string nodeFieldName,
|
||||
out string signalName)
|
||||
{
|
||||
nodeFieldName = string.Empty;
|
||||
signalName = string.Empty;
|
||||
|
||||
if (!TryResolveCtorString(attribute, 0, out nodeFieldName))
|
||||
{
|
||||
ReportInvalidConstructorArgumentDiagnostic(context, candidate, attribute, "nodeFieldName");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryResolveCtorString(attribute, 1, out signalName))
|
||||
{
|
||||
ReportInvalidConstructorArgumentDiagnostic(context, candidate, attribute, "signalName");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ReportInvalidConstructorArgumentDiagnostic(
|
||||
SourceProductionContext context,
|
||||
MethodCandidate candidate,
|
||||
AttributeData attribute,
|
||||
string argumentName)
|
||||
{
|
||||
ReportMethodDiagnostic(
|
||||
context,
|
||||
BindNodeSignalDiagnostics.InvalidConstructorArgument,
|
||||
candidate,
|
||||
attribute,
|
||||
candidate.MethodSymbol.Name,
|
||||
argumentName);
|
||||
}
|
||||
|
||||
private static bool TryFindCompatibleField(
|
||||
SourceProductionContext context,
|
||||
MethodCandidate candidate,
|
||||
AttributeData attribute,
|
||||
INamedTypeSymbol godotNodeSymbol,
|
||||
string nodeFieldName,
|
||||
out IFieldSymbol fieldSymbol)
|
||||
{
|
||||
fieldSymbol = null!;
|
||||
|
||||
var resolvedField = FindField(candidate.MethodSymbol.ContainingType, nodeFieldName);
|
||||
if (resolvedField is null)
|
||||
{
|
||||
ReportNodeFieldNotFoundDiagnostic(context, candidate, attribute, nodeFieldName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resolvedField.IsStatic)
|
||||
{
|
||||
ReportNodeFieldMustBeInstanceDiagnostic(context, candidate, attribute, resolvedField);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!resolvedField.Type.IsAssignableTo(godotNodeSymbol))
|
||||
{
|
||||
ReportFieldTypeMustDeriveFromNodeDiagnostic(context, candidate, attribute, resolvedField);
|
||||
return false;
|
||||
}
|
||||
|
||||
fieldSymbol = resolvedField;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ReportNodeFieldNotFoundDiagnostic(
|
||||
SourceProductionContext context,
|
||||
MethodCandidate candidate,
|
||||
AttributeData attribute,
|
||||
string nodeFieldName)
|
||||
{
|
||||
ReportMethodDiagnostic(
|
||||
context,
|
||||
BindNodeSignalDiagnostics.NodeFieldNotFound,
|
||||
candidate,
|
||||
attribute,
|
||||
candidate.MethodSymbol.Name,
|
||||
nodeFieldName,
|
||||
candidate.MethodSymbol.ContainingType.Name);
|
||||
}
|
||||
|
||||
private static void ReportNodeFieldMustBeInstanceDiagnostic(
|
||||
SourceProductionContext context,
|
||||
MethodCandidate candidate,
|
||||
AttributeData attribute,
|
||||
IFieldSymbol fieldSymbol)
|
||||
{
|
||||
ReportMethodDiagnostic(
|
||||
context,
|
||||
BindNodeSignalDiagnostics.NodeFieldMustBeInstanceField,
|
||||
candidate,
|
||||
attribute,
|
||||
candidate.MethodSymbol.Name,
|
||||
fieldSymbol.Name);
|
||||
}
|
||||
|
||||
private static void ReportFieldTypeMustDeriveFromNodeDiagnostic(
|
||||
SourceProductionContext context,
|
||||
MethodCandidate candidate,
|
||||
AttributeData attribute,
|
||||
IFieldSymbol fieldSymbol)
|
||||
{
|
||||
ReportMethodDiagnostic(
|
||||
context,
|
||||
BindNodeSignalDiagnostics.FieldTypeMustDeriveFromNode,
|
||||
candidate,
|
||||
attribute,
|
||||
fieldSymbol.Name);
|
||||
}
|
||||
|
||||
private static bool TryFindCompatibleEvent(
|
||||
SourceProductionContext context,
|
||||
MethodCandidate candidate,
|
||||
AttributeData attribute,
|
||||
IFieldSymbol fieldSymbol,
|
||||
string signalName,
|
||||
out IEventSymbol eventSymbol)
|
||||
{
|
||||
eventSymbol = null!;
|
||||
|
||||
var resolvedEvent = FindEvent(fieldSymbol.Type, signalName);
|
||||
if (resolvedEvent is null)
|
||||
{
|
||||
ReportSignalNotFoundDiagnostic(context, candidate, attribute, fieldSymbol, signalName);
|
||||
return false;
|
||||
}
|
||||
|
||||
eventSymbol = resolvedEvent;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ReportSignalNotFoundDiagnostic(
|
||||
SourceProductionContext context,
|
||||
MethodCandidate candidate,
|
||||
AttributeData attribute,
|
||||
IFieldSymbol fieldSymbol,
|
||||
string signalName)
|
||||
{
|
||||
ReportMethodDiagnostic(
|
||||
context,
|
||||
BindNodeSignalDiagnostics.SignalNotFound,
|
||||
candidate,
|
||||
attribute,
|
||||
fieldSymbol.Name,
|
||||
signalName);
|
||||
}
|
||||
|
||||
private static void ReportIncompatibleSignatureDiagnostic(
|
||||
SourceProductionContext context,
|
||||
MethodCandidate candidate,
|
||||
AttributeData attribute,
|
||||
IEventSymbol eventSymbol,
|
||||
IFieldSymbol fieldSymbol)
|
||||
{
|
||||
ReportMethodDiagnostic(
|
||||
context,
|
||||
BindNodeSignalDiagnostics.MethodSignatureNotCompatible,
|
||||
candidate,
|
||||
attribute,
|
||||
candidate.MethodSymbol.Name,
|
||||
eventSymbol.Name,
|
||||
fieldSymbol.Name);
|
||||
}
|
||||
|
||||
private static void ReportMethodDiagnostic(
|
||||
SourceProductionContext context,
|
||||
DiagnosticDescriptor descriptor,
|
||||
@ -404,11 +533,7 @@ public sealed class BindNodeSignalGenerator : IIncrementalGenerator
|
||||
{
|
||||
return typeSymbol.GetMembers()
|
||||
.OfType<IMethodSymbol>()
|
||||
.FirstOrDefault(method =>
|
||||
method.Name == methodName &&
|
||||
!method.IsStatic &&
|
||||
method.Parameters.Length == 0 &&
|
||||
method.MethodKind == MethodKind.Ordinary);
|
||||
.FirstOrDefault(method => IsParameterlessInstanceMethod(method, methodName));
|
||||
}
|
||||
|
||||
private static bool CallsGeneratedMethod(
|
||||
@ -447,6 +572,14 @@ public sealed class BindNodeSignalGenerator : IIncrementalGenerator
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsParameterlessInstanceMethod(IMethodSymbol method, string methodName)
|
||||
{
|
||||
return string.Equals(method.Name, methodName, StringComparison.Ordinal) &&
|
||||
!method.IsStatic &&
|
||||
method.Parameters.Length == 0 &&
|
||||
method.MethodKind == MethodKind.Ordinary;
|
||||
}
|
||||
|
||||
private static bool IsBindNodeSignalAttributeName(NameSyntax attributeName)
|
||||
{
|
||||
var simpleName = GetAttributeSimpleName(attributeName);
|
||||
@ -608,4 +741,4 @@ public sealed class BindNodeSignalGenerator : IIncrementalGenerator
|
||||
return RuntimeHelpers.GetHashCode(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,11 +259,7 @@ public sealed class GetNodeGenerator : IIncrementalGenerator
|
||||
{
|
||||
return typeSymbol.GetMembers()
|
||||
.OfType<IMethodSymbol>()
|
||||
.FirstOrDefault(static method =>
|
||||
method.Name == "_Ready" &&
|
||||
!method.IsStatic &&
|
||||
method.Parameters.Length == 0 &&
|
||||
method.MethodKind == MethodKind.Ordinary);
|
||||
.FirstOrDefault(static method => IsParameterlessInstanceMethod(method, "_Ready"));
|
||||
}
|
||||
|
||||
private static bool CallsGeneratedInjection(IMethodSymbol readyMethod)
|
||||
@ -306,6 +302,14 @@ public sealed class GetNodeGenerator : IIncrementalGenerator
|
||||
return attribute.GetNamedArgument("Required", true);
|
||||
}
|
||||
|
||||
private static bool IsParameterlessInstanceMethod(IMethodSymbol method, string methodName)
|
||||
{
|
||||
return string.Equals(method.Name, methodName, StringComparison.Ordinal) &&
|
||||
!method.IsStatic &&
|
||||
method.Parameters.Length == 0 &&
|
||||
method.MethodKind == MethodKind.Ordinary;
|
||||
}
|
||||
|
||||
private static bool TryResolvePath(
|
||||
IFieldSymbol fieldSymbol,
|
||||
AttributeData attribute,
|
||||
@ -373,7 +377,10 @@ public sealed class GetNodeGenerator : IIncrementalGenerator
|
||||
if (!string.Equals(namedArgument.Key, "Lookup", StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
if (namedArgument.Value.Type?.ToDisplayString() != GetNodeLookupModeMetadataName)
|
||||
if (!string.Equals(
|
||||
namedArgument.Value.Type?.ToDisplayString(),
|
||||
GetNodeLookupModeMetadataName,
|
||||
StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
if (namedArgument.Value.Value is int value)
|
||||
@ -568,4 +575,4 @@ public sealed class GetNodeGenerator : IIncrementalGenerator
|
||||
|
||||
public List<FieldCandidate> Fields { get; } = new();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,7 +126,27 @@ public sealed class GodotProjectMetadataGenerator : IIncrementalGenerator
|
||||
|
||||
var explicitMappings = new Dictionary<string, List<INamedTypeSymbol>>(StringComparer.Ordinal);
|
||||
var implicitCandidates = new Dictionary<string, List<INamedTypeSymbol>>(StringComparer.Ordinal);
|
||||
CollectMappingCandidates(
|
||||
context,
|
||||
typeCandidates,
|
||||
autoLoadAttributeSymbol,
|
||||
godotNodeSymbol,
|
||||
projectAutoLoadNames,
|
||||
explicitMappings,
|
||||
implicitCandidates);
|
||||
|
||||
return ResolveTypedMappings(context, projectAutoLoadNames, explicitMappings, implicitCandidates);
|
||||
}
|
||||
|
||||
private static void CollectMappingCandidates(
|
||||
SourceProductionContext context,
|
||||
IReadOnlyList<GodotTypeCandidate> typeCandidates,
|
||||
INamedTypeSymbol? autoLoadAttributeSymbol,
|
||||
INamedTypeSymbol godotNodeSymbol,
|
||||
ISet<string> projectAutoLoadNames,
|
||||
IDictionary<string, List<INamedTypeSymbol>> explicitMappings,
|
||||
IDictionary<string, List<INamedTypeSymbol>> implicitCandidates)
|
||||
{
|
||||
foreach (var candidate in typeCandidates)
|
||||
{
|
||||
var typeSymbol = candidate.TypeSymbol;
|
||||
@ -176,7 +196,14 @@ public sealed class GodotProjectMetadataGenerator : IIncrementalGenerator
|
||||
|
||||
explicitList.Add(typeSymbol);
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, INamedTypeSymbol> ResolveTypedMappings(
|
||||
SourceProductionContext context,
|
||||
IEnumerable<string> projectAutoLoadNames,
|
||||
IReadOnlyDictionary<string, List<INamedTypeSymbol>> explicitMappings,
|
||||
IReadOnlyDictionary<string, List<INamedTypeSymbol>> implicitCandidates)
|
||||
{
|
||||
var resolvedMappings = new Dictionary<string, INamedTypeSymbol>(StringComparer.Ordinal);
|
||||
|
||||
foreach (var projectAutoLoadName in projectAutoLoadNames.OrderBy(static name => name, StringComparer.Ordinal))
|
||||
@ -408,24 +435,40 @@ public sealed class GodotProjectMetadataGenerator : IIncrementalGenerator
|
||||
|
||||
foreach (var member in members)
|
||||
{
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine($" /// 获取 AutoLoad <c>{member.AutoLoadName}</c>。");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
builder.AppendLine(
|
||||
$" public static {member.TypeName} {member.Identifier} => GetRequiredNode<{member.TypeName}>({SymbolDisplay.FormatLiteral(member.AutoLoadName, true)});");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine($" /// 尝试获取 AutoLoad <c>{member.AutoLoadName}</c>。");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
builder.AppendLine(
|
||||
$" public static bool TryGet{member.Identifier}(out {member.TypeName}? value)");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(
|
||||
$" return TryGetNode({SymbolDisplay.FormatLiteral(member.AutoLoadName, true)}, out value);");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
AppendAutoLoadMemberSource(builder, member);
|
||||
}
|
||||
|
||||
AppendGetRequiredNodeSource(builder);
|
||||
AppendTryGetNodeSource(builder);
|
||||
builder.AppendLine("}");
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static void AppendAutoLoadMemberSource(
|
||||
StringBuilder builder,
|
||||
GeneratedAutoLoadMember member)
|
||||
{
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine($" /// 获取 AutoLoad <c>{member.AutoLoadName}</c>。");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
builder.AppendLine(
|
||||
$" public static {member.TypeName} {member.Identifier} => GetRequiredNode<{member.TypeName}>({SymbolDisplay.FormatLiteral(member.AutoLoadName, true)});");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine($" /// 尝试获取 AutoLoad <c>{member.AutoLoadName}</c>。");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
builder.AppendLine(
|
||||
$" public static bool TryGet{member.Identifier}(out {member.TypeName}? value)");
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine(
|
||||
$" return TryGetNode({SymbolDisplay.FormatLiteral(member.AutoLoadName, true)}, out value);");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
private static void AppendGetRequiredNodeSource(StringBuilder builder)
|
||||
{
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(" /// 获取一个必填的 AutoLoad 节点;缺失时抛出异常。");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
@ -444,6 +487,10 @@ public sealed class GodotProjectMetadataGenerator : IIncrementalGenerator
|
||||
" throw new global::System.InvalidOperationException($\"AutoLoad '{autoLoadName}' is not available on the active SceneTree root.\");");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
private static void AppendTryGetNodeSource(StringBuilder builder)
|
||||
{
|
||||
builder.AppendLine(" /// <summary>");
|
||||
builder.AppendLine(" /// 尝试从当前 SceneTree 根节点解析 AutoLoad。");
|
||||
builder.AppendLine(" /// </summary>");
|
||||
@ -470,9 +517,6 @@ public sealed class GodotProjectMetadataGenerator : IIncrementalGenerator
|
||||
builder.AppendLine(" value = root.GetNodeOrNull<TNode>($\"/root/{autoLoadName}\");");
|
||||
builder.AppendLine(" return value is not null;");
|
||||
builder.AppendLine(" }");
|
||||
builder.AppendLine("}");
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string GenerateInputActionsSource(IReadOnlyList<GeneratedInputActionMember> members)
|
||||
@ -530,45 +574,16 @@ public sealed class GodotProjectMetadataGenerator : IIncrementalGenerator
|
||||
if (string.IsNullOrWhiteSpace(content) || content.StartsWith(";", StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
if (content.StartsWith("[", StringComparison.Ordinal) && content.EndsWith("]", StringComparison.Ordinal))
|
||||
{
|
||||
currentSection = content.Substring(1, content.Length - 2).Trim();
|
||||
if (TryUpdateSection(content, ref currentSection))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryParseAssignment(content, out var key, out var value))
|
||||
continue;
|
||||
|
||||
if (string.Equals(currentSection, "autoload", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!seenAutoLoads.Add(key))
|
||||
{
|
||||
diagnostics.Add(Diagnostic.Create(
|
||||
GodotProjectDiagnostics.DuplicateAutoLoadEntry,
|
||||
CreateFileLocation(file.Path),
|
||||
key));
|
||||
continue;
|
||||
}
|
||||
|
||||
autoLoads.Add(new ProjectAutoLoadEntry(
|
||||
key,
|
||||
NormalizeProjectPath(value)));
|
||||
if (TryCollectAutoLoadEntry(file, currentSection, key, value, seenAutoLoads, autoLoads, diagnostics))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.Equals(currentSection, "input", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!seenInputActions.Add(key))
|
||||
{
|
||||
diagnostics.Add(Diagnostic.Create(
|
||||
GodotProjectDiagnostics.DuplicateInputActionEntry,
|
||||
CreateFileLocation(file.Path),
|
||||
key));
|
||||
continue;
|
||||
}
|
||||
|
||||
inputActions.Add(key);
|
||||
}
|
||||
TryCollectInputAction(currentSection, key, seenInputActions, inputActions, diagnostics, file.Path);
|
||||
}
|
||||
|
||||
return new ProjectMetadataParseResult(
|
||||
@ -578,6 +593,68 @@ public sealed class GodotProjectMetadataGenerator : IIncrementalGenerator
|
||||
diagnostics.ToImmutableArray());
|
||||
}
|
||||
|
||||
private static bool TryUpdateSection(string content, ref string currentSection)
|
||||
{
|
||||
if (!content.StartsWith("[", StringComparison.Ordinal) ||
|
||||
!content.EndsWith("]", StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
currentSection = content.Substring(1, content.Length - 2).Trim();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryCollectAutoLoadEntry(
|
||||
AdditionalText file,
|
||||
string currentSection,
|
||||
string key,
|
||||
string value,
|
||||
ISet<string> seenAutoLoads,
|
||||
ICollection<ProjectAutoLoadEntry> autoLoads,
|
||||
ICollection<Diagnostic> diagnostics)
|
||||
{
|
||||
if (!string.Equals(currentSection, "autoload", StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
|
||||
if (!seenAutoLoads.Add(key))
|
||||
{
|
||||
diagnostics.Add(Diagnostic.Create(
|
||||
GodotProjectDiagnostics.DuplicateAutoLoadEntry,
|
||||
CreateFileLocation(file.Path),
|
||||
key));
|
||||
return true;
|
||||
}
|
||||
|
||||
autoLoads.Add(new ProjectAutoLoadEntry(
|
||||
key,
|
||||
NormalizeProjectPath(value)));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void TryCollectInputAction(
|
||||
string currentSection,
|
||||
string key,
|
||||
ISet<string> seenInputActions,
|
||||
ICollection<string> inputActions,
|
||||
ICollection<Diagnostic> diagnostics,
|
||||
string filePath)
|
||||
{
|
||||
if (!string.Equals(currentSection, "input", StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
|
||||
if (!seenInputActions.Add(key))
|
||||
{
|
||||
diagnostics.Add(Diagnostic.Create(
|
||||
GodotProjectDiagnostics.DuplicateInputActionEntry,
|
||||
CreateFileLocation(filePath),
|
||||
key));
|
||||
return;
|
||||
}
|
||||
|
||||
inputActions.Add(key);
|
||||
}
|
||||
|
||||
private static string NormalizeProjectPath(string rawValue)
|
||||
{
|
||||
var trimmed = rawValue.Trim();
|
||||
|
||||
@ -190,6 +190,48 @@ public sealed class AutoRegisterExportedCollectionsGenerator : IIncrementalGener
|
||||
{
|
||||
registration = null!;
|
||||
|
||||
if (!TryResolveCollectionType(context, collectionMember, enumerableType, out var collectionType))
|
||||
return false;
|
||||
|
||||
if (!TryResolveRegistryTarget(
|
||||
context,
|
||||
compilation,
|
||||
ownerType,
|
||||
collectionMember,
|
||||
attribute,
|
||||
out var registryMemberName,
|
||||
out var registerMethodName,
|
||||
out var registryType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryResolveElementType(context, collectionMember, collectionType, out var elementType))
|
||||
return false;
|
||||
|
||||
if (!HasCompatibleRegisterMethod(compilation, ownerType, registryType, registerMethodName, elementType))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(
|
||||
AutoRegisterExportedCollectionsDiagnostics.RegisterMethodNotFound,
|
||||
collectionMember.Locations.FirstOrDefault() ?? Location.None,
|
||||
registerMethodName,
|
||||
registryMemberName,
|
||||
collectionMember.Name));
|
||||
return false;
|
||||
}
|
||||
|
||||
registration = new RegistrationSpec(collectionMember.Name, registryMemberName, registerMethodName);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryResolveCollectionType(
|
||||
SourceProductionContext context,
|
||||
ISymbol collectionMember,
|
||||
INamedTypeSymbol enumerableType,
|
||||
out ITypeSymbol collectionType)
|
||||
{
|
||||
collectionType = null!;
|
||||
|
||||
if (!IsInstanceReadableMember(collectionMember))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(
|
||||
@ -199,17 +241,11 @@ public sealed class AutoRegisterExportedCollectionsGenerator : IIncrementalGener
|
||||
return false;
|
||||
}
|
||||
|
||||
var collectionType = collectionMember switch
|
||||
{
|
||||
IFieldSymbol field => field.Type,
|
||||
IPropertySymbol property => property.Type,
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (collectionType is null)
|
||||
var resolvedType = GetMemberType(collectionMember);
|
||||
if (resolvedType is null)
|
||||
return false;
|
||||
|
||||
if (!collectionType.IsAssignableTo(enumerableType))
|
||||
if (!resolvedType.IsAssignableTo(enumerableType))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(
|
||||
AutoRegisterExportedCollectionsDiagnostics.CollectionTypeMustBeEnumerable,
|
||||
@ -218,12 +254,35 @@ public sealed class AutoRegisterExportedCollectionsGenerator : IIncrementalGener
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetRegistrationAttributeArguments(context, collectionMember, attribute, out var registryMemberName,
|
||||
out var registerMethodName))
|
||||
collectionType = resolvedType;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryResolveRegistryTarget(
|
||||
SourceProductionContext context,
|
||||
Compilation compilation,
|
||||
INamedTypeSymbol ownerType,
|
||||
ISymbol collectionMember,
|
||||
AttributeData attribute,
|
||||
out string registryMemberName,
|
||||
out string registerMethodName,
|
||||
out INamedTypeSymbol registryType)
|
||||
{
|
||||
registryMemberName = string.Empty;
|
||||
registerMethodName = string.Empty;
|
||||
registryType = null!;
|
||||
|
||||
if (!TryGetRegistrationAttributeArguments(
|
||||
context,
|
||||
collectionMember,
|
||||
attribute,
|
||||
out registryMemberName,
|
||||
out registerMethodName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var registryMember = FindRegistryMember(ownerType, registryMemberName);
|
||||
|
||||
if (registryMember is null)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(
|
||||
@ -246,18 +305,24 @@ public sealed class AutoRegisterExportedCollectionsGenerator : IIncrementalGener
|
||||
return false;
|
||||
}
|
||||
|
||||
var registryType = registryMember switch
|
||||
{
|
||||
IFieldSymbol field => field.Type as INamedTypeSymbol,
|
||||
IPropertySymbol property => property.Type as INamedTypeSymbol,
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (registryType is null)
|
||||
var resolvedRegistryType = GetMemberType(registryMember) as INamedTypeSymbol;
|
||||
if (resolvedRegistryType is null)
|
||||
return false;
|
||||
|
||||
var elementType = TryGetElementType(collectionType);
|
||||
if (elementType is null)
|
||||
registryType = resolvedRegistryType;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryResolveElementType(
|
||||
SourceProductionContext context,
|
||||
ISymbol collectionMember,
|
||||
ITypeSymbol collectionType,
|
||||
out ITypeSymbol elementType)
|
||||
{
|
||||
elementType = null!;
|
||||
|
||||
var resolvedElementType = TryGetElementType(collectionType);
|
||||
if (resolvedElementType is null)
|
||||
{
|
||||
// Non-generic IEnumerable exposes elements as object at compile time, which is not safe
|
||||
// for validating or generating a strongly typed registry call.
|
||||
@ -268,26 +333,33 @@ public sealed class AutoRegisterExportedCollectionsGenerator : IIncrementalGener
|
||||
return false;
|
||||
}
|
||||
|
||||
var hasCompatibleMethod = EnumerateCandidateMethods(registryType, registerMethodName)
|
||||
elementType = resolvedElementType;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool HasCompatibleRegisterMethod(
|
||||
Compilation compilation,
|
||||
INamedTypeSymbol ownerType,
|
||||
INamedTypeSymbol registryType,
|
||||
string registerMethodName,
|
||||
ITypeSymbol elementType)
|
||||
{
|
||||
return EnumerateCandidateMethods(registryType, registerMethodName)
|
||||
.Any(method =>
|
||||
!method.IsStatic &&
|
||||
method.Parameters.Length == 1 &&
|
||||
compilation.IsSymbolAccessibleWithin(method, ownerType) &&
|
||||
CanAcceptElementType(compilation, elementType, method.Parameters[0].Type));
|
||||
}
|
||||
|
||||
if (!hasCompatibleMethod)
|
||||
private static ITypeSymbol? GetMemberType(ISymbol member)
|
||||
{
|
||||
return member switch
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(
|
||||
AutoRegisterExportedCollectionsDiagnostics.RegisterMethodNotFound,
|
||||
collectionMember.Locations.FirstOrDefault() ?? Location.None,
|
||||
registerMethodName,
|
||||
registryMemberName,
|
||||
collectionMember.Name));
|
||||
return false;
|
||||
}
|
||||
|
||||
registration = new RegistrationSpec(collectionMember.Name, registryMemberName, registerMethodName);
|
||||
return true;
|
||||
IFieldSymbol field => field.Type,
|
||||
IPropertySymbol property => property.Type,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsInstanceReadableMember(ISymbol member)
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<WarningLevel>0</WarningLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@ -11,6 +11,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GFramework.Godot.Setting.Data;
|
||||
|
||||
/// <summary>
|
||||
@ -20,24 +23,47 @@ public class LocalizationMap
|
||||
{
|
||||
private const string DefaultFrameworkLanguage = "eng";
|
||||
private const string DefaultGodotLocale = "en";
|
||||
private readonly Dictionary<string, string> _frameworkLanguageMap;
|
||||
private readonly Dictionary<string, string> _languageMap;
|
||||
|
||||
/// <summary>
|
||||
/// 用户语言 -> Godot locale 映射表。
|
||||
/// 使用默认的 Godot locale 与框架语言码映射初始化本地化设置。
|
||||
/// </summary>
|
||||
public Dictionary<string, string> LanguageMap { get; set; } = new(StringComparer.Ordinal)
|
||||
public LocalizationMap()
|
||||
: this(CreateDefaultLanguageMap(), CreateDefaultFrameworkLanguageMap())
|
||||
{
|
||||
{ "简体中文", "zh_CN" },
|
||||
{ "English", "en" }
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户语言 -> GFramework 本地化语言码映射表。
|
||||
/// 使用外部提供的映射初始化本地化设置。
|
||||
/// 构造函数会复制输入字典,避免调用方在实例创建后继续修改内部状态。
|
||||
/// </summary>
|
||||
public Dictionary<string, string> FrameworkLanguageMap { get; set; } = new(StringComparer.Ordinal)
|
||||
/// <param name="languageMap">用户语言到 Godot locale 的映射。</param>
|
||||
/// <param name="frameworkLanguageMap">用户语言到 GFramework 本地化语言码的映射。</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// 当 <paramref name="languageMap" /> 或 <paramref name="frameworkLanguageMap" /> 为 <see langword="null" /> 时抛出。
|
||||
/// </exception>
|
||||
public LocalizationMap(
|
||||
IReadOnlyDictionary<string, string> languageMap,
|
||||
IReadOnlyDictionary<string, string> frameworkLanguageMap)
|
||||
{
|
||||
{ "简体中文", "zhs" },
|
||||
{ "English", "eng" }
|
||||
};
|
||||
ArgumentNullException.ThrowIfNull(languageMap);
|
||||
ArgumentNullException.ThrowIfNull(frameworkLanguageMap);
|
||||
|
||||
// 复制外部输入,避免公共属性把可变集合直接暴露给调用方。
|
||||
_languageMap = new Dictionary<string, string>(languageMap, StringComparer.Ordinal);
|
||||
_frameworkLanguageMap = new Dictionary<string, string>(frameworkLanguageMap, StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户语言到 Godot locale 的只读映射表。
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, string> LanguageMap => _languageMap;
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户语言到 GFramework 本地化语言码的只读映射表。
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, string> FrameworkLanguageMap => _frameworkLanguageMap;
|
||||
|
||||
/// <summary>
|
||||
/// 解析用户保存的语言值对应的 Godot locale。
|
||||
@ -68,4 +94,22 @@ public class LocalizationMap
|
||||
|
||||
return FrameworkLanguageMap.GetValueOrDefault(storedLanguage, DefaultFrameworkLanguage);
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> CreateDefaultLanguageMap()
|
||||
{
|
||||
return new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
{ "简体中文", "zh_CN" },
|
||||
{ "English", "en" }
|
||||
};
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> CreateDefaultFrameworkLanguageMap()
|
||||
{
|
||||
return new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
{ "简体中文", "zhs" },
|
||||
{ "English", "eng" }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -2553,7 +2553,7 @@ public class SchemaConfigGeneratorTests
|
||||
/// <param name="source">测试输入源码。</param>
|
||||
/// <param name="additionalFiles">参与本次生成的 schema 文件集合。</param>
|
||||
/// <returns>按 HintName 索引的生成源码字典。</returns>
|
||||
private static global::System.Collections.Generic.IReadOnlyDictionary<string, string> RunAndCollectGeneratedSources(
|
||||
private static IReadOnlyDictionary<string, string> RunAndCollectGeneratedSources(
|
||||
string source,
|
||||
params (string path, string content)[] additionalFiles)
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@
|
||||
<Copyright>Copyright © 2025</Copyright>
|
||||
<RepositoryUrl>https://github.com/GeWuYou/GFramework</RepositoryUrl>
|
||||
<PackageProjectUrl>https://github.com/GeWuYou/GFramework</PackageProjectUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
||||
<PackageTags>game;framework</PackageTags>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
@ -16,6 +16,7 @@
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<!-- This package is a pure meta-package that only aggregates dependencies. -->
|
||||
<NoPackageAnalysis>false</NoPackageAnalysis>
|
||||
</PropertyGroup>
|
||||
|
||||
@ -0,0 +1,517 @@
|
||||
# Analyzer Warning Reduction 跟踪
|
||||
|
||||
## 目标
|
||||
|
||||
继续以“优先低风险、保持行为兼容”为原则收敛当前仓库的 Meziantou analyzer warnings,并在首轮大规模清理完成后,
|
||||
判断剩余结构性 warning 是否值得在下一轮继续推进。
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-041`
|
||||
- 当前阶段:`Phase 41`
|
||||
- 当前焦点:
|
||||
- 已通过第五个有效 subagent 切片完成
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
`Generates_Precise_Service_Type_For_Hidden_Generic_Type_Definitions()` 的 `MA0051` 收口:
|
||||
将内联 `source` 文本提取为类级常量,保持既有 expected 常量和断言语义不变
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release warnings-only 基线已从 `11` 条降到 `10` 条;
|
||||
行号 `680` 已从 `MA0051` 输出中消失,剩余热点继续集中在
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
- 已通过第四个有效 subagent 切片完成
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
`Generates_Precise_Service_Type_For_Hidden_Array_Type_Arguments()` 的 `MA0051` 收口:
|
||||
将内联 `source` 文本提取为类级常量,保持既有 expected 常量和断言语义不变
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release warnings-only 基线已从 `12` 条降到 `11` 条;
|
||||
行号 `607` 已从 `MA0051` 输出中消失,剩余热点继续集中在
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
- 已通过第三个有效 subagent 切片完成
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
`Generates_Direct_Interface_Registrations_For_Hidden_Implementation_When_Handler_Interface_Is_Public()`
|
||||
的 `MA0051` 收口:将内联 `source` 文本提取为类级常量,保持既有 expected 常量和断言语义不变
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release warnings-only 基线已从 `13` 条降到 `12` 条;
|
||||
行号 `536` 已从 `MA0051` 输出中消失,剩余热点继续集中在
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
- 已通过第二个有效 subagent 切片完成
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
`Generates_Visible_Handlers_And_Self_Registers_Private_Nested_Handler_When_Assembly_Contains_Hidden_Handler()`
|
||||
的 `MA0051` 收口:将内联 `source` 文本提取为类级常量,保持既有 expected 常量和断言语义不变
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release warnings-only 基线已从 `14` 条降到 `13` 条;
|
||||
行号 `454` 已从 `MA0051` 输出中消失,剩余热点继续集中在
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
- 已通过 subagent 循环的首个可交付切片完成
|
||||
`GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs`
|
||||
`Generates_Assembly_Level_Cqrs_Handler_Registry()` 的 `MA0051` 收口:
|
||||
将内联 `source` / `expected` 文本提取为类级常量,保持生成文本、断言语义和文件名不变
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release warnings-only 基线已从 `15` 条降到 `14` 条;
|
||||
行号 `337` 已从 `MA0051` 输出中消失,剩余热点仍全部集中在
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
- 已确认当前分支相对 `origin/main` 的唯一变更文件数仍只有 `3`;按该统计口径距离用户要求的
|
||||
“接近 `75` 个文件变更”仍很远,需要继续多轮切片
|
||||
- 已完成 `GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs` 的 `MA0051` 收口:
|
||||
将共享 consumer runtime fixture 提取到类级常量,并把生成结果收集与 catalog 契约断言拆成小 helper,
|
||||
保持 schema 文本、断言语义与生成输出契约不变
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release warnings-only 基线已从 `22` 条降到 `15` 条;
|
||||
`SchemaConfigGeneratorTests.cs` 已不再出现在 `MA0051` 列表中,剩余热点全部集中在
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
- 已完成 `SchemaConfigGeneratorTests` 定向验证:串行重跑 `50` 个用例全部通过;并确认先前并行 build/test
|
||||
触发的 `MSB3030` / `CS0006` 属于共享输出竞争噪音,不是代码回归
|
||||
- 已按 `gframework-boot` 重新恢复当前 worktree:确认分支 `fix/analyzer-warning-reduction-batch` 仍映射到
|
||||
`analyzer-warning-reduction`,且当前不存在 `ai-plan/private/` 私有恢复上下文
|
||||
- 已重新抓取当前分支关联的 PR #273 review 状态:PR 已处于 `CLOSED`,latest-head review 仍显示 `2` 条
|
||||
CodeRabbit open thread,但本地复核后 `GeneratorSnapshotTest` 的 snapshot 路径空值防御已显式改为
|
||||
`InvalidOperationException` 防御,`SchemaConfigGenerator` 的 `dependentSchemas` / `allOf` / conditional helper
|
||||
也已补齐 XML 文档,当前更像历史线程未随已关闭 PR 一起收敛
|
||||
- 已重新以 `GFramework.SourceGenerators.Tests` Release warnings-only build 复核当前 `MA0051` 热点:
|
||||
基线现为 `22` 条,且已不再落在 `GeneratorSnapshotTest`、`ContextRegistrationAnalyzerTests` 或
|
||||
`ContextGetGeneratorTests`,而是集中在 `CqrsHandlerRegistryGeneratorTests.cs`(`15` 条)与
|
||||
`SchemaConfigGeneratorTests.cs`(`7` 条)
|
||||
- 已完成 `GFramework.Core` 当前 `MA0016` / `MA0002` / `MA0015` / `MA0077` 低风险收口批次
|
||||
- 已复核 `net10.0` 下的 `MA0158` 基线:`GFramework.Core` / `GFramework.Cqrs` 当前共有 `16` 个 object lock
|
||||
建议点,属于跨 target 兼容性风险,不在本轮直接批量替换
|
||||
- 已完成 `GFramework.Core.SourceGenerators/Rule/ContextAwareGenerator.cs` 的剩余 `MA0051` 结构拆分,生成输出保持不变
|
||||
- 已完成 `GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 的 `MA0051` 结构拆分,生成输出保持不变
|
||||
- 已完成 `GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs` 的 `MA0006` 低风险收口,schema 关键字比较显式使用
|
||||
`StringComparison.Ordinal`
|
||||
- 已完成 `SchemaConfigGenerator.cs` 的第一批 `MA0051` 结构拆分:schema 入口解析、属性解析、schema 遍历、数组属性解析、
|
||||
约束文档生成与若干生成代码发射 helper 已拆出语义阶段
|
||||
- 已完成当前 PR #269 review follow-up:`CqrsHandlerRegistryGenerator` 按职责拆分为 partial 生成器文件,
|
||||
`ContextAwareGenerator` 已补上字段名去冲突与锁内读取修正,`Option<T>` 补齐 `<remarks>` 契约说明
|
||||
- 已完成当前 PR #269 第二轮 follow-up:恢复 `EasyEvents`、`CollectionExtensions`、`LoggingConfiguration` 与
|
||||
`FilterConfiguration` 的公共 API 兼容形状,并将 analyzer 兼容性处理收敛到局部 pragma
|
||||
- 已完成当前 PR #269 第三轮 follow-up:继续收口 `SchemaConfigGenerator` 的根类型标识符校验与 XML 文档转义,
|
||||
并补齐 `LoggingConfigurationTests`、`CollectionExtensionsTests`、`Cqrs` helper 抽取与 `ai-plan` 命令文本修正
|
||||
- 已完成当前 PR #269 第四轮 follow-up:将 `CqrsHandlerRegistryGenerator` 的 Roslyn error type 直接引用改为
|
||||
运行时精确查找路径,并为 `SchemaConfigGenerator` 补上根 `type` 非字符串时的防御与回归测试
|
||||
- 已完成当前 PR #269 第五轮 follow-up:`SchemaConfigGenerator` 补上归一化后属性名冲突诊断并新增
|
||||
`GF_ConfigSchema_014`,`CqrsHandlerRegistryGenerator` 将 `dynamic` 归一化为 `global::System.Object`,
|
||||
同时收紧相关 generator regression tests
|
||||
- 已完成当前 PR #269 failed-test follow-up:修正 `SchemaConfigGeneratorTests`
|
||||
`Run_Should_Assign_Globally_Unique_Reference_Metadata_Member_Names` 的测试输入,使其继续覆盖
|
||||
reference metadata 成员名全局去冲突,但不再依赖现已被 `GF_ConfigSchema_014` 拦截的非法同层 schema key 冲突
|
||||
- 已完成当前 PR #269 Greptile follow-up:`ContextAwareGenerator` 现在会把基类链显式成员名也纳入
|
||||
`_gFrameworkContextAware*` 字段分配冲突检测,并新增 inherited-field collision 快照回归测试
|
||||
- 已完成当前分支与 `main` 的 `CqrsHandlerRegistryGenerator.cs` 文件级冲突收口:确认 `main` 侧新增的是
|
||||
`OrderedRegistrationKind` / `RuntimeTypeReferenceSpec` 的 XML 文档,现已按当前 partial 拆分结构迁移到
|
||||
`CqrsHandlerRegistryGenerator.Models.cs`,不回退已完成的生成器拆分
|
||||
- 已完成 `SchemaConfigGenerator.cs` 剩余 `MA0051` 收口:将 `dependentRequired` / `allOf` / conditional schema 校验
|
||||
拆成更小的验证阶段,并将 `GenerateTableClass`、`GenerateBindingsClass`、`AppendGeneratedConfigCatalogType`
|
||||
拆成稳定的代码发射 helper,保持生成输出与快照一致
|
||||
- 已更新 `AGENTS.md`:变更模块必须运行对应 `dotnet build -c Release`,并处理或显式报告模块构建 warning,
|
||||
不再默认留给长期 warning 清理分支
|
||||
- `CoroutineScheduler` 的 tag/group 字典已显式使用 `StringComparer.Ordinal`,保持既有区分大小写语义
|
||||
- `EasyEvents.AddEvent<T>()` 的重复注册路径已恢复为 `ArgumentException`,以保持既有异常契约
|
||||
- `Option<T>` 已声明 `IEquatable<Option<T>>`,与已有强类型 `Equals(Option<T>)` 契约对齐
|
||||
- 当前 `GFramework.Core` `net8.0` warnings-only 基线已降到 `0` 条
|
||||
- 当前 `GFramework.Core.SourceGenerators` warnings-only 基线已降到 `0` 条
|
||||
- 当前 `GFramework.Cqrs.SourceGenerators` warnings-only 基线已降到 `0` 条
|
||||
- 当前 `GFramework.Game.SourceGenerators` warnings-only 基线已从 `46` 条降到 `0` 条
|
||||
- 已完成 `GFramework.SourceGenerators.Tests` 低风险 `MA0004` / `MA0048` 收口:测试辅助器改为直接返回 `Task`,
|
||||
文件 I/O 显式补齐 `ConfigureAwait(false)`,`AnalyzerTestDriver` 文件名与类型名重新对齐
|
||||
- 当前 `GFramework.SourceGenerators.Tests` warnings-only 基线已从 `61` 条降到 `49` 条,剩余 warning 均为 `MA0051`
|
||||
- 已完成 `GFramework.SourceGenerators.Tests/Logging/LoggerGeneratorSnapshotTests.cs` 的 `MA0051` 收口:同构 snapshot
|
||||
场景已收敛为模板化 helper,保留原有快照目录与生成器输入语义不变
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release build 基线已从 `49` 条降到 `43` 条;`LoggerGeneratorSnapshotTests.cs`
|
||||
已不再出现在 `MA0051` 列表中
|
||||
- 已完成 `GFramework.SourceGenerators.Tests/Architectures/AutoRegisterModuleGeneratorTests.cs` 的 `MA0051` 收口:
|
||||
将内联测试源码与期望快照抽到类级常量、补齐测试类 XML 文档,并将仅作转发的异步测试改为直接返回 `Task`
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release build 基线已从 `43` 条降到 `40` 条;
|
||||
`AutoRegisterModuleGeneratorTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- 已完成 `GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorSnapshotTests.cs` 的 `MA0051` 收口:
|
||||
将 monster 场景的运行时契约与 schema 输入提取为类级常量,并把生成结果与快照目录解析拆成小 helper,保持
|
||||
生成文件名、快照目录和断言语义不变
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release build 基线已从 `40` 条降到 `39` 条;
|
||||
`SchemaConfigGeneratorSnapshotTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- 已完成当前 PR #273 review follow-up 首轮核对:确认本地仍成立的问题集中在
|
||||
`SchemaConfigGenerator` helper XML 文档、`GeneratorSnapshotTest` 的 `StringComparison.Ordinal` /
|
||||
snapshot 路径空值防御、`AutoRegisterModuleGeneratorTests` 的 XML 文档位置,以及
|
||||
`SchemaConfigGeneratorSnapshotTests` 的 monster 快照覆盖缺口
|
||||
- 已将 monster 快照场景扩展到 `dependentRequired`、`dependentSchemas`、`allOf` 与 object-focused
|
||||
`if/then/else`,以便把新增 schema 约束文档纳入 snapshot 验证
|
||||
- 已完成本轮定向验证:
|
||||
`GFramework.Game.SourceGenerators` Release build 通过;
|
||||
`GFramework.SourceGenerators.Tests` 在 `-m:1 --no-restore` 下 Release build 通过;
|
||||
`SchemaConfigGeneratorSnapshotTests` 与 `AutoRegisterModuleGeneratorTests` 定向测试共 `4` 项全部通过
|
||||
- 当前验证仍受环境/基线约束:
|
||||
`GFramework.SourceGenerators.Tests` Release build 保留既有 `MA0051` warning 基线;
|
||||
NuGet vulnerability audit 在离线环境下产生 `NU1900`
|
||||
- `GFramework.Godot` 的 `Timing.cs` 已同步适配新事件签名,但当前 worktree 的 Godot restore 资产仍受 Windows fallback package folder 干扰,独立 build 需在修复资产后补跑
|
||||
- 后续继续按 warning 类型和数量批处理,而不是回退到按单文件切片推进
|
||||
- 下一轮默认重新抓取 PR #273 最新 review 线程,并确认本轮 snapshot 更新后是否还存在剩余 open thread 或
|
||||
`dotnet-format` 细项
|
||||
- 单次 `boot` 的工作树改动上限控制在约 `100` 个文件以内,避免 recovery context 与 review 面同时失控
|
||||
- 若任务边界互不冲突,允许使用不同模型的 subagent 并行处理不同 warning 类型或不同目录,但必须遵守显式 ownership
|
||||
|
||||
## 当前状态摘要
|
||||
|
||||
- 已完成 `GFramework.Core`、`GFramework.Cqrs`、`GFramework.Godot` 与部分 source generator 的低风险 warning 清理
|
||||
- 已完成多轮 CodeRabbit follow-up 修复,并用定向测试与项目/解决方案构建验证了关键回归风险
|
||||
- 已完成当前 PR #265 review follow-up:修复 `CoroutineScheduler` 的零容量扩容边界,并补上 `Store` dispatch 作用域的异常安全回滚
|
||||
- 已继续完成当前 PR #265 review follow-up:修复 `Event<T>` 与 `Event<T, TK>` 监听器计数的 off-by-one,并补充回归测试
|
||||
- 已增强 `gframework-pr-review` 脚本与 skill 文档,降低超长 JSON 直出导致的 review 信号漏看风险
|
||||
- 已完成 `GFramework.Core` 当前 `MA0046` 批次:将阶段、协程与异步日志事件统一迁移到 `EventHandler<TEventArgs>` 形状,
|
||||
并同步更新 `GFramework.Godot` 订阅点、定向测试与 `docs/zh-CN` 示例
|
||||
- 已完成当前 PR #267 review follow-up:修复 `AsyncLogAppender` 的 `ILogAppender.Flush()` 双重完成通知,并补齐
|
||||
`PhaseChanged` / `CoroutineExceptionEventArgs` XML 文档、`PhaseChanged` 迁移说明和 `ai-plan` 基线注释
|
||||
- 已完成当前 PR #267 failed-test follow-up:修复 `AsyncLogAppender.Flush()` 在队列已被后台线程提前清空时仍可能
|
||||
等待满默认超时并返回 `false` 的竞态,并通过整包 `GFramework.Core.Tests` 重新验证
|
||||
- 已完成当前 `GFramework.Core` `net8.0` 剩余低风险 analyzer warning 批次;warnings-only 基线已降到 `0` 条
|
||||
- 已完成 `GFramework.Core.SourceGenerators` 中 `ContextAwareGenerator` 的剩余 `MA0051` 收口;warnings-only 基线已降到 `0` 条
|
||||
- 已完成 `GFramework.Cqrs.SourceGenerators` 中 `CqrsHandlerRegistryGenerator` 的剩余 `MA0051` 收口;warnings-only 基线已降到 `0` 条
|
||||
- 已完成当前 PR #269 的 review follow-up:收口 `ContextAwareGenerator` 的字段命名冲突 / 锁内读取契约、
|
||||
`CqrsHandlerRegistryGenerator` 的运行时类型 null 防御与超大文件拆分、`SchemaConfigGenerator` 的取消语义,
|
||||
并恢复 `EasyEvents` / `CollectionExtensions` / logging 配置模型的公共 API 兼容形状
|
||||
- 已完成当前 PR #269 的第四轮 review follow-up:确认 5 个 latest-head 未解决线程中仅剩 2 个本地仍成立,
|
||||
已分别在 `CqrsHandlerRegistryGenerator` 与 `SchemaConfigGenerator` 中收口,并补齐定向 generator regression tests
|
||||
- 已完成当前 PR #269 的第五轮 review follow-up:收口 `SchemaConfigGenerator` 的归一化字段名冲突诊断、
|
||||
`CqrsHandlerRegistryGenerator` 的 `dynamic` 类型引用风险,并同步更新 `AGENTS.md` 的模块 build / warning 治理规范
|
||||
- 已完成当前 PR #269 的 failed-test follow-up:将 reference metadata 成员名唯一性回归测试改为合法 schema 路径组合,
|
||||
并重新通过定向 generator test
|
||||
- 已完成当前 PR #269 的 Greptile follow-up:修复 `ContextAwareGenerator` 未覆盖基类成员名冲突的问题,并补齐
|
||||
inherited-collision 快照测试
|
||||
- 已完成当前分支与 `main` 的 `CqrsHandlerRegistryGenerator.cs` 冲突化解:保留当前 partial 结构,并把
|
||||
`main` 侧新增的模型文档合并到 `CqrsHandlerRegistryGenerator.Models.cs`
|
||||
- 已完成 `GFramework.Game.SourceGenerators` 中 `SchemaConfigGenerator` 的剩余 `MA0051` 收口;warnings-only 基线已降到 `0` 条
|
||||
- 已完成 `GFramework.SourceGenerators.Tests` 的首轮低风险 warning 清理;当前项目已清空 `MA0004` / `MA0048`,剩余 warning
|
||||
全部收敛为 `MA0051`
|
||||
- 已完成 `LoggerGeneratorSnapshotTests` 的单文件 `MA0051` 收口;当前 `GFramework.SourceGenerators.Tests` Release build 基线已降到
|
||||
`43` 条,并通过 focused snapshot tests 保持行为不变
|
||||
- 已完成 `AutoRegisterModuleGeneratorTests` 的单文件 `MA0051` 收口;当前 `GFramework.SourceGenerators.Tests` Release build 基线已降到
|
||||
`40` 条,并通过 focused generator tests 保持输出契约不变
|
||||
- 已完成 `SchemaConfigGeneratorSnapshotTests` 的单文件 `MA0051` 收口;当前 `GFramework.SourceGenerators.Tests`
|
||||
Release build 基线已降到 `39` 条,并通过 focused snapshot test 保持生成输出契约不变
|
||||
- 已完成 `RP-035` 启动复核:确认 PR #273 已关闭、历史 open thread 暂无新的本地修复点,且
|
||||
`GFramework.SourceGenerators.Tests` 当前剩余 `MA0051` 已重排为 `CqrsHandlerRegistryGeneratorTests` /
|
||||
`SchemaConfigGeneratorTests` 两个测试写集
|
||||
- 已完成 `RP-036`:清空 `SchemaConfigGeneratorTests.cs` 当前 `MA0051`,并将
|
||||
`GFramework.SourceGenerators.Tests` Release warnings-only 基线进一步降到 `15` 条
|
||||
- 已完成 `RP-037` 的首个 subagent 接收:`CqrsHandlerRegistryGeneratorTests.cs` 的
|
||||
`Generates_Assembly_Level_Cqrs_Handler_Registry()` 已抽出类级 fixture,当前测试项目基线进一步降到 `14` 条
|
||||
- 已完成 `RP-038` 的第二个 subagent 接收:`HiddenNestedHandlerSelfRegistrationSource` 已提取到类级常量,
|
||||
当前测试项目基线进一步降到 `13` 条
|
||||
- 已完成 `RP-039` 的第三个 subagent 接收:`HiddenImplementationDirectInterfaceRegistrationSource`
|
||||
已提取到类级常量,当前测试项目基线进一步降到 `12` 条
|
||||
- 已完成 `RP-040` 的第四个 subagent 接收:`HiddenArrayResponseFallbackSource`
|
||||
已提取到类级常量,当前测试项目基线进一步降到 `11` 条
|
||||
- 已完成 `RP-041` 的第五个 subagent 接收:`HiddenGenericEnvelopeResponseSource`
|
||||
已提取到类级常量,当前测试项目基线进一步降到 `10` 条
|
||||
|
||||
## 当前活跃事实
|
||||
|
||||
- 当前主题仍是 active topic,因为剩余结构性 warning 是否继续推进尚未决策
|
||||
- `RP-001` 的详细实现历史、测试记录和 warning 热点清单已归档到主题内 `archive/`
|
||||
- `RP-002` 已在不改公共契约的前提下完成 `CqrsHandlerRegistrar` 结构拆分,并通过定向 build/test 验证
|
||||
- `RP-003` 已在不改生命周期契约的前提下完成 `ArchitectureLifecycle` 初始化主流程拆分,并通过定向 build/test 验证
|
||||
- `RP-004` 已完成当前 PR review follow-up:修复 `TryCreateGeneratedRegistry` 的可空 `out` 契约并清理 trace 文档重复标题
|
||||
- `RP-005` 已在不改公共 API 的前提下完成 `PauseStackManager` 两个 `MA0051` 的结构拆分,并补充销毁通知回归测试
|
||||
- `RP-006` 已在不改公共 API 的前提下完成 `Store` 两个 `MA0051` 的结构拆分,并通过定向 build/test 验证 dispatch、
|
||||
多态 reducer 匹配与历史语义未回归
|
||||
- `RP-007` 已在不改公共 API 的前提下完成 `CoroutineScheduler` 两个 `MA0051` 的结构拆分,并通过定向 build/test 验证
|
||||
调度、取消与完成状态语义未回归
|
||||
- `RP-008` 将后续策略从“单文件 warning 切片”切换为“按类型批处理 + 文件数上限控制”,并允许在非冲突前提下使用
|
||||
不同模型的 subagent 并行处理
|
||||
- `RP-009` 在不改公共 API 的前提下,将同名泛型家族收拢到与类型名一致的单文件中,清空当前 `GFramework.Core`
|
||||
`net8.0` 基线中的 `MA0048`,并通过定向 build/test 验证 `Command`、`Query`、`Event` 路径未回归
|
||||
- `RP-010` 使用 `gframework-pr-review` 复核当前分支 PR #265 后,修复了仍在本地成立的两个 follow-up 风险:
|
||||
`CoroutineScheduler` 的 `initialCapacity: 0` 扩容越界,以及 `Store` 在 dispatch 快照阶段抛异常时可能残留
|
||||
`_isDispatching = true` 的锁死问题
|
||||
- `RP-011` 根据补充复核继续收口 PR #265 的 outside-diff comment,修复 `Event<T>` / `Event<T, TK>` 默认 no-op
|
||||
委托导致的 `GetListenerCount()` off-by-one,并以定向事件测试验证注册、注销和计数语义
|
||||
- `RP-012` 为 `gframework-pr-review` 增加 `--json-output`、`--section`、`--path` 与文本截断能力,并更新 skill 推荐用法,
|
||||
让“先落盘、再定向抽取”成为默认可操作路径
|
||||
- `RP-013` 已完成 `GFramework.Core` 当前 `MA0046` 批次,并以新的事件参数类型替换阶段、协程和异步日志事件的
|
||||
非标准签名;`GFramework.Core` `net8.0` warnings-only 基线由 `15` 降至 `9`
|
||||
- `RP-014` 使用 `gframework-pr-review` 复核当前分支 PR #267 的 latest head review threads、outside-diff comment 与
|
||||
nitpick comment 后,确认 8 条高信号项中仍成立的是 1 个行为 bug 与 7 个文档/测试/跟踪缺口,并按最小改动收口
|
||||
- `RP-015` 使用 `$gframework-pr-review` 复核 PR #267 的 CTRF 失败测试评论后,确认 `AsyncLogAppender` 仍存在
|
||||
“队列已空但 Flush 仍超时失败”的竞态;该问题在本地整包 `GFramework.Core.Tests` 中可复现,现已修复并补上稳定回归测试
|
||||
- `RP-016` 将 `GFramework.Core` 当前剩余 `MA0016` / `MA0002` / `MA0015` / `MA0077` 低风险批次清零,并用
|
||||
warnings-only build 与 focused tests 验证配置反序列化、集合扩展、事件重复注册、`Option<T>` 相等性和协程 tag/group 语义
|
||||
- `RP-017` 复核 `MA0158` 当前仍是跨 target 锁类型迁移问题,因此先收口单点 `ContextAwareGenerator` `MA0051`,
|
||||
并通过 source generator 项目 build 与 `ContextAwareGeneratorSnapshotTests` 验证生成输出未回归
|
||||
- `RP-018` 暂缓跨 target `MA0158`,转入 `GFramework.Cqrs.SourceGenerators` 的单文件结构性 warning;
|
||||
通过拆分 handler 分析、运行时类型引用构造、注册器源码发射与精确反射注册输出阶段,清空该项目当前 `MA0051`
|
||||
- `RP-019` 转入 `GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs`,先完成低风险 `MA0006` 批次;
|
||||
通过 schema 类型比较 helper 与显式 `StringComparison.Ordinal` 清空当前项目的 `MA0006`
|
||||
- `RP-020` 继续拆分 `SchemaConfigGenerator.cs` 的 `MA0051` 热点,将当前项目 warnings-only 基线从 `19` 条降到 `9` 条,
|
||||
并用 focused schema generator tests 验证 50 个用例通过
|
||||
- `RP-032` 已完成 `AutoRegisterModuleGeneratorTests` 的 3 个 `MA0051` 收口:通过提取类级常量承载测试源码与快照,保持
|
||||
生成文件名、断言路径与源生成输出不变;`GFramework.SourceGenerators.Tests` warnings-only 基线由 `43` 降至 `40`
|
||||
- `RP-033` 已完成 `SchemaConfigGeneratorSnapshotTests` 的 `MA0051` 收口:monster schema 运行时契约与 schema 输入已提取为
|
||||
类级常量,生成结果映射与快照目录解析已拆为小 helper;`GFramework.SourceGenerators.Tests` warnings-only 基线由 `40` 降至 `39`
|
||||
- `RP-035` 已完成启动级恢复核对:当前分支对应的 GitHub PR #273 已关闭,因此 remaining open thread 仅作为历史信号参考;
|
||||
下一轮应以本地 `warnings-only` build 的实时热点为主,而不是继续按已过时的 `GeneratorSnapshotTest` /
|
||||
`ContextRegistrationAnalyzerTests` 建议恢复
|
||||
- `RP-036` 已完成 `SchemaConfigGeneratorTests` 的单文件 `MA0051` 收口:共享 runtime fixture、
|
||||
generated-source 收集与 catalog 契约断言均已拆出 helper;当前测试项目剩余 `MA0051` 已全部收敛到
|
||||
`CqrsHandlerRegistryGeneratorTests`
|
||||
- `RP-037` 已验证 subagent 循环开始产生稳定吞吐,但当前一轮只消掉 `1` 个 warning 位点;
|
||||
若继续按“唯一变更文件数接近 `75`”推进,需要接受很多轮单文件、单方法级切片
|
||||
- `RP-038` 继续验证了“单方法 + 主线程记录恢复点”的 subagent 节奏可稳定复用,但按当前速度离
|
||||
“接近 `75` 个唯一变更文件”仍然非常远
|
||||
- `RP-039` 进一步确认:只要 subagent 继续在同一热点文件内逐点消除 warning,唯一变更文件数会基本停留在 `4`
|
||||
左右,不会因为重复修改同一文件快速逼近 `75`
|
||||
- `RP-040` 延续了这一趋势:当前吞吐稳定,但按“唯一变更文件数接近 `75`”作为停止条件并不匹配当前单文件收口节奏
|
||||
- `RP-041` 再次确认当前分支相对 `origin/main` 的唯一变更文件数仍是 `4`;若继续只收口同一文件的 warning,
|
||||
该计数基本不会上涨
|
||||
- `RP-021` 使用 `$gframework-pr-review` 复核当前分支 PR #269 后,修复仍在本地成立的 4 个项:将
|
||||
`CqrsHandlerRegistryGenerator` 拆分为职责清晰的 partial 文件、为 `ContextAwareGenerator` 生成字段增加稳定前缀并补上
|
||||
`SetContextProvider` 的运行时 null 校验、为 `Option<T>` 补齐 `<remarks>`,并新增字段重名场景的生成器快照测试
|
||||
- `RP-022` 继续复核 PR #269 的 latest-head review threads 与 nitpick,确认仍成立的项包括公共 API 兼容回退、
|
||||
`ContextAwareGenerator` 字段名真正去冲突与锁内读取、`SchemaConfigGenerator` 取消传播、`Cqrs` 运行时类型 null 防御;
|
||||
已补齐对应回归测试与 focused build/test 验证
|
||||
- `RP-023` 继续复核 PR #269 剩余 nitpick/outside-diff 项,确认仍成立的项集中在 `SchemaConfigGenerator` 根类型名校验、
|
||||
aggregate registration comparer XML 文档转义、logging / collection 反射测试补强,以及跟踪文档中的
|
||||
`RestoreFallbackFolders=""` 可复制性问题
|
||||
- `RP-024` 使用 `$gframework-pr-review` 继续复核 PR #269 latest-head unresolved threads,确认 `EasyEvents` 异常契约、
|
||||
`SchemaConfigGenerator` 取消传播与 `ContextAwareGenerator` 快照冲突线程均已在本地收口,仅剩 `Cqrs` error type
|
||||
直接引用与根 schema `type` 非字符串防御仍成立;现已补齐实现与回归测试
|
||||
- `RP-025` 继续复核 PR #269 剩余 outside-diff / nitpick 信号后,确认本地仍成立的是 `SchemaConfigGenerator`
|
||||
的归一化字段名冲突与 `Cqrs` 对 `dynamic` 的直接类型引用;已分别补上诊断、运行时类型归一化与回归测试,
|
||||
并把“变更模块必须运行对应 build 且处理 warning”的治理规则写回 `AGENTS.md`
|
||||
- `RP-029` 已完成 `SchemaConfigGenerator` 剩余 `MA0051` 收口:`GFramework.Game.SourceGenerators` 独立 Release
|
||||
warnings-only build 已清零,并通过 `SchemaConfigGenerator` focused generator tests 锁定生成输出未回退
|
||||
- `RP-030` 已完成 `GFramework.SourceGenerators.Tests` 低风险 `MA0004` / `MA0048` 收口:`AnalyzerTestDriver` 文件名已与
|
||||
类型名一致,测试辅助器与 schema snapshot 断言路径已改为直接返回 `Task` 或显式使用 `ConfigureAwait(false)`;
|
||||
当前测试项目 warnings-only 基线从 `61` 条降到 `49` 条,剩余均为 `MA0051`
|
||||
- `RP-031` 已完成 `LoggerGeneratorSnapshotTests` 的 `MA0051` 收口:重复场景源码改为模板化 helper 生成,
|
||||
当前 `GFramework.SourceGenerators.Tests` Release build 基线从 `49` 条降到 `43` 条,并通过 focused test 验证 6 个用例全部通过
|
||||
- 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射
|
||||
|
||||
## 当前风险
|
||||
|
||||
- analyzer 收口回退风险:后续若继续压 `MA0015` / `MA0016`,容易再次把公共 API 收窄成与既有契约不兼容的形状
|
||||
- 缓解措施:优先保留既有公共 API,并将兼容性例外收敛到局部 pragma;继续用反射断言覆盖返回类型、属性类型与异常类型
|
||||
- 测试宿主稳定性风险:部分 Godot 失败路径在当前 .NET 测试宿主下仍不稳定
|
||||
- 缓解措施:继续优先使用稳定的 targeted test、项目构建和相邻 smoke test 组合验证
|
||||
- 多目标框架 warning 解释风险:同一源位置会在多个 target framework 下重复计数
|
||||
- 缓解措施:继续以唯一源位置和 warning 家族为主要决策依据,而不是只看原始 warning 总数
|
||||
- net10 专属 warning 风险:`MA0158` 建议使用 `System.Threading.Lock`,但项目多 target 时需要确认兼容边界
|
||||
- 缓解措施:下一轮先按 target framework 与 API 可用性评估,不直接批量替换共享源码中的 `object` lock
|
||||
- source generator warning 外溢风险:运行 `GFramework.SourceGenerators.Tests` 会构建相邻 generator/test 项目,并在输出中混入
|
||||
测试项目自身的结构性 warning 基线
|
||||
- 缓解措施:继续以被修改项目的独立 warnings-only build 作为主验收,并用 focused generator test 验证行为
|
||||
- source generator test warning 治理风险:`GFramework.SourceGenerators.Tests` 当前仍有 `43` 条既有 `MA0051` warning;
|
||||
一旦继续进入该写集,就必须把测试项目 warning 一并纳入本轮完成条件
|
||||
- 缓解措施:已先清空低风险 `MA0004` / `MA0048`,后续继续保持“单 warning family、单测试域”的节奏推进 `MA0051`
|
||||
- ContextAware 基类命名隐藏风险:若生成器只看当前类型声明成员,派生规则会重新占用基类已声明的
|
||||
`_gFrameworkContextAware*` 字段名,导致生成成员隐藏继承状态并让快照无法锁定后缀分配行为
|
||||
- 缓解措施:本轮已改为遍历完整 base-type 链收集保留名,并用 inherited collision 快照用例锁定该行为
|
||||
- Godot 资产文件环境风险:当前 worktree 的 `GFramework.Godot` restore/build 仍会命中 Windows fallback package folder
|
||||
- 缓解措施:后续若继续触达 Godot 模块,先用 Linux 侧 restore 资产或 Windows-hosted 构建链刷新该项目,再补跑定向 build
|
||||
- 并行实现风险:批量收敛时若 subagent 写入边界不清晰,容易引入命名冲突或重复重构
|
||||
- 缓解措施:只在 warning 类型或目录边界清晰时并行;每个 subagent 必须有独占文件 ownership,主代理负责合并验证
|
||||
|
||||
## 活跃文档
|
||||
|
||||
- 历史跟踪归档:[analyzer-warning-reduction-history-rp001.md](analyzer-warning-reduction-history-rp001.md)
|
||||
- 历史 trace 归档:[analyzer-warning-reduction-history-rp001.md](../traces/analyzer-warning-reduction-history-rp001.md)
|
||||
|
||||
## 验证说明
|
||||
|
||||
- `RP-001` 的详细 warning 清理、回归修复与定向验证命令均已迁入主题内历史归档
|
||||
- `RP-002` 的定向验证结果:
|
||||
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:UseSharedCompilation=false -p:RestoreFallbackFolders=`
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter FullyQualifiedName~CqrsHandlerRegistrarTests -p:RestoreFallbackFolders=`
|
||||
- `RP-003` 的定向验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -p:RestoreFallbackFolders= -nologo -clp:Summary;WarningsOnly`
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter FullyQualifiedName~ArchitectureLifecycleBehaviorTests -p:RestoreFallbackFolders=`
|
||||
- `RP-004` 的定向验证结果:
|
||||
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:UseSharedCompilation=false -p:RestoreFallbackFolders=`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`
|
||||
- `RP-005` 的定向验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`27 Warning(s)`,`0 Error(s)`;`PauseStackManager.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter FullyQualifiedName~PauseStackManagerTests -p:RestoreFallbackFolders=`
|
||||
- 结果:`25 Passed`,`0 Failed`
|
||||
- `RP-006` 的定向验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -p:RestorePackagesPath=<linux-nuget-cache> -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`25 Warning(s)`,`0 Error(s)`;`Store.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter FullyQualifiedName~StoreTests -p:RestoreFallbackFolders="" -p:RestorePackagesPath=<linux-nuget-cache> -nologo`
|
||||
- 结果:`30 Passed`,`0 Failed`
|
||||
- `RP-007` 的定向验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -p:RestorePackagesPath=<linux-nuget-cache> -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`23 Warning(s)`,`0 Error(s)`;`CoroutineScheduler.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter FullyQualifiedName~CoroutineScheduler -p:RestoreFallbackFolders="" -p:RestorePackagesPath=<linux-nuget-cache> -nologo`
|
||||
- 结果:`34 Passed`,`0 Failed`
|
||||
- `RP-008` 的策略基线:
|
||||
- 当前 `GFramework.Core` 剩余 warning 分布:`MA0048=8`、`MA0046=6`、`MA0016=5`、`MA0002=2`、`MA0015=1`、`MA0077=1`
|
||||
- 后续批处理规则:优先按类型推进;若当轮主类型数量不足,可顺手吸收其他低冲突类型,不限定于 `MA0015` 与 `MA0077`
|
||||
- `RP-009` 的定向验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`15 Warning(s)`,`0 Error(s)`;当前 `GFramework.Core` `net8.0` warnings-only 输出中已不再出现 `MA0048`
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~CommandExecutorTests|FullyQualifiedName~AbstractAsyncCommandTests|FullyQualifiedName~QueryExecutorTests|FullyQualifiedName~AbstractAsyncQueryTests|FullyQualifiedName~EventTests" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`83 Passed`,`0 Failed`
|
||||
- `RP-010` 的定向验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`15 Warning(s)`,`0 Error(s)`;新增修复未引入新的 `GFramework.Core` `net8.0` 构建错误
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~CoroutineSchedulerTests.Run_Should_Grow_From_Zero_Initial_Capacity|FullyQualifiedName~StoreTests.Dispatch_Should_Reset_Dispatching_Flag_When_Snapshot_Creation_Throws" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`2 Passed`,`0 Failed`
|
||||
- `RP-011` 的定向验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`15 Warning(s)`,`0 Error(s)`;`Event.cs` 的 listener count 修复未引入新的 `GFramework.Core` `net8.0` 构建错误
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~EventTests.EventT_GetListenerCount_Should_Exclude_Placeholder_Handler|FullyQualifiedName~EventTests.EventTTK_GetListenerCount_Should_Exclude_Placeholder_Handler" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`2 Passed`,`0 Failed`
|
||||
- `RP-012` 的定向验证结果:
|
||||
- `python3 -m py_compile .codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py`
|
||||
- 结果:通过;使用 `PYTHONPYCACHEPREFIX=/tmp/codex-pycache` 规避技能目录只读导致的 `__pycache__` 写入限制
|
||||
- `python3 .codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --help`
|
||||
- 结果:通过;`--json-output`、`--section`、`--path`、`--max-description-length` 已出现在 CLI 帮助中
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`
|
||||
- `RP-013` 的定向验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`9 Warning(s)`,`0 Error(s)`;相对 `RP-009` / `RP-011` 的 warnings-only 基线 `15 Warning(s)` 已降到 `9 Warning(s)`,
|
||||
当前 `GFramework.Core` `net8.0` 输出中已不再出现 `MA0046`
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureLifecycleBehaviorTests|FullyQualifiedName~CoroutineSchedulerTests|FullyQualifiedName~AsyncLogAppenderTests" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`50 Passed`,`0 Failed`
|
||||
- `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release --no-restore -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:失败;当前 worktree 的 Godot restore 资产仍引用 Windows fallback package folder,尚未完成独立项目编译验证
|
||||
- `RP-014` 的定向验证结果:
|
||||
- `dotnet restore GFramework.Core.Tests/GFramework.Core.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过;host Windows `dotnet` 首次验证前补齐了缺失的 `Meziantou.Analyzer 3.0.48` 包
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`9 Warning(s)`,`0 Error(s)`;`AsyncLogAppender` 行为修复与 XML / 文档补充未引入新的 `GFramework.Core` `net8.0` 构建错误
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~CoroutineSchedulerTests.Scheduler_Should_Raise_OnCoroutineException_With_EventArgs|FullyQualifiedName~AsyncLogAppenderTests.Flush_Should_Raise_OnFlushCompleted_With_Sender_And_Result|FullyQualifiedName~AsyncLogAppenderTests.ILogAppender_Flush_Should_Raise_OnFlushCompleted_Only_Once|FullyQualifiedName~ArchitectureLifecycleBehaviorTests.InitializeAsync_Should_Raise_PhaseChanged_With_Sender_And_EventArgs" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`4 Passed`,`0 Failed`
|
||||
- `RP-015` 的验证结果:
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --disable-build-servers --filter "FullyQualifiedName~AsyncLogAppenderTests"`
|
||||
- 结果:`15 Passed`,`0 Failed`
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --disable-build-servers`
|
||||
- 结果:`1607 Passed`,`0 Failed`
|
||||
- `RP-016` 的验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`;当前 `GFramework.Core` `net8.0` analyzer baseline 已清零
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~LoggingConfigurationTests|FullyQualifiedName~ConfigurableLoggerFactoryTests|FullyQualifiedName~CollectionExtensionsTests|FullyQualifiedName~EasyEventsTests|FullyQualifiedName~OptionTests|FullyQualifiedName~CoroutineGroupTests|FullyQualifiedName~CoroutineSchedulerTests" -m:1 -nologo`
|
||||
- 结果:`112 Passed`,`0 Failed`;测试构建仍会显示既有 `net10.0` `MA0158` 与 source generator `MA0051` warning
|
||||
- `RP-017` 的验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net10.0 -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`16 Warning(s)`,`0 Error(s)`;当前 `MA0158` 跨 `GFramework.Core` / `GFramework.Cqrs`,本轮只记录基线不批量改锁
|
||||
- `dotnet build GFramework.Core.SourceGenerators/GFramework.Core.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`;`ContextAwareGenerator.cs` 已不再出现 `MA0051`
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~ContextAwareGeneratorSnapshotTests" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`1 Passed`,`0 Failed`;测试构建仍显示相邻 source generator 和测试项目的既有 analyzer warning
|
||||
- `RP-018` 的验证结果:
|
||||
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`;`CqrsHandlerRegistryGenerator.cs` 当前 `MA0051` 已清零
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter FullyQualifiedName~CqrsHandlerRegistryGeneratorTests -m:1 -p:RestoreFallbackFolders= -nologo`
|
||||
- 结果:`14 Passed`,`0 Failed`
|
||||
- 说明:该 test project 构建仍显示 `GFramework.Game.SourceGenerators` 与测试项目中的既有 analyzer warning;本轮关注的
|
||||
`GFramework.Cqrs.SourceGenerators` 独立 build 已清零
|
||||
- `RP-019` 的验证结果:
|
||||
- `dotnet restore GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -p:RestoreFallbackFolders= -nologo`
|
||||
- 结果:通过;刷新 Linux 侧资产以清除 stale Windows fallback package folder
|
||||
- `dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`19 Warning(s)`,`0 Error(s)`;当前项目输出已不再出现 `MA0006`,剩余均为 `SchemaConfigGenerator.cs` 的
|
||||
`MA0051`
|
||||
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders= -nologo`
|
||||
- 结果:通过;刷新 test project 资产以清除 stale Windows fallback package folder
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter FullyQualifiedName~SchemaConfigGenerator -m:1 -p:RestoreFallbackFolders= -nologo`
|
||||
- 结果:`50 Passed`,`0 Failed`
|
||||
- 说明:测试项目构建仍显示既有 source generator test analyzer warning;不属于本轮写集
|
||||
- `RP-020` 的验证结果:
|
||||
- `dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`9 Warning(s)`,`0 Error(s)`;当前项目剩余 warning 均为 `SchemaConfigGenerator.cs` 的 `MA0051`
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter FullyQualifiedName~SchemaConfigGenerator -m:1 -p:RestoreFallbackFolders= -nologo`
|
||||
- 结果:`50 Passed`,`0 Failed`
|
||||
- 说明:测试项目构建仍显示既有 source generator test analyzer warning;不属于本轮写集
|
||||
- `RP-021` 的验证结果:
|
||||
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`;拆分后最大单文件已降到 `851` 行,满足仓库 800-1000 行上限
|
||||
- `dotnet build GFramework.Core.SourceGenerators/GFramework.Core.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`;`ContextAwareGenerator` 的字段命名与 provider 契约修复未引入新的 generator warning
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~ContextAwareGeneratorSnapshotTests|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests" -m:1 -p:RestoreFallbackFolders= -nologo`
|
||||
- 结果:先并行运行两条 `dotnet test` 时触发共享输出文件锁冲突;改为串行重跑后 `ContextAwareGeneratorSnapshotTests=2 Passed`、
|
||||
`CqrsHandlerRegistryGeneratorTests=14 Passed`
|
||||
- 说明:失败来自测试宿主并行写入同一 build 输出,不是代码回归;串行重跑后快照新增的字段重名场景和 CQRS 快照均通过
|
||||
- `RP-022` 的验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:通过;`EasyEvents`、`CollectionExtensions` 与 logging 配置模型的兼容性回退未引入新的 `net8.0` 构建错误
|
||||
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:通过;`ContainingAssembly` null 防御与发射 helper 精简未引入新的构建错误
|
||||
- `dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:通过;仍保留既有 `9` 条 `SchemaConfigGenerator.cs` `MA0051` warning,非本轮新增
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~SchemaConfigGeneratorTests|FullyQualifiedName~ContextAwareGeneratorSnapshotTests|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests" -m:1 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:`63 Passed`,`0 Failed`
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~EasyEventsTests|FullyQualifiedName~CollectionExtensionsTests|FullyQualifiedName~LoggingConfigurationTests|FullyQualifiedName~ConfigurableLoggerFactoryTests" -m:1 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:`38 Passed`,`0 Failed`
|
||||
- `RP-023` 的验证结果:
|
||||
- `dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release --no-restore -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:通过;仍保留既有 `9` 条 `SchemaConfigGenerator.cs` `MA0051` warning,未新增新的 generator warning
|
||||
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release --no-restore -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:通过;并行构建时 `GFramework.SourceGenerators.Common.dll` 复制阶段出现一次 `MSB3026` 重试,随后成功完成
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~SchemaConfigGeneratorTests|FullyQualifiedName~SchemaConfigGeneratorSnapshotTests|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests" -m:1 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:`63 Passed`,`0 Failed`
|
||||
- 说明:测试项目构建仍会显示既有 `GFramework.SourceGenerators.Tests` analyzer warning;不属于本轮写集
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~CollectionExtensionsTests|FullyQualifiedName~LoggingConfigurationTests" -m:1 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:`27 Passed`,`0 Failed`
|
||||
- `RP-029` 的验证结果:
|
||||
- `dotnet restore GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过;刷新 Linux 侧 restore 资产以移除 Windows fallback package folder 干扰
|
||||
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过;focused test 所属测试项目已同步刷新 Linux 侧 restore 资产
|
||||
- `dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`;`SchemaConfigGenerator.cs` 剩余 `MA0051` 已清零
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter FullyQualifiedName~SchemaConfigGenerator -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`54 Passed`,`0 Failed`
|
||||
- 说明:测试项目构建仍显示既有 `GFramework.SourceGenerators.Tests` `MA0048` / `MA0051` / `MA0004` warning;不属于本轮
|
||||
`GFramework.Game.SourceGenerators` 写集
|
||||
- `RP-030` 的验证结果:
|
||||
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过;刷新 Linux 侧 restore 资产以移除 Windows fallback package folder 干扰
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`49 Warning(s)`,`0 Error(s)`;当前项目已不再出现 `MA0004` / `MA0048`,剩余 warning 全部为 `MA0051`
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~GeneratorSnapshotTestSecurityTests|FullyQualifiedName~SchemaConfigGeneratorSnapshotTests|FullyQualifiedName~SchemaConfigGeneratorEnumTests" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`6 Passed`,`0 Failed`
|
||||
- `RP-031` 的验证结果:
|
||||
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过;刷新 Linux 侧 restore 资产以支持后续串行 build/test 验证
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:Summary`
|
||||
- 结果:`43 Warning(s)`,`0 Error(s)`;`LoggerGeneratorSnapshotTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --disable-build-servers --filter FullyQualifiedName~LoggerGeneratorSnapshotTests -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`6 Passed`,`0 Failed`
|
||||
- `RP-033` 的验证结果:
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`39 Warning(s)`,`0 Error(s)`;`SchemaConfigGeneratorSnapshotTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --disable-build-servers --filter FullyQualifiedName~SchemaConfigGeneratorSnapshotTests -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`1 Passed`,`0 Failed`
|
||||
- `RP-035` 的验证结果:
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/current-pr-review.json`
|
||||
- 结果:成功定位当前分支关联的 `PR #273`;状态为 `CLOSED`,latest-head review threads 仍显示 `2` 条 open thread,
|
||||
test report 均为通过,MegaLinter 仅保留 docstring coverage / `dotnet-format` 历史信号
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`22 Warning(s)`,`0 Error(s)`;剩余 `MA0051` 全部集中在 `CqrsHandlerRegistryGeneratorTests.cs` 与
|
||||
`SchemaConfigGeneratorTests.cs`
|
||||
- `RP-036` 的验证结果:
|
||||
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过;刷新测试依赖输出,规避 `--no-build` 场景下的缺包噪音
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`15 Warning(s)`,`0 Error(s)`;`SchemaConfigGeneratorTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --disable-build-servers --filter FullyQualifiedName~SchemaConfigGeneratorTests -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`50 Passed`,`0 Failed`
|
||||
- `RP-037` 的验证结果:
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`14 Warning(s)`,`0 Error(s)`;`CqrsHandlerRegistryGeneratorTests.cs` 的行号 `337` 已不再出现在 `MA0051` 列表中
|
||||
- `RP-038` 的验证结果:
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`13 Warning(s)`,`0 Error(s)`;`CqrsHandlerRegistryGeneratorTests.cs` 的行号 `454` 已不再出现在 `MA0051` 列表中
|
||||
- `RP-039` 的验证结果:
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`12 Warning(s)`,`0 Error(s)`;`CqrsHandlerRegistryGeneratorTests.cs` 的行号 `536` 已不再出现在 `MA0051` 列表中
|
||||
- `RP-040` 的验证结果:
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`11 Warning(s)`,`0 Error(s)`;`CqrsHandlerRegistryGeneratorTests.cs` 的行号 `607` 已不再出现在 `MA0051` 列表中
|
||||
- `RP-041` 的验证结果:
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`10 Warning(s)`,`0 Error(s)`;`CqrsHandlerRegistryGeneratorTests.cs` 的行号 `680` 已不再出现在 `MA0051` 列表中
|
||||
- active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 若要继续该主题,先读 active tracking,再按需展开历史归档中的 warning 热点与验证记录
|
||||
2. 下一轮优先继续 `GFramework.SourceGenerators.Tests` 的 `MA0051` 收口,并直接进入唯一剩余热点
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`;但若用户真正关心“唯一变更文件数接近 `75`”,下一轮应改为选择新的文件写集,
|
||||
而不是继续停留在当前同一文件
|
||||
3. 若改回推进 `MA0158`,先设计 `net8.0` / `net9.0` / `net10.0` 多 target 条件编译方案,不直接批量替换共享源码中的
|
||||
`object` lock
|
||||
4. 若后续继续改动 `GFramework.Godot`,先修复该项目的 Linux 侧 restore 资产,再补跑独立 build
|
||||
5. 若本主题确认暂缓,可保持当前归档状态,不需要再恢复 `local-plan/`
|
||||
@ -0,0 +1,16 @@
|
||||
# Analyzer Warning Reduction 跟踪历史(RP-042 至 RP-048)
|
||||
|
||||
## 范围说明
|
||||
|
||||
本归档承接 `RP-042` 至 `RP-048` 的晚期 active todo 内容,保留当时围绕 warning-reduction batch、baseline 与构建入口讨论的阶段性结论。
|
||||
|
||||
## 归档摘要
|
||||
|
||||
- 曾记录 `origin/main` baseline、branch diff 文件数与行数,用于 `$gframework-batch-boot 75` 的批处理停点判断
|
||||
- 曾记录 `UnifiedSettingsFile`、`UnifiedSettingsDataRepository`、`LocalizationMap` 与 `CqrsHandlerRegistryGeneratorTests` 的 warning-reduction 切片已提交到当前分支
|
||||
- 曾记录 RP-048 时在仓库根目录执行 plain `dotnet build` 成功,结果为 `0 Warning(s)` / `0 Error(s)`
|
||||
- 这些内容在 RP-049 之后不再保留在 active todo 中,因为当前恢复入口应只聚焦“plain `dotnet build` 是否打印 warning”这个真值
|
||||
|
||||
## superseded by
|
||||
|
||||
- [analyzer-warning-reduction-tracking.md](../../todos/analyzer-warning-reduction-tracking.md)
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,16 @@
|
||||
# Analyzer Warning Reduction 追踪历史(RP-042 至 RP-048)
|
||||
|
||||
## 范围说明
|
||||
|
||||
本归档承接 `RP-042` 至 `RP-048` 的 late-stage trace,保留 active trace 在被 RP-049 压缩前的关键执行背景。
|
||||
|
||||
## 归档摘要
|
||||
|
||||
- 记录了 warning-reduction batch 在 `origin/main` 基线上的 diff 指标与“接近 75 个文件时停止”的批处理语境
|
||||
- 记录了对 plain `dotnet build` 与带参数构建命令的比较,以及当时对 warning 检查入口的整理过程
|
||||
- 记录了 RP-048 已确认默认 `dotnet build` 成功且当前工作树无活动代码修改
|
||||
- RP-049 之后,这些内容不再作为默认恢复入口,而改为保存在 archive 供历史追溯
|
||||
|
||||
## superseded by
|
||||
|
||||
- [analyzer-warning-reduction-trace.md](../../traces/analyzer-warning-reduction-trace.md)
|
||||
@ -2,516 +2,64 @@
|
||||
|
||||
## 目标
|
||||
|
||||
继续以“优先低风险、保持行为兼容”为原则收敛当前仓库的 Meziantou analyzer warnings,并在首轮大规模清理完成后,
|
||||
判断剩余结构性 warning 是否值得在下一轮继续推进。
|
||||
继续以“直接看构建输出、直接修构建 warning”为原则推进当前分支,并保持 active recovery 文档只保留当前真值。
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-041`
|
||||
- 当前阶段:`Phase 41`
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-051`
|
||||
- 当前阶段:`Phase 51`
|
||||
- 当前焦点:
|
||||
- 已通过第五个有效 subagent 切片完成
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
`Generates_Precise_Service_Type_For_Hidden_Generic_Type_Definitions()` 的 `MA0051` 收口:
|
||||
将内联 `source` 文本提取为类级常量,保持既有 expected 常量和断言语义不变
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release warnings-only 基线已从 `11` 条降到 `10` 条;
|
||||
行号 `680` 已从 `MA0051` 输出中消失,剩余热点继续集中在
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
- 已通过第四个有效 subagent 切片完成
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
`Generates_Precise_Service_Type_For_Hidden_Array_Type_Arguments()` 的 `MA0051` 收口:
|
||||
将内联 `source` 文本提取为类级常量,保持既有 expected 常量和断言语义不变
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release warnings-only 基线已从 `12` 条降到 `11` 条;
|
||||
行号 `607` 已从 `MA0051` 输出中消失,剩余热点继续集中在
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
- 已通过第三个有效 subagent 切片完成
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
`Generates_Direct_Interface_Registrations_For_Hidden_Implementation_When_Handler_Interface_Is_Public()`
|
||||
的 `MA0051` 收口:将内联 `source` 文本提取为类级常量,保持既有 expected 常量和断言语义不变
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release warnings-only 基线已从 `13` 条降到 `12` 条;
|
||||
行号 `536` 已从 `MA0051` 输出中消失,剩余热点继续集中在
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
- 已通过第二个有效 subagent 切片完成
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
`Generates_Visible_Handlers_And_Self_Registers_Private_Nested_Handler_When_Assembly_Contains_Hidden_Handler()`
|
||||
的 `MA0051` 收口:将内联 `source` 文本提取为类级常量,保持既有 expected 常量和断言语义不变
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release warnings-only 基线已从 `14` 条降到 `13` 条;
|
||||
行号 `454` 已从 `MA0051` 输出中消失,剩余热点继续集中在
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
- 已通过 subagent 循环的首个可交付切片完成
|
||||
`GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs`
|
||||
`Generates_Assembly_Level_Cqrs_Handler_Registry()` 的 `MA0051` 收口:
|
||||
将内联 `source` / `expected` 文本提取为类级常量,保持生成文本、断言语义和文件名不变
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release warnings-only 基线已从 `15` 条降到 `14` 条;
|
||||
行号 `337` 已从 `MA0051` 输出中消失,剩余热点仍全部集中在
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
- 已确认当前分支相对 `origin/main` 的唯一变更文件数仍只有 `3`;按该统计口径距离用户要求的
|
||||
“接近 `75` 个文件变更”仍很远,需要继续多轮切片
|
||||
- 已完成 `GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorTests.cs` 的 `MA0051` 收口:
|
||||
将共享 consumer runtime fixture 提取到类级常量,并把生成结果收集与 catalog 契约断言拆成小 helper,
|
||||
保持 schema 文本、断言语义与生成输出契约不变
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release warnings-only 基线已从 `22` 条降到 `15` 条;
|
||||
`SchemaConfigGeneratorTests.cs` 已不再出现在 `MA0051` 列表中,剩余热点全部集中在
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`
|
||||
- 已完成 `SchemaConfigGeneratorTests` 定向验证:串行重跑 `50` 个用例全部通过;并确认先前并行 build/test
|
||||
触发的 `MSB3030` / `CS0006` 属于共享输出竞争噪音,不是代码回归
|
||||
- 已按 `gframework-boot` 重新恢复当前 worktree:确认分支 `fix/analyzer-warning-reduction-batch` 仍映射到
|
||||
`analyzer-warning-reduction`,且当前不存在 `ai-plan/private/` 私有恢复上下文
|
||||
- 已重新抓取当前分支关联的 PR #273 review 状态:PR 已处于 `CLOSED`,latest-head review 仍显示 `2` 条
|
||||
CodeRabbit open thread,但本地复核后 `GeneratorSnapshotTest` 的 snapshot 路径空值防御已显式改为
|
||||
`InvalidOperationException` 防御,`SchemaConfigGenerator` 的 `dependentSchemas` / `allOf` / conditional helper
|
||||
也已补齐 XML 文档,当前更像历史线程未随已关闭 PR 一起收敛
|
||||
- 已重新以 `GFramework.SourceGenerators.Tests` Release warnings-only build 复核当前 `MA0051` 热点:
|
||||
基线现为 `22` 条,且已不再落在 `GeneratorSnapshotTest`、`ContextRegistrationAnalyzerTests` 或
|
||||
`ContextGetGeneratorTests`,而是集中在 `CqrsHandlerRegistryGeneratorTests.cs`(`15` 条)与
|
||||
`SchemaConfigGeneratorTests.cs`(`7` 条)
|
||||
- 已完成 `GFramework.Core` 当前 `MA0016` / `MA0002` / `MA0015` / `MA0077` 低风险收口批次
|
||||
- 已复核 `net10.0` 下的 `MA0158` 基线:`GFramework.Core` / `GFramework.Cqrs` 当前共有 `16` 个 object lock
|
||||
建议点,属于跨 target 兼容性风险,不在本轮直接批量替换
|
||||
- 已完成 `GFramework.Core.SourceGenerators/Rule/ContextAwareGenerator.cs` 的剩余 `MA0051` 结构拆分,生成输出保持不变
|
||||
- 已完成 `GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 的 `MA0051` 结构拆分,生成输出保持不变
|
||||
- 已完成 `GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs` 的 `MA0006` 低风险收口,schema 关键字比较显式使用
|
||||
`StringComparison.Ordinal`
|
||||
- 已完成 `SchemaConfigGenerator.cs` 的第一批 `MA0051` 结构拆分:schema 入口解析、属性解析、schema 遍历、数组属性解析、
|
||||
约束文档生成与若干生成代码发射 helper 已拆出语义阶段
|
||||
- 已完成当前 PR #269 review follow-up:`CqrsHandlerRegistryGenerator` 按职责拆分为 partial 生成器文件,
|
||||
`ContextAwareGenerator` 已补上字段名去冲突与锁内读取修正,`Option<T>` 补齐 `<remarks>` 契约说明
|
||||
- 已完成当前 PR #269 第二轮 follow-up:恢复 `EasyEvents`、`CollectionExtensions`、`LoggingConfiguration` 与
|
||||
`FilterConfiguration` 的公共 API 兼容形状,并将 analyzer 兼容性处理收敛到局部 pragma
|
||||
- 已完成当前 PR #269 第三轮 follow-up:继续收口 `SchemaConfigGenerator` 的根类型标识符校验与 XML 文档转义,
|
||||
并补齐 `LoggingConfigurationTests`、`CollectionExtensionsTests`、`Cqrs` helper 抽取与 `ai-plan` 命令文本修正
|
||||
- 已完成当前 PR #269 第四轮 follow-up:将 `CqrsHandlerRegistryGenerator` 的 Roslyn error type 直接引用改为
|
||||
运行时精确查找路径,并为 `SchemaConfigGenerator` 补上根 `type` 非字符串时的防御与回归测试
|
||||
- 已完成当前 PR #269 第五轮 follow-up:`SchemaConfigGenerator` 补上归一化后属性名冲突诊断并新增
|
||||
`GF_ConfigSchema_014`,`CqrsHandlerRegistryGenerator` 将 `dynamic` 归一化为 `global::System.Object`,
|
||||
同时收紧相关 generator regression tests
|
||||
- 已完成当前 PR #269 failed-test follow-up:修正 `SchemaConfigGeneratorTests`
|
||||
`Run_Should_Assign_Globally_Unique_Reference_Metadata_Member_Names` 的测试输入,使其继续覆盖
|
||||
reference metadata 成员名全局去冲突,但不再依赖现已被 `GF_ConfigSchema_014` 拦截的非法同层 schema key 冲突
|
||||
- 已完成当前 PR #269 Greptile follow-up:`ContextAwareGenerator` 现在会把基类链显式成员名也纳入
|
||||
`_gFrameworkContextAware*` 字段分配冲突检测,并新增 inherited-field collision 快照回归测试
|
||||
- 已完成当前分支与 `main` 的 `CqrsHandlerRegistryGenerator.cs` 文件级冲突收口:确认 `main` 侧新增的是
|
||||
`OrderedRegistrationKind` / `RuntimeTypeReferenceSpec` 的 XML 文档,现已按当前 partial 拆分结构迁移到
|
||||
`CqrsHandlerRegistryGenerator.Models.cs`,不回退已完成的生成器拆分
|
||||
- 已完成 `SchemaConfigGenerator.cs` 剩余 `MA0051` 收口:将 `dependentRequired` / `allOf` / conditional schema 校验
|
||||
拆成更小的验证阶段,并将 `GenerateTableClass`、`GenerateBindingsClass`、`AppendGeneratedConfigCatalogType`
|
||||
拆成稳定的代码发射 helper,保持生成输出与快照一致
|
||||
- 已更新 `AGENTS.md`:变更模块必须运行对应 `dotnet build -c Release`,并处理或显式报告模块构建 warning,
|
||||
不再默认留给长期 warning 清理分支
|
||||
- `CoroutineScheduler` 的 tag/group 字典已显式使用 `StringComparer.Ordinal`,保持既有区分大小写语义
|
||||
- `EasyEvents.AddEvent<T>()` 的重复注册路径已恢复为 `ArgumentException`,以保持既有异常契约
|
||||
- `Option<T>` 已声明 `IEquatable<Option<T>>`,与已有强类型 `Equals(Option<T>)` 契约对齐
|
||||
- 当前 `GFramework.Core` `net8.0` warnings-only 基线已降到 `0` 条
|
||||
- 当前 `GFramework.Core.SourceGenerators` warnings-only 基线已降到 `0` 条
|
||||
- 当前 `GFramework.Cqrs.SourceGenerators` warnings-only 基线已降到 `0` 条
|
||||
- 当前 `GFramework.Game.SourceGenerators` warnings-only 基线已从 `46` 条降到 `0` 条
|
||||
- 已完成 `GFramework.SourceGenerators.Tests` 低风险 `MA0004` / `MA0048` 收口:测试辅助器改为直接返回 `Task`,
|
||||
文件 I/O 显式补齐 `ConfigureAwait(false)`,`AnalyzerTestDriver` 文件名与类型名重新对齐
|
||||
- 当前 `GFramework.SourceGenerators.Tests` warnings-only 基线已从 `61` 条降到 `49` 条,剩余 warning 均为 `MA0051`
|
||||
- 已完成 `GFramework.SourceGenerators.Tests/Logging/LoggerGeneratorSnapshotTests.cs` 的 `MA0051` 收口:同构 snapshot
|
||||
场景已收敛为模板化 helper,保留原有快照目录与生成器输入语义不变
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release build 基线已从 `49` 条降到 `43` 条;`LoggerGeneratorSnapshotTests.cs`
|
||||
已不再出现在 `MA0051` 列表中
|
||||
- 已完成 `GFramework.SourceGenerators.Tests/Architectures/AutoRegisterModuleGeneratorTests.cs` 的 `MA0051` 收口:
|
||||
将内联测试源码与期望快照抽到类级常量、补齐测试类 XML 文档,并将仅作转发的异步测试改为直接返回 `Task`
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release build 基线已从 `43` 条降到 `40` 条;
|
||||
`AutoRegisterModuleGeneratorTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- 已完成 `GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorSnapshotTests.cs` 的 `MA0051` 收口:
|
||||
将 monster 场景的运行时契约与 schema 输入提取为类级常量,并把生成结果与快照目录解析拆成小 helper,保持
|
||||
生成文件名、快照目录和断言语义不变
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release build 基线已从 `40` 条降到 `39` 条;
|
||||
`SchemaConfigGeneratorSnapshotTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- 已完成当前 PR #273 review follow-up 首轮核对:确认本地仍成立的问题集中在
|
||||
`SchemaConfigGenerator` helper XML 文档、`GeneratorSnapshotTest` 的 `StringComparison.Ordinal` /
|
||||
snapshot 路径空值防御、`AutoRegisterModuleGeneratorTests` 的 XML 文档位置,以及
|
||||
`SchemaConfigGeneratorSnapshotTests` 的 monster 快照覆盖缺口
|
||||
- 已将 monster 快照场景扩展到 `dependentRequired`、`dependentSchemas`、`allOf` 与 object-focused
|
||||
`if/then/else`,以便把新增 schema 约束文档纳入 snapshot 验证
|
||||
- 已完成本轮定向验证:
|
||||
`GFramework.Game.SourceGenerators` Release build 通过;
|
||||
`GFramework.SourceGenerators.Tests` 在 `-m:1 --no-restore` 下 Release build 通过;
|
||||
`SchemaConfigGeneratorSnapshotTests` 与 `AutoRegisterModuleGeneratorTests` 定向测试共 `4` 项全部通过
|
||||
- 当前验证仍受环境/基线约束:
|
||||
`GFramework.SourceGenerators.Tests` Release build 保留既有 `MA0051` warning 基线;
|
||||
NuGet vulnerability audit 在离线环境下产生 `NU1900`
|
||||
- `GFramework.Godot` 的 `Timing.cs` 已同步适配新事件签名,但当前 worktree 的 Godot restore 资产仍受 Windows fallback package folder 干扰,独立 build 需在修复资产后补跑
|
||||
- 后续继续按 warning 类型和数量批处理,而不是回退到按单文件切片推进
|
||||
- 下一轮默认重新抓取 PR #273 最新 review 线程,并确认本轮 snapshot 更新后是否还存在剩余 open thread 或
|
||||
`dotnet-format` 细项
|
||||
- 单次 `boot` 的工作树改动上限控制在约 `100` 个文件以内,避免 recovery context 与 review 面同时失控
|
||||
- 若任务边界互不冲突,允许使用不同模型的 subagent 并行处理不同 warning 类型或不同目录,但必须遵守显式 ownership
|
||||
|
||||
## 当前状态摘要
|
||||
|
||||
- 已完成 `GFramework.Core`、`GFramework.Cqrs`、`GFramework.Godot` 与部分 source generator 的低风险 warning 清理
|
||||
- 已完成多轮 CodeRabbit follow-up 修复,并用定向测试与项目/解决方案构建验证了关键回归风险
|
||||
- 已完成当前 PR #265 review follow-up:修复 `CoroutineScheduler` 的零容量扩容边界,并补上 `Store` dispatch 作用域的异常安全回滚
|
||||
- 已继续完成当前 PR #265 review follow-up:修复 `Event<T>` 与 `Event<T, TK>` 监听器计数的 off-by-one,并补充回归测试
|
||||
- 已增强 `gframework-pr-review` 脚本与 skill 文档,降低超长 JSON 直出导致的 review 信号漏看风险
|
||||
- 已完成 `GFramework.Core` 当前 `MA0046` 批次:将阶段、协程与异步日志事件统一迁移到 `EventHandler<TEventArgs>` 形状,
|
||||
并同步更新 `GFramework.Godot` 订阅点、定向测试与 `docs/zh-CN` 示例
|
||||
- 已完成当前 PR #267 review follow-up:修复 `AsyncLogAppender` 的 `ILogAppender.Flush()` 双重完成通知,并补齐
|
||||
`PhaseChanged` / `CoroutineExceptionEventArgs` XML 文档、`PhaseChanged` 迁移说明和 `ai-plan` 基线注释
|
||||
- 已完成当前 PR #267 failed-test follow-up:修复 `AsyncLogAppender.Flush()` 在队列已被后台线程提前清空时仍可能
|
||||
等待满默认超时并返回 `false` 的竞态,并通过整包 `GFramework.Core.Tests` 重新验证
|
||||
- 已完成当前 `GFramework.Core` `net8.0` 剩余低风险 analyzer warning 批次;warnings-only 基线已降到 `0` 条
|
||||
- 已完成 `GFramework.Core.SourceGenerators` 中 `ContextAwareGenerator` 的剩余 `MA0051` 收口;warnings-only 基线已降到 `0` 条
|
||||
- 已完成 `GFramework.Cqrs.SourceGenerators` 中 `CqrsHandlerRegistryGenerator` 的剩余 `MA0051` 收口;warnings-only 基线已降到 `0` 条
|
||||
- 已完成当前 PR #269 的 review follow-up:收口 `ContextAwareGenerator` 的字段命名冲突 / 锁内读取契约、
|
||||
`CqrsHandlerRegistryGenerator` 的运行时类型 null 防御与超大文件拆分、`SchemaConfigGenerator` 的取消语义,
|
||||
并恢复 `EasyEvents` / `CollectionExtensions` / logging 配置模型的公共 API 兼容形状
|
||||
- 已完成当前 PR #269 的第四轮 review follow-up:确认 5 个 latest-head 未解决线程中仅剩 2 个本地仍成立,
|
||||
已分别在 `CqrsHandlerRegistryGenerator` 与 `SchemaConfigGenerator` 中收口,并补齐定向 generator regression tests
|
||||
- 已完成当前 PR #269 的第五轮 review follow-up:收口 `SchemaConfigGenerator` 的归一化字段名冲突诊断、
|
||||
`CqrsHandlerRegistryGenerator` 的 `dynamic` 类型引用风险,并同步更新 `AGENTS.md` 的模块 build / warning 治理规范
|
||||
- 已完成当前 PR #269 的 failed-test follow-up:将 reference metadata 成员名唯一性回归测试改为合法 schema 路径组合,
|
||||
并重新通过定向 generator test
|
||||
- 已完成当前 PR #269 的 Greptile follow-up:修复 `ContextAwareGenerator` 未覆盖基类成员名冲突的问题,并补齐
|
||||
inherited-collision 快照测试
|
||||
- 已完成当前分支与 `main` 的 `CqrsHandlerRegistryGenerator.cs` 冲突化解:保留当前 partial 结构,并把
|
||||
`main` 侧新增的模型文档合并到 `CqrsHandlerRegistryGenerator.Models.cs`
|
||||
- 已完成 `GFramework.Game.SourceGenerators` 中 `SchemaConfigGenerator` 的剩余 `MA0051` 收口;warnings-only 基线已降到 `0` 条
|
||||
- 已完成 `GFramework.SourceGenerators.Tests` 的首轮低风险 warning 清理;当前项目已清空 `MA0004` / `MA0048`,剩余 warning
|
||||
全部收敛为 `MA0051`
|
||||
- 已完成 `LoggerGeneratorSnapshotTests` 的单文件 `MA0051` 收口;当前 `GFramework.SourceGenerators.Tests` Release build 基线已降到
|
||||
`43` 条,并通过 focused snapshot tests 保持行为不变
|
||||
- 已完成 `AutoRegisterModuleGeneratorTests` 的单文件 `MA0051` 收口;当前 `GFramework.SourceGenerators.Tests` Release build 基线已降到
|
||||
`40` 条,并通过 focused generator tests 保持输出契约不变
|
||||
- 已完成 `SchemaConfigGeneratorSnapshotTests` 的单文件 `MA0051` 收口;当前 `GFramework.SourceGenerators.Tests`
|
||||
Release build 基线已降到 `39` 条,并通过 focused snapshot test 保持生成输出契约不变
|
||||
- 已完成 `RP-035` 启动复核:确认 PR #273 已关闭、历史 open thread 暂无新的本地修复点,且
|
||||
`GFramework.SourceGenerators.Tests` 当前剩余 `MA0051` 已重排为 `CqrsHandlerRegistryGeneratorTests` /
|
||||
`SchemaConfigGeneratorTests` 两个测试写集
|
||||
- 已完成 `RP-036`:清空 `SchemaConfigGeneratorTests.cs` 当前 `MA0051`,并将
|
||||
`GFramework.SourceGenerators.Tests` Release warnings-only 基线进一步降到 `15` 条
|
||||
- 已完成 `RP-037` 的首个 subagent 接收:`CqrsHandlerRegistryGeneratorTests.cs` 的
|
||||
`Generates_Assembly_Level_Cqrs_Handler_Registry()` 已抽出类级 fixture,当前测试项目基线进一步降到 `14` 条
|
||||
- 已完成 `RP-038` 的第二个 subagent 接收:`HiddenNestedHandlerSelfRegistrationSource` 已提取到类级常量,
|
||||
当前测试项目基线进一步降到 `13` 条
|
||||
- 已完成 `RP-039` 的第三个 subagent 接收:`HiddenImplementationDirectInterfaceRegistrationSource`
|
||||
已提取到类级常量,当前测试项目基线进一步降到 `12` 条
|
||||
- 已完成 `RP-040` 的第四个 subagent 接收:`HiddenArrayResponseFallbackSource`
|
||||
已提取到类级常量,当前测试项目基线进一步降到 `11` 条
|
||||
- 已完成 `RP-041` 的第五个 subagent 接收:`HiddenGenericEnvelopeResponseSource`
|
||||
已提取到类级常量,当前测试项目基线进一步降到 `10` 条
|
||||
- `2026-04-24` 本轮已完成 `GFramework.Godot.SourceGenerators.Tests` warning 清理
|
||||
- 当前主线程切片从生成器实现转到对应测试项目,并已把 `GFramework.Godot.SourceGenerators.Tests` 从 `24` 个 warning 降到 `0`
|
||||
- 当前批次按 `origin/main` merge-base 计算的累计分支 diff 预计为 `23` 个文件,仍低于 `$gframework-batch-boot 75` 的主阈值
|
||||
- 当前工作树除未跟踪的 `.codex` 目录外,还存在与本批次无关的既有文档 / 跟踪文件修改;提交当前批次时必须只包含本 topic 相关文件
|
||||
|
||||
## 当前活跃事实
|
||||
|
||||
- 当前主题仍是 active topic,因为剩余结构性 warning 是否继续推进尚未决策
|
||||
- `RP-001` 的详细实现历史、测试记录和 warning 热点清单已归档到主题内 `archive/`
|
||||
- `RP-002` 已在不改公共契约的前提下完成 `CqrsHandlerRegistrar` 结构拆分,并通过定向 build/test 验证
|
||||
- `RP-003` 已在不改生命周期契约的前提下完成 `ArchitectureLifecycle` 初始化主流程拆分,并通过定向 build/test 验证
|
||||
- `RP-004` 已完成当前 PR review follow-up:修复 `TryCreateGeneratedRegistry` 的可空 `out` 契约并清理 trace 文档重复标题
|
||||
- `RP-005` 已在不改公共 API 的前提下完成 `PauseStackManager` 两个 `MA0051` 的结构拆分,并补充销毁通知回归测试
|
||||
- `RP-006` 已在不改公共 API 的前提下完成 `Store` 两个 `MA0051` 的结构拆分,并通过定向 build/test 验证 dispatch、
|
||||
多态 reducer 匹配与历史语义未回归
|
||||
- `RP-007` 已在不改公共 API 的前提下完成 `CoroutineScheduler` 两个 `MA0051` 的结构拆分,并通过定向 build/test 验证
|
||||
调度、取消与完成状态语义未回归
|
||||
- `RP-008` 将后续策略从“单文件 warning 切片”切换为“按类型批处理 + 文件数上限控制”,并允许在非冲突前提下使用
|
||||
不同模型的 subagent 并行处理
|
||||
- `RP-009` 在不改公共 API 的前提下,将同名泛型家族收拢到与类型名一致的单文件中,清空当前 `GFramework.Core`
|
||||
`net8.0` 基线中的 `MA0048`,并通过定向 build/test 验证 `Command`、`Query`、`Event` 路径未回归
|
||||
- `RP-010` 使用 `gframework-pr-review` 复核当前分支 PR #265 后,修复了仍在本地成立的两个 follow-up 风险:
|
||||
`CoroutineScheduler` 的 `initialCapacity: 0` 扩容越界,以及 `Store` 在 dispatch 快照阶段抛异常时可能残留
|
||||
`_isDispatching = true` 的锁死问题
|
||||
- `RP-011` 根据补充复核继续收口 PR #265 的 outside-diff comment,修复 `Event<T>` / `Event<T, TK>` 默认 no-op
|
||||
委托导致的 `GetListenerCount()` off-by-one,并以定向事件测试验证注册、注销和计数语义
|
||||
- `RP-012` 为 `gframework-pr-review` 增加 `--json-output`、`--section`、`--path` 与文本截断能力,并更新 skill 推荐用法,
|
||||
让“先落盘、再定向抽取”成为默认可操作路径
|
||||
- `RP-013` 已完成 `GFramework.Core` 当前 `MA0046` 批次,并以新的事件参数类型替换阶段、协程和异步日志事件的
|
||||
非标准签名;`GFramework.Core` `net8.0` warnings-only 基线由 `15` 降至 `9`
|
||||
- `RP-014` 使用 `gframework-pr-review` 复核当前分支 PR #267 的 latest head review threads、outside-diff comment 与
|
||||
nitpick comment 后,确认 8 条高信号项中仍成立的是 1 个行为 bug 与 7 个文档/测试/跟踪缺口,并按最小改动收口
|
||||
- `RP-015` 使用 `$gframework-pr-review` 复核 PR #267 的 CTRF 失败测试评论后,确认 `AsyncLogAppender` 仍存在
|
||||
“队列已空但 Flush 仍超时失败”的竞态;该问题在本地整包 `GFramework.Core.Tests` 中可复现,现已修复并补上稳定回归测试
|
||||
- `RP-016` 将 `GFramework.Core` 当前剩余 `MA0016` / `MA0002` / `MA0015` / `MA0077` 低风险批次清零,并用
|
||||
warnings-only build 与 focused tests 验证配置反序列化、集合扩展、事件重复注册、`Option<T>` 相等性和协程 tag/group 语义
|
||||
- `RP-017` 复核 `MA0158` 当前仍是跨 target 锁类型迁移问题,因此先收口单点 `ContextAwareGenerator` `MA0051`,
|
||||
并通过 source generator 项目 build 与 `ContextAwareGeneratorSnapshotTests` 验证生成输出未回归
|
||||
- `RP-018` 暂缓跨 target `MA0158`,转入 `GFramework.Cqrs.SourceGenerators` 的单文件结构性 warning;
|
||||
通过拆分 handler 分析、运行时类型引用构造、注册器源码发射与精确反射注册输出阶段,清空该项目当前 `MA0051`
|
||||
- `RP-019` 转入 `GFramework.Game.SourceGenerators/Config/SchemaConfigGenerator.cs`,先完成低风险 `MA0006` 批次;
|
||||
通过 schema 类型比较 helper 与显式 `StringComparison.Ordinal` 清空当前项目的 `MA0006`
|
||||
- `RP-020` 继续拆分 `SchemaConfigGenerator.cs` 的 `MA0051` 热点,将当前项目 warnings-only 基线从 `19` 条降到 `9` 条,
|
||||
并用 focused schema generator tests 验证 50 个用例通过
|
||||
- `RP-032` 已完成 `AutoRegisterModuleGeneratorTests` 的 3 个 `MA0051` 收口:通过提取类级常量承载测试源码与快照,保持
|
||||
生成文件名、断言路径与源生成输出不变;`GFramework.SourceGenerators.Tests` warnings-only 基线由 `43` 降至 `40`
|
||||
- `RP-033` 已完成 `SchemaConfigGeneratorSnapshotTests` 的 `MA0051` 收口:monster schema 运行时契约与 schema 输入已提取为
|
||||
类级常量,生成结果映射与快照目录解析已拆为小 helper;`GFramework.SourceGenerators.Tests` warnings-only 基线由 `40` 降至 `39`
|
||||
- `RP-035` 已完成启动级恢复核对:当前分支对应的 GitHub PR #273 已关闭,因此 remaining open thread 仅作为历史信号参考;
|
||||
下一轮应以本地 `warnings-only` build 的实时热点为主,而不是继续按已过时的 `GeneratorSnapshotTest` /
|
||||
`ContextRegistrationAnalyzerTests` 建议恢复
|
||||
- `RP-036` 已完成 `SchemaConfigGeneratorTests` 的单文件 `MA0051` 收口:共享 runtime fixture、
|
||||
generated-source 收集与 catalog 契约断言均已拆出 helper;当前测试项目剩余 `MA0051` 已全部收敛到
|
||||
`CqrsHandlerRegistryGeneratorTests`
|
||||
- `RP-037` 已验证 subagent 循环开始产生稳定吞吐,但当前一轮只消掉 `1` 个 warning 位点;
|
||||
若继续按“唯一变更文件数接近 `75`”推进,需要接受很多轮单文件、单方法级切片
|
||||
- `RP-038` 继续验证了“单方法 + 主线程记录恢复点”的 subagent 节奏可稳定复用,但按当前速度离
|
||||
“接近 `75` 个唯一变更文件”仍然非常远
|
||||
- `RP-039` 进一步确认:只要 subagent 继续在同一热点文件内逐点消除 warning,唯一变更文件数会基本停留在 `4`
|
||||
左右,不会因为重复修改同一文件快速逼近 `75`
|
||||
- `RP-040` 延续了这一趋势:当前吞吐稳定,但按“唯一变更文件数接近 `75`”作为停止条件并不匹配当前单文件收口节奏
|
||||
- `RP-041` 再次确认当前分支相对 `origin/main` 的唯一变更文件数仍是 `4`;若继续只收口同一文件的 warning,
|
||||
该计数基本不会上涨
|
||||
- `RP-021` 使用 `$gframework-pr-review` 复核当前分支 PR #269 后,修复仍在本地成立的 4 个项:将
|
||||
`CqrsHandlerRegistryGenerator` 拆分为职责清晰的 partial 文件、为 `ContextAwareGenerator` 生成字段增加稳定前缀并补上
|
||||
`SetContextProvider` 的运行时 null 校验、为 `Option<T>` 补齐 `<remarks>`,并新增字段重名场景的生成器快照测试
|
||||
- `RP-022` 继续复核 PR #269 的 latest-head review threads 与 nitpick,确认仍成立的项包括公共 API 兼容回退、
|
||||
`ContextAwareGenerator` 字段名真正去冲突与锁内读取、`SchemaConfigGenerator` 取消传播、`Cqrs` 运行时类型 null 防御;
|
||||
已补齐对应回归测试与 focused build/test 验证
|
||||
- `RP-023` 继续复核 PR #269 剩余 nitpick/outside-diff 项,确认仍成立的项集中在 `SchemaConfigGenerator` 根类型名校验、
|
||||
aggregate registration comparer XML 文档转义、logging / collection 反射测试补强,以及跟踪文档中的
|
||||
`RestoreFallbackFolders=""` 可复制性问题
|
||||
- `RP-024` 使用 `$gframework-pr-review` 继续复核 PR #269 latest-head unresolved threads,确认 `EasyEvents` 异常契约、
|
||||
`SchemaConfigGenerator` 取消传播与 `ContextAwareGenerator` 快照冲突线程均已在本地收口,仅剩 `Cqrs` error type
|
||||
直接引用与根 schema `type` 非字符串防御仍成立;现已补齐实现与回归测试
|
||||
- `RP-025` 继续复核 PR #269 剩余 outside-diff / nitpick 信号后,确认本地仍成立的是 `SchemaConfigGenerator`
|
||||
的归一化字段名冲突与 `Cqrs` 对 `dynamic` 的直接类型引用;已分别补上诊断、运行时类型归一化与回归测试,
|
||||
并把“变更模块必须运行对应 build 且处理 warning”的治理规则写回 `AGENTS.md`
|
||||
- `RP-029` 已完成 `SchemaConfigGenerator` 剩余 `MA0051` 收口:`GFramework.Game.SourceGenerators` 独立 Release
|
||||
warnings-only build 已清零,并通过 `SchemaConfigGenerator` focused generator tests 锁定生成输出未回退
|
||||
- `RP-030` 已完成 `GFramework.SourceGenerators.Tests` 低风险 `MA0004` / `MA0048` 收口:`AnalyzerTestDriver` 文件名已与
|
||||
类型名一致,测试辅助器与 schema snapshot 断言路径已改为直接返回 `Task` 或显式使用 `ConfigureAwait(false)`;
|
||||
当前测试项目 warnings-only 基线从 `61` 条降到 `49` 条,剩余均为 `MA0051`
|
||||
- `RP-031` 已完成 `LoggerGeneratorSnapshotTests` 的 `MA0051` 收口:重复场景源码改为模板化 helper 生成,
|
||||
当前 `GFramework.SourceGenerators.Tests` Release build 基线从 `49` 条降到 `43` 条,并通过 focused test 验证 6 个用例全部通过
|
||||
- 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射
|
||||
- 之前记录的 plain `dotnet build` `0 Warning(s)` 属于增量构建假阴性,不能再作为 warning 检查真值
|
||||
- 本轮已完成 `GFramework.Godot.SourceGenerators` warning 清理:clean `Release` build 从 9 个 warning 降至 0 个 warning
|
||||
- 当前已确认解决的文件包括 `BindNodeSignalGenerator.cs`、`GetNodeGenerator.cs`、`GodotProjectMetadataGenerator.cs`、`Registration/AutoRegisterExportedCollectionsGenerator.cs`
|
||||
- 本轮直接执行仓库根目录 `dotnet clean` 仍在 `ValidateSolutionConfiguration` 阶段失败,输出未提供具体 error 文本
|
||||
- 本轮直接执行仓库根目录 `dotnet build` 成功,并给出 `1184 warning(s)` 的真实输出
|
||||
- `GFramework.Godot.SourceGenerators.Tests` 已通过测试辅助模板抽取与 `ConfigureAwait(false)` 修正,当前 `Debug` / `Release` 构建均为 `0 Warning(s)`
|
||||
- 本轮已验证 `dotnet test GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release --no-build`,结果为 `Passed: 48`
|
||||
|
||||
## 当前风险
|
||||
|
||||
- analyzer 收口回退风险:后续若继续压 `MA0015` / `MA0016`,容易再次把公共 API 收窄成与既有契约不兼容的形状
|
||||
- 缓解措施:优先保留既有公共 API,并将兼容性例外收敛到局部 pragma;继续用反射断言覆盖返回类型、属性类型与异常类型
|
||||
- 测试宿主稳定性风险:部分 Godot 失败路径在当前 .NET 测试宿主下仍不稳定
|
||||
- 缓解措施:继续优先使用稳定的 targeted test、项目构建和相邻 smoke test 组合验证
|
||||
- 多目标框架 warning 解释风险:同一源位置会在多个 target framework 下重复计数
|
||||
- 缓解措施:继续以唯一源位置和 warning 家族为主要决策依据,而不是只看原始 warning 总数
|
||||
- net10 专属 warning 风险:`MA0158` 建议使用 `System.Threading.Lock`,但项目多 target 时需要确认兼容边界
|
||||
- 缓解措施:下一轮先按 target framework 与 API 可用性评估,不直接批量替换共享源码中的 `object` lock
|
||||
- source generator warning 外溢风险:运行 `GFramework.SourceGenerators.Tests` 会构建相邻 generator/test 项目,并在输出中混入
|
||||
测试项目自身的结构性 warning 基线
|
||||
- 缓解措施:继续以被修改项目的独立 warnings-only build 作为主验收,并用 focused generator test 验证行为
|
||||
- source generator test warning 治理风险:`GFramework.SourceGenerators.Tests` 当前仍有 `43` 条既有 `MA0051` warning;
|
||||
一旦继续进入该写集,就必须把测试项目 warning 一并纳入本轮完成条件
|
||||
- 缓解措施:已先清空低风险 `MA0004` / `MA0048`,后续继续保持“单 warning family、单测试域”的节奏推进 `MA0051`
|
||||
- ContextAware 基类命名隐藏风险:若生成器只看当前类型声明成员,派生规则会重新占用基类已声明的
|
||||
`_gFrameworkContextAware*` 字段名,导致生成成员隐藏继承状态并让快照无法锁定后缀分配行为
|
||||
- 缓解措施:本轮已改为遍历完整 base-type 链收集保留名,并用 inherited collision 快照用例锁定该行为
|
||||
- Godot 资产文件环境风险:当前 worktree 的 `GFramework.Godot` restore/build 仍会命中 Windows fallback package folder
|
||||
- 缓解措施:后续若继续触达 Godot 模块,先用 Linux 侧 restore 资产或 Windows-hosted 构建链刷新该项目,再补跑定向 build
|
||||
- 并行实现风险:批量收敛时若 subagent 写入边界不清晰,容易引入命名冲突或重复重构
|
||||
- 缓解措施:只在 warning 类型或目录边界清晰时并行;每个 subagent 必须有独占文件 ownership,主代理负责合并验证
|
||||
- 如果后续继续依赖增量 `dotnet build`,容易再次把 warning 数量误判为 0
|
||||
- 缓解措施:每轮 warning 检查前先执行 `dotnet clean`,再执行目标 `dotnet build`
|
||||
- 仓库根目录 `dotnet clean` 目前仍然无法给出新的 clean 基线
|
||||
- 缓解措施:若下一轮继续做整仓 warning reduction,先定位 `dotnet clean` 的 solution-level 失败原因,或明确继续沿用用户确认的 `1193 warning(s)` clean 基线与本轮 `1184 warning(s)` direct build 观测值
|
||||
- 当前 worktree 已存在与本批次无关的未提交改动
|
||||
- 缓解措施:提交当前批次时只暂存 `GFramework.Godot.SourceGenerators.Tests` 与对应 `ai-plan` 文件,避免混入其他 topic 变更
|
||||
|
||||
## 活跃文档
|
||||
|
||||
- 历史跟踪归档:[analyzer-warning-reduction-history-rp001.md](../archive/todos/analyzer-warning-reduction-history-rp001.md)
|
||||
- 历史 trace 归档:[analyzer-warning-reduction-history-rp001.md](../archive/traces/analyzer-warning-reduction-history-rp001.md)
|
||||
- 当前轮次归档:
|
||||
- [analyzer-warning-reduction-history-rp042-rp048.md](../archive/todos/analyzer-warning-reduction-history-rp042-rp048.md)
|
||||
- 历史跟踪归档:
|
||||
- [analyzer-warning-reduction-history-rp001.md](../archive/todos/analyzer-warning-reduction-history-rp001.md)
|
||||
- [analyzer-warning-reduction-history-rp002-rp041.md](../archive/todos/analyzer-warning-reduction-history-rp002-rp041.md)
|
||||
- 历史 trace 归档:
|
||||
- [analyzer-warning-reduction-history-rp001.md](../archive/traces/analyzer-warning-reduction-history-rp001.md)
|
||||
- [analyzer-warning-reduction-history-rp002-rp041.md](../archive/traces/analyzer-warning-reduction-history-rp002-rp041.md)
|
||||
- [analyzer-warning-reduction-history-rp042-rp048.md](../archive/traces/analyzer-warning-reduction-history-rp042-rp048.md)
|
||||
|
||||
## 验证说明
|
||||
|
||||
- `RP-001` 的详细 warning 清理、回归修复与定向验证命令均已迁入主题内历史归档
|
||||
- `RP-002` 的定向验证结果:
|
||||
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:UseSharedCompilation=false -p:RestoreFallbackFolders=`
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter FullyQualifiedName~CqrsHandlerRegistrarTests -p:RestoreFallbackFolders=`
|
||||
- `RP-003` 的定向验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -p:RestoreFallbackFolders= -nologo -clp:Summary;WarningsOnly`
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter FullyQualifiedName~ArchitectureLifecycleBehaviorTests -p:RestoreFallbackFolders=`
|
||||
- `RP-004` 的定向验证结果:
|
||||
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:UseSharedCompilation=false -p:RestoreFallbackFolders=`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`
|
||||
- `RP-005` 的定向验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`27 Warning(s)`,`0 Error(s)`;`PauseStackManager.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter FullyQualifiedName~PauseStackManagerTests -p:RestoreFallbackFolders=`
|
||||
- 结果:`25 Passed`,`0 Failed`
|
||||
- `RP-006` 的定向验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -p:RestorePackagesPath=<linux-nuget-cache> -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`25 Warning(s)`,`0 Error(s)`;`Store.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter FullyQualifiedName~StoreTests -p:RestoreFallbackFolders="" -p:RestorePackagesPath=<linux-nuget-cache> -nologo`
|
||||
- 结果:`30 Passed`,`0 Failed`
|
||||
- `RP-007` 的定向验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -p:RestorePackagesPath=<linux-nuget-cache> -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`23 Warning(s)`,`0 Error(s)`;`CoroutineScheduler.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter FullyQualifiedName~CoroutineScheduler -p:RestoreFallbackFolders="" -p:RestorePackagesPath=<linux-nuget-cache> -nologo`
|
||||
- 结果:`34 Passed`,`0 Failed`
|
||||
- `RP-008` 的策略基线:
|
||||
- 当前 `GFramework.Core` 剩余 warning 分布:`MA0048=8`、`MA0046=6`、`MA0016=5`、`MA0002=2`、`MA0015=1`、`MA0077=1`
|
||||
- 后续批处理规则:优先按类型推进;若当轮主类型数量不足,可顺手吸收其他低冲突类型,不限定于 `MA0015` 与 `MA0077`
|
||||
- `RP-009` 的定向验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`15 Warning(s)`,`0 Error(s)`;当前 `GFramework.Core` `net8.0` warnings-only 输出中已不再出现 `MA0048`
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~CommandExecutorTests|FullyQualifiedName~AbstractAsyncCommandTests|FullyQualifiedName~QueryExecutorTests|FullyQualifiedName~AbstractAsyncQueryTests|FullyQualifiedName~EventTests" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`83 Passed`,`0 Failed`
|
||||
- `RP-010` 的定向验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`15 Warning(s)`,`0 Error(s)`;新增修复未引入新的 `GFramework.Core` `net8.0` 构建错误
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~CoroutineSchedulerTests.Run_Should_Grow_From_Zero_Initial_Capacity|FullyQualifiedName~StoreTests.Dispatch_Should_Reset_Dispatching_Flag_When_Snapshot_Creation_Throws" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`2 Passed`,`0 Failed`
|
||||
- `RP-011` 的定向验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`15 Warning(s)`,`0 Error(s)`;`Event.cs` 的 listener count 修复未引入新的 `GFramework.Core` `net8.0` 构建错误
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~EventTests.EventT_GetListenerCount_Should_Exclude_Placeholder_Handler|FullyQualifiedName~EventTests.EventTTK_GetListenerCount_Should_Exclude_Placeholder_Handler" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`2 Passed`,`0 Failed`
|
||||
- `RP-012` 的定向验证结果:
|
||||
- `python3 -m py_compile .codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py`
|
||||
- 结果:通过;使用 `PYTHONPYCACHEPREFIX=/tmp/codex-pycache` 规避技能目录只读导致的 `__pycache__` 写入限制
|
||||
- `python3 .codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --help`
|
||||
- 结果:通过;`--json-output`、`--section`、`--path`、`--max-description-length` 已出现在 CLI 帮助中
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`
|
||||
- `RP-013` 的定向验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`9 Warning(s)`,`0 Error(s)`;相对 `RP-009` / `RP-011` 的 warnings-only 基线 `15 Warning(s)` 已降到 `9 Warning(s)`,
|
||||
当前 `GFramework.Core` `net8.0` 输出中已不再出现 `MA0046`
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureLifecycleBehaviorTests|FullyQualifiedName~CoroutineSchedulerTests|FullyQualifiedName~AsyncLogAppenderTests" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`50 Passed`,`0 Failed`
|
||||
- `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release --no-restore -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:失败;当前 worktree 的 Godot restore 资产仍引用 Windows fallback package folder,尚未完成独立项目编译验证
|
||||
- `RP-014` 的定向验证结果:
|
||||
- `dotnet restore GFramework.Core.Tests/GFramework.Core.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过;host Windows `dotnet` 首次验证前补齐了缺失的 `Meziantou.Analyzer 3.0.48` 包
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`9 Warning(s)`,`0 Error(s)`;`AsyncLogAppender` 行为修复与 XML / 文档补充未引入新的 `GFramework.Core` `net8.0` 构建错误
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~CoroutineSchedulerTests.Scheduler_Should_Raise_OnCoroutineException_With_EventArgs|FullyQualifiedName~AsyncLogAppenderTests.Flush_Should_Raise_OnFlushCompleted_With_Sender_And_Result|FullyQualifiedName~AsyncLogAppenderTests.ILogAppender_Flush_Should_Raise_OnFlushCompleted_Only_Once|FullyQualifiedName~ArchitectureLifecycleBehaviorTests.InitializeAsync_Should_Raise_PhaseChanged_With_Sender_And_EventArgs" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`4 Passed`,`0 Failed`
|
||||
- `RP-015` 的验证结果:
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --disable-build-servers --filter "FullyQualifiedName~AsyncLogAppenderTests"`
|
||||
- 结果:`15 Passed`,`0 Failed`
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --disable-build-servers`
|
||||
- 结果:`1607 Passed`,`0 Failed`
|
||||
- `RP-016` 的验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`;当前 `GFramework.Core` `net8.0` analyzer baseline 已清零
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~LoggingConfigurationTests|FullyQualifiedName~ConfigurableLoggerFactoryTests|FullyQualifiedName~CollectionExtensionsTests|FullyQualifiedName~EasyEventsTests|FullyQualifiedName~OptionTests|FullyQualifiedName~CoroutineGroupTests|FullyQualifiedName~CoroutineSchedulerTests" -m:1 -nologo`
|
||||
- 结果:`112 Passed`,`0 Failed`;测试构建仍会显示既有 `net10.0` `MA0158` 与 source generator `MA0051` warning
|
||||
- `RP-017` 的验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net10.0 -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`16 Warning(s)`,`0 Error(s)`;当前 `MA0158` 跨 `GFramework.Core` / `GFramework.Cqrs`,本轮只记录基线不批量改锁
|
||||
- `dotnet build GFramework.Core.SourceGenerators/GFramework.Core.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`;`ContextAwareGenerator.cs` 已不再出现 `MA0051`
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~ContextAwareGeneratorSnapshotTests" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`1 Passed`,`0 Failed`;测试构建仍显示相邻 source generator 和测试项目的既有 analyzer warning
|
||||
- `RP-018` 的验证结果:
|
||||
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`;`CqrsHandlerRegistryGenerator.cs` 当前 `MA0051` 已清零
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter FullyQualifiedName~CqrsHandlerRegistryGeneratorTests -m:1 -p:RestoreFallbackFolders= -nologo`
|
||||
- 结果:`14 Passed`,`0 Failed`
|
||||
- 说明:该 test project 构建仍显示 `GFramework.Game.SourceGenerators` 与测试项目中的既有 analyzer warning;本轮关注的
|
||||
`GFramework.Cqrs.SourceGenerators` 独立 build 已清零
|
||||
- `RP-019` 的验证结果:
|
||||
- `dotnet restore GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -p:RestoreFallbackFolders= -nologo`
|
||||
- 结果:通过;刷新 Linux 侧资产以清除 stale Windows fallback package folder
|
||||
- `dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`19 Warning(s)`,`0 Error(s)`;当前项目输出已不再出现 `MA0006`,剩余均为 `SchemaConfigGenerator.cs` 的
|
||||
`MA0051`
|
||||
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders= -nologo`
|
||||
- 结果:通过;刷新 test project 资产以清除 stale Windows fallback package folder
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter FullyQualifiedName~SchemaConfigGenerator -m:1 -p:RestoreFallbackFolders= -nologo`
|
||||
- 结果:`50 Passed`,`0 Failed`
|
||||
- 说明:测试项目构建仍显示既有 source generator test analyzer warning;不属于本轮写集
|
||||
- `RP-020` 的验证结果:
|
||||
- `dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`9 Warning(s)`,`0 Error(s)`;当前项目剩余 warning 均为 `SchemaConfigGenerator.cs` 的 `MA0051`
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter FullyQualifiedName~SchemaConfigGenerator -m:1 -p:RestoreFallbackFolders= -nologo`
|
||||
- 结果:`50 Passed`,`0 Failed`
|
||||
- 说明:测试项目构建仍显示既有 source generator test analyzer warning;不属于本轮写集
|
||||
- `RP-021` 的验证结果:
|
||||
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`;拆分后最大单文件已降到 `851` 行,满足仓库 800-1000 行上限
|
||||
- `dotnet build GFramework.Core.SourceGenerators/GFramework.Core.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`;`ContextAwareGenerator` 的字段命名与 provider 契约修复未引入新的 generator warning
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~ContextAwareGeneratorSnapshotTests|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests" -m:1 -p:RestoreFallbackFolders= -nologo`
|
||||
- 结果:先并行运行两条 `dotnet test` 时触发共享输出文件锁冲突;改为串行重跑后 `ContextAwareGeneratorSnapshotTests=2 Passed`、
|
||||
`CqrsHandlerRegistryGeneratorTests=14 Passed`
|
||||
- 说明:失败来自测试宿主并行写入同一 build 输出,不是代码回归;串行重跑后快照新增的字段重名场景和 CQRS 快照均通过
|
||||
- `RP-022` 的验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:通过;`EasyEvents`、`CollectionExtensions` 与 logging 配置模型的兼容性回退未引入新的 `net8.0` 构建错误
|
||||
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:通过;`ContainingAssembly` null 防御与发射 helper 精简未引入新的构建错误
|
||||
- `dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:通过;仍保留既有 `9` 条 `SchemaConfigGenerator.cs` `MA0051` warning,非本轮新增
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~SchemaConfigGeneratorTests|FullyQualifiedName~ContextAwareGeneratorSnapshotTests|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests" -m:1 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:`63 Passed`,`0 Failed`
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~EasyEventsTests|FullyQualifiedName~CollectionExtensionsTests|FullyQualifiedName~LoggingConfigurationTests|FullyQualifiedName~ConfigurableLoggerFactoryTests" -m:1 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:`38 Passed`,`0 Failed`
|
||||
- `RP-023` 的验证结果:
|
||||
- `dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release --no-restore -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:通过;仍保留既有 `9` 条 `SchemaConfigGenerator.cs` `MA0051` warning,未新增新的 generator warning
|
||||
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release --no-restore -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:通过;并行构建时 `GFramework.SourceGenerators.Common.dll` 复制阶段出现一次 `MSB3026` 重试,随后成功完成
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~SchemaConfigGeneratorTests|FullyQualifiedName~SchemaConfigGeneratorSnapshotTests|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests" -m:1 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:`63 Passed`,`0 Failed`
|
||||
- 说明:测试项目构建仍会显示既有 `GFramework.SourceGenerators.Tests` analyzer warning;不属于本轮写集
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~CollectionExtensionsTests|FullyQualifiedName~LoggingConfigurationTests" -m:1 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:`27 Passed`,`0 Failed`
|
||||
- `RP-029` 的验证结果:
|
||||
- `dotnet restore GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过;刷新 Linux 侧 restore 资产以移除 Windows fallback package folder 干扰
|
||||
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过;focused test 所属测试项目已同步刷新 Linux 侧 restore 资产
|
||||
- `dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`;`SchemaConfigGenerator.cs` 剩余 `MA0051` 已清零
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter FullyQualifiedName~SchemaConfigGenerator -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`54 Passed`,`0 Failed`
|
||||
- 说明:测试项目构建仍显示既有 `GFramework.SourceGenerators.Tests` `MA0048` / `MA0051` / `MA0004` warning;不属于本轮
|
||||
`GFramework.Game.SourceGenerators` 写集
|
||||
- `RP-030` 的验证结果:
|
||||
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过;刷新 Linux 侧 restore 资产以移除 Windows fallback package folder 干扰
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`49 Warning(s)`,`0 Error(s)`;当前项目已不再出现 `MA0004` / `MA0048`,剩余 warning 全部为 `MA0051`
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~GeneratorSnapshotTestSecurityTests|FullyQualifiedName~SchemaConfigGeneratorSnapshotTests|FullyQualifiedName~SchemaConfigGeneratorEnumTests" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`6 Passed`,`0 Failed`
|
||||
- `RP-031` 的验证结果:
|
||||
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过;刷新 Linux 侧 restore 资产以支持后续串行 build/test 验证
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:Summary`
|
||||
- 结果:`43 Warning(s)`,`0 Error(s)`;`LoggerGeneratorSnapshotTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --disable-build-servers --filter FullyQualifiedName~LoggerGeneratorSnapshotTests -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`6 Passed`,`0 Failed`
|
||||
- `RP-033` 的验证结果:
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`39 Warning(s)`,`0 Error(s)`;`SchemaConfigGeneratorSnapshotTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --disable-build-servers --filter FullyQualifiedName~SchemaConfigGeneratorSnapshotTests -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`1 Passed`,`0 Failed`
|
||||
- `RP-035` 的验证结果:
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/current-pr-review.json`
|
||||
- 结果:成功定位当前分支关联的 `PR #273`;状态为 `CLOSED`,latest-head review threads 仍显示 `2` 条 open thread,
|
||||
test report 均为通过,MegaLinter 仅保留 docstring coverage / `dotnet-format` 历史信号
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`22 Warning(s)`,`0 Error(s)`;剩余 `MA0051` 全部集中在 `CqrsHandlerRegistryGeneratorTests.cs` 与
|
||||
`SchemaConfigGeneratorTests.cs`
|
||||
- `RP-036` 的验证结果:
|
||||
- `dotnet restore GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:通过;刷新测试依赖输出,规避 `--no-build` 场景下的缺包噪音
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`15 Warning(s)`,`0 Error(s)`;`SchemaConfigGeneratorTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --disable-build-servers --filter FullyQualifiedName~SchemaConfigGeneratorTests -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`50 Passed`,`0 Failed`
|
||||
- `RP-037` 的验证结果:
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`14 Warning(s)`,`0 Error(s)`;`CqrsHandlerRegistryGeneratorTests.cs` 的行号 `337` 已不再出现在 `MA0051` 列表中
|
||||
- `RP-038` 的验证结果:
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`13 Warning(s)`,`0 Error(s)`;`CqrsHandlerRegistryGeneratorTests.cs` 的行号 `454` 已不再出现在 `MA0051` 列表中
|
||||
- `RP-039` 的验证结果:
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`12 Warning(s)`,`0 Error(s)`;`CqrsHandlerRegistryGeneratorTests.cs` 的行号 `536` 已不再出现在 `MA0051` 列表中
|
||||
- `RP-040` 的验证结果:
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`11 Warning(s)`,`0 Error(s)`;`CqrsHandlerRegistryGeneratorTests.cs` 的行号 `607` 已不再出现在 `MA0051` 列表中
|
||||
- `RP-041` 的验证结果:
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`10 Warning(s)`,`0 Error(s)`;`CqrsHandlerRegistryGeneratorTests.cs` 的行号 `680` 已不再出现在 `MA0051` 列表中
|
||||
- active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史
|
||||
- `dotnet clean`
|
||||
- 结果:失败;停在 solution `ValidateSolutionConfiguration`,`0 Warning(s)`、`0 Error(s)`,未输出更具体的 error 文本
|
||||
- `dotnet build`
|
||||
- 结果:成功;`1184 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj`
|
||||
- 初始结果:成功;`24 Warning(s)`、`0 Error(s)`
|
||||
- 本轮收尾结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release`
|
||||
- 结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet test GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release --no-build`
|
||||
- 结果:成功;`Passed: 48`、`Failed: 0`
|
||||
|
||||
## 下一步
|
||||
## 下一步建议
|
||||
|
||||
1. 若要继续该主题,先读 active tracking,再按需展开历史归档中的 warning 热点与验证记录
|
||||
2. 下一轮优先继续 `GFramework.SourceGenerators.Tests` 的 `MA0051` 收口,并直接进入唯一剩余热点
|
||||
`CqrsHandlerRegistryGeneratorTests.cs`;但若用户真正关心“唯一变更文件数接近 `75`”,下一轮应改为选择新的文件写集,
|
||||
而不是继续停留在当前同一文件
|
||||
3. 若改回推进 `MA0158`,先设计 `net8.0` / `net9.0` / `net10.0` 多 target 条件编译方案,不直接批量替换共享源码中的
|
||||
`object` lock
|
||||
4. 若后续继续改动 `GFramework.Godot`,先修复该项目的 Linux 侧 restore 资产,再补跑独立 build
|
||||
5. 若本主题确认暂缓,可保持当前归档状态,不需要再恢复 `local-plan/`
|
||||
1. 提交当前 `GFramework.Godot.SourceGenerators.Tests` 清理批次,并确认提交只包含本 topic 相关文件
|
||||
2. 如果继续 warning reduction,优先重新评估仓库根目录 `dotnet clean` 的 solution-level 失败,再决定是继续从整仓 `dotnet build` 输出挑热点,还是先修复 clean 基线采集问题
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -12,9 +12,11 @@
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`DOCUMENTATION-FULL-COVERAGE-GOV-RP-019`
|
||||
- 恢复点编号:`DOCUMENTATION-FULL-COVERAGE-GOV-RP-023`
|
||||
- 当前阶段:`Phase 5 - Governance Maintenance`
|
||||
- 当前焦点:
|
||||
- 保持 landing page / API 导航页中的仓库 README 入口可点击,避免读者在 docs 站点里遇到裸路径文本
|
||||
- 继续按 `origin/main` 分支 diff 阈值做小批量文档治理,优先处理低风险导航 / 渲染热点
|
||||
- 保持 `Game` persistence docs surface 与当前 `README`、源码、`PersistenceTests` 使用同一套 owner / adoption path 叙述
|
||||
- 保持 `GFramework.Godot.SourceGenerators/README.md` 与 `docs/zh-CN/tutorials/godot-integration.md` 在生命周期接法上的一致性
|
||||
- 保持 active tracking / trace 只承载当前恢复入口,把阶段细节留在 `archive/`
|
||||
@ -36,6 +38,26 @@
|
||||
`SettingsModel<ISettingsDataRepository>`。
|
||||
- 结合当前 PR 已改动的 `docs/zh-CN/godot/storage.md` 做同类巡检后,确认 `SaveRepository<TSaveData>`
|
||||
也会在 VitePress code span 中按字面量渲染;两处现已在本地统一改为真实泛型写法。
|
||||
- `2026-04-23` 以 `origin/main`(`aa879d2`,`2026-04-23T17:51:41+08:00`)为批处理基线,对
|
||||
`README.md`、`GFramework.*` 与 `docs/zh-CN/**` 执行同类模式巡检,确认剩余热点仅位于
|
||||
`docs/zh-CN/core/functional.md` 与 `docs/zh-CN/tutorials/functional-programming.md` 共 8 处。
|
||||
- 上述 8 处 inline code 中的 `Option<T>`、`Result<T>`、`Nullable<T>` 已统一改为真实
|
||||
泛型写法,避免在 VitePress 中显示字面量 HTML entity。
|
||||
- `2026-04-23` 根据本轮使用反馈,已为 `.agents/skills/gframework-batch-boot/SKILL.md` 与
|
||||
`.agents/skills/README.md` 补充数字速记阈值语义:
|
||||
- `$gframework-batch-boot 75` 默认表示“当前分支全部提交相对远程 `origin/main` 接近 75 个分支 diff 文件时停止”
|
||||
- `$gframework-batch-boot 75 2000` 默认表示“当前分支全部提交相对远程 `origin/main` 接近 75 个文件或 2000 行变更时停止”
|
||||
- `75 | 2000` 仅作为可理解的 OR 写法保留,不再作为推荐写法,以避免与 shell pipe 混淆
|
||||
- `2026-04-23` 以 `origin/main`(`aa879d2`,`2026-04-23T17:51:41+08:00`)为批处理基线,对
|
||||
`docs/zh-CN/getting-started/index.md`、`core/index.md`、`game/index.md`、`source-generators/index.md`、
|
||||
`api-reference/index.md`、`abstractions/core-abstractions.md`、`abstractions/game-abstractions.md`
|
||||
做导航可达性修复,把仓库 README / 根 README 裸路径统一改为指向 GitHub `main` 分支的可点击链接。
|
||||
- 该批次不改变文档语义,只收口 docs 站点中的入口可达性;适合继续作为小步快跑的低风险治理模式。
|
||||
- `2026-04-23` 在同一基线下继续收口第二批专题页导航热点,已将 `core/cqrs.md`、`ecs/arch.md`、
|
||||
`abstractions/ecs-arch-abstractions.md`、`game/scene.md`、`game/ui.md` 和 6 个
|
||||
`source-generators/*.md` 专题页里的 README 裸路径统一改为 GitHub `main` blob 外链。
|
||||
- 截至提交 `8a11720`(`2026-04-23T21:01:28+08:00`),当前分支相对 `origin/main`(`aa879d2`)的累计 diff
|
||||
为 `24` 个文件、`264` 行,仍低于 `$gframework-batch-boot 75` 的停止阈值;但剩余命中已主要是正文语义性提及,不再适合作为同类批处理。
|
||||
- 当前剩余的托管侧信号是 GitHub `Title check` 对 PR 标题过泛的 inconclusive 提示;这属于 PR 元数据,不是本地
|
||||
文件缺陷。
|
||||
|
||||
@ -62,14 +84,24 @@
|
||||
`docs/zh-CN/godot/setting.md:75` 的 inline code HTML entity 渲染问题。
|
||||
- `2026-04-23` `rg -n '`[^`]*<[^`]*`|`[^`]*>[^`]*`' GFramework.Godot.SourceGenerators/README.md GFramework.Godot/README.md README.md docs/zh-CN/api-reference/index.md docs/zh-CN/game/data.md docs/zh-CN/game/serialization.md docs/zh-CN/game/setting.md docs/zh-CN/game/storage.md docs/zh-CN/godot/setting.md docs/zh-CN/godot/storage.md docs/zh-CN/source-generators/index.md`
|
||||
- 结果:命中 `docs/zh-CN/godot/setting.md:75` 与 `docs/zh-CN/godot/storage.md:102` 两处同类写法,均已修正。
|
||||
- `2026-04-23` `rg -n '`[^`]*<[^`]*`|`[^`]*>[^`]*`' README.md GFramework.* docs/zh-CN -g '*.md'`
|
||||
- 结果:命中 `docs/zh-CN/core/functional.md` 与 `docs/zh-CN/tutorials/functional-programming.md` 共 8 处,已全部修正。
|
||||
- `2026-04-23` `sed -n '1,260p' .agents/skills/gframework-batch-boot/SKILL.md` 与 `sed -n '1,220p' .agents/skills/README.md`
|
||||
- 结果:确认原文仅描述自然语言 stop condition,没有定义数字速记或多阈值 OR 语义;现已补齐。
|
||||
- `2026-04-23` `rg -n '`GFramework\\.[^`]+/README\\.md`|`docs/zh-CN/[^`]+\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
|
||||
- 结果:确认 landing / API 导航页仍有一批裸路径仓库入口;本轮已先修复 `getting-started`、`core`、`game`、
|
||||
`source-generators`、`api-reference` 与两个 abstractions 页面。
|
||||
- `2026-04-23` `rg -n '`GFramework\\.[^`]+/README\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
|
||||
- 结果:定位第二批专题页导航热点,已修复 `core/cqrs.md`、`ecs/arch.md`、`abstractions/ecs-arch-abstractions.md`、
|
||||
`game/scene.md`、`game/ui.md` 以及 6 个 `source-generators/*.md` 页面。
|
||||
- `2026-04-23` `bun run build`(工作目录:`docs/`)
|
||||
- 结果:通过;仅保留既有 VitePress 大 chunk warning,无构建失败。
|
||||
- 结果:通过;仓库 README 外链改为 GitHub `main` blob 后,不再触发 VitePress dead link;仅保留既有大 chunk warning。
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 提交并推送本地对 `docs/zh-CN/godot/setting.md` 与 `docs/zh-CN/godot/storage.md` 的 Markdown 泛型写法修正,
|
||||
然后重新抓取 PR `#272` 确认 Greptile open thread 是否已在新 head commit 上消失。
|
||||
2. 如果 PR `#272` 的 `Title check` 仍需要消除,到 GitHub 上把标题改成更具体的文档治理描述。
|
||||
1. 若继续执行文档治理批处理,优先改做标题锚点、站内链接和少量非导航型裸路径引用的逐页复核,而不是继续按统一模板机械替换。
|
||||
2. 若后续继续扩展批处理 skill,可考虑再补充显式单位写法,例如 `75 files 2000 lines`,但当前默认速记已足够覆盖
|
||||
常见分支阈值场景。
|
||||
3. 若后续分支继续调整 `Game` persistence runtime、README 或公共 API,优先复核 `docs/zh-CN/game/data.md`、
|
||||
`storage.md`、`serialization.md`、`setting.md` 与 landing page 是否仍保持同一套职责边界。
|
||||
4. 若后续分支继续调整 `Godot` generator 接法,优先复核 `GFramework.Godot.SourceGenerators/README.md`、
|
||||
|
||||
@ -2,49 +2,48 @@
|
||||
|
||||
## 2026-04-23
|
||||
|
||||
### 当前恢复点:RP-019
|
||||
### 当前恢复点:RP-023
|
||||
|
||||
- 使用 `$gframework-pr-review` 重新复核当前分支 PR `#272`。
|
||||
- GitHub latest-head review 当前暴露 1 条新的 Greptile open thread:
|
||||
`docs/zh-CN/godot/setting.md:75` 在 inline code 中写成
|
||||
`SettingsModel<ISettingsDataRepository>`。
|
||||
- 本地核对当前文档渲染语义后,确认 CommonMark / VitePress 不会在 code span 内解码 HTML entity,
|
||||
该评论成立。
|
||||
- 对当前 PR 已变更的 Godot 文档做同类扫描后,又在 `docs/zh-CN/godot/storage.md:102` 发现
|
||||
`SaveRepository<TSaveData>` 的同型问题。
|
||||
- 按当前使用反馈继续执行 `documentation-full-coverage-governance` 下的 skill 文档治理。
|
||||
- 本轮目标定义为“继续沿用上一批的 GitHub 外链策略,收口专题页里的裸路径 README 入口”。
|
||||
- 本轮执行的修复:
|
||||
- 将 `docs/zh-CN/godot/setting.md` 的 `SettingsModel<ISettingsDataRepository>` 改为
|
||||
`SettingsModel<ISettingsDataRepository>`
|
||||
- 将 `docs/zh-CN/godot/storage.md` 的 `SaveRepository<TSaveData>` 改为
|
||||
`SaveRepository<TSaveData>`
|
||||
- 同步更新 active tracking / trace,记录该 PR review follow-up 与新的恢复点
|
||||
- 将 `docs/zh-CN/core/cqrs.md` 与 `ecs/arch.md` 的仓库 README 入口改为 GitHub `main` blob 外链
|
||||
- 将 `docs/zh-CN/abstractions/ecs-arch-abstractions.md`、`game/scene.md`、`game/ui.md` 的回跳 README 入口改为可点击链接
|
||||
- 将 `docs/zh-CN/source-generators/priority-generator.md`、`context-aware-generator.md`、
|
||||
`bind-node-signal-generator.md`、`godot-project-generator.md`、`get-node-generator.md`、
|
||||
`auto-register-exported-collections-generator.md` 的推荐阅读 README 入口改为可点击链接
|
||||
- 同步更新 active tracking / trace,记录第二批导航治理与新的恢复点
|
||||
|
||||
### 当前决策(RP-019)
|
||||
### 当前决策(RP-023)
|
||||
|
||||
- PR review 结果以 GitHub latest-head open threads 为准;即便 active tracking 曾记录“无 open thread”,也必须按新抓取结果回写。
|
||||
- 对 Markdown inline code 中的 C# 泛型示例,必须直接写真实的 `<T>` 语法,不能在反引号内部再写
|
||||
`<` / `>`,否则 VitePress 会把 entity 当作字面量展示。
|
||||
- 当 latest-head review 命中某个文档表述问题时,应顺手扫描同一批 PR 已改动文档中的同类模式,避免只消掉单条 thread 却把相同渲染缺陷留在相邻页面。
|
||||
- 当前本地修复完成后,下一次 GitHub 侧复核需要基于新提交/新 head commit,而不是旧的 PR review 快照。
|
||||
- 继续使用 `origin/main` 作为 `$gframework-batch-boot 75` 的固定基线,并以“分支累计 diff 文件数”作为主状态指标。
|
||||
- 对文档治理类批次,优先选择“导航可达性 / 渲染一致性”这类不改变产品语义的低风险切片。
|
||||
- 在 docs 页面里出现仓库内 README 路径时,优先使用可点击的相对链接,而不是裸路径代码片段。
|
||||
- 当 docs 页需要跳转到 `docs/` 外部的 README 时,使用 GitHub `main` 分支 blob 外链,而不是跨出 `docs/` 根目录的相对路径。
|
||||
- 第二批继续沿用同一外链策略,避免在同一 docs surface 中混用“裸路径 / 相对死链 / GitHub 外链”三套入口风格。
|
||||
|
||||
### 当前验证(RP-019)
|
||||
### 当前验证(RP-023)
|
||||
|
||||
- PR review 抓取:
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output /tmp/current-pr-review.json`
|
||||
- 结果:通过;PR `#272` 处于 `OPEN`,latest head commit 存在 1 条 Greptile open thread,定位到
|
||||
`docs/zh-CN/godot/setting.md:75` 的 inline code HTML entity 渲染问题。
|
||||
- 同类模式巡检:
|
||||
- `rg -n '`[^`]*<[^`]*`|`[^`]*>[^`]*`' GFramework.Godot.SourceGenerators/README.md GFramework.Godot/README.md README.md docs/zh-CN/api-reference/index.md docs/zh-CN/game/data.md docs/zh-CN/game/serialization.md docs/zh-CN/game/setting.md docs/zh-CN/game/storage.md docs/zh-CN/godot/setting.md docs/zh-CN/godot/storage.md docs/zh-CN/source-generators/index.md`
|
||||
- 结果:命中 `docs/zh-CN/godot/setting.md:75` 与 `docs/zh-CN/godot/storage.md:102` 两处同类写法,均已修正。
|
||||
- 导航热点巡检:
|
||||
- `rg -n '`GFramework\\.[^`]+/README\\.md`|`docs/zh-CN/[^`]+\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
|
||||
- 结果:命中 landing / API 导航页中的裸路径仓库入口,已按本轮批次收口 7 个页面。
|
||||
- 第二批专题页巡检:
|
||||
- `rg -n '`GFramework\\.[^`]+/README\\.md`|仓库根 `README\\.md`' docs/zh-CN -g '*.md'`
|
||||
- 结果:命中 `core/cqrs.md`、`ecs/arch.md`、`abstractions/ecs-arch-abstractions.md`、`game/scene.md`、
|
||||
`game/ui.md` 与 6 个 `source-generators/*.md` 专题页,均已修复。
|
||||
- 构建校验:
|
||||
- `bun run build`(工作目录:`docs/`)
|
||||
- 结果:通过;仅保留既有 VitePress 大 chunk warning,无构建失败。
|
||||
- 结果:通过;将仓库 README 跳转改为 GitHub `main` blob 外链后,不再触发 VitePress dead link;仅保留既有大 chunk warning。
|
||||
- 当前阈值状态:
|
||||
- `git diff --name-only origin/main...HEAD | wc -l` => `24`
|
||||
- `git diff --numstat origin/main...HEAD` 汇总 => `264` changed lines
|
||||
- 结论:尚未接近 `75` 文件阈值,但剩余命中主要是正文语义性提及,当前批次在低风险模板化导航治理上可先收口。
|
||||
|
||||
### 归档摘要(RP-018)
|
||||
### 归档摘要(RP-022)
|
||||
|
||||
- 使用 `$gframework-pr-review` 重新复核当前分支 PR `#272`。
|
||||
- latest-head review 命中 `GFramework.Godot.SourceGenerators/README.md:135` 的错误命名空间引用,并已在本地修正。
|
||||
- README 校验与 `docs/` 站点构建通过,待新提交推送后回 GitHub 侧确认 open thread 消失。
|
||||
- 为 `.agents/skills/gframework-batch-boot/SKILL.md` 与 `.agents/skills/README.md` 补齐数字速记 stop condition 语义。
|
||||
- 明确 `$gframework-batch-boot 75` / `75 2000` 默认绑定 `origin/main` 累计 diff 口径。
|
||||
- 完成第一批 landing / API 导航页 README 外链治理,并通过 `docs/` 站点构建。
|
||||
|
||||
### 归档指针
|
||||
|
||||
@ -55,4 +54,4 @@
|
||||
### 下一步
|
||||
|
||||
1. 提交并推送本地修正后,再次抓取 PR `#272`,确认 Greptile open thread 是否已在新 head commit 上消失。
|
||||
2. 如果 PR `#272` 的 `Title check` 仍需要处理,到 GitHub 上把标题改成更具体的文档治理描述。
|
||||
2. 若继续执行文档治理批处理,优先排查剩余的非导航型裸路径引用、标题锚点与站内链接热点,而不是扩成跨模块大波次。
|
||||
|
||||
@ -99,6 +99,6 @@ public sealed class DiagnosticsFeature
|
||||
1. 先读本页,确认你是否真的只需要契约层
|
||||
2. 再看 [`../core/index.md`](../core/index.md) 了解默认运行时怎么组织这些契约
|
||||
3. 回到模块 README:
|
||||
- `GFramework.Core.Abstractions/README.md`
|
||||
- `GFramework.Core/README.md`
|
||||
- [`GFramework.Core.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core.Abstractions/README.md)
|
||||
- [`GFramework.Core/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core/README.md)
|
||||
4. 需要统一导航时,再看 [`../api-reference/index.md`](../api-reference/index.md)
|
||||
|
||||
@ -90,5 +90,5 @@ var options = new ArchOptions
|
||||
1. 先读本页,确认你是否真的只需要契约层
|
||||
2. 如果需要默认实现,再看 [`../ecs/arch.md`](../ecs/arch.md)
|
||||
3. 回到对应模块 README:
|
||||
- `GFramework.Ecs.Arch.Abstractions/README.md`
|
||||
- `GFramework.Ecs.Arch/README.md`
|
||||
- [`GFramework.Ecs.Arch.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Ecs.Arch.Abstractions/README.md)
|
||||
- [`GFramework.Ecs.Arch/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Ecs.Arch/README.md)
|
||||
|
||||
@ -118,5 +118,5 @@ public sealed class ContinueGameCommandHandler
|
||||
- [`../game/scene.md`](../game/scene.md)
|
||||
- [`../game/ui.md`](../game/ui.md)
|
||||
4. 需要仓库侧入口时,回到:
|
||||
- `GFramework.Game.Abstractions/README.md`
|
||||
- `GFramework.Game/README.md`
|
||||
- [`GFramework.Game.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game.Abstractions/README.md)
|
||||
- [`GFramework.Game/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game/README.md)
|
||||
|
||||
@ -21,7 +21,7 @@ description: GFramework 的 API 阅读入口,按模块映射 README、专题
|
||||
先读模块 README,再读对应 landing page:
|
||||
|
||||
- 入门入口:[`../getting-started/index.md`](../getting-started/index.md)
|
||||
- 根模块地图:仓库根 `README.md`
|
||||
- 根模块地图:仓库根 [`README.md`](https://github.com/GeWuYou/GFramework/blob/main/README.md)
|
||||
|
||||
### 想确认“这个功能属于哪个模块”
|
||||
|
||||
@ -29,11 +29,11 @@ description: GFramework 的 API 阅读入口,按模块映射 README、专题
|
||||
|
||||
| 模块族 | 模块 README | 站内入口 | XML 文档关注点 |
|
||||
| --- | --- | --- | --- |
|
||||
| `Core` / `Core.Abstractions` | `GFramework.Core/README.md`、`GFramework.Core.Abstractions/README.md` | [`../core/index.md`](../core/index.md)、[`../abstractions/core-abstractions.md`](../abstractions/core-abstractions.md) | 架构入口、生命周期、命令 / 查询 / 事件 / 状态 / 资源 / 日志 / 配置 / 并发契约 |
|
||||
| `Cqrs` / `Cqrs.Abstractions` / `Cqrs.SourceGenerators` | `GFramework.Cqrs/README.md`、`GFramework.Cqrs.Abstractions/README.md`、`GFramework.Cqrs.SourceGenerators/README.md` | [`../core/cqrs.md`](../core/cqrs.md)、[`../source-generators/cqrs-handler-registry-generator.md`](../source-generators/cqrs-handler-registry-generator.md) | request / notification / handler / pipeline / registry / fallback contract |
|
||||
| `Game` / `Game.Abstractions` / `Game.SourceGenerators` | `GFramework.Game/README.md`、`GFramework.Game.Abstractions/README.md`、`GFramework.Game.SourceGenerators/README.md` | [`../game/index.md`](../game/index.md)、[`../abstractions/game-abstractions.md`](../abstractions/game-abstractions.md) | 配置、数据、设置、场景、UI、存储、序列化契约 |
|
||||
| `Godot` / `Godot.SourceGenerators` | `GFramework.Godot/README.md`、`GFramework.Godot.SourceGenerators/README.md` | [`../godot/index.md`](../godot/index.md)、[`../source-generators/godot-project-generator.md`](../source-generators/godot-project-generator.md)、[`../source-generators/get-node-generator.md`](../source-generators/get-node-generator.md)、[`../source-generators/bind-node-signal-generator.md`](../source-generators/bind-node-signal-generator.md) | 节点扩展、场景 / UI 适配、配置 / 存储 / 设置接线、Godot 生成器入口 |
|
||||
| `Ecs.Arch` / `Ecs.Arch.Abstractions` | `GFramework.Ecs.Arch/README.md`、`GFramework.Ecs.Arch.Abstractions/README.md` | [`../ecs/index.md`](../ecs/index.md)、[`../ecs/arch.md`](../ecs/arch.md)、[`../abstractions/ecs-arch-abstractions.md`](../abstractions/ecs-arch-abstractions.md) | ECS 模块契约、系统适配、配置对象和运行时装配边界 |
|
||||
| `Core` / `Core.Abstractions` | [`GFramework.Core/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core/README.md)、[`GFramework.Core.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core.Abstractions/README.md) | [`../core/index.md`](../core/index.md)、[`../abstractions/core-abstractions.md`](../abstractions/core-abstractions.md) | 架构入口、生命周期、命令 / 查询 / 事件 / 状态 / 资源 / 日志 / 配置 / 并发契约 |
|
||||
| `Cqrs` / `Cqrs.Abstractions` / `Cqrs.SourceGenerators` | [`GFramework.Cqrs/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Cqrs/README.md)、[`GFramework.Cqrs.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Cqrs.Abstractions/README.md)、[`GFramework.Cqrs.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Cqrs.SourceGenerators/README.md) | [`../core/cqrs.md`](../core/cqrs.md)、[`../source-generators/cqrs-handler-registry-generator.md`](../source-generators/cqrs-handler-registry-generator.md) | request / notification / handler / pipeline / registry / fallback contract |
|
||||
| `Game` / `Game.Abstractions` / `Game.SourceGenerators` | [`GFramework.Game/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game/README.md)、[`GFramework.Game.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game.Abstractions/README.md)、[`GFramework.Game.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game.SourceGenerators/README.md) | [`../game/index.md`](../game/index.md)、[`../abstractions/game-abstractions.md`](../abstractions/game-abstractions.md) | 配置、数据、设置、场景、UI、存储、序列化契约 |
|
||||
| `Godot` / `Godot.SourceGenerators` | [`GFramework.Godot/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Godot/README.md)、[`GFramework.Godot.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Godot.SourceGenerators/README.md) | [`../godot/index.md`](../godot/index.md)、[`../source-generators/godot-project-generator.md`](../source-generators/godot-project-generator.md)、[`../source-generators/get-node-generator.md`](../source-generators/get-node-generator.md)、[`../source-generators/bind-node-signal-generator.md`](../source-generators/bind-node-signal-generator.md) | 节点扩展、场景 / UI 适配、配置 / 存储 / 设置接线、Godot 生成器入口 |
|
||||
| `Ecs.Arch` / `Ecs.Arch.Abstractions` | [`GFramework.Ecs.Arch/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Ecs.Arch/README.md)、[`GFramework.Ecs.Arch.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Ecs.Arch.Abstractions/README.md) | [`../ecs/index.md`](../ecs/index.md)、[`../ecs/arch.md`](../ecs/arch.md)、[`../abstractions/ecs-arch-abstractions.md`](../abstractions/ecs-arch-abstractions.md) | ECS 模块契约、系统适配、配置对象和运行时装配边界 |
|
||||
|
||||
## 先看 XML,还是先看教程
|
||||
|
||||
|
||||
@ -186,4 +186,4 @@ RegisterCqrsPipelineBehavior<LoggingBehavior<,>>();
|
||||
- 架构入口:[architecture](./architecture.md)
|
||||
- 上下文入口:[context](./context.md)
|
||||
- 生成器专题:[../source-generators/cqrs-handler-registry-generator.md](../source-generators/cqrs-handler-registry-generator.md)
|
||||
- 模块 README:`GFramework.Cqrs/README.md`
|
||||
- 模块 README:[`GFramework.Cqrs/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Cqrs/README.md)
|
||||
|
||||
@ -17,7 +17,7 @@ GFramework.Core 提供了一套完整的函数式编程工具,帮助开发者
|
||||
|
||||
### Option 类型
|
||||
|
||||
`Option<T>` 表示可能存在或不存在的值,用于替代 null 引用。它有两种状态:
|
||||
`Option<T>` 表示可能存在或不存在的值,用于替代 null 引用。它有两种状态:
|
||||
|
||||
- **Some**:包含一个值
|
||||
- **None**:不包含值
|
||||
@ -26,7 +26,7 @@ GFramework.Core 提供了一套完整的函数式编程工具,帮助开发者
|
||||
|
||||
### Result 类型
|
||||
|
||||
`Result<T>` 表示操作的结果,可能是成功值或失败异常。它有三种状态:
|
||||
`Result<T>` 表示操作的结果,可能是成功值或失败异常。它有三种状态:
|
||||
|
||||
- **Success**:操作成功,包含返回值
|
||||
- **Faulted**:操作失败,包含异常信息
|
||||
@ -586,14 +586,14 @@ public async Task<Result<Response>> ProcessRequestAsync(Request request)
|
||||
|
||||
### Option vs Nullable
|
||||
|
||||
**Q: Option 和 Nullable<T> 有什么区别?**
|
||||
**Q: Option 和 `Nullable<T>` 有什么区别?**
|
||||
|
||||
A:
|
||||
|
||||
- `Nullable<T>` 只能用于值类型,`Option<T>` 可用于任何类型
|
||||
- `Option<T>` 提供丰富的函数式操作(Map、Bind、Filter 等)
|
||||
- `Option<T>` 强制显式处理"无值"情况,更安全
|
||||
- `Option<T>` 可以与 Result 等其他函数式类型组合
|
||||
- `Nullable<T>` 只能用于值类型,`Option<T>` 可用于任何类型
|
||||
- `Option<T>` 提供丰富的函数式操作(Map、Bind、Filter 等)
|
||||
- `Option<T>` 强制显式处理"无值"情况,更安全
|
||||
- `Option<T>` 可以与 Result 等其他函数式类型组合
|
||||
|
||||
### Result vs Exception
|
||||
|
||||
|
||||
@ -71,7 +71,7 @@ dotnet add package GeWuYou.GFramework.Core.Abstractions
|
||||
|
||||
如果你已经知道模块归属,但想确认公开类型的契约边界,建议按下面顺序阅读:
|
||||
|
||||
1. 先看模块 README `GFramework.Core/README.md`,确认包关系和目录边界
|
||||
1. 先看模块 README [`GFramework.Core/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core/README.md),确认包关系和目录边界
|
||||
2. 再看本栏目对应专题页,确认采用顺序、生命周期与推荐接线方式
|
||||
3. 最后回到源码中的 XML 文档,重点核对这些类型族:
|
||||
- `Architecture` / `IArchitectureContext`
|
||||
@ -149,7 +149,7 @@ public sealed class CounterArchitecture : Architecture
|
||||
|
||||
## 对应模块入口
|
||||
|
||||
- `GFramework.Core/README.md`
|
||||
- `GFramework.Core.Abstractions/README.md`
|
||||
- `docs/zh-CN/api-reference/index.md`
|
||||
- 仓库根 `README.md`
|
||||
- [`GFramework.Core/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core/README.md)
|
||||
- [`GFramework.Core.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core.Abstractions/README.md)
|
||||
- [`docs/zh-CN/api-reference/index.md`](../api-reference/index.md)
|
||||
- 仓库根 [`README.md`](https://github.com/GeWuYou/GFramework/blob/main/README.md)
|
||||
|
||||
@ -140,5 +140,5 @@ ecsModule.Update(deltaTime);
|
||||
|
||||
- ECS 模块总览:[`index.md`](./index.md)
|
||||
- 抽象契约页:[`../abstractions/ecs-arch-abstractions.md`](../abstractions/ecs-arch-abstractions.md)
|
||||
- 仓库模块 README:`GFramework.Ecs.Arch/README.md`
|
||||
- 仓库模块 README:[`GFramework.Ecs.Arch/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Ecs.Arch/README.md)
|
||||
- 统一 API / XML 导航:[`../api-reference/index.md`](../api-reference/index.md)
|
||||
|
||||
@ -127,6 +127,6 @@ IStorage storage = new FileStorage("GameData", serializer);
|
||||
|
||||
## 对应模块入口
|
||||
|
||||
- `GFramework.Game/README.md`
|
||||
- `GFramework.Game.Abstractions/README.md`
|
||||
- 仓库根 `README.md`
|
||||
- [`GFramework.Game/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game/README.md)
|
||||
- [`GFramework.Game.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game.Abstractions/README.md)
|
||||
- 仓库根 [`README.md`](https://github.com/GeWuYou/GFramework/blob/main/README.md)
|
||||
|
||||
@ -258,5 +258,5 @@ await sceneRouter.PopAsync();
|
||||
|
||||
1. [game/index.md](./index.md)
|
||||
2. [ui.md](./ui.md)
|
||||
3. `GFramework.Game/README.md`
|
||||
4. `GFramework.Game.Abstractions/README.md`
|
||||
3. [`GFramework.Game/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game/README.md)
|
||||
4. [`GFramework.Game.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game.Abstractions/README.md)
|
||||
|
||||
@ -329,5 +329,5 @@ uiRouter.Hide(modalHandle, UiLayer.Modal);
|
||||
1. [game/index.md](./index.md)
|
||||
2. [scene.md](./scene.md)
|
||||
3. [../source-generators/auto-ui-page-generator.md](../source-generators/auto-ui-page-generator.md)
|
||||
4. `GFramework.Game/README.md`
|
||||
5. `GFramework.Game.Abstractions/README.md`
|
||||
4. [`GFramework.Game/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game/README.md)
|
||||
5. [`GFramework.Game.Abstractions/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game.Abstractions/README.md)
|
||||
|
||||
@ -46,7 +46,7 @@
|
||||
对应文档:
|
||||
|
||||
- [`../core/cqrs.md`](../core/cqrs.md)
|
||||
- 仓库内模块入口:`GFramework.Cqrs/README.md`
|
||||
- 仓库内模块入口:[`GFramework.Cqrs/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Cqrs/README.md)
|
||||
|
||||
### 想做游戏运行时
|
||||
|
||||
@ -65,7 +65,7 @@
|
||||
对应文档:
|
||||
|
||||
- [`../game/index.md`](../game/index.md)
|
||||
- 仓库内模块入口:`GFramework.Game/README.md`
|
||||
- 仓库内模块入口:[`GFramework.Game/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game/README.md)
|
||||
|
||||
### 想接入 Godot
|
||||
|
||||
@ -76,7 +76,7 @@
|
||||
对应文档:
|
||||
|
||||
- [`../godot/index.md`](../godot/index.md)
|
||||
- 仓库内模块入口:`GFramework.Godot/README.md`
|
||||
- 仓库内模块入口:[`GFramework.Godot/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Godot/README.md)
|
||||
|
||||
## Source Generators 什么时候装
|
||||
|
||||
|
||||
@ -220,4 +220,4 @@ public List<IntConfig>? Values { get; } = new();
|
||||
1. [/zh-CN/source-generators/index](./index.md)
|
||||
2. [/zh-CN/game/config-system](../game/config-system.md)
|
||||
3. [/zh-CN/source-generators/godot-project-generator](./godot-project-generator.md)
|
||||
4. `GFramework.Godot.SourceGenerators/README.md`
|
||||
4. [`GFramework.Godot.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Godot.SourceGenerators/README.md)
|
||||
|
||||
@ -189,4 +189,4 @@ private void OnAnyButtonPressed()
|
||||
1. [/zh-CN/source-generators/get-node-generator](./get-node-generator.md)
|
||||
2. [/zh-CN/source-generators/godot-project-generator](./godot-project-generator.md)
|
||||
3. [/zh-CN/godot/ui](../godot/ui.md)
|
||||
4. `GFramework.Godot.SourceGenerators/README.md`
|
||||
4. [`GFramework.Godot.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Godot.SourceGenerators/README.md)
|
||||
|
||||
@ -195,4 +195,4 @@ finally
|
||||
1. [context-get-generator.md](./context-get-generator.md)
|
||||
2. [logging-generator.md](./logging-generator.md)
|
||||
3. [../core/index.md](../core/index.md)
|
||||
4. `GFramework.Core.SourceGenerators/README.md`
|
||||
4. [`GFramework.Core.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core.SourceGenerators/README.md)
|
||||
|
||||
@ -195,4 +195,4 @@ public override void _Ready()
|
||||
1. [/zh-CN/source-generators/bind-node-signal-generator](./bind-node-signal-generator.md)
|
||||
2. [/zh-CN/source-generators/godot-project-generator](./godot-project-generator.md)
|
||||
3. [/zh-CN/godot/ui](../godot/ui.md)
|
||||
4. `GFramework.Godot.SourceGenerators/README.md`
|
||||
4. [`GFramework.Godot.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Godot.SourceGenerators/README.md)
|
||||
|
||||
@ -217,4 +217,4 @@ AutoLoad 名称也遵循同样的冲突处理策略。
|
||||
1. [/zh-CN/source-generators/get-node-generator](./get-node-generator.md)
|
||||
2. [/zh-CN/source-generators/bind-node-signal-generator](./bind-node-signal-generator.md)
|
||||
3. [/zh-CN/tutorials/godot-integration](../tutorials/godot-integration.md)
|
||||
4. `GFramework.Godot.SourceGenerators/README.md`
|
||||
4. [`GFramework.Godot.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Godot.SourceGenerators/README.md)
|
||||
|
||||
@ -101,7 +101,7 @@ GFramework 当前发布的生成器包是:
|
||||
|
||||
## 对应模块入口
|
||||
|
||||
- `GFramework.Core.SourceGenerators/README.md`
|
||||
- `GFramework.Game.SourceGenerators/README.md`
|
||||
- `GFramework.Cqrs.SourceGenerators/README.md`
|
||||
- `GFramework.Godot.SourceGenerators/README.md`
|
||||
- [`GFramework.Core.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core.SourceGenerators/README.md)
|
||||
- [`GFramework.Game.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Game.SourceGenerators/README.md)
|
||||
- [`GFramework.Cqrs.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Cqrs.SourceGenerators/README.md)
|
||||
- [`GFramework.Godot.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Godot.SourceGenerators/README.md)
|
||||
|
||||
@ -213,4 +213,4 @@ public sealed class DynamicPrioritySystem : IPrioritized
|
||||
1. [context-aware-generator.md](./context-aware-generator.md)
|
||||
2. [context-get-generator.md](./context-get-generator.md)
|
||||
3. [../core/index.md](../core/index.md)
|
||||
4. `GFramework.Core.SourceGenerators/README.md`
|
||||
4. [`GFramework.Core.SourceGenerators/README.md`](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Core.SourceGenerators/README.md)
|
||||
|
||||
@ -121,7 +121,7 @@ namespace MyGame.Services
|
||||
|
||||
**代码说明**:
|
||||
|
||||
- `Option<T>` 明确表示值可能不存在,避免 NullReferenceException
|
||||
- `Option<T>` 明确表示值可能不存在,避免 NullReferenceException
|
||||
- `Match` 强制处理两种情况,不会遗漏 null 检查
|
||||
- `Map` 和 `Bind` 实现链式转换,代码更简洁
|
||||
- `Filter` 可以安全地过滤值
|
||||
@ -250,7 +250,7 @@ namespace MyGame.Services
|
||||
|
||||
**代码说明**:
|
||||
|
||||
- `Result<T>` 将错误作为值返回,而不是抛出异常
|
||||
- `Result<T>` 将错误作为值返回,而不是抛出异常
|
||||
- `Result.Try` 自动捕获异常并转换为 Result
|
||||
- `Bind` 可以链接多个可能失败的操作
|
||||
- `Match` 强制处理成功和失败两种情况
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user