Extending Mule-Lint¶
Mule-Lint can be extended through its library API. Custom rules must be registered with a
LintEngine instance; configuration entries alone do not load executable modules.
Create a Per-File Rule¶
Extend BaseRule when a rule can decide from one parsed Mule XML document:
import { ALL_RULES, BaseRule, Issue, LintEngine, ValidationContext } from '@sfdxy/mule-lint';
export class RequiredCategoryRule extends BaseRule {
readonly id = 'ACME-001';
readonly name = 'Required logger category';
readonly description = 'Requires every logger to declare a category';
readonly severity = 'warning' as const;
readonly category = 'logging' as const;
validate(doc: Document, context: ValidationContext): Issue[] {
return this.select('//mule:logger[not(@category)]', doc).map((logger) =>
this.createIssue(logger, 'Logger is missing a category', {
filePath: context.filePath,
suggestion: 'Add a stable application-specific category.',
}),
);
}
}
const engine = new LintEngine({
rules: [...ALL_RULES, new RequiredCategoryRule()],
});
const report = await engine.scan('./my-mule-project');
this.select() uses the namespace-aware XPath helper. this.createIssue() supplies the rule
metadata and source location. Rule-specific options are available with
this.getOption(context, key, fallback).
Create a Project Rule¶
Extend ProjectRule when validation needs several files, non-XML assets, or project structure:
import { Issue, ProjectRule, ValidationContext } from '@sfdxy/mule-lint';
export class OrganizationLayoutRule extends ProjectRule {
readonly id = 'ACME-002';
readonly name = 'Organization layout';
readonly description = 'Checks organization-specific project layout';
readonly severity = 'warning' as const;
readonly category = 'structure' as const;
protected validateProject(context: ValidationContext): Issue[] {
if (context.files.some((file) => file.relativePath === 'pom.xml')) return [];
return [
this.createProjectIssue('Project is missing pom.xml', {
suggestion: 'Add the Maven project descriptor at the repository root.',
}),
];
}
}
The engine invokes project validation once per scan. A hybrid rule may extend BaseRule, implement
normal validate() logic, and also provide runProject(context) for a cross-file phase.
Configure a Registered Rule¶
Once registered, a rule can be disabled, have its severity changed, or receive options:
const engine = new LintEngine({
rules: [...ALL_RULES, new RequiredCategoryRule()],
config: {
rules: {
'ACME-001': {
enabled: true,
severity: 'error',
options: { requiredPrefix: 'com.acme' },
},
},
},
});
The CLI configuration supports extends, rules, include, exclude, defaultFormatter,
failOnWarning, and qualityGate. extends selects one built-in rule profile; explicit per-rule
configuration still wins. The CLI does not dynamically import custom rule files. The reserved keys
maxIssues currently warns and has no runtime effect.
A TypeScript rule must be compiled and passed to the engine, as above. For a check that is a single
XPath expression, customRulesPath avoids writing code at all — see
custom XPath rules. It never imports executable code: a
custom rule is an expression and a message template.
Test the Rule¶
Tests should cover both valid and invalid input and assert the rule ID, message, severity, and useful
location data. For a per-file rule, parse an XML string and call validate(). For project behavior,
prefer an engine scan over a fixture directory so the lifecycle and shared context are exercised.
Before publishing or contributing an extension, run:
npm run check
For built-in rules, also export the class, add exactly one instance to ALL_RULES, document it in
the rules catalog, and add focused regression tests.