Skip to content

Tables, rows and cells

Markdown for agents

A table is the main form of an architecture test. It says, for each set of files, what those files may import. You can read the whole import architecture of an app in one table.

archangel.config.ts
import { defineConfig } from '@enhance-eng/archangel';
export default defineConfig({
parts: {
ui: 'src/ui/**',
domain: 'src/domain/**',
db: 'src/db/**',
log: 'src/log.ts',
},
externals: {
driver: ['pg'],
view: ['react'],
},
rules: ({ mayImport }) => [
mayImport('app', {
ui: ['domain', 'view'],
domain: [],
db: ['domain', 'driver'],
log: [],
}, {
everyRowMayImport: ['log'],
because: 'The UI shows the domain. Only db talks to the database.',
}),
],
});
  • Table: one mayImport(...) call. Its name, here app, is the first word of each rule that it makes.
  • Row: one key of the table, such as ui. A row is a set of files: a part, a group, or a kind. Each row is a rule with the name <table>.<row>, here app.ui. Use this name in an exception, in the baseline and in off.
  • Cell: one name in the list of a row, such as 'domain' in ui: ['domain', 'view']. A cell is a row, a part, a group, an externals set or a path pattern. Archangel refuses a cell that is none of these.

Archangel takes each import of each file, one at a time:

  1. Find the row of the importing file. If the file has no row, no row of this table judges the import.

  2. Find what the import goes to. For a file in the repository, this is the file. For a package, this is the package name.

  3. Skip the packages that no cell names. A table judges an import of a package only if a cell of the table, or everyRowMayImport, names an externals set that holds the package. Otherwise no row judges the import.

  4. Permit the import if the row permits it. A row permits only:

    • the names in its list;
    • its own name, in the same instance. Use mayNotImportItself to remove this;
    • the names in everyRowMayImport.
  5. Refuse all other imports. The error names the row, the target and what the row permits. If the target has no row in the table, the message says “no row for” the target.

Here is the result for the table above:

Terminal
$ npx archangel check
error[may-import]: domain may not import the package `pg`, through "pg": domain may import only log, and itself
--> src/domain/order-store.ts:1:16
|
1 | import pg from 'pg';
| ^^^^ domain may import only log, and itself
|
= help: remove the import, or move the code to a file whose row permits the import
= note: rule: app.domain
= note: because: The UI shows the domain. Only db talks to the database.
checked 6 files and 9 imports: 1 problem
For what a code means, run `archangel docs may-import`.

The file order-store.ts is in domain. It imports pg. The externals set driver holds pg, and the cell driver is on the row db, so the table judges this import. The row domain does not list driver, so it refuses the import.

The row domain permits log, because everyRowMayImport adds log to each row.

order-row.ts imports date-fns. No externals set holds date-fns, so the table does not judge the import. archangel explain shows that each row skipped it:

Terminal
$ npx archangel explain src/ui/order-row.ts:2
src/ui/order-row.ts:2 'date-fns' -> package date-fns
file: part ui
target: package date-fns
skipped by:
app.ui date-fns is outside the repository
app.domain the file is not in domain
app.db the file is not in db
app.log the file is not in log
app.every-file-has-a-kind this rule checks files, not imports

archangel rules also lists it at the end, as a package that no rule names:

Terminal
$ npx archangel rules
app 4 rows archangel.config.ts:15
because: The UI shows the domain. Only db talks to the database.
app.ui: ui may import only domain, view, log, and itself
1 file · 2 imports judged · 0 problems · 0 exceptions
app.domain: domain may import only log, and itself
2 files · 3 imports judged · 1 problem · 0 exceptions
app.db: db may import only domain, driver, log, and itself
1 file · 2 imports judged · 0 problems · 0 exceptions
app.log: log may import only log, and itself
1 file · 0 imports judged · 0 problems · 0 exceptions
app.every-file-has-a-kind: a file in no row is not checked here; add `files` to check it
5 files · 0 problems
Not judged by any rule: 0 files, 0 imports between repository files.
Outside packages that no rule names: date-fns (1 import).

Each table makes one more rule: <table>.every-file-has-a-kind. It refuses a file in the table’s files that has no row.

  • Without files, the table has no set of files to check, so this rule checks nothing. archangel rules says this on the rule’s line: “a file in no row is not checked here; add files to check it”.
  • With files, each file in that set must have a row. A new file with no row is then an error, not a file that no rule judges. Kinds shows this.
  • Only source files count. The config file is in no table.
Error Cause
table-row-overlap Two rows find one file.
table-empty The table has no row, or its rows find no file.

A single row with 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 rule that is not a table row must have a rejected example. A table row does not need one:

  • archangel rules shows what each row judged;
  • a table whose rows find no file is the error table-empty;
  • a cell that names nothing is a config error.

You can still write examples for a table. They run against all its rows. Examples prove rules tells you how.

The table reference lists each option: kinds, files, within, everyRowMayImport, mayNotImportItself and the import options.