@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,123 @@
|
|
|
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
|
+
import * as path from 'path';
|
|
8
|
+
|
|
9
|
+
const createRule = ESLintUtils.RuleCreator(
|
|
10
|
+
() => 'https://github.com/harness-engineering/linter-gen'
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
type MessageIds = 'circularDependency';
|
|
14
|
+
|
|
15
|
+
const ENTRY_POINTS: string[] = {{{json config.entryPoints}}};
|
|
16
|
+
const EXCLUDE_PATTERNS: string[] = {{{json config.exclude}}};
|
|
17
|
+
const MAX_DEPTH = {{#if config.maxDepth}}{{config.maxDepth}}{{else}}20{{/if}};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Normalize path separators to forward slashes
|
|
21
|
+
*/
|
|
22
|
+
function normalizePath(filePath: string): string {
|
|
23
|
+
return filePath.replace(/\\/g, '/');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if a path matches any of the exclude patterns
|
|
28
|
+
*/
|
|
29
|
+
function isExcluded(filePath: string): boolean {
|
|
30
|
+
const normalized = normalizePath(filePath);
|
|
31
|
+
return EXCLUDE_PATTERNS.some((pattern) =>
|
|
32
|
+
minimatch(normalized, pattern, { matchBase: true })
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Track imports per file for cycle detection
|
|
37
|
+
const importGraph = new Map<string, Set<string>>();
|
|
38
|
+
|
|
39
|
+
export default createRule<[], MessageIds>({
|
|
40
|
+
name: '{{name}}',
|
|
41
|
+
meta: {
|
|
42
|
+
type: 'problem',
|
|
43
|
+
docs: {
|
|
44
|
+
description: 'Detect circular import dependencies',
|
|
45
|
+
},
|
|
46
|
+
messages: {
|
|
47
|
+
circularDependency:
|
|
48
|
+
'Circular dependency detected: {{cycle}}',
|
|
49
|
+
},
|
|
50
|
+
schema: [],
|
|
51
|
+
},
|
|
52
|
+
defaultOptions: [],
|
|
53
|
+
create(context) {
|
|
54
|
+
const filePath = normalizePath(context.filename);
|
|
55
|
+
|
|
56
|
+
if (isExcluded(filePath)) {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const imports = new Set<string>();
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
ImportDeclaration(node: TSESTree.ImportDeclaration) {
|
|
64
|
+
const importPath = String(node.source.value);
|
|
65
|
+
|
|
66
|
+
// Only track relative imports (internal dependencies)
|
|
67
|
+
if (!importPath.startsWith('.')) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Resolve to absolute path
|
|
72
|
+
const resolvedPath = normalizePath(
|
|
73
|
+
path.resolve(path.dirname(context.filename), importPath)
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
imports.add(resolvedPath);
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
'Program:exit'() {
|
|
80
|
+
// Store this file's imports
|
|
81
|
+
importGraph.set(filePath, imports);
|
|
82
|
+
|
|
83
|
+
// Check for cycles starting from this file
|
|
84
|
+
const visited = new Set<string>();
|
|
85
|
+
const stack: string[] = [];
|
|
86
|
+
|
|
87
|
+
function detectCycle(current: string, depth: number): string[] | null {
|
|
88
|
+
if (depth > MAX_DEPTH) return null;
|
|
89
|
+
if (stack.includes(current)) {
|
|
90
|
+
const cycleStart = stack.indexOf(current);
|
|
91
|
+
return [...stack.slice(cycleStart), current];
|
|
92
|
+
}
|
|
93
|
+
if (visited.has(current)) return null;
|
|
94
|
+
|
|
95
|
+
visited.add(current);
|
|
96
|
+
stack.push(current);
|
|
97
|
+
|
|
98
|
+
const deps = importGraph.get(current);
|
|
99
|
+
if (deps) {
|
|
100
|
+
for (const dep of deps) {
|
|
101
|
+
const cycle = detectCycle(dep, depth + 1);
|
|
102
|
+
if (cycle) return cycle;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
stack.pop();
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const cycle = detectCycle(filePath, 0);
|
|
111
|
+
if (cycle) {
|
|
112
|
+
context.report({
|
|
113
|
+
loc: { line: 1, column: 0 },
|
|
114
|
+
messageId: 'circularDependency',
|
|
115
|
+
data: {
|
|
116
|
+
cycle: cycle.map((p) => path.basename(p)).join(' → '),
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
import * as path from 'path';
|
|
8
|
+
|
|
9
|
+
const createRule = ESLintUtils.RuleCreator(
|
|
10
|
+
() => 'https://github.com/harness-engineering/linter-gen'
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
type MessageIds = 'forbidden';
|
|
14
|
+
|
|
15
|
+
const SOURCE_PATTERN = '{{{json config.source}}}';
|
|
16
|
+
const FORBIDDEN_IMPORTS: string[] = {{{json config.forbiddenImports}}};
|
|
17
|
+
const MESSAGE = '{{{config.message}}}';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Normalize path separators to forward slashes
|
|
21
|
+
*/
|
|
22
|
+
function normalizePath(filePath: string): string {
|
|
23
|
+
return filePath.replace(/\\/g, '/');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if a path matches a glob pattern
|
|
28
|
+
*/
|
|
29
|
+
function matchesPattern(filePath: string, pattern: string): boolean {
|
|
30
|
+
const normalized = normalizePath(filePath);
|
|
31
|
+
return minimatch(normalized, pattern, { matchBase: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default createRule<[], MessageIds>({
|
|
35
|
+
name: '{{name}}',
|
|
36
|
+
meta: {
|
|
37
|
+
type: 'problem',
|
|
38
|
+
docs: {
|
|
39
|
+
description: MESSAGE,
|
|
40
|
+
},
|
|
41
|
+
messages: {
|
|
42
|
+
forbidden: MESSAGE,
|
|
43
|
+
},
|
|
44
|
+
schema: [],
|
|
45
|
+
},
|
|
46
|
+
defaultOptions: [],
|
|
47
|
+
create(context) {
|
|
48
|
+
const filePath = normalizePath(context.filename);
|
|
49
|
+
|
|
50
|
+
// Only apply to files matching source pattern
|
|
51
|
+
if (!matchesPattern(filePath, SOURCE_PATTERN)) {
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
ImportDeclaration(node: TSESTree.ImportDeclaration) {
|
|
57
|
+
const importPath = String(node.source.value);
|
|
58
|
+
|
|
59
|
+
for (const forbidden of FORBIDDEN_IMPORTS) {
|
|
60
|
+
// Check exact match or pattern match
|
|
61
|
+
if (importPath === forbidden || matchesPattern(importPath, forbidden)) {
|
|
62
|
+
context.report({
|
|
63
|
+
node,
|
|
64
|
+
messageId: 'forbidden',
|
|
65
|
+
});
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@harness-engineering/linter-gen",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Generate ESLint rules from YAML configuration",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"yaml": "^2.3.0",
|
|
20
|
+
"handlebars": "^4.7.0",
|
|
21
|
+
"zod": "^3.22.0",
|
|
22
|
+
"minimatch": "^9.0.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^20.0.0",
|
|
26
|
+
"typescript": "^5.0.0",
|
|
27
|
+
"vitest": "^2.0.0"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/Intense-Visions/harness-engineering.git",
|
|
36
|
+
"directory": "packages/linter-gen"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/Intense-Visions/harness-engineering/issues"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/Intense-Visions/harness-engineering/tree/main/packages/linter-gen#readme",
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsc && cp -r src/templates dist/templates",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"test:watch": "vitest",
|
|
46
|
+
"lint": "eslint src",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"clean": "rm -rf dist"
|
|
49
|
+
}
|
|
50
|
+
}
|