UNPKG

typhonjs-escomplex-module

Version:

Provides module / individual file oriented AST processing for typhonjs-escomplex complexity reports.

153 lines (122 loc) 5.43 kB
import ASTWalker from 'typhonjs-ast-walker/src/ASTWalker'; import ModuleScopeControl from 'typhonjs-escomplex-commons/src/module/report/control/ModuleScopeControl'; import Plugins from './Plugins'; /** * Provides a runtime to invoke ESComplexModule plugins for processing / metrics calculations of independent modules. */ export default class ESComplexModule { /** * Initializes ESComplexModule. * * @param {object} options - module options including user plugins to load including: * ``` * (boolean) loadDefaultPlugins - When false ESComplexModule will not load any default plugins. * (Array<Object>) plugins - A list of ESComplexModule plugins that have already been instantiated. * ``` */ constructor(options = {}) { /* istanbul ignore if */ if (typeof options !== 'object') { throw new TypeError('ctor error: `options` is not an `object`.'); } /** * Provides dispatch methods to all module plugins. * @type {Plugins} * @private */ this._plugins = new Plugins(options); } /** * Processes the given ast and calculates metrics via plugins. * * @param {object|Array} ast - Javascript AST. * @param {object} options - (Optional) module analyze options. * * @returns {ModuleReport} - A single module report. */ analyze(ast, options = {}) { if (typeof ast !== 'object' || Array.isArray(ast)) { throw new TypeError('analyze error: `ast` is not an `object` or `array`.'); } /* istanbul ignore if */ if (typeof options !== 'object') { throw new TypeError('analyze error: `options` is not an `object`.'); } const settings = this._plugins.onConfigure(options); const syntaxes = this._plugins.onLoadSyntax(settings); const moduleReport = this._plugins.onModuleStart(ast, syntaxes, settings); const scopeControl = new ModuleScopeControl(moduleReport); // Completely traverse the provided AST and defer to plugins to process node traversal. new ASTWalker().traverse(ast, { enterNode: (node, parent) => { const syntax = syntaxes[node.type]; // Process node syntax / ignore keys. let ignoreKeys = typeof syntax === 'object' && syntax.ignoreKeys ? syntax.ignoreKeys.valueOf(node, parent) : []; ignoreKeys = this._plugins.onEnterNode(moduleReport, scopeControl, ignoreKeys, syntaxes, settings, node, parent); // Process node syntax / create scope. if (typeof syntax === 'object') { if (syntax.newScope) { const newScope = syntax.newScope.valueOf(node, parent); if (newScope) { this._plugins.onModulePreScopeCreated(moduleReport, scopeControl, newScope, settings, node, parent); scopeControl.createScope(newScope); this._plugins.onModulePostScopeCreated(moduleReport, scopeControl, newScope, settings, node, parent); } } } return ignoreKeys; }, exitNode: (node, parent) => { const syntax = syntaxes[node.type]; // Process node syntax / pop scope. if (typeof syntax === 'object' && syntax.newScope) { const newScope = syntax.newScope.valueOf(node, parent); if (newScope) { this._plugins.onModulePreScopePopped(moduleReport, scopeControl, newScope, settings, node, parent); scopeControl.popScope(newScope); this._plugins.onModulePostScopePopped(moduleReport, scopeControl, newScope, settings, node, parent); } } return this._plugins.onExitNode(moduleReport, scopeControl, syntaxes, settings, node, parent); } }); // Allow all plugins to have a calculation pass at the module report. this._plugins.onModuleCalculate(moduleReport, syntaxes, settings); // Allow all plugins to have a pass at the module report to calculate any averaged data. this._plugins.onModuleAverage(moduleReport, syntaxes, settings); // Allow all plugins to have a pass at the module report to calculate any metrics that depend on averaged data. this._plugins.onModulePostAverage(moduleReport, syntaxes, settings); // Allow all plugins to clean up any resources as necessary. this._plugins.onModuleEnd(moduleReport, syntaxes, settings); return moduleReport.finalize(); } // Asynchronous Promise based methods ---------------------------------------------------------------------------- /** * Wraps in a Promise processing the given ast and calculates metrics via plugins. * * @param {object|Array} ast - Javascript AST. * @param {object} options - (Optional) module analyze options. * * @returns {Promise<ModuleReport>} - A single module report. */ analyzeAsync(ast, options = {}) { return new Promise((resolve, reject) => { try { resolve(this.analyze(ast, options)); } catch (err) { /* istanbul ignore next */ reject(err); } }); } }