Skip to content

Commit

Permalink
Fix: Make rule-tester strictly check messageId. (ref eslint#9890)
Browse files Browse the repository at this point in the history
This change makes rule-tester check messageId directly by value provided instead
of the current behavior, which is to check the messageId's message value against
the message returned from the rule at runtime.
  • Loading branch information
betaorbust committed Jan 28, 2018
1 parent 084351b commit f66a692
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 30 deletions.
6 changes: 1 addition & 5 deletions docs/developer-guide/working-with-rules.md
Expand Up @@ -228,16 +228,12 @@ var RuleTester = require("eslint").RuleTester;
var ruleTester = new RuleTester();
ruleTester.run("my-rule", rule, {
valid: ["bar", "baz"],

invalid: [
{
code: "foo",
errors: [
{
messageId: "avoidName",
data: {
name: "foo"
}
messageId: "avoidName"
}
]
}
Expand Down
1 change: 1 addition & 0 deletions lib/report-translator.js
Expand Up @@ -198,6 +198,7 @@ function normalizeFixes(descriptor, sourceCode) {
* @param {(0|1|2)} options.severity Rule severity
* @param {(ASTNode|null)} options.node Node
* @param {string} options.message Error message
* @param {string} [options.messageId] The error message ID.
* @param {{start: SourceLocation, end: (SourceLocation|null)}} options.loc Start and end location
* @param {{text: string, range: (number[]|null)}} options.fix The fix object
* @param {string[]} options.sourceLines Source lines
Expand Down
39 changes: 16 additions & 23 deletions lib/testers/rule-tester.js
Expand Up @@ -47,8 +47,7 @@ const lodash = require("lodash"),
ajv = require("../util/ajv"),
Linter = require("../linter"),
Environments = require("../config/environments"),
SourceCodeFixer = require("../util/source-code-fixer"),
interpolate = require("../util/interpolate");
SourceCodeFixer = require("../util/source-code-fixer");

//------------------------------------------------------------------------------
// Private Members
Expand Down Expand Up @@ -487,34 +486,28 @@ class RuleTester {

/*
* Error object.
* This may have a message, node type, line, and/or
* This may have a message, messageId, data, node type, line, and/or
* column.
*/
if (error.message) {
if (hasOwnProperty(error, "message")) {
assert.strictEqual(hasOwnProperty(error, "messageId"), false, "Error should not specify both 'message' and a 'messageId'.");
assert.strictEqual(hasOwnProperty(error, "data"), false, "Error should not specify both 'data' and 'message'.");
assertMessageMatches(message.message, error.message);
}

if (error.messageId) {
const hOP = Object.hasOwnProperty.call.bind(Object.hasOwnProperty);

// verify that `error.message` is `undefined`
assert.strictEqual(error.message, void 0, "Error should not specify both a message and a messageId.");
if (!hOP(rule, "meta") || !hOP(rule.meta, "messages")) {
assert.fail("Rule must specify a messages hash in `meta`");
}
if (!hOP(rule.meta.messages, error.messageId)) {
} else if (hasOwnProperty(error, "messageId")) {
assert.ok(
hasOwnProperty(rule, "meta") && hasOwnProperty(rule.meta, "messages"),
"Error can not use 'messageId' if rule under test doesn't define 'meta.messages'."
);
if (!hasOwnProperty(rule.meta.messages, error.messageId)) {
const friendlyIDList = `[${Object.keys(rule.meta.messages).map(key => `'${key}'`).join(", ")}]`;

assert.fail(`Invalid messageId '${error.messageId}'. Expected one of ${friendlyIDList}.`);
}

let expectedMessage = rule.meta.messages[error.messageId];

if (error.data) {
expectedMessage = interpolate(expectedMessage, error.data);
}

assertMessageMatches(message.message, expectedMessage);
assert.strictEqual(
error.messageId,
message.messageId,
`messageId mismatch saw '${message.messageId}' and expected '${error.messageId}'.`
);
}

if (error.type) {
Expand Down
36 changes: 36 additions & 0 deletions tests/fixtures/testers/rule-tester/messageId.js
@@ -0,0 +1,36 @@
"use strict";
module.exports.withMetaWithData = {
meta: {
messages: {
avoidFoo: "Avoid using variables named '{{ name }}'.",
unused: "An unused key"
}
},
create(context) {
return {
Identifier(node) {
if (node.name === "foo") {
context.report({
node,
messageId: "avoidFoo",
data: {
name: "foo"
}
});
}
}
};
}
};

module.exports.withoutMetaWithoutData = {
create(context) {
return {
Identifier(node) {
if (node.name === "foo") {
context.report({ node, message: "Avoid using variables named 'foo'."});
}
}
};
}
};
4 changes: 2 additions & 2 deletions tests/lib/rules/no-fallthrough.js
Expand Up @@ -162,8 +162,8 @@ ruleTester.run("no-fallthrough", rule, {
}],
errors: [
{
message: errorsDefault.message,
type: errorsDefault.type,
message: errorsDefault[0].message,
type: errorsDefault[0].type,
line: 4,
column: 1
}
Expand Down
52 changes: 52 additions & 0 deletions tests/lib/testers/rule-tester.js
Expand Up @@ -830,6 +830,58 @@ describe("RuleTester", () => {
}, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios");
});

// Nominal message/messageId use cases
it("should assert match if message provided in both test and result.", () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withoutMetaWithoutData, {
valid: [],
invalid: [{ code: "foo", errors: [{ message: "something" }] }]
});
}, /Avoid using variables named/);

assert.doesNotThrow(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withoutMetaWithoutData, {
valid: [],
invalid: [{ code: "foo", errors: [{ message: "Avoid using variables named 'foo'." }] }]
});
});
});

it("should assert match between messageId if provided in both test and result.", () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
valid: [],
invalid: [{ code: "foo", errors: [{ messageId: "unused" }] }]
});
}, "messageId mismatch saw 'avoidFoo' and expected 'unused'.");

assert.doesNotThrow(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
valid: [],
invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }]
});
});
});

// messageId/message misconfiguration cases

it("should throw if user tests for both message and messageId", () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
valid: [],
invalid: [{ code: "foo", errors: [{ message: "something", messageId: "avoidFoo" }] }]
});
}, "Error should not specify both 'message' and a 'messageId'.");
});
it("should throw if user tests for messageId but the rule doesn't use the messageId meta syntax.", () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withoutMetaWithoutData, {
valid: [],
invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }]
});
}, "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'");
});

describe("naming test cases", () => {

/**
Expand Down

0 comments on commit f66a692

Please sign in to comment.