@checkdigit/eslint-plugin 7.18.0-PR.143-b6d4 → 7.18.0-PR.143-c73c
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 +1 -1
- package/dist-mjs/athena/athena.mjs +3 -473
- package/dist-mjs/athena/sql-file.mjs +100 -0
- package/dist-mjs/athena/validate.mjs +480 -0
- package/dist-mjs/index.mjs +18 -5
- package/dist-mjs/sql-parser.mjs +25 -0
- package/dist-types/athena/athena.d.ts +1 -2
- package/dist-types/athena/sql-file.d.ts +5 -0
- package/dist-types/athena/validate.d.ts +14 -0
- package/dist-types/index.d.ts +1 -0
- package/dist-types/sql-parser.d.ts +25 -0
- package/package.json +1 -1
- package/src/athena/athena.ts +3 -654
- package/src/athena/sql-file.ts +116 -0
- package/src/athena/validate.ts +625 -0
- package/src/index.ts +12 -0
- package/src/sql-parser.ts +46 -0
- package/src/athena/ATHENA.md +0 -387
- package/src/athena/PLAN.md +0 -355
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// athena/sql-file.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 debug from 'debug';
|
|
10
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
11
|
+
|
|
12
|
+
import { parse } from '../peggy/athena-peggy.ts';
|
|
13
|
+
import type { AST } from './types';
|
|
14
|
+
import { createRootContext } from './context.ts';
|
|
15
|
+
import { ATHENA_ERROR, AthenaError, checkAthenaAst, offsetToLoc, SYNTEXT_ERROR } from './validate.ts';
|
|
16
|
+
|
|
17
|
+
export const ruleId = 'sql-file';
|
|
18
|
+
|
|
19
|
+
const log = debug('eslint-plugin:sql-file');
|
|
20
|
+
const createRule = ESLintUtils.RuleCreator((name) => name);
|
|
21
|
+
|
|
22
|
+
const rule: ESLintUtils.RuleModule<typeof SYNTEXT_ERROR | typeof ATHENA_ERROR> = createRule({
|
|
23
|
+
name: ruleId,
|
|
24
|
+
meta: {
|
|
25
|
+
type: 'problem',
|
|
26
|
+
docs: {
|
|
27
|
+
description: 'Validate plain .sql files against OpenAPI schemas at lint time',
|
|
28
|
+
},
|
|
29
|
+
schema: [],
|
|
30
|
+
messages: {
|
|
31
|
+
[SYNTEXT_ERROR]: `SyntextError {{ errorMessage }}`,
|
|
32
|
+
[ATHENA_ERROR]: `AthenaError {{ errorMessage }}`,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultOptions: [],
|
|
36
|
+
create(context) {
|
|
37
|
+
return {
|
|
38
|
+
// Program fires once per file; the entire source text is the SQL to validate.
|
|
39
|
+
Program() {
|
|
40
|
+
const sql = context.sourceCode.getText();
|
|
41
|
+
|
|
42
|
+
if (!/^\s*(?:SELECT\b[\s\S]*\bFROM\b|WITH\b[\s\S]*\bSELECT\b[\s\S]*\b)/iu.test(sql)) {
|
|
43
|
+
log('skipping non-SELECT SQL', { filename: context.filename });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let ast: AST;
|
|
48
|
+
try {
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
50
|
+
({ ast } = parse(sql, { includeLocations: true }));
|
|
51
|
+
} catch (error) {
|
|
52
|
+
log('error parsing SQL', { error, filename: context.filename });
|
|
53
|
+
const pegLoc = (error as { location?: { start: { offset: number }; end: { offset: number } } }).location;
|
|
54
|
+
if (pegLoc !== undefined) {
|
|
55
|
+
// SQL offsets equal file offsets — no template-literal mapping needed.
|
|
56
|
+
context.report({
|
|
57
|
+
loc: {
|
|
58
|
+
start: offsetToLoc(sql, pegLoc.start.offset),
|
|
59
|
+
end: offsetToLoc(sql, pegLoc.end.offset),
|
|
60
|
+
},
|
|
61
|
+
messageId: SYNTEXT_ERROR,
|
|
62
|
+
data: { errorMessage: (error as Error).message },
|
|
63
|
+
});
|
|
64
|
+
} else {
|
|
65
|
+
context.report({
|
|
66
|
+
loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
67
|
+
messageId: SYNTEXT_ERROR,
|
|
68
|
+
data: { errorMessage: (error as Error).message },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const athenaCtx = createRootContext();
|
|
75
|
+
try {
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
77
|
+
checkAthenaAst(Array.isArray(ast) ? ast[0] : ast, athenaCtx);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
log('error checking SQL AST', { error, filename: context.filename });
|
|
80
|
+
if (error instanceof AthenaError) {
|
|
81
|
+
const astLoc = (error.ast as { loc?: { start: { offset: number }; end: { offset: number } } } | undefined)
|
|
82
|
+
?.loc;
|
|
83
|
+
if (astLoc !== undefined) {
|
|
84
|
+
context.report({
|
|
85
|
+
loc: {
|
|
86
|
+
start: offsetToLoc(sql, astLoc.start.offset),
|
|
87
|
+
end: offsetToLoc(sql, astLoc.end.offset),
|
|
88
|
+
},
|
|
89
|
+
messageId: ATHENA_ERROR,
|
|
90
|
+
data: { errorMessage: error.message },
|
|
91
|
+
});
|
|
92
|
+
} else {
|
|
93
|
+
context.report({
|
|
94
|
+
loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
95
|
+
messageId: ATHENA_ERROR,
|
|
96
|
+
data: { errorMessage: error.message },
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
// eslint-disable-next-line no-console
|
|
101
|
+
console.error(`Failed to apply ${ruleId} rule for "${context.filename}":`, error);
|
|
102
|
+
context.report({
|
|
103
|
+
loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
104
|
+
messageId: ATHENA_ERROR,
|
|
105
|
+
data: {
|
|
106
|
+
errorMessage: error instanceof Error ? String(error) : JSON.stringify(error, undefined, 2),
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
export default rule;
|