Skip to content

Commit eee78a8

Browse files
feat: Add “List Code Analyzer Rules” tool with selector validation and provider wiring (#347)
* rules tool with filter and coverage * 100 cverage
1 parent 107eefb commit eee78a8

File tree

11 files changed

+783
-6
lines changed

11 files changed

+783
-6
lines changed
Binary file not shown.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import {CodeAnalyzerConfigFactory} from "../factories/CodeAnalyzerConfigFactory.js";
2+
import {EnginePluginsFactory} from "../factories/EnginePluginsFactory.js";
3+
import {TelemetryService} from "@salesforce/mcp-provider-api";
4+
import {CodeAnalyzer, Rule, RuleSelection} from "@salesforce/code-analyzer-core";
5+
import {getMessage} from "../messages.js";
6+
import {getErrorMessage} from "../utils.js";
7+
import {ErrorCapturer} from "../listeners/ErrorCapturer.js";
8+
import {TelemetryListenerFactory} from "../factories/TelemetryListenerFactory.js";
9+
import {EnginePlugin} from "@salesforce/code-analyzer-engine-api";
10+
import * as Constants from "../constants.js";
11+
12+
type ListRulesActionOptions = {
13+
configFactory: CodeAnalyzerConfigFactory
14+
enginePluginsFactory: EnginePluginsFactory
15+
telemetryService?: TelemetryService
16+
}
17+
18+
export type ListRulesInput = {
19+
selector: string
20+
};
21+
22+
23+
export type ListRulesOutput = {
24+
status: string
25+
rules?: {
26+
name: string
27+
engine: string
28+
severity: number
29+
tags: string[]
30+
description: string
31+
resources: string[]
32+
}[]
33+
};
34+
35+
export interface ListRulesAction {
36+
exec(input: ListRulesInput): Promise<ListRulesOutput>;
37+
}
38+
39+
export class ListRulesActionImpl implements ListRulesAction {
40+
private readonly configFactory: CodeAnalyzerConfigFactory;
41+
private readonly enginePluginsFactory: EnginePluginsFactory;
42+
private readonly telemetryService?: TelemetryService;
43+
44+
public constructor(options: ListRulesActionOptions) {
45+
this.configFactory = options.configFactory;
46+
this.enginePluginsFactory = options.enginePluginsFactory;
47+
this.telemetryService = options.telemetryService;
48+
}
49+
50+
public async exec(input: ListRulesInput): Promise<ListRulesOutput> {
51+
let analyzer: CodeAnalyzer;
52+
try {
53+
analyzer = new CodeAnalyzer(this.configFactory.create());
54+
} catch (e) {
55+
return {
56+
status: getMessage('errorCreatingConfig', getErrorMessage(e))
57+
};
58+
}
59+
60+
const errorCapturer: ErrorCapturer = new ErrorCapturer();
61+
errorCapturer.listen(analyzer);
62+
63+
const telemetryListener = new TelemetryListenerFactory().create(this.telemetryService)
64+
telemetryListener.listen(analyzer)
65+
66+
const enginePlugins: EnginePlugin[] = this.enginePluginsFactory.create();
67+
try {
68+
for (const enginePlugin of enginePlugins) {
69+
await analyzer.addEnginePlugin(enginePlugin);
70+
}
71+
} catch (e) {
72+
return {
73+
status: getMessage('errorAddingEngine', getErrorMessage(e))
74+
};
75+
}
76+
77+
const ruleSelection: RuleSelection = await analyzer.selectRules([input.selector]);
78+
this.emitEngineTelemetry(ruleSelection, enginePlugins.flatMap(p => p.getAvailableEngineNames()));
79+
80+
const rules = ruleSelection.getEngineNames().flatMap(e => ruleSelection.getRulesFor(e)).map(r => {
81+
return {
82+
name: r.getName(),
83+
engine: r.getEngineName(),
84+
severity: r.getSeverityLevel(),
85+
description: r.getDescription(),
86+
resources: r.getResourceUrls(),
87+
tags: r.getTags()
88+
}
89+
});
90+
return {
91+
status: "success",
92+
rules
93+
};
94+
}
95+
96+
97+
private emitEngineTelemetry(ruleSelection: RuleSelection, coreEngineNames: string[]): void {
98+
const selectedEngineNames: Set<string> = new Set(ruleSelection.getEngineNames());
99+
for (const coreEngineName of coreEngineNames) {
100+
if (!selectedEngineNames.has(coreEngineName)) {
101+
continue;
102+
}
103+
if (this.telemetryService) {
104+
this.telemetryService.sendEvent(Constants.TelemetryEventName, {
105+
source: Constants.TelemetrySource,
106+
sfcaEvent: Constants.McpTelemetryEvents.ENGINE_SELECTION,
107+
engine: coreEngineName,
108+
ruleCount: ruleSelection.getRulesFor(coreEngineName).length
109+
})
110+
}
111+
}
112+
}
113+
}

packages/mcp-provider-code-analyzer/src/constants.ts

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,97 @@ export const TelemetrySource = "MCP"
44
export const McpTelemetryEvents = {
55
ENGINE_SELECTION: 'engine_selection',
66
ENGINE_EXECUTION: 'engine_execution'
7-
}
7+
}
8+
9+
export const ENGINE_NAMES = [
10+
'eslint',
11+
'regex',
12+
'retire-js',
13+
'flow',
14+
'pmd',
15+
'cpd',
16+
'sfge'
17+
] as const;
18+
19+
export type EngineName = typeof ENGINE_NAMES[number];
20+
21+
export const ENGINE_NAME_SET: ReadonlySet<EngineName> = new Set(ENGINE_NAMES);
22+
23+
// Severities
24+
export const SEVERITY_NAMES = [
25+
'Critical',
26+
'High',
27+
'Moderate',
28+
'Low',
29+
'Info'
30+
] as const;
31+
32+
export type SeverityName = typeof SEVERITY_NAMES[number];
33+
34+
export const SEVERITY_NUMBERS = [1, 2, 3, 4, 5] as const;
35+
36+
export type SeverityNumber = typeof SEVERITY_NUMBERS[number];
37+
38+
export const SEVERITY_NAME_TO_NUMBER: Readonly<Record<SeverityName, SeverityNumber>> = {
39+
Critical: 1,
40+
High: 2,
41+
Moderate: 3,
42+
Low: 4,
43+
Info: 5
44+
} as const;
45+
46+
export const SEVERITY_NUMBER_TO_NAME: Readonly<Record<SeverityNumber, SeverityName>> = {
47+
1: 'Critical',
48+
2: 'High',
49+
3: 'Moderate',
50+
4: 'Low',
51+
5: 'Info'
52+
} as const;
53+
54+
// Tags (case-insensitive by convention; these are canonical-cased constants)
55+
export const GENERAL_TAGS = [
56+
'Recommended',
57+
'Custom',
58+
'All'
59+
] as const;
60+
61+
export type GeneralTag = typeof GENERAL_TAGS[number];
62+
63+
export const GENERAL_TAG_SET: ReadonlySet<GeneralTag> = new Set(GENERAL_TAGS);
64+
65+
export const CATEGORIES = [
66+
'BestPractices',
67+
'CodeStyle',
68+
'Design',
69+
'Documentation',
70+
'ErrorProne',
71+
'Security',
72+
'Performance'
73+
] as const;
74+
75+
export type Category = typeof CATEGORIES[number];
76+
77+
export const CATEGORY_SET: ReadonlySet<Category> = new Set(CATEGORIES);
78+
79+
export const LANGUAGES = [
80+
'Apex',
81+
'CSS',
82+
'HTML',
83+
'JavaScript',
84+
'TypeScript',
85+
'Visualforce',
86+
'XML'
87+
] as const;
88+
89+
export type Language = typeof LANGUAGES[number];
90+
91+
export const LANGUAGE_SET: ReadonlySet<Language> = new Set(LANGUAGES);
92+
93+
export const ENGINE_SPECIFIC_TAGS = [
94+
'DevPreview',
95+
'LWC'
96+
] as const;
97+
98+
export type EngineSpecificTag = typeof ENGINE_SPECIFIC_TAGS[number];
99+
100+
export const ENGINE_SPECIFIC_TAG_SET: ReadonlySet<EngineSpecificTag> = new Set(ENGINE_SPECIFIC_TAGS);

packages/mcp-provider-code-analyzer/src/provider.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { McpProvider, McpTool, Services } from "@salesforce/mcp-provider-api";
22
import { CodeAnalyzerRunMcpTool } from "./tools/run_code_analyzer.js";
33
import { CodeAnalyzerDescribeRuleMcpTool } from "./tools/describe_code_analyzer_rule.js";
4+
import { CodeAnalyzerListRulesMcpTool } from "./tools/list_code_analyzer_rules.js";
45
import {CodeAnalyzerConfigFactory, CodeAnalyzerConfigFactoryImpl} from "./factories/CodeAnalyzerConfigFactory.js";
56
import {EnginePluginsFactory, EnginePluginsFactoryImpl} from "./factories/EnginePluginsFactory.js";
67
import {RunAnalyzerActionImpl} from "./actions/run-analyzer.js";
78
import {DescribeRuleActionImpl} from "./actions/describe-rule.js";
9+
import { ListRulesActionImpl } from "./actions/list-rules.js";
810

911
export class CodeAnalyzerMcpProvider extends McpProvider {
1012
public getName(): string {
@@ -24,6 +26,11 @@ export class CodeAnalyzerMcpProvider extends McpProvider {
2426
configFactory,
2527
enginePluginsFactory,
2628
telemetryService: services.getTelemetryService()
29+
})),
30+
new CodeAnalyzerListRulesMcpTool(new ListRulesActionImpl({
31+
configFactory,
32+
enginePluginsFactory,
33+
telemetryService: services.getTelemetryService()
2734
}))
2835
]);
2936
}

0 commit comments

Comments
 (0)