Skip to content

Commit 0930035

Browse files
committed
Only return diagnostics that match the primary file
1 parent bb3540d commit 0930035

File tree

7 files changed

+365
-6
lines changed

7 files changed

+365
-6
lines changed

jest.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ module.exports = {
1818
'<rootDir>/test/lib/processor.js',
1919
'!**/test/lib/rules/**/test.js',
2020
'!**/test/lib/rules/artifacts-combined-files/helper.js',
21-
'!**/test/lib/rules/shared.js'
21+
'!**/test/lib/rules/shared.js',
22+
'!**/test/lib/**/mock-*.js'
2223
],
2324
moduleFileExtensions: ['js', 'json'],
2425
testResultsProcessor: 'jest-sonar-reporter',

lib/util/helper.js

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,24 @@
88
'use strict';
99

1010
const { basename } = require('path');
11-
const staticAnalyzer = require('@komaci/static-analyzer');
1211
const bundleStateManager = require('./bundle-state-manager');
12+
const StaticAnalyzerProvider = require('./static-analyzer-provider');
1313
// eslint-disable-next-line no-unused-vars
1414
const LwcBundle = require('../lwc-bundle');
1515

1616
const lwcNamespace = 'c';
1717

18+
// Create a default provider instance
19+
let staticAnalyzerProvider = new StaticAnalyzerProvider();
20+
21+
/**
22+
* Sets the static analyzer provider to use
23+
* @param {import('./static-analyzer-interface')} provider - The provider to use
24+
*/
25+
function setStaticAnalyzerProvider(provider) {
26+
staticAnalyzerProvider = provider;
27+
}
28+
1829
function rangeToLoc(range) {
1930
const {
2031
start: { line, character: column },
@@ -90,6 +101,16 @@ function extractBundleKey(eslintFilename) {
90101
return filename.replace(/^(\d+_)?/, '');
91102
}
92103

104+
/**
105+
* Gets the Komaci diagnostic reports for a given rule and file.
106+
* Filters the reports to only include those that:
107+
* 1. Match the specified rule
108+
* 2. Target the primary file in the bundle
109+
*
110+
* @param {string} ruleName - The full ESLint rule name (e.g. '@salesforce/lwc-graph-analyzer/rule-name')
111+
* @param {string} filename - The filename being processed by ESLint
112+
* @returns {Array<Object>} An array of ESLint report objects, each containing a message and ___location information
113+
*/
93114
function getKomaciReport(ruleName, filename) {
94115
const bundleKey = extractBundleKey(filename);
95116
const lwcBundle = bundleStateManager.getBundleByKey(bundleKey);
@@ -99,7 +120,7 @@ function getKomaciReport(ruleName, filename) {
99120
}
100121

101122
const lwcBundleFiles = lwcBundle.filesRecord();
102-
let eslintReports = staticAnalyzer.generatePrimingDiagnosticsModule({
123+
let eslintReports = staticAnalyzerProvider.generatePrimingDiagnosticsModule({
103124
type: 'bundle',
104125
namespace: lwcNamespace,
105126
name: lwcBundle.componentBaseName,
@@ -115,7 +136,7 @@ function getKomaciReport(ruleName, filename) {
115136

116137
// Diagnostic messages is the catalog of all Komaci errors. Find the one that matches the rule name
117138
// so that the Komaci reports can be filtered.
118-
const diagnosticMessage = staticAnalyzer.diagnosticMessages[ruleKey];
139+
const diagnosticMessage = staticAnalyzerProvider.diagnosticMessages[ruleKey];
119140

120141
if (!diagnosticMessage) {
121142
// No matching diagnostic message was found. Return an empty array to indicate that
@@ -133,7 +154,11 @@ function getKomaciReport(ruleName, filename) {
133154
eslintReports = eslintReports.filter((reportDiagnostic) => {
134155
const reportDiagnosticValue = extractDiagnosticCodeValue(reportDiagnostic);
135156
const diagnosticValue = extractDiagnosticCodeValue(diagnosticMessage);
136-
return reportDiagnosticValue === diagnosticValue;
157+
// Only include diagnostics that match both the rule and target the primary file
158+
return (
159+
reportDiagnosticValue === diagnosticValue &&
160+
reportDiagnostic.code.target.path === lwcBundle.primaryFile.filename
161+
);
137162
});
138163

139164
return eslintReports.map(diagnosticToReport);
@@ -143,5 +168,7 @@ module.exports = {
143168
setLwcBundleCacheEntry,
144169
removeLwcBundleCacheEntry,
145170
analyzeLWC,
146-
extractBundleKey
171+
extractBundleKey,
172+
setStaticAnalyzerProvider,
173+
getKomaciReport
147174
};

lib/util/static-analyzer-interface.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2025, salesforce.com, inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: MIT
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
6+
*/
7+
8+
'use strict';
9+
10+
/**
11+
* Interface for static analysis functionality
12+
*/
13+
class StaticAnalyzerInterface {
14+
constructor() {
15+
if (this.constructor === StaticAnalyzerInterface) {
16+
throw new TypeError(
17+
'StaticAnalyzerInterface is an abstract class and cannot be instantiated directly'
18+
);
19+
}
20+
}
21+
22+
/**
23+
* Generates priming diagnostics for a module
24+
* @param {Object} options - The options for generating diagnostics
25+
* @param {string} options.type - The type of module
26+
* @param {string} options.namespace - The namespace of the module
27+
* @param {string} options.name - The name of the module
28+
* @param {Object<string, string>} options.files - The files to analyze
29+
* @returns {Array<Object>} The generated diagnostics
30+
*/
31+
// eslint-disable-next-line no-unused-vars
32+
generatePrimingDiagnosticsModule(options) {
33+
throw new Error('Method not implemented');
34+
}
35+
36+
/**
37+
* Gets the diagnostic messages catalog
38+
* @returns {Object} The diagnostic messages catalog
39+
*/
40+
get diagnosticMessages() {
41+
throw new Error('Method not implemented');
42+
}
43+
}
44+
45+
module.exports = StaticAnalyzerInterface;

lib/util/static-analyzer-provider.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright (c) 2025, salesforce.com, inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: MIT
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
6+
*/
7+
8+
'use strict';
9+
10+
const StaticAnalyzerInterface = require('./static-analyzer-interface');
11+
const staticAnalyzer = require('@komaci/static-analyzer');
12+
13+
/**
14+
* Concrete implementation of StaticAnalyzerInterface that uses the actual static analyzer
15+
*/
16+
class StaticAnalyzerProvider extends StaticAnalyzerInterface {
17+
generatePrimingDiagnosticsModule(options) {
18+
return staticAnalyzer.generatePrimingDiagnosticsModule(options);
19+
}
20+
21+
get diagnosticMessages() {
22+
return staticAnalyzer.diagnosticMessages;
23+
}
24+
}
25+
26+
module.exports = StaticAnalyzerProvider;

test/lib/util/helper.js

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* Copyright (c) 2025, salesforce.com, inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: MIT
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
6+
*/
7+
8+
'use strict';
9+
10+
const { expect } = require('chai');
11+
const { getKomaciReport, setStaticAnalyzerProvider } = require('../../../lib/util/helper');
12+
const LwcBundle = require('../../../lib/lwc-bundle');
13+
const bundleStateManager = require('../../../lib/util/bundle-state-manager');
14+
const MockStaticAnalyzer = require('./mock-static-analyzer');
15+
16+
describe('helper', () => {
17+
describe('getKomaciReport', () => {
18+
let mockStaticAnalyzer;
19+
20+
beforeEach(() => {
21+
bundleStateManager.clear();
22+
mockStaticAnalyzer = new MockStaticAnalyzer();
23+
setStaticAnalyzerProvider(mockStaticAnalyzer);
24+
});
25+
26+
afterEach(() => {
27+
bundleStateManager.clear();
28+
});
29+
30+
it('should only include diagnostics that target the primary file', () => {
31+
// Create a bundle with both JS and HTML files
32+
const jsContent = 'export default class Test {}';
33+
const htmlContent = '<template>Test</template>';
34+
const bundle = LwcBundle.lwcBundleFromContent('test', jsContent, htmlContent);
35+
bundle.setPrimaryFileByContent(jsContent);
36+
bundleStateManager.addBundleState(bundle);
37+
38+
// Set up mock diagnostics for both files
39+
const mockDiagnostics = [
40+
{
41+
code: {
42+
value: 'TEST_RULE',
43+
target: { path: 'test.js' }
44+
},
45+
message: 'Test message for JS file',
46+
range: {
47+
start: { line: 0, character: 0 },
48+
end: { line: 0, character: 10 }
49+
}
50+
},
51+
{
52+
code: {
53+
value: 'TEST_RULE',
54+
target: { path: 'test.html' }
55+
},
56+
message: 'Test message for HTML file',
57+
range: {
58+
start: { line: 0, character: 0 },
59+
end: { line: 0, character: 10 }
60+
}
61+
}
62+
];
63+
mockStaticAnalyzer.setDiagnostics(mockDiagnostics);
64+
65+
// Set up mock diagnostic messages
66+
mockStaticAnalyzer.setDiagnosticMessages({
67+
TEST_RULE: {
68+
code: { value: 'TEST_RULE' }
69+
}
70+
});
71+
72+
const bundleKey = bundle.getBundleKey();
73+
const reports = getKomaciReport(
74+
'@salesforce/lwc-graph-analyzer/test-rule',
75+
`0_${bundleKey}`
76+
);
77+
78+
// Should only include the diagnostic for the primary file (test.js)
79+
expect(reports).to.have.length(1);
80+
expect(reports[0].message).to.equal('Test message for JS file');
81+
});
82+
83+
it('should handle cases where no diagnostics match the primary file', () => {
84+
// Create a bundle with both JS and HTML files
85+
const jsContent = 'export default class Test {}';
86+
const htmlContent = '<template>Test</template>';
87+
const bundle = LwcBundle.lwcBundleFromContent('test', jsContent, htmlContent);
88+
bundle.setPrimaryFileByContent(jsContent);
89+
bundleStateManager.addBundleState(bundle);
90+
91+
// Set up mock diagnostics only for the HTML file
92+
const mockDiagnostics = [
93+
{
94+
code: {
95+
value: 'TEST_RULE',
96+
target: { path: 'test.html' }
97+
},
98+
message: 'Test message for HTML file',
99+
range: {
100+
start: { line: 0, character: 0 },
101+
end: { line: 0, character: 10 }
102+
}
103+
}
104+
];
105+
mockStaticAnalyzer.setDiagnostics(mockDiagnostics);
106+
107+
// Set up mock diagnostic messages
108+
mockStaticAnalyzer.setDiagnosticMessages({
109+
TEST_RULE: {
110+
code: { value: 'TEST_RULE' }
111+
}
112+
});
113+
114+
const bundleKey = bundle.getBundleKey();
115+
const reports = getKomaciReport(
116+
'@salesforce/lwc-graph-analyzer/test-rule',
117+
`0_${bundleKey}`
118+
);
119+
120+
// Should return empty array since no diagnostics target the primary file
121+
expect(reports).to.have.length(0);
122+
});
123+
124+
it('should return empty array when no bundle is found', () => {
125+
// Don't add any bundles to the state manager
126+
const reports = getKomaciReport(
127+
'@salesforce/lwc-graph-analyzer/test-rule',
128+
'0_nonexistent-bundle'
129+
);
130+
131+
expect(reports).to.be.an('array').that.is.empty;
132+
});
133+
134+
it('should return empty array when no matching diagnostic message is found', () => {
135+
// Create a bundle with both JS and HTML files
136+
const jsContent = 'export default class Test {}';
137+
const htmlContent = '<template>Test</template>';
138+
const bundle = LwcBundle.lwcBundleFromContent('test', jsContent, htmlContent);
139+
bundle.setPrimaryFileByContent(jsContent);
140+
bundleStateManager.addBundleState(bundle);
141+
142+
// Set up mock diagnostics
143+
const mockDiagnostics = [
144+
{
145+
code: {
146+
value: 'TEST_RULE',
147+
target: { path: 'test.js' }
148+
},
149+
message: 'Test message for JS file',
150+
range: {
151+
start: { line: 0, character: 0 },
152+
end: { line: 0, character: 10 }
153+
}
154+
}
155+
];
156+
mockStaticAnalyzer.setDiagnostics(mockDiagnostics);
157+
158+
// Don't set up any diagnostic messages, so no match will be found
159+
mockStaticAnalyzer.setDiagnosticMessages({});
160+
161+
const bundleKey = bundle.getBundleKey();
162+
const reports = getKomaciReport(
163+
'@salesforce/lwc-graph-analyzer/test-rule',
164+
`0_${bundleKey}`
165+
);
166+
167+
expect(reports).to.be.an('array').that.is.empty;
168+
});
169+
});
170+
});

test/lib/util/mock-static-analyzer.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) 2025, salesforce.com, inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: MIT
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
6+
*/
7+
8+
'use strict';
9+
10+
const StaticAnalyzerInterface = require('../../../lib/util/static-analyzer-interface');
11+
12+
/**
13+
* Mock implementation of StaticAnalyzerInterface for testing
14+
*/
15+
class MockStaticAnalyzer extends StaticAnalyzerInterface {
16+
constructor() {
17+
super();
18+
this._diagnostics = [];
19+
this._diagnosticMessages = {};
20+
}
21+
22+
/**
23+
* Sets the diagnostics that will be returned by generatePrimingDiagnosticsModule
24+
* @param {Array<Object>} diagnostics - The diagnostics to return
25+
*/
26+
setDiagnostics(diagnostics) {
27+
this._diagnostics = diagnostics;
28+
}
29+
30+
/**
31+
* Sets the diagnostic messages catalog
32+
* @param {Object} messages - The diagnostic messages catalog
33+
*/
34+
setDiagnosticMessages(messages) {
35+
this._diagnosticMessages = messages;
36+
}
37+
38+
generatePrimingDiagnosticsModule() {
39+
return this._diagnostics;
40+
}
41+
42+
get diagnosticMessages() {
43+
return this._diagnosticMessages;
44+
}
45+
}
46+
47+
module.exports = MockStaticAnalyzer;

0 commit comments

Comments
 (0)