Skip to content

Commit 094ee00

Browse files
W-20161235: Add getter komaci rules to offline analysis tool (#326)
* feat: add getter komaci rules to offline analysis tool of mobile-web-mcp * test: add tests for getter komaci rules for offline analysis tool of `mobile-web-mcp` server * fix: change based on feedback * fix: change based on feedback. Make instructions more AI friendly * Update packages/mcp-provider-mobile-web/src/tools/offline-analysis/ruleConfig.ts Co-authored-by: Kevin Hawkins <[email protected]> * fix: update graph-analyzer with node 20 engine --------- Co-authored-by: Kevin Hawkins <[email protected]>
1 parent f0cedf5 commit 094ee00

File tree

7 files changed

+612
-220
lines changed

7 files changed

+612
-220
lines changed

packages/mcp-provider-mobile-web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"eslint": "^9.35.0",
1818
"dedent": "^1.5.3",
1919
"@salesforce/mcp-provider-api": "^0.4.0",
20-
"@salesforce/eslint-plugin-lwc-graph-analyzer": "^1.0.0",
20+
"@salesforce/eslint-plugin-lwc-graph-analyzer": "^1.1.0-beta.2",
2121
"zod": "^3.25.76"
2222
},
2323
"devDependencies": {

packages/mcp-provider-mobile-web/src/schemas/analysisSchema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const CodeAnalysisBaseIssueSchema = z.object({
3232
});
3333

3434
export const CodeAnalysisIssueSchema = CodeAnalysisBaseIssueSchema.extend({
35+
filePath: z.string().describe('The relative path to the file where the issue occurs'),
3536
code: z.string().optional().describe('What is the code snippet with the issue?'),
3637
location: z
3738
.object({

packages/mcp-provider-mobile-web/src/schemas/lwcSchema.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ const LwcFileSchema = z.object({
3232
export const LwcCodeSchema = z.object({
3333
name: z.string().min(1).describe('Name of the LWC component'),
3434
namespace: z.string().describe('Namespace of the LWC component').default('c'),
35+
js: LwcFileSchema.describe('LWC component JavaScript file.'),
3536
html: z.array(LwcFileSchema).min(1).describe('LWC component HTML templates.'),
36-
js: z.array(LwcFileSchema).min(1).describe('LWC component JavaScript files.'),
3737
css: z.array(LwcFileSchema).optional().describe('LWC component CSS files.'),
38-
jsMetaXml: LwcFileSchema.describe('LWC component configuration .js-meta.xml file.'),
38+
jsMetaXml: LwcFileSchema.optional().describe('LWC component configuration .js-meta.xml file.'),
3939
});
4040

41-
export type LwcCodeType = z.TypeOf<typeof LwcCodeSchema>;
41+
export type LwcCodeType = z.infer<typeof LwcCodeSchema>;

packages/mcp-provider-mobile-web/src/tools/offline-analysis/get_mobile_lwc_offline_analysis.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { McpTool, type McpToolConfig } from '@salesforce/mcp-provider-api';
1919
import { ReleaseState, Toolset } from '@salesforce/mcp-provider-api';
2020
import { LwcCodeSchema, type LwcCodeType } from '../../schemas/lwcSchema.js';
2121
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
22-
import { z } from 'zod';
2322
import lwcGraphAnalyzerPlugin from '@salesforce/eslint-plugin-lwc-graph-analyzer';
2423
import { ruleConfigs } from './ruleConfig.js';
2524

@@ -34,6 +33,7 @@ import {
3433
const ANALYSIS_EXPERT_NAME = 'Mobile Web Offline Analysis';
3534
const PLUGIN_NAME = '@salesforce/lwc-graph-analyzer';
3635
const RECOMMENDED_CONFIG = lwcGraphAnalyzerPlugin.configs.recommended;
36+
const { bundleAnalyzer } = lwcGraphAnalyzerPlugin.processors;
3737

3838
const LINTER_CONFIG: Linter.Config = {
3939
name: `config: ${PLUGIN_NAME}`,
@@ -45,7 +45,6 @@ const LINTER_CONFIG: Linter.Config = {
4545

4646
type InputArgsShape = typeof LwcCodeSchema.shape;
4747
type OutputArgsShape = typeof ExpertsCodeAnalysisIssuesSchema.shape;
48-
type InputArgs = z.infer<typeof LwcCodeSchema>;
4948

5049
export class OfflineAnalysisTool extends McpTool<InputArgsShape, OutputArgsShape> {
5150
private readonly linter: Linter;
@@ -82,7 +81,7 @@ export class OfflineAnalysisTool extends McpTool<InputArgsShape, OutputArgsShape
8281
};
8382
}
8483

85-
public async exec(args: InputArgs): Promise<CallToolResult> {
84+
public async exec(args: LwcCodeType): Promise<CallToolResult> {
8685
try {
8786
const analysisResults = await this.analyzeCode(args);
8887

@@ -119,22 +118,42 @@ export class OfflineAnalysisTool extends McpTool<InputArgsShape, OutputArgsShape
119118
}
120119

121120
public async analyzeCode(code: LwcCodeType): Promise<ExpertsCodeAnalysisIssuesType> {
122-
const jsCode = code.js.map((js: { content: string }) => js.content).join('\n');
123-
const { messages } = this.linter.verifyAndFix(jsCode, LINTER_CONFIG, {
124-
fix: true,
125-
});
121+
let offlineAnalysisIssues: ExpertCodeAnalysisIssuesType;
122+
123+
if (!code.js) {
124+
offlineAnalysisIssues = {
125+
expertReviewerName: ANALYSIS_EXPERT_NAME,
126+
issues: [],
127+
};
128+
} else {
129+
const jsCode = code.js.content;
130+
const jsPath = code.js.path;
131+
132+
const htmlCodes = code.html.length > 0 ? code.html.map((html) => html.content) : ([''] as string[]);
133+
134+
const baseName = code.name;
135+
136+
bundleAnalyzer.setLwcBundleFromContent(baseName, jsCode, ...htmlCodes);
137+
138+
const { messages } = this.linter.verifyAndFix(jsCode, LINTER_CONFIG, {
139+
fix: true,
140+
filename: `${baseName}.js`,
141+
});
142+
143+
offlineAnalysisIssues = this.analyzeIssues(jsCode, messages, jsPath);
144+
}
126145

127-
const offlineAnalysisIssues = this.analyzeIssues(jsCode, messages);
128146
return {
129147
analysisResults: [offlineAnalysisIssues],
130148
orchestrationInstructions: this.getOrchestrationInstructions(),
131149
};
132150
}
151+
133152
private getOrchestrationInstructions(): string {
134153
return ExpertsCodeAnalysisIssuesSchema.shape.orchestrationInstructions.parse(undefined);
135154
}
136155

137-
private analyzeIssues(code: string, messages: Linter.LintMessage[]): ExpertCodeAnalysisIssuesType {
156+
private analyzeIssues(code: string, messages: Linter.LintMessage[], jsPath: string): ExpertCodeAnalysisIssuesType {
138157
const issues: CodeAnalysisIssueType[] = [];
139158

140159
for (const violation of messages) {
@@ -144,6 +163,7 @@ export class OfflineAnalysisTool extends McpTool<InputArgsShape, OutputArgsShape
144163

145164
if (ruleReviewer) {
146165
const issue: CodeAnalysisIssueType = {
166+
filePath: jsPath,
147167
type: ruleReviewer.type,
148168
description: ruleReviewer.description,
149169
intentAnalysis: ruleReviewer.intentAnalysis,

packages/mcp-provider-mobile-web/src/tools/offline-analysis/ruleConfig.ts

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export interface RuleConfig {
2121
id: string; //ESLint rule id
2222
config: CodeAnalysisBaseIssueType;
2323
}
24-
// ********** Rules: no-private-wire-config-property **********
24+
// ********** Rule: no-private-wire-config-property **********
2525
const NO_PRIVATE_WIRE_CONFIG_RULE_ID = '@salesforce/lwc-graph-analyzer/no-private-wire-config-property';
2626

2727
const noPrivateWireRule: CodeAnalysisBaseIssueType = {
@@ -37,7 +37,7 @@ const noPrivateWireRule: CodeAnalysisBaseIssueType = {
3737
`,
3838
};
3939

40-
// ********** Rules: no-wire-config-references-non-local-property-reactive-value **********
40+
// ********** Rule: no-wire-config-references-non-local-property-reactive-value **********
4141
const NO_WIRE_CONFIG_REFERENCES_NON_LOCAL_PROPERTY_REACTIVE_VALUE_RULE_ID =
4242
'@salesforce/lwc-graph-analyzer/no-wire-config-references-non-local-property-reactive-value';
4343

@@ -74,4 +74,92 @@ const noWireConfigReferenceNonLocalPropertyRuleConfig: RuleConfig = {
7474
config: noWireConfigReferenceNonLocalPropertyRule,
7575
};
7676

77-
export const ruleConfigs: RuleConfig[] = [noPrivateWireRuleConfig, noWireConfigReferenceNonLocalPropertyRuleConfig];
77+
// ********** getter related violations **********
78+
const getterViolation: CodeAnalysisBaseIssueType = {
79+
type: 'Violations in Getter',
80+
description: 'A getter method does more than just returning a value',
81+
intentAnalysis:
82+
'The developer attempted to modify component state, prepare data for consumption, or reference functions within a getter function.',
83+
suggestedAction: dedent`
84+
# Compliant getter implementations
85+
86+
Getters that:
87+
- Directly access and return property values
88+
- Return a literal value
89+
- Compute and return values derived from existing properties
90+
91+
# Non-compliant getter implementations
92+
93+
## Violation: getters that call functions
94+
95+
Getters that call functions cannot be primed for offline use cases.
96+
97+
### Remediation
98+
99+
Reorganize any getter implementation code that calls a function, to move such calls out of the getter. Avoid invoking any function calls within getters.
100+
101+
## Violation: getters with side effects
102+
103+
Getters that assign values to member variables or modify state create unpredictable side effects and are not suitable for offline scenarios.
104+
105+
### Remediation
106+
107+
Never assign values to member variables within a getter. LWC getters should only retrieve data without modifying any state. If you need to compute and cache a value, perform the computation and assignment in a lifecycle hook or method, then have the getter simply return the cached value.
108+
109+
## Violation: getters that do more than just return a value
110+
111+
Getters that perform complex operations beyond returning a value cannot be primed for offline use cases.
112+
113+
### Remediation
114+
115+
Review the getters and make sure that they're composed to only return a value. Move any complex logic, data processing, or multiple operations into separate methods or lifecycle hooks, and have the getter simply return the result.
116+
`,
117+
};
118+
119+
// ********** Rule: no-assignment-expression-assigns-value-to-member-variable **********
120+
const NO_ASSIGNMENT_EXPRESSION_ASSIGNS_VALUE_TO_MEMBER_VARIABLE_RULE_ID =
121+
'@salesforce/lwc-graph-analyzer/no-assignment-expression-assigns-value-to-member-variable';
122+
const noAssignmentExpressionAssignsValueToMemberVariableRuleConfig: RuleConfig = {
123+
id: NO_ASSIGNMENT_EXPRESSION_ASSIGNS_VALUE_TO_MEMBER_VARIABLE_RULE_ID,
124+
config: getterViolation,
125+
};
126+
127+
// ********** Rule: no-reference-to-class-functions **********
128+
const NO_REFERENCE_TO_CLASS_FUNCTIONS_RULE_ID = '@salesforce/lwc-graph-analyzer/no-reference-to-class-functions';
129+
const noReferenceToClassFunctionsRuleConfig: RuleConfig = {
130+
id: NO_REFERENCE_TO_CLASS_FUNCTIONS_RULE_ID,
131+
config: getterViolation,
132+
};
133+
134+
// ********** Rule: no-reference-to-module-functions **********
135+
const NO_REFERENCE_TO_MODULE_FUNCTIONS_RULE_ID = '@salesforce/lwc-graph-analyzer/no-reference-to-module-functions';
136+
const noReferenceToModuleFunctionsRuleConfig: RuleConfig = {
137+
id: NO_REFERENCE_TO_MODULE_FUNCTIONS_RULE_ID,
138+
config: getterViolation,
139+
};
140+
141+
// ********** Rule: no-getter-contains-more-than-return-statement **********
142+
const NO_GETTER_CONTAINS_MORE_THAN_RETURN_STATEMENT_RULE_ID =
143+
'@salesforce/lwc-graph-analyzer/no-getter-contains-more-than-return-statement';
144+
const noGetterContainsMoreThanReturnStatementRuleConfig: RuleConfig = {
145+
id: NO_GETTER_CONTAINS_MORE_THAN_RETURN_STATEMENT_RULE_ID,
146+
config: getterViolation,
147+
};
148+
149+
// ********** Rule: no-unsupported-member-variable-in-member-expression **********
150+
const NO_UNSUPPORTED_MEMBER_VARIABLE_IN_MEMBER_EXPRESSION_RULE_ID =
151+
'@salesforce/lwc-graph-analyzer/no-unsupported-member-variable-in-member-expression';
152+
const noUnsupportedMemberVariableInMemberExpressionRuleConfig: RuleConfig = {
153+
id: NO_UNSUPPORTED_MEMBER_VARIABLE_IN_MEMBER_EXPRESSION_RULE_ID,
154+
config: getterViolation,
155+
};
156+
157+
export const ruleConfigs: RuleConfig[] = [
158+
noPrivateWireRuleConfig,
159+
noWireConfigReferenceNonLocalPropertyRuleConfig,
160+
noAssignmentExpressionAssignsValueToMemberVariableRuleConfig,
161+
noReferenceToClassFunctionsRuleConfig,
162+
noReferenceToModuleFunctionsRuleConfig,
163+
noGetterContainsMoreThanReturnStatementRuleConfig,
164+
noUnsupportedMemberVariableInMemberExpressionRuleConfig,
165+
];

0 commit comments

Comments
 (0)