Skip to content

✨ feat(eslint): enforce PascalCase use-case file naming#352

Merged
cteyton merged 20 commits into
mainfrom
eslint-use-case
Jun 9, 2026
Merged

✨ feat(eslint): enforce PascalCase use-case file naming#352
cteyton merged 20 commits into
mainfrom
eslint-use-case

Conversation

@cteyton

@cteyton cteyton commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Explanation

Enforce the use-case file naming convention automatically. Use-case files under
packages/**/application/useCases/ followed two conflicting conventions — the
desired PascalCase form (AddGitRepoUseCase.ts) and a legacy lowercase-suffix
form (addGitRepo.usecase.ts).

This PR:

  • Adds a custom workspace ESLint rule packmind/use-case-filename (new eslint/
    plugin) that bans the legacy .usecase suffix and requires PascalCase. It is
    unit-tested with RuleTester and wired into the root flat config as error,
    so npm run lint now fails on any *.usecase.ts file.
  • Renames the 132 existing legacy files (sources + specs/tests) across 6
    packages to PascalCase, where each file name matches its exported class name.

ArchUnit was considered and rejected: it is JVM-only and its TS port targets
dependency/layer rules, not filename casing. A custom ESLint rule plugs straight
into the existing npm run lint pipeline with zero new runtime deps.

Type of Change

  • Bug fix
  • New feature
  • Improvement/Enhancement
  • Refactoring
  • Documentation
  • Breaking change

Affected Components

  • Domain packages affected: llm, recipes, deployments, skills, git, standards (file renames); new eslint-rules tooling project
  • Frontend / Backend / Both: Backend / tooling
  • Breaking changes (if any): None — renames are internal; all imports/barrels updated, no public package API changes

Testing

  • Unit tests added/updated
  • Integration tests added/updated
  • Manual testing completed
  • Test coverage maintained or improved

Test Details:

  • New RuleTester suite for packmind/use-case-filename (9 cases: PascalCase + helpers like index.ts/utils.ts/shared/* valid; *.usecase.ts / *.usecase.spec.ts invalid; one case locks the exact error message wording).
  • Full repo verification, all green:
    • npm run lint → 31 projects, 0 errors
    • npm run build → 27 projects
    • npm run test → 26 projects (exit 0)
  • Manual probe: a temporary tmpProbe.usecase.ts triggers the rule with a clear, actionable error suggesting TmpProbeUseCase.ts, then removed.

TODO List

  • CHANGELOG Updated
  • Documentation Updated

Reviewer Notes

  • The rule only flags the forbidden .usecase suffix (not a positive PascalCase
    requirement), so helper files inside useCases/ are never false-positives.
  • Each renamed file matches its exported class name — including bare classes that
    don't end in UseCase (e.g. commitToGit.usecase.tsCommitToGit.ts).
  • The rule's files glob is project-relative (**/application/useCases/**/*.ts)
    because nx lint runs eslint . with cwd = each project dir.
  • The rule + plugin are authored as CommonJS; a small local eslint/eslint.config.mjs
    allows require() in those files so the inferred eslint-rules:lint target passes.
  • Renames are one commit per package for easy review.

cteyton and others added 9 commits June 9, 2026 10:15
New workspace ESLint plugin (eslint/) with a use-case-filename rule that
bans the legacy <name>.usecase.ts form and requires PascalCase. Unit-tested
with RuleTester. Not yet wired into the root config.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename all <name>.usecase.ts use-case files (and their specs) to the
PascalCase <Name>UseCase.ts convention, updating imports and barrels.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename all <name>.usecase.ts use-case files (and their specs) to the
PascalCase <Name>UseCase.ts convention, updating imports and barrels.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename all <name>.usecase.ts use-case files (and their specs) to the
PascalCase <Name>UseCase.ts convention, updating imports and barrels.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename all <name>.usecase.ts use-case files (and their specs) to the
PascalCase <Name>UseCase.ts convention, updating imports and barrels.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename all <name>.usecase.ts use-case files (and their specs) to the
PascalCase <Name>UseCase.ts convention, updating imports and barrels.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename all <name>.usecase.ts use-case files (and their specs) to the
PascalCase <Name>UseCase.ts convention, updating imports and barrels.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the packmind/use-case-filename rule into the root flat config for
**/application/useCases/**/*.ts so npm run lint fails on the legacy
<name>.usecase.ts form. All existing files already renamed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Allow require() in the CommonJS rule/plugin files so the inferred
eslint-rules:lint target passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Too many files changed for review. (170 files found, 100 file limit)

cteyton and others added 6 commits June 9, 2026 10:56
Rename CommitToGit, HandleWebHook, HandleWebHookWithoutContent and
GetFileFromRepo (classes + files + references) to end in UseCase, for a
consistent use-case naming convention.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename use-case classes (and their files + references) from the
inconsistent "...Usecase" form to PascalCase "...UseCase".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename use-case classes (and their files + references) from the
inconsistent "...Usecase" form to PascalCase "...UseCase".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename use-case classes (and their files + references) from the
inconsistent "...Usecase" form to PascalCase "...UseCase".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename use-case classes (and their files + references) from the
inconsistent "...Usecase" form to PascalCase "...UseCase".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Upgrade packmind/use-case-filename to require PascalCase ending in
"UseCase" (capital C) for use-case files and exported classes:

- ban legacy ".usecase" filenames (preserving the original .ts/.tsx
  extension in the suggestion) and mis-cased "Usecase" filenames
- flag exported, non-Error classes that are mis-cased ("Usecase") or
  missing the "UseCase" suffix
- scope the rule to packages/ only; apps/* may co-locate non-use-case
  classes under useCases/ (e.g. the CLI diff strategies)

Addresses review feedback: message no longer overpromises, .tsx is
preserved, and Usecase/UseCase casing is unified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cteyton

cteyton commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Review feedback addressed

Resolved the three findings by tightening the convention to UseCase (capital C) everywhere, rather than softening the message:

#1 — message contradicted the applied convention. The rule now positively enforces the convention via AST, so the message is true:

  • legacy .usecase filename → legacyFilename
  • mis-cased Usecase filename → filenameCasing
  • exported class ending UsecaseclassCasing
  • exported (non-Error) class missing the suffix → classMissingSuffix

The 4 bare git classes (CommitToGit, HandleWebHook, HandleWebHookWithoutContent, GetFileFromRepo) were renamed to …UseCase, so reintroducing commitToGit.usecase.ts now correctly points at CommitToGitUseCase.

#2.tsx suggested as .ts. Fixed: the original extension is preserved in the suggestion (covered by a widget.usecase.tsxWidgetUseCase.tsx test case).

#3Usecase vs UseCase left inconsistent. Unified: 86 classes (82 …Usecase + 4 bare) renamed to …UseCase, classes + files + all references, one commit per package (recipes, skills, deployments, standards, git). 0 Usecase classes/files remain under packages/**/application/useCases/.

Scope note: the rule runs only on packages/apps/cli co-locates genuine non-use-case classes (*DiffStrategy implements IDiffStrategy) under useCases/, which a positive rule must not flag.

Verified: nx test eslint-rules (14 cases) ✓, npm run lint (31 projects, 0 errors) ✓, npm run build (27) ✓, npm run test (26) ✓.

cteyton and others added 5 commits June 9, 2026 11:29
Sweep the mis-cased "Usecase" -> "UseCase" across instance variables,
the LinterUseCases aggregator (class + file), and stale log/test labels,
so the codebase is consistent with the use-case naming convention.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Split the use-case naming checks into two rules:
- use-case-filename: structural (legacy .usecase ban + bare class must end
  in UseCase), scoped to packages useCases/.
- usecase-casing: repo-wide, flags the mis-cased "Usecase" in filenames AND
  identifiers (class names, variables, parameters, properties, types), so
  instance variables and aggregators stay consistent too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add {workspaceRoot}/eslint/**/* to the `default` namedInput so editing the
custom lint rules invalidates the Nx lint cache. Without this, a change to a
rule's logic would return stale cached lint results (the inferred lint inputs
only track eslint.config.mjs and tools/eslint-rules/**/*), so `npm run lint`
and the pre-push `nx affected -t lint` could miss it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The classMissingSuffix message only said to rename to *UseCase, leaving
the shared/ exemption (already configured in the root flat config) for
devs to discover. A legitimate helper class (mapper/DTO/builder) exported
under useCases/ would error with no obvious exit.

Narrowing detection by inheritance was rejected: many real use cases
extend no base class, so gating on `extends *UseCase` would silently miss
them. Instead the message now names both exits — rename to *UseCase, or
move helpers under shared/ — keeping detection coverage unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The new workspace-rules project lived in a top-level `eslint/` directory.
That name shadows the `eslint` npm package under Nx's config loader: when
`@nx/eslint/plugin` processes `eslint.config.mjs`, `@typescript-eslint/utils`
runs `require("eslint").ESLint.version` at module init and resolves to our
`eslint/index.js` (which exports `{ rules }`, no `ESLint`), throwing
"Cannot read properties of undefined (reading 'version')".

Project graph construction then fails, so every `nx run` app refuses to
boot. CI runs with `NX_DAEMON=false` (fresh graph each time) so it fails
hard; locally the daemon cache masked it. This is why the e2e environment
never came up and the e2e job timed out.

Rename the directory to `eslint-rules` (matching the Nx project name) and
update the three external references: the root config import, the nx.json
`default` named input glob, and project.json `sourceRoot`.
@sonarqubecloud

sonarqubecloud Bot commented Jun 9, 2026

Copy link
Copy Markdown

@cteyton cteyton merged commit c2f048a into main Jun 9, 2026
20 checks passed
@cteyton cteyton deleted the eslint-use-case branch June 9, 2026 10:59
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