Skip to content

Commit f5fb9f9

Browse files
SH2-1: complete RD recognizer for shape AST addon.
Add alt/altlit/sameLine/suppress plus arbitrary-body star/opt/sep/not with transaction rollback, expand toy coverage, and clear RD fail-fast entries from typescript+SH0. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent af4b251 commit f5fb9f9

5 files changed

Lines changed: 353 additions & 222 deletions

File tree

src/shape-machine.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,15 @@ export type ShapeTransaction<H = unknown> = {
5656
* Transaction contract:
5757
* - append-only arrays roll back by restoring their checkpointed lengths;
5858
* - overwrites must be recorded in `undo`, or staged and applied only on commit;
59-
* - parser position and every mutable Pratt/control flag join the same transaction;
59+
* - parser position and every mutable Pratt/control flag (`_suppressNext` /
60+
* `_suppressCur` / capped) join the same transaction;
6061
* - function-local `left`/`opText` values need no log when assigned only after success.
6162
*/
6263
export const SHAPE_TRANSACTION_CONTRACT = {
6364
appendOnly: 'restore-lengths',
6465
overwrite: 'undo-log-or-commit-on-success',
6566
prattLocals: 'commit-on-success',
67+
controlFlags: 'restore-with-checkpoint',
6668
} as const;
6769

6870
/** Local custom context (SH2-0). Full AltPathPart form is deferred. */

src/shape-validate.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,10 @@ function checkNodeAgainstSlots(
256256
}
257257
}
258258
}
259-
if (hasListSlot(v) && !sawListBind && !node.fields.some((f) => f.bind !== 'opText' && typeof f.bind === 'object' && 'from' in f.bind && f.bind.from === 'list')) {
259+
// A list nested inside opt/alt is one packed visible slot, not a direct list
260+
// channel. Only direct star/sep slots require a list binding.
261+
const hasDirectList = v.some((s) => s.k === 'star' || s.k === 'sep');
262+
if (hasDirectList && !sawListBind && !node.fields.some((f) => f.bind !== 'opText' && typeof f.bind === 'object' && 'from' in f.bind && f.bind.from === 'list')) {
260263
diags.push({
261264
level: 'error', rule: ruleName, code: 'star-needs-list',
262265
message: `${where}: visible stream has star/sep (${summarizeSlots(v)}) but no list-binding field`,

0 commit comments

Comments
 (0)