@checkdigit/eslint-athena-plugin 1.0.0-PR.2-dcdf
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.txt +21 -0
- package/README.md +17 -0
- package/SECURITY.md +13 -0
- package/dist-mjs/athena/api-locator.mjs +66 -0
- package/dist-mjs/athena/api-matcher.mjs +206 -0
- package/dist-mjs/athena/athena.mjs +165 -0
- package/dist-mjs/athena/column.mjs +1 -0
- package/dist-mjs/athena/context.mjs +21 -0
- package/dist-mjs/athena/index.mjs +1 -0
- package/dist-mjs/athena/service-table.mjs +45 -0
- package/dist-mjs/athena/sql-file.mjs +123 -0
- package/dist-mjs/athena/types.mjs +1 -0
- package/dist-mjs/athena/validate.mjs +619 -0
- package/dist-mjs/athena/visitor.mjs +291 -0
- package/dist-mjs/get-documentation-url.mjs +9 -0
- package/dist-mjs/index.mjs +56 -0
- package/dist-mjs/openapi/deref-schema.mjs +20 -0
- package/dist-mjs/openapi/generate-schema.mjs +375 -0
- package/dist-mjs/openapi/service-schema-generator.mjs +176 -0
- package/dist-mjs/peggy/athena-peggy.mjs +20700 -0
- package/dist-mjs/service.mjs +9 -0
- package/dist-mjs/sql-parser.mjs +28 -0
- package/dist-types/athena/api-locator.d.ts +2 -0
- package/dist-types/athena/api-matcher.d.ts +14 -0
- package/dist-types/athena/athena.d.ts +5 -0
- package/dist-types/athena/column.d.ts +1 -0
- package/dist-types/athena/context.d.ts +21 -0
- package/dist-types/athena/index.d.ts +8 -0
- package/dist-types/athena/service-table.d.ts +8 -0
- package/dist-types/athena/sql-file.d.ts +5 -0
- package/dist-types/athena/types.d.ts +493 -0
- package/dist-types/athena/validate.d.ts +14 -0
- package/dist-types/athena/visitor.d.ts +75 -0
- package/dist-types/get-documentation-url.d.ts +1 -0
- package/dist-types/index.d.ts +5 -0
- package/dist-types/openapi/deref-schema.d.ts +1 -0
- package/dist-types/openapi/generate-schema.d.ts +33 -0
- package/dist-types/openapi/service-schema-generator.d.ts +5 -0
- package/dist-types/peggy/athena-peggy.d.ts +13 -0
- package/dist-types/service.d.ts +2 -0
- package/dist-types/sql-parser.d.ts +25 -0
- package/package.json +1 -0
- package/src/api/v1/swagger.yml +619 -0
- package/src/api/v2/swagger.yml +477 -0
- package/src/athena/api-locator.ts +78 -0
- package/src/athena/api-matcher.ts +323 -0
- package/src/athena/athena.ts +224 -0
- package/src/athena/column.ts +4 -0
- package/src/athena/context.ts +47 -0
- package/src/athena/index.ts +13 -0
- package/src/athena/service-table.ts +78 -0
- package/src/athena/sql-file.ts +161 -0
- package/src/athena/types.ts +568 -0
- package/src/athena/validate.ts +902 -0
- package/src/athena/visitor.ts +406 -0
- package/src/get-documentation-url.ts +7 -0
- package/src/index.ts +67 -0
- package/src/openapi/deref-schema.ts +20 -0
- package/src/openapi/generate-schema.ts +553 -0
- package/src/openapi/service-schema-generator.ts +241 -0
- package/src/peggy/athena-peggy.ts +22149 -0
- package/src/peggy/athena.peggy +2971 -0
- package/src/service.ts +11 -0
- package/src/services/eslintAthenaPlugin/v1/swagger.schema.deref.json +1931 -0
- package/src/services/eslintAthenaPlugin/v2/swagger.schema.deref.json +978 -0
- package/src/sql-parser.ts +53 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
// athena/visitor.ts
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
AggrFunc,
|
|
5
|
+
BaseFrom,
|
|
6
|
+
Binary,
|
|
7
|
+
Case,
|
|
8
|
+
Cast,
|
|
9
|
+
Column,
|
|
10
|
+
ColumnRefItem,
|
|
11
|
+
Dual,
|
|
12
|
+
ExpressionValue,
|
|
13
|
+
ExprList,
|
|
14
|
+
From,
|
|
15
|
+
Function,
|
|
16
|
+
Join,
|
|
17
|
+
Select,
|
|
18
|
+
TableExpr,
|
|
19
|
+
ValuesFrom,
|
|
20
|
+
With,
|
|
21
|
+
} from './types';
|
|
22
|
+
|
|
23
|
+
// UNNEST is not in types.ts (the grammar produces it but the TS types don't model it).
|
|
24
|
+
export interface UnnestFrom {
|
|
25
|
+
type: 'unnest';
|
|
26
|
+
// expr is a column_ref when used with CROSS JOIN; for standalone UNNEST (e.g.
|
|
27
|
+
// UNNEST(SEQUENCE(...))) it is a function call with no `column` property.
|
|
28
|
+
expr: ColumnRefItem | { type: string };
|
|
29
|
+
parentheses: boolean;
|
|
30
|
+
as: {
|
|
31
|
+
type: 'function';
|
|
32
|
+
name: { name: { type: string; value: string }[] };
|
|
33
|
+
args: ExprList;
|
|
34
|
+
} | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ColumnRefItem is extended by the parser with array_index for bracket access (e.g. col['key']).
|
|
38
|
+
export interface ColumnRefWithIndex extends ColumnRefItem {
|
|
39
|
+
array_index: { brackets: true; index: { type: string; value: unknown } }[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// -------------------------------------------------------------------
|
|
43
|
+
// Visitor map — all hooks are optional; implement only what you need.
|
|
44
|
+
// -------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
export interface VisitorMap {
|
|
47
|
+
visitSelect?(node: Select): void;
|
|
48
|
+
visitWith?(node: With): void;
|
|
49
|
+
visitBaseFrom?(node: BaseFrom): void;
|
|
50
|
+
visitJoin?(node: Join): void;
|
|
51
|
+
visitTableExpr?(node: TableExpr): void;
|
|
52
|
+
visitUnnest?(node: UnnestFrom): void;
|
|
53
|
+
visitColumn?(node: Column): void;
|
|
54
|
+
visitColumnRef?(node: ColumnRefItem): void;
|
|
55
|
+
visitFunction?(node: Function): void;
|
|
56
|
+
visitBinary?(node: Binary): void;
|
|
57
|
+
visitAggrFunc?(node: AggrFunc): void;
|
|
58
|
+
visitCast?(node: Cast): void;
|
|
59
|
+
visitCase?(node: Case): void;
|
|
60
|
+
visitExprList?(node: ExprList): void;
|
|
61
|
+
visitValue?(node: ExpressionValue): void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// -------------------------------------------------------------------
|
|
65
|
+
// Type guards — all accept `unknown` so callers can pass untyped AST
|
|
66
|
+
// nodes without an intermediate cast.
|
|
67
|
+
// -------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
function hasNodeType(node: unknown, type: string): boolean {
|
|
70
|
+
return (
|
|
71
|
+
typeof node === 'object' &&
|
|
72
|
+
node !== null &&
|
|
73
|
+
(node as { type?: unknown }).type === type
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function isUnnestFrom(node: unknown): node is UnnestFrom {
|
|
78
|
+
return hasNodeType(node, 'unnest');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function isDual(node: unknown): node is Dual {
|
|
82
|
+
return hasNodeType(node, 'dual');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function isValuesFrom(node: unknown): node is ValuesFrom {
|
|
86
|
+
if (typeof node !== 'object' || node === null) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return hasNodeType((node as { expr?: unknown }).expr, 'values');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function isTableExpr(node: unknown): node is TableExpr {
|
|
93
|
+
if (typeof node !== 'object' || node === null) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
const expr = (node as { expr?: unknown }).expr;
|
|
97
|
+
return typeof expr === 'object' && expr !== null && 'ast' in expr;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isRegularFromItem(node: unknown): boolean {
|
|
101
|
+
return (
|
|
102
|
+
typeof node === 'object' &&
|
|
103
|
+
node !== null &&
|
|
104
|
+
!isUnnestFrom(node) &&
|
|
105
|
+
!isDual(node) &&
|
|
106
|
+
!isTableExpr(node)
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function isJoin(node: unknown): node is Join {
|
|
111
|
+
return isRegularFromItem(node) && 'join' in (node as object);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function isBaseFrom(node: unknown): node is BaseFrom {
|
|
115
|
+
return (
|
|
116
|
+
isRegularFromItem(node) && !isJoin(node) && 'table' in (node as object)
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function hasArrayIndex(node: ColumnRefItem): node is ColumnRefWithIndex {
|
|
121
|
+
const typed = node as unknown as { array_index?: unknown };
|
|
122
|
+
return Array.isArray(typed.array_index) && typed.array_index.length > 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// -------------------------------------------------------------------
|
|
126
|
+
// Core walk helpers — defined before walk() to satisfy no-use-before-define.
|
|
127
|
+
// walkExpr has a single forward reference to walk() for nested selects.
|
|
128
|
+
// -------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
function walkExpr(node: unknown, visitor: VisitorMap): void {
|
|
131
|
+
if (typeof node !== 'object' || node === null) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const typed = node as Record<string, unknown>;
|
|
135
|
+
|
|
136
|
+
switch (typed['type']) {
|
|
137
|
+
case 'select': {
|
|
138
|
+
// eslint-disable-next-line no-use-before-define
|
|
139
|
+
walk(node, visitor);
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
case 'expr': {
|
|
143
|
+
walkExpr((node as Column).expr, visitor);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case 'binary_expr': {
|
|
147
|
+
const bin = node as Binary;
|
|
148
|
+
visitor.visitBinary?.(bin);
|
|
149
|
+
walkExpr(bin.left, visitor);
|
|
150
|
+
walkExpr(bin.right, visitor);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case 'column_ref': {
|
|
154
|
+
visitor.visitColumnRef?.(node as ColumnRefItem);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case 'function': {
|
|
158
|
+
const fn = node as Function;
|
|
159
|
+
visitor.visitFunction?.(fn);
|
|
160
|
+
if (fn.args !== undefined) {
|
|
161
|
+
walkExpr(fn.args, visitor);
|
|
162
|
+
}
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
case 'aggr_func': {
|
|
166
|
+
const agg = node as AggrFunc;
|
|
167
|
+
visitor.visitAggrFunc?.(agg);
|
|
168
|
+
walkExpr(agg.args.expr, visitor);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case 'cast': {
|
|
172
|
+
const cast = node as Cast;
|
|
173
|
+
visitor.visitCast?.(cast);
|
|
174
|
+
walkExpr(cast.expr, visitor);
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
case 'case': {
|
|
178
|
+
const caseNode = node as Case;
|
|
179
|
+
visitor.visitCase?.(caseNode);
|
|
180
|
+
for (const arm of caseNode.args) {
|
|
181
|
+
if ('cond' in arm) {
|
|
182
|
+
walkExpr(arm.cond, visitor);
|
|
183
|
+
}
|
|
184
|
+
walkExpr(arm.result, visitor);
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
case 'expr_list': {
|
|
189
|
+
const list = node as ExprList;
|
|
190
|
+
visitor.visitExprList?.(list);
|
|
191
|
+
for (const item of list.value) {
|
|
192
|
+
walkExpr(item, visitor);
|
|
193
|
+
}
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
case 'array': {
|
|
197
|
+
// ARRAY [...] nodes nest their items inside an expr_list property, not args/value.
|
|
198
|
+
walkExpr((node as { expr_list?: unknown }).expr_list, visitor);
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
default: {
|
|
202
|
+
// Column wrapper nodes (e.g. bare `SELECT *`) may lack a 'type' field but carry an 'expr'.
|
|
203
|
+
if (typed['type'] === undefined && 'expr' in typed) {
|
|
204
|
+
walkExpr(typed['expr'], visitor);
|
|
205
|
+
} else {
|
|
206
|
+
visitor.visitValue?.(node as ExpressionValue);
|
|
207
|
+
}
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function walkFrom(node: From, visitor: VisitorMap): void {
|
|
214
|
+
if (isDual(node)) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (isUnnestFrom(node)) {
|
|
218
|
+
visitor.visitUnnest?.(node);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (isTableExpr(node)) {
|
|
222
|
+
visitor.visitTableExpr?.(node);
|
|
223
|
+
// eslint-disable-next-line no-use-before-define
|
|
224
|
+
walk(node.expr.ast, visitor);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (isJoin(node)) {
|
|
228
|
+
visitor.visitJoin?.(node);
|
|
229
|
+
if (node.on !== undefined) {
|
|
230
|
+
walkExpr(node.on, visitor);
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (isValuesFrom(node)) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
visitor.visitBaseFrom?.(node);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Normalise select.from (null / single item / array) to a From[]. */
|
|
241
|
+
export function fromClauseItems(select: Select): From[] {
|
|
242
|
+
if (Array.isArray(select.from)) {
|
|
243
|
+
return select.from;
|
|
244
|
+
}
|
|
245
|
+
if (select.from !== null) {
|
|
246
|
+
return [select.from];
|
|
247
|
+
}
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// -------------------------------------------------------------------
|
|
252
|
+
// Core walk — dispatches per node type and recurses into children.
|
|
253
|
+
// -------------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
export function walk(node: unknown, visitor: VisitorMap): void {
|
|
256
|
+
if (typeof node !== 'object' || node === null) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const typed = node as Record<string, unknown>;
|
|
260
|
+
|
|
261
|
+
if (typed['type'] === 'select') {
|
|
262
|
+
const sel = node as Select;
|
|
263
|
+
visitor.visitSelect?.(sel);
|
|
264
|
+
for (const withItem of sel.with ?? []) {
|
|
265
|
+
visitor.visitWith?.(withItem);
|
|
266
|
+
walk(withItem.stmt.ast, visitor);
|
|
267
|
+
}
|
|
268
|
+
for (const fromItem of fromClauseItems(sel)) {
|
|
269
|
+
walkFrom(fromItem, visitor);
|
|
270
|
+
}
|
|
271
|
+
for (const col of sel.columns) {
|
|
272
|
+
walkExpr(col, visitor);
|
|
273
|
+
}
|
|
274
|
+
if (sel.where !== null) {
|
|
275
|
+
walkExpr(sel.where, visitor);
|
|
276
|
+
}
|
|
277
|
+
if (sel._next !== undefined) {
|
|
278
|
+
walk(sel._next, visitor);
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
walkExpr(node, visitor);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// -------------------------------------------------------------------
|
|
286
|
+
// Convenience extractors — replace ad-hoc JSONPath queries.
|
|
287
|
+
// -------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
/** Return true if the expression contains a lambda (`->`) subexpression. */
|
|
290
|
+
export function containsLambda(expr: unknown): boolean {
|
|
291
|
+
let found = false;
|
|
292
|
+
walkExpr(expr, {
|
|
293
|
+
visitBinary(node) {
|
|
294
|
+
if (node.operator === '->') {
|
|
295
|
+
found = true;
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
return found;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Collect all column_ref nodes within an expression subtree. */
|
|
303
|
+
export function extractColumnRefs(expr: unknown): ColumnRefItem[] {
|
|
304
|
+
const refs: ColumnRefItem[] = [];
|
|
305
|
+
walkExpr(expr, {
|
|
306
|
+
visitColumnRef(node) {
|
|
307
|
+
refs.push(node);
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
return refs;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export interface JsonExtractCall {
|
|
314
|
+
ref: ColumnRefItem;
|
|
315
|
+
path: string;
|
|
316
|
+
fnNode: Function;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Collect ALL json_extract / json_extract_scalar calls as (source column_ref, path) pairs. */
|
|
320
|
+
export function extractJsonExtractCalls(expr: unknown): JsonExtractCall[] {
|
|
321
|
+
const calls: JsonExtractCall[] = [];
|
|
322
|
+
walkExpr(expr, {
|
|
323
|
+
visitFunction(node) {
|
|
324
|
+
const fnName = node.name.name[0]?.value;
|
|
325
|
+
if (fnName !== 'json_extract_scalar' && fnName !== 'json_extract') {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const sourceArg = node.args?.value[0];
|
|
329
|
+
const pathArg = node.args?.value[1];
|
|
330
|
+
if (sourceArg === undefined || pathArg === undefined) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const sourceRefs = extractColumnRefs(sourceArg);
|
|
334
|
+
if (sourceRefs.length !== 1) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const [ref] = sourceRefs;
|
|
338
|
+
const path = (pathArg as unknown as { value?: unknown }).value;
|
|
339
|
+
if (ref === undefined || typeof path !== 'string') {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
calls.push({ ref, path, fnNode: node });
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
return calls;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** Return the path string from the first json_extract_scalar / json_extract call found. */
|
|
349
|
+
export function extractJsonExtractPath(expr: unknown): string | undefined {
|
|
350
|
+
return extractJsonExtractCalls(expr)[0]?.path;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** Return the JSONPath-style string from a bracket accessor (col['key']), or undefined. */
|
|
354
|
+
export function extractBracketAccessorPath(expr: unknown): string | undefined {
|
|
355
|
+
let path: string | undefined;
|
|
356
|
+
walkExpr(expr, {
|
|
357
|
+
visitColumnRef(node) {
|
|
358
|
+
if (path !== undefined || !hasArrayIndex(node)) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
const indexValue = node.array_index[0]?.index.value;
|
|
362
|
+
if (typeof indexValue === 'string') {
|
|
363
|
+
path = `$["${indexValue}"]`;
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
return path;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** Return true if the expression tree contains any function or aggregate call. */
|
|
371
|
+
export function hasFunctionCalls(expr: unknown): boolean {
|
|
372
|
+
let found = false;
|
|
373
|
+
walkExpr(expr, {
|
|
374
|
+
visitFunction() {
|
|
375
|
+
found = true;
|
|
376
|
+
},
|
|
377
|
+
visitAggrFunc() {
|
|
378
|
+
found = true;
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
return found;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function containsCastToType(expr: unknown, dataType: 'ARRAY' | 'MAP'): boolean {
|
|
385
|
+
let found = false;
|
|
386
|
+
walkExpr(expr, {
|
|
387
|
+
visitCast(node) {
|
|
388
|
+
const target = (node as unknown as { target?: { dataType?: string }[] })
|
|
389
|
+
.target?.[0];
|
|
390
|
+
if (target?.dataType === dataType) {
|
|
391
|
+
found = true;
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
});
|
|
395
|
+
return found;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** Return true when the expression tree contains any CAST / TRY_CAST to ARRAY<…>. */
|
|
399
|
+
export function containsCastToArray(expr: unknown): boolean {
|
|
400
|
+
return containsCastToType(expr, 'ARRAY');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** Return true when the expression tree contains any CAST / TRY_CAST to MAP<…>. */
|
|
404
|
+
export function containsCastToMap(expr: unknown): boolean {
|
|
405
|
+
return containsCastToType(expr, 'MAP');
|
|
406
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// get-documentation-url.ts
|
|
2
|
+
|
|
3
|
+
import packageJson from '../package.json' with { type: 'json' };
|
|
4
|
+
|
|
5
|
+
export default function getDocumentationUrl(ruleId: string): string {
|
|
6
|
+
return `${packageJson.repository.url}/blob/v${packageJson.version}/docs/rules/${ruleId}.md`;
|
|
7
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// index.ts
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Copyright (c) 2021-2026 Check Digit, LLC
|
|
5
|
+
*
|
|
6
|
+
* This code is licensed under the MIT license (see LICENSE.txt for details).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { TSESLint } from '@typescript-eslint/utils';
|
|
10
|
+
|
|
11
|
+
import athena, { ruleId as athenaRuleId } from './athena/athena.ts';
|
|
12
|
+
import sqlFile, { ruleId as sqlFileRuleId } from './athena/sql-file.ts';
|
|
13
|
+
import { parseForESLint } from './sql-parser.ts';
|
|
14
|
+
|
|
15
|
+
const rules: Record<string, TSESLint.LooseRuleDefinition> = {
|
|
16
|
+
[athenaRuleId]: athena,
|
|
17
|
+
[sqlFileRuleId]: sqlFile,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const plugin: TSESLint.FlatConfig.Plugin = {
|
|
21
|
+
rules,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const configs: Record<string, TSESLint.FlatConfig.Config[]> = {
|
|
25
|
+
all: [
|
|
26
|
+
{
|
|
27
|
+
files: ['**/*.ts'],
|
|
28
|
+
plugins: {
|
|
29
|
+
'@checkdigit': plugin,
|
|
30
|
+
},
|
|
31
|
+
rules: {
|
|
32
|
+
[`@checkdigit/${athenaRuleId}`]: 'error',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
files: ['**/*.sql'],
|
|
37
|
+
plugins: { '@checkdigit': plugin },
|
|
38
|
+
languageOptions: { parser: { parseForESLint } },
|
|
39
|
+
rules: { [`@checkdigit/${sqlFileRuleId}`]: 'error' },
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
recommended: [
|
|
43
|
+
{
|
|
44
|
+
files: ['**/*.ts'],
|
|
45
|
+
plugins: {
|
|
46
|
+
'@checkdigit': plugin,
|
|
47
|
+
},
|
|
48
|
+
rules: {
|
|
49
|
+
[`@checkdigit/${athenaRuleId}`]: 'off',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
files: ['**/*.sql'],
|
|
54
|
+
plugins: { '@checkdigit': plugin },
|
|
55
|
+
languageOptions: { parser: { parseForESLint } },
|
|
56
|
+
rules: { [`@checkdigit/${sqlFileRuleId}`]: 'off' },
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const defaultToExport: Exclude<TSESLint.FlatConfig.Plugin, 'config'> & {
|
|
62
|
+
configs: Record<string, TSESLint.FlatConfig.Config[]>;
|
|
63
|
+
} = {
|
|
64
|
+
...plugin,
|
|
65
|
+
configs,
|
|
66
|
+
};
|
|
67
|
+
export default defaultToExport;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// openapi/deref-schema.ts
|
|
2
|
+
|
|
3
|
+
import { promises as fs } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
import { dereference } from '@apidevtools/json-schema-ref-parser';
|
|
6
|
+
|
|
7
|
+
export async function derefSchema(schemaFileName: string): Promise<void> {
|
|
8
|
+
const schemaFile = await fs.readFile(`${schemaFileName}.json`, 'utf-8');
|
|
9
|
+
const adjustedSchemaFileForDeref = schemaFile.replace(
|
|
10
|
+
/"\$ref": ".*\/definitions\//gu,
|
|
11
|
+
'"$ref": "#/definitions/',
|
|
12
|
+
);
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
14
|
+
const json = JSON.parse(adjustedSchemaFileForDeref);
|
|
15
|
+
const deref = await dereference(json);
|
|
16
|
+
await fs.writeFile(
|
|
17
|
+
`${schemaFileName}.deref.json`,
|
|
18
|
+
JSON.stringify(deref, undefined, 2),
|
|
19
|
+
);
|
|
20
|
+
}
|