Skip to content
LogoLogo

Folder structure

The folder-structure rule checks the shape of the file tree. Other rules check the dependencies between modules. This rule checks which files and folders exist and which names they have. It looks at all files in the entry folders, not only at modules. For the folders above the entry folders, such as the root folder, it knows their own files and folders, but not what is inside the other folders. It does not check those other folders.

The rule is in the nursery category, so it is off by default. Enable it in rules.

The folders option

The option folders is a list of entries. Each entry has a path and at least one check.

KeyWhat it does
pathGlobs of the folders to check. {name} captures a path segment.
requireGlobs of the files and folders that the folder must have.
allowGlobs of the names that the files and folders directly in the folder can have.
caseThe case of the names: kebab-case, camel-case, pascal-case or snake-case.
messageText that replaces the generic message of the violations of the entry.

A glob that starts with ! removes folders from the match. A path glob matches only the folder it names. The glob src/pages/{page} matches src/pages/home, but not src/pages/home/components. When several entries match one folder, all of them apply.

Require files

Use require to name the files that every matching folder must have. A glob in require is relative to the folder. It can contain /, and {a,b} means one of a and b.

export default defineConfig({
  rules: {
    "folder-structure": {
      level: "error",
      folders: [
        { path: "src/pages/{page}", require: ["codeowners.ts", "index.{ts,tsx}"] },
        { path: "packages/{p}", require: ["package.json", "README.md"] },
      ],
    },
  },
});

Each glob needs a match. The page src/pages/about has an error if it has no codeowners.ts.

Use captures

{name} in path is a capture. Use it in require to name a file after its folder. path must capture each name that require uses.

{ path: "src/components/{c}", require: "{c}.tsx" }

For the folder src/components/Button, the rule requires Button.tsx.

Companion files

A path can match a file. Then the checks apply to the folder of the file, with the captures of the file name. Use this to require a test and a story next to each component.

{
  path: ["src/components/**/{c}.tsx", "!**/*.{test,stories}.tsx"],
  require: ["{c}.test.tsx", "{c}.stories.tsx"],
}

The glob that starts with ! keeps the test and story files out of the match. Without it, Button.test.tsx would also need a Button.test.test.tsx.

Allow names

Use allow to list the names that a folder can have. It checks the files and folders directly in the folder. A glob that starts with ! forbids a name. A list of only such globs allows every other name.

// a module has no utils or helpers folder
{ path: "src/modules/{m}", allow: "!{utils,helpers}", message: "Put shared helpers in src/lib." }
 
// a feature has only these names
{ path: "src/features/{f}", allow: ["hooks", "components", "*.{ts,tsx}"] }

allow does not look at deeper levels. Add another entry for them.

Check the case of names

Use case to set the case of the names. The rule checks the part of the name before its first .. The rule reads Button.test.tsx as Button. The rule skips a name that starts with ..

{ path: "src/components", case: "pascal-case" }

Make an exception

A violation about a missing file, or about a file that is not a module, has no place for a comment. An exception to the structure is a decision about a folder, so it belongs in the config file:

  • Narrow path with a glob that starts with !: path: ["src/pages/{page}", "!src/pages/legacy"].
  • Use overrides to switch the rule off for an area, or to change its options. Oxcruise scopes a violation by the path that it is about.
overrides: [
  { files: "src/pages/legacy/**", rules: { "folder-structure": "off" } },
]

When the violation is about a module, a // oxcruise-ignore-file folder-structure comment in that module also works. See Inline ignore comments.

  • To keep sibling folders independent, use the feature-isolation recipe in Forbid rules.
  • To limit what other code can import from a folder, use visibility.