Skip to content

Filename & naming

There are two levels of naming enforcement:

  • Per-rule: a rule's filename (files) and foldername (parent folder) apply to the files that rule governs.
  • Repo-wide: config naming sets defaults for every source file and folder under root. A rule's filename / foldername overrides the repo-wide default for what it governs. See Repo-wide naming.

Per-rule filename

The filename option constrains a file's basename. It does two jobs: it enforces a naming convention, and, when it contains a placeholder, it binds that placeholder so the pattern can require the declaration name to match the filename.

ts
{
  name: 'ui-component',
  files: 'components/ui/**/*.tsx',
  filename: '$Name.tsx',
  pattern: `const $Name: React.FC<IProps> = () => {}\nexport default $Name`,
}

On Button.tsx, filename binds $Name = Button, and the pattern then requires the component to be named Button. Widget.tsx containing const Panel = ... fails, because $Name is already bound to Widget.

Forms

Named conventions

A bare convention name checks the filename stem (basename without extension):

ValuePassesFails
'kebab-case'get-user, use-companygetUser
'PascalCase'Button, UserCardbutton
'camelCase'useUser, totalUseUser
ts
filename: 'kebab-case'; // matches get-user.ts, fails getUser.ts

Placeholder specs

A spec containing a $-placeholder captures part of the basename and binds it:

ts
filename: '$Name.tsx'; // Button.tsx      -> $Name = Button
filename: 'use-$Name.ts'; // use-user.ts     -> $Name = user

The captured value is what the pattern then enforces. In v0.1 the placeholder's casing rule (PascalCase for $Name, etc.) is checked when a placeholder binds to a declaration in the pattern, not when it's captured from the filename, so keep filename specs and pattern placeholders consistent.

Glob specs

A * in the spec matches any run of characters without binding anything:

ts
filename: 'use-*.ts'; // use-anything.ts passes; binds nothing
filename: '*.slice.ts'; // counter.slice.ts passes

Placeholders and * can be combined in one spec; everything else is matched literally.

Diagnostic

A filename mismatch is reported as ARCH_FILENAME_MISMATCH at line 1, showing the expected convention or spec and the actual basename. Because filename runs first and seeds bindings, a filename failure often makes the pattern's expectations clearer in the same run.

Per-rule foldername

A rule can also constrain the immediate parent folder of the files it matches, using a naming convention:

ts
{
  name: 'ui-component',
  files: 'components/ui/**/*.tsx',
  foldername: 'PascalCase', // components/ui/Button/Button.tsx → the "Button" folder
}

Reported as ARCH_FOLDERNAME_MISMATCH.

Repo-wide naming

Config-level naming sets defaults for every source file and folder under root, useful for a uniform house style (e.g. everything kebab-case) without writing a rule per folder.

ts
export default defineConfig({
  root: 'src',
  naming: {
    files: 'kebab-case', // the stem before the first dot (Button.test.tsx → "Button")
    folders: 'kebab-case', // every folder segment under root
    ignore: ['app/**'], // exempt framework folders (Next.js [id], (group), etc.)
  },
  rules: [
    /* ... */
  ],
});

Conventions: 'kebab-case', 'camelCase', 'PascalCase', 'snake_case', 'CONSTANT_CASE'.

Precedence

Rule-level wins over config-level, for what the rule governs.

  • A file's name: if the claiming rule sets filename, that applies; otherwise naming.files applies.
  • A folder's name: if a rule that governs a file directly in that folder sets foldername, that applies (first such rule in config order); otherwise naming.folders applies.
  • Files matched by no rule fall back entirely to naming.

Repo-wide naming is bluntest for files (components are usually PascalCase, hooks kebab-case, that's a per-rule filename job). It shines for folders, which are often uniform across a codebase. Add framework and generated directories to naming.ignore.

Released under the MIT License.