mirror of https://github.com/lukechilds/node.git
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.
215 lines
6.7 KiB
215 lines
6.7 KiB
9 years ago
|
/**
|
||
|
* @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"]
|
||
|
}
|
||
|
];
|