Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid using CJS globals in internal source files #12963

Merged
merged 12 commits into from Mar 5, 2021
Merged
21 changes: 21 additions & 0 deletions .eslintrc.cjs
Expand Up @@ -2,6 +2,8 @@

const path = require("path");

const cjsGlobals = ["__dirname", "__filename", "require", "module", "exports"];

module.exports = {
root: true,
plugins: [
Expand Down Expand Up @@ -68,6 +70,25 @@ module.exports = {
"import/extensions": ["error", { json: "always", cjs: "always" }],
},
},
{
files: [
"packages/*/src/**/*.{js,ts}",
"codemods/*/src/**/*.{js,ts}",
"eslint/*/src/**/*.{js,ts}",
"packages/*/test/**/*.js",
"codemods/*/test/**/*.js",
"eslint/*/test/**/*.js",
"packages/babel-helper-transform-fixture-test-runner/src/helpers.{ts,js}",
"test/**/*.js",
],
excludedFiles: [
// @babel/register is the require() hook, so it will always be CJS-based
"packages/babel-register/**/*.js",
],
rules: {
"no-restricted-globals": ["error", ...cjsGlobals],
},
},
{
files: ["packages/babel-plugin-*/src/index.{js,ts}"],
excludedFiles: ["packages/babel-plugin-transform-regenerator/**/*.js"],
Expand Down
94 changes: 89 additions & 5 deletions babel.config.js
Expand Up @@ -159,6 +159,7 @@ module.exports = function (api) {
convertESM ? "@babel/proposal-export-namespace-from" : null,
convertESM ? "@babel/transform-modules-commonjs" : null,
convertESM ? pluginNodeImportInterop : null,
convertESM ? pluginImportMetaUrl : null,

pluginPackageJsonMacro,

Expand All @@ -177,14 +178,17 @@ module.exports = function (api) {
plugins: ["babel-plugin-transform-charcodes"],
assumptions: parserAssumptions,
},
{
convertESM && {
test: ["./packages/babel-cli", "./packages/babel-core"].map(normalize),
plugins: [
// Explicitly use the lazy version of CommonJS modules.
convertESM
? ["@babel/transform-modules-commonjs", { lazy: true }]
: null,
].filter(Boolean),
["@babel/transform-modules-commonjs", { lazy: true }],
],
},
convertESM && {
test: ["./packages/babel-node/src"].map(normalize),
// Used to conditionally import kexec
plugins: ["@babel/plugin-proposal-dynamic-import"],
},
{
test: sources.map(normalize),
Expand Down Expand Up @@ -465,3 +469,83 @@ function pluginNodeImportInterop({ template }) {
},
};
}

function pluginImportMetaUrl({ types: t, template }) {
const isImportMeta = node =>
t.isMetaProperty(node) &&
t.isIdentifier(node.meta, { name: "import" }) &&
t.isIdentifier(node.property, { name: "meta" });

const isImportMetaUrl = node =>
t.isMemberExpression(node, { computed: false }) &&
t.isIdentifier(node.property, { name: "url" }) &&
isImportMeta(node.object);

return {
visitor: {
Program(programPath) {
// We must be sure to run this before the instanbul plugins, because its
// instrumentation breaks our detection.
programPath.traverse({
// fileURLToPath(import.meta.url)
CallExpression(path) {
const { node } = path;

if (
!t.isIdentifier(node.callee, { name: "fileURLToPath" }) ||
node.arguments.length !== 1
) {
return;
}

const arg = node.arguments[0];

if (
!t.isMemberExpression(arg, { computed: false }) ||
!t.isIdentifier(arg.property, { name: "url" }) ||
!isImportMeta(arg.object)
) {
return;
}

path.replaceWith(t.identifier("__filename"));
},

// const require = createRequire(import.meta.url)
VariableDeclarator(path) {
const { node } = path;

if (
!t.isIdentifier(node.id, { name: "require" }) ||
!t.isCallExpression(node.init) ||
!t.isIdentifier(node.init.callee, { name: "createRequire" }) ||
node.init.arguments.length !== 1 ||
!isImportMetaUrl(node.init.arguments[0])
) {
return;
}

// Let's just remove this declaration to unshadow the "global" cjs require.
path.remove();
},

// import.meta.url
MemberExpression(path) {
if (!isImportMetaUrl(path.node)) return;

path.replaceWith(
template.expression
.ast`\`file://\${__filename.replace(/\\\\/g, "/")}\``
);
},

MetaProperty(path) {
if (isImportMeta(path.node)) {
throw path.buildCodeFrameError("Unsupported import.meta");
}
},
});
},
},
};
}
@@ -1,3 +1,3 @@
import runner from "@babel/helper-plugin-test-runner";

runner(__dirname);
runner(import.meta.url);
@@ -1,3 +1,3 @@
import runner from "@babel/helper-plugin-test-runner";

runner(__dirname);
runner(import.meta.url);
7 changes: 6 additions & 1 deletion eslint/babel-eslint-parser/test/index.js
@@ -1,10 +1,13 @@
import path from "path";
import escope from "eslint-scope";
import unpad from "dedent";
import { fileURLToPath } from "url";
import { createRequire } from "module";
import { parseForESLint } from "../src";

const BABEL_OPTIONS = {
configFile: require.resolve(
configFile: path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
"../../babel-eslint-shared-fixtures/config/babel.config.js",
),
Comment on lines +9 to 12
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In many places we could use URLs directly rather than path.*, but only after dropping Node.js <= 10 (because we need native fileURLToPath):

configFile: fileURLToPath(
  new URL("../../babel-eslint-shared-fixtures/config/babel.config.js", import.meta.url)
)

};
Expand Down Expand Up @@ -73,6 +76,8 @@ describe("Babel and Espree", () => {
}

beforeAll(async () => {
const require = createRequire(import.meta.url);

// Use the version of Espree that is a dependency of
// the version of ESLint we are testing against.
const espreePath = require.resolve("espree", {
Expand Down
8 changes: 4 additions & 4 deletions eslint/babel-eslint-plugin-development-internal/src/index.js
@@ -1,7 +1,7 @@
import dryErrorMessages from "./rules/dry-error-messages";

module.exports = {
rules: {
"dry-error-messages": dryErrorMessages,
},
export const rules = {
"dry-error-messages": dryErrorMessages,
};

export default { rules };
@@ -1,11 +1,14 @@
import path from "path";
import rule from "../../src/rules/dry-error-messages";
import RuleTester from "../../../babel-eslint-shared-fixtures/utils/RuleTester";
import { fileURLToPath } from "url";

const FILENAME = path.resolve(__dirname, "test/lib/index.js");
const dirname = path.dirname(fileURLToPath(import.meta.url));

const FILENAME = path.resolve(dirname, "test/lib/index.js");
const ERRORS_MODULE = "errorsModule";
const MODULE_SAME_DIR = path.resolve(__dirname, "test/lib/errorsModule.js");
const MODULE_PARENT_DIR = path.resolve(__dirname, "test/errorsModule.js");
const MODULE_SAME_DIR = path.resolve(dirname, "test/lib/errorsModule.js");
const MODULE_PARENT_DIR = path.resolve(dirname, "test/errorsModule.js");

const ruleTester = new RuleTester();

Expand Down
12 changes: 6 additions & 6 deletions eslint/babel-eslint-plugin-development/src/index.js
Expand Up @@ -2,10 +2,10 @@ import noDeprecatedClone from "./rules/no-deprecated-clone";
import noUndefinedIdentifier from "./rules/no-undefined-identifier";
import pluginName from "./rules/plugin-name";

module.exports = {
rules: {
"no-deprecated-clone": noDeprecatedClone,
"no-undefined-identifier": noUndefinedIdentifier,
"plugin-name": pluginName,
},
export const rules = {
"no-deprecated-clone": noDeprecatedClone,
"no-undefined-identifier": noUndefinedIdentifier,
"plugin-name": pluginName,
};

export default { rules };
31 changes: 16 additions & 15 deletions eslint/babel-eslint-plugin/src/index.js
Expand Up @@ -4,19 +4,20 @@ import noUnusedExpressions from "./rules/no-unused-expressions";
import objectCurlySpacing from "./rules/object-curly-spacing";
import semi from "./rules/semi";

module.exports = {
rules: {
"new-cap": newCap,
"no-invalid-this": noInvalidThis,
"no-unused-expressions": noUnusedExpressions,
"object-curly-spacing": objectCurlySpacing,
semi,
},
rulesConfig: {
"new-cap": "off",
"no-invalid-this": "off",
"no-unused-expressions": "off",
"object-curly-spacing": "off",
semi: "off",
},
export const rules = {
"new-cap": newCap,
"no-invalid-this": noInvalidThis,
"no-unused-expressions": noUnusedExpressions,
"object-curly-spacing": objectCurlySpacing,
semi,
};

export const rulesConfig = {
"new-cap": "off",
"no-invalid-this": "off",
"no-unused-expressions": "off",
"object-curly-spacing": "off",
semi: "off",
};

export default { rules, rulesConfig };
@@ -1,5 +1,7 @@
import eslint from "eslint";
import unpad from "dedent";
import path from "path";
import { fileURLToPath } from "url";
import * as parser from "../../../babel-eslint-parser";

export default function verifyAndAssertMessages(
Expand All @@ -24,7 +26,8 @@ export default function verifyAndAssertMessages(
sourceType,
requireConfigFile: false,
babelOptions: {
configFile: require.resolve(
configFile: path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
"../../../babel-eslint-shared-fixtures/config/babel.config.js",
),
},
Expand Down
@@ -1,12 +1,16 @@
import eslint from "eslint";
import path from "path";
import { fileURLToPath } from "url";

describe("https://github.com/babel/babel-eslint/issues/558", () => {
it("doesn't crash with eslint-plugin-import", () => {
const engine = new eslint.CLIEngine({ ignore: false });
engine.executeOnFiles(
["a.js", "b.js", "c.js"].map(file =>
path.resolve(__dirname, `../fixtures/eslint-plugin-import/${file}`),
path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
`../fixtures/eslint-plugin-import/${file}`,
),
),
);
});
Expand Down
5 changes: 4 additions & 1 deletion eslint/babel-eslint-tests/test/integration/eslint/config.js
@@ -1,4 +1,6 @@
import eslint from "eslint";
import path from "path";
import { fileURLToPath } from "url";
import * as parser from "@babel/eslint-parser";

describe("ESLint config", () => {
Expand All @@ -10,7 +12,8 @@ describe("ESLint config", () => {
parser: "@babel/eslint-parser",
parserOptions: {
babelOptions: {
configFile: require.resolve(
configFile: path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
"../../../../babel-eslint-shared-fixtures/config/babel.config.js",
),
},
Expand Down
Expand Up @@ -2,11 +2,17 @@ import eslint from "eslint";
import fs from "fs";
import path from "path";
import * as parser from "../../../../../babel-eslint-parser";
import { fileURLToPath } from "url";

eslint.linter.defineParser("@babel/eslint-parser", parser);

const paths = {
fixtures: path.join(__dirname, "../../..", "fixtures", "rules"),
fixtures: path.join(
path.dirname(fileURLToPath(import.meta.url)),
"../../..",
"fixtures",
"rules",
),
};

const encoding = "utf8";
Expand Down
5 changes: 4 additions & 1 deletion eslint/babel-eslint-tests/test/integration/eslint/verify.js
@@ -1,4 +1,6 @@
import verifyAndAssertMessages from "../../helpers/verifyAndAssertMessages";
import path from "path";
import { fileURLToPath } from "url";

describe("verify", () => {
it("arrow function support (issue #1)", () => {
Expand Down Expand Up @@ -1080,7 +1082,8 @@ describe("verify", () => {
parserOptions: {
sourceType,
babelOptions: {
configFile: require.resolve(
configFile: path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
"../../../../babel-eslint-shared-fixtures/config/babel.config.decorators-legacy.js",
),
},
Expand Down
4 changes: 4 additions & 0 deletions lib/third-party-libs.js.flow
Expand Up @@ -2,6 +2,10 @@
* Basic declarations for the npm modules we use.
*/

declare module "module" {
declare export function createRequire(url: any): any;
}

declare module "debug" {
declare export default (namespace: string) => (formatter: string, ...args: any[]) => void;
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -24,6 +24,7 @@
"@babel/eslint-parser": "workspace:*",
"@babel/eslint-plugin-development": "workspace:*",
"@babel/eslint-plugin-development-internal": "workspace:*",
"@babel/plugin-proposal-dynamic-import": "workspace:packages/babel-plugin-proposal-dynamic-import",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that mean @babel/node should always be built after @babel/plugin-proposal-dynamic-import?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it means I should have added the dependency from npm 😅

"@babel/plugin-proposal-export-namespace-from": "^7.12.13",
"@babel/plugin-proposal-object-rest-spread": "^7.13.0",
"@babel/plugin-transform-modules-commonjs": "^7.13.0",
Expand Down
4 changes: 4 additions & 0 deletions packages/babel-cli/src/babel/util.js
Expand Up @@ -4,6 +4,7 @@ import readdirRecursive from "fs-readdir-recursive";
import * as babel from "@babel/core";
import path from "path";
import fs from "fs";
import { createRequire } from "module";

export function chmod(src: string, dest: string): void {
try {
Expand Down Expand Up @@ -119,6 +120,9 @@ process.on("uncaughtException", function (err) {
});

export function requireChokidar(): Object {
// $FlowIgnore - https://github.com/facebook/flow/issues/6913#issuecomment-662787504
const require = createRequire(import /*::("")*/.meta.url);

try {
// todo(babel 8): revert `@nicolo-ribaudo/chokidar-2` hack
return parseInt(process.versions.node) >= 8
Expand Down