eslint-plugin-slonik 1.7.0 → 1.8.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/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const checkSql_utils = require('./shared/eslint-plugin-slonik.rlOTrCdf.cjs');
3
+ const checkSql_utils = require('./shared/eslint-plugin-slonik.kniz1QWh.cjs');
4
4
  const utils = require('@typescript-eslint/utils');
5
5
  const tsPattern = require('ts-pattern');
6
6
  const E = require('fp-ts/lib/Either.js');
@@ -8,9 +8,9 @@ const function_js = require('fp-ts/lib/function.js');
8
8
  require('fp-ts/lib/Option.js');
9
9
  require('fp-ts/lib/TaskEither.js');
10
10
  const J = require('fp-ts/lib/Json.js');
11
- const ts = require('typescript');
12
11
  const path = require('path');
13
12
  const fs = require('fs');
13
+ const ts = require('typescript');
14
14
  const synckit = require('synckit');
15
15
  const node_url = require('node:url');
16
16
  const z = require('zod');
@@ -35,226 +35,16 @@ function _interopNamespaceCompat(e) {
35
35
 
36
36
  const E__namespace = /*#__PURE__*/_interopNamespaceCompat(E);
37
37
  const J__namespace = /*#__PURE__*/_interopNamespaceCompat(J);
38
- const ts__default = /*#__PURE__*/_interopDefaultCompat(ts);
39
38
  const path__default = /*#__PURE__*/_interopDefaultCompat(path);
40
39
  const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
40
+ const ts__default = /*#__PURE__*/_interopDefaultCompat(ts);
41
41
  const z__default = /*#__PURE__*/_interopDefaultCompat(z);
42
42
 
43
- const PRIMITIVES = {
44
- string: "string",
45
- number: "number",
46
- boolean: "boolean",
47
- false: "false",
48
- true: "true",
49
- null: "null",
50
- undefined: "undefined",
51
- any: "any"
52
- };
53
- function getResolvedTargetByTypeNode(params) {
54
- if (!params.typeNode) {
55
- console.error("[slonik/check-sql] DEBUG: typeNode is undefined in getResolvedTargetByTypeNode");
56
- throw new Error("typeNode is undefined");
57
- }
58
- const tsNode = params.parser.esTreeNodeToTSNodeMap.get(params.typeNode);
59
- if (!tsNode) {
60
- console.error("[slonik/check-sql] DEBUG: Could not map ESTree node to TS node. typeNode.type:", params.typeNode.type);
61
- throw new Error(`Could not map ESTree node (type: ${params.typeNode.type}) to TS node`);
62
- }
63
- const typeText = tsNode.getText();
64
- if (isReservedType(typeText, params.reservedTypes)) {
65
- return { kind: "type", value: typeText };
66
- }
67
- switch (params.typeNode.type) {
68
- case utils.TSESTree.AST_NODE_TYPES.TSLiteralType:
69
- return handleLiteralType(params.typeNode);
70
- case utils.TSESTree.AST_NODE_TYPES.TSUnionType:
71
- return {
72
- kind: "union",
73
- value: params.typeNode.types.map(
74
- (type) => getResolvedTargetByTypeNode({ ...params, typeNode: type })
75
- )
76
- };
77
- case utils.TSESTree.AST_NODE_TYPES.TSNullKeyword:
78
- return { kind: "type", value: "null" };
79
- case utils.TSESTree.AST_NODE_TYPES.TSUndefinedKeyword:
80
- return { kind: "type", value: "undefined" };
81
- case utils.TSESTree.AST_NODE_TYPES.TSTypeLiteral:
82
- return handleTypeLiteral(params.typeNode, params);
83
- case utils.TSESTree.AST_NODE_TYPES.TSTypeReference:
84
- return handleTypeReference(params.typeNode, params);
85
- case utils.TSESTree.AST_NODE_TYPES.TSIntersectionType:
86
- return handleIntersectionType(params.typeNode, params);
87
- case utils.TSESTree.AST_NODE_TYPES.TSArrayType:
88
- return {
89
- kind: "array",
90
- value: getResolvedTargetByTypeNode({
91
- ...params,
92
- typeNode: params.typeNode.elementType
93
- })
94
- };
95
- default:
96
- return { kind: "type", value: typeText };
97
- }
98
- }
99
- function isReservedType(typeText, reservedTypes) {
100
- return reservedTypes.has(typeText) || reservedTypes.has(`${typeText}[]`);
101
- }
102
- function handleLiteralType(typeNode) {
103
- return typeNode.literal.type === utils.TSESTree.AST_NODE_TYPES.Literal ? { kind: "type", value: `'${typeNode.literal.value}'` } : { kind: "type", value: "unknown" };
104
- }
105
- function handleTypeLiteral(typeNode, params) {
106
- const properties = typeNode.members.flatMap((member) => {
107
- if (member.type !== utils.TSESTree.AST_NODE_TYPES.TSPropertySignature || !member.typeAnnotation) {
108
- return [];
109
- }
110
- const key = extractPropertyKey(member.key);
111
- if (!key) return [];
112
- const propertyName = member.optional ? `${key}?` : key;
113
- const propertyType = getResolvedTargetByTypeNode({
114
- ...params,
115
- typeNode: member.typeAnnotation.typeAnnotation
116
- });
117
- return [[propertyName, propertyType]];
118
- });
119
- return { kind: "object", value: properties };
120
- }
121
- function extractPropertyKey(key) {
122
- switch (key.type) {
123
- case utils.TSESTree.AST_NODE_TYPES.Identifier:
124
- return key.name;
125
- case utils.TSESTree.AST_NODE_TYPES.Literal:
126
- return String(key.value);
127
- default:
128
- return void 0;
129
- }
130
- }
131
- function handleTypeReference(typeNode, params) {
132
- if (typeNode.typeName.type !== utils.TSESTree.AST_NODE_TYPES.Identifier && typeNode.typeName.type !== utils.TSESTree.AST_NODE_TYPES.TSQualifiedName) {
133
- return { kind: "type", value: "unknown" };
134
- }
135
- const typeNameText = params.parser.esTreeNodeToTSNodeMap.get(typeNode.typeName).getText();
136
- if (params.reservedTypes.has(typeNameText)) {
137
- return { kind: "type", value: typeNameText };
138
- }
139
- if (typeNameText === "Array" && typeNode.typeArguments?.params[0]) {
140
- return {
141
- kind: "array",
142
- syntax: "type-reference",
143
- value: getResolvedTargetByTypeNode({
144
- ...params,
145
- typeNode: typeNode.typeArguments.params[0]
146
- })
147
- };
148
- }
149
- const type = params.checker.getTypeFromTypeNode(
150
- params.parser.esTreeNodeToTSNodeMap.get(typeNode)
151
- );
152
- return resolveType(type, { ...params, typeNode });
153
- }
154
- function handleIntersectionType(typeNode, params) {
155
- const allProperties = typeNode.types.flatMap((type) => {
156
- const resolved = getResolvedTargetByTypeNode({ ...params, typeNode: type });
157
- return resolved.kind === "object" ? resolved.value : [];
158
- });
159
- return { kind: "object", value: Array.from(new Map(allProperties).entries()) };
160
- }
161
- function resolveType(type, params) {
162
- const typeAsString = params.checker.typeToString(type);
163
- if (params.reservedTypes.has(typeAsString)) {
164
- return { kind: "type", value: typeAsString };
165
- }
166
- if (params.reservedTypes.has(`${typeAsString}[]`)) {
167
- return { kind: "array", value: { kind: "type", value: typeAsString.replace("[]", "") } };
168
- }
169
- const primitive = getPrimitiveType(type, typeAsString);
170
- if (primitive) return primitive;
171
- if (type.isLiteral()) {
172
- return { kind: "type", value: `'${type.value}'` };
173
- }
174
- if (type.isUnion()) {
175
- return handleUnionTypeReference(type, params);
176
- }
177
- if (type.isIntersection()) {
178
- return handleIntersectionTypeReference(type, params);
179
- }
180
- if (params.checker.isArrayType(type)) {
181
- return handleArrayTypeReferenceFromType(type, params);
182
- }
183
- return handleObjectType(type, params);
184
- }
185
- function getPrimitiveType(type, typeAsString) {
186
- if (PRIMITIVES[typeAsString]) {
187
- return { kind: "type", value: PRIMITIVES[typeAsString] };
188
- }
189
- const flagMap = {
190
- [ts__default.TypeFlags.String]: "string",
191
- [ts__default.TypeFlags.Number]: "number",
192
- [ts__default.TypeFlags.Boolean]: "boolean",
193
- [ts__default.TypeFlags.Null]: "null",
194
- [ts__default.TypeFlags.Undefined]: "undefined",
195
- [ts__default.TypeFlags.Any]: "any"
196
- };
197
- return flagMap[type.flags] ? { kind: "type", value: flagMap[type.flags] } : null;
43
+ function isIdentifier(node) {
44
+ return node?.type === utils.TSESTree.AST_NODE_TYPES.Identifier;
198
45
  }
199
- function handleUnionTypeReference(type, params) {
200
- const types = type.types.map((t) => resolveType(t, params));
201
- const isBooleanUnionWithNull = types.length === 3 && types.some((t) => t.value === "false") && types.some((t) => t.value === "true") && types.some((t) => t.value === "null");
202
- if (isBooleanUnionWithNull) {
203
- return {
204
- kind: "union",
205
- value: [
206
- { kind: "type", value: "boolean" },
207
- { kind: "type", value: "null" }
208
- ]
209
- };
210
- }
211
- return { kind: "union", value: types };
212
- }
213
- function handleIntersectionTypeReference(type, params) {
214
- const properties = type.types.flatMap((t) => {
215
- const resolved = resolveType(t, params);
216
- return resolved.kind === "object" ? resolved.value : [];
217
- });
218
- return { kind: "object", value: properties };
219
- }
220
- function handleArrayTypeReferenceFromType(type, params) {
221
- const typeArguments = type.typeArguments;
222
- const firstArgument = typeArguments?.[0];
223
- if (firstArgument) {
224
- const elementType = resolveType(firstArgument, params);
225
- return { kind: "array", value: elementType };
226
- }
227
- return { kind: "array", value: { kind: "type", value: "unknown" } };
228
- }
229
- function handleObjectType(type, params) {
230
- if (!type.symbol) {
231
- return { kind: "type", value: type.aliasSymbol?.escapedName.toString() ?? "unknown" };
232
- }
233
- if (type.symbol.valueDeclaration) {
234
- const declaration = type.symbol.valueDeclaration;
235
- const sourceFile = declaration.getSourceFile();
236
- const filePath = sourceFile.fileName;
237
- if (!filePath.includes("node_modules")) {
238
- return extractObjectProperties(type, params);
239
- }
240
- return { kind: "type", value: type.symbol.name };
241
- }
242
- if (type.flags === ts__default.TypeFlags.Object) {
243
- return extractObjectProperties(type, params);
244
- }
245
- return { kind: "object", value: [] };
246
- }
247
- function extractObjectProperties(type, params) {
248
- const properties = type.getProperties().map((property) => {
249
- const key = property.escapedName.toString();
250
- const propType = params.checker.getTypeOfSymbolAtLocation(
251
- property,
252
- params.parser.esTreeNodeToTSNodeMap.get(params.typeNode)
253
- );
254
- const resolvedType = resolveType(propType, params);
255
- return [key, resolvedType];
256
- });
257
- return { kind: "object", value: properties };
46
+ function isMemberExpression(node) {
47
+ return node?.type === utils.TSESTree.AST_NODE_TYPES.MemberExpression;
258
48
  }
259
49
 
260
50
  function isInEditorEnv() {
@@ -1189,7 +979,6 @@ const zConfig = z__default.object({
1189
979
  connections: z__default.union([z__default.array(zRuleOptionConnection), zRuleOptionConnection])
1190
980
  });
1191
981
  const RuleOptions = z__default.array(zConfig).min(1).max(1);
1192
- const defaultInferLiteralOptions = ["string"];
1193
982
 
1194
983
  function getConfigFromFileWithContext(params) {
1195
984
  return params.context.options[0];
@@ -1198,12 +987,7 @@ function getConfigFromFileWithContext(params) {
1198
987
  const messages = {
1199
988
  typeInferenceFailed: "Type inference failed {{error}}",
1200
989
  error: "{{error}}",
1201
- invalidQuery: "Invalid Query: {{error}}",
1202
- missingTypeAnnotations: "Query is missing type annotation\n Fix with: {{fix}}",
1203
- incorrectTypeAnnotations: `Query has incorrect type annotation.
1204
- Expected: {{expected}}
1205
- Actual: {{actual}}`,
1206
- invalidTypeAnnotations: `Query has invalid type annotation (SafeQL does not support it. If you think it should, please open an issue)`
990
+ invalidQuery: "Invalid Query: {{error}}"
1207
991
  };
1208
992
  function check(params) {
1209
993
  const connections = Array.isArray(params.config.connections) ? params.config.connections : [params.config.connections];
@@ -1214,10 +998,10 @@ function check(params) {
1214
998
  }
1215
999
  }
1216
1000
  function isTagMemberValid(expr) {
1217
- if (checkSql_utils.isIdentifier(expr.tag)) {
1001
+ if (isIdentifier(expr.tag)) {
1218
1002
  return true;
1219
1003
  }
1220
- if (checkSql_utils.isMemberExpression(expr.tag) && checkSql_utils.isIdentifier(expr.tag.property)) {
1004
+ if (isMemberExpression(expr.tag) && isIdentifier(expr.tag.property)) {
1221
1005
  return true;
1222
1006
  }
1223
1007
  return false;
@@ -1239,13 +1023,11 @@ const generateSyncE = function_js.flow(
1239
1023
  );
1240
1024
  let fatalError;
1241
1025
  function reportCheck(params) {
1242
- const { context, tag, connection, target, projectDir, typeParameter, baseNode } = params;
1026
+ const { context, tag, connection, target, projectDir } = params;
1243
1027
  if (fatalError !== void 0) {
1244
1028
  const hint = isInEditorEnv() ? "If you think this is a bug, please open an issue. If not, please try to fix the error and restart ESLint." : "If you think this is a bug, please open an issue.";
1245
1029
  return checkSql_utils.reportBaseError({ context, error: fatalError, tag, hint });
1246
1030
  }
1247
- const nullAsOptional = connection.nullAsOptional ?? false;
1248
- const nullAsUndefined = connection.nullAsUndefined ?? false;
1249
1031
  return function_js.pipe(
1250
1032
  E__namespace.Do,
1251
1033
  E__namespace.bind("parser", () => {
@@ -1309,83 +1091,7 @@ If you believe this query should be supported, please open an issue at https://g
1309
1091
  return;
1310
1092
  }).exhaustive();
1311
1093
  },
1312
- ({ result, checker, parser }) => {
1313
- if (result === null) {
1314
- return;
1315
- }
1316
- const isMissingTypeAnnotations = typeParameter === void 0;
1317
- if (isMissingTypeAnnotations) {
1318
- if (result.output === null) {
1319
- return;
1320
- }
1321
- return checkSql_utils.reportMissingTypeAnnotations({
1322
- tag,
1323
- context,
1324
- baseNode,
1325
- actual: checkSql_utils.getFinalResolvedTargetString({
1326
- target: result.output,
1327
- nullAsOptional: nullAsOptional ?? false,
1328
- nullAsUndefined: nullAsUndefined ?? false,
1329
- transform: target.transform,
1330
- inferLiterals: connection.inferLiterals ?? defaultInferLiteralOptions
1331
- })
1332
- });
1333
- }
1334
- const reservedTypes = memoize({
1335
- key: `reserved-types:${JSON.stringify(connection.overrides)}`,
1336
- value: () => {
1337
- const types = /* @__PURE__ */ new Set();
1338
- for (const value of Object.values(connection.overrides?.types ?? {})) {
1339
- types.add(typeof value === "string" ? value : value.return);
1340
- }
1341
- for (const columnType of Object.values(connection.overrides?.columns ?? {})) {
1342
- types.add(columnType);
1343
- }
1344
- return types;
1345
- }
1346
- });
1347
- const typeAnnotationState = getTypeAnnotationState({
1348
- generated: result.output,
1349
- typeParameter,
1350
- transform: target.transform,
1351
- checker,
1352
- parser,
1353
- reservedTypes,
1354
- nullAsOptional,
1355
- nullAsUndefined,
1356
- inferLiterals: connection.inferLiterals ?? defaultInferLiteralOptions
1357
- });
1358
- if (typeAnnotationState === "INVALID") {
1359
- return checkSql_utils.reportInvalidTypeAnnotations({
1360
- context,
1361
- typeParameter
1362
- });
1363
- }
1364
- if (!typeAnnotationState.isEqual) {
1365
- return checkSql_utils.reportIncorrectTypeAnnotations({
1366
- context,
1367
- typeParameter,
1368
- expected: checkSql_utils.fmap(
1369
- typeAnnotationState.expected,
1370
- (expected) => checkSql_utils.getResolvedTargetString({
1371
- target: expected,
1372
- nullAsOptional: false,
1373
- nullAsUndefined: false,
1374
- inferLiterals: params.connection.inferLiterals ?? defaultInferLiteralOptions
1375
- })
1376
- ),
1377
- actual: checkSql_utils.fmap(
1378
- result.output,
1379
- (output) => checkSql_utils.getFinalResolvedTargetString({
1380
- target: output,
1381
- nullAsOptional: connection.nullAsOptional ?? false,
1382
- nullAsUndefined: connection.nullAsUndefined ?? false,
1383
- transform: target.transform,
1384
- inferLiterals: connection.inferLiterals ?? defaultInferLiteralOptions
1385
- })
1386
- )
1387
- });
1388
- }
1094
+ () => {
1389
1095
  }
1390
1096
  )
1391
1097
  );
@@ -1439,98 +1145,13 @@ function checkConnectionByWrapperExpression(params) {
1439
1145
  });
1440
1146
  }
1441
1147
  }
1442
- function getTypeAnnotationState({
1443
- generated,
1444
- typeParameter,
1445
- transform,
1446
- parser,
1447
- checker,
1448
- reservedTypes,
1449
- nullAsOptional,
1450
- nullAsUndefined,
1451
- inferLiterals
1452
- }) {
1453
- if (typeParameter.params.length !== 1) {
1454
- return "INVALID";
1455
- }
1456
- const typeNode = typeParameter.params[0];
1457
- let expected;
1458
- try {
1459
- expected = getResolvedTargetByTypeNode({
1460
- checker,
1461
- parser,
1462
- typeNode,
1463
- reservedTypes
1464
- });
1465
- } catch (error) {
1466
- console.error("[slonik/check-sql] DEBUG: Error in getResolvedTargetByTypeNode:", error);
1467
- console.error("[slonik/check-sql] DEBUG: typeNode:", typeNode);
1468
- console.error("[slonik/check-sql] DEBUG: typeNode.type:", typeNode?.type);
1469
- throw error;
1470
- }
1471
- return getResolvedTargetsEquality({
1472
- expected,
1473
- generated,
1474
- nullAsOptional,
1475
- nullAsUndefined,
1476
- inferLiterals,
1477
- transform
1478
- });
1479
- }
1480
- function getResolvedTargetsEquality(params) {
1481
- if (params.expected === null && params.generated === null) {
1482
- return {
1483
- isEqual: true,
1484
- expected: params.expected,
1485
- generated: params.generated
1486
- };
1487
- }
1488
- if (params.expected === null || params.generated === null) {
1489
- return {
1490
- isEqual: false,
1491
- expected: params.expected,
1492
- generated: params.generated
1493
- };
1494
- }
1495
- let expectedString = checkSql_utils.getResolvedTargetComparableString({
1496
- target: params.expected,
1497
- nullAsOptional: false,
1498
- nullAsUndefined: false,
1499
- inferLiterals: params.inferLiterals
1500
- });
1501
- let generatedString = checkSql_utils.getResolvedTargetComparableString({
1502
- target: params.generated,
1503
- nullAsOptional: params.nullAsOptional,
1504
- nullAsUndefined: params.nullAsUndefined,
1505
- inferLiterals: params.inferLiterals
1506
- });
1507
- if (expectedString === null || generatedString === null) {
1508
- return {
1509
- isEqual: false,
1510
- expected: params.expected,
1511
- generated: params.generated
1512
- };
1513
- }
1514
- expectedString = expectedString.replace(/'/g, '"');
1515
- generatedString = generatedString.replace(/'/g, '"');
1516
- expectedString = expectedString.split(", ").sort().join(", ");
1517
- generatedString = generatedString.split(", ").sort().join(", ");
1518
- if (params.transform !== void 0) {
1519
- generatedString = checkSql_utils.transformTypes(generatedString, params.transform);
1520
- }
1521
- return {
1522
- isEqual: expectedString === generatedString,
1523
- expected: params.expected,
1524
- generated: params.generated
1525
- };
1526
- }
1527
1148
  const createRule = utils.ESLintUtils.RuleCreator(() => `https://github.com/gajus/eslint-plugin-slonik`);
1528
1149
  const checkSql = createRule({
1529
1150
  name: "check-sql",
1530
1151
  meta: {
1531
1152
  fixable: "code",
1532
1153
  docs: {
1533
- description: "Ensure that sql queries have type annotations"
1154
+ description: "Validate SQL queries against the database schema"
1534
1155
  },
1535
1156
  messages,
1536
1157
  type: "problem",