Tables
Markdown for agentsA table says what each set of files may import. It is the main form of an architecture test. For the ideas behind it, read Tables, rows and cells.
mayImport(name, rows, options)| Argument | Type | What it is |
|---|---|---|
name |
string | The name of the table. It has no : and no /. |
rows |
object | One row for each set of files. The value is the list of cells. |
options |
object | Optional, except kinds for a table of kinds. |
This table has rows of parts:
mayImport('backend', { routes: ['services'], services: ['domain', 'db'], db: ['domain', 'driver'], domain: [],}, { because: 'A route calls a service. Only db talks to the database.',})A row name means, in this order:
- the declared part or group with that name;
- in a table with
kinds, the kind with that name.
A row cannot name an externals set. Archangel refuses a row name that means nothing, and shows the nearest declared name.
Each row is a rule with the name <table>.<row>, for example backend.routes. Use this name in
off, in an exception comment and in the baseline.
A cell is one name in the list of a row. A cell can be:
- the name of a row of the same table;
- the name of a part, a group or an externals set;
- a path pattern, such as
'src/config.ts'or'src/generated/**'.
Archangel refuses a cell that is none of these.
What a row permits
Section titled “What a row permits”A row permits only:
- the cells in its list;
- its own name, in the same instance. A row of a part with a capture permits imports inside its
own instance, and refuses imports into other instances of that part. Use
mayNotImportItselfto remove this; - the cells in
everyRowMayImport.
The row refuses each other import into the repository. An import of a file that has no row gives the message “no row for …”.
A table judges a package only when a cell names it. A table judges an import of an outside
package only when a cell, or everyRowMayImport, names an externals set with that package. So a
row with an empty list can still import each package that no cell names. To refuse a package, put
it in an externals set and name the set in the cells of the rows that may use it.
Options
Section titled “Options”| Option | Type | Default | What it does |
|---|---|---|---|
kinds |
path pattern with {kind} |
none | The pattern that gives each file its kind. With it, a row name can be a kind. Read Kinds. |
files |
set of files | each file with a row | The importers that the table judges. With files, each file in the set must have a row. |
within |
set of files | the repository | The area of the table. The rows judge only imports whose target is in the area. |
everyRowMayImport |
list of cells | [] |
Cells that each row has, for example a file that all code may use. |
mayNotImportItself |
list of row names | [] |
Rows that may not import their own name, in the same instance or in a different instance. |
because |
string | none | The reason, which each problem shows. |
link |
string | none | A link, which each problem shows. |
examples |
object | none | Examples for the whole table. A row needs no written example. |
imports |
'all', 'runtime', 'static', 'types' |
'all' |
The kinds of import that each row counts. |
names, specifier, instance, dependency |
none | The same as for an import condition. Each row gets the option. Read Import options. |
A row that everyRowMayImport names cannot be in mayNotImportItself.
mayImport('model', { entity: ['value-object'], event: ['entity', 'value-object'], 'value-object': [],}, { files: 'domain', kinds: 'src/domain/*.{kind}.ts',})| Pattern | File | Kind |
|---|---|---|
**/*.{kind}.ts |
src/order/order.entity.ts |
entity |
src/features/*/{kind}/** |
src/features/cart/views/page.tsx |
views |
**/{kind}/**/*.{kind}.ts |
src/entity/user.entity.ts |
entity |
{kind} can be two times in a pattern. Then the two places must give the same name. A
declaration file counts .d.ts as one extension, so env.d.ts has no kind d.
A row name that is a declared part or group means that part or group, also when the kind pattern gives the same name.
The files option does not change the kind of a file. It only chooses the importers that the
table judges. A file outside files still has its kind when a file in files imports it.
Without files, the table judges each file that has a row. A file that has no row is not
checked, and archangel rules lists it under the files that no rule judges.
With files, each file in the set must have a row. A file without a row is a problem of the rule
<table>.every-file-has-a-kind, with the code
every-file-has-a-kind. To let a file
out, use not in files, or put <table>.every-file-has-a-kind in off.
within
Section titled “within”mayImport('domain-model', { entity: ['value-object'], 'value-object': [],}, { kinds: 'src/domain/*.{kind}.ts', within: 'domain',})With within, the table ignores each import whose target is outside the area. Here, a domain file
can import a package or a file in shared, and the table does not judge it.
The rules that a table makes
Section titled “The rules that a table makes”A table with the rows routes and services makes these rules:
| Rule | It refuses |
|---|---|
<table>.routes |
an import of a file in routes that its row does not permit |
<table>.services |
an import of a file in services that its row does not permit |
<table>.every-file-has-a-kind |
a file in the table’s files that has no row |
archangel rules shows each one as a sentence:
$ npx archangel rulesbackend 4 rows archangel.config.ts:14 because: A route calls a service. Only db talks to the database. The domain depends on nothing.
backend.routes: routes may import only services, and itself 2 files · 2 imports judged · 1 problem · 0 exceptions backend.services: services may import only domain, db, and itself 1 file · 2 imports judged · 0 problems · 0 exceptions backend.db: db may import only domain, driver, and itself 1 file · 2 imports judged · 0 problems · 0 exceptions backend.domain: domain may import only itself 3 files · 3 imports judged · 0 problems · 0 exceptions backend.every-file-has-a-kind: a file in no row is not checked here; add `files` to check it 7 files · 0 problems
model 3 rows archangel.config.ts:23 because: An entity does not know its events. A service publishes them.
model.entity: entity may import only value-object, and itself 1 file · 2 imports judged · 1 problem · 0 exceptions model.event: event may import only entity, value-object, and itself 1 file · 1 import judged · 0 problems · 0 exceptions model.value-object: value-object may import only itself 1 file · 0 imports judged · 0 problems · 0 exceptions model.every-file-has-a-kind: every file of model has a row 3 files · 0 problems
no-cycles archangel.config.ts:33 because: Files that import each other cannot change one at a time. files in src/** may not import each other in a cycle 7 files · 0 problems
Not judged by any rule: 0 files, 0 imports between repository files.Outside packages that no rule names: none.Errors in a table
Section titled “Errors in a table”| Code | When |
|---|---|
table-empty |
The table has no row, or its rows find no file, so it checks nothing. |
table-row-overlap |
Two rows find one file. |
rule-unknown-element |
A row or a cell names nothing. |
config-invalid |
A name has a : or a /, or an option has a wrong value. |
One row that finds no file is not an error, because you can write a row before its first file
exists. archangel check and archangel rules list that row. A table in a preset that checks
nothing gives one line in the summary, not an error.
A table in JSON
Section titled “A table in JSON”In a JSON config, a table is an object with "kind": "mayImport", a name and its rows:
{ "$schema": "./node_modules/@enhance-eng/archangel/archangel.schema.json", "parts": { "feature": "src/features/{feature}/**", "shared": "src/shared/**" }, "rules": [ { "kind": "mayImport", "name": "app", "rows": { "feature": ["shared"], "shared": [] }, "because": "Shared code never depends on a feature." } ]}The options have the same names. For a table of kinds, add "kinds": "src/features/*/{kind}/**".