Skip to content

A plugin adds conditions that you write in TypeScript. Use a plugin when the built-in conditions cannot say what you want. For a step-by-step guide, read Write a plugin.

import { defineCondition, definePlugin } from '@enhance-eng/archangel';
export default definePlugin({ name: 'team', schemaVersion: 1, conditions: [maxImports] });
Field Type What it is
name string The name of the plugin. By convention, the name of its package without archangel-plugin-.
schemaVersion 1 The version of the view that the plugin was written for.
conditions list The conditions of the plugin.

definePlugin and defineCondition change nothing at run time. They give the types.

Field Type Required What it is
name string yes The name that a rule uses after the plugin’s name, as team/max-imports.
description string yes One sentence that says what the condition requires. archangel rules uses it.
reads list of 'imports', 'importers', 'exports' yes The facts that check needs. Archangel sends only these facts.
optionsSchema JSON Schema no The schema of the options of a rule. Archangel refuses a rule whose options do not match it. Without it, a rule cannot have options.
docs string no Where to read more.
check function yes check(view, options). It returns a list of problems, or a promise of a list.

If a condition has a field that archangel does not know, archangel stops before the check and names the field.

Load the plugin in plugins, with the name that rules use. Then write plugin: '<plugin>/<condition>':

plugins: { team },
rules: ({ rule }) => [
rule('small-files', { files: 'feature', plugin: 'team/max-imports', options: { max: 2 }, examples: { ... } }),
],
Option What it is
files The files that the condition sees.
plugin The condition, as <plugin>/<condition>.
options The options. Archangel checks them against optionsSchema when the config loads.
name, because, link, examples The same as for each rule.

A plugin rule needs a rejected example, written as an object of files. One-line examples are refused, because the condition can read the text of a file.

check(view, options) gets the files of the rule’s files, sorted by path. This is a real call. The condition show prints its two arguments, and the rule has options: { max: 2 }:

plugins/dump.ts
import { defineCondition, definePlugin } from '@enhance-eng/archangel';
const show = defineCondition({
name: 'show',
description: 'Prints what check receives, and reports nothing.',
reads: ['imports', 'importers', 'exports'],
optionsSchema: { type: 'object', properties: { max: { type: 'integer' } } },
check(view, options) {
console.log(JSON.stringify({ options, view }, null, 2));
return [];
},
});
export default definePlugin({ name: 'dump', schemaVersion: 1, conditions: [show] });
Terminal
$ npx archangel check
checked 4 files and 5 imports: no problems
[dump] {
[dump] "options": {
[dump] "max": 2
[dump] },
[dump] "view": {
[dump] "schemaVersion": 1,
[dump] "files": [
[dump] {
[dump] "path": "src/features/cart/cart.ts",
[dump] "element": "feature",
[dump] "captures": {
[dump] "feature": "cart"
[dump] },
[dump] "selectors": [],
[dump] "imports": [
[dump] {
[dump] "specifier": "react",
[dump] "occurrence": 0,
[dump] "line": 1,
[dump] "column": 26,
[dump] "kind": "runtime",
[dump] "names": [
[dump] "useState"
[dump] ],
[dump] "file": null,
[dump] "package": "react"
[dump] },
[dump] {
[dump] "specifier": "../login",
[dump] "occurrence": 0,
[dump] "line": 2,
[dump] "column": 25,
[dump] "kind": "runtime",
[dump] "names": [
[dump] "session"
[dump] ],
[dump] "file": "src/features/login/index.ts",
[dump] "package": null
[dump] }
[dump] ],
[dump] "importers": [],
[dump] "exports": {
[dump] "names": [
[dump] "cart"
[dump] ],
[dump] "default": false,
[dump] "star": false
[dump] }
[dump] },
[dump] {
[dump] "path": "src/features/login/index.ts",
[dump] "element": "feature",
[dump] "captures": {
[dump] "feature": "login"
[dump] },
[dump] "selectors": [],
[dump] "imports": [],
[dump] "importers": [
[dump] "src/features/cart/cart.ts"
[dump] ],
[dump] "exports": {
[dump] "names": [
[dump] "session"
[dump] ],
[dump] "default": false,
[dump] "star": false
[dump] }
[dump] }
[dump] ]
[dump] }
[dump] }
  • options is the options object of the rule, without changes.
  • view.files has one record for each file in the rule’s files.
  • Each line that a plugin prints goes to standard error, or to the log of the editor, with the name of the plugin.
Field Type What it is
path string The path from the config folder.
element string or null The part of the file. null when the file is in no part.
captures object The values of the captures of the part. For src/features/{feature}/**, this is { "feature": "cart" }.
selectors string[] The groups that hold the file.
imports list of imports Only when the condition reads 'imports'.
importers string[] Only when the condition reads 'importers'. The paths of the files that import this file.
exports object Only when the condition reads 'exports'.
Field Type What it is
specifier string The text in the quotes, such as '../login'.
occurrence number 0 for the first import of this specifier in the file, 1 for the second, and so on.
line, column number The position of the specifier. Both start at 1. The column counts characters.
kind 'runtime' or 'type' 'type' for an import that the compiler removes, such as import type.
names string[] The names that the import brings in: a name, default or *.
file string or null The repository file that the import resolves to. null for a package, or when the import does not resolve.
package string or null The package that the import resolves to. null for a repository file.
Field Type What it is
names string[] The names that the file exports, default included.
default boolean true when the file has a default export.
star boolean true when the file has an export *.

A list of problems. An empty list means no problem.

{ file: 'src/features/cart/cart.ts', message: 'cart.ts imports 4 files, and the limit is 2' }
Field Required What it is
file yes A file of the view, or a folder that holds a file of the view.
message yes What is wrong, for the reader.
import no { specifier, occurrence }. The import that the problem is about. An exception comment above that import can then permit it.

Each problem is a diagnostic with the code plugin, like each other problem. The baseline and off work the same way.

A plugin rule never passes because its code did not run. Archangel shows plugin-failed, and the check fails, when:

  • a condition throws an error;
  • a condition reports a file that is not in its view;
  • the plugin process stops.

Plugins run in the Node process that loads your config, not in archangel’s own process. In the editor, archangel shows its own problems at once, and the problems of a plugin when the plugin answers.

@enhance-eng/archangel exports the types: View, FileRecord, ImportRecord, ExportsRecord, Violation, Condition, Plugin and Fact. With defineCondition, the type of each file in view.files has the facts of reads, so file.imports has no undefined.

A new field in the view does not change schemaVersion. A plugin reads only the fields that it knows, so a new release of archangel does not break it. schemaVersion changes only when the meaning of a field changes.

  • Name the package archangel-plugin-<name> or @scope/archangel-plugin.
  • Export the plugin as the default export, and a preset as a plain function next to it if you have one.