Skip to content

Commit 4981ee1

Browse files
authored
NEW @W-17042397@ Detail output now has multilocation support (#1673)
1 parent 865619b commit 4981ee1

File tree

7 files changed

+142
-50
lines changed

7 files changed

+142
-50
lines changed

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
"bugs": "https://github.com/forcedotcom/sfdx-scanner/issues",
77
"dependencies": {
88
"@oclif/core": "^3.3.2",
9-
"@salesforce/code-analyzer-core": "0.14.1",
10-
"@salesforce/code-analyzer-engine-api": "0.11.1",
11-
"@salesforce/code-analyzer-eslint-engine": "0.11.1",
12-
"@salesforce/code-analyzer-pmd-engine": "0.11.1",
13-
"@salesforce/code-analyzer-regex-engine": "0.11.1",
14-
"@salesforce/code-analyzer-retirejs-engine": "0.11.1",
9+
"@salesforce/code-analyzer-core": "0.16.0",
10+
"@salesforce/code-analyzer-engine-api": "0.13.0",
11+
"@salesforce/code-analyzer-eslint-engine": "0.13.0",
12+
"@salesforce/code-analyzer-pmd-engine": "0.13.0",
13+
"@salesforce/code-analyzer-regex-engine": "0.13.0",
14+
"@salesforce/code-analyzer-retirejs-engine": "0.13.0",
1515
"@salesforce/core": "^5",
1616
"@salesforce/sf-plugins-core": "^5.0.4",
1717
"@salesforce/ts-types": "^2.0.9",

src/lib/utils/StylingUtil.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import ansis from 'ansis';
44
* For now, the styling methods only accept objects if all of their keys correspond to string values. This puts the
55
* burden of formatting non-string properties on the caller.
66
*/
7-
type Styleable = null | undefined | {[key: string]: string};
7+
type Styleable = null | undefined | {[key: string]: string|string[]};
88

99
export function toStyledHeaderAndBody(header: string, body: Styleable, keys?: string[]): string {
1010
const styledHeader: string = toStyledHeader(header);
@@ -27,11 +27,16 @@ export function toStyledPropertyList(body: Styleable, selectedKeys?: string[]):
2727
const keysToPrint = selectedKeys || [...Object.keys(body)];
2828
const longestKeyLength = Math.max(...keysToPrint.map(k => k.length));
2929

30-
const styleProperty = (key: string, value: string): string => {
30+
const styleProperty = (key: string, value: string|string[]): string => {
3131
const keyPortion = `${ansis.blue(key)}:`;
3232
const keyValueGap = ' '.repeat(longestKeyLength - key.length + 1);
33-
const valuePortion = value.replace('\n', `\n${' '.repeat(longestKeyLength + 2)}`);
34-
return `${keyPortion}${keyValueGap}${valuePortion}`;
33+
if (typeof value === 'string') {
34+
const valuePortion = value.replace('\n', `\n${' '.repeat(longestKeyLength + 2)}`);
35+
return `${keyPortion}${keyValueGap}${valuePortion}`;
36+
} else {
37+
const valuePortion: string = value.map(v => `${indent(v, 4)}`).join('\n');
38+
return `${keyPortion}\n${valuePortion}`;
39+
}
3540
}
3641

3742
const output = keysToPrint.map(key => styleProperty(key, body[key] || ''));

src/lib/viewers/ResultsViewer.ts

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,25 +58,49 @@ export class ResultsDetailDisplayer extends AbstractResultsDisplayer {
5858
private styleViolation(violation: Violation, idx: number): string {
5959
const rule = violation.getRule();
6060
const sev = rule.getSeverityLevel();
61-
const primaryLocation = violation.getCodeLocations()[violation.getPrimaryLocationIndex()];
6261

6362
const header = getMessage(
6463
BundleName.ResultsViewer,
6564
'summary.detail.violation-header',
6665
[idx + 1, rule.getName()]
6766
);
68-
const body = {
69-
severity: `${sev.valueOf()} (${SeverityLevel[sev]})`,
70-
engine: rule.getEngineName(),
71-
message: violation.getMessage(),
72-
location: `${primaryLocation.getFile()}:${primaryLocation.getStartLine()}:${primaryLocation.getStartColumn()}`,
73-
resources: violation.getResourceUrls().join(',')
74-
};
75-
const keys = ['severity', 'engine', 'message', 'location', 'resources'];
76-
return toStyledHeaderAndBody(header, body, keys);
67+
if (violation.getCodeLocations().length > 1) {
68+
const body = {
69+
severity: `${sev.valueOf()} (${SeverityLevel[sev]})`,
70+
engine: rule.getEngineName(),
71+
message: violation.getMessage(),
72+
locations: stringifyLocations(violation.getCodeLocations(), violation.getPrimaryLocationIndex()),
73+
resources: violation.getResourceUrls().join(',')
74+
};
75+
const keys = ['severity', 'engine', 'message', 'locations', 'resources'];
76+
return toStyledHeaderAndBody(header, body, keys);
77+
} else {
78+
const body = {
79+
severity: `${sev.valueOf()} (${SeverityLevel[sev]})`,
80+
engine: rule.getEngineName(),
81+
message: violation.getMessage(),
82+
location: stringifyLocations(violation.getCodeLocations())[0],
83+
resources: violation.getResourceUrls().join(',')
84+
};
85+
const keys = ['severity', 'engine', 'message', 'location', 'resources'];
86+
return toStyledHeaderAndBody(header, body, keys);
87+
}
7788
}
7889
}
7990

91+
function stringifyLocations(codeLocations: CodeLocation[], primaryIndex?: number): string[] {
92+
const locationStrings: string[] = [];
93+
94+
codeLocations.forEach((loc, idx) => {
95+
const commentPortion: string = loc.getComment() ? ` ${loc.getComment()}` : '';
96+
const locationString: string = `${loc.getFile()}:${loc.getStartLine()}:${loc.getStartColumn()}${commentPortion}`;
97+
const mainPortion: string = primaryIndex != null && primaryIndex === idx ? '(main) ' : '';
98+
locationStrings.push(`${mainPortion}${locationString}`);
99+
});
100+
101+
return locationStrings;
102+
}
103+
80104
type ResultRow = {
81105
num: number;
82106
location: string;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Found 1 violation(s) across 1 file(s):
2+
=== 1. stub1RuleA
3+
severity: 4 (Low)
4+
engine: stubEngine1
5+
message: This is a message
6+
locations:
7+
__PATH_TO_FILE_A__:20:1
8+
(main) __PATH_TO_FILE_Z__:2:1 This is a comment at Location 2
9+
__PATH_TO_FILE_A__:1:1 This is a comment at Location 3
10+
resources: https://example.com/stub1RuleA

test/lib/factories/EnginePluginsFactory.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ describe('EnginePluginsFactoryImpl', () => {
88

99
expect(enginePlugins).toHaveLength(4);
1010
expect(enginePlugins[0].getAvailableEngineNames()).toEqual(['eslint']);
11-
expect(enginePlugins[1].getAvailableEngineNames()).toEqual(['pmd']);
11+
expect(enginePlugins[1].getAvailableEngineNames()).toEqual(['pmd', 'cpd']);
1212
expect(enginePlugins[2].getAvailableEngineNames()).toEqual(['retire-js']);
1313
expect(enginePlugins[3].getAvailableEngineNames()).toEqual(['regex']);
1414
});

test/lib/viewers/ResultsViewer.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,53 @@ describe('ResultsViewer implementations', () => {
132132
.replace(/__PATH_TO_FILE_Z__/g, PATH_TO_FILE_Z);
133133
expect(actualEventText).toContain(expectedViolationDetails);
134134
});
135+
136+
it('Multi-location violations are correctly displayed', async () => {
137+
// ==== TEST SETUP ====
138+
// Populate the engine with:
139+
const violations: Violation[] = [
140+
// A violation.
141+
createViolation(rule1.name, PATH_TO_FILE_A, 20, 1),
142+
];
143+
144+
// Add some additional locations to the violation.
145+
violations[0].codeLocations.push({
146+
file: PATH_TO_FILE_Z,
147+
startLine: 2,
148+
startColumn: 1,
149+
comment: 'This is a comment at Location 2'
150+
}, {
151+
file: PATH_TO_FILE_A,
152+
startLine: 1,
153+
startColumn: 1,
154+
comment: 'This is a comment at Location 3'
155+
});
156+
// Declare the second location to be the primary.
157+
violations[0].primaryLocationIndex = 1;
158+
engine1.resultsToReturn = {violations};
159+
160+
// "Run" the plugin.
161+
const workspace = await codeAnalyzerCore.createWorkspace(['package.json']);
162+
const rules = await codeAnalyzerCore.selectRules(['all'], {workspace});
163+
const results = await codeAnalyzerCore.run(rules, {workspace});
164+
165+
// ==== TESTED METHOD ====
166+
// Pass the result object into the viewer.
167+
viewer.view(results);
168+
169+
// ==== ASSERTIONS ====
170+
// Compare the text in the events with the text in our comparison file.
171+
const actualDisplayEvents: DisplayEvent[] = spyDisplay.getDisplayEvents();
172+
for (const event of actualDisplayEvents) {
173+
expect(event.type).toEqual(DisplayEventType.LOG);
174+
}
175+
// Rip off all of ansis's styling, so we're just comparing plain text.
176+
const actualEventText = ansis.strip(actualDisplayEvents.map(e => e.data).join('\n'));
177+
const expectedViolationDetails = (await readComparisonFile('one-multilocation-violation-details.txt'))
178+
.replace(/__PATH_TO_FILE_A__/g, PATH_TO_FILE_A)
179+
.replace(/__PATH_TO_FILE_Z__/g, PATH_TO_FILE_Z);
180+
expect(actualEventText).toContain(expectedViolationDetails);
181+
})
135182
});
136183

137184
describe('ResultsTableDisplayer', () => {

yarn.lock

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,35 +1569,36 @@
15691569
strip-ansi "6.0.1"
15701570
ts-retry-promise "^0.8.1"
15711571

1572-
"@salesforce/code-analyzer-core@0.14.1":
1573-
version "0.14.1"
1574-
resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-core/-/code-analyzer-core-0.14.1.tgz#6b14e12b1bbfc32ea4401b03f86117330431c952"
1575-
integrity sha512-kTgrGAsDxpeV4FU+V0i91h9byvD6tECJHfX0lKv/6bTfDwNJEuLFypb1t/g+w8qfIymRLq7IYvJ+wFjYAJTdDA==
1572+
"@salesforce/code-analyzer-core@0.16.0":
1573+
version "0.16.0"
1574+
resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-core/-/code-analyzer-core-0.16.0.tgz#e9aec49c7e3835d29f22b715388a16267c632afd"
1575+
integrity sha512-hqOUF4dFNjY9w6pMlw8eIDN1jnm8aypLH3KEdPMI2asmLjOI1C1EOZ6IYrlsclYX6Czr9f5LzShaa0l5CJabaQ==
15761576
dependencies:
1577-
"@salesforce/code-analyzer-engine-api" "0.11.1"
1577+
"@salesforce/code-analyzer-engine-api" "0.13.0"
15781578
"@types/js-yaml" "^4.0.9"
15791579
"@types/node" "^20.0.0"
1580+
"@types/sarif" "^2.1.7"
15801581
csv-stringify "^6.5.0"
15811582
js-yaml "^4.1.0"
15821583
xmlbuilder "^15.1.1"
15831584

1584-
"@salesforce/code-analyzer-engine-api@0.11.1":
1585-
version "0.11.1"
1586-
resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-engine-api/-/code-analyzer-engine-api-0.11.1.tgz#c4e1ad9375263d1daf70cfef2118b11013ce4b4a"
1587-
integrity sha512-RW2OU3dHNL+ecYQ5B1TSmKCOFXlruT1M4ATG0pTp3E9wYvz0oah8wWeaFPRT37HNn+Sf7SNYpkbIVDZYwV46iw==
1585+
"@salesforce/code-analyzer-engine-api@0.13.0":
1586+
version "0.13.0"
1587+
resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-engine-api/-/code-analyzer-engine-api-0.13.0.tgz#dbaba9102d34ea12472f4115298da8d617a0492d"
1588+
integrity sha512-dcVuoYUbzEXcW1+l0tjoOquu6VSgr41ti+tOKE/569Wb+hf4Yu7LhXFGq4Gq5tueZDuODjDYDWuFnmmgcAwBJw==
15881589
dependencies:
15891590
"@types/node" "^20.0.0"
15901591

1591-
"@salesforce/code-analyzer-eslint-engine@0.11.1":
1592-
version "0.11.1"
1593-
resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-eslint-engine/-/code-analyzer-eslint-engine-0.11.1.tgz#f640324b05411404c6907e224ca482d3b57564e0"
1594-
integrity sha512-wOuY8nAtBnnkH2Bi8kz3PlJr97Mc6fQgKzOnUJR6fC3AcdLTz8rZ9YWR8yQdi9ASEPfGQq9ZyA20PDEzJMWezQ==
1592+
"@salesforce/code-analyzer-eslint-engine@0.13.0":
1593+
version "0.13.0"
1594+
resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-eslint-engine/-/code-analyzer-eslint-engine-0.13.0.tgz#1bb3688e94a63f4b6c0db941e0ce6fc84ca2b0de"
1595+
integrity sha512-UnEQB+5KiZIcQJYIVrAB1XzJnymyRAm6NXy2naETOdqXwVxjXo1jvSNuw68BfKQXs36GXf+EfjF/H+CnXurvHQ==
15951596
dependencies:
15961597
"@babel/core" "^7.24.7"
15971598
"@babel/eslint-parser" "^7.24.7"
15981599
"@eslint/js" "^8.57.0"
15991600
"@lwc/eslint-plugin-lwc" "^1.8.0"
1600-
"@salesforce/code-analyzer-engine-api" "0.11.1"
1601+
"@salesforce/code-analyzer-engine-api" "0.13.0"
16011602
"@salesforce/eslint-config-lwc" "^3.5.3"
16021603
"@salesforce/eslint-plugin-lightning" "^1.0.0"
16031604
"@types/eslint" "^8.56.10"
@@ -1608,33 +1609,33 @@
16081609
eslint-plugin-import "^2.29.1"
16091610
eslint-plugin-jest "^28.6.0"
16101611

1611-
"@salesforce/code-analyzer-pmd-engine@0.11.1":
1612-
version "0.11.1"
1613-
resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-pmd-engine/-/code-analyzer-pmd-engine-0.11.1.tgz#8c6be62ad172ed1650149fc55840bac7490bd4df"
1614-
integrity sha512-hIbqT+PBhNiRu0NbYs66aNw8ML+PtvB0wneQ7IvOErvFhznL6RY7AokXix3FxMkCk4xyBdAC1s080m+QmojCug==
1612+
"@salesforce/code-analyzer-pmd-engine@0.13.0":
1613+
version "0.13.0"
1614+
resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-pmd-engine/-/code-analyzer-pmd-engine-0.13.0.tgz#c3db9ee1cd73d46bdb940aea1c6888e64bea51b1"
1615+
integrity sha512-5TMiTL520jNfVcewX7IlsScNoxh5CDyQI5lrilZEa+LkgR9wvFI8b0N+uzz82Iz9xPp+tzmgnW9QVODaCLwdwQ==
16151616
dependencies:
1616-
"@salesforce/code-analyzer-engine-api" "0.11.1"
1617+
"@salesforce/code-analyzer-engine-api" "0.13.0"
16171618
"@types/node" "^20.0.0"
16181619
"@types/semver" "^7.5.8"
16191620
"@types/tmp" "^0.2.6"
16201621
semver "^7.6.3"
16211622
tmp "^0.2.3"
16221623

1623-
"@salesforce/code-analyzer-regex-engine@0.11.1":
1624-
version "0.11.1"
1625-
resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-regex-engine/-/code-analyzer-regex-engine-0.11.1.tgz#e34974f356bc20c1afd8c9d6834fb4dcdc461fc7"
1626-
integrity sha512-KVdg44ENoIfripHIqVwmf2UemlAtQGQOw4Kn0fuqrjOXFbKATjKrJsawZZhyoKtSdejWt58ni1bjCvYesej93g==
1624+
"@salesforce/code-analyzer-regex-engine@0.13.0":
1625+
version "0.13.0"
1626+
resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-regex-engine/-/code-analyzer-regex-engine-0.13.0.tgz#0bff0037483663d2707a01d05a9b4d8f5951e1fc"
1627+
integrity sha512-6eDG9muy74jHw46rVE+W3MOzuKPpbxvmE+DK6i/JB3qh00OIv7JmVysuAuXV9mvGhO1jj+FBHfug2ZexKEhGUw==
16271628
dependencies:
1628-
"@salesforce/code-analyzer-engine-api" "0.11.1"
1629+
"@salesforce/code-analyzer-engine-api" "0.13.0"
16291630
"@types/node" "^20.0.0"
16301631
isbinaryfile "^5.0.2"
16311632

1632-
"@salesforce/code-analyzer-retirejs-engine@0.11.1":
1633-
version "0.11.1"
1634-
resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-retirejs-engine/-/code-analyzer-retirejs-engine-0.11.1.tgz#2a12a97626f32ff4841182f4969361ac570d9f81"
1635-
integrity sha512-nCaU7Sg24EZ/l8RljCjQEHccs7FgM5+t5oXVYrVU0/UoOuMHjgZvELsit02WD63K65J0vAQkBEsgRO4s7mLtlQ==
1633+
"@salesforce/code-analyzer-retirejs-engine@0.13.0":
1634+
version "0.13.0"
1635+
resolved "https://registry.yarnpkg.com/@salesforce/code-analyzer-retirejs-engine/-/code-analyzer-retirejs-engine-0.13.0.tgz#943c6be8f1cd4607a34044e33114bd5834e4e015"
1636+
integrity sha512-1AN9vTKifDR2QsR56VCr4Xuy5EzxmsL95gmfYoxJwo4brf6QzW3/5XgaaanCEfWAdLbONYkEnJMsT45RE30uJA==
16361637
dependencies:
1637-
"@salesforce/code-analyzer-engine-api" "0.11.1"
1638+
"@salesforce/code-analyzer-engine-api" "0.13.0"
16381639
"@types/node" "^20.0.0"
16391640
"@types/tmp" "^0.2.6"
16401641
isbinaryfile "^5.0.2"
@@ -2495,6 +2496,11 @@
24952496
dependencies:
24962497
undici-types "~6.19.2"
24972498

2499+
"@types/sarif@^2.1.7":
2500+
version "2.1.7"
2501+
resolved "https://registry.yarnpkg.com/@types/sarif/-/sarif-2.1.7.tgz#dab4d16ba7568e9846c454a8764f33c5d98e5524"
2502+
integrity sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==
2503+
24982504
"@types/semver@^7.5.4", "@types/semver@^7.5.8":
24992505
version "7.5.8"
25002506
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e"

0 commit comments

Comments
 (0)