// ─────────────────────────────────────────────────────────────────────────────
// VISITOR [Behavioral]
// ─────────────────────────────────────────────────────────────────────────────
// FIRST VISIT : From Session 2.2, read the table in section 0 and stop. Run its code after Session 3.4;
// sections 1 and 2 are Unit 9.
// INTENT : Represent an operation to be performed on the elements of an object structure; define a new
// operation without changing the classes of the elements on which it operates.
// PROBLEM : A document tree (Heading, Paragraph, Image, Section) needs render-to-HTML, render-to-Markdown,
// word count, link extraction... Adding each as a method on every node class bloats the nodes and
// smears one feature across many files.
// SAY : Participants: Visitor (visitX() per element type), ConcreteVisitor (one operation), Element
// (accept(visitor)), ConcreteElement (calls visitor.visitX(this) — "double dispatch"),
// ObjectStructure (the tree). Pitch: "new operations over a stable set of node types, without
// touching the nodes."
// SEEN IN : Babel / TypeScript compiler AST traversal (`traverse(ast, { Identifier(path) {} })`); ESLint rules
// (objects keyed by node type); GraphQL-js `visit(ast, visitor)`; PostCSS `walkRules/walkDecls`;
// Prettier printers (print by node type); `JSON.parse(s, reviver)` / `JSON.stringify(v, replacer)`
// — a callback visiting every node (-ish).
// VS : Iterator yields elements and you do the work outside; Visitor dispatches on element TYPE and the
// work lives in the visitor. Interpreter puts the ONE operation on the nodes; Visitor pulls operations
// OUT — pick Visitor when ops change often, Interpreter/Composite when node types do.
// ─────────────────────────────────────────────────────────────────────────────
'use strict';
const assert = require('node:assert/strict');
// ── 0. Start here: three elements, two visitors
// Session 2.2 showed the DevTools Styles panel: pick one element and the panel walks the rules that matched
// it, asking each rule the same question ("what do you set?"). Visitor is that walk, written as code: the element
// is the rule the panel is looking at, the visitor is the question it asks every rule.
//
// Here the document is three elements and two visitors ask them questions. The element's kind picks which method
// runs; the visitor supplies what that method answers. Same three elements, two visitors, two sets of answers:
//
// element method called toText returns wordCount returns
// heading visitHeading 'GUILD NOTICES' 2
// paragraph visitParagraph 'Two quests remain open tonight.' 5
// image visitImage '[quest board]' 0
//
// Stop here on your first visit from Session 2.2; that table is the visit. Come back after Session 3.4 to run the
// code below, which builds the table and checks it. Sections 1 and 2 are the interview shapes for Unit 9.
const page = [ // a literal three-element document
{ kind: 'heading', text: 'Guild notices' },
{ kind: 'paragraph', text: 'Two quests remain open tonight.' },
{ kind: 'image', alt: 'quest board' },
];
const toText = { // visitor 1: one method per element kind
visitHeading: (n) => n.text.toUpperCase(),
visitParagraph: (n) => n.text,
visitImage: (n) => `[${n.alt}]`,
};
const wordCount = { // visitor 2: the same three method names, different answers
visitHeading: (n) => n.text.split(' ').length,
visitParagraph: (n) => n.text.split(' ').length,
visitImage: () => 0,
};
const methodFor = { heading: 'visitHeading', paragraph: 'visitParagraph', image: 'visitImage' };
const accept = (node, visitor) => visitor[methodFor[node.kind]](node); // the element picks the method by its kind
assert.deepEqual(page.map((n) => accept(n, toText)), ['GUILD NOTICES', 'Two quests remain open tonight.', '[quest board]']);
assert.deepEqual(page.map((n) => accept(n, wordCount)), [2, 5, 0]);
// Two choices decide what runs: the element's kind picks the method, the visitor object supplies the body.
// Two objects dispatching together is what the sections below call "double dispatch".
// ── 1. Classic shape (classes, so the participant names are visible)
// ConcreteElements — each accept() dispatches to the matching visit method (double dispatch)
class Heading { constructor(level, text) { this.level = level; this.text = text; } accept(v) { return v.visitHeading(this); } }
class Paragraph { constructor(text) { this.text = text; } accept(v) { return v.visitParagraph(this); } }
class Image { constructor(src, alt) { this.src = src; this.alt = alt; } accept(v) { return v.visitImage(this); } }
class Section { // composite element — still just accept()
constructor(...children) { this.children = children; }
accept(v) { return v.visitSection(this); }
}
class HtmlRenderer { // ConcreteVisitor
visitHeading(h) { return `
${p.text}
`; } visitImage(i) { return `Faster builds and fewer bugs.
' + '
Node 18 dropped.