jscs
Version:
JavaScript Code Style
131 lines (109 loc) • 4.42 kB
JavaScript
var assert = require('assert');
var FUNCTION_TYPE_RE = /Function/;
var PAREN_KEYWORD_TYPE_RE = /Statement$|^CatchClause$/;
var NO_PAREN_KEYWORD_TYPE_RE = /^ExpressionStatement$|^ReturnStatement$|^ThrowStatement$/;
var QUASI_STATEMENT_TYPE_RE = /Statement$|Declaration$/;
function nodeContains(node, token) {
var currentNode = token.parentElement;
while (currentNode) {
if (currentNode === node) {
return true;
}
currentNode = currentNode.parentElement;
}
return false;
}
/**
* Returns the type of AST node or ECMAScript production in which the provided
* open parenthesis token is included.
*
* @param {Object} token
* @returns {String}
*/
exports.categorizeOpenParen = function(token) {
assert(token.value === '(', 'Input token must be a parenthesis');
var node = token.parentElement;
var nodeType = node.type;
var prevToken = token.getPreviousCodeToken();
// Outermost grouping parenthesis
if (!prevToken) {
return 'ParenthesizedExpression';
}
// Part of a parentheses-bearing statement (if, with, while, switch, etc.)
if (prevToken.type === 'Keyword' && PAREN_KEYWORD_TYPE_RE.test(nodeType) &&
!NO_PAREN_KEYWORD_TYPE_RE.test(nodeType)) {
return 'Statement';
}
// Part of a function definition (declaration, expression, method, etc.)
if (FUNCTION_TYPE_RE.test(nodeType) &&
// Name is optional for function expressions
(prevToken.type === 'Identifier' || prevToken.value === 'function')) {
return 'Function';
}
// Part of a call expression
var prevNode = prevToken.parentElement;
if ((nodeType === 'CallExpression' || nodeType === 'NewExpression') &&
// Must not be inside an arguments list or other grouping parentheses
prevToken.value !== ',' && prevToken.value !== '(' &&
// If the callee is parenthesized (e.g., `(foo.bar)()`), prevNode will match node
// Otherwise (e.g., `foo.bar()`), prevToken must be the last token of the callee node
(prevNode === node || prevToken === node.callee.getLastToken())) {
return 'CallExpression';
}
// All remaining cases are grouping parentheses
return 'ParenthesizedExpression';
};
/**
* Returns the type of AST node or ECMAScript production in which the provided
* close parenthesis token is included.
*
* @param {Object} token
* @returns {String}
*/
exports.categorizeCloseParen = function(token) {
assert(token.value === ')', 'Input token must be a parenthesis');
var node = token.parentElement;
var nodeType = node.type;
var nextToken = token.getNextCodeToken();
// Terminal statement
if (nextToken.type === 'EOF') {
switch (nodeType) {
case 'DoWhileStatement':
return 'Statement';
case 'CallExpression':
case 'NewExpression':
return 'CallExpression';
default:
return 'ParenthesizedExpression';
}
}
// Part of a parentheses-bearing statement (if, with, while, switch, etc.)
if (PAREN_KEYWORD_TYPE_RE.test(nodeType) && !NO_PAREN_KEYWORD_TYPE_RE.test(nodeType)) {
// Closing parentheses for `switch` and `catch` must be followed by "{"
// Closing parentheses for `do..while` may be the last punctuation inside a block
if (nextToken.value === '{' || nextToken.value === '}') {
return 'Statement';
}
// Closing parentheses for other statements must be followed by a statement or declaration
var nextNode = nextToken.parentElement;
while (!nodeContains(nextNode, token)) {
if (QUASI_STATEMENT_TYPE_RE.test(nextNode.type)) {
return 'Statement';
}
nextNode = nextNode.parentElement;
}
}
// Part of a function definition (declaration, expression, method, etc.)
if (nextToken.value === '{' && FUNCTION_TYPE_RE.test(nodeType)) {
return 'Function';
}
// Part of a call expression
if ((nodeType === 'CallExpression' || nodeType === 'NewExpression')) {
var openParen = node.callee.getNextToken();
if (openParen.parentElement === node && node.lastChild === token) {
return 'CallExpression';
}
}
// All remaining cases are grouping parentheses
return 'ParenthesizedExpression';
};