Skip to content

Commit 0cb38e1

Browse files
committed
feat: major improvements to coverage linting and detailing what is missing, prettier output
1 parent fe05a36 commit 0cb38e1

File tree

2 files changed

+85
-28
lines changed

2 files changed

+85
-28
lines changed

linters/typescript/src/context_linter.ts

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import matter from 'gray-matter';
66
import { ContextdocsLinter } from './contextdocs_linter';
77
import { ContextignoreLinter } from './contextignore_linter';
88
import { getContextFiles, lintFileIfExists, fileExists, printHeader } from './utils/file_utils';
9-
import { ContextValidator, ValidationResult } from './utils/validator';
9+
import { ContextValidator, ValidationResult, SectionValidationResult } from './utils/validator';
1010

1111
export class ContextLinter {
1212
private md: MarkdownIt;
@@ -78,7 +78,14 @@ export class ContextLinter {
7878
} else if (filePath.endsWith('.context.json')) {
7979
result = await this.lintJsonFile(fileContent, filePath);
8080
} else {
81-
result = { isValid: false, coveragePercentage: 0, missingFields: [] };
81+
result = {
82+
isValid: false,
83+
coveragePercentage: 0,
84+
coveredFields: 0,
85+
totalFields: 0,
86+
missingFields: [],
87+
sections: {}
88+
};
8289
}
8390
this.printValidationResult(result, filePath);
8491
return result.isValid;
@@ -87,35 +94,47 @@ export class ContextLinter {
8794

8895
private printValidationResult(result: ValidationResult, filePath: string): void {
8996
const fileName = path.basename(filePath);
90-
const totalFields = result.missingFields.length + Math.round(result.coveragePercentage * 100 / 100);
91-
console.log(` Context coverage: ${result.coveragePercentage.toFixed(2)}% (${totalFields - result.missingFields.length}/${totalFields} fields)`);
97+
console.log(`main context: ${result.coveragePercentage.toFixed(2)}% (${result.coveredFields}/${result.totalFields} top level fields)`);
98+
9299
if (result.missingFields.length > 0) {
93-
console.log(` Missing fields: ${result.missingFields.join(', ')}`);
100+
console.warn(`⚠️ missing: ${result.missingFields.join(', ')}`);
94101
}
102+
103+
for (const [sectionName, sectionResult] of Object.entries(result.sections)) {
104+
console.log(`|- ${sectionName}: ${sectionResult.coveragePercentage.toFixed(2)}% (${sectionResult.coveredFields}/${sectionResult.totalFields} fields)`);
105+
106+
if (sectionResult.missingFields.length > 0) {
107+
console.warn(`⚠️ missing: ${sectionResult.missingFields.join(', ')}`);
108+
}
109+
}
110+
95111
if (result.isValid) {
96-
console.log(` ${fileName} passed validation`);
112+
console.log(`✅ ${fileName} passed validation`);
97113
} else {
98-
console.warn(` ⚠️ Missing fields: ${result.missingFields.join(', ')}`);
114+
console.warn(`${fileName} failed validation`);
99115
}
100116
}
101117

102118
private async lintMarkdownFile(content: string, filePath: string): Promise<ValidationResult> {
103-
console.log(' - Validating Markdown structure');
104-
console.log(' - Checking YAML frontmatter');
105-
106119
try {
107120
const { data: frontmatterData, content: markdownContent } = matter(content);
108121
const validationResult = this.contextValidator.validateContextData(frontmatterData, 'markdown');
109122
const markdownValid = this.validateMarkdownContent(markdownContent.trim());
110123

111124
return {
112-
isValid: validationResult.isValid && markdownValid,
113-
coveragePercentage: validationResult.coveragePercentage,
114-
missingFields: validationResult.missingFields
125+
...validationResult,
126+
isValid: validationResult.isValid && markdownValid
115127
};
116128
} catch (error) {
117129
console.error(` Error parsing Markdown file: ${error}`);
118-
return { isValid: false, coveragePercentage: 0, missingFields: [] };
130+
return {
131+
isValid: false,
132+
coveragePercentage: 0,
133+
coveredFields: 0,
134+
totalFields: 0,
135+
missingFields: [],
136+
sections: {}
137+
};
119138
}
120139
}
121140

@@ -163,7 +182,14 @@ export class ContextLinter {
163182
} else {
164183
console.error(` Error parsing YAML file: ${error}`);
165184
}
166-
return { isValid: false, coveragePercentage: 0, missingFields: [] };
185+
return {
186+
isValid: false,
187+
coveragePercentage: 0,
188+
coveredFields: 0,
189+
totalFields: 0,
190+
missingFields: [],
191+
sections: {}
192+
};
167193
}
168194
}
169195

@@ -179,7 +205,14 @@ export class ContextLinter {
179205
} else {
180206
console.error(` Error parsing JSON file: ${error}`);
181207
}
182-
return { isValid: false, coveragePercentage: 0, missingFields: [] };
208+
return {
209+
isValid: false,
210+
coveragePercentage: 0,
211+
coveredFields: 0,
212+
totalFields: 0,
213+
missingFields: [],
214+
sections: {}
215+
};
183216
}
184217
}
185218

linters/typescript/src/utils/validator.ts

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
import { kebabToCamelCase } from './string_utils';
22
import { allowedTopLevelFields, sectionChecks, listTypes, stringTypes, directoryTypes } from './context_structure';
33

4+
export interface SectionValidationResult {
5+
isValid: boolean;
6+
coveragePercentage: number;
7+
coveredFields: number;
8+
totalFields: number;
9+
missingFields: string[];
10+
}
11+
412
export interface ValidationResult {
513
isValid: boolean;
614
coveragePercentage: number;
15+
coveredFields: number;
16+
totalFields: number;
717
missingFields: string[];
18+
sections: Record<string, SectionValidationResult>;
819
}
920

1021
export class ContextValidator {
@@ -19,6 +30,7 @@ export class ContextValidator {
1930
const coveredFields = new Set<string>();
2031
let isValid = true;
2132
const allMissingFields: string[] = [];
33+
const sections: Record<string, SectionValidationResult> = {};
2234

2335
for (const [field, value] of Object.entries(data)) {
2436
const normalizedField = isJson ? kebabToCamelCase(field, this.kebabToCamelCache) : field;
@@ -34,20 +46,24 @@ export class ContextValidator {
3446

3547
const { coveragePercentage, missingFields } = this.calculateCoverage(coveredFields, allowedTopLevelFields);
3648
allMissingFields.push(...missingFields);
37-
console.log(` Context coverage: ${coveragePercentage.toFixed(2)}% (${coveredFields.size}/${allowedTopLevelFields.size} fields)`);
38-
if (missingFields.length > 0) {
39-
console.log(` Missing top-level fields: ${missingFields.join(', ')}`);
40-
}
4149

4250
for (const section of Object.keys(sectionChecks)) {
4351
if (section in data) {
4452
const sectionResult = this.validateSectionFields(section, data[section] as Record<string, unknown>, isJson);
4553
isValid = sectionResult.isValid && isValid;
4654
allMissingFields.push(...sectionResult.missingFields);
55+
sections[section] = sectionResult;
4756
}
4857
}
4958

50-
return { isValid, coveragePercentage, missingFields: allMissingFields };
59+
return {
60+
isValid,
61+
coveragePercentage,
62+
coveredFields: coveredFields.size,
63+
totalFields: allowedTopLevelFields.size,
64+
missingFields: allMissingFields,
65+
sections
66+
};
5167
}
5268

5369
private validateField(field: string, value: unknown, isJson: boolean): boolean {
@@ -64,7 +80,7 @@ export class ContextValidator {
6480
return isValid;
6581
}
6682

67-
private validateSectionFields(sectionName: string, data: Record<string, unknown>, isJson: boolean): ValidationResult {
83+
private validateSectionFields(sectionName: string, data: Record<string, unknown>, isJson: boolean): SectionValidationResult {
6884
const checks = sectionChecks[sectionName];
6985
const coveredFields = new Set<string>();
7086
let isValid = true;
@@ -83,15 +99,23 @@ export class ContextValidator {
8399
}
84100

85101
const { coveragePercentage, missingFields } = this.calculateCoverage(coveredFields, checks);
86-
console.log(` ${sectionName} coverage: ${coveragePercentage.toFixed(2)}% (${coveredFields.size}/${checks.size} fields)`);
87-
if (missingFields.length > 0) {
88-
console.log(` Missing fields in '${sectionName}' section: ${missingFields.join(', ')}`);
89-
}
90102

91-
return { isValid, coveragePercentage, missingFields };
103+
return {
104+
isValid,
105+
coveragePercentage,
106+
coveredFields: coveredFields.size,
107+
totalFields: checks.size,
108+
missingFields
109+
};
92110
}
93111

94-
return { isValid: true, coveragePercentage: 100, missingFields: [] };
112+
return {
113+
isValid: true,
114+
coveragePercentage: 100,
115+
coveredFields: 0,
116+
totalFields: 0,
117+
missingFields: []
118+
};
95119
}
96120

97121
private calculateCoverage(coveredFields: Set<string>, expectedFields: Set<string>): { coveragePercentage: number, missingFields: string[] } {

0 commit comments

Comments
 (0)