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