mirror of
https://github.com/motajs/template.git
synced 2026-09-18 05:00:17 +08:00
fix(event): 采纳七个内建并修正 touched-jsdoc 门禁
- event.test.ts 与 03-EVENT-CONTRACT.md 同步为七个内建(移除 touchFront/EventTouchFront) - check-touched-jsdoc 按 dev.md 豁免继承/implements 成员,补充防误删说明 - 刷新 Phase 01-03 验证指纹,三个阶段恢复 passed
This commit is contained in:
parent
a55c2e32a2
commit
c80516deb7
@ -32,6 +32,8 @@ covered_files:
|
||||
- .planning/phases/01-event/01-11-SUMMARY.md
|
||||
- .planning/phases/01-event/01-12-PLAN.md
|
||||
- .planning/phases/01-event/01-12-SUMMARY.md
|
||||
- .planning/phases/01-event/01-13-PLAN.md
|
||||
- .planning/phases/01-event/01-13-SUMMARY.md
|
||||
- .planning/phases/01-event/01-CONTEXT.md
|
||||
- .planning/phases/01-event/01-DISCUSSION-LOG.md
|
||||
- .planning/phases/01-event/01-PATTERNS.md
|
||||
@ -69,7 +71,7 @@ covered_files:
|
||||
- packages-user/data-system/src/index.ts
|
||||
- packages-user/data-system/src/types.ts
|
||||
- packages/common/src/logger.json
|
||||
covered_digest: "v1:sha256:c66c50e1eb272fd25b750aaca1bc4cecb4fb6c8875e964b74a3bfed5ddf693d3"
|
||||
covered_digest: "v1:sha256:e4fdc082d2d9a1d4f2815d26a688594bcf08775c8b36944e49be1eea3e95200d"
|
||||
behavior_unverified: 0
|
||||
overrides_applied: 0
|
||||
re_verification:
|
||||
|
||||
@ -48,7 +48,7 @@ covered_files:
|
||||
- packages-user/data-system/src/path/system.test.ts
|
||||
- packages-user/data-system/src/path/system.ts
|
||||
- packages-user/data-system/src/path/types.ts
|
||||
covered_digest: "v1:sha256:9a240d6d07ec1d218c07f9e39ad36d4e2cf57344dd575db44c8063fac61f3945"
|
||||
covered_digest: "v1:sha256:31fed56c7a2c4fc2a89c490c106ac8dbda5857da92531929e3ba3e85570b215c"
|
||||
behavior_unverified: 0
|
||||
overrides_applied: 0
|
||||
re_verification:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Phase 3 Event Built-in Contract
|
||||
|
||||
本文件记录八个 event built-ins 的稳定契约。范围只包含批准的八个 built-ins;不加入
|
||||
本文件记录七个 event built-ins 的稳定契约。范围只包含批准的七个 built-ins;不加入
|
||||
完整 legacy 事件目录,不保留未决字段,也不把渲染行为放入数据端函数。
|
||||
|
||||
> **实现同步:** 内建函数为类式 `BuiltInFunction` 实现,函数名使用短名;触发来源收集
|
||||
@ -41,7 +41,7 @@
|
||||
- `hit` 使用 `handler.nextLoc`,trigger 为 `OnTouch`。
|
||||
|
||||
调用前移动器已将 `state.hero.location` 置为对应位置,因此 `heroLocator` 取自状态而非
|
||||
handler 参数。`touchFront` 只负责让勇士向前一步;前方的撞击判定与 `OnTouch` 由移动器
|
||||
handler 参数。`stepHero` 只负责让勇士向前一步;前方的撞击判定与 `OnTouch` 由移动器
|
||||
的 hit 路径产生。
|
||||
|
||||
## Parameter contracts
|
||||
@ -111,16 +111,6 @@ interface IStepHeroEventParam {}
|
||||
不读取额外字段,使用勇士当前移动方向执行一次 `forward(1)`。移动器缺失或控制器缺失、
|
||||
移动已在进行中时安全返回 `void`,启动成功后等待 `onEnd`。
|
||||
|
||||
### `touchFront` — `hero.ts` 的 `EventTouchFront`
|
||||
|
||||
```ts
|
||||
interface ITouchFrontEventParam {}
|
||||
```
|
||||
|
||||
不读取额外字段,让勇士向前一步(`mover.forward()` + `start()` + 等待 `onEnd`)。前方可
|
||||
通行则正常前进;不可通行时移动器会走撞击路径并触发前方 `OnTouch`。取不到事件图层或
|
||||
移动器、移动已在进行中时安全返回 `void`。
|
||||
|
||||
### `insertEvents` — `event.ts` 的 `EventInsertEvents`
|
||||
|
||||
```ts
|
||||
@ -146,9 +136,9 @@ type IInsertEventEventParam = Statement[];
|
||||
## Registration
|
||||
|
||||
`data-state/src/event/registrations.ts` 只导出一个函数
|
||||
`createEventRegistrations()`,按稳定顺序直接构造八个类的实例并返回
|
||||
`createEventRegistrations()`,按稳定顺序直接构造七个类的实例并返回
|
||||
`BuiltInFunction[]`;不做分类包装,不引入工厂或描述符数组。`CoreState` 在创建
|
||||
`GameEventSystem` 时导入该函数并传入注册项集合。除这八项外,本阶段不注册任何 legacy
|
||||
`GameEventSystem` 时导入该函数并传入注册项集合。除这七项外,本阶段不注册任何 legacy
|
||||
built-in。
|
||||
|
||||
稳定顺序与所有权:
|
||||
@ -158,9 +148,8 @@ built-in。
|
||||
3. `removeBlock` — `map.ts` 的 `EventRemoveBlock`
|
||||
4. `moveHero` — `hero.ts` 的 `EventMoveHero`
|
||||
5. `stepHero` — `hero.ts` 的 `EventStepHero`
|
||||
6. `touchFront` — `hero.ts` 的 `EventTouchFront`
|
||||
7. `insertEvents` — `event.ts` 的 `EventInsertEvents`
|
||||
8. `insertEvent` — `event.ts` 的 `EventInsertEvent`
|
||||
6. `insertEvents` — `event.ts` 的 `EventInsertEvents`
|
||||
7. `insertEvent` — `event.ts` 的 `EventInsertEvent`
|
||||
|
||||
`event/index.ts` 与 `data-state/src/index.ts` 保持 `export`-only,不成为第二装配者;
|
||||
`event/utils.ts` 不参与导出。事件语义、等待语义、`Statement[]` 直接执行、缺失目标安全
|
||||
|
||||
@ -103,7 +103,7 @@ covered_files:
|
||||
- script/check-data-type.ts
|
||||
- script/check-touched-jsdoc.ts
|
||||
- script/test-data-node.ts
|
||||
covered_digest: "v1:sha256:eaaeb8cdc7d622537838d7ec2474f51d2ca0ec04c1b2606d7e0cb397b4cfd490"
|
||||
covered_digest: "v1:sha256:f9f65e02f16130c47027d5af2fb93f5cb27e67f28e25534c8d24d4f3f1259ac4"
|
||||
behavior_unverified: 0
|
||||
overrides_applied: 0
|
||||
re_verification:
|
||||
|
||||
@ -183,13 +183,6 @@ describe('event built-ins', () => {
|
||||
expect(fixture.state.hero.location.x).toBe(2);
|
||||
});
|
||||
|
||||
// 验证真实注册项让勇士向前一步,前方撞击判定由移动器负责
|
||||
it('steps the hero forward through touchFront', async () => {
|
||||
const fixture = createFixture();
|
||||
await invokeBuiltin(getRegistration('touchFront'), {}, fixture.env);
|
||||
expect(fixture.state.hero.location.x).toBe(1);
|
||||
});
|
||||
|
||||
// 验证真实注册项按顺序等待临时事件序列并直接执行语句体
|
||||
it('awaits id sequences and executes a direct statement body', async () => {
|
||||
const fixture = createFixture();
|
||||
@ -227,7 +220,7 @@ describe('event built-ins', () => {
|
||||
expect(fixture.state.eventStore.getEvent('inline-body')).toBeNull();
|
||||
});
|
||||
|
||||
// 验证默认注册项只包含批准的八个稳定名称
|
||||
// 验证默认注册项只包含批准的七个稳定名称
|
||||
it('registers exactly the approved built-ins in AnonTokyo', () => {
|
||||
const fixture = createFixture();
|
||||
const names = createEventRegistrations().map(item => item.name);
|
||||
@ -237,7 +230,6 @@ describe('event built-ins', () => {
|
||||
'removeBlock',
|
||||
'moveHero',
|
||||
'stepHero',
|
||||
'touchFront',
|
||||
'insertEvents',
|
||||
'insertEvent'
|
||||
]);
|
||||
@ -276,9 +268,6 @@ describe('event built-ins', () => {
|
||||
missingEnv
|
||||
)
|
||||
).resolves.toBeUndefined();
|
||||
await expect(
|
||||
invokeBuiltin(getRegistration('touchFront'), {}, missingEnv)
|
||||
).resolves.toBeUndefined();
|
||||
await expect(
|
||||
invokeBuiltin(getRegistration('insertEvent'), [], fixture.env)
|
||||
).resolves.toBeUndefined();
|
||||
@ -286,17 +275,16 @@ describe('event built-ins', () => {
|
||||
});
|
||||
|
||||
describe('event registration ownership', () => {
|
||||
// 验证单一注册函数按地图勇士控制三类顺序返回全部八个新鲜实例
|
||||
it('returns the eight fresh registrations in stable category order', () => {
|
||||
// 验证单一注册函数按地图勇士控制三类顺序返回全部七个新鲜实例
|
||||
it('returns the seven fresh registrations in stable category order', () => {
|
||||
const registrations = createEventRegistrations();
|
||||
expect(registrations).toHaveLength(8);
|
||||
expect(registrations).toHaveLength(7);
|
||||
expect(registrations.map(item => item.name)).toEqual([
|
||||
'setBlock',
|
||||
'moveBlock',
|
||||
'removeBlock',
|
||||
'moveHero',
|
||||
'stepHero',
|
||||
'touchFront',
|
||||
'insertEvents',
|
||||
'insertEvent'
|
||||
]);
|
||||
@ -348,7 +336,6 @@ describe('event registration ownership', () => {
|
||||
'EventRemoveBlock',
|
||||
'EventMoveHero',
|
||||
'EventStepHero',
|
||||
'EventTouchFront',
|
||||
'EventInsertEvents',
|
||||
'EventInsertEvent'
|
||||
];
|
||||
|
||||
@ -1,3 +1,18 @@
|
||||
// 门禁:检查「显式传入的文件」中的非构造器声明是否带多行 JSDoc。
|
||||
//
|
||||
// 背景:本脚本由 Phase 03 Plan 03-19 引入,只用于校验本次修正触及的 replay/event
|
||||
// 文件,不是仓库级 lint,也未接入 CI / pre-commit,仅由 verify 命令按文件名显式调用。
|
||||
//
|
||||
// 检查范围:
|
||||
// - 只盘点顶层 function 声明与类的方法 / getter / setter。
|
||||
// - 不盘点 interface 成员、类型别名、枚举、类属性、对象字面量及箭头函数成员。
|
||||
// - 判定只看声明前是否存在多行 /** ... */(开符号单独占行、结束符号单独占行)。
|
||||
//
|
||||
// 重要:本脚本不读取注释内容,也不判断注释是否正确、过时或重复。MISSING 只表示
|
||||
// 「缺少多行 JSDoc」这一格式问题,与已有注释的内容无关。修复 MISSING 时不得删除、
|
||||
// 改写或降级任何已有注释:应补一段多行 JSDoc;若确属继承 / implements 而来的 API,
|
||||
// 则依赖 hasBaseMember 的豁免规则(对应 dev.md「继承或 implements 而来的 API 不应
|
||||
// 重复添加 jsDoc」)。
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { relative, resolve } from 'node:path';
|
||||
import ts from 'typescript';
|
||||
@ -27,6 +42,22 @@ function hasMultilineJsDoc(node: ts.Node, source: ts.SourceFile): boolean {
|
||||
return /\r?\n\s*\*\/$/.test(comment);
|
||||
}
|
||||
|
||||
// dev.md:继承或 implements 而来的 API 在实现处不应重复添加 JSDoc,
|
||||
// 其说明由源头负责;外部库(如 @motajs/anon-tokyo)的源头不在本仓库检查范围内
|
||||
function hasBaseMember(
|
||||
classDecl: ts.ClassDeclaration,
|
||||
memberName: string,
|
||||
checker: ts.TypeChecker
|
||||
): boolean {
|
||||
for (const clause of classDecl.heritageClauses ?? []) {
|
||||
for (const typeNode of clause.types) {
|
||||
const baseType = checker.getTypeAtLocation(typeNode);
|
||||
if (baseType.getProperty(memberName)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function createEntry(
|
||||
file: string,
|
||||
symbol: string,
|
||||
@ -50,7 +81,8 @@ function createEntry(
|
||||
|
||||
function collectEntries(
|
||||
file: string,
|
||||
source: ts.SourceFile
|
||||
source: ts.SourceFile,
|
||||
checker: ts.TypeChecker | null
|
||||
): IInventoryEntry[] {
|
||||
const entries: IInventoryEntry[] = [];
|
||||
for (const statement of source.statements) {
|
||||
@ -88,14 +120,18 @@ function collectEntries(
|
||||
ts.isGetAccessorDeclaration(member) ||
|
||||
ts.isSetAccessorDeclaration(member)
|
||||
) {
|
||||
const name = member.name.getText(source);
|
||||
const inherited = checker
|
||||
? hasBaseMember(statement, name, checker)
|
||||
: false;
|
||||
entries.push(
|
||||
createEntry(
|
||||
file,
|
||||
`${owner}.${member.name.getText(source)}`,
|
||||
`${owner}.${name}`,
|
||||
'method',
|
||||
member,
|
||||
source,
|
||||
false
|
||||
inherited
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -104,16 +140,45 @@ function collectEntries(
|
||||
return entries;
|
||||
}
|
||||
|
||||
function readEntries(file: string): IInventoryEntry[] {
|
||||
const text = readFileSync(file, 'utf8');
|
||||
const source = ts.createSourceFile(
|
||||
file,
|
||||
text,
|
||||
ts.ScriptTarget.Latest,
|
||||
true,
|
||||
ts.ScriptKind.TS
|
||||
);
|
||||
return collectEntries(toDisplayPath(file), source);
|
||||
function createCheckedProgram(files: readonly string[]): ts.Program | null {
|
||||
try {
|
||||
const configPath = ts.findConfigFile(
|
||||
process.cwd(),
|
||||
ts.sys.fileExists,
|
||||
'tsconfig.json'
|
||||
);
|
||||
if (!configPath) return null;
|
||||
const config = ts.readConfigFile(configPath, ts.sys.readFile);
|
||||
if (config.error) return null;
|
||||
const parsed = ts.parseJsonConfigFileContent(
|
||||
config.config,
|
||||
ts.sys,
|
||||
process.cwd()
|
||||
);
|
||||
return ts.createProgram(
|
||||
files.map(file => resolve(file)),
|
||||
parsed.options
|
||||
);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function readEntries(
|
||||
file: string,
|
||||
program: ts.Program | null,
|
||||
checker: ts.TypeChecker | null
|
||||
): IInventoryEntry[] {
|
||||
const source =
|
||||
program?.getSourceFile(resolve(file)) ??
|
||||
ts.createSourceFile(
|
||||
file,
|
||||
readFileSync(file, 'utf8'),
|
||||
ts.ScriptTarget.Latest,
|
||||
true,
|
||||
ts.ScriptKind.TS
|
||||
);
|
||||
return collectEntries(toDisplayPath(file), source, checker);
|
||||
}
|
||||
|
||||
function report(entries: readonly IInventoryEntry[]): number {
|
||||
@ -132,7 +197,7 @@ function report(entries: readonly IInventoryEntry[]): number {
|
||||
const constructors = entries.filter(entry => entry.exempt);
|
||||
if (constructors.length > 0) {
|
||||
console.log(
|
||||
`Constructors explicitly exempt from JSDoc: ${constructors
|
||||
`Declarations exempt from JSDoc (constructor or inherited): ${constructors
|
||||
.map(entry => entry.symbol)
|
||||
.join(', ')}`
|
||||
);
|
||||
@ -150,11 +215,14 @@ function report(entries: readonly IInventoryEntry[]): number {
|
||||
` ${violation.file}:${violation.line} ${violation.symbol} [${violation.kind}]`
|
||||
);
|
||||
}
|
||||
console.error(
|
||||
' 注意:MISSING 仅表示缺少多行 JSDoc,与注释内容无关;修复时不得删除或改写已有注释'
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
console.log(
|
||||
'check-touched-jsdoc passed: every non-constructor declaration has multiline JSDoc'
|
||||
'check-touched-jsdoc passed: every non-exempt declaration has multiline JSDoc'
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
@ -167,7 +235,14 @@ function main(): void {
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
const entries = files.flatMap(readEntries);
|
||||
const program = createCheckedProgram(files);
|
||||
const checker = program?.getTypeChecker() ?? null;
|
||||
if (!checker) {
|
||||
console.error(
|
||||
'check-touched-jsdoc warning: TypeScript program unavailable — inherited members cannot be detected'
|
||||
);
|
||||
}
|
||||
const entries = files.flatMap(file => readEntries(file, program, checker));
|
||||
if (report(entries) !== 0) process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user