The eslint config's ignores list and comment still named the deleted src/db-drizzle/schema.ts and relations.ts and never named src/db-kysely/schema.ts, so the generated mirror was being linted for the first time and tripping sonarjs/redundant-type-aliases — exactly the trap the config's own comment already described from #261 and #217. The ignores list now names src/db-kysely/schema.ts and the comment is updated to match. The schema mirror drift test built one flat Set of every two-space-indented key in the whole generated file and asked only whether a live column name appeared anywhere in it, rather than checking it against the specific table it belongs to. Seventeen column names are declared on two or more tables and created_at is on fourteen of eighteen, so a migration adding created_at, updated_at, status, name, sort_order, token, or expires_at to a table that lacks it would pass vacuously. Replaced mirroredTables with mirroredColumns, which reads the DB interface to map each table name to its declaring interface and then reads that interface's own columns, and changed the column-mirroring test to look up columns per table. Verified the guard can actually fail: removing customer_id from the Carts interface made the test fail naming carts.customer_id exactly, and restoring the file made it pass again. The root .gitignore still carried a comment block and two patterns for drizzle-kit pull output under backend/src/db-drizzle, a directory this branch deleted along with backend/drizzle.config.ts. kysely-codegen writes only the single tracked file it's pointed at, so nothing replaces the rule — deleted the block and both patterns. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
140 lines
5.7 KiB
JavaScript
140 lines
5.7 KiB
JavaScript
import js from '@eslint/js';
|
|
import tseslint from 'typescript-eslint';
|
|
import sonarjs from 'eslint-plugin-sonarjs';
|
|
import globals from 'globals';
|
|
|
|
// Named `.mjs` because this package is CommonJS — `eslint.config.js` would be
|
|
// parsed as CJS and the imports above would fail.
|
|
//
|
|
// Policy: every preset is downgraded to advisory, and the rules that actually
|
|
// fail the build are listed once at the bottom. That way the CI gate is
|
|
// readable in one place rather than inferred from four presets' defaults.
|
|
// The reasoning behind the split, and the measurements it rests on, are in
|
|
// docs/superpowers/specs/2026-08-19-eslint-design.md.
|
|
|
|
/**
|
|
* Rewrites a preset's enabled rules to `warn`, preserving each rule's options.
|
|
* Rules the preset explicitly turned off stay off — a preset that disables a
|
|
* rule means it, and flipping those to `warn` turns the whole of SonarJS's
|
|
* opt-in catalogue (file headers, naming conventions) into daily noise.
|
|
*/
|
|
const advisory = (config) => ({
|
|
...config,
|
|
rules: Object.fromEntries(
|
|
Object.entries(config.rules ?? {}).map(([rule, level]) => {
|
|
const severity = Array.isArray(level) ? level[0] : level;
|
|
if (severity === 'off' || severity === 0) return [rule, level];
|
|
return [rule, Array.isArray(level) ? ['warn', ...level.slice(1)] : 'warn'];
|
|
})
|
|
),
|
|
});
|
|
|
|
export default tseslint.config(
|
|
// src/db-kysely/schema.ts is `kysely-codegen` output, not written by anyone
|
|
// here. #261 hand-fixed an unused-parameter warning in the equivalent Drizzle
|
|
// file and #217's regeneration put it straight back, which is the whole
|
|
// argument: linting generated code buys a fix that the next regeneration
|
|
// undoes. The hand-written files in that directory are still linted.
|
|
{
|
|
ignores: [
|
|
'dist/**',
|
|
'coverage/**',
|
|
'eslint.config.mjs',
|
|
'src/db-kysely/schema.ts'
|
|
]
|
|
},
|
|
|
|
...[js.configs.recommended, ...tseslint.configs.recommended, sonarjs.configs.recommended].map(
|
|
advisory
|
|
),
|
|
|
|
{
|
|
files: ['src/**/*.ts'],
|
|
languageOptions: {
|
|
globals: globals.node,
|
|
parserOptions: {
|
|
projectService: true,
|
|
tsconfigRootDir: import.meta.dirname,
|
|
},
|
|
},
|
|
rules: {
|
|
// The two rules this repo has actually been bitten by. #59 is the whole
|
|
// argument: an async handler whose rejection nothing forwards produces no
|
|
// response at all, and the request hangs rather than failing visibly.
|
|
'@typescript-eslint/no-floating-promises': 'error',
|
|
'@typescript-eslint/no-misused-promises': [
|
|
'error',
|
|
{ checksVoidReturn: { attributes: false } },
|
|
],
|
|
},
|
|
},
|
|
|
|
{
|
|
// The test suites, in scope since #298. They had never been linted at all:
|
|
// this config said tests were out of scope because tsconfig.json includes
|
|
// only `src`, and that stayed true for long enough that two defects lived
|
|
// here undetected — a unit test that opened a real TLS connection to Gmail
|
|
// on every run, and integration tests that mocked the shared pg pool and
|
|
// made a suite unrunnable. Neither is something lint would necessarily have
|
|
// caught, but neither was ever looked at.
|
|
//
|
|
// `project` rather than `projectService`, for the reason the frontend's
|
|
// equivalent block records: the service resolves each file to the nearest
|
|
// tsconfig.json, which for tests/ is the one that excludes them, and every
|
|
// file then errors as not part of a project.
|
|
files: ['tests/**/*.ts'],
|
|
languageOptions: {
|
|
globals: { ...globals.node, ...globals.jest },
|
|
parserOptions: {
|
|
project: ['./tsconfig.test.json'],
|
|
tsconfigRootDir: import.meta.dirname,
|
|
},
|
|
},
|
|
rules: {
|
|
// The same rule src is held to, and it matters at least as much here.
|
|
// An unawaited promise in a test does not fail the test — it passes,
|
|
// having asserted nothing, and the failure surfaces later as a suite that
|
|
// will not exit.
|
|
'@typescript-eslint/no-floating-promises': 'error',
|
|
'@typescript-eslint/no-misused-promises': [
|
|
'error',
|
|
{ checksVoidReturn: { attributes: false } },
|
|
],
|
|
|
|
// Everything below is switched off for tests rather than left as a
|
|
// warning, on #60's argument: bringing these files in scope produced 77
|
|
// warnings, of which 60 were rules that cannot be true in a test. A rule
|
|
// that cannot be true here is noise, and noise hides the rules that can.
|
|
// What is left is signal — unused variables, useless escapes, a regex
|
|
// worth a second look.
|
|
|
|
// 41 of the 77. Test credentials are the entire point of a test, and this
|
|
// project's own rule is that they must live only in test paths — which is
|
|
// here. Flagging them where they belong trains a reader to skip the rule
|
|
// where they do not.
|
|
'sonarjs/no-hardcoded-passwords': 'off',
|
|
|
|
// Stub servers and fixtures: `http://127.0.0.1:<port>`. There is no
|
|
// transport to secure between a test and a socket it opened itself.
|
|
'sonarjs/no-clear-text-protocols': 'off',
|
|
|
|
// 203.0.113.5 is TEST-NET-3, reserved by RFC 5737 for exactly this. A
|
|
// documentation address is the correct thing to hardcode.
|
|
'sonarjs/no-hardcoded-ip': 'off',
|
|
|
|
// `os.tmpdir()`, via mkdtemp, which is how these suites get a scratch
|
|
// uploads directory they can delete afterwards.
|
|
'sonarjs/publicly-writable-directories': 'off',
|
|
|
|
// Math.random for a run id. Nothing here is a secret; it only has to not
|
|
// collide with a parallel worker.
|
|
'sonarjs/pseudo-random': 'off',
|
|
|
|
// Sorting two string arrays to compare them is how several guards assert
|
|
// set equality. The locale-aware comparator the rule wants would change
|
|
// nothing except the reading.
|
|
'sonarjs/no-alphabetical-sort': 'off',
|
|
},
|
|
}
|
|
);
|