@ontrails/warden 1.0.0-beta.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 (118) hide show
  1. package/.turbo/turbo-build.log +1 -0
  2. package/.turbo/turbo-lint.log +3 -0
  3. package/.turbo/turbo-typecheck.log +1 -0
  4. package/CHANGELOG.md +21 -0
  5. package/README.md +132 -0
  6. package/dist/cli.d.ts +46 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +221 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/drift.d.ts +26 -0
  11. package/dist/drift.d.ts.map +1 -0
  12. package/dist/drift.js +27 -0
  13. package/dist/drift.js.map +1 -0
  14. package/dist/formatters.d.ts +29 -0
  15. package/dist/formatters.d.ts.map +1 -0
  16. package/dist/formatters.js +87 -0
  17. package/dist/formatters.js.map +1 -0
  18. package/dist/index.d.ts +26 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +26 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/rules/ast.d.ts +41 -0
  23. package/dist/rules/ast.d.ts.map +1 -0
  24. package/dist/rules/ast.js +163 -0
  25. package/dist/rules/ast.js.map +1 -0
  26. package/dist/rules/context-no-surface-types.d.ts +12 -0
  27. package/dist/rules/context-no-surface-types.d.ts.map +1 -0
  28. package/dist/rules/context-no-surface-types.js +96 -0
  29. package/dist/rules/context-no-surface-types.js.map +1 -0
  30. package/dist/rules/implementation-returns-result.d.ts +13 -0
  31. package/dist/rules/implementation-returns-result.d.ts.map +1 -0
  32. package/dist/rules/implementation-returns-result.js +231 -0
  33. package/dist/rules/implementation-returns-result.js.map +1 -0
  34. package/dist/rules/index.d.ts +22 -0
  35. package/dist/rules/index.d.ts.map +1 -0
  36. package/dist/rules/index.js +41 -0
  37. package/dist/rules/index.js.map +1 -0
  38. package/dist/rules/no-direct-impl-in-route.d.ts +12 -0
  39. package/dist/rules/no-direct-impl-in-route.d.ts.map +1 -0
  40. package/dist/rules/no-direct-impl-in-route.js +46 -0
  41. package/dist/rules/no-direct-impl-in-route.js.map +1 -0
  42. package/dist/rules/no-direct-implementation-call.d.ts +12 -0
  43. package/dist/rules/no-direct-implementation-call.d.ts.map +1 -0
  44. package/dist/rules/no-direct-implementation-call.js +39 -0
  45. package/dist/rules/no-direct-implementation-call.js.map +1 -0
  46. package/dist/rules/no-sync-result-assumption.d.ts +6 -0
  47. package/dist/rules/no-sync-result-assumption.d.ts.map +1 -0
  48. package/dist/rules/no-sync-result-assumption.js +98 -0
  49. package/dist/rules/no-sync-result-assumption.js.map +1 -0
  50. package/dist/rules/no-throw-in-detour-target.d.ts +12 -0
  51. package/dist/rules/no-throw-in-detour-target.d.ts.map +1 -0
  52. package/dist/rules/no-throw-in-detour-target.js +87 -0
  53. package/dist/rules/no-throw-in-detour-target.js.map +1 -0
  54. package/dist/rules/no-throw-in-implementation.d.ts +9 -0
  55. package/dist/rules/no-throw-in-implementation.d.ts.map +1 -0
  56. package/dist/rules/no-throw-in-implementation.js +34 -0
  57. package/dist/rules/no-throw-in-implementation.js.map +1 -0
  58. package/dist/rules/prefer-schema-inference.d.ts +7 -0
  59. package/dist/rules/prefer-schema-inference.d.ts.map +1 -0
  60. package/dist/rules/prefer-schema-inference.js +86 -0
  61. package/dist/rules/prefer-schema-inference.js.map +1 -0
  62. package/dist/rules/scan.d.ts +8 -0
  63. package/dist/rules/scan.d.ts.map +1 -0
  64. package/dist/rules/scan.js +32 -0
  65. package/dist/rules/scan.js.map +1 -0
  66. package/dist/rules/specs.d.ts +29 -0
  67. package/dist/rules/specs.d.ts.map +1 -0
  68. package/dist/rules/specs.js +192 -0
  69. package/dist/rules/specs.js.map +1 -0
  70. package/dist/rules/structure.d.ts +13 -0
  71. package/dist/rules/structure.d.ts.map +1 -0
  72. package/dist/rules/structure.js +142 -0
  73. package/dist/rules/structure.js.map +1 -0
  74. package/dist/rules/types.d.ts +52 -0
  75. package/dist/rules/types.d.ts.map +1 -0
  76. package/dist/rules/types.js +2 -0
  77. package/dist/rules/types.js.map +1 -0
  78. package/dist/rules/valid-describe-refs.d.ts +7 -0
  79. package/dist/rules/valid-describe-refs.d.ts.map +1 -0
  80. package/dist/rules/valid-describe-refs.js +51 -0
  81. package/dist/rules/valid-describe-refs.js.map +1 -0
  82. package/dist/rules/valid-detour-refs.d.ts +6 -0
  83. package/dist/rules/valid-detour-refs.d.ts.map +1 -0
  84. package/dist/rules/valid-detour-refs.js +116 -0
  85. package/dist/rules/valid-detour-refs.js.map +1 -0
  86. package/package.json +25 -0
  87. package/src/__tests__/cli.test.ts +198 -0
  88. package/src/__tests__/drift.test.ts +74 -0
  89. package/src/__tests__/formatters.test.ts +157 -0
  90. package/src/__tests__/implementation-returns-result.test.ts +75 -0
  91. package/src/__tests__/no-direct-implementation-call.test.ts +83 -0
  92. package/src/__tests__/no-sync-result-assumption.test.ts +85 -0
  93. package/src/__tests__/no-throw-in-detour-target.test.ts +78 -0
  94. package/src/__tests__/prefer-schema-inference.test.ts +84 -0
  95. package/src/__tests__/rules.test.ts +188 -0
  96. package/src/__tests__/valid-describe-refs.test.ts +60 -0
  97. package/src/cli.ts +343 -0
  98. package/src/drift.ts +50 -0
  99. package/src/formatters.ts +113 -0
  100. package/src/index.ts +47 -0
  101. package/src/rules/ast.ts +217 -0
  102. package/src/rules/context-no-surface-types.ts +150 -0
  103. package/src/rules/implementation-returns-result.ts +343 -0
  104. package/src/rules/index.ts +54 -0
  105. package/src/rules/no-direct-impl-in-route.ts +77 -0
  106. package/src/rules/no-direct-implementation-call.ts +47 -0
  107. package/src/rules/no-sync-result-assumption.ts +156 -0
  108. package/src/rules/no-throw-in-detour-target.ts +150 -0
  109. package/src/rules/no-throw-in-implementation.ts +41 -0
  110. package/src/rules/prefer-schema-inference.ts +141 -0
  111. package/src/rules/scan.ts +46 -0
  112. package/src/rules/specs.ts +384 -0
  113. package/src/rules/structure.ts +234 -0
  114. package/src/rules/types.ts +62 -0
  115. package/src/rules/valid-describe-refs.ts +94 -0
  116. package/src/rules/valid-detour-refs.ts +187 -0
  117. package/tsconfig.json +9 -0
  118. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,94 @@
1
+ import type {
2
+ ProjectAwareWardenRule,
3
+ ProjectContext,
4
+ WardenDiagnostic,
5
+ } from './types.js';
6
+ import { isTestFile } from './scan.js';
7
+ import { collectTrailIds, parseStringLiteral } from './specs.js';
8
+ import { captureBalanced, lineNumberAt } from './structure.js';
9
+
10
+ const DESCRIBE_PATTERN = /\.describe\s*\(/g;
11
+
12
+ const SEE_PATTERN = /@see\s+([A-Za-z0-9_.-]+)/g;
13
+
14
+ interface DescribeRef {
15
+ readonly line: number;
16
+ readonly ref: string;
17
+ }
18
+
19
+ const describeTextAt = (
20
+ sourceCode: string,
21
+ matchIndex: number
22
+ ): string | null => {
23
+ const openParen = sourceCode.indexOf('(', matchIndex);
24
+ if (openParen === -1) {
25
+ return null;
26
+ }
27
+
28
+ return captureBalanced(sourceCode, openParen)?.text.slice(1, -1) ?? null;
29
+ };
30
+
31
+ const refsInDescription = (
32
+ description: string,
33
+ line: number
34
+ ): readonly DescribeRef[] =>
35
+ [...description.matchAll(SEE_PATTERN)].flatMap((see) =>
36
+ see[1] ? [{ line, ref: see[1] }] : []
37
+ );
38
+
39
+ const refsForDescribe = (
40
+ sourceCode: string,
41
+ matchIndex: number
42
+ ): readonly DescribeRef[] => {
43
+ const args = describeTextAt(sourceCode, matchIndex);
44
+ const description = args ? parseStringLiteral(args) : null;
45
+ return description === null
46
+ ? []
47
+ : refsInDescription(description, lineNumberAt(sourceCode, matchIndex));
48
+ };
49
+
50
+ const collectDescribeRefs = (sourceCode: string): readonly DescribeRef[] =>
51
+ [...sourceCode.matchAll(DESCRIBE_PATTERN)].flatMap((match) =>
52
+ match.index === undefined ? [] : refsForDescribe(sourceCode, match.index)
53
+ );
54
+
55
+ const checkDescribeRefs = (
56
+ sourceCode: string,
57
+ filePath: string,
58
+ knownTrailIds: ReadonlySet<string>
59
+ ): readonly WardenDiagnostic[] => {
60
+ if (isTestFile(filePath)) {
61
+ return [];
62
+ }
63
+
64
+ return collectDescribeRefs(sourceCode)
65
+ .filter(({ ref }) => !knownTrailIds.has(ref))
66
+ .map(({ line, ref }) => ({
67
+ filePath,
68
+ line,
69
+ message: `@see reference "${ref}" does not resolve to a defined trail.`,
70
+ rule: 'valid-describe-refs',
71
+ severity: 'warn' as const,
72
+ }));
73
+ };
74
+
75
+ /**
76
+ * Warns when @see references inside Zod .describe() strings point at unknown
77
+ * trails.
78
+ */
79
+ export const validDescribeRefs: ProjectAwareWardenRule = {
80
+ check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
81
+ return checkDescribeRefs(sourceCode, filePath, collectTrailIds(sourceCode));
82
+ },
83
+ checkWithContext(
84
+ sourceCode: string,
85
+ filePath: string,
86
+ context: ProjectContext
87
+ ): readonly WardenDiagnostic[] {
88
+ return checkDescribeRefs(sourceCode, filePath, context.knownTrailIds);
89
+ },
90
+ description:
91
+ 'Ensure @see tags inside schema .describe() strings reference defined trails.',
92
+ name: 'valid-describe-refs',
93
+ severity: 'warn',
94
+ };
@@ -0,0 +1,187 @@
1
+ import { collectTrailIds } from './specs.js';
2
+ import type {
3
+ ProjectAwareWardenRule,
4
+ ProjectContext,
5
+ WardenDiagnostic,
6
+ } from './types.js';
7
+
8
+ interface BraceState {
9
+ depth: number;
10
+ found: boolean;
11
+ }
12
+
13
+ const trackBraces = (line: string, state: BraceState): void => {
14
+ for (const ch of line) {
15
+ if (ch === '{') {
16
+ state.depth += 1;
17
+ state.found = true;
18
+ }
19
+ if (ch === '}') {
20
+ state.depth -= 1;
21
+ }
22
+ }
23
+ };
24
+
25
+ const collectArrayText = (lines: readonly string[], start: number): string => {
26
+ let text = '';
27
+ for (let k = start; k < lines.length && k < start + 20; k += 1) {
28
+ const line = lines[k];
29
+ if (!line) {
30
+ continue;
31
+ }
32
+ text += `${line}\n`;
33
+ if (text.includes(']')) {
34
+ break;
35
+ }
36
+ }
37
+ return text;
38
+ };
39
+
40
+ const findMissingDetourTargets = (
41
+ text: string,
42
+ knownIds: ReadonlySet<string>
43
+ ): string[] => {
44
+ const missing: string[] = [];
45
+ for (const m of text.matchAll(/target\s*:\s*["'`]([^"'`]+)["'`]/g)) {
46
+ const [, id] = m;
47
+ if (id && !knownIds.has(id)) {
48
+ missing.push(id);
49
+ }
50
+ }
51
+ return missing;
52
+ };
53
+
54
+ const findMissingPlainDetours = (
55
+ text: string,
56
+ knownIds: ReadonlySet<string>
57
+ ): string[] => {
58
+ const missing: string[] = [];
59
+ const cleaned = text.replaceAll(/target\s*:\s*["'`][^"'`]+["'`]/g, '');
60
+ for (const m of cleaned.matchAll(/["'`]([^"'`]+)["'`]/g)) {
61
+ const [, id] = m;
62
+ if (id && id.includes('.') && !knownIds.has(id)) {
63
+ missing.push(id);
64
+ }
65
+ }
66
+ return missing;
67
+ };
68
+
69
+ const findAllMissingDetours = (
70
+ text: string,
71
+ knownIds: ReadonlySet<string>
72
+ ): string[] => [
73
+ ...findMissingDetourTargets(text, knownIds),
74
+ ...findMissingPlainDetours(text, knownIds),
75
+ ];
76
+
77
+ const addMissingDetourDiagnostics = (
78
+ specLine: string,
79
+ j: number,
80
+ lines: readonly string[],
81
+ trailId: string,
82
+ lineNum: number,
83
+ filePath: string,
84
+ knownIds: ReadonlySet<string>,
85
+ diagnostics: WardenDiagnostic[]
86
+ ): void => {
87
+ if (!/\bdetours\s*:/.test(specLine)) {
88
+ return;
89
+ }
90
+ for (const targetId of findAllMissingDetours(
91
+ collectArrayText(lines, j),
92
+ knownIds
93
+ )) {
94
+ diagnostics.push({
95
+ filePath,
96
+ line: lineNum,
97
+ message: `Trail "${trailId}" has detour targeting "${targetId}" which is not defined.`,
98
+ rule: 'valid-detour-refs',
99
+ severity: 'error',
100
+ });
101
+ }
102
+ };
103
+
104
+ const scanTrailDetours = (
105
+ lines: readonly string[],
106
+ startIndex: number,
107
+ trailId: string,
108
+ filePath: string,
109
+ knownIds: ReadonlySet<string>,
110
+ diagnostics: WardenDiagnostic[]
111
+ ): void => {
112
+ const braceState: BraceState = { depth: 0, found: false };
113
+ for (let j = startIndex; j < lines.length && j < startIndex + 200; j += 1) {
114
+ const specLine = lines[j];
115
+ if (!specLine) {
116
+ continue;
117
+ }
118
+ trackBraces(specLine, braceState);
119
+ addMissingDetourDiagnostics(
120
+ specLine,
121
+ j,
122
+ lines,
123
+ trailId,
124
+ startIndex + 1,
125
+ filePath,
126
+ knownIds,
127
+ diagnostics
128
+ );
129
+ if (braceState.found && braceState.depth <= 0) {
130
+ break;
131
+ }
132
+ }
133
+ };
134
+
135
+ const processLine = (
136
+ line: string,
137
+ i: number,
138
+ lines: readonly string[],
139
+ filePath: string,
140
+ knownIds: ReadonlySet<string>,
141
+ diagnostics: WardenDiagnostic[]
142
+ ): void => {
143
+ const trailMatch = line.match(/\btrail\s*\(\s*["'`]([^"'`]+)["'`]/);
144
+ if (!trailMatch) {
145
+ return;
146
+ }
147
+ const [, trailId] = trailMatch;
148
+ if (!trailId) {
149
+ return;
150
+ }
151
+ scanTrailDetours(lines, i, trailId, filePath, knownIds, diagnostics);
152
+ };
153
+
154
+ const checkDetourRefs = (
155
+ sourceCode: string,
156
+ filePath: string,
157
+ knownIds: ReadonlySet<string>
158
+ ): readonly WardenDiagnostic[] => {
159
+ const diagnostics: WardenDiagnostic[] = [];
160
+ const lines = sourceCode.split('\n');
161
+ for (let i = 0; i < lines.length; i += 1) {
162
+ const line = lines[i];
163
+ if (line) {
164
+ processLine(line, i, lines, filePath, knownIds, diagnostics);
165
+ }
166
+ }
167
+ return diagnostics;
168
+ };
169
+
170
+ /**
171
+ * Checks that all trail IDs referenced in `detours` declarations exist.
172
+ */
173
+ export const validDetourRefs: ProjectAwareWardenRule = {
174
+ check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
175
+ return checkDetourRefs(sourceCode, filePath, collectTrailIds(sourceCode));
176
+ },
177
+ checkWithContext(
178
+ sourceCode: string,
179
+ filePath: string,
180
+ context: ProjectContext
181
+ ): readonly WardenDiagnostic[] {
182
+ return checkDetourRefs(sourceCode, filePath, context.knownTrailIds);
183
+ },
184
+ description: 'Ensure all detour target trail IDs reference defined trails.',
185
+ name: 'valid-detour-refs',
186
+ severity: 'error',
187
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"],
8
+ "exclude": ["**/__tests__/**", "**/*.test.ts", "dist"]
9
+ }
@@ -0,0 +1 @@
1
+ {"root":["./src/cli.ts","./src/drift.ts","./src/formatters.ts","./src/index.ts","./src/rules/ast.ts","./src/rules/context-no-surface-types.ts","./src/rules/implementation-returns-result.ts","./src/rules/index.ts","./src/rules/no-direct-impl-in-route.ts","./src/rules/no-direct-implementation-call.ts","./src/rules/no-sync-result-assumption.ts","./src/rules/no-throw-in-detour-target.ts","./src/rules/no-throw-in-implementation.ts","./src/rules/prefer-schema-inference.ts","./src/rules/scan.ts","./src/rules/specs.ts","./src/rules/structure.ts","./src/rules/types.ts","./src/rules/valid-describe-refs.ts","./src/rules/valid-detour-refs.ts"],"version":"5.9.3"}