@sfdxy/mule-lint 1.7.1 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -7
- package/dist/bin/mule-lint-mcp.d.ts +3 -0
- package/dist/bin/mule-lint-mcp.d.ts.map +1 -0
- package/dist/bin/mule-lint-mcp.js +13 -0
- package/dist/bin/mule-lint-mcp.js.map +1 -0
- package/dist/package.json +6 -3
- package/dist/src/core/ComplexityCalculator.d.ts.map +1 -1
- package/dist/src/core/ComplexityCalculator.js +8 -8
- package/dist/src/core/ComplexityCalculator.js.map +1 -1
- package/dist/src/core/FileScanner.d.ts.map +1 -1
- package/dist/src/core/FileScanner.js +11 -12
- package/dist/src/core/FileScanner.js.map +1 -1
- package/dist/src/core/XPathHelper.d.ts.map +1 -1
- package/dist/src/core/XPathHelper.js +23 -23
- package/dist/src/core/XPathHelper.js.map +1 -1
- package/dist/src/core/XmlParser.js +1 -1
- package/dist/src/core/XmlParser.js.map +1 -1
- package/dist/src/core/YamlParser.d.ts.map +1 -1
- package/dist/src/core/YamlParser.js +1 -1
- package/dist/src/core/YamlParser.js.map +1 -1
- package/dist/src/engine/LintEngine.d.ts.map +1 -1
- package/dist/src/engine/LintEngine.js +6 -4
- package/dist/src/engine/LintEngine.js.map +1 -1
- package/dist/src/formatters/CsvFormatter.d.ts.map +1 -1
- package/dist/src/formatters/CsvFormatter.js +6 -4
- package/dist/src/formatters/CsvFormatter.js.map +1 -1
- package/dist/src/formatters/HtmlFormatter.d.ts.map +1 -1
- package/dist/src/formatters/HtmlFormatter.js +12 -7
- package/dist/src/formatters/HtmlFormatter.js.map +1 -1
- package/dist/src/formatters/SarifFormatter.d.ts.map +1 -1
- package/dist/src/formatters/SarifFormatter.js +20 -10
- package/dist/src/formatters/SarifFormatter.js.map +1 -1
- package/dist/src/formatters/TableFormatter.d.ts.map +1 -1
- package/dist/src/formatters/TableFormatter.js +1 -1
- package/dist/src/formatters/TableFormatter.js.map +1 -1
- package/dist/src/mcp/index.d.ts +13 -0
- package/dist/src/mcp/index.d.ts.map +1 -0
- package/dist/src/mcp/index.js +375 -0
- package/dist/src/mcp/index.js.map +1 -0
- package/dist/src/rules/api-led/ApiLedRules.d.ts.map +1 -1
- package/dist/src/rules/api-led/ApiLedRules.js +3 -1
- package/dist/src/rules/api-led/ApiLedRules.js.map +1 -1
- package/dist/src/rules/base/BaseRule.d.ts.map +1 -1
- package/dist/src/rules/base/BaseRule.js +1 -1
- package/dist/src/rules/base/BaseRule.js.map +1 -1
- package/dist/src/rules/complexity/FlowComplexityRule.d.ts.map +1 -1
- package/dist/src/rules/complexity/FlowComplexityRule.js +3 -5
- package/dist/src/rules/complexity/FlowComplexityRule.js.map +1 -1
- package/dist/src/rules/dataweave/DataWeaveRules.d.ts.map +1 -1
- package/dist/src/rules/dataweave/DataWeaveRules.js +5 -5
- package/dist/src/rules/dataweave/DataWeaveRules.js.map +1 -1
- package/dist/src/rules/dataweave/Java17DWErrorHandlingRule.d.ts +21 -0
- package/dist/src/rules/dataweave/Java17DWErrorHandlingRule.d.ts.map +1 -0
- package/dist/src/rules/dataweave/Java17DWErrorHandlingRule.js +162 -0
- package/dist/src/rules/dataweave/Java17DWErrorHandlingRule.js.map +1 -0
- package/dist/src/rules/documentation/FlowDescriptionRule.d.ts.map +1 -1
- package/dist/src/rules/documentation/FlowDescriptionRule.js +1 -1
- package/dist/src/rules/documentation/FlowDescriptionRule.js.map +1 -1
- package/dist/src/rules/documentation/MissingDocNameRule.d.ts.map +1 -1
- package/dist/src/rules/documentation/MissingDocNameRule.js +1 -1
- package/dist/src/rules/documentation/MissingDocNameRule.js.map +1 -1
- package/dist/src/rules/error-handling/CorrelationIdRule.d.ts.map +1 -1
- package/dist/src/rules/error-handling/CorrelationIdRule.js +1 -1
- package/dist/src/rules/error-handling/CorrelationIdRule.js.map +1 -1
- package/dist/src/rules/error-handling/GenericErrorRule.d.ts.map +1 -1
- package/dist/src/rules/error-handling/GenericErrorRule.js +2 -5
- package/dist/src/rules/error-handling/GenericErrorRule.js.map +1 -1
- package/dist/src/rules/error-handling/GlobalErrorHandlerRule.d.ts +1 -1
- package/dist/src/rules/error-handling/GlobalErrorHandlerRule.d.ts.map +1 -1
- package/dist/src/rules/error-handling/GlobalErrorHandlerRule.js +2 -2
- package/dist/src/rules/error-handling/GlobalErrorHandlerRule.js.map +1 -1
- package/dist/src/rules/error-handling/HttpStatusRule.d.ts.map +1 -1
- package/dist/src/rules/error-handling/HttpStatusRule.js +1 -1
- package/dist/src/rules/error-handling/HttpStatusRule.js.map +1 -1
- package/dist/src/rules/error-handling/MissingErrorHandlerRule.d.ts.map +1 -1
- package/dist/src/rules/error-handling/MissingErrorHandlerRule.js +9 -1
- package/dist/src/rules/error-handling/MissingErrorHandlerRule.js.map +1 -1
- package/dist/src/rules/experimental/ExperimentalRules.d.ts.map +1 -1
- package/dist/src/rules/experimental/ExperimentalRules.js +1 -1
- package/dist/src/rules/experimental/ExperimentalRules.js.map +1 -1
- package/dist/src/rules/http/HttpContentTypeRule.d.ts.map +1 -1
- package/dist/src/rules/http/HttpContentTypeRule.js +1 -1
- package/dist/src/rules/http/HttpContentTypeRule.js.map +1 -1
- package/dist/src/rules/http/HttpTimeoutRule.d.ts.map +1 -1
- package/dist/src/rules/http/HttpTimeoutRule.js +1 -1
- package/dist/src/rules/http/HttpTimeoutRule.js.map +1 -1
- package/dist/src/rules/http/HttpUserAgentRule.d.ts.map +1 -1
- package/dist/src/rules/http/HttpUserAgentRule.js +1 -1
- package/dist/src/rules/http/HttpUserAgentRule.js.map +1 -1
- package/dist/src/rules/index.d.ts +1 -1
- package/dist/src/rules/index.d.ts.map +1 -1
- package/dist/src/rules/index.js +7 -5
- package/dist/src/rules/index.js.map +1 -1
- package/dist/src/rules/logging/LoggerCategoryRule.js +2 -2
- package/dist/src/rules/logging/LoggerCategoryRule.js.map +1 -1
- package/dist/src/rules/logging/LoggerInUntilSuccessfulRule.js +1 -1
- package/dist/src/rules/logging/LoggerInUntilSuccessfulRule.js.map +1 -1
- package/dist/src/rules/logging/LoggerPayloadRule.d.ts.map +1 -1
- package/dist/src/rules/logging/LoggerPayloadRule.js +3 -3
- package/dist/src/rules/logging/LoggerPayloadRule.js.map +1 -1
- package/dist/src/rules/naming/FlowCasingRule.d.ts +1 -0
- package/dist/src/rules/naming/FlowCasingRule.d.ts.map +1 -1
- package/dist/src/rules/naming/FlowCasingRule.js +7 -1
- package/dist/src/rules/naming/FlowCasingRule.js.map +1 -1
- package/dist/src/rules/naming/FlowNamingRule.d.ts.map +1 -1
- package/dist/src/rules/naming/FlowNamingRule.js +12 -1
- package/dist/src/rules/naming/FlowNamingRule.js.map +1 -1
- package/dist/src/rules/naming/VariableNamingRule.d.ts.map +1 -1
- package/dist/src/rules/naming/VariableNamingRule.js +3 -3
- package/dist/src/rules/naming/VariableNamingRule.js.map +1 -1
- package/dist/src/rules/performance/AsyncErrorHandlerRule.d.ts.map +1 -1
- package/dist/src/rules/performance/AsyncErrorHandlerRule.js +1 -1
- package/dist/src/rules/performance/AsyncErrorHandlerRule.js.map +1 -1
- package/dist/src/rules/performance/LargeChoiceBlockRule.d.ts.map +1 -1
- package/dist/src/rules/performance/LargeChoiceBlockRule.js +1 -1
- package/dist/src/rules/performance/LargeChoiceBlockRule.js.map +1 -1
- package/dist/src/rules/performance/ScatterGatherRoutesRule.d.ts.map +1 -1
- package/dist/src/rules/performance/ScatterGatherRoutesRule.js +1 -1
- package/dist/src/rules/performance/ScatterGatherRoutesRule.js.map +1 -1
- package/dist/src/rules/security/HardcodedCredentialsRule.d.ts.map +1 -1
- package/dist/src/rules/security/HardcodedCredentialsRule.js +6 -6
- package/dist/src/rules/security/HardcodedCredentialsRule.js.map +1 -1
- package/dist/src/rules/security/HardcodedHttpRule.d.ts.map +1 -1
- package/dist/src/rules/security/HardcodedHttpRule.js +2 -6
- package/dist/src/rules/security/HardcodedHttpRule.js.map +1 -1
- package/dist/src/rules/security/InsecureTlsRule.d.ts.map +1 -1
- package/dist/src/rules/security/InsecureTlsRule.js +2 -2
- package/dist/src/rules/security/InsecureTlsRule.js.map +1 -1
- package/dist/src/rules/standards/ChoiceAntiPatternRule.d.ts.map +1 -1
- package/dist/src/rules/standards/ChoiceAntiPatternRule.js +8 -2
- package/dist/src/rules/standards/ChoiceAntiPatternRule.js.map +1 -1
- package/dist/src/rules/standards/DeprecatedComponentRule.d.ts.map +1 -1
- package/dist/src/rules/standards/DeprecatedComponentRule.js +1 -1
- package/dist/src/rules/standards/DeprecatedComponentRule.js.map +1 -1
- package/dist/src/rules/standards/DwlStandardsRule.d.ts.map +1 -1
- package/dist/src/rules/standards/DwlStandardsRule.js +1 -1
- package/dist/src/rules/standards/DwlStandardsRule.js.map +1 -1
- package/dist/src/rules/structure/StructureRules.d.ts.map +1 -1
- package/dist/src/rules/structure/StructureRules.js +6 -9
- package/dist/src/rules/structure/StructureRules.js.map +1 -1
- package/dist/src/rules/yaml/YamlRules.d.ts.map +1 -1
- package/dist/src/rules/yaml/YamlRules.js +19 -12
- package/dist/src/rules/yaml/YamlRules.js.map +1 -1
- package/dist/src/types/Config.d.ts.map +1 -1
- package/dist/src/types/Config.js +1 -5
- package/dist/src/types/Config.js.map +1 -1
- package/docs/README.md +95 -0
- package/docs/best-practices/documentation-standards.md +82 -0
- package/docs/best-practices/folder-structure.md +61 -0
- package/docs/best-practices/mulesoft-best-practices.md +902 -0
- package/docs/best-practices/rules-catalog.md +736 -0
- package/docs/linter/architecture.md +294 -0
- package/docs/linter/extending.md +426 -0
- package/docs/linter/folder-structure.md +251 -0
- package/docs/linter/images/html-report-dashboard.png +0 -0
- package/docs/linter/naming-conventions.md +300 -0
- package/docs/linter/rule-engine.md +532 -0
- package/docs/mcp-design.md +80 -0
- package/package.json +6 -3
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
# Rule Engine Documentation
|
|
2
|
+
|
|
3
|
+
> **Version:** 1.0.0
|
|
4
|
+
> **Purpose:** Comprehensive guide to the mule-lint rule engine internals, extensibility, and best practices.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
The Rule Engine is the heart of mule-lint. It orchestrates:
|
|
11
|
+
|
|
12
|
+
1. **Discovery** - Finding XML files to analyze
|
|
13
|
+
2. **Parsing** - Converting XML to queryable DOM
|
|
14
|
+
3. **Validation** - Running rules against the DOM
|
|
15
|
+
4. **Reporting** - Aggregating and formatting results
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Core Interfaces
|
|
20
|
+
|
|
21
|
+
### Rule Interface
|
|
22
|
+
|
|
23
|
+
Every rule must implement this contract:
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { Document } from '@xmldom/xmldom';
|
|
27
|
+
|
|
28
|
+
export type Severity = 'error' | 'warning' | 'info';
|
|
29
|
+
|
|
30
|
+
export type RuleCategory =
|
|
31
|
+
| 'naming'
|
|
32
|
+
| 'error-handling'
|
|
33
|
+
| 'security'
|
|
34
|
+
| 'logging'
|
|
35
|
+
| 'standards'
|
|
36
|
+
| 'performance'
|
|
37
|
+
| 'documentation';
|
|
38
|
+
|
|
39
|
+
export interface Issue {
|
|
40
|
+
line: number;
|
|
41
|
+
column?: number;
|
|
42
|
+
message: string;
|
|
43
|
+
ruleId: string;
|
|
44
|
+
severity: Severity;
|
|
45
|
+
suggestion?: string; // Optional fix suggestion
|
|
46
|
+
codeSnippet?: string; // Relevant code context
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ValidationContext {
|
|
50
|
+
filePath: string; // Absolute path to file
|
|
51
|
+
relativePath: string; // Path relative to project root
|
|
52
|
+
projectRoot: string; // Project root directory
|
|
53
|
+
config: RuleConfig; // Rule-specific configuration
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface Rule {
|
|
57
|
+
// Unique identifier (e.g., "MULE-001")
|
|
58
|
+
id: string;
|
|
59
|
+
|
|
60
|
+
// Human-readable name
|
|
61
|
+
name: string;
|
|
62
|
+
|
|
63
|
+
// Detailed description for documentation
|
|
64
|
+
description: string;
|
|
65
|
+
|
|
66
|
+
// Default severity (can be overridden by config)
|
|
67
|
+
severity: Severity;
|
|
68
|
+
|
|
69
|
+
// Category for grouping in reports
|
|
70
|
+
category: RuleCategory;
|
|
71
|
+
|
|
72
|
+
// Optional: documentation URL
|
|
73
|
+
docsUrl?: string;
|
|
74
|
+
|
|
75
|
+
// The validation function
|
|
76
|
+
validate(doc: Document, context: ValidationContext): Issue[];
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### RuleConfig Interface
|
|
81
|
+
|
|
82
|
+
Per-rule configuration:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
export interface RuleConfig {
|
|
86
|
+
enabled: boolean;
|
|
87
|
+
severity?: Severity; // Override default
|
|
88
|
+
options?: Record<string, unknown>; // Rule-specific options
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Base Rule Class
|
|
95
|
+
|
|
96
|
+
All rules should extend `BaseRule` for common utilities:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { Document, Node } from '@xmldom/xmldom';
|
|
100
|
+
import { Rule, Issue, Severity, RuleCategory, ValidationContext } from '@types';
|
|
101
|
+
import { XPathHelper } from '@core/XPathHelper';
|
|
102
|
+
|
|
103
|
+
export abstract class BaseRule implements Rule {
|
|
104
|
+
abstract id: string;
|
|
105
|
+
abstract name: string;
|
|
106
|
+
abstract description: string;
|
|
107
|
+
abstract severity: Severity;
|
|
108
|
+
abstract category: RuleCategory;
|
|
109
|
+
|
|
110
|
+
docsUrl?: string;
|
|
111
|
+
|
|
112
|
+
protected xpath = XPathHelper.getInstance();
|
|
113
|
+
|
|
114
|
+
abstract validate(doc: Document, context: ValidationContext): Issue[];
|
|
115
|
+
|
|
116
|
+
// --- Utility Methods ---
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Execute XPath and return matching nodes
|
|
120
|
+
*/
|
|
121
|
+
protected select(expression: string, doc: Document): Node[] {
|
|
122
|
+
return this.xpath.select(expression, doc);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Create an issue with consistent formatting
|
|
127
|
+
*/
|
|
128
|
+
protected createIssue(
|
|
129
|
+
node: Node,
|
|
130
|
+
message: string,
|
|
131
|
+
options?: {
|
|
132
|
+
suggestion?: string;
|
|
133
|
+
severity?: Severity;
|
|
134
|
+
}
|
|
135
|
+
): Issue {
|
|
136
|
+
return {
|
|
137
|
+
line: this.getLineNumber(node),
|
|
138
|
+
column: this.getColumnNumber(node),
|
|
139
|
+
message,
|
|
140
|
+
ruleId: this.id,
|
|
141
|
+
severity: options?.severity ?? this.severity,
|
|
142
|
+
suggestion: options?.suggestion,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get line number from node (xmldom stores this)
|
|
148
|
+
*/
|
|
149
|
+
protected getLineNumber(node: Node): number {
|
|
150
|
+
// xmldom stores line info in columnNumber/lineNumber
|
|
151
|
+
return (node as any).lineNumber ?? 1;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get column number from node
|
|
156
|
+
*/
|
|
157
|
+
protected getColumnNumber(node: Node): number | undefined {
|
|
158
|
+
return (node as any).columnNumber;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Check if a node has a specific attribute
|
|
163
|
+
*/
|
|
164
|
+
protected hasAttribute(node: Node, attrName: string): boolean {
|
|
165
|
+
const element = node as Element;
|
|
166
|
+
return element.hasAttribute?.(attrName) ?? false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get attribute value from node
|
|
171
|
+
*/
|
|
172
|
+
protected getAttribute(node: Node, attrName: string): string | null {
|
|
173
|
+
const element = node as Element;
|
|
174
|
+
return element.getAttribute?.(attrName) ?? null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## XPath Reference for MuleSoft
|
|
182
|
+
|
|
183
|
+
### Namespace Map
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
const MULE_NAMESPACES = {
|
|
187
|
+
'mule': 'http://www.mulesoft.org/schema/mule/core',
|
|
188
|
+
'http': 'http://www.mulesoft.org/schema/mule/http',
|
|
189
|
+
'https': 'http://www.mulesoft.org/schema/mule/https',
|
|
190
|
+
'ee': 'http://www.mulesoft.org/schema/mule/ee/core',
|
|
191
|
+
'doc': 'http://www.mulesoft.org/schema/mule/documentation',
|
|
192
|
+
'tls': 'http://www.mulesoft.org/schema/mule/tls',
|
|
193
|
+
'db': 'http://www.mulesoft.org/schema/mule/db',
|
|
194
|
+
'file': 'http://www.mulesoft.org/schema/mule/file',
|
|
195
|
+
'sftp': 'http://www.mulesoft.org/schema/mule/sftp',
|
|
196
|
+
'vm': 'http://www.mulesoft.org/schema/mule/vm',
|
|
197
|
+
'jms': 'http://www.mulesoft.org/schema/mule/jms',
|
|
198
|
+
'apikit': 'http://www.mulesoft.org/schema/mule/mule-apikit',
|
|
199
|
+
'api-gateway': 'http://www.mulesoft.org/schema/mule/api-gateway',
|
|
200
|
+
'secure-properties': 'http://www.mulesoft.org/schema/mule/secure-properties',
|
|
201
|
+
'os': 'http://www.mulesoft.org/schema/mule/os',
|
|
202
|
+
'batch': 'http://www.mulesoft.org/schema/mule/batch',
|
|
203
|
+
};
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Common XPath Patterns
|
|
207
|
+
|
|
208
|
+
| Purpose | XPath Expression |
|
|
209
|
+
|---------|------------------|
|
|
210
|
+
| All flows | `//mule:flow` |
|
|
211
|
+
| All sub-flows | `//mule:sub-flow` |
|
|
212
|
+
| Flow by name | `//mule:flow[@name='my-flow']` |
|
|
213
|
+
| All loggers | `//mule:logger` |
|
|
214
|
+
| Loggers without category | `//mule:logger[not(@category)]` |
|
|
215
|
+
| All error handlers | `//mule:error-handler` |
|
|
216
|
+
| All on-error blocks | `//mule:on-error-continue \| //mule:on-error-propagate` |
|
|
217
|
+
| All HTTP listeners | `//http:listener` |
|
|
218
|
+
| All HTTP requests | `//http:request` |
|
|
219
|
+
| Choice blocks | `//mule:choice` |
|
|
220
|
+
| DataWeave transforms | `//ee:transform` |
|
|
221
|
+
| Set-variable | `//mule:set-variable` |
|
|
222
|
+
| Flows without error handler | `//mule:flow[not(mule:error-handler)]` |
|
|
223
|
+
| Attributes starting with http | `//*[@*[starts-with(., 'http:')]]` |
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Rule Implementation Examples
|
|
228
|
+
|
|
229
|
+
### Example 1: Flow Naming Rule
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import { Document, Node } from '@xmldom/xmldom';
|
|
233
|
+
import { BaseRule, Issue, ValidationContext } from '@types';
|
|
234
|
+
|
|
235
|
+
export class FlowNamingRule extends BaseRule {
|
|
236
|
+
id = 'MULE-002';
|
|
237
|
+
name = 'Flow Naming Convention';
|
|
238
|
+
description = 'Flows must end with "-flow", sub-flows with "-subflow"';
|
|
239
|
+
severity = 'warning' as const;
|
|
240
|
+
category = 'naming' as const;
|
|
241
|
+
|
|
242
|
+
validate(doc: Document, context: ValidationContext): Issue[] {
|
|
243
|
+
const issues: Issue[] = [];
|
|
244
|
+
|
|
245
|
+
// Check flows
|
|
246
|
+
const flows = this.select('//mule:flow', doc);
|
|
247
|
+
for (const flow of flows) {
|
|
248
|
+
const name = this.getAttribute(flow, 'name');
|
|
249
|
+
if (name && !name.endsWith('-flow')) {
|
|
250
|
+
issues.push(this.createIssue(
|
|
251
|
+
flow,
|
|
252
|
+
`Flow "${name}" should end with "-flow"`,
|
|
253
|
+
{ suggestion: `Rename to "${name}-flow"` }
|
|
254
|
+
));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Check sub-flows
|
|
259
|
+
const subflows = this.select('//mule:sub-flow', doc);
|
|
260
|
+
for (const subflow of subflows) {
|
|
261
|
+
const name = this.getAttribute(subflow, 'name');
|
|
262
|
+
if (name && !name.endsWith('-subflow')) {
|
|
263
|
+
issues.push(this.createIssue(
|
|
264
|
+
subflow,
|
|
265
|
+
`Sub-flow "${name}" should end with "-subflow"`,
|
|
266
|
+
{ suggestion: `Rename to "${name}-subflow"` }
|
|
267
|
+
));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return issues;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Example 2: Logger Category Rule
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import { Document, Node } from '@xmldom/xmldom';
|
|
280
|
+
import { BaseRule, Issue, ValidationContext } from '@types';
|
|
281
|
+
|
|
282
|
+
export class LoggerCategoryRule extends BaseRule {
|
|
283
|
+
id = 'MULE-006';
|
|
284
|
+
name = 'Logger Category Required';
|
|
285
|
+
description = 'All loggers must have a category attribute for proper log filtering';
|
|
286
|
+
severity = 'warning' as const;
|
|
287
|
+
category = 'logging' as const;
|
|
288
|
+
|
|
289
|
+
validate(doc: Document, context: ValidationContext): Issue[] {
|
|
290
|
+
const issues: Issue[] = [];
|
|
291
|
+
|
|
292
|
+
const loggers = this.select('//mule:logger[not(@category)]', doc);
|
|
293
|
+
|
|
294
|
+
for (const logger of loggers) {
|
|
295
|
+
const message = this.getAttribute(logger, 'message') || 'unknown';
|
|
296
|
+
issues.push(this.createIssue(
|
|
297
|
+
logger,
|
|
298
|
+
`Logger is missing 'category' attribute`,
|
|
299
|
+
{
|
|
300
|
+
suggestion: `Add category="com.myorg.${context.relativePath.replace(/\//g, '.')}"`
|
|
301
|
+
}
|
|
302
|
+
));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return issues;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Example 3: Hardcoded HTTP Rule (Security)
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import { Document, Node, Element } from '@xmldom/xmldom';
|
|
314
|
+
import { BaseRule, Issue, ValidationContext } from '@types';
|
|
315
|
+
|
|
316
|
+
export class HardcodedHttpRule extends BaseRule {
|
|
317
|
+
id = 'MULE-004';
|
|
318
|
+
name = 'Hardcoded HTTP URLs';
|
|
319
|
+
description = 'HTTP/HTTPS URLs should use properties, not hardcoded values';
|
|
320
|
+
severity = 'error' as const;
|
|
321
|
+
category = 'security' as const;
|
|
322
|
+
|
|
323
|
+
private readonly URL_PATTERN = /^https?:\/\//i;
|
|
324
|
+
private readonly ALLOWED_PATTERNS = [
|
|
325
|
+
/\$\{[^}]+\}/, // Property placeholders ${...}
|
|
326
|
+
/\#\[[^\]]+\]/, // DataWeave expressions #[...]
|
|
327
|
+
];
|
|
328
|
+
|
|
329
|
+
validate(doc: Document, context: ValidationContext): Issue[] {
|
|
330
|
+
const issues: Issue[] = [];
|
|
331
|
+
|
|
332
|
+
// Find all elements with attributes containing http:// or https://
|
|
333
|
+
const allElements = doc.getElementsByTagName('*');
|
|
334
|
+
|
|
335
|
+
for (let i = 0; i < allElements.length; i++) {
|
|
336
|
+
const element = allElements[i];
|
|
337
|
+
const attrs = element.attributes;
|
|
338
|
+
|
|
339
|
+
for (let j = 0; j < attrs.length; j++) {
|
|
340
|
+
const attr = attrs[j];
|
|
341
|
+
const value = attr.value;
|
|
342
|
+
|
|
343
|
+
if (this.URL_PATTERN.test(value) && !this.isAllowedPattern(value)) {
|
|
344
|
+
issues.push(this.createIssue(
|
|
345
|
+
element,
|
|
346
|
+
`Hardcoded URL "${this.truncate(value)}" found in attribute "${attr.name}"`,
|
|
347
|
+
{
|
|
348
|
+
suggestion: 'Use property placeholder: ${http.url}'
|
|
349
|
+
}
|
|
350
|
+
));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return issues;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
private isAllowedPattern(value: string): boolean {
|
|
359
|
+
return this.ALLOWED_PATTERNS.some(pattern => pattern.test(value));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private truncate(value: string, maxLen = 50): string {
|
|
363
|
+
return value.length > maxLen ? value.substring(0, maxLen) + '...' : value;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## Rule Registration
|
|
371
|
+
|
|
372
|
+
Rules are registered in `src/rules/index.ts`:
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
import { Rule } from '@types';
|
|
376
|
+
|
|
377
|
+
// Import all rules
|
|
378
|
+
import { FlowNamingRule } from './naming/FlowNamingRule';
|
|
379
|
+
import { GlobalErrorHandlerRule } from './error-handling/GlobalErrorHandlerRule';
|
|
380
|
+
import { MissingErrorHandlerRule } from './error-handling/MissingErrorHandlerRule';
|
|
381
|
+
import { HardcodedHttpRule } from './security/HardcodedHttpRule';
|
|
382
|
+
import { HttpStatusRule } from './error-handling/HttpStatusRule';
|
|
383
|
+
import { LoggerCategoryRule } from './logging/LoggerCategoryRule';
|
|
384
|
+
import { CorrelationIdRule } from './error-handling/CorrelationIdRule';
|
|
385
|
+
import { ChoiceAntiPatternRule } from './standards/ChoiceAntiPatternRule';
|
|
386
|
+
import { GenericErrorRule } from './error-handling/GenericErrorRule';
|
|
387
|
+
import { DwlStandardsRule } from './standards/DwlStandardsRule';
|
|
388
|
+
|
|
389
|
+
// Rule registry
|
|
390
|
+
export const RULES: Rule[] = [
|
|
391
|
+
new FlowNamingRule(),
|
|
392
|
+
new GlobalErrorHandlerRule(),
|
|
393
|
+
new MissingErrorHandlerRule(),
|
|
394
|
+
new HardcodedHttpRule(),
|
|
395
|
+
new HttpStatusRule(),
|
|
396
|
+
new LoggerCategoryRule(),
|
|
397
|
+
new CorrelationIdRule(),
|
|
398
|
+
new ChoiceAntiPatternRule(),
|
|
399
|
+
new GenericErrorRule(),
|
|
400
|
+
new DwlStandardsRule(),
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
// Export for external use
|
|
404
|
+
export * from './naming/FlowNamingRule';
|
|
405
|
+
export * from './error-handling/GlobalErrorHandlerRule';
|
|
406
|
+
// ... etc
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## Engine Processing Flow
|
|
412
|
+
|
|
413
|
+
```mermaid
|
|
414
|
+
flowchart TD
|
|
415
|
+
START[Start Scan] --> CONFIG[Load Configuration]
|
|
416
|
+
CONFIG --> DISCOVER[Discover XML Files]
|
|
417
|
+
DISCOVER --> PARSE[Parse XML to DOM]
|
|
418
|
+
PARSE --> CHECK{Parse Error?}
|
|
419
|
+
CHECK -- Yes --> CRITICAL[Report Critical Error]
|
|
420
|
+
CHECK -- No --> RULES[Run All Rules]
|
|
421
|
+
RULES --> COLLECT[Collect Issues]
|
|
422
|
+
COLLECT --> AGGREGATE[Aggregate Results]
|
|
423
|
+
AGGREGATE --> FORMAT[Format Output]
|
|
424
|
+
FORMAT --> EXIT[Set Exit Code]
|
|
425
|
+
CRITICAL --> COLLECT
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## Configuration
|
|
431
|
+
|
|
432
|
+
### `.mulelintrc.json` Schema
|
|
433
|
+
|
|
434
|
+
```json
|
|
435
|
+
{
|
|
436
|
+
"$schema": "./node_modules/mule-lint/config-schema.json",
|
|
437
|
+
"rules": {
|
|
438
|
+
"MULE-001": { "enabled": true, "severity": "error" },
|
|
439
|
+
"MULE-002": { "enabled": true, "severity": "warning" },
|
|
440
|
+
"MULE-003": {
|
|
441
|
+
"enabled": true,
|
|
442
|
+
"options": {
|
|
443
|
+
"excludePatterns": ["*-api-main"]
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
"include": ["src/main/mule/**/*.xml"],
|
|
448
|
+
"exclude": ["**/test/**", "**/*.munit.xml"],
|
|
449
|
+
"formatters": {
|
|
450
|
+
"default": "table",
|
|
451
|
+
"ci": "sarif"
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Rule-Specific Options
|
|
457
|
+
|
|
458
|
+
Some rules support additional options:
|
|
459
|
+
|
|
460
|
+
| Rule | Option | Type | Default | Description |
|
|
461
|
+
|------|--------|------|---------|-------------|
|
|
462
|
+
| MULE-002 | `flowSuffix` | string | `-flow` | Expected flow name suffix |
|
|
463
|
+
| MULE-002 | `subflowSuffix` | string | `-subflow` | Expected sub-flow suffix |
|
|
464
|
+
| MULE-003 | `excludePatterns` | string[] | `[]` | Flow name patterns to exclude |
|
|
465
|
+
| MULE-006 | `requirePrefix` | string | `null` | Required category prefix |
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## Testing Rules
|
|
470
|
+
|
|
471
|
+
### Test Structure
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
import { FlowNamingRule } from '../rules/naming/FlowNamingRule';
|
|
475
|
+
import { parseXml } from '../core/XmlParser';
|
|
476
|
+
|
|
477
|
+
describe('FlowNamingRule', () => {
|
|
478
|
+
const rule = new FlowNamingRule();
|
|
479
|
+
|
|
480
|
+
it('should pass for correctly named flow', () => {
|
|
481
|
+
const xml = `
|
|
482
|
+
<mule xmlns="http://www.mulesoft.org/schema/mule/core">
|
|
483
|
+
<flow name="my-process-flow">
|
|
484
|
+
<logger message="test"/>
|
|
485
|
+
</flow>
|
|
486
|
+
</mule>
|
|
487
|
+
`;
|
|
488
|
+
const doc = parseXml(xml);
|
|
489
|
+
const issues = rule.validate(doc, mockContext);
|
|
490
|
+
|
|
491
|
+
expect(issues).toHaveLength(0);
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('should fail for incorrectly named flow', () => {
|
|
495
|
+
const xml = `
|
|
496
|
+
<mule xmlns="http://www.mulesoft.org/schema/mule/core">
|
|
497
|
+
<flow name="myProcess">
|
|
498
|
+
<logger message="test"/>
|
|
499
|
+
</flow>
|
|
500
|
+
</mule>
|
|
501
|
+
`;
|
|
502
|
+
const doc = parseXml(xml);
|
|
503
|
+
const issues = rule.validate(doc, mockContext);
|
|
504
|
+
|
|
505
|
+
expect(issues).toHaveLength(1);
|
|
506
|
+
expect(issues[0].ruleId).toBe('MULE-002');
|
|
507
|
+
expect(issues[0].message).toContain('myProcess');
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
## Performance Specifications
|
|
515
|
+
|
|
516
|
+
| Metric | Target |
|
|
517
|
+
|--------|--------|
|
|
518
|
+
| Files per second | > 100 |
|
|
519
|
+
| Memory per file | < 10MB |
|
|
520
|
+
| Rule execution | < 50ms per rule |
|
|
521
|
+
| Total for 100 files | < 5 seconds |
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## Exit Codes
|
|
526
|
+
|
|
527
|
+
| Code | Meaning |
|
|
528
|
+
|------|---------|
|
|
529
|
+
| 0 | No errors or warnings |
|
|
530
|
+
| 1 | At least one error found |
|
|
531
|
+
| 2 | Configuration error |
|
|
532
|
+
| 3 | Critical error (parse failure) |
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Mule Lint MCP Server Design
|
|
2
|
+
|
|
3
|
+
## Executive Summary
|
|
4
|
+
This document outlines the strategy for exposing `mule-lint` capabilities via the Model Context Protocol (MCP). By wrapping the linter in an MCP server, we enable AI agents (like Claude, IDE assistants, etc.) to autonomously discover linting rules, validate MuleSoft projects, and retrieve detailed rule documentation without needing to shell out or parse CLI text output.
|
|
5
|
+
|
|
6
|
+
## Architecture Decision: Monorepo vs. Separate Repo
|
|
7
|
+
**Recommendation: Same Repo (Monorepo)**
|
|
8
|
+
|
|
9
|
+
We should implement the MCP server directly in the `mule-lint` repository, likely under `src/mcp` or as a separate package if this was a workspace.
|
|
10
|
+
* **Pros**: Direct access to `src/core`, `src/rules`, and types without publishing/installing packages. Easier to keep rule definitions and agent-exposed descriptions in sync.
|
|
11
|
+
* **Cons**: Adds a dependency on `@modelcontextprotocol/sdk` to the main repo (or requires a build split).
|
|
12
|
+
|
|
13
|
+
## Features & Capabilities
|
|
14
|
+
|
|
15
|
+
### 1. Tools
|
|
16
|
+
Tools allow the agent to perform actions.
|
|
17
|
+
|
|
18
|
+
| Tool Name | Arguments | Description |
|
|
19
|
+
| :--- | :--- | :--- |
|
|
20
|
+
| `run_lint_analysis` | `projectPath` (string) | Runs the scanning engine on a specified directory. Returns a JSON summary of errors, warnings, and code references. |
|
|
21
|
+
| `validate_snippet` | `code` (string), `type` (xml/dwl) | quickly validates a small chunk of code without a full project structure. Useful for agents generating code on the fly. |
|
|
22
|
+
| `get_rule_details` | `ruleId` (string) | Returns the full documentation, examples, and rationale for a specific rule (e.g., `MULE-001`). |
|
|
23
|
+
|
|
24
|
+
### 2. Resources
|
|
25
|
+
Resources allow the agent to read context.
|
|
26
|
+
|
|
27
|
+
| Resource URI | Description |
|
|
28
|
+
| :--- | :--- |
|
|
29
|
+
| `mule-lint://rules` | A JSON list of all registered rules, their categories, and severity levels. |
|
|
30
|
+
| `mule-lint://config/schema` | The JSON schema for `.mule-lintrc` to help agents author valid configurations. |
|
|
31
|
+
| `mule-lint://docs/{slug}` | Access internal documentation (e.g., `best-practices`, `architecture`, `naming`). |
|
|
32
|
+
|
|
33
|
+
### 3. Prompts
|
|
34
|
+
Pre-defined prompts to help users interacting with the agent.
|
|
35
|
+
|
|
36
|
+
| Prompt Name | Description |
|
|
37
|
+
| :--- | :--- |
|
|
38
|
+
| `analyze_current_project` | "Run a comprehensive analysis on this project and summarize the top 3 critical issues." |
|
|
39
|
+
| `explain_violation` | "Here is an error I found: {{ErrorString}}. Explain why this is bad and how to fix it using `get_rule_details`." |
|
|
40
|
+
|
|
41
|
+
## Implementation Phases
|
|
42
|
+
|
|
43
|
+
### Phase 1: Foundation (The "Reader" Agent)
|
|
44
|
+
> [!NOTE]
|
|
45
|
+
> **Status**: Completed. Available on NPM as `@sfdxy/mule-lint`.
|
|
46
|
+
|
|
47
|
+
*Goal: Allow an agent to see what rules exist and run a scan.*
|
|
48
|
+
- [x] Install `@modelcontextprotocol/sdk`.
|
|
49
|
+
- [x] Create `McpServer` class in `src/mcp/index.ts`.
|
|
50
|
+
- [x] Implement `mule-lint://rules` resource.
|
|
51
|
+
- [x] Implement `run_lint_analysis` tool (wrapping `LintEngine`).
|
|
52
|
+
- [x] Add `stdio` transport for local running.
|
|
53
|
+
|
|
54
|
+
### Phase 2: Interactive Context (The "Helper" Agent)
|
|
55
|
+
> [!NOTE]
|
|
56
|
+
> **Status**: Completed.
|
|
57
|
+
|
|
58
|
+
*Goal: Allow the agent to understand *why* things failed.*
|
|
59
|
+
- [x] Implement `get_rule_details` tool.
|
|
60
|
+
- [x] Expose internal documentation of rules via MCP.
|
|
61
|
+
- [x] Add `validate_snippet` for real-time code generation checks.
|
|
62
|
+
|
|
63
|
+
### Phase 3: Remediation (The "Fixer" Agent)
|
|
64
|
+
> [!NOTE]
|
|
65
|
+
> **Status**: Partially Completed. `apply_fix` deferred. Enhanced reporting added.
|
|
66
|
+
|
|
67
|
+
*Goal: Allow the agent to automatically fix issues.*
|
|
68
|
+
- [ ] Implement `apply_fix` tool (Deferred: requires AST write support).
|
|
69
|
+
- [x] Enhanced error reporting with precise range/location data (Added column/suggestion).
|
|
70
|
+
|
|
71
|
+
## Libraries & Dependencies
|
|
72
|
+
* **Core**: `@modelcontextprotocol/sdk`
|
|
73
|
+
* **Transport**: Stdio (standard input/output) for local CLI integration.
|
|
74
|
+
* **Runtime**: Node.js (uses existing project runtime).
|
|
75
|
+
|
|
76
|
+
## Agent Workflow Example
|
|
77
|
+
1. **Discovery**: Agent connects and reads `mule-lint://rules` to know what it is looking for.
|
|
78
|
+
2. **Action**: User asks "Check my code". Agent calls `run_lint_analysis(cwd)`.
|
|
79
|
+
3. **Context**: Agent sees error `DW-004`. Agent calls `get_rule_details("DW-004")` to read the "Java 17 DataWeave" docs.
|
|
80
|
+
4. **Result**: Agent explains the error to the user with specific context from the official rule definitions.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sfdxy/mule-lint",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.2",
|
|
4
4
|
"description": "Static analysis tool for MuleSoft applications - supports humans, AI agents, and CI/CD pipelines",
|
|
5
5
|
"author": "Avinava",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,14 +14,16 @@
|
|
|
14
14
|
"cli"
|
|
15
15
|
],
|
|
16
16
|
"bin": {
|
|
17
|
-
"mule-lint": "./dist/bin/mule-lint.js"
|
|
17
|
+
"mule-lint": "./dist/bin/mule-lint.js",
|
|
18
|
+
"mule-lint-mcp": "./dist/bin/mule-lint-mcp.js"
|
|
18
19
|
},
|
|
19
20
|
"main": "./dist/index.js",
|
|
20
21
|
"types": "./dist/index.d.ts",
|
|
21
22
|
"files": [
|
|
22
23
|
"dist",
|
|
23
24
|
"README.md",
|
|
24
|
-
"LICENSE"
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"docs"
|
|
25
27
|
],
|
|
26
28
|
"scripts": {
|
|
27
29
|
"build": "tsc",
|
|
@@ -37,6 +39,7 @@
|
|
|
37
39
|
"release": "git tag v$(node -p 'require(\"./package.json\").version') && git push origin v$(node -p 'require(\"./package.json\").version')"
|
|
38
40
|
},
|
|
39
41
|
"dependencies": {
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
40
43
|
"@xmldom/xmldom": "^0.8.10",
|
|
41
44
|
"chalk": "^4.1.2",
|
|
42
45
|
"commander": "^11.1.0",
|