Concepts: Rules and conditions: A rule has a set of files and one condition, and reads as a sentence. Tables, shorthands, folder rules and plugin rules are forms of rules. # Rules and conditions > A rule has a set of files and one condition, and reads as a sentence. Tables, shorthands, folder rules and plugin rules are forms of rules. Each test in a config is a **rule**. A rule has a set of files and one **condition**: what those files must do, or must not do. You can read each rule as a sentence: ```ts rule('no-react-in-shared', { files: 'shared', mayNotImport: { packages: ['react'] } }) ``` “Files in `shared` may not import `react`.” ## The forms of a rule | Form | Write it as | Use it for | | ------------------------- | ------------------------------------------------------ | ------------------------------------------------------ | | Table | `mayImport(name, rows, options)` | What each set of files may import. Each row is a rule. | | Rule | `rule(name, { files, condition })` | One decision about a set of files. | | Folder rule | `rule(name, { folders, condition })` | Names, contents or presence of folders. | | Rule of several sentences | `rule(name, { sentences })` | One decision that needs more than one condition. | | Shorthand | `layers(parts)`, `isolate(part)`, `entry(part, files)` | Common import rules, in one line. | | Plugin rule | `rule(name, { files, plugin, options })` | A condition that you write in TypeScript. | This config has a shorthand of each type, a rule that follows chains of imports, and a rule of two sentences: archangel.config.ts ```ts import { defineConfig } from '@enhance-eng/archangel'; export default defineConfig({ parts: { app: 'src/app/**', feature: 'src/features/{feature}/**', shared: 'src/shared/**', }, rules: ({ entry, layers, rule }) => [ layers(['app', 'feature', 'shared'], { because: 'Code depends only on the layers below it.', examples: { rejected: ['src/shared/a.ts imports src/app/main.ts'] }, }), entry('feature', 'index.ts', { examples: { rejected: ['src/app/main.ts imports src/features/cart/cart.ts'] }, }), rule('no-react-in-shared', { files: 'shared', mayNotImport: { packages: ['react'] }, transitively: true, because: 'Shared code also runs on the server.', examples: { rejected: ['src/shared/a.ts imports src/shared/b.ts imports react'] }, }), rule('no-utils', { sentences: [ { folders: 'src/**/utils', mustNotExist: true }, { files: 'src/**/utils.ts', mustNotExist: true }, ], because: 'A name says what the code does, not that it is a utility.', examples: { rejected: ['src/shared/utils.ts'] }, }), ], }); ``` `archangel rules` writes each rule as the sentences that it checks. Read this output to see what your config says: Terminal ```ansi $ npx archangel rules layers(app, feature, shared) archangel.config.ts:10 because: Code depends only on the layers below it. files in feature may not import app files in shared may not import app, feature 3 files · 3 imports judged · 0 problems · 0 exceptions entry(feature) archangel.config.ts:15 files in feature may be imported from outside their instance only through its entry files index.ts 2 files · 2 imports judged · 0 problems · 0 exceptions no-react-in-shared archangel.config.ts:19 because: Shared code also runs on the server. files in shared may not import react, directly or through other files 1 file · 0 imports judged · 0 problems · 0 exceptions no-utils archangel.config.ts:27 because: A name says what the code does, not that it is a utility. no folder may be in src/**/utils files in src/**/utils.ts must not exist 0 folders · 0 problems Files that no rule judges: 1 src/app/main.ts Outside packages that no rule names: none. ``` * `layers(...)` becomes one sentence for each part after the first. * A shorthand without a `name` gets a name from the rule itself, such as `layers(app, feature, shared)`. * `no-utils` finds 0 folders in this repository, because no folder is named `utils`. That is correct for a rule that says “must not exist”. Archangel shows the count, so that you can see it. ## The conditions A rule has exactly one condition. The conditions are in three groups. | Group | Conditions | A rule reports | | --------- | ------------------------------------------------------------------ | ------------------------------------------------------------------ | | Imports | `mayNotImport`, `mayOnlyImport`, `mayOnlyBeImportedBy`, `noCycles` | An import, or a cycle of imports. | | Structure | `mustNotExist`, `mustBeNamed`, `mustContain` | A file or folder that exists, has the wrong name, or lacks a file. | | Exports | `mustExport`, `mayNotExport`, `mustOnlyReExport` | A file that exports the wrong names, or has code of its own. | Each condition is in [the conditions reference](/archangel/reference/conditions/), with an example. ### Make an import condition narrower An import condition can apply to some imports only. Add an option such as `names`, `specifier`, `instance`, `dependency` or `within`: ```ts rule('no-effects-in-view', { files: 'view', mayNotImport: { packages: ['react'] }, names: ['useEffect'], examples: { rejected: [{ 'src/view/a.ts': "import { useEffect } from 'react';" }] }, }) ``` “Files in `view` may not import `useEffect` from `react`.” The [import options reference](/archangel/reference/import-options/) lists each option. ### Follow chains of imports A rule with `transitively: true` refuses a chain of imports, not only a direct import. In the config above, `no-react-in-shared` refuses `shared/a.ts → shared/b.ts → react`. The report shows the shortest chain. `stopAt` ends a chain at some files. A transitive rule counts only runtime imports, because a chain of type imports never runs. ## Options for each rule | Option | What it does | | ---------- | ------------------------------------------------------------------------------------------------- | | `name` | The name in each report, exception and baseline entry. Write it first in `rule('name', { ... })`. | | `because` | Your reason. Each report shows it. Tell the next reader why the rule exists. | | `link` | A link that each report shows. | | `imports` | `'all'`, `'runtime'`, `'static'` or `'types'`: which imports the rule counts. | | `examples` | Code that the rule must accept, and code that it must reject. | A `mustNotExist` rule passes when it finds nothing, so its `files` or `folders` may match nothing. `no-utils` above uses both: a folder named `utils`, and a file named `utils.ts`. ## One rule, one decision Write one rule for each decision, and give it a `because`. A problem then names the decision that the code broke, and the reason for it. A rule with a reason is easy to keep, change or delete. A rule without a reason is hard to question.