| /** |
| * @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 |
| }, |
| } |