diff --git a/eslint.config.js b/eslint.config.js index d77b1f914..e41f532a9 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -122,6 +122,7 @@ export default [ // Internal rules "prettier-internal-rules/jsx-identifier-case": "error", + "prettier-internal-rules/massage-ast-parameter-names": "error", "prettier-internal-rules/no-identifier-n": "error", "prettier-internal-rules/prefer-fs-promises-submodule": "error", diff --git a/scripts/tools/eslint-plugin-prettier-internal-rules/index.js b/scripts/tools/eslint-plugin-prettier-internal-rules/index.js index 0bb181d79..e47345c6d 100644 --- a/scripts/tools/eslint-plugin-prettier-internal-rules/index.js +++ b/scripts/tools/eslint-plugin-prettier-internal-rules/index.js @@ -7,6 +7,7 @@ module.exports = { "directly-loc-start-end": require("./directly-loc-start-end.js"), "flat-ast-path-call": require("./flat-ast-path-call.js"), "jsx-identifier-case": require("./jsx-identifier-case.js"), + "massage-ast-parameter-names": require("./massage-ast-parameter-names.js"), "no-conflicting-comment-check-flags": require("./no-conflicting-comment-check-flags.js"), "no-doc-public-import": require("./no-doc-public-import.js"), "no-empty-flat-contents-for-if-break": require("./no-empty-flat-contents-for-if-break.js"), diff --git a/scripts/tools/eslint-plugin-prettier-internal-rules/massage-ast-parameter-names.js b/scripts/tools/eslint-plugin-prettier-internal-rules/massage-ast-parameter-names.js new file mode 100644 index 000000000..be4e6963c --- /dev/null +++ b/scripts/tools/eslint-plugin-prettier-internal-rules/massage-ast-parameter-names.js @@ -0,0 +1,65 @@ +"use strict"; + +const MESSAGE_ID = "massage-ast-parameter-names"; + +const massageAstFunctionSelector = [ + "FunctionDeclaration", + "[async!=true]", + "[generator!=true]", + '[id.type="Identifier"]', + '[id.name="clean"]', +].join(""); + +const getVariableIdentifiers = ({ identifiers, references }) => [ + ...new Set([ + ...identifiers, + ...references.map(({ identifier }) => identifier), + ]), +]; + +module.exports = { + meta: { + type: "suggestion", + docs: { + url: "https://github.com/prettier/prettier/blob/main/scripts/tools/eslint-plugin-prettier-internal-rules/massage-ast-parameter-names.js", + }, + messages: { + [MESSAGE_ID]: + "The {{name}} node parameter '{{original}}' should be named '{{name}}'.", + }, + fixable: "code", + schema: { + type: "array", + uniqueItems: true, + }, + }, + create: (context) => + Object.fromEntries( + ["original", "cloned"].map((name, index) => [ + `${massageAstFunctionSelector} > Identifier[name!="${name}"].params:nth-child(${index + 1})`, + (parameter) => { + const variables = context.sourceCode.getDeclaredVariables( + parameter.parent, + ); + const variable = variables.find( + (variable) => variable.name === parameter.name, + ); + if (!variable) { + throw new Error("Unexpected error."); + } + + context.report({ + node: parameter, + messageId: MESSAGE_ID, + data: { original: parameter.name, name }, + // Good enough for our use case + // A prefect fix should be https://github.com/sindresorhus/eslint-plugin-unicorn/blob/702d51bed176a9c2c93bc4a2ca52e700dd0c2339/rules/fix/rename-variable.js#L5 + fix: (fixer) => + getVariableIdentifiers(variable).map((node) => + fixer.replaceText(node, name), + ), + }); + }, + ]), + ), +}; diff --git a/scripts/tools/eslint-plugin-prettier-internal-rules/test.js b/scripts/tools/eslint-plugin-prettier-internal-rules/test.js index af044d8c3..369430c75 100644 --- a/scripts/tools/eslint-plugin-prettier-internal-rules/test.js +++ b/scripts/tools/eslint-plugin-prettier-internal-rules/test.js @@ -970,3 +970,22 @@ test("prefer-ast-path-getters", { }, ], }); + +test("massage-ast-parameter-names", { + valid: [ + "function notNamedClean(a, b) {}", + "function clean(original, cloned) {}", + ], + invalid: [ + { + code: "function clean(theOriginalNode, cloned) {delete theOriginalNode.property}", + output: "function clean(original, cloned) {delete original.property}", + errors: 1, + }, + { + code: "function clean(original, theClonedNode) {delete theClonedNode.property}", + output: "function clean(original, cloned) {delete cloned.property}", + errors: 1, + }, + ], +}); diff --git a/src/index.d.ts b/src/index.d.ts index 2b01fbd50..747027f9b 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -496,10 +496,12 @@ export interface Printer { insertPragma?: (text: string) => string; /** * @returns `null` if you want to remove this node - * @returns `void` if you want to use modified newNode + * @returns `void` if you want to use modified `cloned` * @returns anything if you want to replace the node with it */ - massageAstNode?: ((node: any, newNode: any, parent: any) => any) | undefined; + massageAstNode?: + | ((original: any, cloned: any, parent: any) => any) + | undefined; hasPrettierIgnore?: ((path: AstPath) => boolean) | undefined; canAttachComment?: ((node: T) => boolean) | undefined; isBlockComment?: ((node: T) => boolean) | undefined; diff --git a/src/language-css/clean.js b/src/language-css/clean.js index d03c35819..d54d32358 100644 --- a/src/language-css/clean.js +++ b/src/language-css/clean.js @@ -11,135 +11,131 @@ const ignoredProperties = new Set([ "spaces", ]); -function clean(ast, newObj, parent) { - if (isFrontMatter(ast) && ast.lang === "yaml") { - delete newObj.value; +function clean(original, cloned, parent) { + if (isFrontMatter(original) && original.lang === "yaml") { + delete cloned.value; } if ( - ast.type === "css-comment" && + original.type === "css-comment" && parent.type === "css-root" && parent.nodes.length > 0 ) { // --insert-pragma // first non-front-matter comment if ( - parent.nodes[0] === ast || - (isFrontMatter(parent.nodes[0]) && parent.nodes[1] === ast) + parent.nodes[0] === original || + (isFrontMatter(parent.nodes[0]) && parent.nodes[1] === original) ) { /** * something * * @format */ - delete newObj.text; + delete cloned.text; // standalone pragma - if (/^\*\s*@(?:format|prettier)\s*$/.test(ast.text)) { + if (/^\*\s*@(?:format|prettier)\s*$/.test(original.text)) { return null; } } // Last comment is not parsed, when omitting semicolon, #8675 - if (parent.type === "css-root" && parent.nodes.at(-1) === ast) { + if (parent.type === "css-root" && parent.nodes.at(-1) === original) { return null; } } - if (ast.type === "value-root") { - delete newObj.text; + if (original.type === "value-root") { + delete cloned.text; } if ( - ast.type === "media-query" || - ast.type === "media-query-list" || - ast.type === "media-feature-expression" + original.type === "media-query" || + original.type === "media-query-list" || + original.type === "media-feature-expression" ) { - delete newObj.value; + delete cloned.value; } - if (ast.type === "css-rule") { - delete newObj.params; - } - - if (ast.type === "selector-combinator") { - newObj.value = newObj.value.replaceAll(/\s+/g, " "); - } - - if (ast.type === "media-feature") { - newObj.value = newObj.value.replaceAll(" ", ""); + if (original.type === "css-rule") { + delete cloned.params; } if ( - (ast.type === "value-word" && - ((ast.isColor && ast.isHex) || + (original.type === "media-feature" || + original.type === "media-keyword" || + original.type === "media-type" || + original.type === "media-unknown" || + original.type === "media-url" || + original.type === "media-value" || + original.type === "selector-attribute" || + original.type === "selector-string" || + original.type === "selector-class" || + original.type === "selector-combinator" || + original.type === "value-string") && + original.value + ) { + cloned.value = cleanCSSStrings(original.value); + } + + if (original.type === "selector-combinator") { + cloned.value = cloned.value.replaceAll(/\s+/g, " "); + } + + if (original.type === "media-feature") { + cloned.value = cloned.value.replaceAll(" ", ""); + } + + if ( + (original.type === "value-word" && + ((original.isColor && original.isHex) || ["initial", "inherit", "unset", "revert"].includes( - newObj.value.toLowerCase(), + original.value.toLowerCase(), ))) || - ast.type === "media-feature" || - ast.type === "selector-root-invalid" || - ast.type === "selector-pseudo" + original.type === "media-feature" || + original.type === "selector-root-invalid" || + original.type === "selector-pseudo" ) { - newObj.value = newObj.value.toLowerCase(); + cloned.value = cloned.value.toLowerCase(); } - if (ast.type === "css-decl") { - newObj.prop = newObj.prop.toLowerCase(); + if (original.type === "css-decl") { + cloned.prop = original.prop.toLowerCase(); } - if (ast.type === "css-atrule" || ast.type === "css-import") { - newObj.name = newObj.name.toLowerCase(); + if (original.type === "css-atrule" || original.type === "css-import") { + cloned.name = original.name.toLowerCase(); } - if (ast.type === "value-number") { - newObj.unit = newObj.unit.toLowerCase(); + if (original.type === "value-number") { + cloned.unit = original.unit.toLowerCase(); } - if (ast.type === "value-unknown") { - newObj.value = newObj.value.replaceAll(/;$/g, ""); + if (original.type === "value-unknown") { + cloned.value = cloned.value.replaceAll(/;$/g, ""); } - if ( - (ast.type === "media-feature" || - ast.type === "media-keyword" || - ast.type === "media-type" || - ast.type === "media-unknown" || - ast.type === "media-url" || - ast.type === "media-value" || - ast.type === "selector-attribute" || - ast.type === "selector-string" || - ast.type === "selector-class" || - ast.type === "selector-combinator" || - ast.type === "value-string") && - newObj.value - ) { - newObj.value = cleanCSSStrings(newObj.value); - } + if (original.type === "selector-attribute") { + cloned.attribute = original.attribute.trim(); - if (ast.type === "selector-attribute") { - newObj.attribute = newObj.attribute.trim(); - - if (newObj.namespace && typeof newObj.namespace === "string") { - newObj.namespace = newObj.namespace.trim(); - - if (newObj.namespace.length === 0) { - newObj.namespace = true; - } + if (original.namespace && typeof original.namespace === "string") { + cloned.namespace = original.namespace.trim() || true; } - if (newObj.value) { - newObj.value = newObj.value.trim().replaceAll(/^["']|["']$/g, ""); - delete newObj.quoted; + if (original.value) { + cloned.value = cloned.value.trim().replaceAll(/^["']|["']$/g, ""); + delete cloned.quoted; } } if ( - (ast.type === "media-value" || - ast.type === "media-type" || - ast.type === "value-number" || - ast.type === "selector-root-invalid" || - ast.type === "selector-class" || - ast.type === "selector-combinator" || - ast.type === "selector-tag") && - newObj.value + (original.type === "media-value" || + original.type === "media-type" || + original.type === "value-number" || + original.type === "selector-root-invalid" || + original.type === "selector-class" || + original.type === "selector-combinator" || + original.type === "selector-tag") && + original.value ) { - newObj.value = newObj.value.replaceAll( + cloned.value = cloned.value.replaceAll( /([\d+.e-]+)([a-z]*)/gi, (match, numStr, unit) => { const num = Number(numStr); @@ -148,33 +144,36 @@ function clean(ast, newObj, parent) { ); } - if (ast.type === "selector-tag") { - const lowercasedValue = ast.value.toLowerCase(); + if (original.type === "selector-tag") { + const lowercasedValue = cloned.value.toLowerCase(); if (["from", "to"].includes(lowercasedValue)) { - newObj.value = lowercasedValue; + cloned.value = lowercasedValue; } } // Workaround when `postcss-values-parser` parse `not`, `and` or `or` keywords as `value-func` - if (ast.type === "css-atrule" && ast.name.toLowerCase() === "supports") { - delete newObj.value; + if ( + original.type === "css-atrule" && + original.name.toLowerCase() === "supports" + ) { + delete cloned.value; } // Workaround for SCSS nested properties - if (ast.type === "selector-unknown") { - delete newObj.value; + if (original.type === "selector-unknown") { + delete cloned.value; } // Workaround for SCSS arbitrary arguments - if (ast.type === "value-comma_group") { - const index = ast.groups.findIndex( + if (original.type === "value-comma_group") { + const index = original.groups.findIndex( (node) => node.type === "value-number" && node.unit === "...", ); if (index !== -1) { - newObj.groups[index].unit = ""; - newObj.groups.splice(index + 1, 0, { + cloned.groups[index].unit = ""; + cloned.groups.splice(index + 1, 0, { type: "value-word", value: "...", isColor: false, @@ -185,8 +184,8 @@ function clean(ast, newObj, parent) { // We parse `@var[ foo ]` and `@var[foo]` differently if ( - ast.type === "value-comma_group" && - ast.groups.some( + original.type === "value-comma_group" && + original.groups.some( (node) => (node.type === "value-atword" && node.value.endsWith("[")) || (node.type === "value-word" && node.value.startsWith("]")), @@ -194,7 +193,7 @@ function clean(ast, newObj, parent) { ) { return { type: "value-atword", - value: ast.groups.map((node) => node.value).join(""), + value: original.groups.map((node) => node.value).join(""), group: { open: null, close: null, diff --git a/src/language-graphql/printer-graphql.js b/src/language-graphql/printer-graphql.js index 31d6037b2..4eb0bfef7 100644 --- a/src/language-graphql/printer-graphql.js +++ b/src/language-graphql/printer-graphql.js @@ -500,11 +500,15 @@ function printVariableDefinitions(path, print) { ]); } -function clean(node, newNode /* , parent */) { +function clean(original, cloned /* , parent */) { // We print single line `""" string """` as multiple line string, // and the parser ignores space in multiple line string - if (node.kind === "StringValue" && node.block && !node.value.includes("\n")) { - newNode.value = newNode.value.trim(); + if ( + original.kind === "StringValue" && + original.block && + !original.value.includes("\n") + ) { + cloned.value = original.value.trim(); } } clean.ignoredProperties = new Set(["loc", "comments"]); diff --git a/src/language-handlebars/clean.js b/src/language-handlebars/clean.js index 4c114b6cd..5eeebb157 100644 --- a/src/language-handlebars/clean.js +++ b/src/language-handlebars/clean.js @@ -1,29 +1,29 @@ import htmlWhitespaceUtils from "../utils/html-whitespace-utils.js"; -function clean(ast, newNode /*, parent*/) { +function clean(original, cloned /*, parent*/) { // (Glimmer/HTML) ignore TextNode - if (ast.type === "TextNode") { - const trimmed = ast.chars.trim(); + if (original.type === "TextNode") { + const trimmed = original.chars.trim(); if (!trimmed) { return null; } - newNode.chars = htmlWhitespaceUtils.split(trimmed).join(" "); + cloned.chars = htmlWhitespaceUtils.split(trimmed).join(" "); } - if (ast.type === "ElementNode") { - delete newNode.startTag; - delete newNode.parts; - delete newNode.endTag; - delete newNode.nameNode; + if (original.type === "ElementNode") { + delete cloned.startTag; + delete cloned.parts; + delete cloned.endTag; + delete cloned.nameNode; } - if (ast.type === "Block" || ast.type === "ElementNode") { - delete newNode.blockParamNodes; + if (original.type === "Block" || original.type === "ElementNode") { + delete cloned.blockParamNodes; } // `class` is reformatted - if (ast.type === "AttrNode" && ast.name.toLowerCase() === "class") { - delete newNode.value; + if (original.type === "AttrNode" && original.name.toLowerCase() === "class") { + delete cloned.value; } } diff --git a/src/language-html/clean.js b/src/language-html/clean.js index 50b8ed3b1..0b8ac2e1f 100644 --- a/src/language-html/clean.js +++ b/src/language-html/clean.js @@ -23,27 +23,34 @@ const embeddedAngularControlFlowBlocks = new Set([ "case", ]); -function clean(ast, newNode) { - if (ast.type === "text" || ast.type === "comment") { +function clean(original, cloned) { + if (original.type === "text" || original.type === "comment") { return null; } // may be formatted by multiparser - if (isFrontMatter(ast) || ast.type === "yaml" || ast.type === "toml") { + if ( + isFrontMatter(original) || + original.type === "yaml" || + original.type === "toml" + ) { return null; } - if (ast.type === "attribute") { - delete newNode.value; + if (original.type === "attribute") { + delete cloned.value; } - if (ast.type === "docType") { - delete newNode.value; + if (original.type === "docType") { + delete cloned.value; } - if (ast.type === "angularControlFlowBlock" && newNode.parameters?.children) { - for (const parameter of newNode.parameters.children) { - if (embeddedAngularControlFlowBlocks.has(ast.name)) { + if ( + original.type === "angularControlFlowBlock" && + original.parameters?.children + ) { + for (const parameter of cloned.parameters.children) { + if (embeddedAngularControlFlowBlocks.has(original.name)) { delete parameter.expression; } else { parameter.expression = parameter.expression.trim(); @@ -51,8 +58,8 @@ function clean(ast, newNode) { } } - if (ast.type === "angularIcuExpression") { - newNode.switchValue = ast.switchValue.trim(); + if (original.type === "angularIcuExpression") { + cloned.switchValue = original.switchValue.trim(); } } diff --git a/src/language-js/clean.js b/src/language-js/clean.js index 11163c29c..41e838439 100644 --- a/src/language-js/clean.js +++ b/src/language-js/clean.js @@ -23,46 +23,46 @@ const removeTemplateElementsValue = (node) => { } }; -function clean(ast, newObj, parent) { - if (ast.type === "Program") { - delete newObj.sourceType; +function clean(original, cloned, parent) { + if (original.type === "Program") { + delete cloned.sourceType; } if ( - (ast.type === "BigIntLiteral" || - ast.type === "BigIntLiteralTypeAnnotation") && - newObj.value + (original.type === "BigIntLiteral" || + original.type === "BigIntLiteralTypeAnnotation") && + original.value ) { - newObj.value = newObj.value.toLowerCase(); + cloned.value = original.value.toLowerCase(); } if ( - (ast.type === "BigIntLiteral" || ast.type === "Literal") && - newObj.bigint + (original.type === "BigIntLiteral" || original.type === "Literal") && + original.bigint ) { - newObj.bigint = newObj.bigint.toLowerCase(); + cloned.bigint = original.bigint.toLowerCase(); } - if (ast.type === "DecimalLiteral") { - newObj.value = Number(newObj.value); + if (original.type === "DecimalLiteral") { + cloned.value = Number(original.value); } - if (ast.type === "Literal" && newObj.decimal) { - newObj.decimal = Number(newObj.decimal); + if (original.type === "Literal" && cloned.decimal) { + cloned.decimal = Number(original.decimal); } // We remove extra `;` and add them when needed - if (ast.type === "EmptyStatement") { + if (original.type === "EmptyStatement") { return null; } // We move text around, including whitespaces and add {" "} - if (ast.type === "JSXText") { + if (original.type === "JSXText") { return null; } if ( - ast.type === "JSXExpressionContainer" && - (ast.expression.type === "Literal" || - ast.expression.type === "StringLiteral") && - ast.expression.value === " " + original.type === "JSXExpressionContainer" && + (original.expression.type === "Literal" || + original.expression.type === "StringLiteral") && + original.expression.value === " " ) { return null; } @@ -71,35 +71,35 @@ function clean(ast, newObj, parent) { // And {key: value} into {'key': value}. // Also for (some) number keys. if ( - (ast.type === "Property" || - ast.type === "ObjectProperty" || - ast.type === "MethodDefinition" || - ast.type === "ClassProperty" || - ast.type === "ClassMethod" || - ast.type === "PropertyDefinition" || - ast.type === "TSDeclareMethod" || - ast.type === "TSPropertySignature" || - ast.type === "ObjectTypeProperty") && - typeof ast.key === "object" && - ast.key && - (ast.key.type === "Literal" || - ast.key.type === "NumericLiteral" || - ast.key.type === "StringLiteral" || - ast.key.type === "Identifier") + (original.type === "Property" || + original.type === "ObjectProperty" || + original.type === "MethodDefinition" || + original.type === "ClassProperty" || + original.type === "ClassMethod" || + original.type === "PropertyDefinition" || + original.type === "TSDeclareMethod" || + original.type === "TSPropertySignature" || + original.type === "ObjectTypeProperty") && + typeof original.key === "object" && + original.key && + (original.key.type === "Literal" || + original.key.type === "NumericLiteral" || + original.key.type === "StringLiteral" || + original.key.type === "Identifier") ) { - delete newObj.key; + delete cloned.key; } // Remove raw and cooked values from TemplateElement when it's CSS // styled-jsx if ( - ast.type === "JSXElement" && - ast.openingElement.name.name === "style" && - ast.openingElement.attributes.some( + original.type === "JSXElement" && + original.openingElement.name.name === "style" && + original.openingElement.attributes.some( (attr) => attr.type === "JSXAttribute" && attr.name.name === "jsx", ) ) { - for (const { type, expression } of newObj.children) { + for (const { type, expression } of cloned.children) { if ( type === "JSXExpressionContainer" && expression.type === "TemplateLiteral" @@ -111,39 +111,39 @@ function clean(ast, newObj, parent) { // CSS template literals in css prop if ( - ast.type === "JSXAttribute" && - ast.name.name === "css" && - ast.value.type === "JSXExpressionContainer" && - ast.value.expression.type === "TemplateLiteral" + original.type === "JSXAttribute" && + original.name.name === "css" && + original.value.type === "JSXExpressionContainer" && + original.value.expression.type === "TemplateLiteral" ) { - removeTemplateElementsValue(newObj.value.expression); + removeTemplateElementsValue(cloned.value.expression); } // We change quotes if ( - ast.type === "JSXAttribute" && - ast.value?.type === "Literal" && - /["']|"|'/.test(ast.value.value) + original.type === "JSXAttribute" && + original.value?.type === "Literal" && + /["']|"|'/.test(original.value.value) ) { - newObj.value.value = newObj.value.value.replaceAll( + cloned.value.value = original.value.value.replaceAll( /["']|"|'/g, '"', ); } // Angular Components: Inline HTML template and Inline CSS styles - const expression = ast.expression || ast.callee; + const expression = original.expression || original.callee; if ( - ast.type === "Decorator" && + original.type === "Decorator" && expression.type === "CallExpression" && expression.callee.name === "Component" && expression.arguments.length === 1 ) { - const astProps = ast.expression.arguments[0].properties; + const astProps = original.expression.arguments[0].properties; for (const [ index, prop, - ] of newObj.expression.arguments[0].properties.entries()) { + ] of cloned.expression.arguments[0].properties.entries()) { switch (astProps[index].key.name) { case "styles": if (isArrayOrTupleExpression(prop.value)) { @@ -161,26 +161,26 @@ function clean(ast, newObj, parent) { // styled-components, graphql, markdown if ( - ast.type === "TaggedTemplateExpression" && - (ast.tag.type === "MemberExpression" || - (ast.tag.type === "Identifier" && - (ast.tag.name === "gql" || - ast.tag.name === "graphql" || - ast.tag.name === "css" || - ast.tag.name === "md" || - ast.tag.name === "markdown" || - ast.tag.name === "html")) || - ast.tag.type === "CallExpression") + original.type === "TaggedTemplateExpression" && + (original.tag.type === "MemberExpression" || + (original.tag.type === "Identifier" && + (original.tag.name === "gql" || + original.tag.name === "graphql" || + original.tag.name === "css" || + original.tag.name === "md" || + original.tag.name === "markdown" || + original.tag.name === "html")) || + original.tag.type === "CallExpression") ) { - removeTemplateElementsValue(newObj.quasi); + removeTemplateElementsValue(cloned.quasi); } - if (ast.type === "TemplateLiteral") { + if (original.type === "TemplateLiteral") { // This checks for a leading comment that is exactly `/* GraphQL */` // In order to be in line with other implementations of this comment tag // we will not trim the comment value and we will expect exactly one space on // either side of the GraphQL string // Also see ./embed.js - const hasLanguageComment = ast.leadingComments?.some( + const hasLanguageComment = original.leadingComments?.some( (comment) => isBlockComment(comment) && ["GraphQL", "HTML"].some( @@ -192,30 +192,29 @@ function clean(ast, newObj, parent) { (parent.type === "CallExpression" && parent.callee.name === "graphql") || // TODO: check parser // `flow` and `typescript` don't have `leadingComments` - !ast.leadingComments + !original.leadingComments ) { - removeTemplateElementsValue(newObj); + removeTemplateElementsValue(cloned); } } // Prettier removes degenerate union and intersection types with only one member. if ( - (ast.type === "TSIntersectionType" || ast.type === "TSUnionType") && - ast.types.length === 1 + (original.type === "TSIntersectionType" || + original.type === "TSUnionType") && + original.types.length === 1 ) { - return newObj.types[0]; + return cloned.types[0]; } // We print `(a?.b!).c` as `(a?.b)!.c`, but `typescript` parse them differently if ( - ast.type === "ChainExpression" && - ast.expression.type === "TSNonNullExpression" + original.type === "ChainExpression" && + original.expression.type === "TSNonNullExpression" ) { // Ideally, we should swap these two nodes, but `type` is the only difference - [newObj.type, newObj.expression.type] = [ - newObj.expression.type, - newObj.type, - ]; + cloned.type = "TSNonNullExpression"; + cloned.expression.type = "ChainExpression"; } } diff --git a/src/language-json/printer-estree-json.js b/src/language-json/printer-estree-json.js index 257dabb44..6987bb4b2 100644 --- a/src/language-json/printer-estree-json.js +++ b/src/language-json/printer-estree-json.js @@ -80,33 +80,33 @@ const ignoredProperties = new Set([ "tokens", ]); -function clean(node, newNode /*, parent*/) { - const { type } = node; +function clean(original, cloned /*, parent*/) { + const { type } = original; // We print quoted key if (type === "ObjectProperty") { - const { key } = node; + const { key } = original; if (key.type === "Identifier") { - newNode.key = { type: "StringLiteral", value: key.name }; + cloned.key = { type: "StringLiteral", value: key.name }; } else if (key.type === "NumericLiteral") { - newNode.key = { type: "StringLiteral", value: String(key.value) }; + cloned.key = { type: "StringLiteral", value: String(key.value) }; } return; } - if (type === "UnaryExpression" && node.operator === "+") { - return newNode.argument; + if (type === "UnaryExpression" && original.operator === "+") { + return cloned.argument; } // We print holes in array as `null` if (type === "ArrayExpression") { - for (const [index, element] of node.elements.entries()) { + for (const [index, element] of original.elements.entries()) { if (element === null) { - newNode.elements.splice(index, 0, { type: "NullLiteral" }); + cloned.elements.splice(index, 0, { type: "NullLiteral" }); } } return; } // We print `TemplateLiteral` as string if (type === "TemplateLiteral") { - return { type: "StringLiteral", value: node.quasis[0].value.cooked }; + return { type: "StringLiteral", value: original.quasis[0].value.cooked }; } } diff --git a/src/language-markdown/clean.js b/src/language-markdown/clean.js index 21daaf0ef..0fec2a1ce 100644 --- a/src/language-markdown/clean.js +++ b/src/language-markdown/clean.js @@ -7,75 +7,78 @@ const ignoredProperties = new Set([ "position", "raw", // front-matter ]); -function clean(ast, newObj, parent) { +function clean(original, cloned, parent) { // for codeblock if ( - ast.type === "front-matter" || - ast.type === "code" || - ast.type === "yaml" || - ast.type === "import" || - ast.type === "export" || - ast.type === "jsx" + original.type === "front-matter" || + original.type === "code" || + original.type === "yaml" || + original.type === "import" || + original.type === "export" || + original.type === "jsx" ) { - delete newObj.value; + delete cloned.value; } - if (ast.type === "list") { - delete newObj.isAligned; + if (original.type === "list") { + delete cloned.isAligned; } - if (ast.type === "list" || ast.type === "listItem") { - delete newObj.spread; + if (original.type === "list" || original.type === "listItem") { + delete cloned.spread; } // texts can be splitted or merged - if (ast.type === "text") { + if (original.type === "text") { return null; } - if (ast.type === "inlineCode") { - newObj.value = ast.value.replaceAll("\n", " "); + if (original.type === "inlineCode") { + cloned.value = original.value.replaceAll("\n", " "); } - if (ast.type === "wikiLink") { - newObj.value = ast.value.trim().replaceAll(/[\t\n]+/g, " "); + if (original.type === "wikiLink") { + cloned.value = original.value.trim().replaceAll(/[\t\n]+/g, " "); } if ( - ast.type === "definition" || - ast.type === "linkReference" || - ast.type === "imageReference" + original.type === "definition" || + original.type === "linkReference" || + original.type === "imageReference" ) { - newObj.label = collapseWhiteSpace(ast.label); + cloned.label = collapseWhiteSpace(original.label); } if ( - (ast.type === "link" || ast.type === "image") && - ast.url && - ast.url.includes("(") + (original.type === "link" || original.type === "image") && + original.url && + original.url.includes("(") ) { for (const character of "<>") { - newObj.url = ast.url.replaceAll(character, encodeURIComponent(character)); + cloned.url = original.url.replaceAll( + character, + encodeURIComponent(character), + ); } } if ( - (ast.type === "definition" || - ast.type === "link" || - ast.type === "image") && - ast.title + (original.type === "definition" || + original.type === "link" || + original.type === "image") && + original.title ) { - newObj.title = ast.title.replaceAll(/\\(?=["')])/g, ""); + cloned.title = original.title.replaceAll(/\\(?=["')])/g, ""); } // for insert pragma if ( parent?.type === "root" && parent.children.length > 0 && - (parent.children[0] === ast || - (isFrontMatter(parent.children[0]) && parent.children[1] === ast)) && - ast.type === "html" && - startWithPragma(ast.value) + (parent.children[0] === original || + (isFrontMatter(parent.children[0]) && parent.children[1] === original)) && + original.type === "html" && + startWithPragma(original.value) ) { return null; } diff --git a/src/language-yaml/printer-yaml.js b/src/language-yaml/printer-yaml.js index 281ea094e..439065c33 100644 --- a/src/language-yaml/printer-yaml.js +++ b/src/language-yaml/printer-yaml.js @@ -402,23 +402,23 @@ function printFlowScalarContent(nodeType, content, options) { ); } -function clean(node, newNode /*, parent */) { - if (isNode(newNode)) { - delete newNode.position; - switch (newNode.type) { +function clean(original, cloned /*, parent */) { + if (isNode(original)) { + switch (original.type) { case "comment": // insert pragma - if (isPragma(newNode.value)) { + if (isPragma(original.value)) { return null; } break; case "quoteDouble": case "quoteSingle": - newNode.type = "quote"; + cloned.type = "quote"; break; } } } +clean.ignoredProperties = new Set(["position"]); const printer = { preprocess, diff --git a/src/main/massage-ast.js b/src/main/massage-ast.js index d81ae054f..553c8733b 100644 --- a/src/main/massage-ast.js +++ b/src/main/massage-ast.js @@ -17,35 +17,35 @@ function massageAst(ast, options) { return recurse(ast); - function recurse(node, parent) { - if (!(node !== null && typeof node === "object")) { - return node; + function recurse(original, parent) { + if (!(original !== null && typeof original === "object")) { + return original; } - if (Array.isArray(node)) { - return node.map((child) => recurse(child, parent)).filter(Boolean); + if (Array.isArray(original)) { + return original.map((child) => recurse(child, parent)).filter(Boolean); } - const newObj = {}; - const childrenKeys = new Set(getVisitorKeys(node)); - for (const key in node) { - if (!Object.hasOwn(node, key) || ignoredProperties.has(key)) { + const cloned = {}; + const childrenKeys = new Set(getVisitorKeys(original)); + for (const key in original) { + if (!Object.hasOwn(original, key) || ignoredProperties.has(key)) { continue; } if (childrenKeys.has(key)) { - newObj[key] = recurse(node[key], node); + cloned[key] = recurse(original[key], original); } else { - newObj[key] = node[key]; + cloned[key] = original[key]; } } - const result = cleanFunction(node, newObj, parent); + const result = cleanFunction(original, cloned, parent); if (result === null) { return; } - return result ?? newObj; + return result ?? cloned; } }