Skip to content

Write Lint Rules

Lexicons can contribute three kinds of validation: imperative rules (TypeScript compiler API), declarative rules (pattern matching DSL), and post-synth checks (validate serialized output).

Imperative rules use the TypeScript compiler API to inspect AST nodes:

import type { LintRule, LintContext, LintDiagnostic } from "@intentius/chant";
export const myRule: LintRule = {
id: "MYD001",
severity: "warning",
category: "style",
description: "Example custom rule",
check(context: LintContext): LintDiagnostic[] {
// Your rule logic here
return [];
},
};

Return these from your plugin’s lintRules() method.

Declarative rules use the rule() builder for common patterns:

import type { RuleSpec } from "@intentius/chant";
const spec: RuleSpec = {
id: "MYD002",
severity: "warning",
category: "style",
description: "Resource names must be lowercase",
selector: "resource > property",
match: { pattern: /^[A-Z]/ },
message: "Property name '{node}' should be lowercase",
};

Return these from your plugin’s declarativeRules() method.

Post-synth checks validate the serialized output after the build pipeline completes:

import type { PostSynthCheck, PostSynthDiagnostic } from "@intentius/chant";
const myCheck: PostSynthCheck = {
id: "MYD010",
description: "Check output constraints",
check(context): PostSynthDiagnostic[] {
// Validate the serialized output
return [];
},
};

Return these from your plugin’s postSynthChecks() method.

Rule IDs follow the pattern {PREFIX}{CATEGORY}{NUMBER}:

  • Prefix — your lexicon’s rulePrefix (e.g. MY, AWS, K8S)
  • Category — single letter: D (declarative), S (style), C (correctness), E (evaluability)
  • Number — three-digit sequential number

Every post-synth check needs both positive and negative tests. Use a makeCtx helper:

function makeCtx(yaml: string) {
return {
outputs: new Map([["my-lexicon", yaml]]),
};
}
describe("MYD010: description", () => {
test("flags bad pattern", () => {
const yaml = `...bad yaml...`;
const diags = myCheck.check(makeCtx(yaml));
expect(diags.length).toBeGreaterThanOrEqual(1);
expect(diags[0].checkId).toBe("MYD010");
});
test("no diagnostic when correct", () => {
const yaml = `...good yaml...`;
const diags = myCheck.check(makeCtx(yaml));
expect(diags).toHaveLength(0);
});
});

Test imperative rules by constructing a minimal AST context and verifying the returned diagnostics array. Test declarative rules by running the rule() builder output against sample input strings.

See Testing Your Lexicon for the full testing guide, including patterns for all test file types.

With rules in place, add LSP & MCP providers for editor integration.