Skip to content

Commit

Permalink
[babel 8] Align allow* parser options with ESLint behavior (#13921)
Browse files Browse the repository at this point in the history
* [babel 8] Align `allow*` parser options with ESLint behavior

- The `@babel/eslint-parser` `allowImportExportEverywhere` option is removed; users can pass it to `parserOpts`
- `allowSuperOutsideMethod` is disabled
- `allowReturnOutsideFunction` is inferred from `ecmaFeatures.globalReturn`

* Update failing tests
  • Loading branch information
nicolo-ribaudo committed Nov 4, 2021
1 parent 531db5d commit de28707
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 34 deletions.
15 changes: 8 additions & 7 deletions eslint/babel-eslint-parser/README.md
Expand Up @@ -51,6 +51,7 @@ Additional configuration options can be set in your ESLint configuration under t

- `requireConfigFile` (default `true`) can be set to `false` to allow @babel/eslint-parser to run on files that do not have a Babel configuration associated with them. This can be useful for linting files that are not transformed by Babel (such as tooling configuration files), though we recommend using the default parser via [glob-based configuration](https://eslint.org/docs/user-guide/configuring/configuration-files#configuration-based-on-glob-patterns). Note: @babel/eslint-parser will not parse any experimental syntax when no configuration file is found.
- `sourceType` can be set to `"module"`(default) or `"script"` if your code isn't using ECMAScript modules.
<!-- TODO(Babel 8): Remove this -->
- `allowImportExportEverywhere` (default `false`) can be set to `true` to allow import and export declarations to appear anywhere a statement is allowed if your build environment supports that. Otherwise import and export declarations can only appear at a program's top level.
- `ecmaFeatures.globalReturn` (default `false`) allow return statements in the global scope when used with `sourceType: "script"`.
- `babelOptions` is an object containing Babel configuration [options](https://babeljs.io/docs/en/options) that are passed to Babel's parser at runtime. For cases where users might not want to use a Babel configuration file or are running Babel through another tool (such as Webpack with `babel-loader`).
Expand Down Expand Up @@ -97,13 +98,13 @@ This configuration is useful for monorepo, when you are running ESLint on every

```js
module.exports = {
"parser": "@babel/eslint-parser",
"parserOptions": {
"babelOptions": {
"rootMode": "upward"
}
}
}
parser: "@babel/eslint-parser",
parserOptions: {
babelOptions: {
rootMode: "upward",
},
},
};
```

### Run
Expand Down
3 changes: 1 addition & 2 deletions eslint/babel-eslint-parser/src/analyze-scope.cjs
Expand Up @@ -340,8 +340,7 @@ module.exports = function analyzeScope(ast, parserOptions, client) {
directive: false,
nodejsScope:
ast.sourceType === "script" &&
(parserOptions.ecmaFeatures &&
parserOptions.ecmaFeatures.globalReturn) === true,
parserOptions.ecmaFeatures?.globalReturn === true,
impliedStrict: false,
sourceType: ast.sourceType,
ecmaVersion: parserOptions.ecmaVersion,
Expand Down
2 changes: 0 additions & 2 deletions eslint/babel-eslint-parser/src/configuration.cjs
Expand Up @@ -4,7 +4,6 @@ exports.normalizeESLintConfig = function (options) {
// ESLint sets ecmaVersion: undefined when ecmaVersion is not set in the config.
ecmaVersion = 2020,
sourceType = "module",
allowImportExportEverywhere = false,
requireConfigFile = true,
...otherOptions
} = options;
Expand All @@ -13,7 +12,6 @@ exports.normalizeESLintConfig = function (options) {
babelOptions: { cwd: process.cwd(), ...babelOptions },
ecmaVersion: ecmaVersion === "latest" ? 1e8 : ecmaVersion,
sourceType,
allowImportExportEverywhere,
requireConfigFile,
...otherOptions,
};
Expand Down
13 changes: 10 additions & 3 deletions eslint/babel-eslint-parser/src/worker/configuration.cjs
Expand Up @@ -26,9 +26,16 @@ function normalizeParserOptions(options) {
filename: options.filePath,
...options.babelOptions,
parserOpts: {
allowImportExportEverywhere: options.allowImportExportEverywhere,
allowReturnOutsideFunction: true,
allowSuperOutsideMethod: true,
...(process.env.BABEL_8_BREAKING
? {}
: {
allowImportExportEverywhere:
options.allowImportExportEverywhere ?? false,
allowSuperOutsideMethod: true,
}),
allowReturnOutsideFunction:
options.ecmaFeatures?.globalReturn ??
(process.env.BABEL_8_BREAKING ? false : true),
...options.babelOptions.parserOpts,
plugins: getParserPlugins(options.babelOptions),
// skip comment attaching for parsing performance
Expand Down
134 changes: 118 additions & 16 deletions eslint/babel-eslint-parser/test/index.js
Expand Up @@ -68,8 +68,6 @@ describe("Babel and Espree", () => {
globalReturn: true,
// enable implied strict mode (if ecmaVersion >= 5)
impliedStrict: true,
// allow experimental object rest/spread
experimentalObjectRestSpread: true,
},
tokens: true,
loc: true,
Expand All @@ -78,7 +76,11 @@ describe("Babel and Espree", () => {
sourceType: "module",
};

function parseAndAssertSame(code, /* optional */ eslintVersion) {
function parseAndAssertSame(
code,
eslintVersion = undefined,
babelEcmaFeatures = null,
) {
code = unpad(code);

if (eslintVersion !== 8) {
Expand All @@ -91,6 +93,7 @@ describe("Babel and Espree", () => {
eslintVisitorKeys: true,
eslintScopeManager: true,
babelOptions: BABEL_OPTIONS,
ecmaFeatures: babelEcmaFeatures,
}).ast;

deeplyRemoveProperties(babelAST, PROPS_TO_REMOVE);
Expand All @@ -113,6 +116,7 @@ describe("Babel and Espree", () => {
eslintVisitorKeys: true,
eslintScopeManager: true,
babelOptions: BABEL_OPTIONS,
ecmaFeatures: babelEcmaFeatures,
}).ast;

deeplyRemoveProperties(babelAST, PROPS_TO_REMOVE);
Expand Down Expand Up @@ -851,24 +855,122 @@ describe("Babel and Espree", () => {

it("do not allow import export everywhere", () => {
expect(() => {
parseAndAssertSame('function F() { import a from "a"; }');
}).toThrow(
new SyntaxError(
"'import' and 'export' may only appear at the top level",
),
);
});

it("return outside function", () => {
parseAndAssertSame("return;");
parseForESLint('function F() { import a from "a"; }', {
babelOptions: BABEL_OPTIONS,
});
}).toThrow(/'import' and 'export' may only appear at the top level/);
});

it("super outside method", () => {
it("allowImportExportEverywhere", () => {
expect(() => {
parseAndAssertSame("function F() { super(); }");
}).toThrow(new SyntaxError("'super' keyword outside a method"));
parseForESLint('function F() { import a from "a"; }', {
babelOptions: {
...BABEL_OPTIONS,
parserOpts: {
allowImportExportEverywhere: true,
},
},
});
}).not.toThrow();
});

if (!process.env.BABEL_8_BREAKING) {
it("top-level allowImportExportEverywhere", () => {
expect(() => {
parseForESLint('function F() { import a from "a"; }', {
babelOptions: BABEL_OPTIONS,
allowImportExportEverywhere: true,
});
}).not.toThrow();
});
}

if (process.env.BABEL_8_BREAKING) {
it("return outside function with ecmaFeatures.globalReturn: true", () => {
parseAndAssertSame("return;", /* version */ undefined, {
globalReturn: true,
});
});

it("return outside function with ecmaFeatures.globalReturn: false", () => {
expect(() =>
parseForESLint("return;", {
babelOptions: BABEL_OPTIONS,
ecmaVersion: { globalReturn: false },
}),
).toThrow(new SyntaxError("'return' outside of function. (1:0)"));

expect(() =>
parseForESLint8("return;", {
babelOptions: BABEL_OPTIONS,
ecmaVersion: { globalReturn: false },
}),
).toThrow(new SyntaxError("'return' outside of function. (1:0)"));
});

it("return outside function without ecmaFeatures.globalReturn", () => {
expect(() =>
parseForESLint("return;", { babelOptions: BABEL_OPTIONS }),
).toThrow(new SyntaxError("'return' outside of function. (1:0)"));

expect(() =>
parseForESLint8("return;", { babelOptions: BABEL_OPTIONS }),
).toThrow(new SyntaxError("'return' outside of function. (1:0)"));
});
} else {
it("return outside function", () => {
parseAndAssertSame("return;");
});
}

if (process.env.BABEL_8_BREAKING) {
it("super outside method", () => {
expect(() => {
parseForESLint("function F() { super(); }", {
babelOptions: BABEL_OPTIONS,
});
}).toThrow(
/`super\(\)` is only valid inside a class constructor of a subclass\./,
);
});

it("super outside method - enabled", () => {
expect(() => {
parseForESLint("function F() { super(); }", {
babelOptions: {
...BABEL_OPTIONS,
parserOpts: {
allowSuperOutsideMethod: true,
},
},
});
}).not.toThrow();
});
} else {
it("super outside method", () => {
expect(() => {
parseForESLint("function F() { super(); }", {
babelOptions: BABEL_OPTIONS,
});
}).not.toThrow();
});

it("super outside method - disabled", () => {
expect(() => {
parseForESLint("function F() { super(); }", {
babelOptions: {
...BABEL_OPTIONS,
parserOpts: {
allowSuperOutsideMethod: false,
},
},
});
}).toThrow(
/`super\(\)` is only valid inside a class constructor of a subclass\./,
);
});
}

it("StringLiteral", () => {
parseAndAssertSame("");
parseAndAssertSame("");
Expand Down
34 changes: 30 additions & 4 deletions eslint/babel-eslint-tests/test/integration/eslint/verify.js
Expand Up @@ -45,9 +45,10 @@ describe("verify", () => {
});

it("super keyword in class (issue #10)", () => {
verifyAndAssertMessages("class Foo { constructor() { super() } }", {
"no-undef": 1,
});
verifyAndAssertMessages(
"class Foo extends class {} { constructor() { super() } }",
{ "no-undef": 1 },
);
});

it("Rest parameter in destructuring assignment (issue #11)", () => {
Expand Down Expand Up @@ -1549,7 +1550,9 @@ describe("verify", () => {
);
});

it("allowImportExportEverywhere option (#327)", () => {
const babel7 = process.env.BABEL_8_BREAKING ? it.skip : it;

babel7("allowImportExportEverywhere option (#327)", () => {
verifyAndAssertMessages(
`
if (true) { import Foo from 'foo'; }
Expand All @@ -1570,6 +1573,29 @@ describe("verify", () => {
);
});

it("allowImportExportEverywhere @babel/parser option (#327)", () => {
verifyAndAssertMessages(
`
if (true) { import Foo from 'foo'; }
function foo() { import Bar from 'bar'; }
switch (a) { case 1: import FooBar from 'foobar'; }
`,
{},
[],
"module",
{
env: {},
parserOptions: {
ecmaVersion: 6,
sourceType: "module",
babelOptions: {
parserOpts: { allowImportExportEverywhere: true },
},
},
},
);
});

it("with does not crash parsing in script mode (strict off) #171", () => {
verifyAndAssertMessages("with (arguments) { length; }", {}, [], "script");
});
Expand Down

0 comments on commit de28707

Please sign in to comment.