Comfy-Org / Comfy-Org/ComfyUI_frontend
Enforce branded-ID minting rule (src/types/AGENTS.md) with verified no-restricted-syntax selectors
- Dominant language
- TypeScript
- Stars
- 2k
- Forks
- 699
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 490
Description
`src/types/AGENTS.md` (added in #13085) says branded IDs are minted only in `src/types/*Id.ts` helpers, but nothing enforces it: the rule lives in an agent-guidance file three directories deep, and the ECS branch already carries a production violation (`mappers.ts` L47/L92 casts raw strings to `WidgetId`, flagged at https://github.com/Comfy-Org/ComfyUI_frontend/pull/14246#discussion_r3838139690). Proposal: enforce it with two `no-restricted-syntax` selectors, verified empirically at `f1bfb313d6`.
## Verified selector block
```ts
{
name: 'comfy/branded-id-minting',
files: ['src/**/*.ts', 'src/**/*.vue'],
ignores: ['src/types/*Id.ts'],
rules: {
'no-restricted-syntax': ['error',
{ selector: "TSAsExpression[typeAnnotation.typeName.name=/^(NodeId|WidgetId|GroupId|LinkId|RerouteId|SlotId|RootGraphId|OwningGraphId)$/]",
message: 'Branded IDs are minted only in src/types/*Id.ts. Use the exported helper (toNodeId, widgetId, ...).' },
{ selector: "TSTypeAssertion[typeAnnotation.typeName.name=/^(NodeId|WidgetId|GroupId|LinkId|RerouteId|SlotId|RootGraphId|OwningGraphId)$/]",
message: 'Branded IDs are minted only in src/types/*Id.ts. Use the exported helper (toNodeId, widgetId, ...).' }
]
}
}
```
## Measurements (block appended to real eslint.config.ts in a worktree, then reverted)
- **25 hits total**, line-for-line identical to a `git grep` inventory of cast sites outside the defining modules.
- **2 production violations**: the `mappers.ts` pair already flagged on #14246.
- **23 test-file casts** across 5 files. 16 build well-formed ids via template strings and can switch to the helpers mechanically. **7 deliberately mint malformed ids** to test parser rejection (helpers refuse to build them by design) and need `eslint-disable-next-line` with a one-line justification — which makes deliberate invalid minting visible and greppable, arguably a feature.
- **0 hits** inside the 7 defining `src/types/*Id.ts` modules; `widgetId.test.ts` correctly NOT excluded by the glob.
- **0 false positives** on `as SubgraphId` / `as NodeLocatorId` (plain aliases, no brand).
## Why enumerate the 8 brands instead of `/Id$/`
A naive `/Id$/` regex reaches 54 sites; the extra 21 span 12 non-branded type names (SubgraphId, NodeLocatorId, TestSettingId, RunId, VueNodeId, ExecutionId, NodeExecutionId, CoachId, BuilderStepId, SortingStrategyId, GroupingStrategyId, SupportedTemplateId) that are plain aliases with no helper to point people at. Cost of enumeration: adding a new brand means adding one name to the regex, in the same PR that introduces the brand.
## Blocked by #15744
The config currently has a flat-config clobber where the warn-level DOM block silently disables the existing error-level `no-restricted-syntax` bans for all non-test src files. Appending this block as-is would either be clobbered or clobber others. Fix #15744's consolidation first, then add these selectors to the consolidated structure.
## Related
- #11844 bans bare `string` params *named* nodeId — complementary (parameter types vs casts); neither subsumes the other.
- #15524 (Zod rule scoping) is in the same config neighborhood and is also affected by #15744.
Contributor guide
Assessment
This issue has not been assessed yet.