Skip to content

Commit

Permalink
feat: Implement onUnreachableCodePathStart/End
Browse files Browse the repository at this point in the history
refs # 17457
  • Loading branch information
nzakas committed Aug 29, 2023
1 parent 10c4f85 commit ecc2dcc
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 25 deletions.
56 changes: 32 additions & 24 deletions lib/linter/code-path-analysis/code-path-analyzer.js
Expand Up @@ -192,15 +192,18 @@ function forwardCurrentToHead(analyzer, node) {
headSegment = headSegments[i];

if (currentSegment !== headSegment && currentSegment) {
debug.dump(`onCodePathSegmentEnd ${currentSegment.id}`);

if (currentSegment.reachable) {
analyzer.emitter.emit(
"onCodePathSegmentEnd",
currentSegment,
node
);
}
const eventName = currentSegment.reachable
? "onCodePathSegmentEnd"
: "onUnreachableCodePathSegmentEnd";

debug.dump(`${eventName} ${currentSegment.id}`);

analyzer.emitter.emit(
eventName,
currentSegment,
node
);
}
}

Expand All @@ -213,16 +216,19 @@ function forwardCurrentToHead(analyzer, node) {
headSegment = headSegments[i];

if (currentSegment !== headSegment && headSegment) {
debug.dump(`onCodePathSegmentStart ${headSegment.id}`);

const eventName = headSegment.reachable
? "onCodePathSegmentStart"
: "onUnreachableCodePathSegmentStart";

debug.dump(`${eventName} ${headSegment.id}`);

CodePathSegment.markUsed(headSegment);
if (headSegment.reachable) {
analyzer.emitter.emit(
"onCodePathSegmentStart",
headSegment,
node
);
}
analyzer.emitter.emit(
eventName,
headSegment,
node
);
}
}

Expand All @@ -241,15 +247,17 @@ function leaveFromCurrentSegment(analyzer, node) {

for (let i = 0; i < currentSegments.length; ++i) {
const currentSegment = currentSegments[i];
const eventName = currentSegment.reachable
? "onCodePathSegmentEnd"
: "onUnreachableCodePathSegmentEnd";

debug.dump(`onCodePathSegmentEnd ${currentSegment.id}`);
if (currentSegment.reachable) {
analyzer.emitter.emit(
"onCodePathSegmentEnd",
currentSegment,
node
);
}
debug.dump(`${eventName} ${currentSegment.id}`);

analyzer.emitter.emit(
eventName,
currentSegment,
node
);
}

state.currentSegments = [];
Expand Down
27 changes: 26 additions & 1 deletion lib/rules/no-unreachable.js
Expand Up @@ -132,6 +132,12 @@ module.exports = {
/** @type {ConsecutiveRange} */
const range = new ConsecutiveRange(context.sourceCode);

/** @type {Array<Array<CodePathSegments>>} */
const codePathSegments = [];

/** @type {Array<CodePathSegments>} */
let currentCodePathSegments = [];

/**
* Reports a given node if it's unreachable.
* @param {ASTNode} node A statement node to report.
Expand All @@ -140,7 +146,7 @@ module.exports = {
function reportIfUnreachable(node) {
let nextNode = null;

if (node && (node.type === "PropertyDefinition" || currentCodePath.currentSegments.every(isUnreachable))) {
if (node && (node.type === "PropertyDefinition" || currentCodePathSegments.every(isUnreachable))) {

// Store this statement to distinguish consecutive statements.
if (range.isEmpty) {
Expand Down Expand Up @@ -183,10 +189,29 @@ module.exports = {
// Manages the current code path.
onCodePathStart(codePath) {
currentCodePath = codePath;
codePathSegments.push(currentCodePathSegments);
currentCodePathSegments = [];
},

onCodePathEnd() {
currentCodePath = currentCodePath.upper;
currentCodePathSegments = codePathSegments.pop();
},

onUnreachableCodePathSegmentStart(segment) {
currentCodePathSegments.push(segment);
},

onUnreachableCodePathSegmentEnd() {
currentCodePathSegments.pop();
},

onCodePathSegmentStart(segment) {
currentCodePathSegments.push(segment);
},

onCodePathSegmentEnd() {
currentCodePathSegments.pop();
},

// Registers for all statement nodes (excludes FunctionDeclaration).
Expand Down
136 changes: 136 additions & 0 deletions tests/lib/linter/code-path-analysis/code-path-analyzer.js
Expand Up @@ -439,6 +439,142 @@ describe("CodePathAnalyzer", () => {
});
});

describe("onUnreachableCodePathSegmentStart", () => {
it("should be fired after a throw", () => {
let lastCodePathNodeType = null;

linter.defineRule("test", {
create: () => ({
onUnreachableCodePathSegmentStart(segment, node) {
lastCodePathNodeType = node.type;

assert(segment instanceof CodePathSegment);
assert.strictEqual(node.type, "ExpressionStatement");
},
ExpressionStatement() {
assert.strictEqual(lastCodePathNodeType, "ExpressionStatement");
}
})
});
linter.verify(
"throw 'boom'; foo();",
{ rules: { test: 2 } }
);

});

it("should be fired after a return", () => {
let lastCodePathNodeType = null;

linter.defineRule("test", {
create: () => ({
onUnreachableCodePathSegmentStart(segment, node) {
lastCodePathNodeType = node.type;

assert(segment instanceof CodePathSegment);
assert.strictEqual(node.type, "ExpressionStatement");
},
ExpressionStatement() {
assert.strictEqual(lastCodePathNodeType, "ExpressionStatement");
}
})
});
linter.verify(
"function foo() { return; foo(); }",
{ rules: { test: 2 } }
);

});
});

describe("onUnreachableCodePathSegmentEnd", () => {
it("should be fired after a throw", () => {
let lastCodePathNodeType = null;

linter.defineRule("test", {
create: () => ({
onUnreachableCodePathSegmentEnd(segment, node) {
lastCodePathNodeType = node.type;

assert(segment instanceof CodePathSegment);
assert.strictEqual(node.type, "Program");
}
})
});
linter.verify(
"throw 'boom'; foo();",
{ rules: { test: 2 } }
);

assert.strictEqual(lastCodePathNodeType, "Program");
});

it("should be fired after a return", () => {
let lastCodePathNodeType = null;

linter.defineRule("test", {
create: () => ({
onUnreachableCodePathSegmentEnd(segment, node) {
lastCodePathNodeType = node.type;
assert(segment instanceof CodePathSegment);
assert.strictEqual(node.type, "FunctionDeclaration");
},
"Program:exit"() {
assert.strictEqual(lastCodePathNodeType, "FunctionDeclaration");
}
})
});
linter.verify(
"function foo() { return; foo(); }",
{ rules: { test: 2 } }
);

});

it("should be fired at the end of programs/functions for the final segment", () => {
let count = 0;
let lastNodeType = null;

linter.defineRule("test", {
create: () => ({
onCodePathSegmentEnd(cp, node) {
count += 1;

assert(cp instanceof CodePathSegment);
if (count === 4) {
assert(node.type === "Program");
} else if (count === 1) {
assert(node.type === "FunctionDeclaration");
} else if (count === 2) {
assert(node.type === "FunctionExpression");
} else if (count === 3) {
assert(node.type === "ArrowFunctionExpression");
}
assert(node.type === lastNodeType);
},
"Program:exit"() {
lastNodeType = "Program";
},
"FunctionDeclaration:exit"() {
lastNodeType = "FunctionDeclaration";
},
"FunctionExpression:exit"() {
lastNodeType = "FunctionExpression";
},
"ArrowFunctionExpression:exit"() {
lastNodeType = "ArrowFunctionExpression";
}
})
});
linter.verify(
"foo(); function foo() {} var foo = function() {}; var foo = () => {};",
{ rules: { test: 2 }, env: { es6: true } }
);

assert(count === 4);
});
});

describe("onCodePathSegmentLoop", () => {
it("should be fired in `while` loops", () => {
let count = 0;
Expand Down

0 comments on commit ecc2dcc

Please sign in to comment.