Tables, rows and cells
Markdown for agentsA 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.
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.', }), ],});The words
Section titled “The words”- Table: one
mayImport(...)call. Its name, hereapp, 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>, hereapp.ui. Use this name in an exception, in the baseline and inoff. - Cell: one name in the list of a row, such as
'domain'inui: ['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.
How a row decides
Section titled “How a row decides”Archangel takes each import of each file, one at a time:
-
Find the row of the importing file. If the file has no row, no row of this table judges the import.
-
Find what the import goes to. For a file in the repository, this is the file. For a package, this is the package name.
-
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. -
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
mayNotImportItselfto remove this; - the names in
everyRowMayImport.
-
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:
$ npx archangel checkerror[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 problemFor 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.
A package that no cell names
Section titled “A package that no cell names”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:
$ npx archangel explain src/ui/order-row.ts:2src/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 importsarchangel rules also lists it at the end, as a package that no rule names:
$ npx archangel rulesapp 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 file has a row
Section titled “Each file has a row”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 rulessays this on the rule’s line: “a file in no row is not checked here; addfilesto 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.
Problems with a table itself
Section titled “Problems with a table itself”| 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.
Tables need no written example
Section titled “Tables need no written example”A rule that is not a table row must have a rejected example. A table row does not need one:
archangel rulesshows 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.