Skip to content

Commit

Permalink
Rename parameters in massageAstNode (#16024)
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker committed Feb 4, 2024
1 parent 71b9cde commit 76b8f95
Show file tree
Hide file tree
Showing 14 changed files with 357 additions and 257 deletions.
1 change: 1 addition & 0 deletions eslint.config.js
Expand Up @@ -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",

Expand Down
Expand Up @@ -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"),
Expand Down
@@ -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),
),
});
},
]),
),
};
19 changes: 19 additions & 0 deletions scripts/tools/eslint-plugin-prettier-internal-rules/test.js
Expand Up @@ -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,
},
],
});
6 changes: 4 additions & 2 deletions src/index.d.ts
Expand Up @@ -496,10 +496,12 @@ export interface Printer<T = any> {
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<T>) => boolean) | undefined;
canAttachComment?: ((node: T) => boolean) | undefined;
isBlockComment?: ((node: T) => boolean) | undefined;
Expand Down
173 changes: 86 additions & 87 deletions src/language-css/clean.js
Expand Up @@ -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 (original.type === "css-rule") {
delete cloned.params;
}

if (ast.type === "selector-combinator") {
newObj.value = newObj.value.replaceAll(/\s+/g, " ");
if (
(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 (ast.type === "media-feature") {
newObj.value = newObj.value.replaceAll(" ", "");
if (original.type === "media-feature") {
cloned.value = cloned.value.replaceAll(" ", "");
}

if (
(ast.type === "value-word" &&
((ast.isColor && ast.isHex) ||
(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();
}
if (ast.type === "css-decl") {
newObj.prop = newObj.prop.toLowerCase();
cloned.value = cloned.value.toLowerCase();
}
if (ast.type === "css-atrule" || ast.type === "css-import") {
newObj.name = newObj.name.toLowerCase();
if (original.type === "css-decl") {
cloned.prop = original.prop.toLowerCase();
}
if (ast.type === "value-number") {
newObj.unit = newObj.unit.toLowerCase();
if (original.type === "css-atrule" || original.type === "css-import") {
cloned.name = original.name.toLowerCase();
}
if (ast.type === "value-unknown") {
newObj.value = newObj.value.replaceAll(/;$/g, "");
if (original.type === "value-number") {
cloned.unit = original.unit.toLowerCase();
}

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 === "value-unknown") {
cloned.value = cloned.value.replaceAll(/;$/g, "");
}

if (ast.type === "selector-attribute") {
newObj.attribute = newObj.attribute.trim();

if (newObj.namespace && typeof newObj.namespace === "string") {
newObj.namespace = newObj.namespace.trim();
if (original.type === "selector-attribute") {
cloned.attribute = original.attribute.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);
Expand All @@ -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,
Expand All @@ -185,16 +184,16 @@ 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("]")),
)
) {
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,
Expand Down

0 comments on commit 76b8f95

Please sign in to comment.