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 10 commits
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
462 changes: 306 additions & 156 deletions docs/src/extend/code-path-analysis.md

Large diffs are not rendered by default.

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
1 change: 1 addition & 0 deletions lib/linter/linter.js
Expand Up @@ -898,6 +898,7 @@ const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
getTokensBetween: "getTokensBetween"
};


const BASE_TRAVERSAL_CONTEXT = Object.freeze(
Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).reduce(
(contextInfo, methodName) =>
Expand Down
47 changes: 36 additions & 11 deletions lib/rules/array-callback-return.js
Expand Up @@ -18,15 +18,6 @@ const astUtils = require("./utils/ast-utils");
const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
const TARGET_METHODS = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort|toSorted)$/u;

/**
* Checks a given code path segment is reachable.
* @param {CodePathSegment} segment A segment to check.
* @returns {boolean} `true` if the segment is reachable.
*/
function isReachable(segment) {
return segment.reachable;
}

/**
* Checks a given node is a member access which has the specified name's
* property.
Expand All @@ -38,6 +29,22 @@ function isTargetMethod(node) {
return astUtils.isSpecificMemberAccess(node, null, TARGET_METHODS);
}

/**
* Checks all segments in a set and returns true if any are reachable.
* @param {Set<CodePathSegment>} segments The segments to check.
* @returns {boolean} True if any segment is reachable; false otherwise.
*/
function isAnySegmentReachable(segments) {

for (const segment of segments) {
if (segment.reachable) {
return true;
}
}

return false;
}

/**
* Returns a human-legible description of an array method
* @param {string} arrayMethodName A method name to fully qualify
Expand Down Expand Up @@ -205,7 +212,7 @@ module.exports = {
messageId = "expectedNoReturnValue";
}
} else {
if (node.body.type === "BlockStatement" && funcInfo.codePath.currentSegments.some(isReachable)) {
if (node.body.type === "BlockStatement" && isAnySegmentReachable(funcInfo.currentSegments)) {
messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside";
}
}
Expand Down Expand Up @@ -242,7 +249,8 @@ module.exports = {
methodName &&
!node.async &&
!node.generator,
node
node,
currentSegments: new Set()
};
},

Expand All @@ -251,6 +259,23 @@ module.exports = {
funcInfo = funcInfo.upper;
},

onUnreachableCodePathSegmentStart(segment) {
funcInfo.currentSegments.add(segment);
},

onUnreachableCodePathSegmentEnd(segment) {
funcInfo.currentSegments.delete(segment);
},

onCodePathSegmentStart(segment) {
funcInfo.currentSegments.add(segment);
},

onCodePathSegmentEnd(segment) {
funcInfo.currentSegments.delete(segment);
},


// Checks the return statement is valid.
ReturnStatement(node) {

Expand Down
43 changes: 36 additions & 7 deletions lib/rules/consistent-return.js
Expand Up @@ -16,12 +16,23 @@ const { upperCaseFirst } = require("../shared/string-utils");
//------------------------------------------------------------------------------

/**
* Checks whether or not a given code path segment is unreachable.
* @param {CodePathSegment} segment A CodePathSegment to check.
* @returns {boolean} `true` if the segment is unreachable.
* Checks all segments in a set and returns true if all are unreachable.
* @param {Set<CodePathSegment>} segments The segments to check.
* @returns {boolean} True if all segments are unreachable; false otherwise.
*/
function isUnreachable(segment) {
return !segment.reachable;
function areAllSegmentsUnreachable(segments) {

if (segments.size === 0) {
return false;
}

nzakas marked this conversation as resolved.
Show resolved Hide resolved
for (const segment of segments) {
if (segment.reachable) {
return false;
}
}

return true;
}

/**
Expand Down Expand Up @@ -88,7 +99,7 @@ module.exports = {
* When unreachable, all paths are returned or thrown.
*/
if (!funcInfo.hasReturnValue ||
funcInfo.codePath.currentSegments.every(isUnreachable) ||
areAllSegmentsUnreachable(funcInfo.currentSegments) ||
astUtils.isES5Constructor(node) ||
isClassConstructor(node)
) {
Expand Down Expand Up @@ -141,13 +152,31 @@ module.exports = {
hasReturn: false,
hasReturnValue: false,
messageId: "",
node
node,
currentSegments: new Set()
};
},
onCodePathEnd() {
funcInfo = funcInfo.upper;
},

onUnreachableCodePathSegmentStart(segment) {
funcInfo.currentSegments.add(segment);
},

onUnreachableCodePathSegmentEnd(segment) {
funcInfo.currentSegments.delete(segment);
},

onCodePathSegmentStart(segment) {
funcInfo.currentSegments.add(segment);
},

onCodePathSegmentEnd(segment) {
funcInfo.currentSegments.delete(segment);
},


// Reports a given return statement if it's inconsistent.
ReturnStatement(node) {
const argument = node.argument;
Expand Down
51 changes: 37 additions & 14 deletions lib/rules/constructor-super.js
Expand Up @@ -10,12 +10,19 @@
//------------------------------------------------------------------------------

/**
* Checks whether a given code path segment is reachable or not.
* @param {CodePathSegment} segment A code path segment to check.
* @returns {boolean} `true` if the segment is reachable.
* Checks all segments in a set and returns true if any are reachable.
* @param {Set<CodePathSegment>} segments The segments to check.
* @returns {boolean} True if any segment is reachable; false otherwise.
*/
function isReachable(segment) {
return segment.reachable;
function isAnySegmentReachable(segments) {

for (const segment of segments) {
if (segment.reachable) {
return true;
}
}

return false;
}

/**
Expand Down Expand Up @@ -210,15 +217,17 @@ module.exports = {
isConstructor: true,
hasExtends: Boolean(superClass),
superIsConstructor: isPossibleConstructor(superClass),
codePath
codePath,
currentSegments: new Set()
};
} else {
funcInfo = {
upper: funcInfo,
isConstructor: false,
hasExtends: false,
superIsConstructor: false,
codePath
codePath,
currentSegments: new Set()
};
}
},
Expand Down Expand Up @@ -261,6 +270,9 @@ module.exports = {
* @returns {void}
*/
onCodePathSegmentStart(segment) {

funcInfo.currentSegments.add(segment);

if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
return;
}
Expand All @@ -281,6 +293,19 @@ module.exports = {
}
},

onUnreachableCodePathSegmentStart(segment) {
funcInfo.currentSegments.add(segment);
},

onUnreachableCodePathSegmentEnd(segment) {
funcInfo.currentSegments.delete(segment);
},

onCodePathSegmentEnd(segment) {
funcInfo.currentSegments.delete(segment);
},


/**
* Update information of the code path segment when a code path was
* looped.
Expand Down Expand Up @@ -344,12 +369,11 @@ module.exports = {

// Reports if needed.
if (funcInfo.hasExtends) {
const segments = funcInfo.codePath.currentSegments;
const segments = funcInfo.currentSegments;
let duplicate = false;
let info = null;

for (let i = 0; i < segments.length; ++i) {
const segment = segments[i];
for (const segment of segments) {

if (segment.reachable) {
info = segInfoMap[segment.id];
Expand All @@ -374,7 +398,7 @@ module.exports = {
info.validNodes.push(node);
}
}
} else if (funcInfo.codePath.currentSegments.some(isReachable)) {
} else if (isAnySegmentReachable(funcInfo.currentSegments)) {
context.report({
messageId: "unexpected",
node
Expand All @@ -398,10 +422,9 @@ module.exports = {
}

// Returning argument is a substitute of 'super()'.
const segments = funcInfo.codePath.currentSegments;
const segments = funcInfo.currentSegments;

for (let i = 0; i < segments.length; ++i) {
const segment = segments[i];
for (const segment of segments) {

if (segment.reachable) {
const info = segInfoMap[segment.id];
Expand Down