UNPKG

escomplex-plugin-syntax-estree

Version:

Provides a plugin for typhonjs-escomplex module processing which loads syntax definitions for trait resolution for ESTree AST.

1,309 lines (1,165 loc) 53.4 kB
import AbstractSyntaxLoader from 'typhonjs-escomplex-commons/src/module/plugin/syntax/AbstractSyntaxLoader'; import ASTGenerator from 'typhonjs-escomplex-commons/src/utils/ast/ASTGenerator'; import HalsteadArray from 'typhonjs-escomplex-commons/src/module/traits/HalsteadArray'; import TraitUtil from 'typhonjs-escomplex-commons/src/module/traits/TraitUtil'; import actualize from 'typhonjs-escomplex-commons/src/module/traits/actualize'; /** * Provides an typhonjs-escomplex-module / ESComplexModule plugin which loads syntax definitions for trait resolution * for all ESTree AST nodes up to and including ES6. * * @see https://www.npmjs.com/package/typhonjs-escomplex-module */ export default class PluginSyntaxESTree extends AbstractSyntaxLoader { // ESComplexModule plugin callbacks ------------------------------------------------------------------------------ /** * Loads any default settings that are not already provided by any user options. * * @param {object} ev - escomplex plugin event data. * * The following options are: * ``` * (boolean) commonjs - Boolean indicating that source code being processed is CommonJS; defaults to false. * * (function) dependencyResolver - Provides a function to resolve dynamic dependencies; defaults to undefined. * * (boolean|object) esmImportExport - As a boolean indicating whether ES Modules import / export statements * contribute to modules logical SLOC & Halstead operators / operands; defaults * to false. As an object separate boolean entries to set logical SLOC (lloc) * and operators / operands (halstead) are available to set these parameters * separately. * * (boolean) forin - Boolean indicating whether for...in / for...of loops should be considered a source of * cyclomatic complexity; defaults to false. * * (boolean) logicalor - Boolean indicating whether operator || should be considered a source of cyclomatic * complexity; defaults to true. * * (boolean) switchcase - Boolean indicating whether switch statements should be considered a source of cyclomatic * complexity; defaults to true. * * (boolean|object) templateExpressions - As a boolean indicating whether template literal expressions * contribute to logical SLOC & Halstead operators / operands; defaults * to `true`. As an object separate boolean entries to set logical SLOC (lloc) * and operators / operands (halstead) are available to set these parameters * separately. When `lloc` is true tagged template expressions * increment lloc by 1. If `halstead` is true then operators are added for '``' * and each instance of a `${...}` expression. * (boolean) trycatch - Boolean indicating whether catch clauses should be considered a source of cyclomatic * complexity; defaults to false. * ``` */ onConfigure(ev) { ev.data.settings.commonjs = typeof ev.data.options.commonjs === 'boolean' ? ev.data.options.commonjs : false; ev.data.settings.dependencyResolver = typeof ev.data.options.dependencyResolver === 'function' ? ev.data.options.dependencyResolver : void 0; if (typeof ev.data.options.esmImportExport === 'object') { ev.data.settings.esmImportExport = { halstead: typeof ev.data.options.esmImportExport.halstead === 'boolean' ? ev.data.options.esmImportExport.halstead : false, lloc: typeof ev.data.options.esmImportExport.lloc === 'boolean' ? ev.data.options.esmImportExport.lloc : false, }; } else { // Catch all boolean to set `esmImportExport.halstead` & `esmImportExport.lloc` if a boolean is supplied. const value = typeof ev.data.options.esmImportExport === 'boolean' ? ev.data.options.esmImportExport : false; ev.data.settings.esmImportExport = { halstead: value, lloc: value }; } ev.data.settings.forin = typeof ev.data.options.forin === 'boolean' ? ev.data.options.forin : false; ev.data.settings.logicalor = typeof ev.data.options.logicalor === 'boolean' ? ev.data.options.logicalor : true; ev.data.settings.switchcase = typeof ev.data.options.switchcase === 'boolean' ? ev.data.options.switchcase : true; if (typeof ev.data.options.templateExpression === 'object') { ev.data.settings.templateExpression = { halstead: typeof ev.data.options.templateExpression.halstead === 'boolean' ? ev.data.options.templateExpression.halstead : true, lloc: typeof ev.data.options.templateExpression.lloc === 'boolean' ? ev.data.options.templateExpression.lloc : true, }; } else { // Catch all boolean to set `templateExpression.halstead` & `templateExpression.lloc` if a boolean is supplied. const value = typeof ev.data.options.templateExpression === 'boolean' ? ev.data.options.templateExpression : true; ev.data.settings.templateExpression = { halstead: value, lloc: value }; } ev.data.settings.trycatch = typeof ev.data.options.trycatch === 'boolean' ? ev.data.options.trycatch : false; } // Core / ES5 ESTree AST nodes ----------------------------------------------------------------------------------- /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#arrayexpression * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ArrayExpression() { return actualize(0, 0, (node) => { const operators = ['[]']; if (Array.isArray(node.elements) && node.elements.length > 0) // Add length - 1 commas { operators.push(...(new Array(node.elements.length - 1).fill(','))); } return operators; }); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#assignmentexpression * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ AssignmentExpression() { return actualize((node, parent) => { // Handle multiple assignment such as `a = b = c = 1` which resolves logical SLOC of 3. // The top most `ExpressionStatement` already provides 1 lloc and `AssignmentExpression` for 'c' & '1' are // additionally counted as additional lloc. return parent && parent.type !== 'ExpressionStatement' ? 1 : 0; }, 0, (node) => { return node.operator; }); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#blockstatement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ BlockStatement() { return actualize(0, 0); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#binaryexpression * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ BinaryExpression() { return actualize(0, 0, (node) => { return node.operator; }); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#breakstatement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ BreakStatement() { return actualize(1, 0, 'break'); } /** * ES5 Node * * Processes CommonJS dependencies if settings.commonjs is set to true. An optional function * settings.dependencyResolver may be used to resolve dynamic dependencies. * @param {object} settings - escomplex settings * * @see https://github.com/estree/estree/blob/master/es5.md#callexpression * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ CallExpression(settings) { return actualize((node, parent) => // lloc { // Handles nested CallExpression nodes. Since ExpressionStatement provides 1 lloc already the first // CallExpression ignores posting an additional lloc; subsequent CallExpression nodes do contribute 1 lloc. // Likewise the first CallExpression after a YieldExpression doesn't contribute 1 lloc. return parent && parent.type !== 'ExpressionStatement' && parent.type !== 'YieldExpression' ? 1 : 0; }, 0, // cyclomatic '()', // operators void 0, // operands void 0, // ignoreKeys void 0, // newScope (node) => { // Only process CJS dependencies if settings.commonjs is true. if (settings.commonjs && node.callee.type === 'Identifier' && node.callee.name === 'require' && node.arguments.length === 1) { const dependency = node.arguments[0]; let dependencyPath = '* dynamic dependency *'; if (dependency.type === 'Literal' || dependency.type === 'StringLiteral') { dependencyPath = typeof settings.dependencyResolver === 'function' ? settings.dependencyResolver(dependency.value) : dependency.value; } return { line: node.loc.start.line, path: dependencyPath, type: 'cjs' }; } }); } /** * ES5 Node * @param {object} settings - escomplex settings * @see https://github.com/estree/estree/blob/master/es5.md#catchclause * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ CatchClause(settings) { return actualize(1, () => { return settings.trycatch ? 1 : 0; }, 'catch'); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#conditionalexpression * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ConditionalExpression() { return actualize(0, 1, ':?'); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#continuestatement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ContinueStatement() { return actualize(1, 0, 'continue'); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#dowhilestatement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ DoWhileStatement() { return actualize(2, (node) => { return node.test ? 1 : 0; }, 'dowhile'); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#emptystatement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ EmptyStatement() { return actualize(0, 0); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#expressionstatement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ExpressionStatement() { return actualize((node) => { // Ignore adding 1 lloc when the child expression is an ArrowFunctionExpression which is invalid / no-op. return typeof node.expression === 'object' && node.expression.type !== 'ArrowFunctionExpression' ? 1 : 0; }, 0); } /** * ES5 Node * @param {object} settings - escomplex settings * @see https://github.com/estree/estree/blob/master/es5.md#forinstatement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ForInStatement(settings) { // return actualize(1, () => { return settings.forin ? 1 : 0; }, 'forin'); return actualize(1, () => { return settings.forin ? 1 : 0; }, (node) => { const operators = ['forin']; const childNodes = []; if (node.left) { childNodes.push(node.left); } if (node.right) { childNodes.push(node.right); } if (childNodes.length > 0) { operators.push(...ASTGenerator.parseNodes(childNodes).operators); } return operators; }, (node) => { const operands = []; const childNodes = []; if (node.left) { childNodes.push(node.left); } if (node.right) { childNodes.push(node.right); } if (childNodes.length > 0) { operands.push(...ASTGenerator.parseNodes(childNodes).operands); } return operands; }, ['left', 'right']); } /** * ES5 Node * * Note: ASTGenerator is necessary to pick up operators / operands of `init, test, update` child nodes while * excluding traversal / LLOC counts for these nodes. * * @see https://github.com/estree/estree/blob/master/es5.md#forstatement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ForStatement() { return actualize(1, (node) => { return node.test ? 1 : 0; }, (node) => { const operators = ['for']; const childNodes = []; if (node.init) { childNodes.push(node.init); } if (node.test) { childNodes.push(node.test); } if (node.update) { childNodes.push(node.update); } if (childNodes.length > 0) { operators.push(...ASTGenerator.parseNodes(childNodes).operators); } return operators; }, (node) => { const operands = []; const childNodes = []; if (node.init) { childNodes.push(node.init); } if (node.test) { childNodes.push(node.test); } if (node.update) { childNodes.push(node.update); } if (childNodes.length > 0) { operands.push(...ASTGenerator.parseNodes(childNodes).operands); } return operands; }, ['init', 'test', 'update']); } /** * ES5 Node * * Note: The function name (node.id) is returned as an operand and excluded from traversal as to not be included in * the function operand calculations. * * @see https://github.com/estree/estree/blob/master/es5.md#functiondeclaration * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ FunctionDeclaration() { return actualize(0, 0, void 0, void 0, ['id', 'params', 'defaults'], (node, parent) => { const name = s_SAFE_COMPUTED_NAME(node, parent); const operands = s_SAFE_COMPUTED_OPERANDS(node, parent); const operators = []; if (parent && parent.type === 'MethodDefinition') { if (typeof node.generator === 'boolean' && node.generator) { operators.push('function*'); } if (typeof parent.computed === 'boolean' && parent.computed) { operators.push(...ASTGenerator.parse(parent.key).operators); } } else { operators.push(typeof node.generator === 'boolean' && node.generator ? 'function*' : 'function'); } const paramNames = []; // Parse params; must use ASTGenerator if (Array.isArray(node.params)) { node.params.forEach((param) => { const parsedParams = ASTGenerator.parse(param); operands.push(...parsedParams.operands); operators.push(...parsedParams.operators); // For param names only the left hand node of AssignmentPattern must be considered. if (param.type === 'AssignmentPattern') { paramNames.push(...ASTGenerator.parse(param.left).operands); } else { paramNames.push(...parsedParams.operands); } }); } // Esprima default values; param default values are stored in a non-standard `defaults` node. if (Array.isArray(node.defaults)) { node.defaults.forEach((value) => { if (value !== null && typeof value === 'object') { const parsedParams = ASTGenerator.parse(value); operands.push(...parsedParams.operands); operators.push(...parsedParams.operators); operators.push('='); // Must also manually push '=' } }); } return { type: 'method', name, cyclomatic: 1, lineStart: node.loc.start.line, lineEnd: node.loc.end.line, lloc: 1, operands: new HalsteadArray('operands', operands), operators: new HalsteadArray('operators', operators), paramNames }; }); } // FunctionDeclaration() // { // return actualize(1, 0, (node, parent) => // { // const operators = parent && parent.type === 'MethodDefinition' && typeof parent.computed === 'boolean' && // parent.computed ? [...ASTGenerator.parse(parent.key).operators] : []; // // if (parent && parent.type === 'MethodDefinition') // { // if (typeof node.generator === 'boolean' && node.generator) // { // operators.push('function*'); // } // } // else // { // operators.push(typeof node.generator === 'boolean' && node.generator ? 'function*' : 'function'); // } // // return operators; // }, // (node, parent) => { return s_SAFE_COMPUTED_OPERANDS(node, parent); }, // 'id', // (node, parent) => // { // return { // type: 'method', // name: s_SAFE_COMPUTED_NAME(node, parent), // lineStart: node.loc.start.line, // lineEnd: node.loc.end.line, // paramCount: node.params.length // }; // }); // } /** * ES5 Node * * Note: The function name (node.id) is returned as an operand and excluded from traversal as to not be included in * the function operand calculations. * * @see https://github.com/estree/estree/blob/master/es5.md#functionexpression * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ FunctionExpression() { return actualize(0, 0, void 0, void 0, ['id', 'params', 'defaults'], (node, parent) => { const name = s_SAFE_COMPUTED_NAME(node, parent); const operands = s_SAFE_COMPUTED_OPERANDS(node, parent); const operators = []; if (parent && parent.type === 'MethodDefinition') { if (typeof node.generator === 'boolean' && node.generator) { operators.push('function*'); } if (typeof parent.computed === 'boolean' && parent.computed) { operators.push(...ASTGenerator.parse(parent.key).operators); } } else { operators.push(typeof node.generator === 'boolean' && node.generator ? 'function*' : 'function'); } const paramNames = []; // Parse params; must use ASTGenerator if (Array.isArray(node.params)) { node.params.forEach((param) => { const parsedParams = ASTGenerator.parse(param); operands.push(...parsedParams.operands); operators.push(...parsedParams.operators); // For param names only the left hand node of AssignmentPattern must be considered. if (param.type === 'AssignmentPattern') { paramNames.push(...ASTGenerator.parse(param.left).operands); } else { paramNames.push(...parsedParams.operands); } }); } // Esprima default values; param default values are stored in a non-standard `defaults` node. if (Array.isArray(node.defaults)) { node.defaults.forEach((value) => { if (value !== null && typeof value === 'object') { const parsedParams = ASTGenerator.parse(value); operands.push(...parsedParams.operands); operators.push(...parsedParams.operators); operators.push('='); // Must also manually push '=' } }); } return { type: 'method', name, cyclomatic: 1, lineStart: node.loc.start.line, lineEnd: node.loc.end.line, lloc: 1, operands: new HalsteadArray('operands', operands), operators: new HalsteadArray('operators', operators), paramNames }; }); } // FunctionExpression() // { // return actualize(1, 0, (node, parent) => // { // const operators = parent && parent.type === 'MethodDefinition' && typeof parent.computed === 'boolean' && // parent.computed ? [...ASTGenerator.parse(parent.key).operators] : []; // // if (parent && parent.type === 'MethodDefinition') // { // if (typeof node.generator === 'boolean' && node.generator) // { // operators.push('function*'); // } // } // else // { // operators.push(typeof node.generator === 'boolean' && node.generator ? 'function*' : 'function'); // } // // return operators; // }, // (node, parent) => // { // return s_SAFE_COMPUTED_OPERANDS(node, parent); // }, // 'id', // (node, parent) => // { // return { // type: 'method', // name: s_SAFE_COMPUTED_NAME(node, parent), // lineStart: node.loc.start.line, // lineEnd: node.loc.end.line, // paramCount: node.params.length // }; // }); // } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#identifier * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ Identifier() { return actualize(0, 0, void 0, (node) => { return node.name; }); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#ifstatement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ IfStatement() { return actualize((node) => { return node.alternate ? 2 : 1; }, 1, ['if', { identifier: 'else', filter: (node) => { return !!node.alternate; } }]); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#labeledstatement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ LabeledStatement() { return actualize(0, 0); } /** * ES5 Node * * Avoid conflicts between string literals and identifiers. * * @see https://github.com/estree/estree/blob/master/es5.md#literal * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ Literal() { return actualize(0, 0, void 0, (node) => { const operands = []; if (typeof node.regex === 'object' && typeof node.raw !== 'undefined') { operands.push(node.raw); } else { operands.push(typeof node.value === 'string' ? `"${node.value}"` : node.value); } return operands; }); } /** * ES5 Node * @param {object} settings - escomplex settings * @see https://github.com/estree/estree/blob/master/es5.md#logicalexpression * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ LogicalExpression(settings) { return actualize(0, (node) => { const isAnd = node.operator === '&&'; const isOr = node.operator === '||'; return (isAnd || (settings.logicalor && isOr)) ? 1 : 0; }, (node) => { return node.operator; }); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#memberexpression * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ MemberExpression() { return actualize(0, 0, (node) => { return typeof node.computed === 'boolean' && node.computed ? '[]' : '.'; }); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#newexpression * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ NewExpression() { return actualize((node) => { return node.callee.type === 'FunctionExpression' ? 1 : 0; }, 0, 'new'); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#objectexpression * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ObjectExpression() { return actualize(0, 0, '{}'); } /** * ES5 Node * * Note that w/ ES6+ `:` may be omitted and the Property node defines `shorthand` to indicate this case. * * @see https://github.com/estree/estree/blob/master/es5.md#property * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ Property() { return actualize(1, 0, (node) => { return typeof node.shorthand === 'undefined' ? ':' : typeof node.shorthand === 'boolean' && !node.shorthand ? ':' : void 0; }); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#returnstatement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ReturnStatement() { return actualize(1, 0, 'return'); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#sequenceexpression * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ SequenceExpression() { return actualize(0, 0); } /** * ES5 Node * @param {object} settings - escomplex settings * @see https://github.com/estree/estree/blob/master/es5.md#switchcase * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ SwitchCase(settings) { return actualize(1, (node) => { return settings.switchcase && node.test ? 1 : 0; }, (node) => { return node.test ? 'case' : 'default'; }); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#switchstatement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ SwitchStatement() { return actualize(1, 0, 'switch'); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#thisexpression * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ThisExpression() { return actualize(0, 0, 'this'); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#throwstatement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ThrowStatement() { return actualize(1, 0, 'throw'); } /** * ES5 Node * * Note: esprima has duplicate nodes the catch block; `handler` is the actual ESTree spec. * * @see https://github.com/estree/estree/blob/master/es5.md#trystatement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ TryStatement() { return actualize(1, 0, (node) => { return node.finalizer ? ['try', 'finally'] : ['try']; }, void 0, ['guardedHandlers', 'handlers']); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#unaryexpression * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ UnaryExpression() { return actualize(0, 0, (node) => { return `${node.operator} (${node.prefix ? 'pre' : 'post'}fix)`; }); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#updateexpression * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ UpdateExpression() { return actualize(0, 0, (node) => { return `${node.operator} (${node.prefix ? 'pre' : 'post'}fix)`; }); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#variabledeclaration * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ VariableDeclaration() { return actualize(0, 0, (node) => { return node.kind; }); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#variabledeclarator * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ VariableDeclarator() { return actualize(1, 0, { identifier: '=', filter: (node) => { return !!node.init; } }); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#whilestatement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ WhileStatement() { return actualize(1, (node) => { return node.test ? 1 : 0; }, 'while'); } /** * ES5 Node * @see https://github.com/estree/estree/blob/master/es5.md#withstatement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ WithStatement() { return actualize(1, 0, 'with'); } // ES6 ESTree AST nodes ------------------------------------------------------------------------------------------ /** * ES6 Node * @see https://github.com/estree/estree/blob/master/es2015.md#assignmentpattern * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ AssignmentPattern() { return actualize(1, 0, (node) => { return node.operator; }); } /** * ES6 Node * @see https://github.com/estree/estree/blob/master/es2015.md#arraypattern * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ArrayPattern() { return actualize(0, 0, (node) => { const operators = ['[]']; if (Array.isArray(node.elements) && node.elements.length > 0) // Add length - 1 commas { operators.push(...(new Array(node.elements.length - 1).fill(','))); } return operators; }); } /** * ES6 Node * * Note: If the parent node is an ExpressionStatement the arrow function is essentially a no-op / invalid. In this * case no metrics are gathered, but a method is still registered with no data. Further analysis looking for methods * with no logical SLOC will reveal this case. * * @see https://github.com/estree/estree/blob/master/es2015.md#arrowfunctionexpression * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ArrowFunctionExpression() { return actualize(0, 0, void 0, void 0, (node, parent) => { // If not valid do not traverse any children. return parent && parent.type !== 'ExpressionStatement' ? ['id', 'params', 'defaults'] : null; }, (node, parent) => { const isValid = parent && parent.type !== 'ExpressionStatement'; const name = TraitUtil.safeName(node.id); const operands = []; const operators = isValid ? ['function=>'] : []; const paramNames = []; // Parse params; must use ASTGenerator if (isValid && Array.isArray(node.params)) { node.params.forEach((param) => { const parsedParams = ASTGenerator.parse(param); operands.push(...parsedParams.operands); operators.push(...parsedParams.operators); // For param names only the left hand node of AssignmentPattern must be considered. if (param.type === 'AssignmentPattern') { paramNames.push(...ASTGenerator.parse(param.left).operands); } else { paramNames.push(...parsedParams.operands); } }); } // Esprima default values; param default values are stored in a non-standard `defaults` node. if (isValid && Array.isArray(node.defaults)) { node.defaults.forEach((value) => { if (value !== null && typeof value === 'object') { const parsedParams = ASTGenerator.parse(value); operands.push(...parsedParams.operands); operators.push(...parsedParams.operators); operators.push('='); // Must also manually push '=' } }); } return { type: 'method', name, cyclomatic: isValid ? 1 : 0, lineStart: node.loc.start.line, lineEnd: node.loc.end.line, lloc: isValid ? 1 : 0, operands: isValid ? new HalsteadArray('operands', operands) : void 0, operators: isValid ? new HalsteadArray('operators', operators) : void 0, paramNames, // ArrowFunctionExpressions without brackets have an implicit `return` expression so post increment lloc. postLloc: isValid && typeof node.body === 'object' && node.body.type !== 'BlockStatement' ? 1 : 0 // NOTE (altered 12/8/18): ESTree has expression entry which was removed in Babel Parser / 7.0 so // node.body.type must be checked to determine if an expression or block / return statement. // Reference: https://github.com/babel/babel/issues/6773 // postLloc: isValid && typeof node.expression === 'boolean' && node.expression ? 1 : 0 }; }); } /** * ES6 Node * @see https://github.com/estree/estree/blob/master/es2015.md#classbody * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ClassBody() { return actualize(0, 0); } // /** // * ES6 Node // * @see https://github.com/estree/estree/blob/master/es2015.md#classdeclaration // * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} // */ // ClassDeclaration() // { // return actualize(1, 0, 'class', void 0, void 0, (node) => // { // return { // type: 'class', // name: TraitUtil.safeName(node.id), // lineStart: node.loc.start.line, // lineEnd: node.loc.end.line // }; // }); // } // // /** // * ES6 Node // * @see https://github.com/estree/estree/blob/master/es2015.md#classexpression // * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} // */ // ClassExpression() // { // return actualize(1, 0, 'class', void 0, void 0, (node) => // { // return { // type: 'class', // name: TraitUtil.safeName(node.id), // lineStart: node.loc.start.line, // lineEnd: node.loc.end.line // }; // }); // } /** * ES6 Node * @see https://github.com/estree/estree/blob/master/es2015.md#classdeclaration * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ClassDeclaration() { return actualize(0, 0, void 0, void 0, ['id', 'superClass'], (node) => { const name = TraitUtil.safeName(node.id); const operands = name !== '<anonymous>' ? [name] : []; const operators = ['class']; let superClassName; if (typeof node.superClass !== 'undefined' && node.superClass !== null) { const parsed = ASTGenerator.parse(node.superClass); operators.push('extends'); operands.push(...parsed.operands); operators.push(...parsed.operators); // The following will pick up a Identifier or a computed value (string); computed expressions return // `<computed~expression>`. superClassName = node.superClass.type === 'Identifier' ? TraitUtil.safeName(node.superClass) : `<computed~${parsed.source}>`; } return { type: 'class', name, superClassName, lineStart: node.loc.start.line, lineEnd: node.loc.end.line, lloc: 1, operands: new HalsteadArray('operands', operands), operators: new HalsteadArray('operators', operators) }; }); } /** * ES6 Node * @see https://github.com/estree/estree/blob/master/es2015.md#classexpression * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ClassExpression() { return actualize(0, 0, void 0, void 0, ['id', 'superClass'], (node) => { const name = TraitUtil.safeName(node.id); const operands = name !== '<anonymous>' ? [name] : []; const operators = ['class']; let superClassName; if (typeof node.superClass !== 'undefined' && node.superClass !== null) { const parsed = ASTGenerator.parse(node.superClass); operators.push('extends'); operands.push(...parsed.operands); operators.push(...parsed.operators); // The following will pick up a Identifier or a computed value (string); computed expressions return // `<computed~expression>`. superClassName = node.superClass.type === 'Identifier' ? TraitUtil.safeName(node.superClass) : `<computed~${parsed.source}>`; } return { type: 'class', name: TraitUtil.safeName(node.id), superClassName, lineStart: node.loc.start.line, lineEnd: node.loc.end.line, lloc: 1, operands: new HalsteadArray('operands', operands), operators: new HalsteadArray('operators', operators) }; }); } /** * ES6 Node * @param {object} settings - Runtime settings of escomplex. * @see https://github.com/estree/estree/blob/master/es2015.md#exportalldeclaration * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ExportAllDeclaration(settings) { return actualize(settings.esmImportExport.lloc ? 1 : 0, 0, settings.esmImportExport.halstead ? ['export', '*'] : void 0, void 0, settings.esmImportExport.halstead ? void 0 : null); } /** * ES6 Node * @param {object} settings - Runtime settings of escomplex. * @see https://github.com/estree/estree/blob/master/es2015.md#exportdefaultdeclaration * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ExportDefaultDeclaration(settings) { return actualize(settings.esmImportExport.lloc ? 1 : 0, 0, settings.esmImportExport.halstead ? ['export', 'default'] : void 0); } /** * ES6 Node * @param {object} settings - Runtime settings of escomplex. * @see https://github.com/estree/estree/blob/master/es2015.md#exportnameddeclaration * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ExportNamedDeclaration(settings) { return actualize(settings.esmImportExport.lloc ? 1 : 0, 0, settings.esmImportExport.halstead ? ['export', '{}'] : void 0, void 0, settings.esmImportExport.halstead ? void 0 : ['source', 'specifiers']); } /** * ES6 Node * @param {object} settings - Runtime settings of escomplex. * @see https://github.com/estree/estree/blob/master/es2015.md#exportspecifier * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ExportSpecifier(settings) { return actualize(0, 0, !settings.esmImportExport.halstead ? void 0 : (node) => { return node.exported.name !== node.local.name ? 'as' : void 0; }, void 0, !settings.esmImportExport.halstead ? null : (node) => { return node.exported.name === node.local.name ? ['local'] : void 0; }); } /** * ES6 Node * @see https://github.com/estree/estree/blob/master/es2015.md#forofstatement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ForOfStatement() { return actualize(1, (node) => { return node.test ? 1 : 0; }, (node) => { const operators = ['forof']; const childNodes = []; if (node.left) { childNodes.push(node.left); } if (node.right) { childNodes.push(node.right); } if (childNodes.length > 0) { operators.push(...ASTGenerator.parseNodes(childNodes).operators); } return operators; }, (node) => { const operands = []; const childNodes = []; if (node.left) { childNodes.push(node.left); } if (node.right) { childNodes.push(node.right); } if (childNodes.length > 0) { operands.push(...ASTGenerator.parseNodes(childNodes).operands); } return operands; }, ['left', 'right']); } /** * ES6 Node * @param {object} settings - escomplex settings * @see https://github.com/estree/estree/blob/master/es2015.md#importdeclaration * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ImportDeclaration(settings) { return actualize(settings.esmImportExport.lloc ? 1 : 0, 0, settings.esmImportExport.halstead ? ['import', 'from'] : void 0, void 0, settings.esmImportExport.halstead ? void 0 : null, void 0, (node) => { const dependencyPath = typeof settings.dependencyResolver === 'function' ? settings.dependencyResolver(node.source.value) : node.source.value; return { line: node.source.loc.start.line, path: dependencyPath, type: 'esm' }; }); } /** * ES6 Node * @see https://github.com/estree/estree/blob/master/es2015.md#importdefaultspecifier * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ImportDefaultSpecifier() { return actualize(0, 0); } /** * ES6 Node * @param {object} settings - Runtime settings of escomplex. * @see https://github.com/estree/estree/blob/master/es2015.md#importnamespacespecifier * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ImportNamespaceSpecifier(settings) { return actualize(0, 0, settings.esmImportExport.halstead ? ['import', '*', 'as'] : void 0, void 0, settings.esmImportExport.halstead ? void 0 : null); } /** * ES6 Node * @param {object} settings - Runtime settings of escomplex. * @see https://github.com/estree/estree/blob/master/es2015.md#importspecifier * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ImportSpecifier(settings) { return actualize(0, 0, !settings.esmImportExport.halstead ? void 0 : (node) => { return node.imported.name === node.local.name ? '{}' : ['{}', 'as']; }, void 0, settings.esmImportExport.halstead ? void 0 : null); } /** * ES6 Node * * Note: esprima doesn't follow the ESTree spec and `meta` & `property` are strings instead of Identifier nodes. * * @see https://github.com/estree/estree/blob/master/es2015.md#metaproperty * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ MetaProperty() { return actualize(0, 0, '.', (node) => { return typeof node.meta === 'string' && typeof node.property === 'string' ? [node.meta, node.property] : void 0; }); } /** * ES6 Node * * Note: must skip as the following FunctionExpression assigns the name. * * @see https://github.com/estree/estree/blob/master/es2015.md#methoddefinition * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ MethodDefinition() { return actualize(0, 0, (node) => { const operators = []; if (node.kind && (node.kind === 'get' || node.kind === 'set')) { operators.push(node.kind); } if (typeof node.static === 'boolean' && node.static) { operators.push('static'); } return operators; }, void 0, 'key'); } /** * ES6 Node * @see https://github.com/estree/estree/blob/master/es2015.md#objectpattern * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ ObjectPattern() { return actualize(0, 0, '{}'); } /** * ES6 Node * @see https://github.com/estree/estree/blob/master/es2015.md#restelement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ RestElement() { return actualize(0, 0, '... (rest)'); } /** * ES6 Node * @see https://github.com/estree/estree/blob/master/es2015.md#spreadelement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ SpreadElement() { return actualize(0, 0, '... (spread)'); } /** * ES6 Node * @see https://github.com/estree/estree/blob/master/es2015.md#super * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ Super() { return actualize(0, 0, 'super'); } /** * ES6 Node * @param {object} settings - Runtime settings of escomplex. * @see https://github.com/estree/estree/blob/master/es2015.md#taggedtemplateexpression * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ TaggedTemplateExpression(settings) { return actualize(settings.templateExpression.lloc ? 1 : 0, 0); } /** * ES6 Node * @see https://github.com/estree/estree/blob/master/es2015.md#templateelement * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ TemplateElement() { return actualize(0, 0, void 0, (node) => { return node.value.cooked !== '' ? node.value.cooked : void 0; }); } /** * ES6 Node * @param {object} settings - Runtime settings of escomplex. * @see https://github.com/estree/estree/blob/master/es2015.md#templateliteral * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ TemplateLiteral(settings) { return actualize(0, 0, !settings.templateExpression.halstead ? [] : (node) => { const operators = ['``']; if (Array.isArray(node.expressions) && node.expressions.length > 0) // Add length '${}' elements. { operators.push(...(new Array(node.expressions.length).fill('${}'))); } return operators; }); } /** * ES6 Node * @see https://github.com/estree/estree/blob/master/es2015.md#yieldexpression * @returns {{lloc: *, cyclomatic: *, operators: *, operands: *, ignoreKeys: *, newScope: *, dependencies: *}} */ YieldExpression() { return actualize((node, parent) => { // The top most `ExpressionStatement` already provides 1 lloc. return parent && parent.type !== 'ExpressionStatement' ? 1 : 0; }, 0,