把三处功能要求也变成可复跑的检查(structcheck 93 条 + A/B 反证)
背景
----
「点组名不折起」「组名改名框不吞项数」「SignalData 去掉 displayName」
三处用户要求此前只有散文描述与已压缩的历史记录。本提交给它们补上
(a) 源码级断言、(b) A/B 反证、(c) 运行时断言三类证据。
新增 / 修改
-----------
- Tools/structcheck.ps1 新增第 [3] 组「行为与注释不变量」(31 条断言):
* 折起热区:RowHitPart 产出专用 RowPart::GROUP_TITLE,其矩形正好是「组N」标签矩形;
单击门限 `if (part != RowPart::DISCLOSURE && part != RowPart::GROUP_TITLE)` 全文件恰好 1 处;
ToggleGroupFromRow 恰好 2 个入口(单击门限 + 双击折叠三角);
双击非名称本体(`if (part != RowPart::BODY) return false;`)不进入改名。
* 改名框:右边界必须是 `statsRight - statsWidth - 6*scale`,且全文件不得出现 `right = statsRight;`;
项数宽度必须来自 GroupStatsText。
* 数据模型:SignalData / SignalGroup 各只声明 1 个字符串字段且名为 name;
RenameSignal / RenameGroup 存在;全仓 displayName / Title() / Set*DisplayName 残留 0 处。
* 注释核验:过时措辞(文件下标 / 所属文件下标 / 原信号所属文件下标 / 文件分组 / 原文件下标 /
文件序号)全仓 0 处;已修正注释确实存在(例如 DerivedSignalRequest::groupIndex →
「原信号所属分组下标」)。
- Tools/structcheck_phrases.txt(新增,UTF-8):中文短语表,供 .ps1 以 UTF8Encoding($false)
读入后逐条断言(forbid / require)。⚠️ 中文不能直接写进 .ps1 —— PowerShell 5.1 按 ANSI 读脚本。
- 断言总数 62 → 93;退出码仍为 0/1,可直接接 CI。
A/B 反证(判别力)
------------------
回退两处 UI 修复(`if (part != DISCLOSURE && part != GROUP_TITLE)` → `if (false)`;
`right = statsRight - statsWidth - 6*scale;` → `right = statsRight;`):
* panelanim.exe 147/156(9 项如实失败)→ 恢复后 156/156
失败文案直接点名需求:the group rename box stops before the item counter /
clicking the group name does not start collapsing / does not collapse the group ...
* structcheck 90/93(3 项如实失败)→ 恢复后 93/93
single-click toggle gate accepts only DISCLOSURE / GROUP_TITLE (found 0) /
rename box right edge excludes the item counter / rename box never extends over the counter
运行时断言(数据层,datacheck 69/69)
-------------------------------------
Check(set.RenameSignal(groupIndex, 0, L"改名") && set.Resolve(SignalRef{0,0})->name == L"改名",
"signal rename writes the single name field");
Check(set.RenameGroup(groupIndex, L"我的组") && set.GroupAt(0)->name == L"我的组",
"group rename writes the single name field");
Check(!set.RenameSignal(groupIndex, 0, L"") && ..., "empty rename is rejected (name is never blanked)");
即重命名只写一个 name 字段(编译期已无 displayName 可写),空名被拒。
文档
----
- Documents/refactor_report_2026-10-16.md:新增 §9.7(三处功能要求的源码断言 + A/B + 运行时断言);
§1.3 补完整 A/B 实测;§9.4 验证表与 §9.5 变更清单同步(62 → 93)。
- Documents/agent_mem.md:§9.37 开头补可复跑证据块;§11.2 第 96 条扩写为三组 + 中文短语数据文件的做法;
变更日志 d2e6fdd 行的结构检查描述更新为 93 条与 A/B 数据。
验证
----
make release 零警告,Target.exe 764 416 B;
structcheck 93/93;verify 49/49;datacheck 69/69;edgecheck 46/46;
panelanim 156/156;errcheck 19/19。
This commit is contained in:
1 parent
695b73a366
commit
9cf6b2fedc
4 files changed
+181
-13
No files matched your search
+74
-1
@@ -195,8 +195,81 @@ Check ($paintUtilIncludes.Count -eq 0) "Utilities/PaintUtil (base layer) does no
|
||||
$badDir = @(Select-String -Path "Utilities/Src/*.cpp", "Utilities/Inc/*.hpp" -Pattern '#include "(PlotView|SignalPanel|Toolbar|TabBar)' -ErrorAction SilentlyContinue)
|
||||
Check ($badDir.Count -eq 0) "Utilities never includes a feature component"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "== [3] user-requested behaviour invariants (source level) =="
|
||||
|
||||
$utf8 = New-Object System.Text.UTF8Encoding($false)
|
||||
$panelFile = "Components/SignalPanel/Src/SignalPanel.cpp"
|
||||
$panelIntFile = "Components/SignalPanel/Src/SignalPanelInteraction.cpp"
|
||||
$modelFile = "Utilities/Inc/DataModel.hpp"
|
||||
|
||||
$panelText = [System.IO.File]::ReadAllText($panelFile, $utf8)
|
||||
$panelIntText = [System.IO.File]::ReadAllText($panelIntFile, $utf8)
|
||||
$modelText = [System.IO.File]::ReadAllText($modelFile, $utf8)
|
||||
|
||||
# 3a. collapsing a group needs the disclosure triangle OR the "Group N" label -- clicking the name
|
||||
# itself must not collapse it. (User 2026-10-16.)
|
||||
Check ($panelText -match "return RowPart::GROUP_TITLE;") "hit test yields a dedicated GROUP_TITLE part"
|
||||
Check ($panelText -match "const gd::RectF labelRect\(labelLeft, rect\.Y, GroupLabelWidth\(row\), rect\.Height\);") "GROUP_TITLE hot zone is exactly the Group-N label rect"
|
||||
$gate = @(Select-String -Path $panelIntFile -Pattern "^\s*if \(part != RowPart::DISCLOSURE && part != RowPart::GROUP_TITLE\)")
|
||||
Check ($gate.Count -eq 1) ("single-click toggle gate accepts only DISCLOSURE / GROUP_TITLE (found " + $gate.Count + ")")
|
||||
$toggles = @(Select-String -Path $panelIntFile -Pattern "^\s*ToggleGroupFromRow\(item\.groupIndex, row\);")
|
||||
Check ($toggles.Count -eq 2) ("ToggleGroupFromRow has exactly 2 entry points, single click + double click (found " + $toggles.Count + ")")
|
||||
Check ($panelIntText -match "(?s)if \(part == RowPart::DISCLOSURE\)\s*\{\s*//[^\n]*\n(\s*//[^\n]*\n)*\s*ToggleGroupFromRow") "double click on the triangle still collapses"
|
||||
Check ($panelIntText -match "(?s)if \(part != RowPart::BODY\)\s*\{\s*return false;\s*\}") "double click outside the name body never starts a rename"
|
||||
|
||||
# 3b. the group rename box must stop before the "N items" counter. (User 2026-10-16.)
|
||||
Check ($panelText -match "right = statsRight - statsWidth - 6\.0f \* m_scale;") "rename box right edge excludes the item counter"
|
||||
Check ($panelText -notmatch "right = statsRight;") "rename box never extends over the counter"
|
||||
Check ($panelText -match "const float statsWidth = UiMeasureTextWidth\(m_fonts->uiSmall, GroupStatsText\(row\)\);") "counter width comes from GroupStatsText"
|
||||
|
||||
# 3c. SignalData / SignalGroup keep exactly ONE name field. (User 2026-10-16: drop displayName.)
|
||||
$data = [regex]::Match($modelText, "(?s)struct SignalData\s*\{(.*?)\n\t\};")
|
||||
Check ($data.Success) "SignalData struct found in the data model"
|
||||
if ($data.Success) {
|
||||
$body = $data.Groups[1].Value
|
||||
$fields = @([regex]::Matches($body, "std::wstring\s+\w+;"))
|
||||
Check ($fields.Count -eq 1) ("SignalData declares exactly one string field (found " + $fields.Count + ")")
|
||||
Check ($body -match "std::wstring name;") "that single field is name"
|
||||
}
|
||||
$group = [regex]::Match($modelText, "(?s)struct SignalGroup\s*\{(.*?)\n\t\};")
|
||||
Check ($group.Success) "SignalGroup struct found in the data model"
|
||||
if ($group.Success) {
|
||||
$gbody = $group.Groups[1].Value
|
||||
$gfields = @([regex]::Matches($gbody, "std::wstring\s+\w+;"))
|
||||
Check ($gfields.Count -eq 1) ("SignalGroup declares exactly one string field (found " + $gfields.Count + ")")
|
||||
}
|
||||
Check ($modelText -match "bool RenameSignal\(") "DataSet::RenameSignal replaces the old SetSignalDisplayName"
|
||||
Check ($modelText -match "bool RenameGroup\(") "DataSet::RenameGroup replaces the old SetGroupDisplayName"
|
||||
$staleNames = @(Select-String -Path "Components/*/Src/*.cpp","Components/*/Inc/*.hpp","Components/*/Src/*.hpp",
|
||||
"Utilities/Src/*.cpp","Utilities/Inc/*.hpp" -Pattern "displayName|SetSignalDisplayName|SetGroupDisplayName|\.Title\(\)|->Title\(\)")
|
||||
Check ($staleNames.Count -eq 0) ("no displayName / Title() left in any source (found " + $staleNames.Count + ")")
|
||||
|
||||
# 3d. comment hygiene, driven by a UTF-8 data file (the .ps1 itself must stay ASCII for PowerShell 5.1)
|
||||
$phraseFile = "Tools/structcheck_phrases.txt"
|
||||
Check (Test-Path $phraseFile) "comment-hygiene phrase list exists"
|
||||
if (Test-Path $phraseFile) {
|
||||
$scanFiles = @(Get-ChildItem "Components/*/Src/*.cpp","Components/*/Src/*.hpp","Components/*/Inc/*.hpp",
|
||||
"Utilities/Src/*.cpp","Utilities/Inc/*.hpp" | ForEach-Object { $_.FullName })
|
||||
$scanText = @{}
|
||||
foreach ($f in $scanFiles) { $scanText[$f] = [System.IO.File]::ReadAllText($f, $utf8) }
|
||||
foreach ($line in [System.IO.File]::ReadAllLines($phraseFile, $utf8)) {
|
||||
if ([string]::IsNullOrWhiteSpace($line)) { continue }
|
||||
$parts = $line -split "`t"
|
||||
$mode = $parts[0]
|
||||
if ($mode -eq "forbid") {
|
||||
$phrase = $parts[1]
|
||||
$offenders = @($scanFiles | Where-Object { $scanText[$_].Contains($phrase) })
|
||||
Check ($offenders.Count -eq 0) ("stale wording absent everywhere: " + $phrase + " (found in " + $offenders.Count + " files)")
|
||||
} elseif ($mode -eq "require") {
|
||||
$target = $parts[1]
|
||||
$phrase = $parts[2]
|
||||
Check ((Test-Path $target) -and ([System.IO.File]::ReadAllText($target, $utf8).Contains($phrase))) ("corrected comment present in " + $target + ": " + $phrase)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host ("structcheck: " + $pass + " passed, " + $fail + " failed")
|
||||
if ($fail -ne 0) { exit 1 }
|
||||
exit 0
|
||||
|
||||
Reference in new issue
Block a user