@harness-engineering/linter-gen 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +161 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/engine/context-builder.d.ts +24 -0
- package/dist/engine/context-builder.d.ts.map +1 -0
- package/dist/engine/context-builder.js +33 -0
- package/dist/engine/context-builder.js.map +1 -0
- package/dist/engine/template-loader.d.ts +27 -0
- package/dist/engine/template-loader.d.ts.map +1 -0
- package/dist/engine/template-loader.js +84 -0
- package/dist/engine/template-loader.js.map +1 -0
- package/dist/engine/template-renderer.d.ts +17 -0
- package/dist/engine/template-renderer.d.ts.map +1 -0
- package/dist/engine/template-renderer.js +44 -0
- package/dist/engine/template-renderer.js.map +1 -0
- package/dist/generator/index-generator.d.ts +5 -0
- package/dist/generator/index-generator.d.ts.map +1 -0
- package/dist/generator/index-generator.js +36 -0
- package/dist/generator/index-generator.js.map +1 -0
- package/dist/generator/orchestrator.d.ts +57 -0
- package/dist/generator/orchestrator.d.ts.map +1 -0
- package/dist/generator/orchestrator.js +106 -0
- package/dist/generator/orchestrator.js.map +1 -0
- package/dist/generator/rule-generator.d.ts +21 -0
- package/dist/generator/rule-generator.d.ts.map +1 -0
- package/dist/generator/rule-generator.js +30 -0
- package/dist/generator/rule-generator.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/config-parser.d.ts +20 -0
- package/dist/parser/config-parser.d.ts.map +1 -0
- package/dist/parser/config-parser.js +62 -0
- package/dist/parser/config-parser.js.map +1 -0
- package/dist/schema/linter-config.d.ts +79 -0
- package/dist/schema/linter-config.d.ts.map +1 -0
- package/dist/schema/linter-config.js +28 -0
- package/dist/schema/linter-config.js.map +1 -0
- package/dist/templates/import-restriction.ts.hbs +2 -0
- package/dist/templates/templates/boundary-validation.ts.hbs +99 -0
- package/dist/templates/templates/dependency-graph.ts.hbs +123 -0
- package/dist/templates/templates/import-restriction.ts.hbs +72 -0
- package/package.json +50 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-generator.js","sourceRoot":"","sources":["../../src/generator/index-generator.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,SAAS,WAAW,CAAC,GAAW;IAC9B,OAAO,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,CAAS,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;AACtF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,SAAmB;IAC/C,MAAM,OAAO,GAAG,SAAS;SACtB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QAChC,OAAO,UAAU,KAAK,YAAY,IAAI,IAAI,CAAC;IAC7C,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,MAAM,WAAW,GAAG,SAAS;SAC1B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QAChC,OAAO,MAAM,IAAI,MAAM,KAAK,GAAG,CAAC;IAClC,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,MAAM,YAAY,GAAG,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAE3D,OAAO;;;EAGP,OAAO;;;EAGP,WAAW;;;WAGF,YAAY;CACtB,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type ParseError } from '../parser/config-parser.js';
|
|
2
|
+
import { type TemplateLoadError } from '../engine/template-loader.js';
|
|
3
|
+
import type { TemplateError } from '../engine/template-renderer.js';
|
|
4
|
+
export interface GenerateOptions {
|
|
5
|
+
/** Path to harness-linter.yml */
|
|
6
|
+
configPath: string;
|
|
7
|
+
/** Override output directory from config */
|
|
8
|
+
outputDir?: string;
|
|
9
|
+
/** Remove existing files before generating */
|
|
10
|
+
clean?: boolean;
|
|
11
|
+
/** Preview without writing files */
|
|
12
|
+
dryRun?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface ValidateOptions {
|
|
15
|
+
configPath: string;
|
|
16
|
+
}
|
|
17
|
+
export type GeneratorError = {
|
|
18
|
+
type: 'parse';
|
|
19
|
+
error: ParseError;
|
|
20
|
+
} | {
|
|
21
|
+
type: 'template';
|
|
22
|
+
error: TemplateLoadError;
|
|
23
|
+
ruleName: string;
|
|
24
|
+
} | {
|
|
25
|
+
type: 'render';
|
|
26
|
+
error: TemplateError;
|
|
27
|
+
ruleName: string;
|
|
28
|
+
} | {
|
|
29
|
+
type: 'write';
|
|
30
|
+
error: Error;
|
|
31
|
+
path: string;
|
|
32
|
+
};
|
|
33
|
+
export type GenerateResult = {
|
|
34
|
+
success: true;
|
|
35
|
+
rulesGenerated: string[];
|
|
36
|
+
outputDir: string;
|
|
37
|
+
dryRun: boolean;
|
|
38
|
+
} | {
|
|
39
|
+
success: false;
|
|
40
|
+
errors: GeneratorError[];
|
|
41
|
+
};
|
|
42
|
+
export type ValidateResult = {
|
|
43
|
+
success: true;
|
|
44
|
+
ruleCount: number;
|
|
45
|
+
} | {
|
|
46
|
+
success: false;
|
|
47
|
+
error: ParseError;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Validate a harness-linter.yml config without generating
|
|
51
|
+
*/
|
|
52
|
+
export declare function validate(options: ValidateOptions): Promise<ValidateResult>;
|
|
53
|
+
/**
|
|
54
|
+
* Generate ESLint rules from harness-linter.yml config
|
|
55
|
+
*/
|
|
56
|
+
export declare function generate(options: GenerateOptions): Promise<GenerateResult>;
|
|
57
|
+
//# sourceMappingURL=orchestrator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/generator/orchestrator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAEpE,MAAM,WAAW,eAAe;IAC9B,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oCAAoC;IACpC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,iBAAiB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,cAAc,GACtB;IACE,OAAO,EAAE,IAAI,CAAC;IACd,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;CACjB,GACD;IACE,OAAO,EAAE,KAAK,CAAC;IACf,MAAM,EAAE,cAAc,EAAE,CAAC;CAC1B,CAAC;AAEN,MAAM,MAAM,cAAc,GACtB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,CAAC;AAE1C;;GAEG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAMhF;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CA6FhF"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// src/generator/orchestrator.ts
|
|
2
|
+
import * as fs from 'fs/promises';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { parseConfig } from '../parser/config-parser.js';
|
|
5
|
+
import { loadTemplate } from '../engine/template-loader.js';
|
|
6
|
+
import { generateRule } from './rule-generator.js';
|
|
7
|
+
import { generateIndex } from './index-generator.js';
|
|
8
|
+
/**
|
|
9
|
+
* Validate a harness-linter.yml config without generating
|
|
10
|
+
*/
|
|
11
|
+
export async function validate(options) {
|
|
12
|
+
const parseResult = await parseConfig(options.configPath);
|
|
13
|
+
if (!parseResult.success) {
|
|
14
|
+
return { success: false, error: parseResult.error };
|
|
15
|
+
}
|
|
16
|
+
return { success: true, ruleCount: parseResult.data.rules.length };
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generate ESLint rules from harness-linter.yml config
|
|
20
|
+
*/
|
|
21
|
+
export async function generate(options) {
|
|
22
|
+
const errors = [];
|
|
23
|
+
// Parse config
|
|
24
|
+
const parseResult = await parseConfig(options.configPath);
|
|
25
|
+
if (!parseResult.success) {
|
|
26
|
+
return { success: false, errors: [{ type: 'parse', error: parseResult.error }] };
|
|
27
|
+
}
|
|
28
|
+
const config = parseResult.data;
|
|
29
|
+
const configDir = path.dirname(path.resolve(options.configPath));
|
|
30
|
+
const outputDir = options.outputDir
|
|
31
|
+
? path.resolve(options.outputDir)
|
|
32
|
+
: path.resolve(configDir, config.output);
|
|
33
|
+
// Clean output directory if requested
|
|
34
|
+
if (options.clean && !options.dryRun) {
|
|
35
|
+
try {
|
|
36
|
+
await fs.rm(outputDir, { recursive: true, force: true });
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Ignore errors - directory might not exist
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Create output directory
|
|
43
|
+
if (!options.dryRun) {
|
|
44
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
const generatedRules = [];
|
|
47
|
+
// Generate each rule
|
|
48
|
+
for (const rule of config.rules) {
|
|
49
|
+
// Load template
|
|
50
|
+
const templateResult = await loadTemplate(rule.type, config.templates, configDir);
|
|
51
|
+
if (!templateResult.success) {
|
|
52
|
+
errors.push({
|
|
53
|
+
type: 'template',
|
|
54
|
+
error: templateResult.error,
|
|
55
|
+
ruleName: rule.name,
|
|
56
|
+
});
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
// Generate rule
|
|
60
|
+
const ruleResult = generateRule(rule, templateResult.source, outputDir, options.configPath);
|
|
61
|
+
if (!ruleResult.success) {
|
|
62
|
+
errors.push({
|
|
63
|
+
type: 'render',
|
|
64
|
+
error: ruleResult.error,
|
|
65
|
+
ruleName: ruleResult.ruleName,
|
|
66
|
+
});
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
// Write file
|
|
70
|
+
if (!options.dryRun) {
|
|
71
|
+
try {
|
|
72
|
+
await fs.writeFile(ruleResult.rule.outputPath, ruleResult.rule.content, 'utf-8');
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
errors.push({
|
|
76
|
+
type: 'write',
|
|
77
|
+
error: err,
|
|
78
|
+
path: ruleResult.rule.outputPath,
|
|
79
|
+
});
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
generatedRules.push(rule.name);
|
|
84
|
+
}
|
|
85
|
+
// Generate index file
|
|
86
|
+
if (generatedRules.length > 0 && !options.dryRun) {
|
|
87
|
+
const indexContent = generateIndex(generatedRules);
|
|
88
|
+
const indexPath = path.join(outputDir, 'index.ts');
|
|
89
|
+
try {
|
|
90
|
+
await fs.writeFile(indexPath, indexContent, 'utf-8');
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
errors.push({ type: 'write', error: err, path: indexPath });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (errors.length > 0) {
|
|
97
|
+
return { success: false, errors };
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
success: true,
|
|
101
|
+
rulesGenerated: generatedRules,
|
|
102
|
+
outputDir,
|
|
103
|
+
dryRun: options.dryRun ?? false,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=orchestrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../src/generator/orchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { RuleConfig } from '../schema/linter-config.js';
|
|
2
|
+
import type { TemplateSource } from '../engine/template-loader.js';
|
|
3
|
+
import { TemplateError } from '../engine/template-renderer.js';
|
|
4
|
+
export interface GeneratedRule {
|
|
5
|
+
name: string;
|
|
6
|
+
outputPath: string;
|
|
7
|
+
content: string;
|
|
8
|
+
}
|
|
9
|
+
export type GenerateRuleResult = {
|
|
10
|
+
success: true;
|
|
11
|
+
rule: GeneratedRule;
|
|
12
|
+
} | {
|
|
13
|
+
success: false;
|
|
14
|
+
error: TemplateError;
|
|
15
|
+
ruleName: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Generate a single ESLint rule file from config and template
|
|
19
|
+
*/
|
|
20
|
+
export declare function generateRule(rule: RuleConfig, template: TemplateSource, outputDir: string, configPath: string): GenerateRuleResult;
|
|
21
|
+
//# sourceMappingURL=rule-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-generator.d.ts","sourceRoot":"","sources":["../../src/generator/rule-generator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,EAAkB,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAE/E,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,aAAa,CAAA;CAAE,GACtC;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/D;;GAEG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,cAAc,EACxB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,kBAAkB,CAyBpB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { buildRuleContext } from '../engine/context-builder.js';
|
|
3
|
+
import { renderTemplate } from '../engine/template-renderer.js';
|
|
4
|
+
/**
|
|
5
|
+
* Generate a single ESLint rule file from config and template
|
|
6
|
+
*/
|
|
7
|
+
export function generateRule(rule, template, outputDir, configPath) {
|
|
8
|
+
// Build template context
|
|
9
|
+
const context = buildRuleContext(rule, configPath);
|
|
10
|
+
// Render template
|
|
11
|
+
const renderResult = renderTemplate(template.content, context);
|
|
12
|
+
if (!renderResult.success) {
|
|
13
|
+
return {
|
|
14
|
+
success: false,
|
|
15
|
+
error: renderResult.error,
|
|
16
|
+
ruleName: rule.name,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
// Construct output path
|
|
20
|
+
const outputPath = path.join(outputDir, `${rule.name}.ts`);
|
|
21
|
+
return {
|
|
22
|
+
success: true,
|
|
23
|
+
rule: {
|
|
24
|
+
name: rule.name,
|
|
25
|
+
outputPath,
|
|
26
|
+
content: renderResult.output,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=rule-generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-generator.js","sourceRoot":"","sources":["../../src/generator/rule-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAG7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAiB,MAAM,gCAAgC,CAAC;AAY/E;;GAEG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAgB,EAChB,QAAwB,EACxB,SAAiB,EACjB,UAAkB;IAElB,yBAAyB;IACzB,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAEnD,kBAAkB;IAClB,MAAM,YAAY,GAAG,cAAc,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC/D,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;QAC1B,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,YAAY,CAAC,KAAK;YACzB,QAAQ,EAAE,IAAI,CAAC,IAAI;SACpB,CAAC;IACJ,CAAC;IAED,wBAAwB;IACxB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC;IAE3D,OAAO;QACL,OAAO,EAAE,IAAI;QACb,IAAI,EAAE;YACJ,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,UAAU;YACV,OAAO,EAAE,YAAY,CAAC,MAAM;SAC7B;KACF,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @harness-engineering/linter-gen
|
|
3
|
+
*
|
|
4
|
+
* Generate ESLint rules from YAML configuration
|
|
5
|
+
*/
|
|
6
|
+
export { generate, validate } from './generator/orchestrator.js';
|
|
7
|
+
export type { GenerateOptions, ValidateOptions, GenerateResult, ValidateResult, GeneratorError, } from './generator/orchestrator.js';
|
|
8
|
+
export { LinterConfigSchema, RuleConfigSchema } from './schema/linter-config.js';
|
|
9
|
+
export type { LinterConfig, RuleConfig } from './schema/linter-config.js';
|
|
10
|
+
export type { RuleContext } from './engine/context-builder.js';
|
|
11
|
+
export type { TemplateSource, TemplateSourceType } from './engine/template-loader.js';
|
|
12
|
+
export { ParseError } from './parser/config-parser.js';
|
|
13
|
+
export { TemplateLoadError } from './engine/template-loader.js';
|
|
14
|
+
export { TemplateError } from './engine/template-renderer.js';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AACjE,YAAY,EACV,eAAe,EACf,eAAe,EACf,cAAc,EACd,cAAc,EACd,cAAc,GACf,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACjF,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAG1E,YAAY,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAGtF,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @harness-engineering/linter-gen
|
|
3
|
+
*
|
|
4
|
+
* Generate ESLint rules from YAML configuration
|
|
5
|
+
*/
|
|
6
|
+
// Main API
|
|
7
|
+
export { generate, validate } from './generator/orchestrator.js';
|
|
8
|
+
// Schema types
|
|
9
|
+
export { LinterConfigSchema, RuleConfigSchema } from './schema/linter-config.js';
|
|
10
|
+
// Error types
|
|
11
|
+
export { ParseError } from './parser/config-parser.js';
|
|
12
|
+
export { TemplateLoadError } from './engine/template-loader.js';
|
|
13
|
+
export { TemplateError } from './engine/template-renderer.js';
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,WAAW;AACX,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AASjE,eAAe;AACf,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAOjF,cAAc;AACd,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type LinterConfig } from '../schema/linter-config.js';
|
|
2
|
+
export type ParseErrorCode = 'FILE_NOT_FOUND' | 'FILE_READ_ERROR' | 'YAML_PARSE_ERROR' | 'VALIDATION_ERROR';
|
|
3
|
+
export declare class ParseError extends Error {
|
|
4
|
+
readonly code: ParseErrorCode;
|
|
5
|
+
readonly cause?: unknown | undefined;
|
|
6
|
+
constructor(message: string, code: ParseErrorCode, cause?: unknown | undefined);
|
|
7
|
+
}
|
|
8
|
+
export type ParseResult = {
|
|
9
|
+
success: true;
|
|
10
|
+
data: LinterConfig;
|
|
11
|
+
configPath: string;
|
|
12
|
+
} | {
|
|
13
|
+
success: false;
|
|
14
|
+
error: ParseError;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Parse and validate a harness-linter.yml config file
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseConfig(configPath: string): Promise<ParseResult>;
|
|
20
|
+
//# sourceMappingURL=config-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-parser.d.ts","sourceRoot":"","sources":["../../src/parser/config-parser.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAEnF,MAAM,MAAM,cAAc,GACtB,gBAAgB,GAChB,iBAAiB,GACjB,kBAAkB,GAClB,kBAAkB,CAAC;AAEvB,qBAAa,UAAW,SAAQ,KAAK;aAGjB,IAAI,EAAE,cAAc;aACpB,KAAK,CAAC,EAAE,OAAO;gBAF/B,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,cAAc,EACpB,KAAK,CAAC,EAAE,OAAO,YAAA;CAKlC;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,YAAY,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACzD;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,CAAC;AAE1C;;GAEG;AACH,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAgD1E"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import * as fs from 'fs/promises';
|
|
3
|
+
import * as yaml from 'yaml';
|
|
4
|
+
import { LinterConfigSchema } from '../schema/linter-config.js';
|
|
5
|
+
export class ParseError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
cause;
|
|
8
|
+
constructor(message, code, cause) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.cause = cause;
|
|
12
|
+
this.name = 'ParseError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parse and validate a harness-linter.yml config file
|
|
17
|
+
*/
|
|
18
|
+
export async function parseConfig(configPath) {
|
|
19
|
+
// Read file
|
|
20
|
+
let content;
|
|
21
|
+
try {
|
|
22
|
+
content = await fs.readFile(configPath, 'utf-8');
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
if (err.code === 'ENOENT') {
|
|
26
|
+
return {
|
|
27
|
+
success: false,
|
|
28
|
+
error: new ParseError(`Config file not found: ${configPath}`, 'FILE_NOT_FOUND', err),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
success: false,
|
|
33
|
+
error: new ParseError(`Failed to read config file: ${configPath}`, 'FILE_READ_ERROR', err),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// Parse YAML
|
|
37
|
+
let parsed;
|
|
38
|
+
try {
|
|
39
|
+
parsed = yaml.parse(content);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
return {
|
|
43
|
+
success: false,
|
|
44
|
+
error: new ParseError(`Invalid YAML syntax in ${configPath}: ${err.message}`, 'YAML_PARSE_ERROR', err),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// Validate with Zod
|
|
48
|
+
const result = LinterConfigSchema.safeParse(parsed);
|
|
49
|
+
if (!result.success) {
|
|
50
|
+
const issues = result.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ');
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
error: new ParseError(`Invalid config: ${issues}`, 'VALIDATION_ERROR', result.error),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
success: true,
|
|
58
|
+
data: result.data,
|
|
59
|
+
configPath,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=config-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-parser.js","sourceRoot":"","sources":["../../src/parser/config-parser.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAqB,MAAM,4BAA4B,CAAC;AAQnF,MAAM,OAAO,UAAW,SAAQ,KAAK;IAGjB;IACA;IAHlB,YACE,OAAe,EACC,IAAoB,EACpB,KAAe;QAE/B,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,SAAI,GAAJ,IAAI,CAAgB;QACpB,UAAK,GAAL,KAAK,CAAU;QAG/B,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF;AAMD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,UAAkB;IAClD,YAAY;IACZ,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAAyB,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACjD,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,IAAI,UAAU,CAAC,0BAA0B,UAAU,EAAE,EAAE,gBAAgB,EAAE,GAAG,CAAC;aACrF,CAAC;QACJ,CAAC;QACD,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,IAAI,UAAU,CAAC,+BAA+B,UAAU,EAAE,EAAE,iBAAiB,EAAE,GAAG,CAAC;SAC3F,CAAC;IACJ,CAAC;IAED,aAAa;IACb,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,IAAI,UAAU,CACnB,0BAA0B,UAAU,KAAM,GAAa,CAAC,OAAO,EAAE,EACjE,kBAAkB,EAClB,GAAG,CACJ;SACF,CAAC;IACJ,CAAC;IAED,oBAAoB;IACpB,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACpD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9F,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,IAAI,UAAU,CAAC,mBAAmB,MAAM,EAAE,EAAE,kBAAkB,EAAE,MAAM,CAAC,KAAK,CAAC;SACrF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,UAAU;KACX,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Schema for a single rule configuration in harness-linter.yml
|
|
4
|
+
*/
|
|
5
|
+
export declare const RuleConfigSchema: z.ZodObject<{
|
|
6
|
+
/** Rule name in kebab-case (e.g., 'no-ui-in-services') */
|
|
7
|
+
name: z.ZodString;
|
|
8
|
+
/** Rule type - determines which template to use */
|
|
9
|
+
type: z.ZodString;
|
|
10
|
+
/** ESLint severity level */
|
|
11
|
+
severity: z.ZodDefault<z.ZodEnum<["error", "warn", "off"]>>;
|
|
12
|
+
/** Template-specific configuration */
|
|
13
|
+
config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
14
|
+
}, "strip", z.ZodTypeAny, {
|
|
15
|
+
name: string;
|
|
16
|
+
type: string;
|
|
17
|
+
severity: "error" | "warn" | "off";
|
|
18
|
+
config: Record<string, unknown>;
|
|
19
|
+
}, {
|
|
20
|
+
name: string;
|
|
21
|
+
type: string;
|
|
22
|
+
config: Record<string, unknown>;
|
|
23
|
+
severity?: "error" | "warn" | "off" | undefined;
|
|
24
|
+
}>;
|
|
25
|
+
export type RuleConfig = z.infer<typeof RuleConfigSchema>;
|
|
26
|
+
/**
|
|
27
|
+
* Schema for the complete harness-linter.yml configuration
|
|
28
|
+
*/
|
|
29
|
+
export declare const LinterConfigSchema: z.ZodObject<{
|
|
30
|
+
/** Config version - currently only 1 is supported */
|
|
31
|
+
version: z.ZodLiteral<1>;
|
|
32
|
+
/** Output directory for generated rules */
|
|
33
|
+
output: z.ZodString;
|
|
34
|
+
/** Optional explicit template path mappings (type → path) */
|
|
35
|
+
templates: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
36
|
+
/** Rules to generate */
|
|
37
|
+
rules: z.ZodArray<z.ZodObject<{
|
|
38
|
+
/** Rule name in kebab-case (e.g., 'no-ui-in-services') */
|
|
39
|
+
name: z.ZodString;
|
|
40
|
+
/** Rule type - determines which template to use */
|
|
41
|
+
type: z.ZodString;
|
|
42
|
+
/** ESLint severity level */
|
|
43
|
+
severity: z.ZodDefault<z.ZodEnum<["error", "warn", "off"]>>;
|
|
44
|
+
/** Template-specific configuration */
|
|
45
|
+
config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
46
|
+
}, "strip", z.ZodTypeAny, {
|
|
47
|
+
name: string;
|
|
48
|
+
type: string;
|
|
49
|
+
severity: "error" | "warn" | "off";
|
|
50
|
+
config: Record<string, unknown>;
|
|
51
|
+
}, {
|
|
52
|
+
name: string;
|
|
53
|
+
type: string;
|
|
54
|
+
config: Record<string, unknown>;
|
|
55
|
+
severity?: "error" | "warn" | "off" | undefined;
|
|
56
|
+
}>, "many">;
|
|
57
|
+
}, "strip", z.ZodTypeAny, {
|
|
58
|
+
version: 1;
|
|
59
|
+
output: string;
|
|
60
|
+
rules: {
|
|
61
|
+
name: string;
|
|
62
|
+
type: string;
|
|
63
|
+
severity: "error" | "warn" | "off";
|
|
64
|
+
config: Record<string, unknown>;
|
|
65
|
+
}[];
|
|
66
|
+
templates?: Record<string, string> | undefined;
|
|
67
|
+
}, {
|
|
68
|
+
version: 1;
|
|
69
|
+
output: string;
|
|
70
|
+
rules: {
|
|
71
|
+
name: string;
|
|
72
|
+
type: string;
|
|
73
|
+
config: Record<string, unknown>;
|
|
74
|
+
severity?: "error" | "warn" | "off" | undefined;
|
|
75
|
+
}[];
|
|
76
|
+
templates?: Record<string, string> | undefined;
|
|
77
|
+
}>;
|
|
78
|
+
export type LinterConfig = z.infer<typeof LinterConfigSchema>;
|
|
79
|
+
//# sourceMappingURL=linter-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linter-config.d.ts","sourceRoot":"","sources":["../../src/schema/linter-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,eAAO,MAAM,gBAAgB;IAC3B,0DAA0D;;IAE1D,mDAAmD;;IAEnD,4BAA4B;;IAE5B,sCAAsC;;;;;;;;;;;;EAEtC,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D;;GAEG;AACH,eAAO,MAAM,kBAAkB;IAC7B,qDAAqD;;IAErD,2CAA2C;;IAE3C,6DAA6D;;IAE7D,wBAAwB;;QAtBxB,0DAA0D;;QAE1D,mDAAmD;;QAEnD,4BAA4B;;QAE5B,sCAAsC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkBtC,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Schema for a single rule configuration in harness-linter.yml
|
|
4
|
+
*/
|
|
5
|
+
export const RuleConfigSchema = z.object({
|
|
6
|
+
/** Rule name in kebab-case (e.g., 'no-ui-in-services') */
|
|
7
|
+
name: z.string().regex(/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/, 'Rule name must be kebab-case'),
|
|
8
|
+
/** Rule type - determines which template to use */
|
|
9
|
+
type: z.string().min(1),
|
|
10
|
+
/** ESLint severity level */
|
|
11
|
+
severity: z.enum(['error', 'warn', 'off']).default('error'),
|
|
12
|
+
/** Template-specific configuration */
|
|
13
|
+
config: z.record(z.unknown()),
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Schema for the complete harness-linter.yml configuration
|
|
17
|
+
*/
|
|
18
|
+
export const LinterConfigSchema = z.object({
|
|
19
|
+
/** Config version - currently only 1 is supported */
|
|
20
|
+
version: z.literal(1),
|
|
21
|
+
/** Output directory for generated rules */
|
|
22
|
+
output: z.string().min(1),
|
|
23
|
+
/** Optional explicit template path mappings (type → path) */
|
|
24
|
+
templates: z.record(z.string()).optional(),
|
|
25
|
+
/** Rules to generate */
|
|
26
|
+
rules: z.array(RuleConfigSchema).min(1, 'At least one rule is required'),
|
|
27
|
+
});
|
|
28
|
+
//# sourceMappingURL=linter-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linter-config.js","sourceRoot":"","sources":["../../src/schema/linter-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,0DAA0D;IAC1D,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,+BAA+B,EAAE,8BAA8B,CAAC;IACvF,mDAAmD;IACnD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,4BAA4B;IAC5B,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;IAC3D,sCAAsC;IACtC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;CAC9B,CAAC,CAAC;AAIH;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,qDAAqD;IACrD,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACrB,2CAA2C;IAC3C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACzB,6DAA6D;IAC7D,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IAC1C,wBAAwB;IACxB,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,+BAA+B,CAAC;CACzE,CAAC,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Generated by @harness-engineering/linter-gen
|
|
2
|
+
// Do not edit manually - regenerate from harness-linter.yml
|
|
3
|
+
// Config: {{meta.configPath}}
|
|
4
|
+
|
|
5
|
+
import { ESLintUtils, type TSESTree } from '@typescript-eslint/utils';
|
|
6
|
+
import { minimatch } from 'minimatch';
|
|
7
|
+
|
|
8
|
+
const createRule = ESLintUtils.RuleCreator(
|
|
9
|
+
() => 'https://github.com/harness-engineering/linter-gen'
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
type MessageIds = 'missingSchema';
|
|
13
|
+
|
|
14
|
+
const FILE_PATTERN = '{{{json config.pattern}}}';
|
|
15
|
+
const REQUIRE_ZOD_SCHEMA = {{config.requireZodSchema}};
|
|
16
|
+
const MESSAGE = '{{{config.message}}}';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Normalize path separators to forward slashes
|
|
20
|
+
*/
|
|
21
|
+
function normalizePath(filePath: string): string {
|
|
22
|
+
return filePath.replace(/\\/g, '/');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Check if a path matches a glob pattern
|
|
27
|
+
*/
|
|
28
|
+
function matchesPattern(filePath: string, pattern: string): boolean {
|
|
29
|
+
const normalized = normalizePath(filePath);
|
|
30
|
+
return minimatch(normalized, pattern, { matchBase: true });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default createRule<[], MessageIds>({
|
|
34
|
+
name: '{{name}}',
|
|
35
|
+
meta: {
|
|
36
|
+
type: 'problem',
|
|
37
|
+
docs: {
|
|
38
|
+
description: MESSAGE,
|
|
39
|
+
},
|
|
40
|
+
messages: {
|
|
41
|
+
missingSchema: MESSAGE,
|
|
42
|
+
},
|
|
43
|
+
schema: [],
|
|
44
|
+
},
|
|
45
|
+
defaultOptions: [],
|
|
46
|
+
create(context) {
|
|
47
|
+
const filePath = normalizePath(context.filename);
|
|
48
|
+
|
|
49
|
+
// Only apply to files matching pattern
|
|
50
|
+
if (!matchesPattern(filePath, FILE_PATTERN)) {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!REQUIRE_ZOD_SCHEMA) {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let hasZodSchema = false;
|
|
59
|
+
let hasExport = false;
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
// Check for Zod imports
|
|
63
|
+
ImportDeclaration(node: TSESTree.ImportDeclaration) {
|
|
64
|
+
if (node.source.value === 'zod') {
|
|
65
|
+
hasZodSchema = true;
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
// Check for z.object, z.string, etc.
|
|
70
|
+
CallExpression(node: TSESTree.CallExpression) {
|
|
71
|
+
if (
|
|
72
|
+
node.callee.type === 'MemberExpression' &&
|
|
73
|
+
node.callee.object.type === 'Identifier' &&
|
|
74
|
+
node.callee.object.name === 'z'
|
|
75
|
+
) {
|
|
76
|
+
hasZodSchema = true;
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// Track exports
|
|
81
|
+
ExportNamedDeclaration() {
|
|
82
|
+
hasExport = true;
|
|
83
|
+
},
|
|
84
|
+
ExportDefaultDeclaration() {
|
|
85
|
+
hasExport = true;
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
// Check at end of file
|
|
89
|
+
'Program:exit'(node: TSESTree.Program) {
|
|
90
|
+
if (hasExport && !hasZodSchema) {
|
|
91
|
+
context.report({
|
|
92
|
+
node,
|
|
93
|
+
messageId: 'missingSchema',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
});
|