Skip to content

feat(eslint-config): shared ESLint 9 flat config, and fix this repo's lint - #98

Merged
pyramation merged 3 commits into
mainfrom
feat/eslint-config
Jul 31, 2026
Merged

feat(eslint-config): shared ESLint 9 flat config, and fix this repo's lint#98
pyramation merged 3 commits into
mainfrom
feat/eslint-config

Conversation

@pyramation

Copy link
Copy Markdown
Contributor

Summary

pnpm lint has been dead in every Constructive repo since the eslint 8→9 bump — eslint 9 only reads flat config, and we only ship .eslintrc.json. This adds @constructive-io/eslint-config@0.1.0, a faithful flat-config port of that rule set, and dogfoods it here.

Consumers get 3 lines:

// eslint.config.mjs
import base from '@constructive-io/eslint-config';
export default [...base, { ignores: ['**/generated/**'] }];

The package is deliberately a leaf. No build step (hand-written index.mjs + index.d.ts), no makage anywhere in its dependency graph — so makage can devDepend on it without a publish cycle. Because there's no dist/, it's the one package here that publishes from the package root (files: [index.mjs, index.d.ts, README.md, LICENSE]) rather than publishConfig.directory: dist.

Surface:

export default: Linter.Config[]              // spread this
export function createConfig(options?): Linter.Config[]   // { ignores, files, rules }
export const defaultIgnores, files, rules

Verified --print-config against the old .eslintrc.json semantics (parser, plugins, indent/quotes/quote-props/semi, warn-level import sorting and unused vars, the off list, browser+node+es2021+jest globals), and verified an out-of-repo consumer by npm packing the tarball into a scratch project and linting a file with it.

Two deviations from a literal port

The old config was written against @typescript-eslint v6-era recommended; on v8 the same "recommended" pulls in rules that flag pre-existing, non-buggy code. Rather than edit 300+ sites:

  • shared config: @typescript-eslint/no-empty-object-type relaxed to { allowInterfaces: 'always' } — empty interfaces are used across our repos as extension points (interface ParserOptions {}) and as named aliases (interface JSONStringifyOptions extends Omit<...> {}). Also added no-require-imports: off alongside the already-off no-var-requires (v8 renamed it).
  • this repo's eslint.config.mjs (not the shared package): no-control-regex off for yanse/clean-ansi/inquirerer (they match ANSI escapes on purpose), all of noble-hashes exempted from no-unused-expressions/no-empty-object-type (vendored upstream port, keep it diffable), and **/__fixtures__/output/** ignored (codegen output).

Commits

  1. the package + root eslint.config.mjs, .eslintrc.json deleted
  2. the two override groups above
  3. mechanical eslint . --fix churn across 176 files — almost entirely import sorting and unused-import removal

No behavior changes. The only non-autofixable hand edits, all semantics-preserving: dropped 3 catch (e) { throw e } rethrows, removed useless regex escapes in strfy-js/src/utils.ts (/\/[^\/]*$//\/[^/]*$/), and put comments in 2 empty catch {} blocks. pnpm lint now exits 0 (38 no-unused-vars warnings remain, warn-level by design); pnpm build and pnpm test pass (schema-ts-cli and yamlize-cli fail with "no tests found" on main too; neither is in the CI matrix).

Follow-ups (not in this PR)

  • Roll out to pgsql-parser, constructive, constructive-db, etc. — one 3-line eslint.config.mjs per repo, after this publishes.
  • No repo runs lint in CI, which is exactly why this stayed broken. Worth adding a lint job.
  • Scaffolding/boilerplate templates should emit eslint.config.mjs so new repos aren't born broken.

Context: surfaced in constructive-io/pgsql-parser#329 / constructive-io/constructive-planning#1336.

Link to Devin session: https://app.devin.ai/sessions/3b5f5e76ddc74eda83a3543d7da2c75f
Requested by: @pyramation

Adds @constructive-io/eslint-config, a build-free leaf package that ports the
legacy .eslintrc.json rule set to flat config, and consumes it from the repo
root so pnpm lint works under eslint 9.
Ports of the legacy rule set surface three classes of pre-existing errors that
are not code smells: empty interfaces used as extension points (relaxed in the
shared config via allowInterfaces), ANSI control characters in the terminal
packages, and the vendored noble-hashes port (both scoped to this repo's
eslint.config.mjs). Generated __fixtures__/output is ignored.
Mechanical output of `eslint . --fix` under the new flat config (mostly import
sorting and unused-import removal), plus a few hand fixes for rules autofix
can't handle: useless escapes in strfy-js regexes, useless try/catch rethrows,
and comments in empty catch blocks. No behavior changes.
@pyramation pyramation self-assigned this Jul 31, 2026
@devin-ai-integration

Copy link
Copy Markdown

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedglobals@​17.8.01001008694100

View full report

@pyramation
pyramation merged commit e9381aa into main Jul 31, 2026
51 checks passed
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