From 5fa0802315346ef04e953779081dcf13d6944f82 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Wed, 17 Aug 2022 09:47:08 +0800 Subject: [PATCH] Add `getVisitorKeys` (#13255) --- docs/plugins.md | 48 + package.json | 4 +- src/language-css/clean.js | 1 + src/language-css/printer-postcss.js | 3 + src/language-css/visitor-keys.js | 49 + src/language-graphql/printer-graphql.js | 3 + src/language-graphql/visitor-keys.evaluate.js | 1 + src/language-handlebars/printer-glimmer.js | 3 + .../visitor-keys.evaluate.js | 3 + src/language-html/printer-html.js | 4 + src/language-html/visitor-keys.js | 16 + .../parse/postprocess/visit-node.js | 23 +- src/language-js/printer-estree-json.js | 3 + src/language-js/printer-estree.js | 3 +- src/language-js/traverse/get-visitor-keys.js | 6 + src/language-js/traverse/json-visitor-keys.js | 16 + .../traverse/union-visitor-keys.js | 11 + .../traverse/visitor-keys.evaluate.js | 56 + src/language-js/utils/index.js | 5 +- src/language-markdown/printer-markdown.js | 7 +- src/language-markdown/visitor-keys.js | 43 + src/language-yaml/printer-yaml.js | 3 + src/language-yaml/visitor-keys.js | 42 + src/main/comments.js | 89 +- src/main/create-get-visitor-keys-function.js | 19 + src/main/massage-ast.js | 65 +- src/main/multiparser.js | 51 +- src/utils/create-get-visitor-keys.js | 28 + tests/unit/__snapshots__/visitor-keys.js.snap | 1758 +++++++++++++++++ tests/unit/visitor-keys.js | 24 + website/playground/util.js | 5 +- yarn.lock | 9 + 32 files changed, 2279 insertions(+), 122 deletions(-) create mode 100644 src/language-css/visitor-keys.js create mode 100644 src/language-graphql/visitor-keys.evaluate.js create mode 100644 src/language-handlebars/visitor-keys.evaluate.js create mode 100644 src/language-html/visitor-keys.js create mode 100644 src/language-js/traverse/get-visitor-keys.js create mode 100644 src/language-js/traverse/json-visitor-keys.js create mode 100644 src/language-js/traverse/union-visitor-keys.js create mode 100644 src/language-js/traverse/visitor-keys.evaluate.js create mode 100644 src/language-markdown/visitor-keys.js create mode 100644 src/language-yaml/visitor-keys.js create mode 100644 src/main/create-get-visitor-keys-function.js create mode 100644 src/utils/create-get-visitor-keys.js create mode 100644 tests/unit/__snapshots__/visitor-keys.js.snap create mode 100644 tests/unit/visitor-keys.js diff --git a/docs/plugins.md b/docs/plugins.md index bdd1ca0f5..a6b26b2a9 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -156,6 +156,7 @@ export const printers = { print, embed, preprocess, + getVisitorKeys, insertPragma, canAttachComment, isBlockComment, @@ -308,6 +309,53 @@ The `preprocess` method can process the AST from the parser before passing it in function preprocess(ast: AST, options: Options): AST | Promise; ``` +#### (optional) `getVisitorKeys` + +This property might come in handy if the plugin uses comment attachment or embedded languages. These features traverse the AST iterating through all the own enumerable properties of each node starting from the root. If the AST has [cycles](), such a traverse ends up in an infinite loop. Also, nodes might contain non-node objects (e.g., location data), iterating through which is a waste of resources. To solve these issues, the printer can define a function to return property names that should be traversed. + +Its signature is: + +```ts +function getVisitorKeys(node, nonTraversableKeys: Set): string[]; +``` + +The default `getVisitorKeys`: + +```js +function getVisitorKeys(node, nonTraversableKeys) { + return Object.keys(node).filter((key) => !nonTraversableKeys.has(key)); +} +``` + +The second argument `nonTraversableKeys` is a set of common keys and keys that prettier used internal. + +If you have full list of visitor keys + +```js +const visitorKeys = { + Program: ["body"], + Identifier: [], + // ... +}; + +function getVisitorKeys(node /* , nonTraversableKeys*/) { + // Return `[]` for unknown node to prevent Prettier fallback to use `Object.keys()` + return visitorKeys[node.type] ?? []; +} +``` + +If you only need exclude a small set of keys + +```js +const ignoredKeys = new Set(["prev", "next", "range"]); + +function getVisitorKeys(node, nonTraversableKeys) { + return Object.keys(node).filter( + (key) => !nonTraversableKeys.has(key) && !ignoredKeys.has(key) + ); +} +``` + #### (optional) `insertPragma` A plugin can implement how a pragma comment is inserted in the resulting code when the `--insert-pragma` option is used, in the `insertPragma` function. Its signature is: diff --git a/package.json b/package.json index 0ef8425d0..3e9087231 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,11 @@ "@angular/compiler": "12.2.16", "@babel/code-frame": "7.18.6", "@babel/parser": "7.18.11", + "@babel/types": "7.18.10", "@glimmer/syntax": "0.84.2", "@iarna/toml": "2.2.5", "@typescript-eslint/typescript-estree": "5.33.0", + "@typescript-eslint/visitor-keys": "5.33.0", "acorn": "8.8.0", "acorn-jsx": "5.3.2", "angular-estree-parser": "2.5.1", @@ -86,6 +88,7 @@ "semver": "7.3.7", "string-width": "5.0.1", "strip-ansi": "7.0.1", + "to-fast-properties": "4.0.0", "typescript": "4.7.4", "unicode-regex": "3.0.0", "unified": "9.2.2", @@ -94,7 +97,6 @@ "yaml-unist-parser": "1.3.1" }, "devDependencies": { - "@babel/types": "7.18.10", "@esbuild-plugins/node-modules-polyfill": "0.1.4", "@glimmer/reference": "0.84.2", "@types/estree": "1.0.0", diff --git a/src/language-css/clean.js b/src/language-css/clean.js index 344874c94..3ab984a08 100644 --- a/src/language-css/clean.js +++ b/src/language-css/clean.js @@ -9,6 +9,7 @@ const ignoredProperties = new Set([ "before", "after", "trailingComma", + "spaces", ]); function clean(ast, newObj, parent) { diff --git a/src/language-css/printer-postcss.js b/src/language-css/printer-postcss.js index 0a67b421c..b7dd826de 100644 --- a/src/language-css/printer-postcss.js +++ b/src/language-css/printer-postcss.js @@ -20,9 +20,11 @@ import { breakParent, } from "../document/builders.js"; import { removeLines, getDocParts } from "../document/utils.js"; +import createGetVisitorKeys from "../utils/create-get-visitor-keys.js"; import clean from "./clean.js"; import embed from "./embed.js"; import { insertPragma } from "./pragma.js"; +import visitorKeys from "./visitor-keys.js"; import { getAncestorNode, @@ -1121,6 +1123,7 @@ const printer = { embed, insertPragma, massageAstNode: clean, + getVisitorKeys: createGetVisitorKeys(visitorKeys), }; export default printer; diff --git a/src/language-css/visitor-keys.js b/src/language-css/visitor-keys.js new file mode 100644 index 000000000..7a23be6db --- /dev/null +++ b/src/language-css/visitor-keys.js @@ -0,0 +1,49 @@ +const visitorKeys = { + "front-matter": [], + "css-root": ["nodes"], + "css-comment": [], + "css-rule": ["selector", "nodes"], + "css-decl": ["value", "selector", "nodes"], + "css-atrule": ["selector", "params", "value", "nodes"], + "media-query-list": ["nodes"], + "media-query": ["nodes"], + "media-type": [], + "media-feature-expression": ["nodes"], + "media-feature": [], + "media-colon": [], + "media-value": [], + "media-keyword": [], + "media-url": [], + "media-unknown": [], + "selector-root": ["nodes"], + "selector-selector": ["nodes"], + "selector-comment": [], + "selector-string": [], + "selector-tag": [], + "selector-id": [], + "selector-class": [], + "selector-attribute": [], + "selector-combinator": ["nodes"], + "selector-universal": [], + "selector-pseudo": ["nodes"], + "selector-nesting": [], + "selector-unknown": [], + "value-value": ["group"], + "value-root": ["group"], + "value-comment": [], + "value-comma_group": ["groups"], + "value-paren_group": ["open", "groups", "close"], + "value-func": ["group"], + "value-paren": [], + "value-number": [], + "value-operator": [], + "value-word": [], + "value-colon": [], + "value-comma": [], + "value-string": [], + "value-atword": [], + "value-unicode-range": [], + "value-unknown": [], +}; + +export default visitorKeys; diff --git a/src/language-graphql/printer-graphql.js b/src/language-graphql/printer-graphql.js index c9be434af..eb46b2bc6 100644 --- a/src/language-graphql/printer-graphql.js +++ b/src/language-graphql/printer-graphql.js @@ -8,8 +8,10 @@ import { ifBreak, } from "../document/builders.js"; import { isNextLineEmpty, isNonEmptyArray } from "../common/util.js"; +import createGetVisitorKeys from "../utils/create-get-visitor-keys.js"; import { insertPragma } from "./pragma.js"; import { locStart, locEnd } from "./loc.js"; +import visitorKeys from "./visitor-keys.evaluate.js"; function genericPrint(path, options, print) { const node = path.getValue(); @@ -601,6 +603,7 @@ const printer = { insertPragma, printComment, canAttachComment, + getVisitorKeys: createGetVisitorKeys(visitorKeys, "kind"), }; export default printer; diff --git a/src/language-graphql/visitor-keys.evaluate.js b/src/language-graphql/visitor-keys.evaluate.js new file mode 100644 index 000000000..fef9b00d0 --- /dev/null +++ b/src/language-graphql/visitor-keys.evaluate.js @@ -0,0 +1 @@ +export { QueryDocumentKeys as default } from "graphql/language/ast.mjs"; diff --git a/src/language-handlebars/printer-glimmer.js b/src/language-handlebars/printer-glimmer.js index 105b81bf3..19626f77e 100644 --- a/src/language-handlebars/printer-glimmer.js +++ b/src/language-handlebars/printer-glimmer.js @@ -11,6 +11,7 @@ import { } from "../document/builders.js"; import { replaceTextEndOfLine } from "../document/utils.js"; import { getPreferredQuote, isNonEmptyArray } from "../common/util.js"; +import createGetVisitorKeys from "../utils/create-get-visitor-keys.js"; import { locStart, locEnd } from "./loc.js"; import clean from "./clean.js"; import { @@ -25,6 +26,7 @@ import { isVoid, isWhitespaceNode, } from "./utils.js"; +import visitorKeys from "./visitor-keys.evaluate.js"; const NEWLINES_TO_PRESERVE_MAX = 2; @@ -786,6 +788,7 @@ function printBlockParams(node) { const printer = { print, massageAstNode: clean, + getVisitorKeys: createGetVisitorKeys(visitorKeys), }; export default printer; diff --git a/src/language-handlebars/visitor-keys.evaluate.js b/src/language-handlebars/visitor-keys.evaluate.js new file mode 100644 index 000000000..bb1a38867 --- /dev/null +++ b/src/language-handlebars/visitor-keys.evaluate.js @@ -0,0 +1,3 @@ +import visitorKeys from "@glimmer/syntax/dist/commonjs/es2017/lib/v1/visitor-keys.js"; + +export default visitorKeys.default; diff --git a/src/language-html/printer-html.js b/src/language-html/printer-html.js index 126d966eb..296dd45d6 100644 --- a/src/language-html/printer-html.js +++ b/src/language-html/printer-html.js @@ -4,6 +4,7 @@ import { fill, group, hardline, literalline } from "../document/builders.js"; import { cleanDoc, replaceTextEndOfLine } from "../document/utils.js"; +import createGetVisitorKeys from "../utils/create-get-visitor-keys.js"; import clean from "./clean.js"; import { countChars, @@ -22,6 +23,7 @@ import { } from "./print/tag.js"; import { printElement } from "./print/element.js"; import { printChildren } from "./print/children.js"; +import visitorKeys from "./visitor-keys.js"; function genericPrint(path, options, print) { const node = path.getValue(); @@ -114,6 +116,7 @@ function genericPrint(path, options, print) { quote, ]; } + case "cdata": // Transformed into `text` default: /* istanbul ignore next */ throw new Error(`Unexpected node type ${node.type}`); @@ -126,6 +129,7 @@ const printer = { insertPragma, massageAstNode: clean, embed, + getVisitorKeys: createGetVisitorKeys(visitorKeys), }; export default printer; diff --git a/src/language-html/visitor-keys.js b/src/language-html/visitor-keys.js new file mode 100644 index 000000000..521da08b8 --- /dev/null +++ b/src/language-html/visitor-keys.js @@ -0,0 +1,16 @@ +const visitorKeys = { + "front-matter": [], + root: ["children"], + element: ["attrs", "children"], + ieConditionalComment: ["children"], + ieConditionalStartComment: [], + ieConditionalEndComment: [], + interpolation: ["children"], + text: ["children"], + docType: [], + comment: [], + attribute: [], + cdata: [], +}; + +export default visitorKeys; diff --git a/src/language-js/parse/postprocess/visit-node.js b/src/language-js/parse/postprocess/visit-node.js index bcdc113d6..57c0d722e 100644 --- a/src/language-js/parse/postprocess/visit-node.js +++ b/src/language-js/parse/postprocess/visit-node.js @@ -1,4 +1,10 @@ +import getVisitorKeys from "../../traverse/get-visitor-keys.js"; + function visitNode(node, fn) { + if (!(node !== null && typeof node === "object")) { + return node; + } + if (Array.isArray(node)) { // As of Node.js 16 using raw for loop over Array.entries provides a // measurable difference in performance. Array.entries returns an iterator @@ -8,18 +14,13 @@ function visitNode(node, fn) { } return node; } - if (node && typeof node === "object" && typeof node.type === "string") { - // As of Node.js 16 this is benchmarked to be faster over Object.entries. - // Object.entries returns an array of arrays. There are multiple ways to - // iterate over objects but the Object.keys combined with a for loop - // benchmarks well. - const keys = Object.keys(node); - for (let i = 0; i < keys.length; i++) { - node[keys[i]] = visitNode(node[keys[i]], fn); - } - return fn(node) || node; + + const keys = getVisitorKeys(node); + for (let i = 0; i < keys.length; i++) { + node[keys[i]] = visitNode(node[keys[i]], fn); } - return node; + + return fn(node) || node; } export default visitNode; diff --git a/src/language-js/printer-estree-json.js b/src/language-js/printer-estree-json.js index 423758509..4ccd59ce9 100644 --- a/src/language-js/printer-estree-json.js +++ b/src/language-js/printer-estree-json.js @@ -1,4 +1,6 @@ import { hardline, indent, join } from "../document/builders.js"; +import createGetVisitorKeys from "../utils/create-get-visitor-keys.js"; +import visitorKeys from "./traverse/json-visitor-keys.js"; import preprocess from "./print-preprocess.js"; function genericPrint(path, options, print) { @@ -109,6 +111,7 @@ const printer = { preprocess, print: genericPrint, massageAstNode: clean, + getVisitorKeys: createGetVisitorKeys(visitorKeys), }; export default printer; diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js index f258018fb..a1b88d9d2 100644 --- a/src/language-js/printer-estree.js +++ b/src/language-js/printer-estree.js @@ -34,6 +34,7 @@ import { } from "./utils/index.js"; import { locStart, locEnd } from "./loc.js"; import isBlockComment from "./utils/is-block-comment.js"; +import getVisitorKeys from "./traverse/get-visitor-keys.js"; import { printHtmlBinding, @@ -866,7 +867,7 @@ const printer = { remaining: handleComments.handleRemainingComment, }, getCommentChildNodes: handleComments.getCommentChildNodes, - isNode: (node) => typeof node.type === "string", + getVisitorKeys, }; export default printer; diff --git a/src/language-js/traverse/get-visitor-keys.js b/src/language-js/traverse/get-visitor-keys.js new file mode 100644 index 000000000..96b5dbfd0 --- /dev/null +++ b/src/language-js/traverse/get-visitor-keys.js @@ -0,0 +1,6 @@ +import createGetVisitorKeys from "../../utils/create-get-visitor-keys.js"; +import visitorKeys from "./visitor-keys.evaluate.js"; + +const getVisitorKeys = createGetVisitorKeys(visitorKeys); + +export default getVisitorKeys; diff --git a/src/language-js/traverse/json-visitor-keys.js b/src/language-js/traverse/json-visitor-keys.js new file mode 100644 index 000000000..bb13cbc10 --- /dev/null +++ b/src/language-js/traverse/json-visitor-keys.js @@ -0,0 +1,16 @@ +const visitorKeys = { + JsonRoot: ["node"], + ArrayExpression: ["elements"], + ObjectExpression: ["properties"], + ObjectProperty: ["key", "value"], + UnaryExpression: ["argument"], + NullLiteral: [], + BooleanLiteral: [], + StringLiteral: [], + NumericLiteral: [], + Identifier: [], + TemplateLiteral: ["quasis"], + TemplateElement: [], +}; + +export default visitorKeys; diff --git a/src/language-js/traverse/union-visitor-keys.js b/src/language-js/traverse/union-visitor-keys.js new file mode 100644 index 000000000..ec32c6795 --- /dev/null +++ b/src/language-js/traverse/union-visitor-keys.js @@ -0,0 +1,11 @@ +function unionVisitorKeys(all) { + const result = {}; + + for (const [key, value] of all.flatMap((keys) => Object.entries(keys))) { + result[key] = [...new Set([...(result[key] ?? []), ...value])]; + } + + return result; +} + +export default unionVisitorKeys; diff --git a/src/language-js/traverse/visitor-keys.evaluate.js b/src/language-js/traverse/visitor-keys.evaluate.js new file mode 100644 index 000000000..c0a6a4d4b --- /dev/null +++ b/src/language-js/traverse/visitor-keys.evaluate.js @@ -0,0 +1,56 @@ +import { VISITOR_KEYS as babelVisitorKeys } from "@babel/types"; +import { visitorKeys as tsVisitorKeys } from "@typescript-eslint/visitor-keys"; +import unionVisitorKeys from "./union-visitor-keys.js"; + +const angularVisitorKeys = { + NGRoot: ["node"], + NGPipeExpression: ["left", "right", "arguments"], + NGChainedExpression: ["expressions"], + NGEmptyExpression: [], + NGQuotedExpression: [], + NGMicrosyntax: ["body"], + NGMicrosyntaxKey: [], + NGMicrosyntaxExpression: ["expression", "name"], + NGMicrosyntaxKeyedExpression: ["key", "expression"], + NGMicrosyntaxLet: ["key", "value"], + NGMicrosyntaxAs: ["key", "alias"], +}; + +const additionalVisitorKeys = { + // Prettier + JsExpressionRoot: ["node"], + JsonRoot: ["node"], + + // Babel missing this + Program: ["interpreter"], + + // Legacy node type + SpreadProperty: ["argument"], + + // typescript + TSJSDocAllType: [], + TSJSDocUnknownType: [], + TSJSDocNullableType: ["typeAnnotation"], + TSJSDocNonNullableType: ["typeAnnotation"], + // This one maybe invalid, need investigate + TSAbstractMethodDefinition: ["decorators"], + TSModuleDeclaration: ["modifiers"], + TSEnumDeclaration: ["modifiers"], + + // flow + QualifiedTypeofIdentifier: ["id", "qualification"], + BigIntLiteralTypeAnnotation: [], + BigIntTypeAnnotation: [], + FunctionTypeAnnotation: ["this"], + PropertyDefinition: ["variance"], + ArrowFunctionExpression: ["predicate"], + DeclareFunction: ["predicate"], + FunctionDeclaration: ["predicate"], +}; + +export default unionVisitorKeys([ + babelVisitorKeys, + tsVisitorKeys, + angularVisitorKeys, + additionalVisitorKeys, +]); diff --git a/src/language-js/utils/index.js b/src/language-js/utils/index.js index 6e808ed09..d7d8350fd 100644 --- a/src/language-js/utils/index.js +++ b/src/language-js/utils/index.js @@ -8,6 +8,7 @@ import { getStringWidth, } from "../../common/util.js"; import { locStart, locEnd, hasSameLocStart } from "../loc.js"; +import getVisitorKeys from "../traverse/get-visitor-keys.js"; import isBlockComment from "./is-block-comment.js"; import isNodeMatches from "./is-node-matches.js"; @@ -77,7 +78,7 @@ function hasFlowAnnotationComment(comments) { * @returns {boolean} */ function hasNode(node, fn) { - if (!node || typeof node !== "object") { + if (node === null || typeof node !== "object") { return false; } if (Array.isArray(node)) { @@ -86,7 +87,7 @@ function hasNode(node, fn) { const result = fn(node); return typeof result === "boolean" ? result - : Object.values(node).some((value) => hasNode(value, fn)); + : getVisitorKeys(node).some((key) => hasNode(node[key], fn)); } /** diff --git a/src/language-markdown/printer-markdown.js b/src/language-markdown/printer-markdown.js index 75aea893a..b12994297 100644 --- a/src/language-markdown/printer-markdown.js +++ b/src/language-markdown/printer-markdown.js @@ -22,6 +22,7 @@ import { } from "../document/builders.js"; import { normalizeDoc, replaceTextEndOfLine } from "../document/utils.js"; import { printDocToString } from "../document/printer.js"; +import createGetVisitorKeys from "../utils/create-get-visitor-keys.js"; import embed from "./embed.js"; import { insertPragma } from "./pragma.js"; import { locStart, locEnd } from "./loc.js"; @@ -36,6 +37,7 @@ import { INLINE_NODE_WRAPPER_TYPES, isAutolink, } from "./utils.js"; +import visitorKeys from "./visitor-keys.js"; /** * @typedef {import("../document/builders.js").Doc} Doc @@ -452,6 +454,9 @@ function genericPrint(path, options, print) { case "tableRow": // handled in "table" case "listItem": // handled in "list" + case "text": // handled in other types + case "import": // transformed in to `importExport` + case "export": // transformed in to `importExport` default: /* istanbul ignore next */ throw new Error(`Unknown markdown type ${JSON.stringify(node.type)}`); @@ -916,7 +921,7 @@ const printer = { massageAstNode: clean, hasPrettierIgnore, insertPragma, - isNode: (obj) => Boolean(obj.type), + getVisitorKeys: createGetVisitorKeys(visitorKeys), }; export default printer; diff --git a/src/language-markdown/visitor-keys.js b/src/language-markdown/visitor-keys.js new file mode 100644 index 000000000..08064e713 --- /dev/null +++ b/src/language-markdown/visitor-keys.js @@ -0,0 +1,43 @@ +const visitorKeys = { + "front-matter": [], + root: ["children"], + paragraph: ["children"], + sentence: ["children"], + word: [], + whitespace: [], + emphasis: ["children"], + strong: ["children"], + delete: ["children"], + inlineCode: [], + wikiLink: [], + link: ["children"], + image: [], + blockquote: ["children"], + heading: ["children"], + code: [], + html: [], + list: ["children"], + thematicBreak: [], + linkReference: ["children"], + imageReference: [], + definition: [], + footnote: ["children"], + footnoteReference: [], + footnoteDefinition: ["children"], + table: ["children"], + tableCell: ["children"], + break: [], + liquidNode: [], + import: [], + export: [], + importExport: [], + esComment: [], + jsx: [], + math: [], + inlineMath: [], + tableRow: ["children"], + listItem: ["children"], + text: [], +}; + +export default visitorKeys; diff --git a/src/language-yaml/printer-yaml.js b/src/language-yaml/printer-yaml.js index 2a5643352..74b911af0 100644 --- a/src/language-yaml/printer-yaml.js +++ b/src/language-yaml/printer-yaml.js @@ -12,6 +12,7 @@ import { } from "../document/builders.js"; import { replaceTextEndOfLine } from "../document/utils.js"; import { isPreviousLineEmpty } from "../common/util.js"; +import createGetVisitorKeys from "../utils/create-get-visitor-keys.js"; import { insertPragma, isPragma } from "./pragma.js"; import { locStart } from "./loc.js"; import embed from "./embed.js"; @@ -27,6 +28,7 @@ import { isNode, isInlineNode, } from "./utils.js"; +import visitorKeys from "./visitor-keys.js"; import preprocess from "./print-preprocess.js"; import { alignWithSpaces, @@ -443,6 +445,7 @@ const printer = { print: genericPrint, massageAstNode: clean, insertPragma, + getVisitorKeys: createGetVisitorKeys(visitorKeys), }; export default printer; diff --git a/src/language-yaml/visitor-keys.js b/src/language-yaml/visitor-keys.js new file mode 100644 index 000000000..1a7d4ef52 --- /dev/null +++ b/src/language-yaml/visitor-keys.js @@ -0,0 +1,42 @@ +const visitorKeys = Object.fromEntries( + Object.entries({ + root: ["children"], + document: ["head", "body", "children"], + documentHead: ["children"], + documentBody: ["children"], + directive: [], + alias: [], + blockLiteral: [], + blockFolded: ["children"], + plain: ["children"], + quoteSingle: [], + quoteDouble: [], + mapping: ["children"], + mappingItem: ["key", "value", "children"], + mappingKey: ["content", "children"], + mappingValue: ["content", "children"], + sequence: ["children"], + sequenceItem: ["content", "children"], + flowMapping: ["children"], + flowMappingItem: ["key", "value", "children"], + flowSequence: ["children"], + flowSequenceItem: ["children"], + comment: [], + tag: [], + anchor: [], + }).map(([type, keys]) => [ + type, + [ + ...keys, + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + ]) +); + +export default visitorKeys; diff --git a/src/main/comments.js b/src/main/comments.js index 375cb5248..a3a719f5d 100644 --- a/src/main/comments.js +++ b/src/main/comments.js @@ -1,5 +1,4 @@ import assert from "node:assert"; - import { line, hardline, @@ -9,7 +8,6 @@ import { join, cursor, } from "../document/builders.js"; - import { hasNewline, skipNewline, @@ -18,66 +16,53 @@ import { addLeadingComment, addDanglingComment, addTrailingComment, + isNonEmptyArray, } from "../common/util.js"; +import createGetVisitorKeysFunction from "./create-get-visitor-keys-function.js"; const childNodesCache = new WeakMap(); -function getSortedChildNodes(node, options, resultArray) { - if (!node) { - return; - } - const { printer, locStart, locEnd } = options; - - if (resultArray) { - if (printer.canAttachComment && printer.canAttachComment(node)) { - // This reverse insertion sort almost always takes constant - // time because we almost always (maybe always?) append the - // nodes in order anyway. - let i; - for (i = resultArray.length - 1; i >= 0; --i) { - if ( - locStart(resultArray[i]) <= locStart(node) && - locEnd(resultArray[i]) <= locEnd(node) - ) { - break; - } - } - resultArray.splice(i + 1, 0, node); - return; - } - } else if (childNodesCache.has(node)) { +function getSortedChildNodes(node, options) { + if (childNodesCache.has(node)) { return childNodesCache.get(node); } - const childNodes = - (printer.getCommentChildNodes && - printer.getCommentChildNodes(node, options)) || - (typeof node === "object" && - Object.entries(node) - .filter( - ([key]) => - key !== "enclosingNode" && - key !== "precedingNode" && - key !== "followingNode" && - key !== "tokens" && - key !== "comments" && - key !== "parent" - ) - .map(([, value]) => value)); + const { + printer: { + getCommentChildNodes, + canAttachComment, + getVisitorKeys: printerGetVisitorKeys, + }, + locStart, + locEnd, + } = options; - if (!childNodes) { - return; + if (!canAttachComment) { + return []; } - if (!resultArray) { - resultArray = []; - childNodesCache.set(node, resultArray); - } + const childNodes = ( + getCommentChildNodes?.(node, options) ?? + createGetVisitorKeysFunction(printerGetVisitorKeys)(node).flatMap( + (key) => node[key] + ) + ).flatMap((childNode) => { + if (!(childNode !== null && typeof childNode === "object")) { + return []; + } - for (const childNode of childNodes) { - getSortedChildNodes(childNode, options, resultArray); - } + return canAttachComment(childNode) + ? childNode + : getSortedChildNodes(childNode, options); + }); - return resultArray; + // Sort by `start` location first, then `end` location + childNodes.sort( + (nodeA, nodeB) => + locStart(nodeA) - locStart(nodeB) || locEnd(nodeA) - locEnd(nodeB) + ); + + childNodesCache.set(node, childNodes); + return childNodes; } // As efficiently as possible, decorate the comment object with @@ -161,7 +146,7 @@ function decorateComment(node, comment, options, enclosingNode) { const returnFalse = () => false; function attach(comments, ast, text, options) { - if (!Array.isArray(comments)) { + if (!isNonEmptyArray(comments) || !options.printer.canAttachComment) { return; } diff --git a/src/main/create-get-visitor-keys-function.js b/src/main/create-get-visitor-keys-function.js new file mode 100644 index 000000000..9091a9125 --- /dev/null +++ b/src/main/create-get-visitor-keys-function.js @@ -0,0 +1,19 @@ +const nonTraversableKeys = new Set([ + "tokens", + "comments", + "parent", + "enclosingNode", + "precedingNode", + "followingNode", +]); + +const defaultGetVisitorKeys = (node) => + Object.keys(node).filter((key) => !nonTraversableKeys.has(key)); + +function createGetVisitorKeysFunction(printerGetVisitorKeys) { + return printerGetVisitorKeys + ? (node) => printerGetVisitorKeys(node, nonTraversableKeys) + : defaultGetVisitorKeys; +} + +export default createGetVisitorKeysFunction; diff --git a/src/main/massage-ast.js b/src/main/massage-ast.js index f8dfbe1b7..858c392d7 100644 --- a/src/main/massage-ast.js +++ b/src/main/massage-ast.js @@ -1,38 +1,55 @@ -function massageAST(ast, options, parent) { - if (Array.isArray(ast)) { - return ast.map((e) => massageAST(e, options, parent)).filter(Boolean); - } +import createGetVisitorKeysFunction from "./create-get-visitor-keys-function.js"; - if (!ast || typeof ast !== "object") { +function massageAST(ast, options) { + const { + printer: { + massageAstNode: cleanFunction, + getVisitorKeys: printerGetVisitorKeys, + }, + } = options; + + if (!cleanFunction) { return ast; } - const cleanFunction = options.printer.massageAstNode; - let ignoredProperties; - if (cleanFunction && cleanFunction.ignoredProperties) { - ignoredProperties = cleanFunction.ignoredProperties; - } else { - ignoredProperties = new Set(); - } + const getVisitorKeys = createGetVisitorKeysFunction(printerGetVisitorKeys); + const ignoredProperties = cleanFunction.ignoredProperties ?? new Set(); - const newObj = {}; - for (const [key, value] of Object.entries(ast)) { - if (!ignoredProperties.has(key) && typeof value !== "function") { - newObj[key] = massageAST(value, options, ast); + return recurse(ast); + + function recurse(node, parent) { + if (!(node !== null && typeof node === "object")) { + return node; } - } - if (cleanFunction) { - const result = cleanFunction(ast, newObj, parent); + if (Array.isArray(node)) { + return node.map((child) => recurse(child, parent)).filter(Boolean); + } + + const newObj = {}; + const childrenKeys = new Set(getVisitorKeys(node)); + for (const key in node) { + if ( + !Object.prototype.hasOwnProperty.call(node, key) || + ignoredProperties.has(key) + ) { + continue; + } + + if (childrenKeys.has(key)) { + newObj[key] = recurse(node[key], node); + } else { + newObj[key] = node[key]; + } + } + + const result = cleanFunction(node, newObj, parent); if (result === null) { return; } - if (result) { - return result; - } - } - return newObj; + return result ?? newObj; + } } export default massageAST; diff --git a/src/main/multiparser.js b/src/main/multiparser.js index dacdbde5e..b4f83bce6 100644 --- a/src/main/multiparser.js +++ b/src/main/multiparser.js @@ -2,6 +2,7 @@ import { stripTrailingHardline } from "../document/utils.js"; import { normalize } from "./options.js"; import { ensureAllCommentsPrinted, attach } from "./comments.js"; import { parse } from "./parser.js"; +import createGetVisitorKeysFunction from "./create-get-visitor-keys-function.js"; async function printEmbeddedLanguages( /** @type {import("../common/ast-path").default} */ path, @@ -10,24 +11,26 @@ async function printEmbeddedLanguages( printAstToDoc, embeds ) { - const { printer } = options; + const { + embeddedLanguageFormatting, + printer: { + embed, + hasPrettierIgnore = () => false, + getVisitorKeys: printerGetVisitorKeys, + }, + } = options; - if (!printer.embed || options.embeddedLanguageFormatting !== "auto") { + if (!embed || embeddedLanguageFormatting !== "auto") { return; } - if (printer.embed.length > 2) { + if (embed.length > 2) { throw new Error( "printer.embed has too many parameters. The API changed in Prettier v3. Please update your plugin. See https://prettier.io/docs/en/plugins.html#optional-embed" ); } - const isNode = - (printer.isNode ?? printer.canAttachComment)?.bind(printer) ?? (() => true); - const hasPrettierIgnore = - printer.hasPrettierIgnore?.bind(printer) ?? (() => false); - const ignoredProperties = printer.massageAstNode?.ignoredProperties; - + const getVisitorKeys = createGetVisitorKeysFunction(printerGetVisitorKeys); const embedCallResults = []; recurse(); @@ -58,30 +61,19 @@ async function printEmbeddedLanguages( function recurse() { const node = path.getValue(); - - if ( - node === null || - typeof node !== "object" || - !isNode(node) || - hasPrettierIgnore(path) - ) { + if (node === null || typeof node !== "object" || hasPrettierIgnore(path)) { return; } - for (const key in node) { - if ( - Object.prototype.hasOwnProperty.call(node, key) && - !ignoredProperties?.has(key) - ) { - if (Array.isArray(node[key])) { - path.each(recurse, key); - } else { - path.call(recurse, key); - } + for (const key of getVisitorKeys(node)) { + if (Array.isArray(node[key])) { + path.each(recurse, key); + } else { + path.call(recurse, key); } } - const result = printer.embed(path, options); + const result = embed(path, options); if (!result) { return; @@ -96,7 +88,10 @@ async function printEmbeddedLanguages( return; } - if (process.env.PRETTIER_DEBUG && typeof result.then === "function") { + if ( + process.env.NODE_ENV !== "production" && + typeof result.then === "function" + ) { throw new Error( "`embed` should return an async function instead of Promise." ); diff --git a/src/utils/create-get-visitor-keys.js b/src/utils/create-get-visitor-keys.js new file mode 100644 index 000000000..8068363a1 --- /dev/null +++ b/src/utils/create-get-visitor-keys.js @@ -0,0 +1,28 @@ +import toFastProperties from "to-fast-properties"; + +function createGetVisitorKeys(visitorKeys, typeProperty = "type") { + toFastProperties(visitorKeys); + + function getVisitorKeys(node) { + const type = node[typeProperty]; + + if (process.env.NODE_ENV !== "production" && typeof type === "undefined") { + throw new Error( + `Can't get node type, you must pass the wrong typeProperty '${typeProperty}'` + ); + } + + const keys = visitorKeys[type]; + if (process.env.NODE_ENV !== "production" && !Array.isArray(keys)) { + throw Object.assign(new Error(`Missing visitor keys for '${type}'.`), { + node, + }); + } + + return keys; + } + + return getVisitorKeys; +} + +export default createGetVisitorKeys; diff --git a/tests/unit/__snapshots__/visitor-keys.js.snap b/tests/unit/__snapshots__/visitor-keys.js.snap new file mode 100644 index 000000000..8b1500210 --- /dev/null +++ b/tests/unit/__snapshots__/visitor-keys.js.snap @@ -0,0 +1,1758 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`visitor keys estree 1`] = ` +{ + "AnyTypeAnnotation": [], + "ArgumentPlaceholder": [], + "ArrayExpression": [ + "elements", + ], + "ArrayPattern": [ + "elements", + "typeAnnotation", + "decorators", + ], + "ArrayTypeAnnotation": [ + "elementType", + ], + "ArrowFunctionExpression": [ + "params", + "body", + "returnType", + "typeParameters", + "predicate", + ], + "AssignmentExpression": [ + "left", + "right", + ], + "AssignmentPattern": [ + "left", + "right", + "decorators", + "typeAnnotation", + ], + "AwaitExpression": [ + "argument", + ], + "BigIntLiteral": [], + "BigIntLiteralTypeAnnotation": [], + "BigIntTypeAnnotation": [], + "BinaryExpression": [ + "left", + "right", + ], + "BindExpression": [ + "object", + "callee", + ], + "BlockStatement": [ + "directives", + "body", + ], + "BooleanLiteral": [], + "BooleanLiteralTypeAnnotation": [], + "BooleanTypeAnnotation": [], + "BreakStatement": [ + "label", + ], + "CallExpression": [ + "callee", + "arguments", + "typeParameters", + "typeArguments", + ], + "CatchClause": [ + "param", + "body", + ], + "ChainExpression": [ + "expression", + ], + "ClassAccessorProperty": [ + "key", + "value", + "typeAnnotation", + "decorators", + ], + "ClassBody": [ + "body", + ], + "ClassDeclaration": [ + "id", + "body", + "superClass", + "mixins", + "typeParameters", + "superTypeParameters", + "implements", + "decorators", + ], + "ClassExpression": [ + "id", + "body", + "superClass", + "mixins", + "typeParameters", + "superTypeParameters", + "implements", + "decorators", + ], + "ClassImplements": [ + "id", + "typeParameters", + ], + "ClassMethod": [ + "key", + "params", + "body", + "decorators", + "returnType", + "typeParameters", + ], + "ClassPrivateMethod": [ + "key", + "params", + "body", + "decorators", + "returnType", + "typeParameters", + ], + "ClassPrivateProperty": [ + "key", + "value", + "decorators", + "typeAnnotation", + ], + "ClassProperty": [ + "key", + "value", + "typeAnnotation", + "decorators", + ], + "ConditionalExpression": [ + "test", + "consequent", + "alternate", + ], + "ContinueStatement": [ + "label", + ], + "DebuggerStatement": [], + "DecimalLiteral": [], + "DeclareClass": [ + "id", + "typeParameters", + "extends", + "mixins", + "implements", + "body", + ], + "DeclareExportAllDeclaration": [ + "source", + ], + "DeclareExportDeclaration": [ + "declaration", + "specifiers", + "source", + ], + "DeclareFunction": [ + "id", + "predicate", + ], + "DeclareInterface": [ + "id", + "typeParameters", + "extends", + "mixins", + "implements", + "body", + ], + "DeclareModule": [ + "id", + "body", + ], + "DeclareModuleExports": [ + "typeAnnotation", + ], + "DeclareOpaqueType": [ + "id", + "typeParameters", + "supertype", + ], + "DeclareTypeAlias": [ + "id", + "typeParameters", + "right", + ], + "DeclareVariable": [ + "id", + ], + "DeclaredPredicate": [ + "value", + ], + "Decorator": [ + "expression", + ], + "Directive": [ + "value", + ], + "DirectiveLiteral": [], + "DoExpression": [ + "body", + ], + "DoWhileStatement": [ + "test", + "body", + ], + "EmptyStatement": [], + "EmptyTypeAnnotation": [], + "EnumBooleanBody": [ + "members", + ], + "EnumBooleanMember": [ + "id", + ], + "EnumDeclaration": [ + "id", + "body", + ], + "EnumDefaultedMember": [ + "id", + ], + "EnumNumberBody": [ + "members", + ], + "EnumNumberMember": [ + "id", + "init", + ], + "EnumStringBody": [ + "members", + ], + "EnumStringMember": [ + "id", + "init", + ], + "EnumSymbolBody": [ + "members", + ], + "ExistsTypeAnnotation": [], + "ExperimentalRestProperty": [ + "argument", + ], + "ExperimentalSpreadProperty": [ + "argument", + ], + "ExportAllDeclaration": [ + "source", + "exported", + "assertions", + ], + "ExportDefaultDeclaration": [ + "declaration", + ], + "ExportDefaultSpecifier": [ + "exported", + ], + "ExportNamedDeclaration": [ + "declaration", + "specifiers", + "source", + "assertions", + ], + "ExportNamespaceSpecifier": [ + "exported", + ], + "ExportSpecifier": [ + "local", + "exported", + ], + "ExpressionStatement": [ + "expression", + ], + "File": [ + "program", + ], + "ForInStatement": [ + "left", + "right", + "body", + ], + "ForOfStatement": [ + "left", + "right", + "body", + ], + "ForStatement": [ + "init", + "test", + "update", + "body", + ], + "FunctionDeclaration": [ + "id", + "params", + "body", + "returnType", + "typeParameters", + "predicate", + ], + "FunctionExpression": [ + "id", + "params", + "body", + "returnType", + "typeParameters", + ], + "FunctionTypeAnnotation": [ + "typeParameters", + "params", + "rest", + "returnType", + "this", + ], + "FunctionTypeParam": [ + "name", + "typeAnnotation", + ], + "GenericTypeAnnotation": [ + "id", + "typeParameters", + ], + "Identifier": [ + "typeAnnotation", + "decorators", + ], + "IfStatement": [ + "test", + "consequent", + "alternate", + ], + "Import": [], + "ImportAttribute": [ + "key", + "value", + ], + "ImportDeclaration": [ + "specifiers", + "source", + "assertions", + ], + "ImportDefaultSpecifier": [ + "local", + ], + "ImportExpression": [ + "source", + "attributes", + ], + "ImportNamespaceSpecifier": [ + "local", + ], + "ImportSpecifier": [ + "local", + "imported", + ], + "IndexedAccessType": [ + "objectType", + "indexType", + ], + "InferredPredicate": [], + "InterfaceDeclaration": [ + "id", + "typeParameters", + "extends", + "mixins", + "implements", + "body", + ], + "InterfaceExtends": [ + "id", + "typeParameters", + ], + "InterfaceTypeAnnotation": [ + "extends", + "body", + ], + "InterpreterDirective": [], + "IntersectionTypeAnnotation": [ + "types", + ], + "JSXAttribute": [ + "name", + "value", + ], + "JSXClosingElement": [ + "name", + ], + "JSXClosingFragment": [], + "JSXElement": [ + "openingElement", + "children", + "closingElement", + ], + "JSXEmptyExpression": [], + "JSXExpressionContainer": [ + "expression", + ], + "JSXFragment": [ + "openingFragment", + "children", + "closingFragment", + ], + "JSXIdentifier": [], + "JSXMemberExpression": [ + "object", + "property", + ], + "JSXNamespacedName": [ + "namespace", + "name", + ], + "JSXOpeningElement": [ + "name", + "attributes", + "typeParameters", + ], + "JSXOpeningFragment": [], + "JSXSpreadAttribute": [ + "argument", + ], + "JSXSpreadChild": [ + "expression", + ], + "JSXText": [], + "JsExpressionRoot": [ + "node", + ], + "JsonRoot": [ + "node", + ], + "LabeledStatement": [ + "label", + "body", + ], + "Literal": [], + "LogicalExpression": [ + "left", + "right", + ], + "MemberExpression": [ + "object", + "property", + ], + "MetaProperty": [ + "meta", + "property", + ], + "MethodDefinition": [ + "decorators", + "key", + "value", + "typeParameters", + ], + "MixedTypeAnnotation": [], + "ModuleExpression": [ + "body", + ], + "NGChainedExpression": [ + "expressions", + ], + "NGEmptyExpression": [], + "NGMicrosyntax": [ + "body", + ], + "NGMicrosyntaxAs": [ + "key", + "alias", + ], + "NGMicrosyntaxExpression": [ + "expression", + "name", + ], + "NGMicrosyntaxKey": [], + "NGMicrosyntaxKeyedExpression": [ + "key", + "expression", + ], + "NGMicrosyntaxLet": [ + "key", + "value", + ], + "NGPipeExpression": [ + "left", + "right", + "arguments", + ], + "NGQuotedExpression": [], + "NGRoot": [ + "node", + ], + "NewExpression": [ + "callee", + "arguments", + "typeParameters", + "typeArguments", + ], + "Noop": [], + "NullLiteral": [], + "NullLiteralTypeAnnotation": [], + "NullableTypeAnnotation": [ + "typeAnnotation", + ], + "NumberLiteralTypeAnnotation": [], + "NumberTypeAnnotation": [], + "NumericLiteral": [], + "ObjectExpression": [ + "properties", + ], + "ObjectMethod": [ + "key", + "params", + "body", + "decorators", + "returnType", + "typeParameters", + ], + "ObjectPattern": [ + "properties", + "typeAnnotation", + "decorators", + ], + "ObjectProperty": [ + "key", + "value", + "decorators", + ], + "ObjectTypeAnnotation": [ + "properties", + "indexers", + "callProperties", + "internalSlots", + ], + "ObjectTypeCallProperty": [ + "value", + ], + "ObjectTypeIndexer": [ + "id", + "key", + "value", + "variance", + ], + "ObjectTypeInternalSlot": [ + "id", + "value", + "optional", + "static", + "method", + ], + "ObjectTypeProperty": [ + "key", + "value", + "variance", + ], + "ObjectTypeSpreadProperty": [ + "argument", + ], + "OpaqueType": [ + "id", + "typeParameters", + "supertype", + "impltype", + ], + "OptionalCallExpression": [ + "callee", + "arguments", + "typeParameters", + "typeArguments", + ], + "OptionalIndexedAccessType": [ + "objectType", + "indexType", + ], + "OptionalMemberExpression": [ + "object", + "property", + ], + "ParenthesizedExpression": [ + "expression", + ], + "PipelineBareFunction": [ + "callee", + ], + "PipelinePrimaryTopicReference": [], + "PipelineTopicExpression": [ + "expression", + ], + "Placeholder": [], + "PrivateIdentifier": [], + "PrivateName": [ + "id", + ], + "Program": [ + "directives", + "body", + "interpreter", + ], + "Property": [ + "key", + "value", + ], + "PropertyDefinition": [ + "decorators", + "key", + "typeAnnotation", + "value", + "variance", + ], + "QualifiedTypeIdentifier": [ + "id", + "qualification", + ], + "QualifiedTypeofIdentifier": [ + "id", + "qualification", + ], + "RecordExpression": [ + "properties", + ], + "RegExpLiteral": [], + "RestElement": [ + "argument", + "typeAnnotation", + "decorators", + ], + "ReturnStatement": [ + "argument", + ], + "SequenceExpression": [ + "expressions", + ], + "SpreadElement": [ + "argument", + ], + "SpreadProperty": [ + "argument", + ], + "StaticBlock": [ + "body", + ], + "StringLiteral": [], + "StringLiteralTypeAnnotation": [], + "StringTypeAnnotation": [], + "Super": [], + "SwitchCase": [ + "test", + "consequent", + ], + "SwitchStatement": [ + "discriminant", + "cases", + ], + "SymbolTypeAnnotation": [], + "TSAbstractKeyword": [], + "TSAbstractMethodDefinition": [ + "key", + "value", + "decorators", + ], + "TSAbstractPropertyDefinition": [ + "decorators", + "key", + "typeAnnotation", + ], + "TSAnyKeyword": [], + "TSArrayType": [ + "elementType", + ], + "TSAsExpression": [ + "expression", + "typeAnnotation", + ], + "TSAsyncKeyword": [], + "TSBigIntKeyword": [], + "TSBooleanKeyword": [], + "TSCallSignatureDeclaration": [ + "typeParameters", + "parameters", + "typeAnnotation", + "params", + "returnType", + ], + "TSClassImplements": [ + "expression", + "typeParameters", + ], + "TSConditionalType": [ + "checkType", + "extendsType", + "trueType", + "falseType", + ], + "TSConstructSignatureDeclaration": [ + "typeParameters", + "parameters", + "typeAnnotation", + "params", + "returnType", + ], + "TSConstructorType": [ + "typeParameters", + "parameters", + "typeAnnotation", + "params", + "returnType", + ], + "TSDeclareFunction": [ + "id", + "typeParameters", + "params", + "returnType", + "body", + ], + "TSDeclareKeyword": [], + "TSDeclareMethod": [ + "decorators", + "key", + "typeParameters", + "params", + "returnType", + ], + "TSEmptyBodyFunctionExpression": [ + "id", + "typeParameters", + "params", + "returnType", + ], + "TSEnumDeclaration": [ + "id", + "members", + "modifiers", + ], + "TSEnumMember": [ + "id", + "initializer", + ], + "TSExportAssignment": [ + "expression", + ], + "TSExportKeyword": [], + "TSExpressionWithTypeArguments": [ + "expression", + "typeParameters", + ], + "TSExternalModuleReference": [ + "expression", + ], + "TSFunctionType": [ + "typeParameters", + "parameters", + "typeAnnotation", + "params", + "returnType", + ], + "TSImportEqualsDeclaration": [ + "id", + "moduleReference", + ], + "TSImportType": [ + "argument", + "qualifier", + "typeParameters", + "parameter", + ], + "TSIndexSignature": [ + "parameters", + "typeAnnotation", + ], + "TSIndexedAccessType": [ + "objectType", + "indexType", + ], + "TSInferType": [ + "typeParameter", + ], + "TSInstantiationExpression": [ + "expression", + "typeParameters", + ], + "TSInterfaceBody": [ + "body", + ], + "TSInterfaceDeclaration": [ + "id", + "typeParameters", + "extends", + "body", + ], + "TSInterfaceHeritage": [ + "expression", + "typeParameters", + ], + "TSIntersectionType": [ + "types", + ], + "TSIntrinsicKeyword": [], + "TSJSDocAllType": [], + "TSJSDocNonNullableType": [ + "typeAnnotation", + ], + "TSJSDocNullableType": [ + "typeAnnotation", + ], + "TSJSDocUnknownType": [], + "TSLiteralType": [ + "literal", + ], + "TSMappedType": [ + "typeParameter", + "typeAnnotation", + "nameType", + ], + "TSMethodSignature": [ + "key", + "typeParameters", + "parameters", + "typeAnnotation", + "params", + "returnType", + ], + "TSModuleBlock": [ + "body", + ], + "TSModuleDeclaration": [ + "id", + "body", + "modifiers", + ], + "TSNamedTupleMember": [ + "label", + "elementType", + ], + "TSNamespaceExportDeclaration": [ + "id", + ], + "TSNeverKeyword": [], + "TSNonNullExpression": [ + "expression", + ], + "TSNullKeyword": [], + "TSNumberKeyword": [], + "TSObjectKeyword": [], + "TSOptionalType": [ + "typeAnnotation", + ], + "TSParameterProperty": [ + "parameter", + "decorators", + ], + "TSParenthesizedType": [ + "typeAnnotation", + ], + "TSPrivateKeyword": [], + "TSPropertySignature": [ + "key", + "typeAnnotation", + "initializer", + ], + "TSProtectedKeyword": [], + "TSPublicKeyword": [], + "TSQualifiedName": [ + "left", + "right", + ], + "TSReadonlyKeyword": [], + "TSRestType": [ + "typeAnnotation", + ], + "TSStaticKeyword": [], + "TSStringKeyword": [], + "TSSymbolKeyword": [], + "TSTemplateLiteralType": [ + "quasis", + "types", + ], + "TSThisType": [], + "TSTupleType": [ + "elementTypes", + ], + "TSTypeAliasDeclaration": [ + "id", + "typeParameters", + "typeAnnotation", + ], + "TSTypeAnnotation": [ + "typeAnnotation", + ], + "TSTypeAssertion": [ + "typeAnnotation", + "expression", + ], + "TSTypeLiteral": [ + "members", + ], + "TSTypeOperator": [ + "typeAnnotation", + ], + "TSTypeParameter": [ + "constraint", + "default", + "name", + ], + "TSTypeParameterDeclaration": [ + "params", + ], + "TSTypeParameterInstantiation": [ + "params", + ], + "TSTypePredicate": [ + "parameterName", + "typeAnnotation", + ], + "TSTypeQuery": [ + "exprName", + "typeParameters", + ], + "TSTypeReference": [ + "typeName", + "typeParameters", + ], + "TSUndefinedKeyword": [], + "TSUnionType": [ + "types", + ], + "TSUnknownKeyword": [], + "TSVoidKeyword": [], + "TaggedTemplateExpression": [ + "tag", + "quasi", + "typeParameters", + ], + "TemplateElement": [], + "TemplateLiteral": [ + "quasis", + "expressions", + ], + "ThisExpression": [], + "ThisTypeAnnotation": [], + "ThrowStatement": [ + "argument", + ], + "TopicReference": [], + "TryStatement": [ + "block", + "handler", + "finalizer", + ], + "TupleExpression": [ + "elements", + ], + "TupleTypeAnnotation": [ + "types", + ], + "TypeAlias": [ + "id", + "typeParameters", + "right", + ], + "TypeAnnotation": [ + "typeAnnotation", + ], + "TypeCastExpression": [ + "expression", + "typeAnnotation", + ], + "TypeParameter": [ + "bound", + "default", + "variance", + ], + "TypeParameterDeclaration": [ + "params", + ], + "TypeParameterInstantiation": [ + "params", + ], + "TypeofTypeAnnotation": [ + "argument", + ], + "UnaryExpression": [ + "argument", + ], + "UnionTypeAnnotation": [ + "types", + ], + "UpdateExpression": [ + "argument", + ], + "V8IntrinsicIdentifier": [], + "VariableDeclaration": [ + "declarations", + ], + "VariableDeclarator": [ + "id", + "init", + ], + "Variance": [], + "VoidTypeAnnotation": [], + "WhileStatement": [ + "test", + "body", + ], + "WithStatement": [ + "object", + "body", + ], + "YieldExpression": [ + "argument", + ], +} +`; + +exports[`visitor keys estree-json 1`] = ` +{ + "ArrayExpression": [ + "elements", + ], + "BooleanLiteral": [], + "Identifier": [], + "JsonRoot": [ + "node", + ], + "NullLiteral": [], + "NumericLiteral": [], + "ObjectExpression": [ + "properties", + ], + "ObjectProperty": [ + "key", + "value", + ], + "StringLiteral": [], + "TemplateElement": [], + "TemplateLiteral": [ + "quasis", + ], + "UnaryExpression": [ + "argument", + ], +} +`; + +exports[`visitor keys glimmer 1`] = ` +{ + "AttrNode": [ + "value", + ], + "Block": [ + "body", + ], + "BlockStatement": [ + "path", + "params", + "hash", + "program", + "inverse", + ], + "BooleanLiteral": [], + "CommentStatement": [], + "Component": [ + "head", + "attributes", + "modifiers", + "children", + "comments", + ], + "ConcatStatement": [ + "parts", + ], + "ElementModifierStatement": [ + "path", + "params", + "hash", + ], + "ElementNode": [ + "attributes", + "modifiers", + "children", + "comments", + ], + "Hash": [ + "pairs", + ], + "HashPair": [ + "value", + ], + "MustacheCommentStatement": [], + "MustacheStatement": [ + "path", + "params", + "hash", + ], + "NamedBlock": [ + "attributes", + "modifiers", + "children", + "comments", + ], + "NullLiteral": [], + "NumberLiteral": [], + "PartialStatement": [ + "name", + "params", + "hash", + ], + "PathExpression": [], + "PathHead": [], + "Program": [ + "body", + ], + "SimpleElement": [ + "attributes", + "modifiers", + "children", + "comments", + ], + "StringLiteral": [], + "SubExpression": [ + "path", + "params", + "hash", + ], + "Template": [ + "body", + ], + "TextNode": [], + "UndefinedLiteral": [], +} +`; + +exports[`visitor keys graphql 1`] = ` +{ + "Argument": [ + "name", + "value", + ], + "BooleanValue": [], + "Directive": [ + "name", + "arguments", + ], + "DirectiveDefinition": [ + "description", + "name", + "arguments", + "locations", + ], + "Document": [ + "definitions", + ], + "EnumTypeDefinition": [ + "description", + "name", + "directives", + "values", + ], + "EnumTypeExtension": [ + "name", + "directives", + "values", + ], + "EnumValue": [], + "EnumValueDefinition": [ + "description", + "name", + "directives", + ], + "Field": [ + "alias", + "name", + "arguments", + "directives", + "selectionSet", + ], + "FieldDefinition": [ + "description", + "name", + "arguments", + "type", + "directives", + ], + "FloatValue": [], + "FragmentDefinition": [ + "name", + "variableDefinitions", + "typeCondition", + "directives", + "selectionSet", + ], + "FragmentSpread": [ + "name", + "directives", + ], + "InlineFragment": [ + "typeCondition", + "directives", + "selectionSet", + ], + "InputObjectTypeDefinition": [ + "description", + "name", + "directives", + "fields", + ], + "InputObjectTypeExtension": [ + "name", + "directives", + "fields", + ], + "InputValueDefinition": [ + "description", + "name", + "type", + "defaultValue", + "directives", + ], + "IntValue": [], + "InterfaceTypeDefinition": [ + "description", + "name", + "interfaces", + "directives", + "fields", + ], + "InterfaceTypeExtension": [ + "name", + "interfaces", + "directives", + "fields", + ], + "ListType": [ + "type", + ], + "ListValue": [ + "values", + ], + "Name": [], + "NamedType": [ + "name", + ], + "NonNullType": [ + "type", + ], + "NullValue": [], + "ObjectField": [ + "name", + "value", + ], + "ObjectTypeDefinition": [ + "description", + "name", + "interfaces", + "directives", + "fields", + ], + "ObjectTypeExtension": [ + "name", + "interfaces", + "directives", + "fields", + ], + "ObjectValue": [ + "fields", + ], + "OperationDefinition": [ + "name", + "variableDefinitions", + "directives", + "selectionSet", + ], + "OperationTypeDefinition": [ + "type", + ], + "ScalarTypeDefinition": [ + "description", + "name", + "directives", + ], + "ScalarTypeExtension": [ + "name", + "directives", + ], + "SchemaDefinition": [ + "description", + "directives", + "operationTypes", + ], + "SchemaExtension": [ + "directives", + "operationTypes", + ], + "SelectionSet": [ + "selections", + ], + "StringValue": [], + "UnionTypeDefinition": [ + "description", + "name", + "directives", + "types", + ], + "UnionTypeExtension": [ + "name", + "directives", + "types", + ], + "Variable": [ + "name", + ], + "VariableDefinition": [ + "variable", + "type", + "defaultValue", + "directives", + ], +} +`; + +exports[`visitor keys html 1`] = ` +{ + "attribute": [], + "cdata": [], + "comment": [], + "docType": [], + "element": [ + "attrs", + "children", + ], + "front-matter": [], + "ieConditionalComment": [ + "children", + ], + "ieConditionalEndComment": [], + "ieConditionalStartComment": [], + "interpolation": [ + "children", + ], + "root": [ + "children", + ], + "text": [ + "children", + ], +} +`; + +exports[`visitor keys postcss 1`] = ` +{ + "css-atrule": [ + "selector", + "params", + "value", + "nodes", + ], + "css-comment": [], + "css-decl": [ + "value", + "selector", + "nodes", + ], + "css-root": [ + "nodes", + ], + "css-rule": [ + "selector", + "nodes", + ], + "front-matter": [], + "media-colon": [], + "media-feature": [], + "media-feature-expression": [ + "nodes", + ], + "media-keyword": [], + "media-query": [ + "nodes", + ], + "media-query-list": [ + "nodes", + ], + "media-type": [], + "media-unknown": [], + "media-url": [], + "media-value": [], + "selector-attribute": [], + "selector-class": [], + "selector-combinator": [ + "nodes", + ], + "selector-comment": [], + "selector-id": [], + "selector-nesting": [], + "selector-pseudo": [ + "nodes", + ], + "selector-root": [ + "nodes", + ], + "selector-selector": [ + "nodes", + ], + "selector-string": [], + "selector-tag": [], + "selector-universal": [], + "selector-unknown": [], + "value-atword": [], + "value-colon": [], + "value-comma": [], + "value-comma_group": [ + "groups", + ], + "value-comment": [], + "value-func": [ + "group", + ], + "value-number": [], + "value-operator": [], + "value-paren": [], + "value-paren_group": [ + "open", + "groups", + "close", + ], + "value-root": [ + "group", + ], + "value-string": [], + "value-unicode-range": [], + "value-unknown": [], + "value-value": [ + "group", + ], + "value-word": [], +} +`; + +exports[`visitor keys remark 1`] = ` +{ + "blockquote": [ + "children", + ], + "break": [], + "code": [], + "definition": [], + "delete": [ + "children", + ], + "emphasis": [ + "children", + ], + "esComment": [], + "export": [], + "footnote": [ + "children", + ], + "footnoteDefinition": [ + "children", + ], + "footnoteReference": [], + "front-matter": [], + "heading": [ + "children", + ], + "html": [], + "image": [], + "imageReference": [], + "import": [], + "importExport": [], + "inlineCode": [], + "inlineMath": [], + "jsx": [], + "link": [ + "children", + ], + "linkReference": [ + "children", + ], + "liquidNode": [], + "list": [ + "children", + ], + "listItem": [ + "children", + ], + "math": [], + "paragraph": [ + "children", + ], + "root": [ + "children", + ], + "sentence": [ + "children", + ], + "strong": [ + "children", + ], + "table": [ + "children", + ], + "tableCell": [ + "children", + ], + "tableRow": [ + "children", + ], + "text": [], + "thematicBreak": [], + "whitespace": [], + "wikiLink": [], + "word": [], +} +`; + +exports[`visitor keys yaml 1`] = ` +{ + "alias": [ + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "anchor": [ + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "blockFolded": [ + "children", + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "blockLiteral": [ + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "comment": [ + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "directive": [ + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "document": [ + "head", + "body", + "children", + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "documentBody": [ + "children", + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "documentHead": [ + "children", + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "flowMapping": [ + "children", + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "flowMappingItem": [ + "key", + "value", + "children", + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "flowSequence": [ + "children", + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "flowSequenceItem": [ + "children", + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "mapping": [ + "children", + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "mappingItem": [ + "key", + "value", + "children", + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "mappingKey": [ + "content", + "children", + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "mappingValue": [ + "content", + "children", + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "plain": [ + "children", + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "quoteDouble": [ + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "quoteSingle": [ + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "root": [ + "children", + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "sequence": [ + "children", + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "sequenceItem": [ + "content", + "children", + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], + "tag": [ + "anchor", + "tag", + "indicatorComment", + "leadingComments", + "middleComments", + "trailingComment", + "endComments", + ], +} +`; diff --git a/tests/unit/visitor-keys.js b/tests/unit/visitor-keys.js new file mode 100644 index 000000000..f76f3bc6f --- /dev/null +++ b/tests/unit/visitor-keys.js @@ -0,0 +1,24 @@ +import estreeVisitorKeys from "../../src/language-js/traverse/visitor-keys.evaluate.js"; +import jsonVisitorKeys from "../../src/language-js/traverse/json-visitor-keys.js"; +import postcssVisitorKeys from "../../src/language-css/visitor-keys.js"; +import graphqlVisitorKeys from "../../src/language-graphql/visitor-keys.evaluate.js"; +import glimmerVisitorKeys from "../../src/language-handlebars/visitor-keys.evaluate.js"; +import htmlVisitorKeys from "../../src/language-html/visitor-keys.js"; +import remarkVisitorKeys from "../../src/language-markdown/visitor-keys.js"; +import yamlVisitorKeys from "../../src/language-yaml/visitor-keys.js"; + +// Keep eye on package change +describe("visitor keys", () => { + test.each([ + { name: "estree", visitorKeys: estreeVisitorKeys }, + { name: "estree-json", visitorKeys: jsonVisitorKeys }, + { name: "postcss", visitorKeys: postcssVisitorKeys }, + { name: "graphql", visitorKeys: graphqlVisitorKeys }, + { name: "glimmer", visitorKeys: glimmerVisitorKeys }, + { name: "html", visitorKeys: htmlVisitorKeys }, + { name: "remark", visitorKeys: remarkVisitorKeys }, + { name: "yaml", visitorKeys: yamlVisitorKeys }, + ])("$name", ({ visitorKeys }) => { + expect(visitorKeys).toMatchSnapshot(); + }); +}); diff --git a/website/playground/util.js b/website/playground/util.js index 592336c28..4485b9eff 100644 --- a/website/playground/util.js +++ b/website/playground/util.js @@ -60,6 +60,7 @@ const astAutoFold = { mdast: /^\s*"position":/, yaml: /^\s*"position":/, glimmer: /^\s*"loc":/, + graphql: /^\s*"loc":/, }; export function getAstAutoFold(parser) { @@ -90,8 +91,8 @@ export function getAstAutoFold(parser) { return astAutoFold.mdast; case "yaml": return astAutoFold.yaml; - case "glimmer": - return astAutoFold.glimmer; + default: + return astAutoFold[parser]; } } diff --git a/yarn.lock b/yarn.lock index 82e588781..b83515ceb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6878,6 +6878,7 @@ __metadata: "@types/jest": 28.1.1 "@typescript-eslint/eslint-plugin": 5.33.0 "@typescript-eslint/typescript-estree": 5.33.0 + "@typescript-eslint/visitor-keys": 5.33.0 acorn: 8.8.0 acorn-jsx: 5.3.2 angular-estree-parser: 2.5.1 @@ -6972,6 +6973,7 @@ __metadata: string-width: 5.0.1 strip-ansi: 7.0.1 tempy: 3.0.0 + to-fast-properties: 4.0.0 typescript: 4.7.4 unicode-regex: 3.0.0 unified: 9.2.2 @@ -8097,6 +8099,13 @@ __metadata: languageName: node linkType: hard +"to-fast-properties@npm:4.0.0": + version: 4.0.0 + resolution: "to-fast-properties@npm:4.0.0" + checksum: c72297bdd126a7e7ffe9cc6230eee60182f0439a7abb19d281e73137880d87d12fc971a47ff4bd1e0dc68e17262344fbf98499400ff5e341db431715c3620346 + languageName: node + linkType: hard + "to-fast-properties@npm:^2.0.0": version: 2.0.0 resolution: "to-fast-properties@npm:2.0.0"