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

feat!: Rule Tester checks for missing placeholder data in the message #18073

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
chore: differentiate message between a single and multiple missing da…
…ta properties
  • Loading branch information
DMartens committed Feb 9, 2024
commit d354d58f9ea1f15827568c81348c2a8a7d67ad59
8 changes: 4 additions & 4 deletions lib/rule-tester/rule-tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,9 +307,9 @@ function throwForbiddenMethodError(methodName, prototype) {
const messagePlaceholderRegex = /\{\{([^{}]+?)\}\}/gu;
Copy link
Member

@bmish bmish Feb 2, 2024

Choose a reason for hiding this comment

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

I do think this regexp should be exported by a shared util file so it can be used in multiple places.


/**
* Extracts names of missing {{ placeholders }} from the reported message.
* Extracts names of {{ placeholders }} from the reported message.
* @param {string} message Reported message
* @returns {string[]} Array of missing placeholder names
* @returns {string[]} Array of placeholder names
*/
function getMessagePlaceholders(message) {
return Array.from(message.matchAll(messagePlaceholderRegex), ([, name]) => name.trim());
Expand Down Expand Up @@ -1028,7 +1028,7 @@ class RuleTester {

assert.ok(
missing.length === 0,
`The reported message has missing placeholders: ${missing.join(", ")}. Please provide them via the 'data' property in the context.report() call.`
`The reported message has ${missing.length > 1 ? `the missing placeholders: ${missing.map(name => `'${name}'`).join(", ")}` : `a missing placeholder '${missing[0]}'`}. Please provide them via the 'data' property in the context.report() call.`
);
}
} else {
Expand Down Expand Up @@ -1128,7 +1128,7 @@ class RuleTester {

assert.ok(
missing.length === 0,
`The message of the suggestion has missing placeholders: ${missing.join(", ")}. Please provide them via the 'data' property for the suggestion in the context.report() call.`
`The message of the suggestion has ${missing.length > 1 ? `the missing placeholders: ${missing.map(name => `'${name}'`).join(", ")}` : `a missing placeholder '${missing[0]}'`}. Please provide them via the 'data' property for the suggestion in the context.report() call.`
);
}
} else if (hasOwnProperty(expectedSuggestion, "data")) {
Expand Down
21 changes: 21 additions & 0 deletions tests/fixtures/testers/rule-tester/messageId.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,27 @@ module.exports.withMissingData = {
}
};

module.exports.withMultipleMissingDataProperties = {
meta: {
messages: {
avoidFoo: "Avoid using {{ type }} named '{{ name }}'.",
unused: "An unused key"
}
},
create(context) {
return {
Identifier(node) {
if (node.name === "foo") {
context.report({
node,
messageId: "avoidFoo",
});
}
}
};
}
};

module.exports.withPlaceholdersInData = {
meta: {
messages: {
Expand Down
29 changes: 29 additions & 0 deletions tests/fixtures/testers/rule-tester/suggestions.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,32 @@ module.exports.withMissingPlaceholderData = {
};
}
};

module.exports.withMultipleMissingPlaceholderDataProperties = {
meta: {
messages: {
avoidFoo: "Avoid using identifiers named '{{ name }}'.",
rename: "Rename identifier '{{ currentName }}' to '{{ newName }}'"
},
hasSuggestions: true
},
create(context) {
return {
Identifier(node) {
if (node.name === "foo") {
context.report({
node,
messageId: "avoidFoo",
data: {
name: "foo"
},
suggest: [{
messageId: "rename",
fix: fixer => fixer.replaceText(node, "bar")
}]
});
}
}
};
}
};
37 changes: 32 additions & 5 deletions tests/lib/rule-tester/rule-tester.js
DMartens marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -1907,13 +1907,22 @@ describe("RuleTester", () => {
}, "Hydrated message \"Avoid using variables named 'notFoo'.\" does not match \"Avoid using variables named 'foo'.\"");
});

it("should throw if the message has missing placeholders when data is not specified", () => {
it("should throw if the message has a single missing placeholders when data is not specified", () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMissingData, {
valid: [],
invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }]
});
}, "The reported message has missing placeholders: name. Please provide them via the 'data' property in the context.report call");
}, "The reported message has a missing placeholder 'name'. Please provide them via the 'data' property in the context.report() call.");
});

it("should throw if the message has multiple missing placeholders when data is not specified", () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMultipleMissingDataProperties, {
valid: [],
invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }]
});
}, "The reported message has the missing placeholders: 'type', 'name'. Please provide them via the 'data' property in the context.report() call.");
});

it("should throw if the data in the message contains placeholders", () => {
Expand All @@ -1922,7 +1931,7 @@ describe("RuleTester", () => {
valid: [],
invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }]
});
}, "The reported message has missing placeholders: placeholder. Please provide them via the 'data' property in the context.report call");
}, "The reported message has a missing placeholder 'placeholder'. Please provide them via the 'data' property in the context.report() call.");
Copy link
Member

@bmish bmish Feb 5, 2024

Choose a reason for hiding this comment

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

This error message seems misleading since, as @mdjermanovic said, we don't want to suggest that recursive/multipass interpolation is allowed. If there was some way to escape placeholders in data, that could solve it.

Copy link
Member

Choose a reason for hiding this comment

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

When only messageId is specified in the test case, I think we could just check if the meta message really has those placeholders we found as missing. While theoretically the same name can appear as a placeholder and in data, that seems like an extreme edge case.

});

// messageId/message misconfiguration cases
Expand Down Expand Up @@ -2176,7 +2185,7 @@ describe("RuleTester", () => {
});
});

it("should fail with missing data placeholders when data is not specified", () => {
it("should fail with a single missing data placeholder when data is not specified", () => {
assert.throws(() => {
ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMissingPlaceholderData, {
valid: [],
Expand All @@ -2191,7 +2200,25 @@ describe("RuleTester", () => {
}]
}]
});
}, "The message of the suggestion has missing placeholders: newName. Please provide them via the 'data' property for the suggestion in the context.report call");
}, "The message of the suggestion has a missing placeholder 'newName'. Please provide them via the 'data' property for the suggestion in the context.report() call.");
});

it("should fail with multiple missing data placeholders when data is not specified", () => {
assert.throws(() => {
ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMultipleMissingPlaceholderDataProperties, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
messageId: "avoidFoo",
suggestions: [{
messageId: "rename",
output: "var bar;"
}]
}]
}]
});
}, "The message of the suggestion has the missing placeholders: 'currentName', 'newName'. Please provide them via the 'data' property for the suggestion in the context.report() call.");
});

it("should fail when tested using empty suggestion test objects even if the array length is correct", () => {
Expand Down