Skip to content

JS-2019: Implement S8985: side effects should not be performed inside "waitFor" callbacks#7488

Open
nathsou wants to merge 6 commits into
masterfrom
new-rule/S8985
Open

JS-2019: Implement S8985: side effects should not be performed inside "waitFor" callbacks#7488
nathsou wants to merge 6 commits into
masterfrom
new-rule/S8985

Conversation

@nathsou

@nathsou nathsou commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Implement S8985 by importing eslint-plugin-testing-library's no-wait-for-side-effects rule.
  • Decorate the upstream rule to force strict Testing Library import resolution (testing-library/utils-module, custom-renders, custom-queries set to off), since the upstream rule's aggressive name-based heuristics would otherwise match any identically-named waitFor/fireEvent/userEvent/render regardless of where it's actually imported from.
  • Add unit tests covering: side effect before waitFor (compliant), side effect inside a .then() chained after waitFor() (compliant), same-named non-Testing-Library waitFor/render helpers (compliant), block-statement and implicit-return side effects inside waitFor (non-compliant, with quickfix), and aliased Testing Library imports (non-compliant).
  • Temporarily pin rspec.sha to the RSPEC PR branch tip until that PR merges, per docs/BUILD.md.

Links


Summary by Gitar

  • Infrastructure:
    • Updated gh-action_cache to v1.7.1 across all CI workflows to address cache-miss issues.
    • Registered the new testing-library external plugin and documented the S8985 rule mapping.
    • Enabled S8985 in the default Sonar way quality profile.

This will update automatically on new commits.

…" callbacks

Import eslint-plugin-testing-library's no-wait-for-side-effects rule,
decorated to force strict Testing Library import resolution (the upstream
rule's aggressive name-based heuristics would otherwise match any
identically-named waitFor/fireEvent/userEvent/render regardless of import
origin).

Temporarily pins rspec.sha to the RSPEC PR branch tip (SonarSource/rspec#7334)
until that PR merges, per docs/BUILD.md.
@nathsou nathsou self-assigned this Jul 7, 2026
@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Jul 7, 2026

Copy link
Copy Markdown

JS-2019

@datadog-sonarsource

datadog-sonarsource Bot commented Jul 7, 2026

Copy link
Copy Markdown

Pipelines

⚠️ Warnings

🚦 1 Pipeline job failed

Build | Prepare RSPEC rule data   View in Datadog   GitHub Actions

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 79ca244 | Docs | Give us feedback!

Comment thread rspec.sha Outdated
Comment thread packages/analysis/src/jsts/rules/S8985/decorator.ts
The floating v1 tag now resolves to a commit (8d5f45db5f, "Skip
duplicate S3 cache save when content matches the default-branch
fallback", merged 2026-06-30) that skips saving the npm cache under a
branch's own lockfile-hash key whenever content matches master's, and
relies on a fallback restore that doesn't account for a differing
lockfile hash - leaving node_modules empty on any branch that changes
package-lock.json (like this one, which adds eslint-plugin-testing-library).

v1.7.1 predates that change. Pinning here as a test to confirm this
is really the regression before deciding between reverting to a
pinned version repo-wide vs. the npm-ci-fallback approach in #7494.
Comment thread .github/workflows/build.yml Outdated
Resolve conflicts: build.yml (keep master's artifact-based JS coverage
handoff, alongside the v1.7.1 gh-action_cache pin), and the generated
rules README/Sonar way profile (interleave S8950 and S8985 entries).
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Ruling Report

Code no longer flagged (222 issues)

S2486

Ghost/core/client/app/controllers/feature.js:16

    14 |             try {
    15 |                 value = JSON.parse(this.get('setting.labs') || {});
>   16 |             } catch (err) {
    17 |                 value = {};
    18 |             }

Ghost/core/client/app/controllers/settings/navigation.js:59

    57 |         try {
    58 |             navItems = JSON.parse(this.get('model.navigation') || [{}]);
>   59 |         } catch (e) {
    60 |             navItems = [{}];
    61 |         }

Ghost/core/client/app/services/config.js:19

    17 |         try {
    18 |             return JSON.parse(val);
>   19 |         } catch (e) {
    20 |             return val;
    21 |         }

Ghost/core/client/app/utils/ajax.js:30

    28 |                 message =  request.responseJSON.error || 'Unknown Error';
    29 |             }
>   30 |         } catch (e) {
    31 |             msgDetail = request.status ? `${request.status} - ${request.statusText}` : 'Server was not available';
    32 |             message = `The server returned an error (${msgDetail}).`;

Ghost/core/server/api/settings.js:48

    46 |         try {
    47 |             labsValue = JSON.parse(settingsCache.labs.value);
>   48 |         } catch (e) {
    49 |             errors.logError.apply(this, errorMessages);
    50 |         }

Ghost/core/server/config/index.js:95

    93 |         try {
    94 |             pg = require('pg');
>   95 |         } catch (e) {
    96 |             pg = require('pg.js');
    97 |         }

Ghost/core/server/ghost-server.js:46

    44 |             try {
    45 |                 fs.unlinkSync(socketConfig.path);
>   46 |             } catch (e) {
    47 |                 // We can ignore this.
    48 |             }

Ghost/core/test/utils/index.js:325

   323 |             try {
   324 |                 data = JSON.parse(fileContents);
>  325 |             } catch (e) {
   326 |                 return new Error('Failed to parse the file');
   327 |             }

TypeScript/lib/tsc.js:2198

  2196 |                             stat = _fs.statSync(name);
  2197 |                         }
> 2198 |                         catch (e) {
  2199 |                             continue;
  2200 |                         }

TypeScript/lib/tsc.js:2310

  2308 |                         return _fs.statSync(path).mtime;
  2309 |                     }
> 2310 |                     catch (e) {
  2311 |                         return undefined;
  2312 |                     }

...and 212 more

📋 View full report

Code no longer flagged (222)

S2486

nathsou added a commit that referenced this pull request Jul 8, 2026
Confirmed on PR #7488 that pinning to v1.7.1 (the release before
8d5f45db5f, "Skip duplicate S3 cache save when content matches the
default-branch fallback", merged into the gh-action_cache v1 branch on
2026-06-30) resolves the cache-miss/"tsx: not found" failure directly,
without needing a fallback install step. That commit is the actual
regression: it skips saving an npm cache entry under a branch's own
lockfile-hash key whenever content matches master's, relying on a
fallback restore that doesn't account for a differing lockfile hash -
so restore-only jobs end up with no node_modules at all whenever a
branch's lockfile actually differs from master (e.g. a new
devDependency, or a Renovate bump).

Pinning to v1.7.1 undoes the regression directly instead of working
around its effects.
nathsou added 3 commits July 8, 2026 10:32
That pin was only pushed here to validate the CI cache-miss root cause
(confirmed - see #7494). The real fix belongs in build.yml on master
via #7494, not duplicated per-PR; this branch will pick it up on the
next rebase/merge once #7494 lands.
…S8985

Bump the temporary rspec.sha pin from 689ac4b9973 to ec837696ca69, the
current tip of SonarSource/rspec#7334 (rule/add-RSPEC-S8985), which adds
explicit fix guidance and relevance/false-positive notes to the S8985
rule.adoc. Verified via `npm run rspec:refresh -- -Drspec.sha=<sha>` that
S8985.json is unaffected (only the rule description wording changed) and
reverted unrelated drift the refresh pulled in for S2068/S6418/S6437.
…kage-lock.json

The eslint-plugin-testing-library@7.16.2 entry added for S8985 was
generated through the local Artifactory npm mirror (repox.jfrog.io),
which differs from the registry.npmjs.org host used by every other
lockfile entry (1001 of 1004). Re-resolving through that mirror caused
npm to rewrite ~16 unrelated optional @esbuild/* platform packages,
dropping their "libc" field.

Root cause identified by diffing against sibling commit 9bc5ee5
(S8980), which added the identical dependency+version with a clean,
18-line, zero-churn diff using the registry.npmjs.org host. Fixed by:
restoring the 16 stripped "libc" blocks to their exact prior values/
positions (verified byte-identical to the pre-S8985 parent commit
b0e0911), and pointing the new package's "resolved" URL at
registry.npmjs.org with its unchanged integrity hash (same tarball,
confirmed identical sha512 across both mirrors), matching the
project-wide convention and S8980's precedent.

package-lock.json now diffs against b0e0911 with only the two
expected additions: the devDependencies entry and the new
node_modules/eslint-plugin-testing-library block, matching S8980's
pattern exactly. Verified: JSON is well-formed, no other files
touched, and the S8985 unit test still passes.
@gitar-bot

gitar-bot Bot commented Jul 8, 2026

Copy link
Copy Markdown

Analyzing CI failures

Code Review ✅ Approved 3 resolved / 3 findings

Implements rule S8985 to restrict side effects in waitFor callbacks and refines upstream rule resolution to prevent false positives. Reverted temporary CI cache and RSPEC pinning configurations as requested.

✅ 3 resolved
Quality: Temporary CI cache-action pin should be reverted before merge

📄 .github/workflows/build.yml:102 📄 .github/workflows/build.yml:167 📄 .github/workflows/build.yml:306 📄 .github/workflows/build.yml:423 📄 .github/workflows/build.yml:443 📄 .github/workflows/build.yml:468 📄 .github/workflows/build.yml:488 📄 .github/workflows/build.yml:527
This commit pins SonarSource/gh-action_cache from the floating @v1 tag to the exact @v1.7.1 across 8 job steps. The commit message states this is a temporary change ("Temporarily pin gh-action_cache to v1.7.1 to test CI cache-miss fix"). Like the temporary rspec.sha pin noted previously, this diagnostic change must be reverted (restored to @v1, or intentionally kept if the pin is meant to be permanent) before this PR merges to master; otherwise the workflow will remain frozen at 1.7.1 and won't receive future patch/security updates from the v1 major tag. Confirm the intent and either revert or document the reason for the fixed version.

Quality: rspec.sha pinned to a temporary RSPEC PR branch tip

📄 rspec.sha:1
rspec.sha (new file) pins the RSPEC dependency to 689ac4b997309404af65c823b1694116180d60f3, the tip of the unmerged RSPEC PR branch (https://github.com/SonarSource/rspec/pull/7334). The PR description acknowledges this is temporary. Merging to master with this pin risks a broken/unreproducible build if that branch is force-pushed or deleted, and freezes rule metadata resolution at a non-master commit. Ensure this file is removed (or updated to a merged master commit) before this PR merges.

Edge Case: Forcing utils-module off may cause false negatives for custom re-exports

📄 packages/analysis/src/jsts/rules/S8985/decorator.ts:31-45
STRICT_IMPORT_RESOLUTION_SETTINGS forces testing-library/utils-module, custom-renders, and custom-queries to 'off', overriding any project-provided values via { ...context.settings, ...STRICT_IMPORT_RESOLUTION_SETTINGS }. This intentionally disables the aggressive name-based heuristics, but it also means projects that legitimately re-export Testing Library utilities (waitFor, fireEvent, custom render) through their own utils module will no longer be recognized, producing false negatives. This is a documented tradeoff in the decorator comment, but consider noting it in the rule docs so users understand why side effects inside waitFor from a custom utils module are not flagged.

Additionally, Object.create(context, { settings: { value: ..., enumerable: true } }) defines settings as non-writable/non-configurable; if the upstream rule ever attempts to assign to context.settings, it would throw in strict mode. A shallow object spread of the context would be more robust, though the prototype approach works for the current read-only usage.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

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.

1 participant