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

Fix: ecmaVersion defaults to 5 and allows "latest" in RuleTester #14710

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
43 changes: 42 additions & 1 deletion lib/rule-tester/rule-tester.js
Expand Up @@ -48,7 +48,8 @@ const
equal = require("fast-deep-equal"),
Traverser = require("../../lib/shared/traverser"),
{ getRuleOptionsSchema, validate } = require("../shared/config-validator"),
{ Linter, SourceCodeFixer, interpolate } = require("../linter");
{ Linter, SourceCodeFixer, interpolate } = require("../linter"),
espree = require("espree");

const ajv = require("../shared/ajv")({ strictDefaults: true });

Expand Down Expand Up @@ -528,6 +529,46 @@ class RuleTester {

linter.defineParser(config.parser, wrapParser(require(config.parser)));

/*
* RuleTester always wraps parser, so Linter can't know whether or not it's espree.
* Therefore, RuleTester has to duplicate Linter's espree-specific normalizations.
*/
if (
config.parser === espreePath &&
(
typeof config.parserOptions === "undefined" ||
typeof config.parserOptions === "object" && config.parserOptions !== null
)
) {
const ecmaVersion = config.parserOptions && config.parserOptions.ecmaVersion;

if (
typeof ecmaVersion === "undefined" &&
!(

// skip if an environment sets ecmaVersion, because parserOptions.ecmaVersion has precedence
typeof config.env === "object" && config.env !== null &&
Object.keys(config.env).some(
envName => config.env[envName] && /^es\d+/u.test(envName)
)
)
) {
config = merge(config, {
parserOptions: {
ecmaVersion: 5
}
});
}

if (ecmaVersion === "latest") {
config = merge(config, {
parserOptions: {
ecmaVersion: espree.latestEcmaVersion
}
});
}
}

Copy link
Member

Choose a reason for hiding this comment

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

it can be removed when eslint has the same defaults with espree?

if (schema) {
ajv.validateSchema(schema);

Expand Down
25 changes: 25 additions & 0 deletions tests/fixtures/parsers/empty-program-parser.js
@@ -0,0 +1,25 @@
exports.parse = function(text, parserOptions) {
return {
"type": "Program",
"start": 0,
"end": 0,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 0
}
},
"range": [
0,
0
],
"body": [],
"sourceType": "script",
"comments": [],
"tokens": []
};
};
201 changes: 200 additions & 1 deletion tests/lib/rule-tester/rule-tester.js
Expand Up @@ -11,7 +11,8 @@ const sinon = require("sinon"),
EventEmitter = require("events"),
{ RuleTester } = require("../../../lib/rule-tester"),
assert = require("chai").assert,
nodeAssert = require("assert");
nodeAssert = require("assert"),
espree = require("espree");

const NODE_ASSERT_STRICT_EQUAL_OPERATOR = (() => {
try {
Expand Down Expand Up @@ -782,6 +783,204 @@ describe("RuleTester", () => {
assert.strictEqual(spy.args[1][1].parser, require.resolve("esprima"));
});

it("should pass normalized ecmaVersion to the rule", () => {
const reportEcmaVersionRule = {
meta: {
messages: {
ecmaVersionMessage: "context.parserOptions.ecmaVersion is {{type}} {{ecmaVersion}}."
}
},
create: context => ({
Program(node) {
const { ecmaVersion } = context.parserOptions;

context.report({
node,
messageId: "ecmaVersionMessage",
data: { type: typeof ecmaVersion, ecmaVersion }
});
}
})
};

const notEspree = require.resolve("../../fixtures/parsers/empty-program-parser");

ruleTester.run("report-ecma-version", reportEcmaVersionRule, {
valid: [],
invalid: [
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "5" } }]
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "5" } }],
parserOptions: {}
},
{
code: "<div/>",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "5" } }],
parserOptions: { ecmaFeatures: { jsx: true } }
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "5" } }],
parser: require.resolve("espree")
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
parserOptions: { ecmaVersion: 6 }
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
parserOptions: { ecmaVersion: 2015 }
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "5" } }],
env: { browser: true }
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "5" } }],
env: { es6: false }
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
env: { es6: true }
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "8" } }],
env: { es6: false, es2017: true }
},
{
code: "let x",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
env: { es6: "truthy" }
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "8" } }],
env: { es2017: true }
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "11" } }],
env: { es2020: true }
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "12" } }],
env: { es2021: true }
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
parserOptions: { ecmaVersion: "latest" }
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
parser: require.resolve("espree"),
parserOptions: { ecmaVersion: "latest" }
},
{
code: "<div/>",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
parserOptions: { ecmaVersion: "latest", ecmaFeatures: { jsx: true } }
},
{
code: "import 'foo'",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
parserOptions: { ecmaVersion: "latest", sourceType: "module" }
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
parserOptions: { ecmaVersion: "latest" },
env: { es6: true }
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
parserOptions: { ecmaVersion: "latest" },
env: { es2020: true }
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
parser: notEspree
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
parser: notEspree,
parserOptions: {}
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "5" } }],
parser: notEspree,
parserOptions: { ecmaVersion: 5 }
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
parser: notEspree,
parserOptions: { ecmaVersion: 6 }
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
parser: notEspree,
parserOptions: { ecmaVersion: 2015 }
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "string", ecmaVersion: "latest" } }],
parser: notEspree,
parserOptions: { ecmaVersion: "latest" }
}
]
});

[{ parserOptions: { ecmaVersion: 6 } }, { env: { es6: true } }].forEach(options => {
new RuleTester(options).run("report-ecma-version", reportEcmaVersionRule, {
valid: [],
invalid: [
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }]
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
parserOptions: {}
}
]
});
});

new RuleTester({ parser: notEspree }).run("report-ecma-version", reportEcmaVersionRule, {
valid: [],
invalid: [
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }]
},
{
code: "",
errors: [{ messageId: "ecmaVersionMessage", data: { type: "string", ecmaVersion: "latest" } }],
parserOptions: { ecmaVersion: "latest" }
}
]
});
});

it("should pass-through services from parseForESLint to the rule", () => {
const enhancedParserPath = require.resolve("../../fixtures/parsers/enhanced-parser");
const disallowHiRule = {
Expand Down