{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/mokevnin/easement/main/easement.schema.json",
"title": "easement.yml",
"description": "Configuration of the easement architecture quality gate.",
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^x-": {
"description": "Free keys that only hold YAML anchors: `x-ban: &ban {...}`, then `<<: *ban` in a rule."
}
},
"properties": {
"extends": {
"description": "Config files merged underneath this one, relative to this file. Patterns stay relative to the root of the config being loaded.",
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
}
]
},
"version": {
"const": 1,
"description": "Config format version."
},
"components": {
"description": "Named parts of the system and the paths they cover.",
"type": "object",
"minProperties": 1,
"additionalProperties": {
"$ref": "#/definitions/component"
}
},
"common": {
"description": "Components every other component may depend on.",
"type": "array",
"items": {
"type": "string"
}
},
"rules": {
"description": "Per component: what it may depend on and use.",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/rule"
}
},
"layers": {
"description": "Components from the top layer to the bottom one: a layer may depend on any layer below it, never on one above.",
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"no_cycles": {
"description": "Fail on dependency cycles between components: true for all, or a list of components.",
"oneOf": [
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string"
}
}
]
},
"strict": {
"description": "Fail on checked files that belong to no component, except those matching `unowned`.",
"type": "boolean"
},
"exclude": {
"description": "Globs of paths to skip.",
"type": "array",
"items": {
"type": "string"
}
},
"baseline": {
"description": "Path of the baseline file, default easement.baseline.yml.",
"type": "string"
},
"unowned": {
"description": "Globs of files and directories that may belong to no component in strict mode.",
"type": "array",
"items": {
"type": "string"
}
},
"go": {
"description": "How the Go indexer builds the project.",
"type": "object",
"additionalProperties": false,
"properties": {
"tags": {
"description": "Build tags to index with, for files behind `//go:build`. Files behind other tags are not built, so they count as not covered.",
"type": "array",
"items": {
"type": "string"
}
}
}
},
"uncovered_ok": {
"description": "Source files that no indexer covers and that may stay that way (globs, each with the reason). Others are listed in the report as not covered, and the result is marked partial.",
"type": "array",
"items": {
"$ref": "#/definitions/listed"
}
},
"tests": {
"description": "`auto` puts the test files of every language (`*_test.go`, `*.test.ts`, `test_*.py`, `*Test.java`, …) into a component named `tests`, which `rules.tests` can restrict. Without such a rule tests may import anything.",
"enum": [
"auto"
]
}
},
"definitions": {
"patterns": {
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
},
"minItems": 1
}
]
},
"component": {
"type": "object",
"additionalProperties": false,
"required": [
"in"
],
"properties": {
"in": {
"description": "A glob or a list of globs naming a module: a directory or a file.",
"$ref": "#/definitions/patterns"
},
"public": {
"description": "When set, other components may import only these paths.",
"$ref": "#/definitions/patterns"
},
"because": {
"description": "Why the public API or the importer list exists; shown with their violations.",
"type": "string"
},
"except": {
"description": "Files matching these patterns do not belong to this component, even though `in` matches them.",
"$ref": "#/definitions/patterns"
},
"priority": {
"description": "Which component wins when several match a file: the higher priority, then the longer pattern. Default 0.",
"type": "integer"
},
"isolated": {
"description": "The directories matched by `in` (each pattern needs a `*`, no `**`) are instances that must not import each other.",
"type": "boolean"
},
"allow_importers": {
"description": "When set, only these components (and the component itself) may import it.",
"type": "array",
"items": {
"type": "string"
}
},
"public_symbols": {
"description": "When set, other components may use only these names from this component's packages. Needs an adapter that reports symbols (Go).",
"type": "array",
"items": {
"type": "string"
}
}
}
},
"rule": {
"type": "object",
"additionalProperties": false,
"properties": {
"may_depend_on": {
"description": "Components this one may depend on. An item may carry its own reason, shown by `explain`.",
"type": "array",
"items": {
"$ref": "#/definitions/listed"
}
},
"must_not_depend_on": {
"description": "Components that are never allowed, even if common or layers would allow them. An item may carry its own reason.",
"type": "array",
"items": {
"$ref": "#/definitions/listed"
}
},
"may_use_external": {
"description": "External modules this component may use; unset means no restriction. A pattern covers submodules: lodash covers lodash/fp. An item may carry its own reason.",
"type": "array",
"items": {
"$ref": "#/definitions/listed"
}
},
"forbid_external": {
"description": "External modules this component must not use. An item may carry its own reason.",
"type": "array",
"items": {
"$ref": "#/definitions/listed"
}
},
"max_fan_out": {
"description": "Most components this one may depend on.",
"type": "integer",
"minimum": 0
},
"max_instability": {
"description": "Highest instability, out / (in + out), from 0 (stable) to 1.",
"type": "number",
"minimum": 0,
"maximum": 1
},
"because": {
"description": "Why the rule exists; shown with its violations.",
"type": "string"
},
"must_depend_on": {
"description": "Components that every directory of this component must import at least once. An item may carry its own reason.",
"type": "array",
"items": {
"$ref": "#/definitions/listed"
}
},
"must_use_external": {
"description": "External modules that every directory of this component must use; a pattern covers submodules. An item may carry its own reason.",
"type": "array",
"items": {
"$ref": "#/definitions/listed"
}
},
"requires_file": {
"description": "File names or globs that every directory of this component must contain. An item may carry its own reason.",
"type": "array",
"items": {
"$ref": "#/definitions/listed"
}
},
"forbid_file": {
"description": "File names or globs that no file of this component may match. An item may carry its own reason.",
"type": "array",
"items": {
"$ref": "#/definitions/listed"
}
},
"requires_directive": {
"description": "Regular expressions that some file in every directory of this component must match, for dependencies written as comments (`//go:embed`, `goverter:converter`). An item may carry its own reason.",
"type": "array",
"items": {
"$ref": "#/definitions/listed"
}
},
"forbid_symbol": {
"description": "Names this component must not use, as `package.Name` or `import/path.Name` (`ent.Client`). Needs an adapter that reports symbols (Go). An item may carry its own reason.",
"type": "array",
"items": {
"$ref": "#/definitions/listed"
}
}
}
},
"listed": {
"description": "A name, or a name with a reason of its own that is shown instead of the reason of the whole rule.",
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"additionalProperties": false,
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
},
"because": {
"type": "string"
}
}
}
]
}
}
}