UNPKG

kitchensink

Version:

Dispatch's awesome components and style guide

121 lines (99 loc) 4.21 kB
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$/; /** * Returns the type of AST node or ECMAScript production in which the provided * open parenthesis token is included. * * @param {Object} token * @param {JsFile} file * @returns {String} */ exports.categorizeOpenParen = function(token, file) { assert(token.value === '(', 'Input token must be a parenthesis'); var node = file.getNodeByRange(token.range[0]); var nodeType = node.type; var prevToken = file.getPrevToken(token); // 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 = file.getNodeByRange(prevToken.range[0]); 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 === file.getLastNodeToken(node.callee))) { 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 * @param {JsFile} file * @returns {String} */ exports.categorizeCloseParen = function(token, file) { assert(token.value === ')', 'Input token must be a parenthesis'); var node = file.getNodeByRange(token.range[0]); var nodeType = node.type; var nextToken = file.getNextToken(token); // 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 = file.getNodeByRange(nextToken.range[0]); while (nextNode.range[0] >= token.range[1]) { if (QUASI_STATEMENT_TYPE_RE.test(nextNode.type)) { return 'Statement'; } nextNode = nextNode.parentNode; } } // 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') && nextToken.range[0] >= node.range[1]) { return 'CallExpression'; } // All remaining cases are grouping parentheses return 'ParenthesizedExpression'; };