From 11180fa532eb4fb62ebedb19344aa4ffbd2dc089 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Mon, 14 Sep 2026 21:42:37 -0700 Subject: [PATCH] chore(lint): add anti-slop oxlint plugin (pinned, all rules off) (#20726) * chore(lint): add anti-slop oxlint plugin (all rules off) Vendors dmmulroy/anti-slop (MIT) plus no-call-only-assertions and no-pass-through-type-alias from maharshi365/deslop (MIT). Every rule starts "off"; each follow-up PR fixes one rule's violations and flips it to "error". * fix(lint): actually exclude the vendored plugin from the anti-slop audit oxlint does not honour ignorePatterns supplied via --config, so the config/oxlint-plugins/anti-slop/** entry never matched and the vendored rule source was being linted as first-party code (505 violations). Move the exclusion to the --ignore-pattern CLI flag in audit:anti-slop, which does work, and drop the entry that gave a false sense of coverage. Keeping vendored source unlinted matters because anti-slop is updated by three-way merge against the upstream snapshot; reformatting it locally would conflict on every update. * chore(lint): pin anti-slop instead of vendoring it; drop deslop Replaces the ~5k vendored lines with a git-pinned devDependency: oxlint-plugin-anti-slop: github:dmmulroy/anti-slop#c44ef22 anti-slop ships raw .ts with no build step, and Node refuses to type-strip anything under node_modules (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING), so oxlint cannot load it from there -- which is why upstream says to vendor it. A postinstall step copies the pinned package's source to .anti-slop-plugin/ (gitignored), which Node will type-strip because it sits outside node_modules. Upgrading is now a SHA bump rather than a re-vendor and three-way merge. Verified byte-identical rule output to the vendored copy across all 16 rules that fire. Drops maharshi365/deslop and its two rules (no-call-only-assertions, no-pass-through-type-alias). It is not on npm either, so it would need a second git pin and copy step, and it is a 5-star single-maintainer repo that is itself a re-namespaced copy of anti-slop. One upstream is enough. * ci(lint): run audit:anti-slop in PR CI config/scripts/pr-workflow-lint-parity.test.mjs requires every step in `pnpm lint` to have a matching step in .github/workflows/pr.yml; adding audit:anti-slop to lint without the workflow step failed that ratchet. Also makes audit:anti-slop sync the plugin itself before linting. The generated .anti-slop-plugin/ directory is gitignored and otherwise only created by postinstall, so a cached install that skips postinstall would leave oxlint unable to load the plugin. --- .github/workflows/pr.yml | 3 ++ .gitignore | 3 ++ .oxfmtrc.json | 6 ++- config/oxlint-anti-slop.json | 60 ++++++++++++++++++++++++ config/scripts/sync-anti-slop-plugin.mjs | 20 ++++++++ package.json | 10 ++-- pnpm-lock.yaml | 26 ++++++++++ 7 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 config/oxlint-anti-slop.json create mode 100644 config/scripts/sync-anti-slop-plugin.mjs diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 7418e8f80aa..b7832ba2d9e 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -133,6 +133,9 @@ jobs: - name: Lint run: pnpm exec oxlint --format github + - name: Reject low-evidence patterns + run: pnpm run audit:anti-slop + - name: Enforce focused code-quality plugins run: pnpm run audit:code-quality:native diff --git a/.gitignore b/.gitignore index c132a265c83..3d51edb0009 100644 --- a/.gitignore +++ b/.gitignore @@ -181,3 +181,6 @@ tests/e2e/.cross-version-checkouts/ # IS committed). Also keeps oxfmt/oxlint, which honor this file, from walking # vendored gems. /mobile/vendor/ + +# Generated by config/scripts/sync-anti-slop-plugin.mjs from the pinned oxlint-plugin-anti-slop +.anti-slop-plugin/ diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 0f27189d7cb..86931f1f9ec 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -4,5 +4,9 @@ "semi": false, "printWidth": 100, "trailingComma": "none", - "ignorePatterns": ["cloud/**", ".github/actions/cloud-sql-rollout-lease/**"] + "ignorePatterns": [ + "cloud/**", + ".github/actions/cloud-sql-rollout-lease/**", + ".anti-slop-plugin/**" + ] } diff --git a/config/oxlint-anti-slop.json b/config/oxlint-anti-slop.json new file mode 100644 index 00000000000..1f488ff8c56 --- /dev/null +++ b/config/oxlint-anti-slop.json @@ -0,0 +1,60 @@ +{ + "$schema": "../node_modules/oxlint/configuration_schema.json", + "plugins": [], + "jsPlugins": [ + { + "name": "anti-slop", + "specifier": "../.anti-slop-plugin/index.ts" + } + ], + "categories": { + "correctness": "off", + "suspicious": "off", + "pedantic": "off", + "perf": "off", + "style": "off", + "restriction": "off", + "nursery": "off" + }, + "ignorePatterns": [ + "**/node_modules", + "**/dist", + "**/out", + "cloud/**", + "src/shared/rpc-contract/rpc-params-catalog.generated.ts", + "tests/e2e/.cross-version-checkouts" + ], + "rules": { + "anti-slop/no-array-filter-map": "off", + "anti-slop/no-chained-type-assertions": "off", + "anti-slop/no-conditional-empty-object-spread": "off", + "anti-slop/no-known-value-widening": "off", + "anti-slop/no-module-mocking": "off", + "anti-slop/no-object-parameters": "off", + "anti-slop/no-reduce-accumulator-copy": "off", + "anti-slop/no-reflect-apply": "off", + "anti-slop/no-reflect-get": "off", + "anti-slop/no-runtime-typeof": "off", + "anti-slop/no-shape-in-symbol-names": "off", + "anti-slop/no-unknown-parameters": "off", + "anti-slop/no-unknown-returns": "off", + "anti-slop/no-unknown-type-aliases": "off", + "anti-slop/no-unsafe-dictionary-type": "off", + "anti-slop/no-widen-then-assert": "off", + "anti-slop/require-readable-spacing": "off", + "anti-slop/require-safety-comment-for-type-assertion": "off" + }, + "overrides": [ + { + "files": [ + "**/*.test.{ts,tsx}", + "**/*.spec.{ts,tsx}", + "tests/**/*.{ts,tsx}", + "**/__mocks__/**" + ], + "rules": { + "anti-slop/no-module-mocking": "off" + } + } + ] +} diff --git a/config/scripts/sync-anti-slop-plugin.mjs b/config/scripts/sync-anti-slop-plugin.mjs new file mode 100644 index 00000000000..66d9ac10a54 --- /dev/null +++ b/config/scripts/sync-anti-slop-plugin.mjs @@ -0,0 +1,20 @@ +// anti-slop ships raw .ts with no build step, and Node refuses to type-strip anything +// under node_modules (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING), so oxlint cannot load +// it from there. Copy the pinned package's source out to a gitignored dir it can load. +import { cpSync, mkdirSync, rmSync, writeFileSync } from 'node:fs' +import { resolve } from 'node:path' + +const repoRoot = resolve(import.meta.dirname, '../..') +const source = resolve(repoRoot, 'node_modules/oxlint-plugin-anti-slop/src') +const target = resolve(repoRoot, '.anti-slop-plugin') + +rmSync(target, { recursive: true, force: true }) +mkdirSync(target, { recursive: true }) +cpSync(source, target, { recursive: true }) +// Effect rules are opt-in upstream and this repo does not use Effect; tests would be linted. +rmSync(resolve(target, 'effect'), { recursive: true, force: true }) +cpSync( + resolve(repoRoot, 'node_modules/oxlint-plugin-anti-slop/LICENSE'), + resolve(target, 'LICENSE') +) +writeFileSync(resolve(target, 'package.json'), '{ "type": "module" }\n') diff --git a/package.json b/package.json index 174a8718641..96d8e00b97f 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "audit:perf": "oxlint --config config/oxlint-performance-audit.json --format json src", "test:perf:contracts": "vitest run --config config/vitest.performance.config.ts", "format": "oxfmt --write .", - "lint": "oxlint && pnpm run audit:code-quality:native && pnpm run audit:code-quality:type-aware && pnpm run check:reliability-gates && pnpm run check:dead-classes && pnpm run check:max-lines-ratchet && pnpm run check:ts-nocheck-ratchet && pnpm run check:runtime-electron-ratchet && pnpm run check:readme-local-links && pnpm run verify:rpc-params-catalog && pnpm run verify:bundled-skill-guides && pnpm run verify:skill-bundle-manifest && pnpm run verify:localization-catalog && pnpm run verify:localization-runtime-catalog && pnpm run verify:localization-extraction && pnpm run verify:localization-coverage", + "lint": "oxlint && pnpm run audit:anti-slop && pnpm run audit:code-quality:native && pnpm run audit:code-quality:type-aware && pnpm run check:reliability-gates && pnpm run check:dead-classes && pnpm run check:max-lines-ratchet && pnpm run check:ts-nocheck-ratchet && pnpm run check:runtime-electron-ratchet && pnpm run check:readme-local-links && pnpm run verify:rpc-params-catalog && pnpm run verify:bundled-skill-guides && pnpm run verify:skill-bundle-manifest && pnpm run verify:localization-catalog && pnpm run verify:localization-runtime-catalog && pnpm run verify:localization-extraction && pnpm run verify:localization-coverage", "audit:code-quality": "pnpm run audit:code-quality:native && pnpm run audit:code-quality:type-aware && pnpm run audit:react-doctor", "audit:code-quality:native": "oxlint --config config/oxlint-code-quality-native-plugins.json src config tests mobile --deny-warnings", "audit:code-quality:type-aware": "oxlint --type-aware --config config/oxlint-code-quality-type-aware.json src config tests --deny-warnings", @@ -97,7 +97,7 @@ "build": "pnpm run build:desktop && pnpm run build:native", "build:release": "pnpm run build:relay && pnpm run build:native && pnpm run verify:computer-native && pnpm run build:cli && pnpm run build:electron-vite && pnpm run verify:built-skills-cli && pnpm run build:web-from-renderer", "build:release:parallel": "pnpm run build:relay && pnpm run build:native && pnpm run verify:computer-native && pnpm run build:cli && pnpm run build:electron-vite:parallel && pnpm run verify:built-skills-cli && pnpm run build:web-from-renderer", - "postinstall": "node config/scripts/rebuild-native-deps.mjs", + "postinstall": "node config/scripts/rebuild-native-deps.mjs && node config/scripts/sync-anti-slop-plugin.mjs", "rebuild:electron": "node config/scripts/rebuild-native-deps.mjs", "reclaim:electron-dists": "node config/scripts/reclaim-electron-dists.mjs", "reclaim:dev-bundles": "node config/scripts/reclaim-dev-electron-bundles.mjs", @@ -164,7 +164,9 @@ "test:e2e:remote-bulk-open-freeze": "pnpm run ensure:electron-runtime && pnpm exec playwright test tests/e2e/remote-session-bulk-open-freeze-repro.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1", "test:e2e:ssh-docker-bulk-open-freeze": "node config/scripts/run-ssh-docker-bulk-open-freeze-e2e.mjs", "repro:live-remote-bulk-open-freeze": "node config/scripts/live-remote-bulk-open-freeze-repro.mjs", - "repro:live-remote-realistic-freeze": "node config/scripts/live-remote-realistic-freeze-repro.mjs" + "repro:live-remote-realistic-freeze": "node config/scripts/live-remote-realistic-freeze-repro.mjs", + "audit:anti-slop": "node config/scripts/sync-anti-slop-plugin.mjs && oxlint --config config/oxlint-anti-slop.json src config tests mobile --deny-warnings", + "sync:anti-slop-plugin": "node config/scripts/sync-anti-slop-plugin.mjs" }, "dependencies": { "@anthropic-ai/claude-agent-sdk": "0.3.251", @@ -199,6 +201,7 @@ "@electron-toolkit/tsconfig": "^2.0.0", "@electron/rebuild": "^4.2.0", "@monaco-editor/react": "^4.7.0", + "@oxlint/plugins": "1.80.0", "@playwright/test": "^1.59.1", "@sanity/diff-match-patch": "^3.2.0", "@shadcn/lint": "^0.1.0", @@ -265,6 +268,7 @@ "monaco-editor": "^0.55.1", "oxfmt": "^0.65.0", "oxlint": "^1.80.0", + "oxlint-plugin-anti-slop": "github:dmmulroy/anti-slop#c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b", "oxlint-plugin-react-doctor": "0.9.1", "oxlint-tsgolint": "7.0.2001", "pdfjs-dist": "^6.3.289", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e9de58b228..6c9c98349d6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -213,6 +213,9 @@ importers: '@monaco-editor/react': specifier: ^4.7.0 version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@oxlint/plugins': + specifier: 1.80.0 + version: 1.80.0 '@playwright/test': specifier: ^1.59.1 version: 1.59.1 @@ -411,6 +414,9 @@ importers: oxlint: specifier: ^1.80.0 version: 1.80.0(oxlint-tsgolint@7.0.2001) + oxlint-plugin-anti-slop: + specifier: github:dmmulroy/anti-slop#c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b + version: https://codeload.github.com/dmmulroy/anti-slop/tar.gz/c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b oxlint-plugin-react-doctor: specifier: 0.9.1 version: 0.9.1 @@ -1940,6 +1946,14 @@ packages: cpu: [x64] os: [win32] + '@oxlint/plugins@1.78.0': + resolution: {integrity: sha512-Ypt8KeRYw+4jUtlPirfcHWMrn5ms12VrrFPD+Mds477/7tJxG1Kcz2Yrg2nVcTQEUx/GdlhS+BUg1kmxNm04Ug==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@oxlint/plugins@1.80.0': + resolution: {integrity: sha512-QRgH1XqQEYNHa4f1vvPQ5fAdNdncHGIUG1ZWLlGIZHky3qwCEeAKYitZNbZMtaXtAQAAFFTOwqUfzESvimqZNA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@parcel/watcher-android-arm64@2.5.6': resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} engines: {node: '>= 10.0.0'} @@ -6059,6 +6073,10 @@ packages: vite-plus: optional: true + oxlint-plugin-anti-slop@https://codeload.github.com/dmmulroy/anti-slop/tar.gz/c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b: + resolution: {gitHosted: true, integrity: sha512-Vj/M0k5Bt1Q2pGdfXZ24wGXycBcvFHwJ/pjHIFl1hV8soaZKHl1lba5UXlQFY5xgwQ5TNfEWiKfsEt0yyRyVUg==, tarball: https://codeload.github.com/dmmulroy/anti-slop/tar.gz/c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b} + version: 0.1.2 + oxlint-plugin-react-doctor@0.9.1: resolution: {integrity: sha512-yCW8USbiuszbVsUMN4fL1iU7mRu3Ae3w96+k/xqCyWvW6bF6DzCMtLy5N/w6WLRX78iQsWxVqW/SEgzRBXLfsA==} engines: {node: ^20.19.0 || >=22.13.0} @@ -8605,6 +8623,10 @@ snapshots: '@oxlint/binding-win32-x64-msvc@1.80.0': optional: true + '@oxlint/plugins@1.78.0': {} + + '@oxlint/plugins@1.80.0': {} + '@parcel/watcher-android-arm64@2.5.6': optional: true @@ -13162,6 +13184,10 @@ snapshots: '@oxfmt/binding-win32-ia32-msvc': 0.65.0 '@oxfmt/binding-win32-x64-msvc': 0.65.0 + oxlint-plugin-anti-slop@https://codeload.github.com/dmmulroy/anti-slop/tar.gz/c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b: + dependencies: + '@oxlint/plugins': 1.78.0 + oxlint-plugin-react-doctor@0.9.1: dependencies: '@typescript-eslint/types': 8.60.0