From 4c2281a85b16574f98f8f8150682f923e7ffc0bf Mon Sep 17 00:00:00 2001 From: Yongwook Choi Date: Thu, 3 Mar 2022 13:40:11 +0900 Subject: [PATCH] Implement visitor for printer (linting) (#7) Co-authored-by: Jongchan Choi --- ast.ts | 2 +- compile/js-url-checker.ts | 2 +- stringifier/formatter.test.ts | 100 +++++ stringifier/formatter.ts | 118 +++++ stringifier/printer.ts | 39 ++ test.json | 780 ++++++++++++++++++++++++++++++++++ visitor/index.ts | 148 +++++++ 7 files changed, 1187 insertions(+), 2 deletions(-) create mode 100644 stringifier/formatter.test.ts create mode 100644 stringifier/formatter.ts create mode 100644 stringifier/printer.ts create mode 100644 test.json create mode 100644 visitor/index.ts diff --git a/ast.ts b/ast.ts index 046a492..8b3cc56 100644 --- a/ast.ts +++ b/ast.ts @@ -55,7 +55,7 @@ export interface Key { export interface TailRuleFormPatternRule { comment?: Token; key: Key; - value: TailRulePatternValue[]; + value: TailRulePatternValue[] | null; array: boolean; } export interface TailRulePatternValue { diff --git a/compile/js-url-checker.ts b/compile/js-url-checker.ts index 2c77570..ce3133b 100644 --- a/compile/js-url-checker.ts +++ b/compile/js-url-checker.ts @@ -166,7 +166,7 @@ export function compile( write(`{ const values = form.getAll(${compileKeyToken(key)}); if (values.every( - value => ${compileTailRulePattern("value", value)} + value => ${compileTailRulePattern("value", value ?? [])} )) { formResult[${compileKeyToken(key)}] = values; } else { diff --git a/stringifier/formatter.test.ts b/stringifier/formatter.test.ts new file mode 100644 index 0000000..3ce8235 --- /dev/null +++ b/stringifier/formatter.test.ts @@ -0,0 +1,100 @@ +import { parse } from "../index.ts"; +import { assertEquals } from "https://deno.land/std@0.127.0/testing/asserts.ts"; +import format from "./formatter.ts"; + +Deno.test("formatter", () => { + const fixture = ` +scheme:example1.com/foo/bar/baz { + ? match + /web-path=(?.*)/ + # match id-1 + | id-2 + | /id-3/ +} + +/** + * foo + */ +scheme:example2.com/path/[param1]/[param2] { + /** + * bar + */ +? form { + /** + * baz + */ + '' = '' +a + b + param1 = exact | match | value + param2 = /^[0-9]+$/ + param3 [] = /^[a-z]+$/i + } + # match /id/ +} + +scheme:username@example3.com:4321 { + ? form {'a' = /.*/ + 'b' = /.*/ + 'c-d' = /.*/ + '/' = /.*/ + '?' = /.*/ + 'f/' = /.*/ + 'g' = /.*/ + 'h' = /.*/ + } +}`; + + const schema = parse(fixture); + if (!schema) throw new Error("No schema"); + const formatted = format(schema); + console.log(formatted); + assertEquals( + formatted, + `scheme:example1.com/foo/bar/baz { + ? match /web-path=(?.*)/ + + # match + | id-1 + | id-2 + | /id-3/ +} + +/** + * foo + */ +scheme:example2.com/path/[param1]/[param2] { + /** + * bar + */ + ? form { + /** + * baz + */ + '' = '' + a + b + param1 = exact | match | value + param2 = /^[0-9]+$/ + param3 [] = /^[a-z]+$/i + } + + # match /id/ +} + +scheme:username@example3.com:4321 { + ? form { + 'a' = /.*/ + 'b' = /.*/ + 'c-d' = /.*/ + '/' = /.*/ + '?' = /.*/ + 'f/' = /.*/ + 'g' = /.*/ + 'h' = /.*/ + } +} + +`, + ); +}); diff --git a/stringifier/formatter.ts b/stringifier/formatter.ts new file mode 100644 index 0000000..3247a48 --- /dev/null +++ b/stringifier/formatter.ts @@ -0,0 +1,118 @@ +import * as ast from "../ast.ts"; +import { Visitor, visitor as defaultVisitor } from "../visitor/index.ts"; +import { createPrinter } from "./printer.ts"; + +export default function format(ast: ast.Urichk): string { + const printer = createPrinter(); + const formatter: Visitor = { + ...defaultVisitor, + visitToken(visitor, node) { + printer.print(node.text); + }, + visitHead(visitor, node) { + defaultVisitor.visitHead(visitor, node); + printer.print(" "); + }, + visitUserinfo(visitor, node) { + defaultVisitor.visitUserinfo(visitor, node); + printer.print("@"); + }, + visitScheme(visitor, node) { + defaultVisitor.visitScheme(visitor, node); + printer.print(":"); + }, + visitPort(visitor, node) { + printer.print(":"); + defaultVisitor.visitPort(visitor, node); + }, + visitPathFragment(visitor, node) { + printer.print("/"); + defaultVisitor.visitPathFragment(visitor, node); + }, + visitParamPathFragment(visitor, node) { + printer.print("["); + defaultVisitor.visitParamPathFragment(visitor, node); + printer.print("]"); + }, + visitTail(visitor, node) { + printer.print("{"); + printer.indent(); + for (const rule of node) { + printer.print("\n"); + visitor.visitTailRule(visitor, rule); + printer.print("\n"); + } + printer.dedent(); + printer.println("}\n"); + }, + visitTailRule(visitor, node) { + printer.printIndent(); + defaultVisitor.visitTailRule(visitor, node); + }, + visitTailRuleMatch(visitor, node) { + const { matchType, pattern } = node; + visitor.visitMatchType(visitor, matchType); + if (pattern.length > 1) { + printer.indent(); + for (const value of pattern) { + printer.print("\n"); + printer.printIndent(); + printer.print("| "); + visitor.visitTailRulePatternValue(visitor, value); + } + printer.dedent(); + } else { + for (const value of pattern) { + visitor.visitTailRulePatternValue(visitor, value); + } + } + }, + visitTailRuleForm(visitor, node) { + const { matchType, pattern } = node; + visitor.visitMatchType(visitor, matchType); + printer.print("{\n"); + printer.indent(); + for (const rule of pattern) { + visitor.visitTailRuleFormPatternRule(visitor, rule); + } + printer.dedent(); + printer.printIndent(); + printer.print("}"); + }, + visitTailRuleFormPatternRule(visitor, node) { + printer.printIndent(); + const { comment, key, value, array } = node; + if (comment) visitor.visitComment(visitor, comment); + visitor.visitKey(visitor, key); + visitor.visitArrayToken(visitor, array); + if (value) { + printer.print(" "); + printer.print("="); + printer.print(" "); + value.forEach((patternValue, index) => { + if (index > 0) printer.print(" | "); + visitor.visitTailRulePatternValue(visitor, patternValue); + }); + } + printer.print("\n"); + }, + visitArrayToken(visitor, node) { + if (node === true) printer.print(" []"); + }, + visitTailType(visitor, node) { + defaultVisitor.visitTailType(visitor, node); + printer.print(" "); + }, + visitMatchType(visitor, node) { + defaultVisitor.visitMatchType(visitor, node); + printer.print(" "); + }, + visitComment(visitor, node) { + defaultVisitor.visitComment(visitor, node); + printer.print("\n"); + printer.printIndent(); + }, + }; + formatter.visitUrichk(formatter, ast); + return printer.done(); +} diff --git a/stringifier/printer.ts b/stringifier/printer.ts new file mode 100644 index 0000000..3a53cbf --- /dev/null +++ b/stringifier/printer.ts @@ -0,0 +1,39 @@ +export interface Printer { + indent(): void; + dedent(): void; + print(text: string): void; + printIndent(): void; + println(line: string): void; + done(): string; +} + +export interface CreatePrinterConfig { + space?: string; +} +export function createPrinter(config?: CreatePrinterConfig): Printer { + const { space = " " } = { ...config }; + const buffer: string[] = []; + let _indent = 0; + return { + indent() { + ++_indent; + }, + dedent() { + --_indent; + }, + print(text) { + buffer.push(text); + }, + printIndent() { + buffer.push(space.repeat(_indent)); + }, + println(line) { + buffer.push(space.repeat(_indent)); + buffer.push(line); + buffer.push("\n"); + }, + done() { + return buffer.join(""); + }, + }; +} diff --git a/test.json b/test.json new file mode 100644 index 0000000..aca0cd5 --- /dev/null +++ b/test.json @@ -0,0 +1,780 @@ +[ + { + "head": { + "scheme": { + "type": "id", + "value": "scheme", + "text": "scheme", + "offset": 1, + "lineBreaks": 0, + "line": 2, + "col": 1 + }, + "authority": { + "userinfo": null, + "host": { + "type": "id", + "value": "example1.com", + "text": "example1.com", + "offset": 8, + "lineBreaks": 0, + "line": 2, + "col": 8 + }, + "port": null + }, + "path": [ + { + "type": "static", + "name": { + "type": "id", + "value": "foo", + "text": "foo", + "offset": 21, + "lineBreaks": 0, + "line": 2, + "col": 21 + } + }, + { + "type": "static", + "name": { + "type": "id", + "value": "bar", + "text": "bar", + "offset": 25, + "lineBreaks": 0, + "line": 2, + "col": 25 + } + }, + { + "type": "static", + "name": { + "type": "id", + "value": "baz", + "text": "baz", + "offset": 29, + "lineBreaks": 0, + "line": 2, + "col": 29 + } + } + ] + }, + "tail": [ + { + "tailType": { + "type": "symbol", + "value": "?", + "text": "?", + "offset": 37, + "lineBreaks": 0, + "line": 3, + "col": 3 + }, + "matchType": { + "type": "id", + "value": "match", + "text": "match", + "offset": 39, + "lineBreaks": 0, + "line": 3, + "col": 5 + }, + "pattern": [ + { + "type": "regex", + "value": { + "type": "regex", + "value": "/web-path=(?.*)/", + "text": "/web-path=(?.*)/", + "offset": 45, + "lineBreaks": 0, + "line": 3, + "col": 11 + } + } + ] + }, + { + "tailType": { + "type": "symbol", + "value": "#", + "text": "#", + "offset": 70, + "lineBreaks": 0, + "line": 4, + "col": 3 + }, + "matchType": { + "type": "id", + "value": "match", + "text": "match", + "offset": 72, + "lineBreaks": 0, + "line": 4, + "col": 5 + }, + "pattern": [ + { + "type": "id", + "value": { + "type": "id", + "value": "id-1", + "text": "id-1", + "offset": 84, + "lineBreaks": 0, + "line": 5, + "col": 7 + } + }, + { + "type": "id", + "value": { + "type": "id", + "value": "id-2", + "text": "id-2", + "offset": 95, + "lineBreaks": 0, + "line": 6, + "col": 7 + } + }, + { + "type": "regex", + "value": { + "type": "regex", + "value": "/id-3/", + "text": "/id-3/", + "offset": 106, + "lineBreaks": 0, + "line": 7, + "col": 7 + } + } + ] + } + ] + }, + { + "comment": { + "type": "mc", + "value": "/**\n * foo\n */", + "text": "/**\n * foo\n */", + "offset": 116, + "lineBreaks": 0, + "line": 10, + "col": 1 + }, + "head": { + "scheme": { + "type": "id", + "value": "scheme", + "text": "scheme", + "offset": 131, + "lineBreaks": 0, + "line": 11, + "col": 1 + }, + "authority": { + "userinfo": null, + "host": { + "type": "id", + "value": "example2.com", + "text": "example2.com", + "offset": 138, + "lineBreaks": 0, + "line": 11, + "col": 8 + }, + "port": null + }, + "path": [ + { + "type": "static", + "name": { + "type": "id", + "value": "path", + "text": "path", + "offset": 151, + "lineBreaks": 0, + "line": 11, + "col": 21 + } + }, + { + "type": "param", + "name": { + "type": "id", + "value": "param1", + "text": "param1", + "offset": 157, + "lineBreaks": 0, + "line": 11, + "col": 27 + } + }, + { + "type": "param", + "name": { + "type": "id", + "value": "param2", + "text": "param2", + "offset": 166, + "lineBreaks": 0, + "line": 11, + "col": 36 + } + } + ] + }, + "tail": [ + { + "comment": { + "type": "mc", + "value": "/**\n * bar\n */", + "text": "/**\n * bar\n */", + "offset": 178, + "lineBreaks": 0, + "line": 12, + "col": 3 + }, + "tailType": { + "type": "symbol", + "value": "?", + "text": "?", + "offset": 199, + "lineBreaks": 0, + "line": 13, + "col": 3 + }, + "matchType": { + "type": "id", + "value": "form", + "text": "form", + "offset": 201, + "lineBreaks": 0, + "line": 13, + "col": 5 + }, + "pattern": [ + { + "comment": { + "type": "mc", + "value": "/**\n * baz\n */", + "text": "/**\n * baz\n */", + "offset": 212, + "lineBreaks": 0, + "line": 14, + "col": 5 + }, + "key": { + "type": "string", + "value": { + "type": "string", + "value": "''", + "text": "''", + "offset": 239, + "lineBreaks": 0, + "line": 15, + "col": 5 + } + }, + "value": [ + { + "type": "string", + "value": { + "type": "string", + "value": "''", + "text": "''", + "offset": 244, + "lineBreaks": 0, + "line": 15, + "col": 10 + } + } + ], + "array": false + }, + { + "key": { + "type": "id", + "value": { + "type": "id", + "value": "a", + "text": "a", + "offset": 251, + "lineBreaks": 0, + "line": 16, + "col": 5 + } + }, + "value": null, + "array": false + }, + { + "key": { + "type": "id", + "value": { + "type": "id", + "value": "b", + "text": "b", + "offset": 257, + "lineBreaks": 0, + "line": 17, + "col": 5 + } + }, + "value": null, + "array": false + }, + { + "key": { + "type": "id", + "value": { + "type": "id", + "value": "param1", + "text": "param1", + "offset": 263, + "lineBreaks": 0, + "line": 18, + "col": 5 + } + }, + "value": [ + { + "type": "id", + "value": { + "type": "id", + "value": "exact", + "text": "exact", + "offset": 272, + "lineBreaks": 0, + "line": 18, + "col": 14 + } + }, + { + "type": "id", + "value": { + "type": "id", + "value": "match", + "text": "match", + "offset": 280, + "lineBreaks": 0, + "line": 18, + "col": 22 + } + }, + { + "type": "id", + "value": { + "type": "id", + "value": "value", + "text": "value", + "offset": 288, + "lineBreaks": 0, + "line": 18, + "col": 30 + } + } + ], + "array": false + }, + { + "key": { + "type": "id", + "value": { + "type": "id", + "value": "param2", + "text": "param2", + "offset": 298, + "lineBreaks": 0, + "line": 19, + "col": 5 + } + }, + "value": [ + { + "type": "regex", + "value": { + "type": "regex", + "value": "/^[0-9]+$/", + "text": "/^[0-9]+$/", + "offset": 307, + "lineBreaks": 0, + "line": 19, + "col": 14 + } + } + ], + "array": false + }, + { + "key": { + "type": "id", + "value": { + "type": "id", + "value": "param3", + "text": "param3", + "offset": 322, + "lineBreaks": 0, + "line": 20, + "col": 5 + } + }, + "value": [ + { + "type": "regex", + "value": { + "type": "regex", + "value": "/^[a-z]+$/i", + "text": "/^[a-z]+$/i", + "offset": 334, + "lineBreaks": 0, + "line": 20, + "col": 17 + } + } + ], + "array": true + } + ] + }, + { + "tailType": { + "type": "symbol", + "value": "#", + "text": "#", + "offset": 352, + "lineBreaks": 0, + "line": 22, + "col": 3 + }, + "matchType": { + "type": "id", + "value": "match", + "text": "match", + "offset": 354, + "lineBreaks": 0, + "line": 22, + "col": 5 + }, + "pattern": [ + { + "type": "regex", + "value": { + "type": "regex", + "value": "/id/", + "text": "/id/", + "offset": 360, + "lineBreaks": 0, + "line": 22, + "col": 11 + } + } + ] + } + ] + }, + { + "head": { + "scheme": { + "type": "id", + "value": "scheme", + "text": "scheme", + "offset": 368, + "lineBreaks": 0, + "line": 25, + "col": 1 + }, + "authority": { + "userinfo": { + "type": "id", + "value": "username", + "text": "username", + "offset": 375, + "lineBreaks": 0, + "line": 25, + "col": 8 + }, + "host": { + "type": "id", + "value": "example3.com", + "text": "example3.com", + "offset": 384, + "lineBreaks": 0, + "line": 25, + "col": 17 + }, + "port": { + "type": "num", + "value": "4321", + "text": "4321", + "offset": 397, + "lineBreaks": 0, + "line": 25, + "col": 30 + } + }, + "path": null + }, + "tail": [ + { + "tailType": { + "type": "symbol", + "value": "?", + "text": "?", + "offset": 406, + "lineBreaks": 0, + "line": 26, + "col": 3 + }, + "matchType": { + "type": "id", + "value": "form", + "text": "form", + "offset": 408, + "lineBreaks": 0, + "line": 26, + "col": 5 + }, + "pattern": [ + { + "key": { + "type": "string", + "value": { + "type": "string", + "value": "'a'", + "text": "'a'", + "offset": 419, + "lineBreaks": 0, + "line": 27, + "col": 5 + } + }, + "value": [ + { + "type": "regex", + "value": { + "type": "regex", + "value": "/.*/", + "text": "/.*/", + "offset": 425, + "lineBreaks": 0, + "line": 27, + "col": 11 + } + } + ], + "array": false + }, + { + "key": { + "type": "string", + "value": { + "type": "string", + "value": "'b'", + "text": "'b'", + "offset": 434, + "lineBreaks": 0, + "line": 28, + "col": 5 + } + }, + "value": [ + { + "type": "regex", + "value": { + "type": "regex", + "value": "/.*/", + "text": "/.*/", + "offset": 440, + "lineBreaks": 0, + "line": 28, + "col": 11 + } + } + ], + "array": false + }, + { + "key": { + "type": "string", + "value": { + "type": "string", + "value": "'c-d'", + "text": "'c-d'", + "offset": 449, + "lineBreaks": 0, + "line": 29, + "col": 5 + } + }, + "value": [ + { + "type": "regex", + "value": { + "type": "regex", + "value": "/.*/", + "text": "/.*/", + "offset": 457, + "lineBreaks": 0, + "line": 29, + "col": 13 + } + } + ], + "array": false + }, + { + "key": { + "type": "string", + "value": { + "type": "string", + "value": "'/'", + "text": "'/'", + "offset": 466, + "lineBreaks": 0, + "line": 30, + "col": 5 + } + }, + "value": [ + { + "type": "regex", + "value": { + "type": "regex", + "value": "/.*/", + "text": "/.*/", + "offset": 472, + "lineBreaks": 0, + "line": 30, + "col": 11 + } + } + ], + "array": false + }, + { + "key": { + "type": "string", + "value": { + "type": "string", + "value": "'?'", + "text": "'?'", + "offset": 481, + "lineBreaks": 0, + "line": 31, + "col": 5 + } + }, + "value": [ + { + "type": "regex", + "value": { + "type": "regex", + "value": "/.*/", + "text": "/.*/", + "offset": 487, + "lineBreaks": 0, + "line": 31, + "col": 11 + } + } + ], + "array": false + }, + { + "key": { + "type": "string", + "value": { + "type": "string", + "value": "'f/'", + "text": "'f/'", + "offset": 496, + "lineBreaks": 0, + "line": 32, + "col": 5 + } + }, + "value": [ + { + "type": "regex", + "value": { + "type": "regex", + "value": "/.*/", + "text": "/.*/", + "offset": 503, + "lineBreaks": 0, + "line": 32, + "col": 12 + } + } + ], + "array": false + }, + { + "key": { + "type": "string", + "value": { + "type": "string", + "value": "'g'", + "text": "'g'", + "offset": 512, + "lineBreaks": 0, + "line": 33, + "col": 5 + } + }, + "value": [ + { + "type": "regex", + "value": { + "type": "regex", + "value": "/.*/", + "text": "/.*/", + "offset": 518, + "lineBreaks": 0, + "line": 33, + "col": 11 + } + } + ], + "array": false + }, + { + "key": { + "type": "string", + "value": { + "type": "string", + "value": "'h'", + "text": "'h'", + "offset": 527, + "lineBreaks": 0, + "line": 34, + "col": 5 + } + }, + "value": [ + { + "type": "regex", + "value": { + "type": "regex", + "value": "/.*/", + "text": "/.*/", + "offset": 533, + "lineBreaks": 0, + "line": 34, + "col": 11 + } + } + ], + "array": false + } + ] + } + ] + } +] diff --git a/visitor/index.ts b/visitor/index.ts new file mode 100644 index 0000000..2e291ae --- /dev/null +++ b/visitor/index.ts @@ -0,0 +1,148 @@ +import * as ast from "../ast.ts"; + +export interface Visitor { + visitUrichk: VisitFn; + visitRule: VisitFn; + visitHead: VisitFn; + visitTail: VisitFn; + visitScheme: VisitFn; + visitAuthority: VisitFn; + visitUserinfo: VisitFn; + visitHost: VisitFn; + visitPort: VisitFn; + visitPath: VisitFn; + visitPathFragment: VisitFn; + visitStaticPathFragment: VisitFn; + visitParamPathFragment: VisitFn; + visitTailRule: VisitFn; + visitTailRuleMatch: VisitFn; + visitTailRuleForm: VisitFn; + visitTailRuleFormPatternRule: VisitFn; + visitTailRulePatternValue: VisitFn; + visitKey: VisitFn; + visitArrayToken: VisitFn; + visitTailType: VisitFn; + visitMatchType: VisitFn; + visitComment: VisitFn; + visitToken: VisitFn; +} + +interface VisitFn { + (visitor: Visitor, node: T): void; +} + +export const visitor: Visitor = { + visitUrichk(visitor, node) { + for (const rule of node) { + visitor.visitRule(visitor, rule); + } + }, + visitRule(visitor, node) { + const { head, tail, comment } = node; + if (comment) visitor.visitComment(visitor, comment); + visitor.visitHead(visitor, head); + visitor.visitTail(visitor, tail); + }, + visitHead(visitor, node) { + const { scheme, authority, path } = node; + if (scheme) visitor.visitScheme(visitor, scheme); + if (authority) visitor.visitAuthority(visitor, authority); + if (path) visitor.visitPath(visitor, path); + }, + visitTail(visitor, node) { + for (const rule of node) { + visitor.visitTailRule(visitor, rule); + } + }, + visitScheme(visitor, node) { + visitor.visitToken(visitor, node); + }, + visitAuthority(visitor, node) { + const { userinfo, host, port } = node; + if (userinfo) visitor.visitUserinfo(visitor, userinfo); + visitor.visitHost(visitor, host); + if (port) visitor.visitPort(visitor, port); + }, + visitUserinfo(visitor, node) { + visitor.visitToken(visitor, node); + }, + visitHost(visitor, node) { + visitor.visitToken(visitor, node); + }, + visitPort(visitor, node) { + visitor.visitToken(visitor, node); + }, + visitPath(visitor, node) { + for (const pathFragment of node) { + visitor.visitPathFragment(visitor, pathFragment); + } + }, + visitPathFragment(visitor, node) { + if (node.type === "static") { + visitor.visitStaticPathFragment(visitor, node); + } else if (node.type === "param") { + visitor.visitParamPathFragment(visitor, node); + } + }, + visitStaticPathFragment(visitor, node) { + visitor.visitToken(visitor, node.name); + }, + visitParamPathFragment(visitor, node) { + visitor.visitToken(visitor, node.name); + }, + visitTailRule(visitor, node) { + const { comment, tailType } = node; + if (comment) visitor.visitComment(visitor, comment); + visitor.visitTailType(visitor, tailType); + if (isTailRuleMatch(node)) visitor.visitTailRuleMatch(visitor, node); + if (isTailRuleForm(node)) visitor.visitTailRuleForm(visitor, node); + function isTailRuleMatch(node: ast.TailRule): node is ast.TailRuleMatch { + return node.matchType.text === "match"; + } + function isTailRuleForm(node: ast.TailRule): node is ast.TailRuleForm { + return node.matchType.text === "form"; + } + }, + visitTailRuleMatch(visitor, node) { + const { matchType, pattern } = node; + visitor.visitMatchType(visitor, matchType); + for (const value of pattern) { + visitor.visitTailRulePatternValue(visitor, value); + } + }, + visitTailRulePatternValue(visitor, node) { + visitor.visitToken(visitor, node.value); + }, + visitTailRuleForm(visitor, node) { + const { matchType, pattern } = node; + visitor.visitMatchType(visitor, matchType); + for (const rule of pattern) { + visitor.visitTailRuleFormPatternRule(visitor, rule); + } + }, + visitTailRuleFormPatternRule(visitor, node) { + const { comment, key, value, array } = node; + if (comment) visitor.visitComment(visitor, comment); + visitor.visitKey(visitor, key); + visitor.visitArrayToken(visitor, array); + if (value) { + for (const patternValue of value) { + visitor.visitTailRulePatternValue(visitor, patternValue); + } + } + }, + visitKey(visitor, node) { + visitor.visitToken(visitor, node.value); + }, + visitArrayToken() {}, + visitTailType(visitor, node) { + visitor.visitToken(visitor, node); + }, + visitMatchType(visitor, node) { + visitor.visitToken(visitor, node); + }, + visitComment(visitor, node) { + visitor.visitToken(visitor, node); + }, + visitToken() {}, +};