Files
react-native-devtools-frontend/node_modules/eslint/lib/linter/code-path-analysis/code-path.js
T
Tim van der Lippe c8f6ffd7c3 Update ESLint to 6.8.0
Also update the PRESUBMIT linter configuration to make sure
it actually runs the linter when updating ESLint. Otherwise,
it would not properly do the full linter check.

R=jacktfranklin@chromium.org
DISABLE_THIRD_PARTY_CHECK=Update ESLint

Bug: 1068145
Change-Id: Ibf2de156366dc64c90ee1aced5537d2038e7a268
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2137384
Commit-Queue: Tim van der Lippe <tvanderlippe@chromium.org>
Reviewed-by: Jack Franklin <jacktfranklin@chromium.org>
2020-04-06 13:52:18 +00:00

239 lines
6.9 KiB
JavaScript

/**
* @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 {
// eslint-disable-next-line jsdoc/require-description
/**
* @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) {
let resolvedOptions;
let resolvedCallback;
if (typeof options === "function") {
resolvedCallback = options;
resolvedOptions = {};
} else {
resolvedOptions = options || {};
resolvedCallback = callback;
}
const startSegment = resolvedOptions.first || this.internal.initialSegment;
const lastSegment = resolvedOptions.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) {
resolvedCallback.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;