@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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/engine/context-builder.d.ts +24 -0
  5. package/dist/engine/context-builder.d.ts.map +1 -0
  6. package/dist/engine/context-builder.js +33 -0
  7. package/dist/engine/context-builder.js.map +1 -0
  8. package/dist/engine/template-loader.d.ts +27 -0
  9. package/dist/engine/template-loader.d.ts.map +1 -0
  10. package/dist/engine/template-loader.js +84 -0
  11. package/dist/engine/template-loader.js.map +1 -0
  12. package/dist/engine/template-renderer.d.ts +17 -0
  13. package/dist/engine/template-renderer.d.ts.map +1 -0
  14. package/dist/engine/template-renderer.js +44 -0
  15. package/dist/engine/template-renderer.js.map +1 -0
  16. package/dist/generator/index-generator.d.ts +5 -0
  17. package/dist/generator/index-generator.d.ts.map +1 -0
  18. package/dist/generator/index-generator.js +36 -0
  19. package/dist/generator/index-generator.js.map +1 -0
  20. package/dist/generator/orchestrator.d.ts +57 -0
  21. package/dist/generator/orchestrator.d.ts.map +1 -0
  22. package/dist/generator/orchestrator.js +106 -0
  23. package/dist/generator/orchestrator.js.map +1 -0
  24. package/dist/generator/rule-generator.d.ts +21 -0
  25. package/dist/generator/rule-generator.d.ts.map +1 -0
  26. package/dist/generator/rule-generator.js +30 -0
  27. package/dist/generator/rule-generator.js.map +1 -0
  28. package/dist/index.d.ts +15 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +14 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/parser/config-parser.d.ts +20 -0
  33. package/dist/parser/config-parser.d.ts.map +1 -0
  34. package/dist/parser/config-parser.js +62 -0
  35. package/dist/parser/config-parser.js.map +1 -0
  36. package/dist/schema/linter-config.d.ts +79 -0
  37. package/dist/schema/linter-config.d.ts.map +1 -0
  38. package/dist/schema/linter-config.js +28 -0
  39. package/dist/schema/linter-config.js.map +1 -0
  40. package/dist/templates/import-restriction.ts.hbs +2 -0
  41. package/dist/templates/templates/boundary-validation.ts.hbs +99 -0
  42. package/dist/templates/templates/dependency-graph.ts.hbs +123 -0
  43. package/dist/templates/templates/import-restriction.ts.hbs +72 -0
  44. 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
+ }