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.
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
| Option | Type | Default | Description |
|---|---|---|---|
root | string | . | Base directory for all rule globs, relative to the config. |
ignore | string[] | [] | Globs excluded from every rule (in addition to built-in defaults). |
rules | Rule[] | , | 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.
| Option | Type | Description |
|---|---|---|
name | string | Rule id. Appears in every diagnostic. Required. |
files | string | string[] | Glob(s) relative to root. Required. |
pattern | string | 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. |
filename | string | Filename convention. Overrides config naming.files. See filename rules. |
foldername | NameConvention | Convention for the immediate parent folder of matched files. Overrides config naming.folders. |
exclude | string[] | Globs to skip within this rule (e.g. ['**/*.test.tsx']). |
denyImports / allowImports | string[] | Import path boundaries. See import rules. |
disallowElements / disallowCalls | UsageBan[] | 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. |
methods | MethodSpec | MethodSpec[] | Per-member contract: required decorators (require) and exclusive/inclusive decorator groups (oneOf/anyOf). See member contracts. |
memberOrder | string[] | 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 / pairWith | string[] / { 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. |
contains | Record<string, Cardinality> | Cardinality per folder ('exactly-one' / 'at-most-one' / 'at-least-one'). See structure. |
allowExtraExports | boolean | string[] | Default open. false closes the export surface to what the pattern declares; a glob list allows named exceptions. |
requires | string[] | 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. |
semantic | string | { prompt, blocking? } | Natural-language criterion for the AI tier. Runs only under check --semantic. See semantic rules. |
description | string | One line on the rule's intent. Shown by explain and in diagnostics. |
hint | string | Fix 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:
| Option | Type | Description |
|---|---|---|
sameType | string[][] | 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'. |
sourceFiles | string[] | 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. |
judge | function | { 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:
// .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:
// 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:
// 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:
// 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:
import { baseRules } from 'shapelint-rules-acme';
export default defineConfig({
root: 'src',
rules: [...baseRules, { name: 'project-specific', files: '…', pattern: '…' }],
});Severity and exit codes
| Result | Exit code |
|---|---|
No violations, or warn only | 0 |
One or more error findings | 1 |
| Config missing / invalid | 2 |