/** * @author Toru Nagashima * @copyright 2016 Toru Nagashima. All rights reserved. * See LICENSE file in root directory for full license. */ "use strict" //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ const CodePathAnalyzer = safeRequire("eslint/lib/code-path-analysis/code-path-analyzer") const CodePath = safeRequire("eslint/lib/code-path-analysis/code-path") const CodePathSegment = safeRequire("eslint/lib/code-path-analysis/code-path-segment") //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ const originalLeaveNode = CodePathAnalyzer && CodePathAnalyzer.prototype.leaveNode /** * Imports a specific module. * * @param {string} moduleName - A module name to import. * @returns {object|null} The imported object, or null. */ function safeRequire(moduleName) { try { return require(moduleName) } catch (_err) { return null } } /* istanbul ignore next */ /** * Copied from https://github.com/eslint/eslint/blob/16fad5880bb70e9dddbeab8ed0f425ae51f5841f/lib/code-path-analysis/code-path-analyzer.js#L137 * * @param {CodePathAnalyzer} analyzer - The instance. * @param {ASTNode} node - The current AST node. * @returns {void} */ function forwardCurrentToHead(analyzer, node) { const codePath = analyzer.codePath const state = CodePath.getState(codePath) const currentSegments = state.currentSegments const headSegments = state.headSegments const end = Math.max(currentSegments.length, headSegments.length) let i = 0 let currentSegment = null let headSegment = null // Fires leaving events. for (i = 0; i < end; ++i) { currentSegment = currentSegments[i] headSegment = headSegments[i] if (currentSegment !== headSegment && currentSegment) { if (currentSegment.reachable) { analyzer.emitter.emit( "onCodePathSegmentEnd", currentSegment, node) } } } // Update state. state.currentSegments = headSegments // Fires entering events. for (i = 0; i < end; ++i) { currentSegment = currentSegments[i] headSegment = headSegments[i] if (currentSegment !== headSegment && headSegment) { CodePathSegment.markUsed(headSegment) if (headSegment.reachable) { analyzer.emitter.emit( "onCodePathSegmentStart", headSegment, node) } } } } /** * Checks whether a given node is `process.exit()` or not. * * @param {ASTNode} node - A node to check. * @returns {boolean} `true` if the node is `process.exit()`. */ function isProcessExit(node) { return ( node.type === "CallExpression" && node.callee.type === "MemberExpression" && node.callee.computed === false && node.callee.object.type === "Identifier" && node.callee.object.name === "process" && node.callee.property.type === "Identifier" && node.callee.property.name === "exit" ) } /** * The function to override `CodePathAnalyzer.prototype.leaveNode` in order to * address `process.exit()` as throw. * * @this CodePathAnalyzer * @param {ASTNode} node - A node to be left. * @returns {void} */ function overrideLeaveNode(node) { if (isProcessExit(node)) { this.currentNode = node forwardCurrentToHead(this, node) CodePath.getState(this.codePath).makeThrow() this.original.leaveNode(node) this.currentNode = null } else { originalLeaveNode.call(this, node) } } const visitor = CodePathAnalyzer == null ? {} : { "Program": function installProcessExitAsThrow() { CodePathAnalyzer.prototype.leaveNode = overrideLeaveNode }, "Program:exit": function restoreProcessExitAsThrow() { CodePathAnalyzer.prototype.leaveNode = originalLeaveNode }, } //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ module.exports = { meta: { docs: { description: "make `process.exit()` expressions the same code path as `throw`", category: "Possible Errors", recommended: true, }, fixable: false, schema: [], supported: CodePathAnalyzer != null, }, create() { return visitor }, }