Skip to content

Configuration

A config file exports a single object. It lives at the project root as shapelint.config.ts (or .js / .mjs).

Looking for one file that shows every option at once? See the Example config.

ts
import { defineConfig } from 'shapelint';

export default defineConfig({
  root: 'src',
  ignore: ['**/*.stories.tsx'],
  rules: [
    /* ... */
  ],
});

defineConfig is an identity helper that exists purely for type-checking and editor autocomplete. Exporting a bare object works too.

Config options

OptionTypeDefaultDescription
rootstring.Base directory for all rule globs, relative to the config.
ignorestring[][]Globs excluded from every rule (in addition to built-in defaults).
rulesRule[],The rules. Required, non-empty.

Built-in ignores (always applied): **/node_modules/**, **/dist/**, **/*.d.ts.

Rule ordering: rules are evaluated top to bottom. Every rule whose files glob matches a file applies to it, in array order, so independent conventions can stack on the same file (e.g. a pattern rule and a bodyOrder rule). For each file, matching rules run in turn until one reports an error; that error is shown and the remaining rules are skipped, so you fix one layer at a time. A rule that passes (or only warns) lets the next matching rule run too. Reading the array top to bottom tells you the order in which rules apply to any file.

Rule options

Define rules inline in the array, or in their own file with defineRule and import them.

OptionTypeDescription
namestringRule id. Appears in every diagnostic. Required.
filesstring | string[]Glob(s) relative to root. Required.
patternstring | string[]Code template(s) the file must match. A list gives several acceptable shapes; the file passes if it matches any one. See pattern matching.
filenamestringFilename convention. Overrides config naming.files. See filename rules.
foldernameNameConventionConvention for the immediate parent folder of matched files. Overrides config naming.folders.
excludestring[]Globs to skip within this rule (e.g. ['**/*.test.tsx']).
denyImports / allowImportsstring[]Import path boundaries. See import rules.
disallowElements / disallowCallsUsageBan[]Ban JSX elements / calls, require a replacement. See usage rules.
bodyOrder{ groups, in?, interGroupSpacing?, intraGroupSpacing? }Enforce statement order and blank-line spacing inside component/function bodies. See body order.
methodsMethodSpec | MethodSpec[]Per-member contract: required decorators (require) and exclusive/inclusive decorator groups (oneOf/anyOf). See member contracts.
memberOrderstring[]Order of class-member groups (['field','constructor','public-method','private-method']). See member contracts.
inject{ require?, allowParams? }What the constructor must take, and which types it may accept. See member contracts.
optionalNames / pairWithstring[] / { when, require }Optional & paired declarations whose names must line up. See member contracts.
decoratorArgs{ name, arg?, convention?, correlateToFilename? }[]Validate/correlate a decorator's positional argument. See member contracts.
matchNames / plurals'exact' | 'plural-aware' / Record<string,string>How names must line up; 'plural-aware' treats singular↔plural of one word as a match. See pattern matching.
barrel{ mustReexport }Index/barrel completeness, re-export every matching sibling. See structure.
containsRecord<string, Cardinality>Cardinality per folder ('exactly-one' / 'at-most-one' / 'at-least-one'). See structure.
allowExtraExportsboolean | string[]Default open. false closes the export surface to what the pattern declares; a glob list allows named exceptions.
requiresstring[]Sibling files that must exist next to each matched file (e.g. ['index.ts']).
belongsHere{ exportsType?, importsNone? }Inverse check, a file matching this signature must live under files, or it's flagged as misplaced.
semanticstring | { prompt, blocking? }Natural-language criterion for the AI tier. Runs only under check --semantic. See semantic rules.
descriptionstringOne line on the rule's intent. Shown by explain and in diagnostics.
hintstringFix guidance shown at the bottom of each of this rule's diagnostics.
severity'error' | 'warn' | 'off'Default 'error'. 'off' skips the rule entirely.

A rule needs at least one enforcing option (pattern, filename, an import rule, a usage rule, requires, or belongsHere) to do anything.

sameType, unmatched, output

Config-level options beyond root / ignore / rules:

OptionTypeDescription
sameTypestring[][]Classes of equal type spellings, e.g. [['React.FC', 'FC']]. Folded before comparison.
unmatched'ignore' | 'warn' | 'error'What to do with a source file no rule claims. 'error' forces every file to be governed. Default 'ignore'.
sourceFilesstring[]The file universe scanned by unmatched / belongsHere. Default ['**/*.ts', '**/*.tsx'].
output'human' | 'agent' | 'json' | 'github'Default output format; the --format flag overrides it.
naming{ files?, folders?, ignore? }Repo-wide file/folder naming defaults. See naming.
judgefunction | { command }AI adapter for semantic rules. See semantic rules. Shapelint bundles no SDK.

Rules in their own file

For a larger config, put each rule in its own file under .shapelint/rules/ and import it. Wrap it in defineRule so the file is type-checked on its own:

ts
// .shapelint/rules/ui-component.ts
import { defineRule } from 'shapelint';

export default defineRule({
  name: 'ui-component',
  files: 'components/ui/**/*.tsx',
  pattern: `
    interface IProps {}

    const $Name: React.FC<IProps> = () => {}

    export default $Name
  `,
  filename: '$Name.tsx',
});

Import the rule files into the config's rules array. You can mix imported and inline rules freely: the array just holds Rule objects:

ts
// shapelint.config.ts
import { defineConfig } from 'shapelint';
import uiComponent from './.shapelint/rules/ui-component';
import blockComponent from './.shapelint/rules/block-component';

export default defineConfig({
  root: 'src',
  rules: [
    uiComponent, // imported
    blockComponent, // imported
    { name: 'hook', files: 'hooks/**/*.ts', filename: 'use-*' }, // inline, fine
  ],
});

defineConfig / defineRule are optional

Both are identity helpers: they return their argument unchanged, purely for editor autocomplete and type-checking. A plain object works too:

ts
// shapelint.config.ts, no helpers, no import
export default {
  root: 'src',
  rules: [{ name: 'ui', files: 'components/ui/**/*.tsx', pattern: '…' }],
};

Inline rules are type-checked by defineConfig regardless of whether you wrap them. defineRule earns its keep for a standalone rule file, where there's no surrounding config to check against, it gives that file the Rule type, so you get autocomplete and errors on typos.

A no-import, plain-object config is also how you run Shapelint against a repo that doesn't have Shapelint installed, see Getting started.

Organizing many rules

Keep the rule files under .shapelint/rules/ and import each one explicitly:

.shapelint/
  rules/
    ui-component.ts
    block-component.ts
    service.ts

.shapelint/ is Shapelint's own directory, it also holds generated artifacts (baseline.json, check-cache.json, semantic-cache.json), and Shapelint ignores everything under it by default, so your rule files there are never linted or counted as unmatched.

Rule files are explicitly imported, never auto-discovered. That's deliberate: the rules array is the single source of order, so the sequence in which rules apply to a file, and short-circuit on the first error, stays visible in one place. There is no config that "picks up" files by convention.

Sharing rules across projects

Because a rule is just a Rule object, you can publish a set of them as an npm package and import them like any other module, no plugin system, just ES imports:

ts
// shapelint.config.ts
import { defineConfig } from 'shapelint';
import { uiComponent, service } from 'shapelint-rules-acme';

export default defineConfig({
  root: 'src',
  rules: [
    uiComponent,
    service,
    // …plus this project's own rules
  ],
});

A shared rule file/package is plain data, so a consuming project can also spread in a base set and append overrides:

ts
import { baseRules } from 'shapelint-rules-acme';

export default defineConfig({
  root: 'src',
  rules: [...baseRules, { name: 'project-specific', files: '…', pattern: '…' }],
});

Severity and exit codes

ResultExit code
No violations, or warn only0
One or more error findings1
Config missing / invalid2

Released under the MIT License.