From e4857cb136ddd1a601f02460006f06816e81f8fd Mon Sep 17 00:00:00 2001 From: markw65 Date: Fri, 8 Sep 2023 16:23:10 -0700 Subject: [PATCH 1/4] Better type checking for visitor --- test/types/peg.test-d.ts | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/test/types/peg.test-d.ts b/test/types/peg.test-d.ts index f4cac1cb..58c791e1 100644 --- a/test/types/peg.test-d.ts +++ b/test/types/peg.test-d.ts @@ -184,13 +184,18 @@ describe("peg.d.ts", () => { it("creates an AST", () => { const grammar = peggy.parser.parse(src); expectExact()(grammar)(); - const visited: { [typ: string]: number } = {}; - function add(typ: string): void { - if (!visited[typ]) { - visited[typ] = 1; - } else { - visited[typ]++; - } + type AstTypes = ( + peggy.ast.Expression | + peggy.ast.Grammar | + peggy.ast.Initializer | + peggy.ast.Named | + peggy.ast.Rule | + peggy.ast.TopLevelInitializer + )["type"]; + const visited: { [typ in AstTypes]?: number } = {}; + function add(typ: AstTypes): void { + const v = visited[typ] || 0; + visited[typ] = v + 1; } const visit = peggy.compiler.visitor.build({ @@ -446,7 +451,7 @@ describe("peg.d.ts", () => { visit(grammar); - expect(Object.keys(visited).sort()).toStrictEqual([ + const astKeys = [ "action", "any", "choice", @@ -470,7 +475,12 @@ describe("peg.d.ts", () => { "text", "top_level_initializer", "zero_or_more", - ]); + ] as const; + + expect(Object.keys(visited).sort()).toStrictEqual(astKeys); + for (const key of astKeys) { + expectType(key); + } }); it("compiles", () => { From 62c413a0a834afaa024359d422d195308c86ef0e Mon Sep 17 00:00:00 2001 From: markw65 Date: Fri, 8 Sep 2023 18:28:35 -0700 Subject: [PATCH 2/4] Even more compile time checking --- test/types/peg.test-d.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/test/types/peg.test-d.ts b/test/types/peg.test-d.ts index 58c791e1..62804fb0 100644 --- a/test/types/peg.test-d.ts +++ b/test/types/peg.test-d.ts @@ -449,6 +449,17 @@ describe("peg.d.ts", () => { }, }); + // Extract the visitor object + type VisitorArg + = typeof visit extends peggy.compiler.visitor.Visitor + ? U : never; + + // Extract the functions that don't return `any` + type DefinedKeys = keyof { + [K in keyof VisitorArg as VisitorArg[K] extends (...args: any) => any + ? unknown extends ReturnType ? never : K : never]: true + }; + visit(grammar); const astKeys = [ @@ -475,12 +486,11 @@ describe("peg.d.ts", () => { "text", "top_level_initializer", "zero_or_more", - ] as const; + ] satisfies AstTypes[]; expect(Object.keys(visited).sort()).toStrictEqual(astKeys); - for (const key of astKeys) { - expectType(key); - } + expectType(astKeys); + expectType(astKeys); }); it("compiles", () => { From 07019ee05a9b6e5363ed0be0362224234cdf8c75 Mon Sep 17 00:00:00 2001 From: markw65 Date: Sat, 9 Sep 2023 07:05:54 -0700 Subject: [PATCH 3/4] cleaner --- lib/peg.d.ts | 9 +++++++++ test/types/peg.test-d.ts | 29 ++++++++--------------------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/lib/peg.d.ts b/lib/peg.d.ts index 7cacf822..0cca1235 100644 --- a/lib/peg.d.ts +++ b/lib/peg.d.ts @@ -68,6 +68,15 @@ declare namespace ast { | GrammarCharacterClass & { type: "class" } ; + type AllNodes = + | Expression + | Grammar + | Initializer + | Named + | Rule + | TopLevelInitializer + ; + /** The main Peggy AST class returned by the parser. */ interface Grammar extends Node<"grammar"> { /** Initializer that run once when importing generated parser module. */ diff --git a/test/types/peg.test-d.ts b/test/types/peg.test-d.ts index 62804fb0..4e25eaf7 100644 --- a/test/types/peg.test-d.ts +++ b/test/types/peg.test-d.ts @@ -184,14 +184,7 @@ describe("peg.d.ts", () => { it("creates an AST", () => { const grammar = peggy.parser.parse(src); expectExact()(grammar)(); - type AstTypes = ( - peggy.ast.Expression | - peggy.ast.Grammar | - peggy.ast.Initializer | - peggy.ast.Named | - peggy.ast.Rule | - peggy.ast.TopLevelInitializer - )["type"]; + type AstTypes = peggy.ast.AllNodes["type"]; const visited: { [typ in AstTypes]?: number } = {}; function add(typ: AstTypes): void { const v = visited[typ] || 0; @@ -449,16 +442,10 @@ describe("peg.d.ts", () => { }, }); - // Extract the visitor object - type VisitorArg - = typeof visit extends peggy.compiler.visitor.Visitor - ? U : never; - - // Extract the functions that don't return `any` - type DefinedKeys = keyof { - [K in keyof VisitorArg as VisitorArg[K] extends (...args: any) => any - ? unknown extends ReturnType ? never : K : never]: true - }; + // Extract the keys from the visitor object + type DefinedKeys + = typeof visit extends peggy.compiler.visitor.Visitor + ? keyof U : never; visit(grammar); @@ -486,11 +473,11 @@ describe("peg.d.ts", () => { "text", "top_level_initializer", "zero_or_more", - ] satisfies AstTypes[]; + ] satisfies AstTypes[] satisfies DefinedKeys[]; expect(Object.keys(visited).sort()).toStrictEqual(astKeys); - expectType(astKeys); - expectType(astKeys); + expectExact()(astKeys)(); + expectExact()(astKeys)(); }); it("compiles", () => { From 484d322968b0eb9b12758397608df534dda2dd72 Mon Sep 17 00:00:00 2001 From: markw65 Date: Sat, 9 Dec 2023 07:38:05 -0800 Subject: [PATCH 4/4] Add a CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e7b501e..c3895a4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Released: TBD ### Minor Changes +- [#437](https://github.com/peggyjs/peggy/pull/437) Better type checking for visitor - [#435](https://github.com/peggyjs/peggy/pull/435) Setup tsconfig to detect use of library functions from es6 or later - [#438](https://github.com/peggyjs/peggy/pull/438) Make test build deterministic - [#436](https://github.com/peggyjs/peggy/pull/436) Get rid of tsd