Skip to content
This repository was archived by the owner on Oct 10, 2025. It is now read-only.

Commit 329729d

Browse files
committed
refactor: Extract context validation logic into a separate class
1 parent b170efd commit 329729d

File tree

2 files changed

+72
-63
lines changed

2 files changed

+72
-63
lines changed

linters/typescript/src/context_linter.ts

Lines changed: 6 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,19 @@ import MarkdownIt from 'markdown-it';
44
import { ContextdocsLinter } from './contextdocs_linter';
55
import { ContextignoreLinter } from './contextignore_linter';
66
import { getContextFiles, lintFileIfExists, fileExists, printHeader } from './utils/file_utils';
7-
import { kebabToCamelCase } from './utils/string_utils';
8-
import { allowedTopLevelFields, sectionChecks, listTypes, stringTypes, directoryTypes } from './utils/context_structure';
7+
import { ContextValidator } from './utils/validator';
98

109
export class ContextLinter {
1110
private md: MarkdownIt;
12-
private kebabToCamelCache: Map<string, string>;
1311
private contextdocsLinter: ContextdocsLinter;
1412
private contextignoreLinter: ContextignoreLinter;
13+
private contextValidator: ContextValidator;
1514

1615
constructor() {
1716
this.md = new MarkdownIt();
18-
this.kebabToCamelCache = new Map();
1917
this.contextdocsLinter = new ContextdocsLinter();
2018
this.contextignoreLinter = new ContextignoreLinter();
19+
this.contextValidator = new ContextValidator();
2120
}
2221

2322
public async lintDirectory(directoryPath: string, packageVersion: string): Promise<void> {
@@ -84,70 +83,14 @@ export class ContextLinter {
8483

8584
try {
8685
const frontmatterData = yaml.load(frontmatter) as Record<string, unknown>;
87-
this.validateContextData(frontmatterData, 'markdown');
86+
this.contextValidator.validateContextData(frontmatterData, 'markdown');
8887
} catch (error) {
8988
console.error(` Error parsing YAML frontmatter: ${error}`);
9089
}
9190

9291
this.validateMarkdownContent(markdownContent);
9392
}
9493

95-
private validateContextData(data: Record<string, unknown>, format: 'markdown' | 'yaml' | 'json'): void {
96-
const isJson = format === 'json';
97-
const coveredFields = new Set<string>();
98-
99-
for (const [field, value] of Object.entries(data)) {
100-
const normalizedField = isJson ? kebabToCamelCase(field, this.kebabToCamelCache) : field;
101-
102-
if (allowedTopLevelFields.has(normalizedField)) {
103-
coveredFields.add(normalizedField);
104-
this.validateField(normalizedField, value, isJson);
105-
} else {
106-
console.warn(` Warning: Unexpected top-level field '${normalizedField}'.`);
107-
}
108-
}
109-
110-
const coveragePercentage = (coveredFields.size / allowedTopLevelFields.size) * 100;
111-
console.log(` Context coverage: ${coveragePercentage.toFixed(2)}% (${coveredFields.size}/${allowedTopLevelFields.size} fields)`);
112-
113-
for (const section of Object.keys(sectionChecks)) {
114-
if (section in data) {
115-
this.validateSectionFields(section, data[section] as Record<string, unknown>, isJson);
116-
}
117-
}
118-
}
119-
120-
private validateField(field: string, value: unknown, isJson: boolean): void {
121-
if (listTypes.has(field) && !Array.isArray(value)) {
122-
console.error(` Error: Field '${field}' should be an array.`);
123-
} else if (stringTypes.has(field) && typeof value !== 'string') {
124-
console.error(` Error: Field '${field}' should be a string.`);
125-
} else if (directoryTypes.has(field)) {
126-
// Additional validation for directory types can be added here
127-
}
128-
}
129-
130-
private validateSectionFields(sectionName: string, data: Record<string, unknown>, isJson: boolean): void {
131-
const checks = sectionChecks[sectionName];
132-
const coveredFields = new Set<string>();
133-
134-
if (checks) {
135-
for (const [field, value] of Object.entries(data)) {
136-
const normalizedField = isJson ? kebabToCamelCase(field, this.kebabToCamelCache) : field;
137-
138-
if (checks.has(normalizedField)) {
139-
coveredFields.add(normalizedField);
140-
this.validateField(normalizedField, value, isJson);
141-
} else {
142-
console.warn(` Warning: Unexpected field '${normalizedField}' in '${sectionName}' section.`);
143-
}
144-
}
145-
146-
const coveragePercentage = (coveredFields.size / checks.size) * 100;
147-
console.log(` ${sectionName} coverage: ${coveragePercentage.toFixed(2)}% (${coveredFields.size}/${checks.size} fields)`);
148-
}
149-
}
150-
15194
private validateMarkdownContent(content: string): void {
15295
const tokens = this.md.parse(content, {});
15396
let hasTitle = false;
@@ -180,7 +123,7 @@ export class ContextLinter {
180123

181124
try {
182125
const yamlData = yaml.load(content) as Record<string, unknown>;
183-
this.validateContextData(yamlData, 'yaml');
126+
this.contextValidator.validateContextData(yamlData, 'yaml');
184127
} catch (error) {
185128
console.error(` Error parsing YAML file: ${error}`);
186129
}
@@ -192,7 +135,7 @@ export class ContextLinter {
192135

193136
try {
194137
const jsonData = JSON.parse(content) as Record<string, unknown>;
195-
this.validateContextData(jsonData, 'json');
138+
this.contextValidator.validateContextData(jsonData, 'json');
196139
} catch (error) {
197140
console.error(` Error parsing JSON file: ${error}`);
198141
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { kebabToCamelCase } from './string_utils';
2+
import { allowedTopLevelFields, sectionChecks, listTypes, stringTypes, directoryTypes } from './context_structure';
3+
4+
export class ContextValidator {
5+
private kebabToCamelCache: Map<string, string>;
6+
7+
constructor() {
8+
this.kebabToCamelCache = new Map();
9+
}
10+
11+
public validateContextData(data: Record<string, unknown>, format: 'markdown' | 'yaml' | 'json'): void {
12+
const isJson = format === 'json';
13+
const coveredFields = new Set<string>();
14+
15+
for (const [field, value] of Object.entries(data)) {
16+
const normalizedField = isJson ? kebabToCamelCase(field, this.kebabToCamelCache) : field;
17+
18+
if (allowedTopLevelFields.has(normalizedField)) {
19+
coveredFields.add(normalizedField);
20+
this.validateField(normalizedField, value, isJson);
21+
} else {
22+
console.warn(` Warning: Unexpected top-level field '${normalizedField}'.`);
23+
}
24+
}
25+
26+
const coveragePercentage = (coveredFields.size / allowedTopLevelFields.size) * 100;
27+
console.log(` Context coverage: ${coveragePercentage.toFixed(2)}% (${coveredFields.size}/${allowedTopLevelFields.size} fields)`);
28+
29+
for (const section of Object.keys(sectionChecks)) {
30+
if (section in data) {
31+
this.validateSectionFields(section, data[section] as Record<string, unknown>, isJson);
32+
}
33+
}
34+
}
35+
36+
private validateField(field: string, value: unknown, isJson: boolean): void {
37+
if (listTypes.has(field) && !Array.isArray(value)) {
38+
console.error(` Error: Field '${field}' should be an array.`);
39+
} else if (stringTypes.has(field) && typeof value !== 'string') {
40+
console.error(` Error: Field '${field}' should be a string.`);
41+
} else if (directoryTypes.has(field)) {
42+
// Additional validation for directory types can be added here
43+
}
44+
}
45+
46+
private validateSectionFields(sectionName: string, data: Record<string, unknown>, isJson: boolean): void {
47+
const checks = sectionChecks[sectionName];
48+
const coveredFields = new Set<string>();
49+
50+
if (checks) {
51+
for (const [field, value] of Object.entries(data)) {
52+
const normalizedField = isJson ? kebabToCamelCase(field, this.kebabToCamelCache) : field;
53+
54+
if (checks.has(normalizedField)) {
55+
coveredFields.add(normalizedField);
56+
this.validateField(normalizedField, value, isJson);
57+
} else {
58+
console.warn(` Warning: Unexpected field '${normalizedField}' in '${sectionName}' section.`);
59+
}
60+
}
61+
62+
const coveragePercentage = (coveredFields.size / checks.size) * 100;
63+
console.log(` ${sectionName} coverage: ${coveragePercentage.toFixed(2)}% (${coveredFields.size}/${checks.size} fields)`);
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)