Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement onUnreachableCodePathStart/End #17511

Merged
merged 34 commits into from Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ecc2dcc
feat: Implement onUnreachableCodePathStart/End
nzakas Aug 29, 2023
a402c0f
Finish up onUnreachable* work
nzakas Aug 30, 2023
40a1475
Refactor to account for out-of-order events
nzakas Sep 1, 2023
f0a093b
Update lib/rules/no-unreachable.js
nzakas Sep 5, 2023
b99db3b
Update lib/rules/no-unreachable.js
nzakas Sep 5, 2023
9e2b95c
Update tests/lib/linter/code-path-analysis/code-path-analyzer.js
nzakas Sep 5, 2023
363fd39
Incorporate feedback
nzakas Sep 5, 2023
01d1214
Clean up rules and docs
nzakas Sep 5, 2023
569175b
Update docs
nzakas Sep 5, 2023
b5b9a6a
Fix code example
nzakas Sep 5, 2023
b465903
Update docs/src/extend/code-path-analysis.md
nzakas Sep 6, 2023
0c6ffca
Update docs/src/extend/code-path-analysis.md
nzakas Sep 6, 2023
9d7c6c7
Update docs/src/extend/code-path-analysis.md
nzakas Sep 6, 2023
e044654
Update lib/rules/consistent-return.js
nzakas Sep 6, 2023
110f752
Update lib/rules/no-this-before-super.js
nzakas Sep 6, 2023
5544ba2
Fix examples
nzakas Sep 6, 2023
7ec047b
Add deprecation notices to RuleTester/FlatRuleTester
nzakas Sep 6, 2023
a945062
Update config
nzakas Sep 6, 2023
99e2ef8
chore: Upgrade config-array (#17512)
nzakas Aug 29, 2023
5972899
test: replace Karma with Webdriver.IO (#17126)
christian-bromann Aug 30, 2023
6b69804
chore: use eslint-plugin-jsdoc's flat config (#17516)
mdjermanovic Sep 1, 2023
549f4a5
docs: Update README
Sep 1, 2023
04d7ae4
docs: add typescript template (#17500)
Zamiell Sep 1, 2023
4bf7ce7
feat: add new `enforce` option to `lines-between-class-members` (#17462)
snitin315 Sep 2, 2023
fd2a909
feat: Emit deprecation warnings in RuleTester (#17527)
nzakas Sep 2, 2023
d2137ec
ci: bump actions/checkout from 3 to 4 (#17530)
dependabot[bot] Sep 4, 2023
929dd4d
docs: update `no-promise-executor-return` examples (#17529)
snitin315 Sep 6, 2023
d350fa9
Add deprecation notices to RuleTester/FlatRuleTester
nzakas Sep 6, 2023
a10206d
Merge branch 'main' into unreachable
nzakas Sep 6, 2023
b52d2a3
Fix lint warning
nzakas Sep 6, 2023
a352a8f
Update docs/src/extend/code-path-analysis.md
nzakas Sep 7, 2023
6405e23
Update docs/src/extend/code-path-analysis.md
nzakas Sep 7, 2023
0acea65
Update docs/src/extend/code-path-analysis.md
nzakas Sep 7, 2023
e444a69
Fix test
nzakas Sep 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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>>} */
nzakas marked this conversation as resolved.
Show resolved Hide resolved
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;
mdjermanovic marked this conversation as resolved.
Show resolved Hide resolved
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();
},
mdjermanovic marked this conversation as resolved.
Show resolved Hide resolved

// 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) {
nzakas marked this conversation as resolved.
Show resolved Hide resolved
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 = () => {};",
nzakas marked this conversation as resolved.
Show resolved Hide resolved
{ rules: { test: 2 }, env: { es6: true } }
);

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

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