d2-ui
Version:
152 lines (128 loc) • 5.45 kB
JavaScript
/**
* @fileoverview Rule to require newlines before `return` statement
* @author Kai Cataldo
*/
;
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "require an empty line before `return` statements",
category: "Stylistic Issues",
recommended: false
},
schema: []
},
create: function(context) {
var sourceCode = context.getSourceCode();
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Tests whether node is preceded by supplied tokens
* @param {ASTNode} node - node to check
* @param {array} testTokens - array of tokens to test against
* @returns {boolean} Whether or not the node is preceded by one of the supplied tokens
* @private
*/
function isPrecededByTokens(node, testTokens) {
var tokenBefore = sourceCode.getTokenBefore(node);
return testTokens.some(function(token) {
return tokenBefore.value === token;
});
}
/**
* Checks whether node is the first node after statement or in block
* @param {ASTNode} node - node to check
* @returns {boolean} Whether or not the node is the first node after statement or in block
* @private
*/
function isFirstNode(node) {
var parentType = node.parent.type;
if (node.parent.body) {
return Array.isArray(node.parent.body)
? node.parent.body[0] === node
: node.parent.body === node;
}
if (parentType === "IfStatement") {
return isPrecededByTokens(node, ["else", ")"]);
} else if (parentType === "DoWhileStatement") {
return isPrecededByTokens(node, ["do"]);
} else if (parentType === "SwitchCase") {
return isPrecededByTokens(node, [":"]);
} else {
return isPrecededByTokens(node, [")"]);
}
}
/**
* Returns the number of lines of comments that precede the node
* @param {ASTNode} node - node to check for overlapping comments
* @param {number} lineNumTokenBefore - line number of previous token, to check for overlapping comments
* @returns {number} Number of lines of comments that precede the node
* @private
*/
function calcCommentLines(node, lineNumTokenBefore) {
var comments = sourceCode.getComments(node).leading,
numLinesComments = 0;
if (!comments.length) {
return numLinesComments;
}
comments.forEach(function(comment) {
numLinesComments++;
if (comment.type === "Block") {
numLinesComments += comment.loc.end.line - comment.loc.start.line;
}
// avoid counting lines with inline comments twice
if (comment.loc.start.line === lineNumTokenBefore) {
numLinesComments--;
}
if (comment.loc.end.line === node.loc.start.line) {
numLinesComments--;
}
});
return numLinesComments;
}
/**
* Checks whether node is preceded by a newline
* @param {ASTNode} node - node to check
* @returns {boolean} Whether or not the node is preceded by a newline
* @private
*/
function hasNewlineBefore(node) {
var tokenBefore = sourceCode.getTokenBefore(node),
lineNumNode = node.loc.start.line,
lineNumTokenBefore,
commentLines;
/**
* Global return (at the beginning of a script) is a special case.
* If there is no token before `return`, then we expect no line
* break before the return. Comments are allowed to occupy lines
* before the global return, just no blank lines.
* Setting lineNumTokenBefore to zero in that case results in the
* desired behavior.
*/
if (tokenBefore) {
lineNumTokenBefore = tokenBefore.loc.end.line;
} else {
lineNumTokenBefore = 0; // global return at beginning of script
}
commentLines = calcCommentLines(node, lineNumTokenBefore);
return (lineNumNode - lineNumTokenBefore - commentLines) > 1;
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
ReturnStatement: function(node) {
if (!isFirstNode(node) && !hasNewlineBefore(node)) {
context.report({
node: node,
message: "Expected newline before return statement."
});
}
}
};
}
};