From 18d6399129312e779d4a4619f48933571ea1555c Mon Sep 17 00:00:00 2001 From: jaymarvelz Date: Sat, 25 Oct 2025 09:02:25 +0300 Subject: [PATCH 1/3] refactor: simplify `JSONRuleVisitor` --- src/types.ts | 47 ++++++++++++++++++----------------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/src/types.ts b/src/types.ts index e27067a..1b2a26e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -31,10 +31,19 @@ import type { import type { JSONLanguageOptions, JSONSourceCode } from "./index.js"; //------------------------------------------------------------------------------ -// Types +// Helpers //------------------------------------------------------------------------------ -type ValueNodeParent = DocumentNode | MemberNode | ElementNode; +/** Adds matching `:exit` selectors for all properties of a `RuleVisitor`. */ +type WithExit = { + [Key in keyof RuleVisitorType as + | Key + | `${Key & string}:exit`]: RuleVisitorType[Key]; +}; + +//------------------------------------------------------------------------------ +// Exports +//------------------------------------------------------------------------------ /** * A JSON syntax element, including nodes and tokens. @@ -44,33 +53,13 @@ export type JSONSyntaxElement = Token | AnyNode; /** * The visitor format returned from rules in this package. */ -export interface JSONRuleVisitor extends RuleVisitor { - Document?(node: DocumentNode): void; - Member?(node: MemberNode, parent?: ObjectNode): void; - Element?(node: ElementNode, parent?: ArrayNode): void; - Object?(node: ObjectNode, parent?: ValueNodeParent): void; - Array?(node: ArrayNode, parent?: ValueNodeParent): void; - String?(node: StringNode, parent?: ValueNodeParent): void; - Null?(node: NullNode, parent?: ValueNodeParent): void; - Number?(node: NumberNode, parent?: ValueNodeParent): void; - Boolean?(node: BooleanNode, parent?: ValueNodeParent): void; - NaN?(node: NaNNode, parent?: ValueNodeParent): void; - Infinity?(node: InfinityNode, parent?: ValueNodeParent): void; - Identifier?(node: IdentifierNode, parent?: ValueNodeParent): void; - - "Document:exit"?(node: DocumentNode): void; - "Member:exit"?(node: MemberNode, parent?: ObjectNode): void; - "Element:exit"?(node: ElementNode, parent?: ArrayNode): void; - "Object:exit"?(node: ObjectNode, parent?: ValueNodeParent): void; - "Array:exit"?(node: ArrayNode, parent?: ValueNodeParent): void; - "String:exit"?(node: StringNode, parent?: ValueNodeParent): void; - "Null:exit"?(node: NullNode, parent?: ValueNodeParent): void; - "Number:exit"?(node: NumberNode, parent?: ValueNodeParent): void; - "Boolean:exit"?(node: BooleanNode, parent?: ValueNodeParent): void; - "NaN:exit"?(node: NaNNode, parent?: ValueNodeParent): void; - "Infinity:exit"?(node: InfinityNode, parent?: ValueNodeParent): void; - "Identifier:exit"?(node: IdentifierNode, parent?: ValueNodeParent): void; -} +export interface JSONRuleVisitor + extends RuleVisitor, + WithExit<{ + [Node in AnyNode as Node["type"]]?: + | ((node: Node) => void) + | undefined; + }> {} export type JSONRuleDefinitionTypeOptions = CustomRuleTypeDefinitions; From 68a17c9f6d297e0094fec93f03486a359df4dc14 Mon Sep 17 00:00:00 2001 From: jaymarvelz Date: Sat, 25 Oct 2025 09:42:31 +0300 Subject: [PATCH 2/3] chore: clean up test visitor function --- tests/types/types.test.ts | 62 ++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/tests/types/types.test.ts b/tests/types/types.test.ts index 0bc3c42..1ccefc4 100644 --- a/tests/types/types.test.ts +++ b/tests/types/types.test.ts @@ -74,15 +74,7 @@ json.configs.recommended.plugins satisfies object; sourceCode.lines satisfies string[]; sourceCode.text satisfies string; - function testVisitor( - node: NodeType, - parent?: - | DocumentNode - | MemberNode - | ElementNode - | ArrayNode - | ObjectNode, - ) { + function testVisitor(node: NodeType) { sourceCode.getLoc(node) satisfies SourceLocation; sourceCode.getLocFromIndex(0) satisfies { line: number; @@ -100,31 +92,33 @@ json.configs.recommended.plugins satisfies object; } return { - Array: (...args) => testVisitor(...args), - "Array:exit": (...args) => testVisitor(...args), - Boolean: (...args) => testVisitor(...args), - "Boolean:exit": (...args) => testVisitor(...args), - Document: (...args) => testVisitor(...args), - "Document:exit": (...args) => testVisitor(...args), - Element: (...args) => testVisitor(...args), - "Element:exit": (...args) => testVisitor(...args), - Identifier: (...args) => testVisitor(...args), - "Identifier:exit": (...args) => - testVisitor(...args), - Infinity: (...args) => testVisitor(...args), - "Infinity:exit": (...args) => testVisitor(...args), - Member: (...args) => testVisitor(...args), - "Member:exit": (...args) => testVisitor(...args), - NaN: (...args) => testVisitor(...args), - "NaN:exit": (...args) => testVisitor(...args), - Null: (...args) => testVisitor(...args), - "Null:exit": (...args) => testVisitor(...args), - Number: (...args) => testVisitor(...args), - "Number:exit": (...args) => testVisitor(...args), - Object: (...args) => testVisitor(...args), - "Object:exit": (...args) => testVisitor(...args), - String: (...args) => testVisitor(...args), - "String:exit": (...args) => testVisitor(...args), + Array: node => testVisitor(node), + "Array:exit": node => testVisitor(node), + Boolean: node => testVisitor(node), + "Boolean:exit": node => testVisitor(node), + Document: node => testVisitor(node), + "Document:exit": node => testVisitor(node), + Element: node => testVisitor(node), + "Element:exit": node => testVisitor(node), + Identifier: node => testVisitor(node), + "Identifier:exit": node => testVisitor(node), + Infinity: node => testVisitor(node), + "Infinity:exit": node => testVisitor(node), + Member: node => testVisitor(node), + "Member:exit": node => testVisitor(node), + NaN: node => testVisitor(node), + "NaN:exit": node => testVisitor(node), + Null: node => testVisitor(node), + "Null:exit": node => testVisitor(node), + Number: node => testVisitor(node), + "Number:exit": node => testVisitor(node), + Object: node => testVisitor(node), + "Object:exit": node => testVisitor(node), + String: node => testVisitor(node), + "String:exit": node => testVisitor(node), + + // Unknown selectors allowed + "Identifier[name=foo]"(node) {}, }; }, }); From 022422951c1409e866532501c702248ac5268f61 Mon Sep 17 00:00:00 2001 From: jaymarvelz Date: Wed, 29 Oct 2025 14:51:26 +0300 Subject: [PATCH 3/3] fix: correct parent typing for rule visitors --- src/types.ts | 33 ++++++++++++++++----- tests/types/types.test.ts | 61 ++++++++++++++++++++++----------------- 2 files changed, 61 insertions(+), 33 deletions(-) diff --git a/src/types.ts b/src/types.ts index 1b2a26e..34f62b6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -42,24 +42,43 @@ type WithExit = { }; //------------------------------------------------------------------------------ -// Exports +// Types //------------------------------------------------------------------------------ +type ValueNodeParent = DocumentNode | MemberNode | ElementNode; + /** * A JSON syntax element, including nodes and tokens. */ export type JSONSyntaxElement = Token | AnyNode; +type JSONNodeVisitor = { + Array?: ((node: ArrayNode, parent: ValueNodeParent) => void) | undefined; + Boolean?: + | ((node: BooleanNode, parent: ValueNodeParent) => void) + | undefined; + Document?: ((node: DocumentNode) => void) | undefined; + Element?: ((node: ElementNode, parent: ArrayNode) => void) | undefined; + Identifier?: + | ((node: IdentifierNode, parent: MemberNode) => void) + | undefined; + Infinity?: + | ((node: InfinityNode, parent: ValueNodeParent) => void) + | undefined; + Member?: ((node: MemberNode, parent: ObjectNode) => void) | undefined; + NaN?: ((node: NaNNode, parent: ValueNodeParent) => void) | undefined; + Null?: ((node: NullNode, parent: ValueNodeParent) => void) | undefined; + Number?: ((node: NumberNode, parent: ValueNodeParent) => void) | undefined; + Object?: ((node: ObjectNode, parent: ValueNodeParent) => void) | undefined; + String?: ((node: StringNode, parent: ValueNodeParent) => void) | undefined; +}; + /** * The visitor format returned from rules in this package. */ export interface JSONRuleVisitor extends RuleVisitor, - WithExit<{ - [Node in AnyNode as Node["type"]]?: - | ((node: Node) => void) - | undefined; - }> {} + WithExit {} export type JSONRuleDefinitionTypeOptions = CustomRuleTypeDefinitions; @@ -70,7 +89,7 @@ export type JSONRuleDefinition< LangOptions: JSONLanguageOptions; Code: JSONSourceCode; Visitor: JSONRuleVisitor; - Node: AnyNode; + Node: JSONSyntaxElement; }, Options >; diff --git a/tests/types/types.test.ts b/tests/types/types.test.ts index 1ccefc4..1cd5cc9 100644 --- a/tests/types/types.test.ts +++ b/tests/types/types.test.ts @@ -74,7 +74,15 @@ json.configs.recommended.plugins satisfies object; sourceCode.lines satisfies string[]; sourceCode.text satisfies string; - function testVisitor(node: NodeType) { + function testVisitor( + node: NodeType, + parent?: + | DocumentNode + | MemberNode + | ElementNode + | ArrayNode + | ObjectNode, + ) { sourceCode.getLoc(node) satisfies SourceLocation; sourceCode.getLocFromIndex(0) satisfies { line: number; @@ -92,33 +100,34 @@ json.configs.recommended.plugins satisfies object; } return { - Array: node => testVisitor(node), - "Array:exit": node => testVisitor(node), - Boolean: node => testVisitor(node), - "Boolean:exit": node => testVisitor(node), - Document: node => testVisitor(node), - "Document:exit": node => testVisitor(node), - Element: node => testVisitor(node), - "Element:exit": node => testVisitor(node), - Identifier: node => testVisitor(node), - "Identifier:exit": node => testVisitor(node), - Infinity: node => testVisitor(node), - "Infinity:exit": node => testVisitor(node), - Member: node => testVisitor(node), - "Member:exit": node => testVisitor(node), - NaN: node => testVisitor(node), - "NaN:exit": node => testVisitor(node), - Null: node => testVisitor(node), - "Null:exit": node => testVisitor(node), - Number: node => testVisitor(node), - "Number:exit": node => testVisitor(node), - Object: node => testVisitor(node), - "Object:exit": node => testVisitor(node), - String: node => testVisitor(node), - "String:exit": node => testVisitor(node), + Array: (...args) => testVisitor(...args), + "Array:exit": (...args) => testVisitor(...args), + Boolean: (...args) => testVisitor(...args), + "Boolean:exit": (...args) => testVisitor(...args), + Document: (...args) => testVisitor(...args), + "Document:exit": (...args) => testVisitor(...args), + Element: (...args) => testVisitor(...args), + "Element:exit": (...args) => testVisitor(...args), + Identifier: (...args) => testVisitor(...args), + "Identifier:exit": (...args) => + testVisitor(...args), + Infinity: (...args) => testVisitor(...args), + "Infinity:exit": (...args) => testVisitor(...args), + Member: (...args) => testVisitor(...args), + "Member:exit": (...args) => testVisitor(...args), + NaN: (...args) => testVisitor(...args), + "NaN:exit": (...args) => testVisitor(...args), + Null: (...args) => testVisitor(...args), + "Null:exit": (...args) => testVisitor(...args), + Number: (...args) => testVisitor(...args), + "Number:exit": (...args) => testVisitor(...args), + Object: (...args) => testVisitor(...args), + "Object:exit": (...args) => testVisitor(...args), + String: (...args) => testVisitor(...args), + "String:exit": (...args) => testVisitor(...args), // Unknown selectors allowed - "Identifier[name=foo]"(node) {}, + "Identifier[name=foo]"(node: IdentifierNode, parent: MemberNode) {}, }; }, });