* [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
1046 lines
26 KiB
JavaScript
1046 lines
26 KiB
JavaScript
import path from "path";
|
|
import escope from "eslint-scope";
|
|
import unpad from "dedent";
|
|
import { fileURLToPath } from "url";
|
|
import { createRequire } from "module";
|
|
import { parseForESLint } from "../lib/index.cjs";
|
|
|
|
function parseForESLint8(code, options) {
|
|
const { ESLINT_VERSION_FOR_BABEL } = process.env;
|
|
process.env.ESLINT_VERSION_FOR_BABEL = "8";
|
|
try {
|
|
return parseForESLint(code, options);
|
|
} finally {
|
|
process.env.ESLINT_VERSION_FOR_BABEL = ESLINT_VERSION_FOR_BABEL;
|
|
}
|
|
}
|
|
|
|
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
const BABEL_OPTIONS = {
|
|
configFile: path.resolve(
|
|
dirname,
|
|
"../../babel-eslint-shared-fixtures/config/babel.config.js",
|
|
),
|
|
};
|
|
const PROPS_TO_REMOVE = [
|
|
"importKind",
|
|
"exportKind",
|
|
"variance",
|
|
"typeArguments",
|
|
"filename",
|
|
"identifierName",
|
|
];
|
|
|
|
function deeplyRemoveProperties(obj, props) {
|
|
for (const [k, v] of Object.entries(obj)) {
|
|
if (typeof v === "object") {
|
|
if (Array.isArray(v)) {
|
|
for (const el of v) {
|
|
if (el != null) {
|
|
deeplyRemoveProperties(el, props);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (props.includes(k)) {
|
|
delete obj[k];
|
|
} else if (v != null) {
|
|
deeplyRemoveProperties(v, props);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (props.includes(k)) {
|
|
delete obj[k];
|
|
}
|
|
}
|
|
}
|
|
|
|
describe("Babel and Espree", () => {
|
|
let espree7, espree8;
|
|
|
|
const espreeOptions = {
|
|
ecmaFeatures: {
|
|
// enable JSX parsing
|
|
jsx: true,
|
|
// enable return in global scope
|
|
globalReturn: true,
|
|
// enable implied strict mode (if ecmaVersion >= 5)
|
|
impliedStrict: true,
|
|
},
|
|
tokens: true,
|
|
loc: true,
|
|
range: true,
|
|
comment: true,
|
|
sourceType: "module",
|
|
};
|
|
|
|
function parseAndAssertSame(
|
|
code,
|
|
eslintVersion = undefined,
|
|
babelEcmaFeatures = null,
|
|
) {
|
|
code = unpad(code);
|
|
|
|
if (eslintVersion !== 8) {
|
|
// ESLint 7
|
|
const espreeAST = espree7.parse(code, {
|
|
...espreeOptions,
|
|
ecmaVersion: 2021,
|
|
});
|
|
const babelAST = parseForESLint(code, {
|
|
eslintVisitorKeys: true,
|
|
eslintScopeManager: true,
|
|
babelOptions: BABEL_OPTIONS,
|
|
ecmaFeatures: babelEcmaFeatures,
|
|
}).ast;
|
|
|
|
deeplyRemoveProperties(babelAST, PROPS_TO_REMOVE);
|
|
expect(babelAST).toEqual(espreeAST);
|
|
}
|
|
|
|
if (eslintVersion !== 7) {
|
|
if (process.env.IS_PUBLISH) {
|
|
console.warn("Skipping ESLint 8 test because using a release build.");
|
|
return;
|
|
}
|
|
|
|
// ESLint 8
|
|
const espreeAST = espree8.parse(code, {
|
|
...espreeOptions,
|
|
ecmaVersion: 2022,
|
|
});
|
|
|
|
const babelAST = parseForESLint8(code, {
|
|
eslintVisitorKeys: true,
|
|
eslintScopeManager: true,
|
|
babelOptions: BABEL_OPTIONS,
|
|
ecmaFeatures: babelEcmaFeatures,
|
|
}).ast;
|
|
|
|
deeplyRemoveProperties(babelAST, PROPS_TO_REMOVE);
|
|
expect(babelAST).toEqual(espreeAST);
|
|
}
|
|
}
|
|
|
|
beforeAll(() => {
|
|
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 espree7Path = require.resolve("espree", {
|
|
paths: [path.dirname(require.resolve("eslint"))],
|
|
});
|
|
const espree8Path = require.resolve("espree", {
|
|
paths: [path.dirname(require.resolve("eslint-8"))],
|
|
});
|
|
|
|
espree7 = require(espree7Path);
|
|
espree8 = require(espree8Path);
|
|
|
|
const espree7pkg = require(require.resolve("espree/package.json", {
|
|
paths: [path.dirname(require.resolve("eslint"))],
|
|
}));
|
|
const espree8pkg = require(require.resolve("espree/package.json", {
|
|
paths: [path.dirname(require.resolve("eslint-8"))],
|
|
}));
|
|
if (!espree7pkg.version.startsWith("7.")) {
|
|
throw new Error(`Expected espree 7, but found ${espree7pkg.version}`);
|
|
}
|
|
if (!espree8pkg.version.startsWith("9.")) {
|
|
throw new Error(`Expected espree 9, but found ${espree8pkg.version}`);
|
|
}
|
|
});
|
|
|
|
describe("compatibility", () => {
|
|
it("should allow ast.analyze to be called without options", () => {
|
|
const ast = parseForESLint("`test`", {
|
|
eslintScopeManager: true,
|
|
eslintVisitorKeys: true,
|
|
babelOptions: BABEL_OPTIONS,
|
|
}).ast;
|
|
expect(() => {
|
|
escope.analyze(ast);
|
|
}).not.toThrow(new TypeError("Should allow no options argument."));
|
|
});
|
|
|
|
it("should not crash when `loadPartialConfigSync` returns `null`", () => {
|
|
const thunk = () =>
|
|
parseForESLint("`test`", {
|
|
eslintScopeManager: true,
|
|
eslintVisitorKeys: true,
|
|
babelOptions: { filename: "test.js", ignore: [/./] },
|
|
});
|
|
expect(thunk).not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe("templates", () => {
|
|
it("empty template string", () => {
|
|
parseAndAssertSame("``");
|
|
});
|
|
|
|
it("template string", () => {
|
|
parseAndAssertSame("`test`");
|
|
});
|
|
|
|
it("template string using $", () => {
|
|
parseAndAssertSame("`$`");
|
|
});
|
|
|
|
it("template string with expression", () => {
|
|
parseAndAssertSame("`${a}`");
|
|
});
|
|
|
|
it("template string with multiple expressions", () => {
|
|
parseAndAssertSame("`${a}${b}${c}`");
|
|
});
|
|
|
|
it("template string with expression and strings", () => {
|
|
parseAndAssertSame("`a${a}a`");
|
|
});
|
|
|
|
it("template string with binary expression", () => {
|
|
parseAndAssertSame("`a${a + b}a`");
|
|
});
|
|
|
|
it("tagged template", () => {
|
|
parseAndAssertSame("jsx`<Button>Click</Button>`");
|
|
});
|
|
|
|
it("tagged template with expression", () => {
|
|
parseAndAssertSame("jsx`<Button>Hi ${name}</Button>`");
|
|
});
|
|
|
|
it("tagged template with new operator", () => {
|
|
parseAndAssertSame("new raw`42`");
|
|
});
|
|
|
|
it("template with nested function/object", () => {
|
|
parseAndAssertSame(
|
|
"`outer${{x: {y: 10}}}bar${`nested${function(){return 1;}}endnest`}end`",
|
|
);
|
|
});
|
|
|
|
it("template with braces inside and outside of template string #96", () => {
|
|
parseAndAssertSame(
|
|
"if (a) { var target = `{}a:${webpackPort}{}}}}`; } else { app.use(); }",
|
|
);
|
|
});
|
|
|
|
it("template also with braces #96", () => {
|
|
parseAndAssertSame(`
|
|
export default function f1() {
|
|
function f2(foo) {
|
|
const bar = 3;
|
|
return \`\${foo} \${bar}\`;
|
|
}
|
|
return f2;
|
|
}
|
|
`);
|
|
});
|
|
|
|
it("template with destructuring #31", () => {
|
|
parseAndAssertSame(`
|
|
module.exports = {
|
|
render() {
|
|
var {name} = this.props;
|
|
return Math.max(null, \`Name: \${name}, Name: \${name}\`);
|
|
}
|
|
};
|
|
`);
|
|
});
|
|
|
|
it("template with arrow returning template #603", () => {
|
|
parseAndAssertSame(`
|
|
var a = \`\${() => {
|
|
\`\${''}\`
|
|
}}\`;
|
|
`);
|
|
});
|
|
|
|
it("template string with object with template string inside", () => {
|
|
parseAndAssertSame("`${ { a:`${2}` } }`");
|
|
});
|
|
});
|
|
|
|
it("simple expression", () => {
|
|
parseAndAssertSame("a = 1");
|
|
});
|
|
|
|
it("logical NOT", () => {
|
|
parseAndAssertSame("!0");
|
|
});
|
|
|
|
it("bitwise NOT", () => {
|
|
parseAndAssertSame("~0");
|
|
});
|
|
|
|
it("class declaration", () => {
|
|
parseAndAssertSame("class Foo {}");
|
|
});
|
|
|
|
it("class expression", () => {
|
|
parseAndAssertSame("var a = class Foo {}");
|
|
});
|
|
|
|
it("jsx expression", () => {
|
|
parseAndAssertSame("<App />");
|
|
});
|
|
|
|
it("jsx expression with 'this' as identifier", () => {
|
|
parseAndAssertSame("<this />");
|
|
});
|
|
|
|
it("jsx expression with a dynamic attribute", () => {
|
|
parseAndAssertSame("<App foo={bar} />");
|
|
});
|
|
|
|
it("jsx expression with a member expression as identifier", () => {
|
|
parseAndAssertSame("<foo.bar />");
|
|
});
|
|
|
|
it("jsx expression with spread", () => {
|
|
parseAndAssertSame("var myDivElement = <div {...this.props} />;");
|
|
});
|
|
|
|
it("empty jsx text", () => {
|
|
parseAndAssertSame("<a></a>");
|
|
});
|
|
|
|
it("jsx text with content", () => {
|
|
parseAndAssertSame("<a>Hello, world!</a>");
|
|
});
|
|
|
|
it("nested jsx", () => {
|
|
parseAndAssertSame("<div>\n<h1>Wat</h1>\n</div>");
|
|
});
|
|
|
|
it("default import", () => {
|
|
parseAndAssertSame('import foo from "foo";');
|
|
});
|
|
|
|
it("import specifier", () => {
|
|
parseAndAssertSame('import { foo } from "foo";');
|
|
});
|
|
|
|
it("import specifier with name", () => {
|
|
parseAndAssertSame('import { foo as bar } from "foo";');
|
|
});
|
|
|
|
it("import bare", () => {
|
|
parseAndAssertSame('import "foo";');
|
|
});
|
|
|
|
it("import meta", () => {
|
|
parseAndAssertSame("const url = import.meta.url");
|
|
});
|
|
|
|
it("export default class declaration", () => {
|
|
parseAndAssertSame("export default class Foo {}");
|
|
});
|
|
|
|
it("export default class expression", () => {
|
|
parseAndAssertSame("export default class {}");
|
|
});
|
|
|
|
it("export default function declaration", () => {
|
|
parseAndAssertSame("export default function Foo() {}");
|
|
});
|
|
|
|
it("export default function expression", () => {
|
|
parseAndAssertSame("export default function () {}");
|
|
});
|
|
|
|
it("export all", () => {
|
|
parseAndAssertSame('export * from "foo";');
|
|
});
|
|
|
|
it("export * as ns", () => {
|
|
parseAndAssertSame('export * as Foo from "foo";');
|
|
});
|
|
|
|
it("export named", () => {
|
|
parseAndAssertSame("var foo = 1;export { foo };");
|
|
});
|
|
|
|
it("export named alias", () => {
|
|
parseAndAssertSame("var foo = 1;export { foo as bar };");
|
|
});
|
|
|
|
it("optional chaining operator", () => {
|
|
parseAndAssertSame("foo?.bar?.().qux()");
|
|
});
|
|
|
|
it("nullish coalescing operator", () => {
|
|
parseAndAssertSame("foo ?? bar");
|
|
});
|
|
|
|
it("logical assignment", () => {
|
|
parseAndAssertSame("foo ??= bar &&= qux ||= quux");
|
|
});
|
|
|
|
it("numeric separator", () => {
|
|
parseAndAssertSame("1_0.0_0e0_1");
|
|
});
|
|
|
|
// Espree doesn't support the pipeline operator yet
|
|
it("pipeline operator (token)", () => {
|
|
const code = "foo |> bar";
|
|
const babylonAST = parseForESLint(code, {
|
|
eslintVisitorKeys: true,
|
|
eslintScopeManager: true,
|
|
babelOptions: BABEL_OPTIONS,
|
|
}).ast;
|
|
expect(babylonAST.tokens[1].type).toEqual("Punctuator");
|
|
});
|
|
|
|
it("brace and bracket hash operator (token)", () => {
|
|
const code = "#[]; #{}";
|
|
const babylonAST = parseForESLint(code, {
|
|
eslintVisitorKeys: true,
|
|
eslintScopeManager: true,
|
|
babelOptions: {
|
|
filename: "test.js",
|
|
parserOpts: {
|
|
plugins: [["recordAndTuple", { syntaxType: "hash" }]],
|
|
tokens: true,
|
|
},
|
|
},
|
|
}).ast;
|
|
expect(babylonAST.tokens[0]).toEqual(
|
|
expect.objectContaining({ type: "Punctuator", value: "#[" }),
|
|
);
|
|
expect(babylonAST.tokens[3]).toEqual(
|
|
expect.objectContaining({ type: "Punctuator", value: "#{" }),
|
|
);
|
|
});
|
|
|
|
it("brace and bracket bar operator (token)", () => {
|
|
const code = "{||}; [||]";
|
|
const babylonAST = parseForESLint(code, {
|
|
eslintVisitorKeys: true,
|
|
eslintScopeManager: true,
|
|
babelOptions: {
|
|
filename: "test.js",
|
|
parserOpts: {
|
|
plugins: [["recordAndTuple", { syntaxType: "bar" }]],
|
|
tokens: true,
|
|
},
|
|
},
|
|
}).ast;
|
|
expect(babylonAST.tokens[0]).toEqual(
|
|
expect.objectContaining({ type: "Punctuator", value: "{|" }),
|
|
);
|
|
expect(babylonAST.tokens[1]).toEqual(
|
|
expect.objectContaining({ type: "Punctuator", value: "|}" }),
|
|
);
|
|
expect(babylonAST.tokens[3]).toEqual(
|
|
expect.objectContaining({ type: "Punctuator", value: "[|" }),
|
|
);
|
|
expect(babylonAST.tokens[4]).toEqual(
|
|
expect.objectContaining({ type: "Punctuator", value: "|]" }),
|
|
);
|
|
});
|
|
|
|
if (process.env.BABEL_8_BREAKING) {
|
|
it("private identifier (token) - ESLint 7", () => {
|
|
const code = "class A { #x }";
|
|
const babylonAST = parseForESLint(code, {
|
|
eslintVisitorKeys: true,
|
|
eslintScopeManager: true,
|
|
babelOptions: BABEL_OPTIONS,
|
|
}).ast;
|
|
expect(babylonAST.tokens[3].type).toEqual("PrivateIdentifier");
|
|
expect(babylonAST.tokens[3].value).toEqual("x");
|
|
});
|
|
} else {
|
|
it("hash (token) - ESLint 7", () => {
|
|
const code = "class A { #x }";
|
|
const babylonAST = parseForESLint(code, {
|
|
eslintVisitorKeys: true,
|
|
eslintScopeManager: true,
|
|
babelOptions: BABEL_OPTIONS,
|
|
}).ast;
|
|
expect(babylonAST.tokens[3].type).toEqual("Punctuator");
|
|
expect(babylonAST.tokens[3].value).toEqual("#");
|
|
});
|
|
}
|
|
|
|
const itNotPublish = process.env.IS_PUBLISH ? it.skip : it;
|
|
|
|
itNotPublish("private identifier (token) - ESLint 8", () => {
|
|
const code = "class A { #x }";
|
|
const babylonAST = parseForESLint8(code, {
|
|
eslintVisitorKeys: true,
|
|
eslintScopeManager: true,
|
|
babelOptions: BABEL_OPTIONS,
|
|
}).ast;
|
|
|
|
expect(babylonAST.tokens[3].type).toEqual("PrivateIdentifier");
|
|
expect(babylonAST.tokens[3].value).toEqual("x");
|
|
});
|
|
|
|
it("static (token)", () => {
|
|
const code = `
|
|
import { static as foo } from "foo";
|
|
|
|
class A {
|
|
static m() {}
|
|
static() {}
|
|
}
|
|
`;
|
|
|
|
parseAndAssertSame(code);
|
|
|
|
const babylonAST = parseForESLint(code, {
|
|
eslintVisitorKeys: true,
|
|
eslintScopeManager: true,
|
|
babelOptions: BABEL_OPTIONS,
|
|
}).ast;
|
|
|
|
const staticKw = { type: "Keyword", value: "static" };
|
|
|
|
expect(babylonAST.tokens[2]).toMatchObject(staticKw);
|
|
expect(babylonAST.tokens[12]).toMatchObject(staticKw);
|
|
expect(babylonAST.tokens[18]).toMatchObject(staticKw);
|
|
});
|
|
|
|
it("parse to PropertyDeclaration when `classFeatures: true`", () => {
|
|
const code = "class A { #x }";
|
|
const babylonAST = parseForESLint(code, {
|
|
eslintVisitorKeys: true,
|
|
eslintScopeManager: true,
|
|
babelOptions: {
|
|
filename: "test.js",
|
|
parserOpts: {
|
|
plugins: [
|
|
["estree", { classFeatures: true }],
|
|
"classPrivateProperties",
|
|
"classProperties",
|
|
],
|
|
},
|
|
},
|
|
}).ast;
|
|
const classDeclaration = babylonAST.body[0];
|
|
expect(classDeclaration.body.body[0].type).toEqual("PropertyDefinition");
|
|
});
|
|
|
|
it("class fields with ESLint 8", () => {
|
|
parseAndAssertSame(
|
|
`
|
|
class A {
|
|
x = 2;
|
|
static #y = 3;
|
|
asi
|
|
#m() {}
|
|
}
|
|
`,
|
|
8,
|
|
);
|
|
});
|
|
|
|
it("static (token) - ESLint 7", () => {
|
|
const code = `
|
|
class A {
|
|
static m() {}
|
|
static() {}
|
|
static x;
|
|
static #y;
|
|
static;
|
|
static = 2;
|
|
}
|
|
`;
|
|
const babylonAST = parseForESLint(code, {
|
|
eslintVisitorKeys: true,
|
|
eslintScopeManager: true,
|
|
babelOptions: BABEL_OPTIONS,
|
|
}).ast;
|
|
|
|
const staticKw = { type: "Keyword", value: "static" };
|
|
|
|
expect(babylonAST.tokens[3]).toMatchObject(staticKw);
|
|
expect(babylonAST.tokens[9]).toMatchObject(staticKw);
|
|
expect(babylonAST.tokens[14]).toMatchObject(staticKw);
|
|
expect(babylonAST.tokens[17]).toMatchObject(staticKw);
|
|
expect(
|
|
babylonAST.tokens[process.env.BABEL_8_BREAKING ? 20 : 21],
|
|
).toMatchObject(staticKw);
|
|
expect(
|
|
babylonAST.tokens[process.env.BABEL_8_BREAKING ? 22 : 23],
|
|
).toMatchObject(staticKw);
|
|
});
|
|
|
|
itNotPublish("static (token) - ESLint 8", () => {
|
|
const code = `
|
|
class A {
|
|
static m() {}
|
|
static() {}
|
|
static x;
|
|
static #y;
|
|
static;
|
|
static = 2;
|
|
}
|
|
`;
|
|
const babylonAST = parseForESLint8(code, {
|
|
eslintVisitorKeys: true,
|
|
eslintScopeManager: true,
|
|
babelOptions: BABEL_OPTIONS,
|
|
}).ast;
|
|
|
|
const staticKw = { type: "Keyword", value: "static" };
|
|
|
|
expect(babylonAST.tokens[3]).toMatchObject(staticKw);
|
|
expect(babylonAST.tokens[9]).toMatchObject(staticKw);
|
|
expect(babylonAST.tokens[14]).toMatchObject(staticKw);
|
|
expect(babylonAST.tokens[17]).toMatchObject(staticKw);
|
|
expect(babylonAST.tokens[20]).toMatchObject(staticKw);
|
|
expect(babylonAST.tokens[22]).toMatchObject(staticKw);
|
|
});
|
|
|
|
if (process.env.BABEL_8_BREAKING) {
|
|
it("pipeline # topic token - ESLint 7", () => {
|
|
const code = `
|
|
x |> #
|
|
y |> #[0]
|
|
class A {
|
|
#x = y |>
|
|
#
|
|
z
|
|
}
|
|
`;
|
|
const babylonAST = parseForESLint(code, {
|
|
eslintVisitorKeys: true,
|
|
eslintScopeManager: true,
|
|
babelOptions: {
|
|
filename: "test.js",
|
|
parserOpts: {
|
|
plugins: [
|
|
["pipelineOperator", { proposal: "hack", topicToken: "#" }],
|
|
],
|
|
tokens: true,
|
|
},
|
|
},
|
|
}).ast;
|
|
|
|
const topicToken = { type: "Punctuator", value: "#" };
|
|
expect(babylonAST.tokens[2]).toMatchObject(topicToken);
|
|
expect(babylonAST.tokens[5]).toMatchObject(topicToken);
|
|
expect(babylonAST.tokens[16]).toMatchObject(topicToken);
|
|
});
|
|
} else {
|
|
it("pipeline # topic token - ESLint 7", () => {
|
|
const code = `
|
|
x |> #
|
|
y |> #[0]
|
|
class A {
|
|
#x = y |>
|
|
#
|
|
z
|
|
}
|
|
`;
|
|
const babylonAST = parseForESLint(code, {
|
|
eslintVisitorKeys: true,
|
|
eslintScopeManager: true,
|
|
babelOptions: {
|
|
filename: "test.js",
|
|
parserOpts: {
|
|
plugins: [
|
|
["pipelineOperator", { proposal: "hack", topicToken: "#" }],
|
|
],
|
|
tokens: true,
|
|
},
|
|
},
|
|
}).ast;
|
|
|
|
const topicToken = { type: "Punctuator", value: "#" };
|
|
expect(babylonAST.tokens[2]).toMatchObject(topicToken);
|
|
expect(babylonAST.tokens[5]).toMatchObject(topicToken);
|
|
expect(babylonAST.tokens[17]).toMatchObject(topicToken);
|
|
});
|
|
}
|
|
|
|
itNotPublish("pipeline # topic token - ESLint 8", () => {
|
|
const code = `
|
|
x |> #
|
|
y |> #[0]
|
|
class A {
|
|
#x = y |>
|
|
#
|
|
z
|
|
}
|
|
`;
|
|
const babylonAST = parseForESLint8(code, {
|
|
eslintVisitorKeys: true,
|
|
eslintScopeManager: true,
|
|
babelOptions: {
|
|
filename: "test.js",
|
|
parserOpts: {
|
|
plugins: [
|
|
["pipelineOperator", { proposal: "hack", topicToken: "#" }],
|
|
],
|
|
tokens: true,
|
|
},
|
|
},
|
|
}).ast;
|
|
|
|
const topicToken = { type: "Punctuator", value: "#" };
|
|
expect(babylonAST.tokens[2]).toMatchObject(topicToken);
|
|
expect(babylonAST.tokens[5]).toMatchObject(topicToken);
|
|
expect(babylonAST.tokens[16]).toMatchObject(topicToken);
|
|
});
|
|
|
|
it("empty program with line comment", () => {
|
|
parseAndAssertSame("// single comment");
|
|
});
|
|
|
|
it("empty program with block comment", () => {
|
|
parseAndAssertSame(" /* multiline\n * comment\n*/");
|
|
});
|
|
|
|
it("line comments", () => {
|
|
parseAndAssertSame(`
|
|
// single comment
|
|
var foo = 15; // comment next to statement
|
|
// second comment after statement
|
|
`);
|
|
});
|
|
|
|
it("block comments", () => {
|
|
parseAndAssertSame(`
|
|
/* single comment */
|
|
var foo = 15; /* comment next to statement */
|
|
/*
|
|
* multiline
|
|
* comment
|
|
*/
|
|
`);
|
|
});
|
|
|
|
it("block comments #124", () => {
|
|
parseAndAssertSame(`
|
|
React.createClass({
|
|
render() {
|
|
// return (
|
|
// <div />
|
|
// ); // <-- this is the line that is reported
|
|
}
|
|
});
|
|
`);
|
|
});
|
|
|
|
it("null", () => {
|
|
parseAndAssertSame("null");
|
|
});
|
|
|
|
it("boolean", () => {
|
|
parseAndAssertSame("if (true) {} else if (false) {}");
|
|
});
|
|
|
|
it("regexp", () => {
|
|
parseAndAssertSame("/affix-top|affix-bottom|affix|[a-z]/");
|
|
});
|
|
|
|
it("regexp", () => {
|
|
parseAndAssertSame("const foo = /foo/;");
|
|
});
|
|
|
|
it("regexp y flag", () => {
|
|
parseAndAssertSame("const foo = /foo/y;");
|
|
});
|
|
|
|
it("regexp u flag", () => {
|
|
parseAndAssertSame("const foo = /foo/u;");
|
|
});
|
|
|
|
it("regexp in a template string", () => {
|
|
parseAndAssertSame('`${/\\d/.exec("1")[0]}`');
|
|
});
|
|
|
|
it("first line is empty", () => {
|
|
parseAndAssertSame('\nimport Immutable from "immutable";');
|
|
});
|
|
|
|
it("empty", () => {
|
|
parseAndAssertSame("");
|
|
});
|
|
|
|
it("jsdoc", () => {
|
|
parseAndAssertSame(`
|
|
/**
|
|
* @param {object} options
|
|
* @return {number}
|
|
*/
|
|
const test = function({ a, b, c }) {
|
|
return a + b + c;
|
|
};
|
|
module.exports = test;
|
|
`);
|
|
});
|
|
|
|
it("empty block with comment", () => {
|
|
parseAndAssertSame(`
|
|
function a () {
|
|
try {
|
|
b();
|
|
} catch (e) {
|
|
// asdf
|
|
}
|
|
}
|
|
`);
|
|
});
|
|
|
|
describe("babel tests", () => {
|
|
it("MethodDefinition", () => {
|
|
parseAndAssertSame(`
|
|
export default class A {
|
|
a() {}
|
|
}
|
|
`);
|
|
});
|
|
|
|
it("MethodDefinition 2", () => {
|
|
parseAndAssertSame(
|
|
"export default class Bar { get bar() { return 42; }}",
|
|
);
|
|
});
|
|
|
|
it("ClassMethod", () => {
|
|
parseAndAssertSame(`
|
|
class A {
|
|
constructor() {
|
|
}
|
|
}
|
|
`);
|
|
});
|
|
|
|
it("ClassMethod multiple params", () => {
|
|
parseAndAssertSame(`
|
|
class A {
|
|
constructor(a, b, c) {
|
|
}
|
|
}
|
|
`);
|
|
});
|
|
|
|
it("ClassMethod multiline", () => {
|
|
parseAndAssertSame(`
|
|
class A {
|
|
constructor (
|
|
a,
|
|
b,
|
|
c
|
|
)
|
|
|
|
{
|
|
|
|
}
|
|
}
|
|
`);
|
|
});
|
|
|
|
it("ClassMethod oneline", () => {
|
|
parseAndAssertSame("class A { constructor(a, b, c) {} }");
|
|
});
|
|
|
|
it("ObjectMethod", () => {
|
|
parseAndAssertSame(`
|
|
var a = {
|
|
b(c) {
|
|
}
|
|
}
|
|
`);
|
|
});
|
|
|
|
it("do not allow import export everywhere", () => {
|
|
expect(() => {
|
|
parseForESLint('function F() { import a from "a"; }', {
|
|
babelOptions: BABEL_OPTIONS,
|
|
});
|
|
}).toThrow(/'import' and 'export' may only appear at the top level/);
|
|
});
|
|
|
|
it("allowImportExportEverywhere", () => {
|
|
expect(() => {
|
|
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("");
|
|
parseAndAssertSame("a");
|
|
});
|
|
|
|
it("getters and setters", () => {
|
|
parseAndAssertSame("class A { get x ( ) { ; } }");
|
|
parseAndAssertSame(`
|
|
class A {
|
|
get x(
|
|
)
|
|
{
|
|
;
|
|
}
|
|
}
|
|
`);
|
|
parseAndAssertSame("class A { set x (a) { ; } }");
|
|
parseAndAssertSame(`
|
|
class A {
|
|
set x(a
|
|
)
|
|
{
|
|
;
|
|
}
|
|
}
|
|
`);
|
|
parseAndAssertSame(`
|
|
var B = {
|
|
get x () {
|
|
return this.ecks;
|
|
},
|
|
set x (ecks) {
|
|
this.ecks = ecks;
|
|
}
|
|
};
|
|
`);
|
|
});
|
|
|
|
it("RestOperator", () => {
|
|
parseAndAssertSame("var { a, ...b } = c");
|
|
parseAndAssertSame("var [ a, ...b ] = c");
|
|
parseAndAssertSame("var a = function (...b) {}");
|
|
});
|
|
|
|
it("SpreadOperator", () => {
|
|
parseAndAssertSame("var a = { b, ...c }");
|
|
parseAndAssertSame("var a = [ a, ...b ]");
|
|
parseAndAssertSame("var a = sum(...b)");
|
|
});
|
|
|
|
it("Async/Await", () => {
|
|
parseAndAssertSame(`
|
|
async function a() {
|
|
await 1;
|
|
}
|
|
`);
|
|
});
|
|
|
|
it("BigInt", () => {
|
|
parseAndAssertSame(`
|
|
const a = 1n;
|
|
`);
|
|
});
|
|
|
|
it("Dynamic Import", () => {
|
|
parseAndAssertSame(`
|
|
const a = import(moduleName);
|
|
`);
|
|
});
|
|
});
|
|
});
|