Add getVisitorKeys (#13255)
This commit is contained in:
1 parent
a7d1ccb72b
commit
5fa0802315
32 files changed
+2279
-122
No files matched your search
@@ -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<AST>;
|
||||
```
|
||||
|
||||
#### (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](<https://en.wikipedia.org/wiki/Cycle_(graph_theory)>), 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>): 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:
|
||||
|
||||
+3
-1
@@ -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",
|
||||
|
||||
@@ -9,6 +9,7 @@ const ignoredProperties = new Set([
|
||||
"before",
|
||||
"after",
|
||||
"trailingComma",
|
||||
"spaces",
|
||||
]);
|
||||
|
||||
function clean(ast, newObj, parent) {
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -0,0 +1 @@
|
||||
export { QueryDocumentKeys as default } from "graphql/language/ast.mjs";
|
||||
@@ -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;
|
||||
@@ -0,0 +1,3 @@
|
||||
import visitorKeys from "@glimmer/syntax/dist/commonjs/es2017/lib/v1/visitor-keys.js";
|
||||
|
||||
export default visitorKeys.default;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
]);
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
+37
-52
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
+41
-24
@@ -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;
|
||||
+23
-28
@@ -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."
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user