Skip to content

fix: reduce false positives from local variable shadowing and early-return guards#687

Open
Akshay090 wants to merge 2 commits into
amilajack:mainfrom
Akshay090:fix/false-positives-and-early-return-guard
Open

fix: reduce false positives from local variable shadowing and early-return guards#687
Akshay090 wants to merge 2 commits into
amilajack:mainfrom
Akshay090:fix/false-positives-and-early-return-guard

Conversation

@Akshay090

@Akshay090 Akshay090 commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Fixes #688

Summary

Two related fixes that reduce false positives in the compat/compat rule:

1. Function parameter names matching browser APIs

The Identifier visitor that tracks locally declared names was missing ArrowFunctionExpression and FunctionExpression parent types. This caused false positives when callback parameters happened to share a name with a browser API.

Example — flagged incorrectly before this fix:

// 'scheduler' here is a domain entity, not the Window.scheduler browser API
schedulers.map(scheduler => scheduler.name);

The existing heuristic already handled FunctionDeclaration params, VariableDeclarator, Property, ClassDeclaration, and import specifiers. This adds the two missing function types for consistency.

2. Early-return guard pattern as feature detection

The existing isInsideIfStatement heuristic only suppressed errors for API usage inside an if block. The common early-return guard pattern was missed:

function setup() {
  if (!('serviceWorker' in navigator)) { return; }
  navigator.serviceWorker.register('/sw.js'); // was falsely flagged
}

The new isGuardedByEarlyReturn helper walks up from the flagged node to its containing block and checks preceding sibling if-statements for early exits (return/throw) whose test references the same API. Respects the ignoreConditionalChecks setting.

Test plan

  • Added 4 valid test cases for arrow function and function expression parameter shadowing
  • Added 4 valid test cases for early-return guard patterns (in operator, member expression, throw, bare identifier)
  • Added 2 invalid test cases (unrelated guard doesn't suppress; ignoreConditionalChecks overrides early-return detection)
  • All 118 tests pass, lint clean, build succeeds

…rowser APIs

The Identifier visitor that tracks locally declared names was missing
ArrowFunctionExpression and FunctionExpression parent types. This caused
false positives when callback parameters happened to share a name with a
browser API (e.g. `items.map(scheduler => scheduler.name)` flagged as
unsupported Window.scheduler usage).

Made-with: Cursor
The existing isInsideIfStatement heuristic only suppressed errors for
API usage inside an if block. The common early-return pattern was missed:

  if (!('serviceWorker' in navigator)) { return; }
  navigator.serviceWorker.register('/sw.js'); // falsely flagged

Now walks up from the flagged node to its containing block and checks
preceding sibling if-statements for early exits (return/throw) whose
test references the same API. Respects ignoreConditionalChecks setting.

Made-with: Cursor
@levensta

Copy link
Copy Markdown

These are really useful features, especially early-return

@amilajack please, look at this PR 🙏

@amilajack

Copy link
Copy Markdown
Owner

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 83880d3545

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/helpers.ts
Comment on lines +72 to +73
if (node.type === "Identifier") {
return node.name === rule.object || node.name === rule.property;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require matching the guarded member before suppressing

For member APIs this treats any mention of the host object as a valid guard, so a preceding unrelated check like if (!navigator.onLine) return; navigator.serviceWorker.register(...) suppresses the Safari 10.1 navigator.serviceWorker error even though the guard never checked serviceWorker. This creates false negatives whenever an early-return condition references the same object but a different property.

Useful? React with 👍 / 👎.

Comment thread src/helpers.ts
Comment on lines +128 to +130
stmt.type === "IfStatement" &&
containsEarlyExit(stmt.consequent) &&
expressionReferencesApi(stmt.test, failingRule)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check guard polarity before accepting early returns

The early-return suppression only checks that the condition mentions the API, so an inverted guard such as if (navigator.serviceWorker) return; navigator.serviceWorker.register('/sw.js') is considered safe even though the call only runs when serviceWorker is absent. This hides real compatibility errors for positive early exits that skip the supported path rather than guarding the unsupported one.

Useful? React with 👍 / 👎.

Comment thread src/rules/compat.ts
Comment on lines +296 to +297
type === "FunctionExpression" || // ex. arr.map(function(Set) {})
type === "ArrowFunctionExpression" || // ex. arr.map(Set => Set.id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope callback parameter shadowing before dropping errors

These new parent types add callback parameter names to the file-wide identifiers set, so one local parameter suppresses every same-named API error in the file. For example, after items.map(fetch => fetch.id); fetch('/api'), the real unsupported global fetch call is dropped at Program:exit because fetch was recorded from the arrow parameter; this needs scope-aware filtering instead of a global name set.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

False positives: local variables/parameters matching browser API names are flagged

3 participants