feat: add prefer-promise-all lint rule#57
Conversation
|
This PR has been open since early March with all CI green (7/7 checks passing) but |
There was a problem hiding this comment.
Reviewed as steward. Approving — this is a careful, conservative implementation.
Things I particularly like:
- Skipping nested functions/arrows so awaits inside
items.forEach(async ...)(already concurrent) don't trip the rule. - Skipping
for await...ofand break/return paths. - The cross-iteration dependency check using
loopPath.scope.getBinding(...)is the right approach — catches reductions/accumulators bound outside the loop. - Severity is
warning, which is correct (perf hint, not correctness bug).
Minor coverage gaps, all non-blocking:
-
arr[i] = await foo()andobj[key] = await foo()patterns. TheAssignmentExpressionvisitor inhasCrossIterationDependencyonly checkst.isIdentifier(left), so member-assignment to outer-scope objects is not tracked. In practice, indexed/keyed writes to a pre-allocated container across iterations are usually parallelizable, so the rule will (correctly) still flag them. Not a bug, just noting. -
.set()on a Map /.add()on a Set are not in the order-dependent allowlist. They are usually order-independent, so the rule should NOT skip them — current behavior is correct. (Just confirming, not asking for a change.) -
try/catchwith sequential semantics — if the user is intentionally awaiting in sequence so a thrown error short-circuits the rest, the rule will still warn. That's acceptable as awarningseverity. -
No platform tag — universal makes sense (perf advice applies on web/backend; expo less so but harmless).
Ready to merge.
|
@danielchen0 — both reviewers approved this PR (dx5v at 00:03:21Z and lainterr[bot] at 00:09:46Z, both on head c2b778c). dx5v left two non-blocking comments worth a look before merge:
Both are mergeable-as-is per the approvals; flagging in case you want to address the false-positive case before merge. mergeable_state is still "blocked" — likely the unresolved review comments. Up to you whether to mark them resolved or address before merging. — danielchen0-pr-monitor |
|
Auto-fix: Lint & Format CI was failing on this rebase because README.md needed prettier formatting. Pushed prettier --write README.md as a follow-up commit. CI should go green on the next run. |
Rebased onto main to resolve conflicts after #51 merge.
3a50582 to
e1042dc
Compare
|
@daniel-chen-1 please address the PR comments. |
Auto-resolved trivial conflicts in shared files. Resolved by Lainter (steward).
…S in cross-iter dep check Addresses dx5v's review on PR #57: - AssignmentExpression LHS now collects names from MemberExpression (root identifier — e.g. `state.prev = ...` → `state`) and ObjectPattern / ArrayPattern (each bound name, including aliased `{ next: prev }`, RestElement, AssignmentPattern defaults). - UpdateExpression on a MemberExpression also resolves to the root. - Replaced ad-hoc `: any` annotations on visitor params and helpers with `NodePath<T>` from @babel/traverse and the corresponding node types from @babel/types, matching the pattern in prefer-named-params. - Added 6 tests covering the false-positive cases from the review: member-expression LHS (incl. nested), object-pattern LHS (incl. aliased `{ next: prev }`), array-pattern LHS, plus a positive case where the root is loop-local and the rule should still fire.
|
@danielchen0 — addressed dx5v's two review comments in commit a974487 (replied + resolved both threads):
Also pushed an earlier auto-fix commit (62077ec) running Test count for |
Summary
prefer-promise-allrule that detects sequentialawaitinfor...ofloops that could be parallelized withPromise.all(items.map(async ...)).push(),.unshift(),.splice()to arraysbreak/return(early exit depends on sequential evaluation)for await...of(async iterator pattern)Heuristics
The rule flags when all of these are true:
for...of(notfor,while, orfor await...of)await(not inside a nested function).push()/.unshift()/.splice()calls (order-dependent accumulation)breakorreturntargeting the loop (sequential early-exit)Test plan
for (const x of items) { await doThing(x); }.push()ed to arraybreakon conditionreturnon conditionlet prev = ...; prev = await ...)total += await ...)for await...of.map()callbackbreakinside switch (not targeting loop)