Skip to content

Commit 2e1d1c4

Browse files
Merge pull request #3 from granodigital/copilot/fix-ef42709c-d695-4b40-b858-2a62906387e9
2 parents 5a6cb44 + ece1ae0 commit 2e1d1c4

File tree

4 files changed

+123
-61
lines changed

4 files changed

+123
-61
lines changed

__tests__/main.test.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,42 @@ at Tests.Registration.main(Registration.java:202)`,
174174
testInputs['max-annotations'] = '1';
175175
await main.run();
176176
expect(warningMock).toHaveBeenCalledWith(
177-
'Maximum number of annotations reached (1)',
177+
'Maximum number of annotations reached (1). 2 annotations were not shown.',
178178
);
179-
expect(setOutputMock).toHaveBeenCalledWith('errors', 0);
179+
expect(setOutputMock).toHaveBeenCalledWith('errors', 1);
180180
expect(setOutputMock).toHaveBeenCalledWith('warnings', 0);
181-
expect(setOutputMock).toHaveBeenCalledWith('notices', 1);
181+
expect(setOutputMock).toHaveBeenCalledWith('notices', 0);
182+
expect(setOutputMock).toHaveBeenCalledWith('total', 1);
183+
});
184+
185+
it('should prioritize errors over warnings and notices when maxAnnotations is reached', async () => {
186+
// This test uses junit-eslint fixture which has both errors and warnings
187+
testInputs.reports = ['junit-eslint|fixtures/junit-eslint.xml'];
188+
testInputs['max-annotations'] = '1';
189+
await main.run();
190+
191+
// Should show the error first, not the warning
192+
expect(errorMock).toHaveBeenCalledWith(
193+
'["Bucket"] is better written in dot notation.',
194+
{
195+
endColumn: undefined,
196+
endLine: undefined,
197+
file: '/home/runner/work/repo-name/repo-name/cypress/plugins/s3-email-client/s3-utils.ts',
198+
startColumn: 28,
199+
startLine: 7,
200+
title: '@typescript-eslint/dot-notation',
201+
},
202+
);
203+
204+
// Warning should not be called since we only allow 1 annotation and error has priority
205+
expect(warningMock).not.toHaveBeenCalledWith(
206+
'Missing JSDoc comment.',
207+
expect.any(Object),
208+
);
209+
210+
expect(setOutputMock).toHaveBeenCalledWith('errors', 1);
211+
expect(setOutputMock).toHaveBeenCalledWith('warnings', 0);
212+
expect(setOutputMock).toHaveBeenCalledWith('notices', 0);
182213
expect(setOutputMock).toHaveBeenCalledWith('total', 1);
183214
});
184215

dist/index.js

Lines changed: 36 additions & 27 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"coverage": "make-coverage-badge --output-path ./badges/coverage.svg",
3030
"format:write": "prettier --write **/*.ts",
3131
"format:check": "prettier --check **/*.ts",
32-
"lint": "npx eslint . -c ./.github/linters/.eslintrc.yml",
32+
"lint": "./node_modules/.bin/eslint . -c ./.github/linters/.eslintrc.yml",
3333
"package": "ncc build src/index.ts --license licenses.txt",
3434
"package:watch": "npm run package -- --watch",
3535
"test": "jest",

src/main.ts

Lines changed: 52 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ export interface Config {
3333

3434
type AnnotationLevel = 'notice' | 'warning' | 'error' | 'ignore';
3535

36+
interface PendingAnnotation {
37+
level: AnnotationLevel;
38+
message: string;
39+
properties: core.AnnotationProperties;
40+
}
41+
3642
export interface ReportMatcher {
3743
/**
3844
* The format of the report e.g. `xml`
@@ -102,8 +108,7 @@ export async function run(): Promise<void> {
102108
core.endGroup();
103109
}
104110

105-
const tally = { errors: 0, warnings: 0, notices: 0, total: 0 };
106-
let maxAnnotationsReached = false;
111+
const allAnnotations: PendingAnnotation[] = [];
107112

108113
for (const [matcherName, files] of reportFiles) {
109114
const matcher = reportMatchers[matcherName];
@@ -114,30 +119,55 @@ export async function run(): Promise<void> {
114119
core.debug(`Parsing ${file}`);
115120
switch (matcher.format) {
116121
case 'xml':
117-
maxAnnotationsReached = await parseXmlReport(
118-
file,
119-
matcher,
120-
tally,
121-
config.maxAnnotations,
122-
);
122+
await parseXmlReport(file, matcher, allAnnotations);
123123
break;
124124
default:
125125
throw new Error(
126126
`Unsupported matcher format in ${matcherName}: ${matcher.format}`,
127127
);
128128
}
129-
if (maxAnnotationsReached) break;
130129
}
131130
core.info(
132-
`Parsed ${tally.total} annotation(s) from ${files.size} report(s)`,
131+
`Parsed ${allAnnotations.length} annotation(s) from ${files.size} report(s)`,
133132
);
134133
core.endGroup();
135-
if (maxAnnotationsReached) break;
136134
}
137-
if (maxAnnotationsReached)
135+
136+
// Sort annotations by priority: errors first, then warnings, then notices
137+
// Ignore level annotations are already filtered out during collection
138+
const priorityOrder: Record<AnnotationLevel, number> = {
139+
error: 0,
140+
warning: 1,
141+
notice: 2,
142+
ignore: 3, // Should not appear in the array, but included for type completeness
143+
};
144+
allAnnotations.sort(
145+
(a, b) => priorityOrder[a.level] - priorityOrder[b.level],
146+
);
147+
148+
// Apply the maxAnnotations limit and create the annotations
149+
const annotationsToCreate = allAnnotations.slice(0, config.maxAnnotations);
150+
const tally = { errors: 0, warnings: 0, notices: 0, total: 0 };
151+
152+
for (const annotation of annotationsToCreate) {
153+
// Type assertion is safe because we filter out 'ignore' level during collection
154+
core[annotation.level as 'error' | 'warning' | 'notice'](
155+
annotation.message,
156+
annotation.properties,
157+
);
158+
if (annotation.level === 'error') tally.errors++;
159+
if (annotation.level === 'warning') tally.warnings++;
160+
if (annotation.level === 'notice') tally.notices++;
161+
tally.total++;
162+
}
163+
164+
if (allAnnotations.length > config.maxAnnotations) {
138165
core.warning(
139-
`Maximum number of annotations reached (${config.maxAnnotations})`,
166+
`Maximum number of annotations reached (${config.maxAnnotations}). ${
167+
allAnnotations.length - config.maxAnnotations
168+
} annotations were not shown.`,
140169
);
170+
}
141171
// Set outputs for other workflow steps to use.
142172
core.setOutput('errors', tally.errors);
143173
core.setOutput('warnings', tally.warnings);
@@ -204,24 +234,21 @@ async function loadConfig(): Promise<Config> {
204234
async function parseXmlReport(
205235
file: string,
206236
matcher: ReportMatcher,
207-
tally: Record<'errors' | 'warnings' | 'notices' | 'total', number>,
208-
maxAnnotations: number,
209-
): Promise<boolean> {
237+
allAnnotations: PendingAnnotation[],
238+
): Promise<void> {
210239
const report = await readFile(file, 'utf8');
211240
core.debug(`Parsing report:\n${report}`);
212241
const doc = new DOMParser().parseFromString(report, 'text/xml');
213242
let items = select(matcher.item, doc);
214243
if (!Array.isArray(items) && isNodeLike(items)) items = [items];
215244
if (!isArrayOfNodes(items)) {
216245
core.warning(`No items found in ${file}`);
217-
return false;
246+
return;
218247
}
219248
core.debug(`Found ${items.length} items in ${file}.`);
220249

221250
for (const item of items) {
222-
core.debug(
223-
`Processing item ${tally.total + 1}/${maxAnnotations}: ${item}.`,
224-
);
251+
core.debug(`Processing item: ${item}.`);
225252
const xpath = xpathSelect(item);
226253
// Figure out the level of the annotation.
227254
let level: AnnotationLevel = 'error';
@@ -239,9 +266,10 @@ async function parseXmlReport(
239266
core.debug('Ignoring item.');
240267
continue;
241268
}
242-
// Create the annotation.
269+
270+
// Create the annotation data.
243271
const message = xpath.string(matcher.message);
244-
const annotation = {
272+
const properties = {
245273
title: matcher.title ? xpath.string(matcher.title) : undefined,
246274
file: matcher.file ? xpath.string(matcher.file) : undefined,
247275
startLine: matcher.startLine
@@ -255,14 +283,8 @@ async function parseXmlReport(
255283
? xpath.number(matcher.endColumn)
256284
: undefined,
257285
} satisfies core.AnnotationProperties;
258-
core[level](message, annotation);
259286

260-
// Count the annotations for the output.
261-
if (level === 'error') tally.errors++;
262-
if (level === 'warning') tally.warnings++;
263-
if (level === 'notice') tally.notices++;
264-
tally.total++;
265-
if (tally.total >= maxAnnotations) return true;
287+
// Collect non-ignore annotations
288+
allAnnotations.push({ level, message, properties });
266289
}
267-
return false;
268290
}

0 commit comments

Comments
 (0)