Gå til innholdet

Skrive regler

Regler er TypeScript-funksjoner som sjekker kodebasen din for ADR-samsvar. De ligger i tilhørende .rules.ts-filer ved siden av ADR-markdown-filer og kjøres når du utfører archgate check.

.archgate/adrs/
ARCH-001-command-structure.md # Beslutningen
ARCH-001-command-structure.rules.ts # De automatiserte sjekkene

Hver regelfil eksporterer et standard-objekt typet med satisfies RuleSet. Hver nøkkel i rules-objektet blir en regel-ID, og hver regel har en description og en asynkron check-funksjon som mottar et kontekstobjekt.

/// <reference path="../rules.d.ts" />
export default {
rules: {
"my-rule-id": {
description: "What this rule checks",
async check(ctx) {
// Your check logic here
},
},
},
} satisfies RuleSet;

En enkelt regelfil kan definere flere regler:

/// <reference path="../rules.d.ts" />
export default {
rules: {
"first-rule": {
description: "Checks one thing",
async check(ctx) {
// ...
},
},
"second-rule": {
description: "Checks another thing",
async check(ctx) {
// ...
},
},
},
} satisfies RuleSet;

ctx-objektet som sendes til hver check-funksjon gir muligheter for fillesing, søk og rapportering. Her er en detaljert referanse med eksempler.

En matrise med filstier som matcher ADR-ens files-glob fra frontmatter. Hvis ADR-en ikke har noe files-felt, inkluderer dette alle prosjektfiler.

for (const file of ctx.scopedFiles) {
const content = await ctx.readFile(file);
// Check content...
}

Bruk ctx.scopedFiles når regelen din bare skal gjelde filer ADR-en styrer. For eksempel vil en kommandostrukturregel avgrenset til src/commands/**/*.ts bare motta kommandofiler.

En matrise med filstier som skiller seg fra grunngrenen. Auto-detektert som standard, eller fylt fra --staged / --base <ref>. Nyttig for inkrementell sjekking og regler for avhengigheter mellom filer.

// Incremental checking -- only validate changed files
const filesToCheck = ctx.scopedFiles.filter((f) =>
ctx.changedFiles.includes(f)
);
// Cross-file dependency -- if file A changed, file B must also change
if (ctx.changedFiles.includes("config/database.yml")) {
if (!ctx.changedFiles.includes("deploy/manifest.yml")) {
ctx.report.violation({
message: "config changed but manifest was not bumped",
file: "config/database.yml",
});
}
}

Les innholdet i en fil som en streng. Stien er relativ til prosjektroten.

const content = await ctx.readFile("src/cli.ts");

Les og parse en JSON-fil. Returnerer unknown — cast den til forventet form.

const pkg = (await ctx.readJSON("package.json")) as {
dependencies?: Record<string, string>;
};

Søk i en enkelt fil med et regulært uttrykk. Returnerer en matrise med GrepMatch-objekter, hver med egenskapene file, line, column og content.

const matches = await ctx.grep(file, /console\.error\(/);
for (const match of matches) {
ctx.report.violation({
message: "Use logError() instead of console.error()",
file: match.file,
line: match.line,
});
}

Søk på tvers av flere filer som matcher et glob-mønster. Returnerer en flat matrise med GrepMatch-objekter fra alle matchende filer. Filer ignorert av .gitignore ekskluderes som standard. Sett respectGitignore: false i ADR-frontmatter for å inkludere dem.

const matches = await ctx.grepFiles(/TODO:/, "src/**/*.ts");
for (const match of matches) {
ctx.report.warning({
message: "TODO comment found",
file: match.file,
line: match.line,
});
}

Finn filer etter glob-mønster. Returnerer en matrise med filstier relative til prosjektroten. Filer ignorert av .gitignore ekskluderes som standard. Sett respectGitignore: false i ADR-frontmatter for å inkludere dem.

const testFiles = await ctx.glob("tests/**/*.test.ts");

Rapporteringsgrensesnittet med tre alvorlighetsgradsmetoder:

  • ctx.report.violation(detail) — feil-alvorlighetsgrad (exit-kode 1, blokkerer CI)
  • ctx.report.warning(detail) — advarsel-alvorlighetsgrad (logges, men blokkerer ikke)
  • ctx.report.info(detail) — informasjon (logges for synlighet)

Hver metode aksepterer et objekt med:

| Felt | Type | Påkrevd | Beskrivelse | | --------- | -------- | ------- | ---------------------------------------- | | message | string | Ja | Hva bruddet er | | file | string | Nei | Sti til den problematiske filen | | line | number | Nei | Linjenummer for bruddet | | fix | string | Nei | Foreslått løsning (vises til utvikleren) |

ctx.report.violation({
message: "Command file must export a register*Command function",
file: "src/commands/check.ts",
line: 5,
fix: "Add: export function registerCheckCommand(program: Command) { ... }",
});

Den absolutte stien til prosjektets rotkatalog. Nyttig når du trenger å konstruere absolutte stier.

Hver regel kan sette en standard alvorlighetsgrad i konfigurasjonen sin. Alvorlighetsgraden bestemmer hvordan brudd behandles:

| Alvorlighetsgrad | Exit-kode | Oppførsel | | ---------------- | --------- | --------------------------------- | | error | 1 | Blokkerer CI, må fikses | | warning | 0 | Logges, men blokkerer ikke | | info | 0 | Informasjon, logges for synlighet |

Sett alvorlighetsgraden i regeldefinisjonen:

export default {
rules: {
"my-rule": {
description: "...",
severity: "warning",
async check(ctx) {
// Violations from this rule are warnings, not errors
ctx.report.violation({ message: "..." });
},
},
},
} satisfies RuleSet;

Hvis severity utelates, er standardverdien error.

Du kan også rapportere med ulike alvorlighetsgrader innenfor samme regel ved å bruke ctx.report.violation(), ctx.report.warning() og ctx.report.info() direkte.

Hver regel har en kjøringstidsgrense på 30 sekunder. Hvis en regel overskrider denne grensen, behandles den som en feil. Dette forhindrer at uendelige sjekker blokkerer pipelinen.

Hold reglene raske ved å:

  • Bruke ctx.grepFiles() i stedet for å lese hver fil manuelt
  • Bruke Promise.all() for å sjekke filer parallelt
  • Avgrense regler med files-feltet i frontmatter for å begrense antall filer som behandles

fix-feltet er en valgfri streng som vises til utvikleren sammen med bruddmeldingen. Den beskriver hvilken handling som må utføres for å løse problemet. Fikser brukes ikke automatisk — de er veiledning.

ctx.report.violation({
message: `Unapproved dependency: "chalk"`,
file: "package.json",
fix: "Use styleText() from node:util instead of chalk",
});

Når den vises, kommer fiksen under bruddmeldingen:

ARCH-006/no-unapproved-deps
package.json
Unapproved dependency: "chalk"
Fix: Use styleText() from node:util instead of chalk
  1. Bruk Promise.all() for parallelle filsjekker. Når du sjekker flere filer uavhengig, prosesser dem parallelt i stedet for sekvensielt.

    // Good: parallel
    const checks = files.map(async (file) => {
    const content = await ctx.readFile(file);
    // ...
    });
    await Promise.all(checks);
    // Avoid: sequential
    for (const file of files) {
    const content = await ctx.readFile(file);
    // ...
    }
  2. Bruk ctx.changedFiles for inkrementell sjekking. ctx.changedFiles fylles automatisk med grenforskjellen (eller stagede filer med --staged). Filtrer ctx.scopedFiles mot den for å bare sjekke det som er endret, eller bruk den direkte for regler om avhengigheter mellom filer.

  3. Hold reglene fokusert på ett anliggende. En regel som sjekker både navnekonvensjoner og importmønstre bør deles i to regler med separate ID-er.

  4. Bruk ctx.grepFiles() fremfor manuell iterering. Når du søker etter et mønster på tvers av mange filer, er ctx.grepFiles() mer effektivt enn å lese hver fil og kjøre et regulært uttrykk.

  5. Gi handlingsbare fix-meldinger. En fiks som “Ikke gjør dette” er ikke nyttig. Fortell utvikleren nøyaktig hva som skal gjøres i stedet.

  6. Filtrer bort ikke-relevante filer tidlig. Hvis regelen din bare gjelder for bestemte filer innenfor omfanget, filtrer ctx.scopedFiles før prosessering:

    const commandFiles = ctx.scopedFiles.filter((f) => !f.endsWith("index.ts"));
  7. Håndter manglende filer elegant. Hvis regelen din leser en spesifikk fil som package.json, pakk lesingen i en try/catch og returner tidlig hvis filen ikke eksisterer.

Det finnes to måter å håndtere unntak i Archgate: undertrykkelse på motornivå (fungerer med alle regler automatisk) og egendefinerte direktiver på regelnivå (implementert av regelforfatteren for domenespesifikke opt-outs).

Archgate støtter inline archgate-ignore-kommentarer som undertrykker brudd uten å endre selve regelen. Motoren analyserer disse kommentarene og filtrerer matchende brudd før rapportering.

Neste-linje-undertrykkelse — undertrykker bruddet på den umiddelbart følgende linjen:

// archgate-ignore ARCH-006/no-unapproved-deps legacy-avhengighet, migrering planlagt for Q3
import chalk from "chalk";

Filnivå-undertrykkelse — undertrykker alle matchende brudd hvor som helst i filen:

// archgate-ignore-file ARCH-005/test-mirrors-src generert fil, ingen manuell test

Flere regler — stable kommentarer for å undertrykke mer enn én regel på samme linje:

// archgate-ignore ARCH-006/no-unapproved-deps legacy-avhengighet
// archgate-ignore ARCH-003/use-style-text tredjepartsbibliotek håndterer farger
import chalk from "chalk";

Sammenhengende undertrykkelseskommentarer sikter alle mot den første ikke-undertrykkelseslinjen etter blokken.

Formatet er ADR-ID/rule-id etterfulgt av en begrunnelse. Begrunnelsen er påkrevd — en undertrykkelse uten begrunnelse ignoreres og gir en advarsel:

[suppression] Suppression for ARCH-006/no-unapproved-deps is missing a reason src/foo.ts:1

Både // og # kommentarstiler støttes, slik at undertrykkelser fungerer i TypeScript, JavaScript, YAML, Python, shell-skript og andre filtyper reglene dine kan skanne.

For domenespesifikke opt-outs kan regelforfattere implementere sine egne kommentarbaserte direktiver inne i check-funksjonen. Dette mønsteret gir regelen full kontroll over direktivsyntaksen, plassering og validering.

// I din .rules.ts-fil:
async check(ctx) {
const files = await ctx.glob("src/components/**/*Connected.tsx");
for (const file of files) {
const content = await ctx.readFile(file);
// Støtt opt-out-direktiv øverst i filen
if (/^\/\/\s*@no-presentational:/u.test(content.trimStart())) continue;
// ... regellogikk som kan rapportere et brudd ...
ctx.report.violation({
message: "Manglende presentasjonskomponent",
file,
fix: 'Legg til "// @no-presentational: <begrunnelse>" øverst i filen for å gjøre opt-out',
});
}
}

Utvikleren gjør opt-out ved å legge til direktivet i filen sin:

// @no-presentational: denne komponenten bare omdirigerer, ingen UI å rendre
import { useNavigate } from "react-router";

| Tilnærming | Best for | Hvem kontrollerer | | --------------------- | ------------------------------------------------------- | ----------------------------- | | archgate-ignore | Ad-hoc-unntak for alle regler | Utvikleren som bruker regelen | | Egendefinert direktiv | Domenespesifikke opt-outs med strukturerte begrunnelser | Regelforfatteren |

Bruk archgate-ignore når en utvikler trenger å undertrykke et engangsbrudd. Bruk egendefinerte direktiver når opt-outen er et førsteklasses konsept i regelens domene — for eksempel å markere en komponent som med vilje uparet, eller en fil som automatisk generert.

  • Vanlige regelmønstre — Kopier-og-lim-inn-mønstre organisert etter kategori: avhengighetshåndtering, importrestriksjoner, filstruktur, kodekvalitet, databaseskjema og arkitekturgrenser.
  • Regel-API-referanse — Fullstendig referanse for alle regel-API-typer og -funksjoner.
  • CI-integrasjon — Koble archgate check til pipelinen din for å håndheve regler på hver PR.