summaryrefslogtreecommitdiff
path: root/tools/node_modules/eslint/lib/code-path-analysis/code-path.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/node_modules/eslint/lib/code-path-analysis/code-path.js')
-rw-r--r--tools/node_modules/eslint/lib/code-path-analysis/code-path.js234
1 files changed, 234 insertions, 0 deletions
diff --git a/tools/node_modules/eslint/lib/code-path-analysis/code-path.js b/tools/node_modules/eslint/lib/code-path-analysis/code-path.js
new file mode 100644
index 0000000000..709a111189
--- /dev/null
+++ b/tools/node_modules/eslint/lib/code-path-analysis/code-path.js
@@ -0,0 +1,234 @@
+/**
+ * @fileoverview A class of the code path.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const CodePathState = require("./code-path-state");
+const IdGenerator = require("./id-generator");
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+/**
+ * A code path.
+ */
+class CodePath {
+
+ /**
+ * @param {string} id - An identifier.
+ * @param {CodePath|null} upper - The code path of the upper function scope.
+ * @param {Function} onLooped - A callback function to notify looping.
+ */
+ constructor(id, upper, onLooped) {
+
+ /**
+ * The identifier of this code path.
+ * Rules use it to store additional information of each rule.
+ * @type {string}
+ */
+ this.id = id;
+
+ /**
+ * The code path of the upper function scope.
+ * @type {CodePath|null}
+ */
+ this.upper = upper;
+
+ /**
+ * The code paths of nested function scopes.
+ * @type {CodePath[]}
+ */
+ this.childCodePaths = [];
+
+ // Initializes internal state.
+ Object.defineProperty(
+ this,
+ "internal",
+ { value: new CodePathState(new IdGenerator(`${id}_`), onLooped) }
+ );
+
+ // Adds this into `childCodePaths` of `upper`.
+ if (upper) {
+ upper.childCodePaths.push(this);
+ }
+ }
+
+ /**
+ * Gets the state of a given code path.
+ *
+ * @param {CodePath} codePath - A code path to get.
+ * @returns {CodePathState} The state of the code path.
+ */
+ static getState(codePath) {
+ return codePath.internal;
+ }
+
+ /**
+ * The initial code path segment.
+ * @type {CodePathSegment}
+ */
+ get initialSegment() {
+ return this.internal.initialSegment;
+ }
+
+ /**
+ * Final code path segments.
+ * This array is a mix of `returnedSegments` and `thrownSegments`.
+ * @type {CodePathSegment[]}
+ */
+ get finalSegments() {
+ return this.internal.finalSegments;
+ }
+
+ /**
+ * Final code path segments which is with `return` statements.
+ * This array contains the last path segment if it's reachable.
+ * Since the reachable last path returns `undefined`.
+ * @type {CodePathSegment[]}
+ */
+ get returnedSegments() {
+ return this.internal.returnedForkContext;
+ }
+
+ /**
+ * Final code path segments which is with `throw` statements.
+ * @type {CodePathSegment[]}
+ */
+ get thrownSegments() {
+ return this.internal.thrownForkContext;
+ }
+
+ /**
+ * Current code path segments.
+ * @type {CodePathSegment[]}
+ */
+ get currentSegments() {
+ return this.internal.currentSegments;
+ }
+
+ /**
+ * Traverses all segments in this code path.
+ *
+ * codePath.traverseSegments(function(segment, controller) {
+ * // do something.
+ * });
+ *
+ * This method enumerates segments in order from the head.
+ *
+ * The `controller` object has two methods.
+ *
+ * - `controller.skip()` - Skip the following segments in this branch.
+ * - `controller.break()` - Skip all following segments.
+ *
+ * @param {Object} [options] - Omittable.
+ * @param {CodePathSegment} [options.first] - The first segment to traverse.
+ * @param {CodePathSegment} [options.last] - The last segment to traverse.
+ * @param {Function} callback - A callback function.
+ * @returns {void}
+ */
+ traverseSegments(options, callback) {
+ if (typeof options === "function") {
+ callback = options;
+ options = null;
+ }
+
+ options = options || {};
+ const startSegment = options.first || this.internal.initialSegment;
+ const lastSegment = options.last;
+
+ let item = null;
+ let index = 0;
+ let end = 0;
+ let segment = null;
+ const visited = Object.create(null);
+ const stack = [[startSegment, 0]];
+ let skippedSegment = null;
+ let broken = false;
+ const controller = {
+ skip() {
+ if (stack.length <= 1) {
+ broken = true;
+ } else {
+ skippedSegment = stack[stack.length - 2][0];
+ }
+ },
+ break() {
+ broken = true;
+ }
+ };
+
+ /**
+ * Checks a given previous segment has been visited.
+ * @param {CodePathSegment} prevSegment - A previous segment to check.
+ * @returns {boolean} `true` if the segment has been visited.
+ */
+ function isVisited(prevSegment) {
+ return (
+ visited[prevSegment.id] ||
+ segment.isLoopedPrevSegment(prevSegment)
+ );
+ }
+
+ while (stack.length > 0) {
+ item = stack[stack.length - 1];
+ segment = item[0];
+ index = item[1];
+
+ if (index === 0) {
+
+ // Skip if this segment has been visited already.
+ if (visited[segment.id]) {
+ stack.pop();
+ continue;
+ }
+
+ // Skip if all previous segments have not been visited.
+ if (segment !== startSegment &&
+ segment.prevSegments.length > 0 &&
+ !segment.prevSegments.every(isVisited)
+ ) {
+ stack.pop();
+ continue;
+ }
+
+ // Reset the flag of skipping if all branches have been skipped.
+ if (skippedSegment && segment.prevSegments.indexOf(skippedSegment) !== -1) {
+ skippedSegment = null;
+ }
+ visited[segment.id] = true;
+
+ // Call the callback when the first time.
+ if (!skippedSegment) {
+ callback.call(this, segment, controller);
+ if (segment === lastSegment) {
+ controller.skip();
+ }
+ if (broken) {
+ break;
+ }
+ }
+ }
+
+ // Update the stack.
+ end = segment.nextSegments.length - 1;
+ if (index < end) {
+ item[1] += 1;
+ stack.push([segment.nextSegments[index], 0]);
+ } else if (index === end) {
+ item[0] = segment.nextSegments[index];
+ item[1] = 0;
+ } else {
+ stack.pop();
+ }
+ }
+ }
+}
+
+module.exports = CodePath;