Skip to content

Some codebases say what a file is in its name or its folder: order.entity.ts, order.repository.ts, views/cart-page.tsx. A kind turns that name into a table row. You write one pattern, and archangel gives each file its kind. You do not declare a part for each kind.

A table’s kinds option is a path pattern with the capture {kind}. The value of the capture is the file’s kind.

Pattern File Kind
**/*.{kind}.ts src/orders/order.entity.ts entity
src/features/*/{kind}/** src/features/cart/views/page.tsx views
**/{kind}/**/*.{kind}.ts src/entity/order.entity.ts entity
  • {kind} can be in a pattern two times. 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 file that the pattern does not match has no kind.

This table uses the end of each file name as its kind. It also has one row, shared, that is a part. One table can mix kinds, parts and groups:

archangel.config.ts
import { defineConfig } from '@enhance-eng/archangel';
export default defineConfig({
parts: {
shared: 'src/shared/**',
},
rules: ({ mayImport }) => [
mayImport('orders', {
service: ['repository', 'entity', 'shared'],
repository: ['entity', 'shared'],
entity: ['shared'],
shared: [],
}, {
kinds: 'src/**/*.{kind}.ts',
files: 'src/**',
because: 'A service uses repositories. A repository stores entities. An entity knows only itself.',
}),
],
});

archangel explain shows the kind of a file. The file is in no part, but it has the kind service in the table orders:

Terminal
$ npx archangel explain src/orders/place-order.service.ts
src/orders/place-order.service.ts
part: no part; no file pattern matches it:
"shared" src/shared/**: the path does not start with "src/shared/"
kind: service in orders
groups: none
rules that apply: orders.service
orders.service: service may import only repository, entity, shared, and itself (judges its imports)
imports:
line 1 './order.entity' -> src/orders/order.entity.ts
line 2 './order.repository' -> src/orders/order.repository.ts
line 3 './order.helpers' -> src/orders/order.helpers.ts breaks orders.service
imported by: none

The repository has a file that the table does not know: order.helpers.ts. Its kind is helpers, and the table has no row for it. Two rules find it:

Terminal
$ npx archangel check
error[every-file-has-a-kind]: src/orders/order.helpers.ts has the kind helpers, and the table "orders" has no row for it: no row for helpers. To fix this, add a row, rename the file, or leave it out of the table's files with `not`
--> src/orders/order.helpers.ts
= help: add a row, rename the file, or leave it out of the table's files with `not`
= note: rule: orders.every-file-has-a-kind
= note: because: A service uses repositories. A repository stores entities. An entity knows only itself.
error[may-import]: service may not import helpers, through "./order.helpers": no row for helpers, and service may import only repository, entity, shared, and itself
--> src/orders/place-order.service.ts:3:28
|
3 | import { orderTotal } from './order.helpers';
| ^^^^^^^^^^^^^^^^^ service may import only repository, entity, shared, and itself
|
= help: remove the import, or move the code to a file whose row permits the import
= note: rule: orders.service
= note: because: A service uses repositories. A repository stores entities. An entity knows only itself.
checked 6 files and 7 imports: 2 problems
For what a code means, run `archangel docs every-file-has-a-kind` or `archangel docs may-import`.
  • orders.every-file-has-a-kind refuses the file itself. This rule checks only the files in the table’s files, here src/**. Without files, this rule checks nothing.
  • orders.service refuses the import of the file, because the row service does not list helpers, and the table has no row for it.

To fix it, decide where helpers belong in your architecture. Add a row, and name it in the rows that may import it:

archangel.config.ts
import { defineConfig } from '@enhance-eng/archangel';
export default defineConfig({
parts: {
shared: 'src/shared/**',
},
rules: ({ mayImport }) => [
mayImport('orders', {
service: ['repository', 'entity', 'helpers', 'shared'],
repository: ['entity', 'shared'],
entity: ['shared'],
helpers: ['entity', 'shared'],
shared: [],
}, {
kinds: 'src/**/*.{kind}.ts',
files: 'src/**',
because: 'A service uses repositories. A repository stores entities. An entity knows only itself.',
}),
],
});
Terminal
$ npx archangel check
checked 6 files and 7 imports: no problems

You can also rename the file so that it has a kind with a row, or leave it out of the table’s files with not.

Use When
A kind One pattern gives the names, for many folders or file endings. You add a kind when you add the first file with that ending, and the table tells you if it has no row.
A part The area has a name of its own, and rules other than this table use it. A rule’s files can name a part.

A kind has a meaning only in its table. A rule’s files cannot name a kind. To select the same files in a rule, write the pattern, for example files: 'src/**/*.entity.ts'.