

























@@ -11,82 +11,89 @@
1111 * 5. parse → emit → parse is fixpoint
1212 * 6. hostile inputs do not throw at parse time
1313 */
14-import { describe, expect, it } from 'vitest';
15-import { inferKind } from '../../dispatch.js';
16-import { emitMd } from '../../emit.js';
17-import { setMdOcPath } from '../../edit.js';
18-import { resolveMdOcPath } from '../../resolve.js';
19-import { emitJsonc } from '../../jsonc/emit.js';
20-import { setJsoncOcPath } from '../../jsonc/edit.js';
21-import { resolveJsoncOcPath } from '../../jsonc/resolve.js';
22-import { parseJsonc } from '../../jsonc/parse.js';
23-import { emitJsonl } from '../../jsonl/emit.js';
24-import { setJsonlOcPath } from '../../jsonl/edit.js';
25-import { resolveJsonlOcPath } from '../../jsonl/resolve.js';
26-import { parseJsonl } from '../../jsonl/parse.js';
27-import { parseOcPath } from '../../oc-path.js';
28-import { parseMd } from '../../parse.js';
14+import { describe, expect, it } from "vitest";
15+import { inferKind } from "../../dispatch.js";
16+import { setMdOcPath } from "../../edit.js";
17+import { emitMd } from "../../emit.js";
18+import { setJsoncOcPath } from "../../jsonc/edit.js";
19+import { emitJsonc } from "../../jsonc/emit.js";
20+import { parseJsonc } from "../../jsonc/parse.js";
21+import { resolveJsoncOcPath } from "../../jsonc/resolve.js";
22+import { setJsonlOcPath } from "../../jsonl/edit.js";
23+import { emitJsonl } from "../../jsonl/emit.js";
24+import { parseJsonl } from "../../jsonl/parse.js";
25+import { resolveJsonlOcPath } from "../../jsonl/resolve.js";
26+import { parseOcPath } from "../../oc-path.js";
27+import { parseMd } from "../../parse.js";
28+import { resolveMdOcPath } from "../../resolve.js";
292930-describe('wave-22 cross-kind property invariants', () => {
31-const mdRaw = '---\nname: x\n---\n\n## Boundaries\n\n- enabled: true\n';
30+describe("wave-22 cross-kind property invariants", () => {
31+const mdRaw = "---\nname: x\n---\n\n## Boundaries\n\n- enabled: true\n";
3232const jsoncRaw = '// h\n{ "k": 1, "n": [1,2,3] }\n';
3333const jsonlRaw = '{"a":1}\n\nbroken\n{"b":2}\n';
343435-it('P-01 round-trip parse → emit is byte-stable across all kinds', () => {
35+it("P-01 round-trip parse → emit is byte-stable across all kinds", () => {
3636expect(emitMd(parseMd(mdRaw).ast)).toBe(mdRaw);
3737expect(emitJsonc(parseJsonc(jsoncRaw).ast)).toBe(jsoncRaw);
3838expect(emitJsonl(parseJsonl(jsonlRaw).ast)).toBe(jsonlRaw);
3939});
404041-it('P-02 resolve is non-mutating across all kinds', () => {
41+it("P-02 resolve is non-mutating across all kinds", () => {
4242const md = parseMd(mdRaw).ast;
4343let before = JSON.stringify(md);
44-resolveMdOcPath(md, parseOcPath('oc://X/[frontmatter]/name'));
45-resolveMdOcPath(md, parseOcPath('oc://X/boundaries'));
44+resolveMdOcPath(md, parseOcPath("oc://X/[frontmatter]/name"));
45+resolveMdOcPath(md, parseOcPath("oc://X/boundaries"));
4646expect(JSON.stringify(md)).toBe(before);
47474848const jsonc = parseJsonc(jsoncRaw).ast;
4949before = JSON.stringify(jsonc);
50-resolveJsoncOcPath(jsonc, parseOcPath('oc://X/k'));
51-resolveJsoncOcPath(jsonc, parseOcPath('oc://X/n.0'));
50+resolveJsoncOcPath(jsonc, parseOcPath("oc://X/k"));
51+resolveJsoncOcPath(jsonc, parseOcPath("oc://X/n.0"));
5252expect(JSON.stringify(jsonc)).toBe(before);
53535454const jsonl = parseJsonl(jsonlRaw).ast;
5555before = JSON.stringify(jsonl);
56-resolveJsonlOcPath(jsonl, parseOcPath('oc://X/L1'));
57-resolveJsonlOcPath(jsonl, parseOcPath('oc://X/$last'));
56+resolveJsonlOcPath(jsonl, parseOcPath("oc://X/L1"));
57+resolveJsonlOcPath(jsonl, parseOcPath("oc://X/$last"));
5858expect(JSON.stringify(jsonl)).toBe(before);
5959});
606061-it('P-03 unresolvable set never throws across all kinds', () => {
62-const ocPath = parseOcPath('oc://X/totally.missing.path');
63-expect(() =>
64-setMdOcPath(parseMd(mdRaw).ast, ocPath, 'x'),
65-).not.toThrow();
66-expect(() =>
61+it("P-03 unresolvable set never throws across all kinds", () => {
62+const ocPath = parseOcPath("oc://X/totally.missing.path");
63+expect(setMdOcPath(parseMd(mdRaw).ast, ocPath, "x")).toEqual({
64+ok: false,
65+reason: "not-writable",
66+});
67+expect(
6768setJsoncOcPath(parseJsonc(jsoncRaw).ast, ocPath, {
68-kind: 'string',
69-value: 'x',
69+kind: "string",
70+value: "x",
7071}),
71-).not.toThrow();
72-expect(() =>
72+).toEqual({
73+ok: false,
74+reason: "unresolved",
75+});
76+expect(
7377setJsonlOcPath(parseJsonl(jsonlRaw).ast, ocPath, {
74-kind: 'string',
75-value: 'x',
78+kind: "string",
79+value: "x",
7680}),
77-).not.toThrow();
81+).toEqual({
82+ok: false,
83+reason: "unresolved",
84+});
7885});
798680-it('P-04 inferKind aligns with the parser actually used', () => {
81-expect(inferKind('AGENTS.md')).toBe('md');
82-expect(inferKind('SOUL.md')).toBe('md');
83-expect(inferKind('config.jsonc')).toBe('jsonc');
84-expect(inferKind('plugins.json')).toBe('jsonc');
85-expect(inferKind('events.jsonl')).toBe('jsonl');
86-expect(inferKind('audit.ndjson')).toBe('jsonl');
87+it("P-04 inferKind aligns with the parser actually used", () => {
88+expect(inferKind("AGENTS.md")).toBe("md");
89+expect(inferKind("SOUL.md")).toBe("md");
90+expect(inferKind("config.jsonc")).toBe("jsonc");
91+expect(inferKind("plugins.json")).toBe("jsonc");
92+expect(inferKind("events.jsonl")).toBe("jsonl");
93+expect(inferKind("audit.ndjson")).toBe("jsonl");
8794});
889589-it('P-05 parse → emit → parse is fixpoint across all kinds', () => {
96+it("P-05 parse → emit → parse is fixpoint across all kinds", () => {
9097const md1 = emitMd(parseMd(mdRaw).ast);
9198const md2 = emitMd(parseMd(md1).ast);
9299expect(md1).toBe(md2);
@@ -100,54 +107,52 @@ describe('wave-22 cross-kind property invariants', () => {
100107expect(jl1).toBe(jl2);
101108});
102109103-it('P-06 hostile inputs do not throw at parse time across all kinds', () => {
110+it("P-06 hostile inputs do not throw at parse time across all kinds", () => {
104111const hostile = [
105-'\x00\x01\x02 binary garbage',
112+"\x00\x01\x02 binary garbage",
106113'{ "unclosed":',
107-'## heading without anything',
108-'\n\n\n\n\n',
114+"## heading without anything",
115+"\n\n\n\n\n",
109116];
110117for (const raw of hostile) {
111-expect(() => parseMd(raw)).not.toThrow();
112-expect(() => parseJsonc(raw)).not.toThrow();
113-expect(() => parseJsonl(raw)).not.toThrow();
118+expect(parseMd(raw).ast.raw).toBe(raw);
119+expect(
120+parseJsonc(raw).diagnostics.every((diagnostic) => diagnostic.severity === "error"),
121+).toBe(true);
122+expect(parseJsonl(raw).ast.raw).toBe(raw);
114123}
115124});
116125117-it('P-07 resolver returns null for paths past valid kinds (no throw)', () => {
118-const overlong = parseOcPath('oc://X/a/b/c.d.e.f.g.h');
119-expect(() => resolveMdOcPath(parseMd(mdRaw).ast, overlong)).not.toThrow();
120-expect(() => resolveJsoncOcPath(parseJsonc(jsoncRaw).ast, overlong)).not.toThrow();
121-expect(() => resolveJsonlOcPath(parseJsonl(jsonlRaw).ast, overlong)).not.toThrow();
126+it("P-07 resolver returns null for paths past valid kinds", () => {
127+const overlong = parseOcPath("oc://X/a/b/c.d.e.f.g.h");
128+expect(resolveMdOcPath(parseMd(mdRaw).ast, overlong)).toBeNull();
129+expect(resolveJsoncOcPath(parseJsonc(jsoncRaw).ast, overlong)).toBeNull();
130+expect(resolveJsonlOcPath(parseJsonl(jsonlRaw).ast, overlong)).toBeNull();
122131});
123132124-it('P-08 set-then-resolve produces the value just written (jsonc)', () => {
133+it("P-08 set-then-resolve produces the value just written (jsonc)", () => {
125134const ast = parseJsonc('{ "k": 1 }').ast;
126-const r = setJsoncOcPath(ast, parseOcPath('oc://X/k'), {
127-kind: 'number',
135+const r = setJsoncOcPath(ast, parseOcPath("oc://X/k"), {
136+kind: "number",
128137value: 42,
129138});
130139if (r.ok) {
131-const m = resolveJsoncOcPath(r.ast, parseOcPath('oc://X/k'));
132-if (m?.kind === 'object-entry') {
133-expect(m.node.value).toEqual({ kind: 'number', value: 42 });
140+const m = resolveJsoncOcPath(r.ast, parseOcPath("oc://X/k"));
141+if (m?.kind === "object-entry") {
142+expect(m.node.value).toEqual({ kind: "number", value: 42 });
134143}
135144}
136145});
137146138-it('P-09 verbs are deterministic — same input twice produces same output', () => {
147+it("P-09 verbs are deterministic — same input twice produces same output", () => {
139148expect(emitMd(parseMd(mdRaw).ast)).toBe(emitMd(parseMd(mdRaw).ast));
140-expect(emitJsonc(parseJsonc(jsoncRaw).ast)).toBe(
141-emitJsonc(parseJsonc(jsoncRaw).ast),
142-);
143-expect(emitJsonl(parseJsonl(jsonlRaw).ast)).toBe(
144-emitJsonl(parseJsonl(jsonlRaw).ast),
145-);
149+expect(emitJsonc(parseJsonc(jsoncRaw).ast)).toBe(emitJsonc(parseJsonc(jsoncRaw).ast));
150+expect(emitJsonl(parseJsonl(jsonlRaw).ast)).toBe(emitJsonl(parseJsonl(jsonlRaw).ast));
146151});
147152148-it('P-10 inferKind returns null for unknown extensions', () => {
149-expect(inferKind('binary.bin')).toBeNull();
150-expect(inferKind('no-ext')).toBeNull();
151-expect(inferKind('archive.tar.gz')).toBeNull();
153+it("P-10 inferKind returns null for unknown extensions", () => {
154+expect(inferKind("binary.bin")).toBeNull();
155+expect(inferKind("no-ext")).toBeNull();
156+expect(inferKind("archive.tar.gz")).toBeNull();
152157});
153158});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。