eslint-plugin-ai-guard 1.1.7 → 1.1.9
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.js +296 -36
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +296 -36
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -5
package/dist/index.js
CHANGED
|
@@ -265,13 +265,43 @@ var createRule5 = import_utils5.ESLintUtils.RuleCreator(
|
|
|
265
265
|
);
|
|
266
266
|
var ARRAY_CALLBACK_METHODS = ["map", "filter", "forEach", "reduce", "flatMap", "find", "findIndex", "some", "every"];
|
|
267
267
|
var PROMISE_COMBINATORS = ["all", "allSettled", "race", "any"];
|
|
268
|
+
var METHODS_ALLOWING_PROMISE_COLLECTION = /* @__PURE__ */ new Set(["map", "flatMap"]);
|
|
269
|
+
function isNode(value) {
|
|
270
|
+
return typeof value === "object" && value !== null && "type" in value;
|
|
271
|
+
}
|
|
272
|
+
function getChildNodes(node) {
|
|
273
|
+
const children = [];
|
|
274
|
+
for (const [key, value] of Object.entries(node)) {
|
|
275
|
+
if (key === "parent") continue;
|
|
276
|
+
if (!value) continue;
|
|
277
|
+
if (Array.isArray(value)) {
|
|
278
|
+
for (const item of value) {
|
|
279
|
+
if (isNode(item)) {
|
|
280
|
+
children.push(item);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (isNode(value)) {
|
|
286
|
+
children.push(value);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return children;
|
|
290
|
+
}
|
|
291
|
+
function isPromiseCombinatorCall(node) {
|
|
292
|
+
return node.callee.type === import_utils5.AST_NODE_TYPES.MemberExpression && node.callee.object.type === import_utils5.AST_NODE_TYPES.Identifier && node.callee.object.name === "Promise" && node.callee.property.type === import_utils5.AST_NODE_TYPES.Identifier && PROMISE_COMBINATORS.includes(
|
|
293
|
+
node.callee.property.name
|
|
294
|
+
);
|
|
295
|
+
}
|
|
268
296
|
function isWrappedInPromiseCombinator(node, context) {
|
|
269
297
|
const ancestors = context.sourceCode.getAncestors(node);
|
|
298
|
+
const directParent = node.parent;
|
|
299
|
+
if (directParent && directParent.type === import_utils5.AST_NODE_TYPES.CallExpression && isPromiseCombinatorCall(directParent)) {
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
270
302
|
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
271
303
|
const ancestor = ancestors[i];
|
|
272
|
-
if (ancestor.type === import_utils5.AST_NODE_TYPES.CallExpression && ancestor
|
|
273
|
-
ancestor.callee.property.name
|
|
274
|
-
)) {
|
|
304
|
+
if (ancestor.type === import_utils5.AST_NODE_TYPES.CallExpression && isPromiseCombinatorCall(ancestor)) {
|
|
275
305
|
return true;
|
|
276
306
|
}
|
|
277
307
|
if (ancestor.type === import_utils5.AST_NODE_TYPES.CallExpression) {
|
|
@@ -280,6 +310,52 @@ function isWrappedInPromiseCombinator(node, context) {
|
|
|
280
310
|
}
|
|
281
311
|
return false;
|
|
282
312
|
}
|
|
313
|
+
function isIdentifierConsumedByPromiseCombinator(node, identifierName) {
|
|
314
|
+
let found = false;
|
|
315
|
+
const visit = (current) => {
|
|
316
|
+
if (found) return;
|
|
317
|
+
if (current.type === import_utils5.AST_NODE_TYPES.CallExpression && isPromiseCombinatorCall(current)) {
|
|
318
|
+
for (const arg of current.arguments) {
|
|
319
|
+
if (arg.type === import_utils5.AST_NODE_TYPES.Identifier && arg.name === identifierName) {
|
|
320
|
+
found = true;
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
for (const child of getChildNodes(current)) {
|
|
326
|
+
visit(child);
|
|
327
|
+
if (found) return;
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
visit(node);
|
|
331
|
+
return found;
|
|
332
|
+
}
|
|
333
|
+
function isAssignedAndConsumedByPromiseCombinator(node) {
|
|
334
|
+
if (!node.parent || node.parent.type !== import_utils5.AST_NODE_TYPES.VariableDeclarator || node.parent.id.type !== import_utils5.AST_NODE_TYPES.Identifier) {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
const variableName = node.parent.id.name;
|
|
338
|
+
const declaration = node.parent.parent;
|
|
339
|
+
if (!declaration || declaration.type !== import_utils5.AST_NODE_TYPES.VariableDeclaration) {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
const container = declaration.parent;
|
|
343
|
+
if (!container || container.type !== import_utils5.AST_NODE_TYPES.Program && container.type !== import_utils5.AST_NODE_TYPES.BlockStatement) {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
const body = container.body;
|
|
347
|
+
const declarationIndex = body.findIndex((statement) => statement === declaration);
|
|
348
|
+
if (declarationIndex === -1) {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
for (let i = declarationIndex + 1; i < body.length; i++) {
|
|
352
|
+
const statement = body[i];
|
|
353
|
+
if (isIdentifierConsumedByPromiseCombinator(statement, variableName)) {
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
283
359
|
function isAsyncCallback(node) {
|
|
284
360
|
return (node.type === import_utils5.AST_NODE_TYPES.FunctionExpression || node.type === import_utils5.AST_NODE_TYPES.ArrowFunctionExpression) && node.async;
|
|
285
361
|
}
|
|
@@ -317,6 +393,9 @@ var noAsyncArrayCallback = createRule5({
|
|
|
317
393
|
if (isWrappedInPromiseCombinator(node, context)) {
|
|
318
394
|
return;
|
|
319
395
|
}
|
|
396
|
+
if (METHODS_ALLOWING_PROMISE_COLLECTION.has(methodName) && isAssignedAndConsumedByPromiseCombinator(node)) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
320
399
|
context.report({
|
|
321
400
|
node: callback,
|
|
322
401
|
messageId: "asyncArrayCallback",
|
|
@@ -335,26 +414,66 @@ var import_utils6 = require("@typescript-eslint/utils");
|
|
|
335
414
|
var createRule6 = import_utils6.ESLintUtils.RuleCreator(
|
|
336
415
|
(name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
|
|
337
416
|
);
|
|
338
|
-
|
|
417
|
+
var KNOWN_PROMISE_FACTORIES = /* @__PURE__ */ new Set([
|
|
418
|
+
"fetch"
|
|
419
|
+
]);
|
|
420
|
+
var PROMISE_STATIC_METHODS = /* @__PURE__ */ new Set([
|
|
421
|
+
"resolve",
|
|
422
|
+
"reject",
|
|
423
|
+
"all",
|
|
424
|
+
"allSettled",
|
|
425
|
+
"race",
|
|
426
|
+
"any"
|
|
427
|
+
]);
|
|
428
|
+
function isLocallyAsyncCallee(node, asyncFunctionNames) {
|
|
339
429
|
if (node.callee.type === import_utils6.AST_NODE_TYPES.Identifier) {
|
|
340
|
-
|
|
430
|
+
if (asyncFunctionNames.has(node.callee.name)) {
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
341
433
|
}
|
|
342
|
-
if (node.callee.type === import_utils6.AST_NODE_TYPES.
|
|
343
|
-
return
|
|
434
|
+
if ((node.callee.type === import_utils6.AST_NODE_TYPES.FunctionExpression || node.callee.type === import_utils6.AST_NODE_TYPES.ArrowFunctionExpression) && node.callee.async) {
|
|
435
|
+
return true;
|
|
344
436
|
}
|
|
345
437
|
return false;
|
|
346
438
|
}
|
|
347
|
-
function
|
|
348
|
-
const lower = name.toLowerCase();
|
|
349
|
-
return lower.startsWith("fetch") || lower.startsWith("load") || lower.startsWith("save") || lower.startsWith("delete") || lower.startsWith("create") || lower.startsWith("update") || lower.startsWith("get") || lower.startsWith("post") || lower.startsWith("put") || lower.startsWith("patch") || lower.startsWith("send") || lower.startsWith("upload") || lower.startsWith("download") || lower.startsWith("connect") || lower.startsWith("disconnect") || lower.startsWith("subscribe") || lower.startsWith("publish") || lower.startsWith("request") || lower.startsWith("query") || lower === "axios" || lower === "fetch";
|
|
350
|
-
}
|
|
351
|
-
function isCalleeAsync(node, asyncFunctionNames) {
|
|
439
|
+
function isKnownPromiseFactoryCall(node) {
|
|
352
440
|
if (node.callee.type === import_utils6.AST_NODE_TYPES.Identifier) {
|
|
353
|
-
|
|
354
|
-
|
|
441
|
+
return KNOWN_PROMISE_FACTORIES.has(node.callee.name);
|
|
442
|
+
}
|
|
443
|
+
if (node.callee.type === import_utils6.AST_NODE_TYPES.MemberExpression && node.callee.object.type === import_utils6.AST_NODE_TYPES.Identifier && node.callee.object.name === "Promise" && node.callee.property.type === import_utils6.AST_NODE_TYPES.Identifier) {
|
|
444
|
+
return PROMISE_STATIC_METHODS.has(node.callee.property.name);
|
|
445
|
+
}
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
function getParserServices(context) {
|
|
449
|
+
const services = context.sourceCode.parserServices;
|
|
450
|
+
if (!services || typeof services !== "object") {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
return services;
|
|
454
|
+
}
|
|
455
|
+
function isPromiseLikeByTypeInfo(node, context) {
|
|
456
|
+
const services = getParserServices(context);
|
|
457
|
+
if (!services?.program || !services.esTreeNodeToTSNodeMap) {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
try {
|
|
461
|
+
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
|
|
462
|
+
if (!tsNode) {
|
|
463
|
+
return false;
|
|
355
464
|
}
|
|
465
|
+
const checker = services.program.getTypeChecker();
|
|
466
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
467
|
+
if (typeof checker.getPromisedTypeOfPromise === "function") {
|
|
468
|
+
if (checker.getPromisedTypeOfPromise(type)) {
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
const text = checker.typeToString(type).toLowerCase();
|
|
473
|
+
return text === "promise" || text.includes("promise<") || text.includes("promiselike<") || text.includes("thenable");
|
|
474
|
+
} catch {
|
|
475
|
+
return false;
|
|
356
476
|
}
|
|
357
|
-
return looksLikeAsyncCall(node);
|
|
358
477
|
}
|
|
359
478
|
function isExpressionHandled(node) {
|
|
360
479
|
const expr = node.expression;
|
|
@@ -404,6 +523,13 @@ var noFloatingPromise = createRule6({
|
|
|
404
523
|
},
|
|
405
524
|
// Check ExpressionStatements — standalone call expressions
|
|
406
525
|
ExpressionStatement(node) {
|
|
526
|
+
if (node.expression.type === import_utils6.AST_NODE_TYPES.NewExpression && node.expression.callee.type === import_utils6.AST_NODE_TYPES.Identifier && node.expression.callee.name === "Promise") {
|
|
527
|
+
context.report({
|
|
528
|
+
node,
|
|
529
|
+
messageId: "floatingPromise"
|
|
530
|
+
});
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
407
533
|
if (node.expression.type !== import_utils6.AST_NODE_TYPES.CallExpression) {
|
|
408
534
|
return;
|
|
409
535
|
}
|
|
@@ -411,7 +537,7 @@ var noFloatingPromise = createRule6({
|
|
|
411
537
|
return;
|
|
412
538
|
}
|
|
413
539
|
const callExpr = node.expression;
|
|
414
|
-
if (
|
|
540
|
+
if (isLocallyAsyncCallee(callExpr, asyncFunctionNames) || isKnownPromiseFactoryCall(callExpr) || isPromiseLikeByTypeInfo(callExpr, context)) {
|
|
415
541
|
context.report({
|
|
416
542
|
node,
|
|
417
543
|
messageId: "floatingPromise"
|
|
@@ -434,6 +560,80 @@ var LOOP_TYPES = /* @__PURE__ */ new Set([
|
|
|
434
560
|
import_utils7.AST_NODE_TYPES.WhileStatement,
|
|
435
561
|
import_utils7.AST_NODE_TYPES.DoWhileStatement
|
|
436
562
|
]);
|
|
563
|
+
var SEQUENTIAL_CALL_HINTS = [
|
|
564
|
+
"read",
|
|
565
|
+
"next",
|
|
566
|
+
"retry",
|
|
567
|
+
"sleep",
|
|
568
|
+
"delay",
|
|
569
|
+
"wait",
|
|
570
|
+
"throttle",
|
|
571
|
+
"ratelimit",
|
|
572
|
+
"acquire",
|
|
573
|
+
"release",
|
|
574
|
+
"drain",
|
|
575
|
+
"flush",
|
|
576
|
+
"consume"
|
|
577
|
+
];
|
|
578
|
+
var INTENT_COMMENT_HINTS = [
|
|
579
|
+
"sequential",
|
|
580
|
+
"ordered",
|
|
581
|
+
"order matters",
|
|
582
|
+
"intentional",
|
|
583
|
+
"rate limit",
|
|
584
|
+
"rate-limit",
|
|
585
|
+
"retry",
|
|
586
|
+
"stream",
|
|
587
|
+
"backpressure"
|
|
588
|
+
];
|
|
589
|
+
function getCalleeName(callee) {
|
|
590
|
+
if (callee.type === import_utils7.AST_NODE_TYPES.Identifier) {
|
|
591
|
+
return callee.name;
|
|
592
|
+
}
|
|
593
|
+
if (callee.type === import_utils7.AST_NODE_TYPES.MemberExpression && callee.property.type === import_utils7.AST_NODE_TYPES.Identifier) {
|
|
594
|
+
return callee.property.name;
|
|
595
|
+
}
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
function isLikelySequentialAwaitCall(node) {
|
|
599
|
+
if (node.argument.type !== import_utils7.AST_NODE_TYPES.CallExpression) {
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
const calleeName = getCalleeName(node.argument.callee);
|
|
603
|
+
if (!calleeName) {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
const lower = calleeName.toLowerCase();
|
|
607
|
+
return SEQUENTIAL_CALL_HINTS.some((hint) => lower.includes(hint));
|
|
608
|
+
}
|
|
609
|
+
function hasIntentionalSequentialComment(loopNode, sourceCode) {
|
|
610
|
+
const directComments = sourceCode.getCommentsBefore(loopNode);
|
|
611
|
+
const allComments = sourceCode.getAllComments();
|
|
612
|
+
const nearComments = allComments.filter((comment) => {
|
|
613
|
+
if (!comment.range || !loopNode.range) {
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
const [commentStart, commentEnd] = comment.range;
|
|
617
|
+
const [loopStart, loopEnd] = loopNode.range;
|
|
618
|
+
const insideLoop = commentStart >= loopStart && commentEnd <= loopEnd;
|
|
619
|
+
const immediatelyBeforeLoop = commentEnd <= loopStart && loopStart - commentEnd < 120;
|
|
620
|
+
return insideLoop || immediatelyBeforeLoop;
|
|
621
|
+
});
|
|
622
|
+
const combined = [...directComments, ...nearComments];
|
|
623
|
+
return combined.some((comment) => {
|
|
624
|
+
const lower = comment.value.toLowerCase();
|
|
625
|
+
return INTENT_COMMENT_HINTS.some((hint) => lower.includes(hint));
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
function shouldAllowIntentionalSequentialAwait(loopNode, awaitNode, context) {
|
|
629
|
+
if (loopNode.type === import_utils7.AST_NODE_TYPES.ForOfStatement && loopNode.await) {
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
632
|
+
if (hasIntentionalSequentialComment(loopNode, context.sourceCode)) {
|
|
633
|
+
return true;
|
|
634
|
+
}
|
|
635
|
+
return isLikelySequentialAwaitCall(awaitNode);
|
|
636
|
+
}
|
|
437
637
|
var noAwaitInLoop = createRule7({
|
|
438
638
|
name: "no-await-in-loop",
|
|
439
639
|
meta: {
|
|
@@ -472,6 +672,10 @@ var noAwaitInLoop = createRule7({
|
|
|
472
672
|
break;
|
|
473
673
|
}
|
|
474
674
|
if (LOOP_TYPES.has(ancestor.type)) {
|
|
675
|
+
const loopNode = ancestor;
|
|
676
|
+
if (shouldAllowIntentionalSequentialAwait(loopNode, node, context)) {
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
475
679
|
const loopName = getLoopName(ancestor.type);
|
|
476
680
|
context.report({
|
|
477
681
|
node,
|
|
@@ -859,6 +1063,60 @@ var createRule12 = import_utils12.ESLintUtils.RuleCreator(
|
|
|
859
1063
|
(name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
|
|
860
1064
|
);
|
|
861
1065
|
var SQL_STATEMENT_PATTERN = /\b(select\s+[\s\S]*\s+from|insert\s+into|update\s+\S+\s+set|delete\s+from|drop\s+table|create\s+(table|database)|alter\s+table|truncate\s+table|exec(?:ute)?\s+\S+|union\s+select)\b/i;
|
|
1066
|
+
var SQL_SINK_METHODS = /* @__PURE__ */ new Set([
|
|
1067
|
+
"query",
|
|
1068
|
+
"execute",
|
|
1069
|
+
"queryraw",
|
|
1070
|
+
"queryrawunsafe",
|
|
1071
|
+
"executeraw",
|
|
1072
|
+
"executerawunsafe",
|
|
1073
|
+
"raw",
|
|
1074
|
+
"run",
|
|
1075
|
+
"all",
|
|
1076
|
+
"get",
|
|
1077
|
+
"prepare"
|
|
1078
|
+
]);
|
|
1079
|
+
var SQL_SINK_FUNCTIONS = /* @__PURE__ */ new Set(["query", "execute"]);
|
|
1080
|
+
function getIdentifierName(node) {
|
|
1081
|
+
if (node.type === import_utils12.AST_NODE_TYPES.Identifier) {
|
|
1082
|
+
return node.name;
|
|
1083
|
+
}
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1086
|
+
function isSqlSinkCall(node) {
|
|
1087
|
+
if (node.callee.type === import_utils12.AST_NODE_TYPES.Identifier) {
|
|
1088
|
+
return SQL_SINK_FUNCTIONS.has(node.callee.name.toLowerCase());
|
|
1089
|
+
}
|
|
1090
|
+
if (node.callee.type === import_utils12.AST_NODE_TYPES.MemberExpression && node.callee.property.type === import_utils12.AST_NODE_TYPES.Identifier) {
|
|
1091
|
+
const methodName = node.callee.property.name.toLowerCase().replace(/^\$/, "");
|
|
1092
|
+
return SQL_SINK_METHODS.has(methodName);
|
|
1093
|
+
}
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
function resolveExpression(node, variableMap) {
|
|
1097
|
+
const identifier = getIdentifierName(node);
|
|
1098
|
+
if (identifier && variableMap.has(identifier)) {
|
|
1099
|
+
return variableMap.get(identifier);
|
|
1100
|
+
}
|
|
1101
|
+
return node;
|
|
1102
|
+
}
|
|
1103
|
+
function isDynamicSqlExpression(node) {
|
|
1104
|
+
if (node.type === import_utils12.AST_NODE_TYPES.TemplateLiteral) {
|
|
1105
|
+
if (node.expressions.length === 0) {
|
|
1106
|
+
return false;
|
|
1107
|
+
}
|
|
1108
|
+
const staticText = node.quasis.map((q) => q.value.raw).join(" ");
|
|
1109
|
+
return SQL_STATEMENT_PATTERN.test(staticText);
|
|
1110
|
+
}
|
|
1111
|
+
if (node.type === import_utils12.AST_NODE_TYPES.BinaryExpression && node.operator === "+") {
|
|
1112
|
+
const staticText = collectStaticText(node);
|
|
1113
|
+
if (!SQL_STATEMENT_PATTERN.test(staticText)) {
|
|
1114
|
+
return false;
|
|
1115
|
+
}
|
|
1116
|
+
return hasDynamicParts(node);
|
|
1117
|
+
}
|
|
1118
|
+
return false;
|
|
1119
|
+
}
|
|
862
1120
|
var noSqlStringConcat = createRule12({
|
|
863
1121
|
name: "no-sql-string-concat",
|
|
864
1122
|
meta: {
|
|
@@ -874,32 +1132,34 @@ var noSqlStringConcat = createRule12({
|
|
|
874
1132
|
},
|
|
875
1133
|
defaultOptions: [],
|
|
876
1134
|
create(context) {
|
|
1135
|
+
const variableMap = /* @__PURE__ */ new Map();
|
|
877
1136
|
return {
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
});
|
|
1137
|
+
VariableDeclarator(node) {
|
|
1138
|
+
if (node.id.type === import_utils12.AST_NODE_TYPES.Identifier && node.init && node.init.type !== import_utils12.AST_NODE_TYPES.AwaitExpression) {
|
|
1139
|
+
variableMap.set(node.id.name, node.init);
|
|
1140
|
+
}
|
|
1141
|
+
},
|
|
1142
|
+
AssignmentExpression(node) {
|
|
1143
|
+
if (node.left.type === import_utils12.AST_NODE_TYPES.Identifier && node.right.type !== import_utils12.AST_NODE_TYPES.AwaitExpression) {
|
|
1144
|
+
variableMap.set(node.left.name, node.right);
|
|
887
1145
|
}
|
|
888
1146
|
},
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
if (node.operator !== "+") return;
|
|
892
|
-
if (node.parent?.type === import_utils12.AST_NODE_TYPES.BinaryExpression && node.parent.operator === "+") {
|
|
1147
|
+
CallExpression(node) {
|
|
1148
|
+
if (!isSqlSinkCall(node)) {
|
|
893
1149
|
return;
|
|
894
1150
|
}
|
|
895
|
-
const
|
|
896
|
-
if (!
|
|
897
|
-
|
|
898
|
-
context.report({
|
|
899
|
-
node,
|
|
900
|
-
messageId: "sqlStringConcat"
|
|
901
|
-
});
|
|
1151
|
+
const firstArg = node.arguments[0];
|
|
1152
|
+
if (!firstArg || firstArg.type === import_utils12.AST_NODE_TYPES.SpreadElement) {
|
|
1153
|
+
return;
|
|
902
1154
|
}
|
|
1155
|
+
const resolved = resolveExpression(firstArg, variableMap);
|
|
1156
|
+
if (!isDynamicSqlExpression(resolved)) {
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
context.report({
|
|
1160
|
+
node: firstArg,
|
|
1161
|
+
messageId: "sqlStringConcat"
|
|
1162
|
+
});
|
|
903
1163
|
}
|
|
904
1164
|
};
|
|
905
1165
|
}
|