pokemon-showdown/dev-tools/jscs-custom-rules/validate-conditionals.js

166 lines
4.0 KiB
JavaScript

/**
* Enforces Pokémon Showdown code style for conditionals
*
* Type: `Boolean`
*
* Value: `true`
*
* #### Example
*
* ```js
* "validateConditionals": true
* ```
*
* ##### Valid
*
* ```js
* if (console.log) {
* console.log("Test");
* }
*
* if (console.log) {
* console.log("Test");
* } else {
* throw new Error("Error");
* }
*
* if (console.log) {
* console.log("Test");
* } else {
* 0;
* }
*
* if (console.log) {
* console.log("Test");
* } else if (Number.isFinite) {
* Number.isFinite(42);
* }
*
* if (Math.random) Math.random();
*
* if (a == 1) // Do something with this magic number
* ```
*
* ##### Invalid
*
* ```js
* if (console.log) {
* console.log("Test");
* } else if (Number.isFinite) Number.isFinite();
*
* if (Math.random) {
* Math.random();
* } else {Number.isFinite()};
*
* if (Math.random) {
* Math.random();
* } else if (Number.isFinite) {Number.isFinite()};
*
* if (Math.random) Math.random();
* else {
* Number.isFinite();
* }
*
* if (Math.random) Math.random(); else Number.isFinite();
*
* if (Math.random) Math.random();
* else Number.isFinite();
*
* if (Math.random) Math.random();
* else if (Number.isFinite) Number.isFinite();
* else Number.isInteger();
*
* if (Math.random)
* Math.random();
*
* ```
*/
var assert = require('assert');
module.exports = function () {};
module.exports.prototype = {
configure: function (options) {
assert(
options === true,
this.getOptionName() + ' option requires a true value or should be removed'
);
},
getOptionName: function () {
return 'validateConditionals';
},
check: function (file, errors) {
file.iterateNodesByType('IfStatement', function (node) {
var consequent = node.consequent;
var statementType = consequent.type;
// Either all `BlockStatement` or none.
var subNode = node;
while (subNode.alternate) {
subNode = subNode.alternate;
if (subNode.type === 'IfStatement') {
if (subNode.consequent.type !== statementType) {
errors.add("Mixed conditional blocks and expressions are disallowed", subNode.loc.start);
break;
} else if (subNode.consequent.type !== 'BlockStatement') {
errors.add("Nested conditionals require curly braces", subNode.loc.start);
break;
}
} else {
if (subNode.type !== statementType && (subNode.type === 'BlockStatement' || statementType === 'BlockStatement')) {
errors.add("Mixed conditional blocks and expressions are disallowed", subNode.loc.start);
} else if (subNode.type !== 'BlockStatement') {
errors.add("Nested conditionals require curly braces", subNode.loc.start);
}
break;
}
}
// Curly braces iff multiline
var nodesCheck = [consequent];
if (node.alternate) nodesCheck.push(node.alternate);
for (var i = 0; i < nodesCheck.length; i++) {
var subNode = nodesCheck[i];
if (subNode.type === 'BlockStatement') {
var openingBrace = file.getFirstNodeToken(subNode);
var closingBrace = file.getLastNodeToken(subNode);
if (!subNode.body.length) {
// Empty block
errors.assert.differentLine({
token: openingBrace,
nextToken: closingBrace
});
continue;
}
var nextToken = file.getFirstNodeToken(subNode.body[0]);
var prevToken = file.getPrevToken(closingBrace);
errors.assert.differentLine({
token: openingBrace,
nextToken: nextToken,
message: 'Newline after opening curly brace required for block conditional (a)'
});
errors.assert.differentLine({
token: prevToken,
nextToken: closingBrace,
message: 'Newline before closing curly brace required for block conditional (b)'
});
} else if (subNode.type !== 'IfStatement') {
if (subNode === consequent) {
var token = file.getFirstNodeToken(subNode);
errors.assert.sameLine({
token: node.test,
nextToken: token,
message: 'Newline disallowed in non-block conditional (b)'
});
}
}
}
});
}
};