Filename & naming
There are two levels of naming enforcement:
- Per-rule: a rule's
filename(files) andfoldername(parent folder) apply to the files that rule governs. - Repo-wide: config
namingsets defaults for every source file and folder underroot. A rule'sfilename/foldernameoverrides 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.
{
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):
| Value | Passes | Fails |
|---|---|---|
'kebab-case' | get-user, use-company | getUser |
'PascalCase' | Button, UserCard | button |
'camelCase' | useUser, total | UseUser |
filename: 'kebab-case'; // matches get-user.ts, fails getUser.tsPlaceholder specs
A spec containing a $-placeholder captures part of the basename and binds it:
filename: '$Name.tsx'; // Button.tsx -> $Name = Button
filename: 'use-$Name.ts'; // use-user.ts -> $Name = userThe 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:
filename: 'use-*.ts'; // use-anything.ts passes; binds nothing
filename: '*.slice.ts'; // counter.slice.ts passesPlaceholders 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:
{
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.
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; otherwisenaming.filesapplies. - 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); otherwisenaming.foldersapplies. - Files matched by no rule fall back entirely to
naming.
Repo-wide
namingis bluntest for files (components are usually PascalCase, hooks kebab-case, that's a per-rulefilenamejob). It shines for folders, which are often uniform across a codebase. Add framework and generated directories tonaming.ignore.