Skip to content

File and folder names

Markdown for agents

This guide checks the names of files and folders, and which files a folder must hold.

The example is a React app with components and features. Some of its names break the team’s conventions:

  • archangel.config.ts
  • Directorysrc/
    • Directorycomponents/
      • Avatar.tsx
      • priceTag.tsx
    • Directoryfeatures/
      • Directorycart/
        • index.ts
        • Directoryui/
          • cart-row.tsx
          • cart.container.tsx
          • cart.view.tsx
      • DirectoryorderHistory/
        • Directoryui/
          • orders.view.tsx

This config has five rules. The sections below tell you about each one.

archangel.config.ts
import { defineConfig } from '@enhance-eng/archangel';
export default defineConfig({
rules: ({ rule }) => [
rule('folder-names', {
folders: 'src/**',
mustBeNamed: 'kebab-case',
because: 'One style for folders, so an import path is easy to guess.',
examples: { rejected: ['src/features/UserProfile/index.ts'] },
}),
rule('component-names', {
files: 'src/components/**/*.tsx',
mustBeNamed: 'PascalCase',
because: 'A component file has the name of the component.',
examples: { rejected: ['src/components/user-avatar.tsx'] },
}),
rule('component-exports', {
files: 'src/components/**/*.tsx',
mustExport: '{FileName}',
because: 'Code imports a component by the name of its file.',
examples: {
rejected: [{ 'src/components/Avatar.tsx': 'export function Picture() {}' }],
},
}),
rule('ui-file-names', {
files: 'src/features/*/ui/**/*.tsx',
mustBeNamed: ['*.view', '*.container'],
because: 'A view only shows data. A container gets the data.',
examples: { rejected: ['src/features/cart/ui/cart-list.tsx'] },
}),
rule('feature-entry', {
folders: 'src/features/*',
mustContain: 'index.ts',
because: 'Other code imports a feature through its index file.',
examples: { rejected: ['src/features/search/ui/search.view.tsx'] },
}),
],
});

folder-names uses folders, not files. The pattern src/** matches each folder inside src. mustBeNamed: 'kebab-case' gives the style.

The styles are kebab-case, camelCase, PascalCase, snake_case and SCREAMING_SNAKE_CASE.

component-names gives each component file a name in PascalCase. For a style, archangel reads the name up to the first dot. So Avatar.tsx has the name Avatar, and Button.test.tsx has the name Button.

ui-file-names permits two name patterns: *.view and *.container. A name pattern reads the full name of the file without its last extension, so cart.view.tsx has the full name cart.view. In a name pattern, * is any text or no text.

  • *.view matches cart.view.tsx.
  • *.view does not match view.tsx, whose full name is view.
  • *.view does not match cart.view.extra.tsx, whose full name is cart.view.extra.

One entry of the list must match. A list can mix styles and name patterns. An empty list is a config error.

component-exports uses an export condition: mustExport: '{FileName}'. {FileName} is the name of the file in PascalCase. So Avatar.tsx must export Avatar. A re-export and export * count.

The name of the file or its folder can go in a name pattern in each style: {FileName}, {fileName}, {file-name}, {file_name}, {FILE_NAME}, and the same spellings of {FolderName}.

feature-entry uses mustContain. Each folder that src/features/* matches must hold index.ts. To permit one file of a list, write a list: mustContain: ['index.ts', 'index.tsx'].

  1. Check the app:

    Terminal
    $ npx archangel check
    error[must-be-named]: the name of priceTag.tsx is priceTag, which is not PascalCase; in PascalCase the file is PriceTag.tsx
    --> src/components/priceTag.tsx
    = help: name it PriceTag.tsx
    = note: rule: component-names
    = note: because: A component file has the name of the component.
    error[must-be-named]: the full name of cart-row.tsx is cart-row, and it must be *.view or *.container
    --> src/features/cart/ui/cart-row.tsx
    = help: rename the file so that its name without the extension is *.view or *.container
    = note: rule: ui-file-names
    = note: because: A view only shows data. A container gets the data.
    error[must-contain]: the folder src/features/orderHistory has no file that matches index.ts
    --> src/features/orderHistory
    = help: add src/features/orderHistory/index.ts
    = note: rule: feature-entry
    = note: because: Other code imports a feature through its index file.
    error[must-be-named]: the folder src/features/orderHistory is not kebab-case; in kebab-case it is src/features/order-history
    --> src/features/orderHistory
    = help: name it src/features/order-history
    = note: rule: folder-names
    = note: because: One style for folders, so an import path is easy to guess.
    checked 8 files and 4 imports: 4 problems
    For what a code means, run `archangel docs must-be-named` or `archangel docs must-contain`.

    Each error says what is wrong and gives the correct name:

    • priceTag.tsx is not in PascalCase.
    • cart-row.tsx matches neither *.view nor *.container.
    • src/features/orderHistory has no index.ts.
    • src/features/orderHistory is not in kebab-case.
  2. Run the examples:

    Terminal
    $ npx archangel test
    pass folder-names rejected #1 src/features/UserProfile/index.ts
    pass component-names rejected #1 src/components/user-avatar.tsx
    pass component-exports rejected #1 src/components/Avatar.tsx
    pass ui-file-names rejected #1 src/features/cart/ui/cart-list.tsx
    pass feature-entry rejected #1 src/features/search/ui/search.view.tsx
    5 examples, none failed; 0 rules without a rejected example
Terminal
$ npx archangel rules
folder-names archangel.config.ts:5
because: One style for folders, so an import path is easy to guess.
folders in src/** must have names in kebab-case
6 folders · 1 problem
component-names archangel.config.ts:12
because: A component file has the name of the component.
files in src/components/**/*.tsx must have names in PascalCase
2 files · 1 problem
component-exports archangel.config.ts:19
because: Code imports a component by the name of its file.
files in src/components/**/*.tsx must export a name that matches {FileName}
2 files · 0 problems
ui-file-names archangel.config.ts:28
because: A view only shows data. A container gets the data.
files in src/features/*/ui/**/*.tsx must have full names that match *.view or *.container
4 files · 1 problem
feature-entry archangel.config.ts:35
because: Other code imports a feature through its index file.
each folder in src/features/* must have a file that matches index.ts
2 folders · 1 problem
Files that no rule judges: 1
src/features/cart/index.ts
Imports between repository files that no rule judges: 3
src/features/cart/index.ts:1 imports src/features/cart/ui/cart.container.tsx
src/features/cart/ui/cart.container.tsx:1 imports src/features/cart/ui/cart.view.tsx
src/features/cart/ui/cart.view.tsx:1 imports src/features/cart/ui/cart-row.tsx
Outside packages that no rule names: none.

The end of the output lists one file and three imports that no rule judges. Name rules judge files, not imports. To check the imports too, add a table.

  • Archangel has no rule for the first or the last character of a name, for example a name that starts with _.
  • Archangel does not check the names inside a file, such as names of functions. mustExport and mayNotExport check only exported names.