Bug 1536556 - Extend no-throw-cr-literal ESLint rule to forbid and fix throw new Error(Cr.ERROR);. r=Standard8

Code should be using `throw Components.Exception("", Cr.ERROR);` instead,
since `new Error()` just converts the int value of the Cr.ERROR into a string,
whereas `Exception` constructs an Exception object with the result property set
to the Cr.ERROR value, so other code can identify it.

Differential Revision: https://phabricator.services.mozilla.com/D28074
This commit is contained in:
Ian Moody 2020-05-05 17:43:39 +00:00
parent 9243ee5033
commit a52653c73c
5 changed files with 70 additions and 19 deletions

View file

@ -193,12 +193,15 @@ It disallows statements such as:
throw Cr.NS_ERROR_UNEXPECTED; throw Cr.NS_ERROR_UNEXPECTED;
throw Components.results.NS_ERROR_ABORT; throw Components.results.NS_ERROR_ABORT;
throw new Error(Cr.NS_ERROR_NO_INTERFACE);
Throwing bare literals is inferior to throwing Exception objects, which provide Throwing bare literals is inferior to throwing Exception objects, which provide
stack information. Cr.ERRORs should be be passed as the second argument to stack information. Cr.ERRORs should be be passed as the second argument to
``Components.Exception()`` to create an Exception object with stack info, and ``Components.Exception()`` to create an Exception object with stack info, and
the correct result property corresponding to the NS_ERROR that other code the correct result property corresponding to the NS_ERROR that other code
expects. expects.
Using a regular ``new Error()`` to wrap just turns it into a string and doesn't
set the result property, so the errors can't be recognised.
This option can be autofixed (``--fix``). This option can be autofixed (``--fix``).

View file

@ -12,6 +12,34 @@
// Rule Definition // Rule Definition
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
function isCr(object) {
return object.type === "Identifier" && object.name === "Cr";
}
function isComponentsResults(object) {
return (
object.type === "MemberExpression" &&
object.object.type === "Identifier" &&
object.object.name === "Components" &&
object.property.type === "Identifier" &&
object.property.name === "results"
);
}
function isNewError(argument) {
return (
argument.type === "NewExpression" &&
argument.callee.type === "Identifier" &&
argument.callee.name === "Error" &&
argument.arguments.length === 1
);
}
function fixT(context, node, argument, fixer) {
const sourceText = context.getSourceCode().getText(argument);
return fixer.replaceText(node, `Components.Exception("", ${sourceText})`);
}
module.exports = { module.exports = {
meta: { meta: {
fixable: "code", fixable: "code",
@ -19,6 +47,10 @@ module.exports = {
bareCR: "Do not throw bare Cr.ERRORs, use Components.Exception instead", bareCR: "Do not throw bare Cr.ERRORs, use Components.Exception instead",
bareComponentsResults: bareComponentsResults:
"Do not throw bare Components.results.ERRORs, use Components.Exception instead", "Do not throw bare Components.results.ERRORs, use Components.Exception instead",
newErrorCR:
"Do not pass Cr.ERRORs to new Error(), use Components.Exception instead",
newErrorComponentsResults:
"Do not pass Components.results.ERRORs to new Error(), use Components.Exception instead",
}, },
}, },
@ -26,35 +58,41 @@ module.exports = {
return { return {
ThrowStatement(node) { ThrowStatement(node) {
if (node.argument.type === "MemberExpression") { if (node.argument.type === "MemberExpression") {
function fix(fixer) { const fix = fixT.bind(null, context, node.argument, node.argument);
const sourceCode = context.getSourceCode();
const source = sourceCode.getText(node.argument);
return fixer.replaceText(
node.argument,
`Components.Exception("", ${source})`
);
}
const obj = node.argument.object; if (isCr(node.argument.object)) {
if (obj.type === "Identifier" && obj.name === "Cr") {
context.report({ context.report({
node, node,
messageId: "bareCR", messageId: "bareCR",
fix, fix,
}); });
} else if ( } else if (isComponentsResults(node.argument.object)) {
obj.type === "MemberExpression" &&
obj.object.type === "Identifier" &&
obj.object.name === "Components" &&
obj.property.type === "Identifier" &&
obj.property.name === "results"
) {
context.report({ context.report({
node, node,
messageId: "bareComponentsResults", messageId: "bareComponentsResults",
fix, fix,
}); });
} }
} else if (isNewError(node.argument)) {
const argument = node.argument.arguments[0];
if (argument.type === "MemberExpression") {
const fix = fixT.bind(null, context, node.argument, argument);
if (isCr(argument.object)) {
context.report({
node,
messageId: "newErrorCR",
fix,
});
} else if (isComponentsResults(argument.object)) {
context.report({
node,
messageId: "newErrorComponentsResults",
fix,
});
}
}
} }
}, },
}; };

View file

@ -1,6 +1,6 @@
{ {
"name": "eslint-plugin-mozilla", "name": "eslint-plugin-mozilla",
"version": "2.5.0", "version": "2.6.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View file

@ -1,6 +1,6 @@
{ {
"name": "eslint-plugin-mozilla", "name": "eslint-plugin-mozilla",
"version": "2.5.0", "version": "2.6.0",
"description": "A collection of rules that help enforce JavaScript coding standard in the Mozilla project.", "description": "A collection of rules that help enforce JavaScript coding standard in the Mozilla project.",
"keywords": [ "keywords": [
"eslint", "eslint",

View file

@ -48,5 +48,15 @@ ruleTester.run("no-throw-cr-literal", rule, {
'function t() { throw Components.Exception("", Cr.NS_ERROR_NULL_POINTER); }', 'function t() { throw Components.Exception("", Cr.NS_ERROR_NULL_POINTER); }',
"bareCR" "bareCR"
), ),
invalidCode(
"throw new Error(Cr.NS_ERROR_ABORT);",
'throw Components.Exception("", Cr.NS_ERROR_ABORT);',
"newErrorCR"
),
invalidCode(
"throw new Error(Components.results.NS_ERROR_ABORT);",
'throw Components.Exception("", Components.results.NS_ERROR_ABORT);',
"newErrorComponentsResults"
),
], ],
}); });