Use .at() and Object.hasOwn() (#13482)
This commit is contained in:
1 parent
8cfdcaa6b2
commit
9bacc75772
51 files changed
+254
-263
No files matched your search
@@ -14,6 +14,7 @@ import esbuildPluginStripNodeProtocol from "./esbuild-plugins/strip-node-protoco
|
||||
import esbuildPluginThrowWarnings from "./esbuild-plugins/throw-warnings.mjs";
|
||||
import esbuildPluginShimCommonjsObjects from "./esbuild-plugins/shim-commonjs-objects.mjs";
|
||||
import bundles from "./config.mjs";
|
||||
import transform from "./transform/index.js";
|
||||
|
||||
const { dirname, readJsonSync, require } = createEsmUtils(import.meta);
|
||||
const packageJson = readJsonSync("../../package.json");
|
||||
@@ -42,6 +43,11 @@ function* getEsbuildOptions(bundle, buildOptions) {
|
||||
find: "const __dirname = path.dirname(fileURLToPath(import.meta.url));",
|
||||
replacement: "",
|
||||
},
|
||||
// Transform `.at` and `Object.hasOwn`
|
||||
{
|
||||
module: "*",
|
||||
process: transform,
|
||||
},
|
||||
// #12493, not sure what the problem is, but replace the cjs version with esm version seems fix it
|
||||
{
|
||||
module: require.resolve("tslib"),
|
||||
|
||||
@@ -10,7 +10,7 @@ export default function esbuildPluginEvaluate() {
|
||||
build.onLoad({ filter: /\.evaluate\.js$/ }, async ({ path }) => {
|
||||
let data = await importModule(path);
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(data, "default")) {
|
||||
if (Object.hasOwn(data, "default")) {
|
||||
if (Object.keys(data).length !== 1) {
|
||||
throw new TypeError("Mixed export not allowed.");
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ export default function esbuildPluginInteropDefault() {
|
||||
const text = fs.readFileSync(file, "utf8").trim();
|
||||
const lines = text.split("\n");
|
||||
if (
|
||||
lines[lines.length - 2] !==
|
||||
lines.at(-2) !==
|
||||
"// Annotate the CommonJS export names for ESM import in node:" ||
|
||||
lines[lines.length - 1] !== "0 && (module.exports = {});"
|
||||
lines.at(-1) !== "0 && (module.exports = {});"
|
||||
) {
|
||||
throw new Error("Unexpected output.");
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ function setupOnLoadListener(build, { concepts, replacements, processors }) {
|
||||
|
||||
let text = original;
|
||||
for (const process of processFunctions) {
|
||||
text = process(text);
|
||||
text = process(text, file);
|
||||
}
|
||||
|
||||
// For files not JavaScript, we need add correct `loader` to the result,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
Note:
|
||||
|
||||
1. `acorn` contains `RegExpValidationState` class with `.at` method (2 arguments)
|
||||
2. `postcss` contains `Container` class with `.at` method
|
||||
*/
|
||||
|
||||
const at = (object, index, ...args) =>
|
||||
object.at
|
||||
? object.at(index, ...args)
|
||||
: object[index < 0 ? object.length + index : index];
|
||||
|
||||
export default at;
|
||||
@@ -0,0 +1,113 @@
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { parse } from "@babel/parser";
|
||||
import { traverseFast as traverse } from "@babel/types";
|
||||
import babelGenerator from "@babel/generator";
|
||||
import { outdent } from "outdent";
|
||||
import { SOURCE_DIR } from "../../utils/index.mjs";
|
||||
|
||||
const generate = babelGenerator.default;
|
||||
const atHelperPath = fileURLToPath(new URL("../shims/at.js", import.meta.url));
|
||||
|
||||
/* Doesn't work for dependencies, optional chaining, and spread arguments */
|
||||
|
||||
// `Object.hasOwn(foo, "bar")` -> `Object.prototype.hasOwnProperty.call(foo, "bar")`
|
||||
function transformObjectHasOwnCall(node) {
|
||||
if (
|
||||
!(
|
||||
node.type === "CallExpression" &&
|
||||
!node.optional &&
|
||||
node.arguments.length === 2 &&
|
||||
node.arguments.every(({ type }) => type !== "SpreadElement") &&
|
||||
node.callee.type === "MemberExpression" &&
|
||||
!node.callee.optional &&
|
||||
!node.callee.computed &&
|
||||
node.callee.object.type === "Identifier" &&
|
||||
node.callee.object.name === "Object" &&
|
||||
node.callee.property.type === "Identifier" &&
|
||||
node.callee.property.name === "hasOwn"
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
node.callee = {
|
||||
type: "MemberExpression",
|
||||
object: {
|
||||
type: "MemberExpression",
|
||||
object: {
|
||||
type: "MemberExpression",
|
||||
object: { type: "Identifier", name: "Object" },
|
||||
property: { type: "Identifier", name: "prototype" },
|
||||
},
|
||||
property: { type: "Identifier", name: "hasOwnProperty" },
|
||||
},
|
||||
property: { type: "Identifier", name: "call" },
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// `foo.at(index)` -> `__at(foo, index)`
|
||||
function transformRelativeIndexingCall(node) {
|
||||
if (
|
||||
!(
|
||||
node.type === "CallExpression" &&
|
||||
!node.optional &&
|
||||
node.arguments.length === 1 &&
|
||||
node.arguments.every(({ type }) => type !== "SpreadElement") &&
|
||||
node.callee.type === "MemberExpression" &&
|
||||
!node.callee.optional &&
|
||||
!node.callee.computed &&
|
||||
node.callee.object.type !== "ThisExpression" &&
|
||||
node.callee.property.type === "Identifier" &&
|
||||
node.callee.property.name === "at"
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
node.arguments.unshift(node.callee.object);
|
||||
node.callee = { type: "Identifier", name: "__at" };
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function transform(original, file) {
|
||||
if (
|
||||
file === atHelperPath ||
|
||||
!file.startsWith(SOURCE_DIR) ||
|
||||
(!original.includes(".at(") && !original.includes("Object.hasOwn("))
|
||||
) {
|
||||
return original;
|
||||
}
|
||||
|
||||
let changed = false;
|
||||
let shouldInjectRelativeIndexingHelper = false;
|
||||
const ast = parse(original, { sourceType: "module" });
|
||||
traverse(ast, (node) => {
|
||||
const hasObjectHasOwnCall = transformObjectHasOwnCall(node);
|
||||
changed ||= hasObjectHasOwnCall;
|
||||
|
||||
const hasRelativeIndexingCall = transformRelativeIndexingCall(node);
|
||||
shouldInjectRelativeIndexingHelper ||= hasRelativeIndexingCall;
|
||||
changed ||= hasRelativeIndexingCall;
|
||||
});
|
||||
|
||||
if (!changed) {
|
||||
return original;
|
||||
}
|
||||
|
||||
let { code } = generate(ast);
|
||||
|
||||
if (shouldInjectRelativeIndexingHelper) {
|
||||
code = outdent`
|
||||
import __at from ${JSON.stringify(atHelperPath)};
|
||||
|
||||
${code}
|
||||
`;
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
export default transform;
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
const selector = [
|
||||
"MemberExpression",
|
||||
"[computed=true]",
|
||||
"[optional=false]",
|
||||
'[property.type="BinaryExpression"]',
|
||||
'[property.operator="-"]',
|
||||
'[property.left.type="MemberExpression"]',
|
||||
"[property.left.optional=false]",
|
||||
"[property.left.computed=false]",
|
||||
'[property.left.property.type="Identifier"]',
|
||||
'[property.left.property.name="length"]',
|
||||
'[property.right.type="Literal"]',
|
||||
`:not(${[
|
||||
"AssignmentExpression > .left",
|
||||
"UpdateExpression > .argument",
|
||||
// Ignore `getPenultimate` and `getLast` function self
|
||||
'VariableDeclarator[id.name="getPenultimate"] > ArrowFunctionExpression.init *',
|
||||
'VariableDeclarator[id.name="getLast"] > ArrowFunctionExpression.init *',
|
||||
].join(", ")})`,
|
||||
].join("");
|
||||
|
||||
const messageId = "consistent-negative-index-access";
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
docs: {
|
||||
url: "https://github.com/prettier/prettier/blob/main/scripts/tools/eslint-plugin-prettier-internal-rules/consistent-negative-index-access.js",
|
||||
},
|
||||
messages: {
|
||||
[messageId]: "Prefer `{{method}}(…)` over `…[….length - {{index}}]`.",
|
||||
},
|
||||
fixable: "code",
|
||||
},
|
||||
create(context) {
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
return {
|
||||
[selector](node) {
|
||||
const { value: index } = node.property.right;
|
||||
|
||||
if (index !== 1 && index !== 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { object } = node;
|
||||
const lengthObject = node.property.left.object;
|
||||
|
||||
const objectText = sourceCode.getText(object);
|
||||
// Simply use text to compare object
|
||||
if (sourceCode.getText(lengthObject) !== objectText) {
|
||||
return;
|
||||
}
|
||||
|
||||
const method = ["getLast", "getPenultimate"][index - 1];
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId,
|
||||
data: {
|
||||
index,
|
||||
method,
|
||||
},
|
||||
fix: (fixer) => fixer.replaceText(node, `${method}(${objectText})`),
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -4,7 +4,6 @@ module.exports = {
|
||||
rules: {
|
||||
"await-cli-tests": require("./await-cli-tests.js"),
|
||||
"better-parent-property-check-in-needs-parens": require("./better-parent-property-check-in-needs-parens.js"),
|
||||
"consistent-negative-index-access": require("./consistent-negative-index-access.js"),
|
||||
"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"),
|
||||
|
||||
@@ -79,50 +79,6 @@ test("better-parent-property-check-in-needs-parens", {
|
||||
})),
|
||||
});
|
||||
|
||||
test("consistent-negative-index-access", {
|
||||
valid: [
|
||||
"getLast(foo)",
|
||||
"getPenultimate(foo)",
|
||||
"foo[foo.length]",
|
||||
"foo[foo.length - 3]",
|
||||
"foo[foo.length + 1]",
|
||||
"foo[foo.length + -1]",
|
||||
"foo[foo.length * -1]",
|
||||
"foo.length - 1",
|
||||
"foo?.[foo.length - 1]",
|
||||
"foo[foo?.length - 1]",
|
||||
"foo[foo['length'] - 1]",
|
||||
"foo[bar.length - 1]",
|
||||
"foo.bar[foo. bar.length - 1]",
|
||||
"foo[foo.length - 1]++",
|
||||
"--foo[foo.length - 1]",
|
||||
"foo[foo.length - 1] += 1",
|
||||
"foo[foo.length - 1] = 1",
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: "foo[foo.length - 1]",
|
||||
output: "getLast(foo)",
|
||||
errors: 1,
|
||||
},
|
||||
{
|
||||
code: "foo[foo.length - 2]",
|
||||
output: "getPenultimate(foo)",
|
||||
errors: 1,
|
||||
},
|
||||
{
|
||||
code: "foo[foo.length - 0b10]",
|
||||
output: "getPenultimate(foo)",
|
||||
errors: 1,
|
||||
},
|
||||
{
|
||||
code: "foo()[foo().length - 1]",
|
||||
output: "getLast(foo())",
|
||||
errors: 1,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
test("directly-loc-start-end", {
|
||||
valid: [],
|
||||
invalid: [
|
||||
|
||||
@@ -32,11 +32,13 @@ async function writeFile(file, content) {
|
||||
const PROJECT_ROOT = url.fileURLToPath(new URL("../../", import.meta.url));
|
||||
const DIST_DIR = path.join(PROJECT_ROOT, "dist");
|
||||
const WEBSITE_DIR = path.join(PROJECT_ROOT, "website");
|
||||
const SOURCE_DIR = path.join(PROJECT_ROOT, "src");
|
||||
|
||||
export {
|
||||
PROJECT_ROOT,
|
||||
DIST_DIR,
|
||||
WEBSITE_DIR,
|
||||
SOURCE_DIR,
|
||||
readJson,
|
||||
writeJson,
|
||||
writeFile,
|
||||
|
||||
Reference in new issue
Block a user