You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

214 lines
6.7 KiB

/**
* @fileoverview Require or disallow spaces before keywords
* @author Marko Raatikka
* @copyright 2015 Marko Raatikka. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict";
var astUtils = require("../ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
var ERROR_MSG_SPACE_EXPECTED = "Missing space before keyword \"{{keyword}}\".";
var ERROR_MSG_NO_SPACE_EXPECTED = "Unexpected space before keyword \"{{keyword}}\".";
module.exports = function(context) {
var sourceCode = context.getSourceCode();
var SPACE_REQUIRED = context.options[0] !== "never";
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Report the error message
* @param {ASTNode} node node to report
* @param {string} message Error message to be displayed
* @param {object} data Data object for the rule message
* @returns {void}
*/
function report(node, message, data) {
context.report({
node: node,
message: message,
data: data,
fix: function(fixer) {
if (SPACE_REQUIRED) {
return fixer.insertTextBefore(node, " ");
} else {
var tokenBefore = context.getTokenBefore(node);
return fixer.removeRange([tokenBefore.range[1], node.range[0]]);
}
}
});
}
/**
* Check if a token meets the criteria
*
* @param {ASTNode} node The node to check
* @param {Object} left The left-hand side token of the node
* @param {Object} right The right-hand side token of the node
* @param {Object} options See check()
* @returns {void}
*/
function checkTokens(node, left, right, options) {
if (!left) {
return;
}
if (left.type === "Keyword") {
return;
}
if (!SPACE_REQUIRED && typeof options.requireSpace === "undefined") {
return;
}
options = options || {};
options.allowedPrecedingChars = options.allowedPrecedingChars || [ "{" ];
options.requireSpace = typeof options.requireSpace === "undefined" ? SPACE_REQUIRED : options.requireSpace;
var hasSpace = sourceCode.isSpaceBetweenTokens(left, right);
var spaceOk = hasSpace === options.requireSpace;
if (spaceOk) {
return;
}
if (!astUtils.isTokenOnSameLine(left, right)) {
if (!options.requireSpace) {
report(node, ERROR_MSG_NO_SPACE_EXPECTED, { keyword: right.value });
}
return;
}
if (!options.requireSpace) {
report(node, ERROR_MSG_NO_SPACE_EXPECTED, { keyword: right.value });
return;
}
if (options.allowedPrecedingChars.indexOf(left.value) !== -1) {
return;
}
report(node, ERROR_MSG_SPACE_EXPECTED, { keyword: right.value });
}
/**
* Get right and left tokens of a node and check to see if they meet the given criteria
*
* @param {ASTNode} node The node to check
* @param {Object} options Options to validate the node against
* @param {Array} options.allowedPrecedingChars Characters that can precede the right token
* @param {Boolean} options.requireSpace Whether or not the right token needs to be
* preceded by a space
* @returns {void}
*/
function check(node, options) {
options = options || {};
var left = context.getTokenBefore(node);
var right = context.getFirstToken(node);
checkTokens(node, left, right, options);
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
"IfStatement": function(node) {
// if
check(node);
// else
if (node.alternate) {
var tokens = context.getTokensBefore(node.alternate, 2);
if (tokens[0].value === "}") {
check(tokens[1], { requireSpace: SPACE_REQUIRED });
}
}
},
"ForStatement": check,
"ForInStatement": check,
"WhileStatement": check,
"DoWhileStatement": function(node) {
var whileNode = context.getTokenAfter(node.body);
// do
check(node);
// while
check(whileNode, { requireSpace: SPACE_REQUIRED });
},
"SwitchStatement": function(node) {
// switch
check(node);
// case/default
node.cases.forEach(function(caseNode) {
check(caseNode);
});
},
"ThrowStatement": check,
"TryStatement": function(node) {
// try
check(node);
// finally
if (node.finalizer) {
check(context.getTokenBefore(node.finalizer), { requireSpace: SPACE_REQUIRED });
}
},
"CatchClause": function(node) {
check(node, { requireSpace: SPACE_REQUIRED });
},
"WithStatement": check,
"VariableDeclaration": function(node) {
check(node, { allowedPrecedingChars: [ "(", "{" ] });
},
"ReturnStatement": check,
"BreakStatement": check,
"LabeledStatement": check,
"ContinueStatement": check,
"FunctionDeclaration": check,
"FunctionExpression": function(node) {
var left = context.getTokenBefore(node);
var right = context.getFirstToken(node);
// If it's a method, a getter, or a setter, the first token is not the `function` keyword.
if (right.type !== "Keyword") {
return;
}
checkTokens(node, left, right, { allowedPrecedingChars: [ "(", "{", "[" ] });
},
"YieldExpression": function(node) {
check(node, { allowedPrecedingChars: [ "(", "{" ] });
},
"ForOfStatement": check,
"ClassBody": function(node) {
// Find the 'class' token
while (node.value !== "class") {
node = context.getTokenBefore(node);
}
check(node);
}
};
};
module.exports.schema = [
{
"enum": ["always", "never"]
}
];