@sarj/eslint-plugin 15.26.0 → 15.26.2

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
@@ -96,6 +96,7 @@ ${missing.join("\n")}`);
96
96
  rationale: spec.rationale,
97
97
  remediation: spec.remediation,
98
98
  category: spec.category,
99
+ defaultLevel: spec.defaultLevel,
99
100
  languages: spec.languages,
100
101
  autofix: spec.autofix,
101
102
  aliases: spec.aliases,
@@ -177,6 +178,7 @@ function nativeSpec(config, documentation) {
177
178
  rationale: documentation.rationale,
178
179
  remediation: documentation.remediation,
179
180
  category: documentation.category,
181
+ defaultLevel: documentation.defaultLevel ?? "error",
180
182
  languages: [...documentation.languages ?? ["typescript"]],
181
183
  autofix: documentation.autofix ?? "none",
182
184
  aliases,
@@ -1020,6 +1022,7 @@ var require_button_accessible_name_default = createRule({
1020
1022
  // src/rules/require-camelcase-properties.ts
1021
1023
  var import_utils9 = require("@typescript-eslint/utils");
1022
1024
  var REQUIRE_CAMELCASE_PROPERTIES_DOCUMENTATION = {
1025
+ defaultLevel: "warning",
1023
1026
  summary: "Require unquoted lower snake_case TypeScript properties and dot access to use camelCase.",
1024
1027
  rationale: "Unquoted snake_case property syntax makes external wire naming indistinguishable from application-domain naming and lets inconsistent contracts spread through typed code.",
1025
1028
  remediation: "Rename application properties to camelCase. At an external wire boundary, make the exception explicit with a quoted key and bracket access.",
@@ -2348,6 +2351,7 @@ var FILE_SELF_REFERENCE_RE = /\bthis (?:file|module)\b/iu;
2348
2351
  var CHANGE_HISTORY_RE = /\b(?:used to|previously|histor(?:y|ical)|keep moving|models? (?:move|moved|change|changed)|still under review)\b/iu;
2349
2352
  var IMPLEMENTATION_MAP_RE = /\b(?:everything (?:above|below)|nothing else|only thing|write half|maps? between|seam between)\b/iu;
2350
2353
  var EXCESSIVE_COMMENTARY_DOCUMENTATION = {
2354
+ defaultLevel: "warning",
2351
2355
  summary: "Flag long standalone implementation commentary that should be expressed by code.",
2352
2356
  rationale: "Narrative implementation paragraphs compete with the code and can drift independently from behavior.",
2353
2357
  remediation: "Delete narration and clarify names, types, or structure; retain only durable constraints and external contracts.",
@@ -4145,12 +4149,12 @@ function createSqlListener(handler) {
4145
4149
 
4146
4150
  // src/rules/no-dynamic-sql.ts
4147
4151
  var NO_DYNAMIC_SQL_DOCUMENTATION = {
4148
- summary: "Disallow runtime values embedded inside quoted SQL values passed to statement-execution methods.",
4149
- rationale: "Embedding runtime values in SQL bypasses driver parameterization and can introduce injection defects or unstable query plans.",
4150
- remediation: "Use SQL placeholders and pass runtime values through the driver's binding API.",
4152
+ summary: "Disallow runtime values and fragments interpolated into SQL passed to statement-execution methods.",
4153
+ rationale: "Embedding runtime values or fragments in SQL bypasses driver parameterization or an explicit allowlist and can introduce injection defects or unstable query plans.",
4154
+ remediation: "Bind data values through SQL placeholders. Select non-bindable identifiers or clauses from fixed, reviewed fragments instead of interpolating runtime text.",
4151
4155
  category: "security",
4152
4156
  limitations: [
4153
- "Only single-quoted SQL values are inspected. Double-quoted identifiers, comments, dollar strings, and unquoted fragments are excluded; this is not a general SQL injection detector.",
4157
+ "Template and concatenation fragments are inspected at recognized execution calls. Double-quoted identifiers, comments, and dollar strings are excluded; this is not a general SQL injection detector.",
4154
4158
  "Literal fragments, legacy uppercase fragment names, and parameterizing tagged templates are exempt; uppercase spelling does not prove a value is static.",
4155
4159
  "The bounded lexer recognizes doubled quotes, comments, and PostgreSQL dollar strings; dialect-specific escape modes and SQL generated through other APIs require separate security review."
4156
4160
  ],
@@ -4172,6 +4176,26 @@ var NO_DYNAMIC_SQL_DOCUMENTATION = {
4172
4176
  focusPath: "src/users.ts",
4173
4177
  expectedCount: 1,
4174
4178
  public: true
4179
+ },
4180
+ {
4181
+ id: "bound-unquoted-value",
4182
+ scenarioId: "unquoted",
4183
+ title: "An unquoted runtime value is bound separately",
4184
+ outcome: "no-match",
4185
+ files: [{ path: "src/users.ts", source: "db.prepare('select id from users where id = ?').bind(userId);" }],
4186
+ focusPath: "src/users.ts",
4187
+ expectedCount: 0,
4188
+ public: true
4189
+ },
4190
+ {
4191
+ id: "unquoted-runtime-fragment",
4192
+ scenarioId: "unquoted",
4193
+ title: "A runtime fragment is interpolated into SQL",
4194
+ outcome: "match",
4195
+ files: [{ path: "src/users.ts", source: "db.prepare(`select id from users where id = ${userId}`);" }],
4196
+ focusPath: "src/users.ts",
4197
+ expectedCount: 1,
4198
+ public: true
4175
4199
  }
4176
4200
  ]
4177
4201
  };
@@ -4194,14 +4218,21 @@ function isStaticFragment(expression) {
4194
4218
  }
4195
4219
  function runtimeInterpolations(template) {
4196
4220
  const parts = template.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw);
4197
- const ranges = sqlSingleQuotedRanges(parts.join(RUNTIME_MARKER));
4221
+ const statement = parts.join(RUNTIME_MARKER);
4222
+ const ranges = sqlSingleQuotedRanges(statement);
4223
+ const visible = stripSqlNoise(statement);
4198
4224
  let offset = 0;
4199
- return template.expressions.filter(
4225
+ return template.expressions.flatMap(
4200
4226
  (expression, index) => {
4201
4227
  offset += parts[index]?.length ?? 0;
4202
4228
  const inValue = ranges.some(([start, end]) => start < offset && offset < end);
4229
+ const unquoted = visible.slice(offset, offset + RUNTIME_MARKER.length) === RUNTIME_MARKER;
4203
4230
  offset += RUNTIME_MARKER.length;
4204
- return inValue && !isStaticFragment(expression) && endsWithSqlQuote(parts[index] ?? "") && startsWithSqlQuote(parts[index + 1] ?? "");
4231
+ if (isStaticFragment(expression)) return [];
4232
+ if (inValue && endsWithSqlQuote(parts[index] ?? "") && startsWithSqlQuote(parts[index + 1] ?? "")) {
4233
+ return [{ expression, messageId: "dynamicSql" }];
4234
+ }
4235
+ return unquoted ? [{ expression, messageId: "dynamicFragment" }] : [];
4205
4236
  }
4206
4237
  );
4207
4238
  }
@@ -4232,16 +4263,21 @@ function runtimeConcatOperands(node) {
4232
4263
  return [];
4233
4264
  }
4234
4265
  const parts = operands.map((operand) => staticLiteralText(operand) ?? RUNTIME_MARKER);
4235
- const ranges = sqlSingleQuotedRanges(parts.join(""));
4266
+ const statement = parts.join("");
4267
+ const ranges = sqlSingleQuotedRanges(statement);
4268
+ const visible = stripSqlNoise(statement);
4236
4269
  let offset = 0;
4237
- return operands.filter((operand, index) => {
4270
+ return operands.flatMap((operand, index) => {
4238
4271
  const inValue = ranges.some(([start, end]) => start < offset && offset < end);
4272
+ const unquoted = visible.slice(offset, offset + RUNTIME_MARKER.length) === RUNTIME_MARKER;
4239
4273
  offset += parts[index]?.length ?? 0;
4240
- if (!inValue) return false;
4241
- if (isStaticFragment(operand)) return false;
4274
+ if (isStaticFragment(operand)) return [];
4242
4275
  const before = operands[index - 1];
4243
4276
  const after = operands[index + 1];
4244
- return before !== void 0 && after !== void 0 && endsWithSqlQuote(staticLiteralText(before) ?? "") && startsWithSqlQuote(staticLiteralText(after) ?? "");
4277
+ if (inValue && before !== void 0 && after !== void 0 && endsWithSqlQuote(staticLiteralText(before) ?? "") && startsWithSqlQuote(staticLiteralText(after) ?? "")) {
4278
+ return [{ expression: operand, messageId: "dynamicSql" }];
4279
+ }
4280
+ return unquoted ? [{ expression: operand, messageId: "dynamicFragment" }] : [];
4245
4281
  });
4246
4282
  }
4247
4283
  function concatOperands(node) {
@@ -4297,7 +4333,8 @@ var no_dynamic_sql_default = createRule({
4297
4333
  }
4298
4334
  ],
4299
4335
  messages: {
4300
- dynamicSql: "Runtime value embedded inside a quoted SQL value passed to `{{method}}()`. Replace the quoted interpolation with a placeholder and bind the value separately."
4336
+ dynamicSql: "Runtime value embedded inside a quoted SQL value passed to `{{method}}()`. Replace the quoted interpolation with a placeholder and bind the value separately.",
4337
+ dynamicFragment: "Runtime fragment interpolated into SQL passed to `{{method}}()`. Bind data values; select non-bindable SQL fragments from fixed, reviewed alternatives."
4301
4338
  }
4302
4339
  },
4303
4340
  defaultOptions: [{}],
@@ -4316,8 +4353,8 @@ var no_dynamic_sql_default = createRule({
4316
4353
  const offenders = statement.type === import_utils27.AST_NODE_TYPES.TemplateLiteral ? runtimeInterpolations(statement) : runtimeConcatOperands(statement);
4317
4354
  for (const offender of offenders) {
4318
4355
  context.report({
4319
- node: offender,
4320
- messageId: "dynamicSql",
4356
+ node: offender.expression,
4357
+ messageId: offender.messageId,
4321
4358
  data: { method }
4322
4359
  });
4323
4360
  }
@@ -5950,6 +5987,7 @@ function branchTerminates(branch) {
5950
5987
  var import_utils36 = require("@typescript-eslint/utils");
5951
5988
  var import_typescript5 = __toESM(require("typescript"), 1);
5952
5989
  var NO_JSON_STRINGIFY_OBJECT_EQUALITY_DOCUMENTATION = {
5990
+ defaultLevel: "warning",
5953
5991
  summary: "Do not use JSON serialization as structural object equality.",
5954
5992
  rationale: "JSON text equality depends on property insertion order and serialization behavior, so semantically equal objects can compare unequal and distinct values can collapse together.",
5955
5993
  remediation: "Compare an explicit domain projection structurally, or use a reviewed canonical serializer when JSON semantics are required.",
@@ -7209,6 +7247,7 @@ var no_bare_return_from_test_catch_default = createRule({
7209
7247
  // src/rules/no-bespoke-api-case-conversion.ts
7210
7248
  var import_utils45 = require("@typescript-eslint/utils");
7211
7249
  var NO_BESPOKE_API_CASE_CONVERSION_DOCUMENTATION = {
7250
+ defaultLevel: "warning",
7212
7251
  summary: "Review direct snake_case/camelCase mirror mappings on explicitly API-typed adapter values.",
7213
7252
  rationale: "Duplicating wire-name translation can drift from an API client contract. When the SDK owns application-facing names, centralizing conversion avoids maintaining another mirror by hand.",
7214
7253
  remediation: "Move wire-name ownership and case conversion into the generated SDK/model layer; keep application adapters on the generated typed surface.",
@@ -8955,6 +8994,7 @@ var WALL_NARRATION_RE2 = /^(?:(?:\d+[.)]|(?:phase|step)\s+\d+\s*:?)\s*)?(?:add|b
8955
8994
  var WALL_CLUSTER_MAX_LINE_GAP = 8;
8956
8995
  var WALL_CLUSTER_MIN_COMMENTS = 3;
8957
8996
  var NO_RESTATED_COMMENT_DOCUMENTATION = {
8997
+ defaultLevel: "warning",
8958
8998
  summary: "Flag short standalone comments that repeat the adjacent statement's identifiers.",
8959
8999
  rationale: "A comment that only repeats code adds no context and can become stale independently.",
8960
9000
  remediation: "Remove a genuine restatement; retain conditions, constraints, rationale, and information absent from the statement.",
@@ -9104,6 +9144,7 @@ function isRestatableLabel(body2) {
9104
9144
  // src/rules/no-restated-jsdoc.ts
9105
9145
  var import_utils57 = require("@typescript-eslint/utils");
9106
9146
  var NO_RESTATED_JSDOC_DOCUMENTATION = {
9147
+ defaultLevel: "warning",
9107
9148
  summary: "Flag JSDoc prose that appears to repeat declaration names without adding behavioral information.",
9108
9149
  rationale: "Signature-only JSDoc duplicates type information and drifts without helping callers.",
9109
9150
  remediation: "Delete the block or document behavior, constraints, failures, or context the signature cannot express.",
@@ -13663,6 +13704,7 @@ var prefer_input_group_search_default = createRule({
13663
13704
  // src/rules/prefer-millisecond-control-duration-schema.ts
13664
13705
  var import_utils84 = require("@typescript-eslint/utils");
13665
13706
  var PREFER_MILLISECOND_CONTROL_DURATION_SCHEMA_DOCUMENTATION = {
13707
+ defaultLevel: "warning",
13666
13708
  summary: "Require application-owned Zod control-duration fields to use millisecond granularity.",
13667
13709
  rationale: "Second-granularity timeout and scheduling controls lose precision and invite implicit unit conversion at API boundaries. Encoding milliseconds in the schema keeps the unit explicit and composes with platform timing APIs.",
13668
13710
  remediation: "Rename the field with an `Ms`/`_ms` suffix and express its bounds and default in milliseconds; update the owning API contract rather than converting in application code.",
@@ -15282,6 +15324,7 @@ var FUNCTION_TYPES9 = /* @__PURE__ */ new Set([
15282
15324
  import_utils89.AST_NODE_TYPES.FunctionExpression
15283
15325
  ]);
15284
15326
  var PREFER_MODULE_LEVEL_REFINED_SCHEMA_DOCUMENTATION = {
15327
+ defaultLevel: "warning",
15285
15328
  summary: "Declare closed Zod scalar, format, and wrapper schemas at module scope.",
15286
15329
  rationale: "A closed validation pipeline created inside a function is rebuilt on every invocation and obscures a reusable constraint.",
15287
15330
  remediation: "Move the validation schema to module scope, name it with a PascalCase Schema suffix, and call parse on the shared schema.",
@@ -15555,6 +15598,7 @@ var prefer_module_level_refined_schema_default = createRule({
15555
15598
  // src/rules/prefer-multi-value-zod-literal.ts
15556
15599
  var import_utils90 = require("@typescript-eslint/utils");
15557
15600
  var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
15601
+ defaultLevel: "warning",
15558
15602
  summary: "Use the Zod 4 multi-value literal API instead of a union of literal schemas.",
15559
15603
  rationale: "One multi-value literal expresses the same closed value domain without repeated schema wrappers.",
15560
15604
  remediation: "Replace the union with z.literal([value1, value2, ...]).",
@@ -15741,6 +15785,7 @@ var prefer_named_callback_domain_default = createRule({
15741
15785
  // src/rules/prefer-named-complex-return-type.ts
15742
15786
  var import_utils92 = require("@typescript-eslint/utils");
15743
15787
  var PREFER_NAMED_COMPLEX_RETURN_TYPE_DOCUMENTATION = {
15788
+ defaultLevel: "warning",
15744
15789
  summary: "Prefer a named contract for structurally complex function return types.",
15745
15790
  rationale: "A large inline return annotation hides a reusable domain concept and makes signatures difficult to scan.",
15746
15791
  remediation: "Name the complex nested shape while preserving its generic wrappers and type parameters; reference the named contract from the return annotation.",
@@ -15901,6 +15946,7 @@ var prefer_native_random_uuid_default = createRule({
15901
15946
  // src/rules/prefer-node-crypto-hash.ts
15902
15947
  var import_utils94 = require("@typescript-eslint/utils");
15903
15948
  var PREFER_NODE_CRYPTO_HASH_DOCUMENTATION = {
15949
+ defaultLevel: "warning",
15904
15950
  summary: "Prefer the modern one-shot node:crypto hash API when streaming state is unnecessary.",
15905
15951
  rationale: "A createHash-update-digest chain allocates mutable streaming state for a single in-memory value; Node's built-in hash function expresses the one-shot operation directly and can use its optimized fast path.",
15906
15952
  remediation: "On a supported Node runtime, consider hash(algorithm, value, encoding). Preserve the output encoding explicitly: digest() returns a Buffer, while hash defaults to hex. Keep createHash for streams or multiple updates.",
@@ -16033,6 +16079,7 @@ function isCreateHashCall(node, directBindings, namespaceBindings, resolve2) {
16033
16079
  // src/rules/prefer-node-fs-promises.ts
16034
16080
  var import_utils95 = require("@typescript-eslint/utils");
16035
16081
  var PREFER_NODE_FS_PROMISES_DOCUMENTATION = {
16082
+ defaultLevel: "warning",
16036
16083
  summary: "Prefer promise-based Node.js filesystem APIs over synchronous calls in production modules.",
16037
16084
  rationale: "Synchronous filesystem work blocks the event loop and can stall unrelated daemon, server, and worker tasks.",
16038
16085
  remediation: "Import the promise API from node:fs/promises and await it; use FileHandle.sync only where a documented durability boundary requires it.",
@@ -17299,6 +17346,7 @@ function ifGuardDominates(use, branch, positive) {
17299
17346
  // src/rules/prefer-shared-zod-enum.ts
17300
17347
  var import_utils100 = require("@typescript-eslint/utils");
17301
17348
  var PREFER_SHARED_ZOD_ENUM_DOCUMENTATION = {
17349
+ defaultLevel: "warning",
17302
17350
  summary: "Give repeated literal Zod enum domains one reusable module-level schema.",
17303
17351
  rationale: "Repeated literal domains hide a shared contract and allow equivalent fields to drift independently.",
17304
17352
  remediation: "Declare a module-level named Zod enum schema and reuse it at each field or contract site.",
@@ -17386,6 +17434,7 @@ var prefer_shared_zod_enum_default = createRule({
17386
17434
  // src/rules/prefer-switch-for-repeated-equality.ts
17387
17435
  var import_utils101 = require("@typescript-eslint/utils");
17388
17436
  var PREFER_SWITCH_FOR_REPEATED_EQUALITY_DOCUMENTATION = {
17437
+ defaultLevel: "warning",
17389
17438
  summary: "Prefer switch over long if/else-if chains that compare one value for strict equality.",
17390
17439
  rationale: "A switch makes finite dispatch cases visually uniform and easier to extend without duplicating the discriminant.",
17391
17440
  remediation: "Replace three or more strict-equality branches over the same discriminant with a switch; keep if statements for ranges, guards, and heterogeneous predicates.",
@@ -18244,6 +18293,7 @@ var LITERAL_KEY_HAZARDS = /* @__PURE__ */ new Set(["__proto__"]);
18244
18293
  var NUMERIC_SIGNS2 = /* @__PURE__ */ new Set(["-", "+"]);
18245
18294
  var MIN_RUN_LENGTH = 2;
18246
18295
  var PREFER_WHOLE_OBJECT_ASSERTION_DOCUMENTATION = {
18296
+ defaultLevel: "warning",
18247
18297
  summary: "Collapse consecutive assertions on one object into a whole-object assertion so related mismatches are reported together.",
18248
18298
  rationale: "One whole-object assertion presents related expectations together and produces a complete structural diff.",
18249
18299
  remediation: "Consider one `toMatchObject` assertion for ordinary data objects. Preserve missing-property checks, identity, and getter or proxy behavior when deciding whether to combine assertions.",
@@ -20071,6 +20121,7 @@ var require_fetch_timeout_default = createRule({
20071
20121
  // src/rules/require-interface-for-exported-class.ts
20072
20122
  var import_utils110 = require("@typescript-eslint/utils");
20073
20123
  var REQUIRE_INTERFACE_FOR_EXPORTED_CLASS_DOCUMENTATION = {
20124
+ defaultLevel: "warning",
20074
20125
  summary: "Require exported concrete classes with public behavior to declare a contract.",
20075
20126
  rationale: "An explicit contract names the intended public capability separately from implementation details. TypeScript already supports structural compatibility; this is an architecture policy, not a prerequisite for substitution.",
20076
20127
  remediation: "Declare a focused interface and add an implements clause, or inherit from an intentional base contract.",
@@ -20850,6 +20901,7 @@ var MODEL_EXECUTION_METHODS = /* @__PURE__ */ new Set([
20850
20901
  ]);
20851
20902
  var DATABASE_NAMES = /^(?:db|database|connection|pool|prisma|query|transaction|tx)$/iu;
20852
20903
  var REQUIRE_SQL_ACCESS_CLASS_DOCUMENTATION = {
20904
+ defaultLevel: "warning",
20853
20905
  summary: "Keep SQL reads and writes inside a class that receives its database dependency.",
20854
20906
  rationale: "An injected repository class is the preferred ownership boundary for database access under this architectural policy; free functions can also express explicit dependencies.",
20855
20907
  remediation: "Move the query into a repository or store class and inject the pool, connection, transaction, or typed database binding through its constructor.",
@@ -22565,6 +22617,7 @@ function importedAssertionKind(specifier) {
22565
22617
  // src/rules/sole-export-matches-filename.ts
22566
22618
  var import_utils120 = require("@typescript-eslint/utils");
22567
22619
  var SOLE_EXPORT_MATCHES_FILENAME_DOCUMENTATION = {
22620
+ defaultLevel: "warning",
22568
22621
  summary: "Make a module filename reflect its sole named public runtime export.",
22569
22622
  rationale: "When a module owns one runtime responsibility, matching names make that responsibility directly discoverable.",
22570
22623
  remediation: "Name the module for the exported responsibility, using either the full export name or a clear leading or trailing domain phrase; otherwise colocate genuinely related exports.",
@@ -23232,7 +23285,7 @@ var RULES = {
23232
23285
  };
23233
23286
  var meta = {
23234
23287
  name: "@sarj/eslint-plugin",
23235
- version: "15.26.0"
23288
+ version: "15.26.2"
23236
23289
  };
23237
23290
  var APPLICATION_ONLY_RULES = [];
23238
23291
  var LIBRARY_IMPORT_POLICY = ["error", {
package/dist/index.d.cts CHANGED
@@ -17,6 +17,7 @@ declare const RETIRED_RULES: Readonly<Record<string, RetiredRule>>;
17
17
 
18
18
  type RuleCategory = "architecture" | "correctness" | "maintainability" | "performance" | "security" | "style" | "testing";
19
19
  type AutofixPolicy = "none" | "safe" | "suggestion";
20
+ type DefaultLevel = "error" | "warning";
20
21
  type ExampleOutcome = "match" | "no-match";
21
22
  interface ExampleFile {
22
23
  readonly path: string;
@@ -40,6 +41,7 @@ interface RuleDocumentation {
40
41
  readonly rationale: string;
41
42
  readonly remediation: string;
42
43
  readonly category: RuleCategory;
44
+ readonly defaultLevel?: DefaultLevel;
43
45
  readonly languages?: readonly "typescript"[];
44
46
  readonly autofix?: AutofixPolicy;
45
47
  readonly aliases?: readonly string[];
@@ -69,6 +71,7 @@ interface PublicRuleSpec {
69
71
  readonly rationale: string;
70
72
  readonly remediation: string;
71
73
  readonly category: RuleCategory;
74
+ readonly defaultLevel: DefaultLevel;
72
75
  readonly languages: readonly "typescript"[];
73
76
  readonly autofix: AutofixPolicy;
74
77
  readonly aliases: readonly string[];
@@ -172,11 +175,6 @@ interface RuleOptions$1 {
172
175
  readonly max?: number;
173
176
  }
174
177
 
175
- /**
176
- * @fileoverview no-dynamic-sql — bind runtime values separately from SQL string literals.
177
- *
178
- * Examples: https://github.com/sarj-ai/code-standards/blob/main/packages/typescript/tests/rules/no-dynamic-sql.test.ts
179
- */
180
178
  interface RuleOptions {
181
179
  readonly methods?: readonly string[];
182
180
  }
@@ -244,7 +242,7 @@ declare const RULES: {
244
242
  readonly "no-cors-wildcard-with-credentials": DocumentedRule<readonly [], "corsWildcardWithCredentials">;
245
243
  readonly "no-duplicate-lifecycle-refresh-listeners": DocumentedRule<readonly [], "duplicateLifecycleRefresh">;
246
244
  readonly "no-dangerously-allow-svg": DocumentedRule<readonly [], "noDangerouslyAllowSvg">;
247
- readonly "no-dynamic-sql": DocumentedRule<readonly [RuleOptions?], "dynamicSql">;
245
+ readonly "no-dynamic-sql": DocumentedRule<readonly [RuleOptions?], "dynamicSql" | "dynamicFragment">;
248
246
  readonly "no-enum": DocumentedRule<readonly [{
249
247
  ignoreFiles?: readonly string[];
250
248
  }?], "noEnum">;
@@ -998,7 +996,7 @@ type FlatPreset = {
998
996
  declare const PLUGIN: {
999
997
  readonly meta: {
1000
998
  readonly name: "@sarj/eslint-plugin";
1001
- readonly version: "15.26.0";
999
+ readonly version: "15.26.2";
1002
1000
  };
1003
1001
  readonly rules: {
1004
1002
  readonly "no-conditional-empty-object-spread": DocumentedRule<[], "avoid">;
@@ -1034,7 +1032,7 @@ declare const PLUGIN: {
1034
1032
  readonly "no-cors-wildcard-with-credentials": DocumentedRule<readonly [], "corsWildcardWithCredentials">;
1035
1033
  readonly "no-duplicate-lifecycle-refresh-listeners": DocumentedRule<readonly [], "duplicateLifecycleRefresh">;
1036
1034
  readonly "no-dangerously-allow-svg": DocumentedRule<readonly [], "noDangerouslyAllowSvg">;
1037
- readonly "no-dynamic-sql": DocumentedRule<readonly [RuleOptions?], "dynamicSql">;
1035
+ readonly "no-dynamic-sql": DocumentedRule<readonly [RuleOptions?], "dynamicSql" | "dynamicFragment">;
1038
1036
  readonly "no-enum": DocumentedRule<readonly [{
1039
1037
  ignoreFiles?: readonly string[];
1040
1038
  }?], "noEnum">;
package/dist/index.d.ts CHANGED
@@ -17,6 +17,7 @@ declare const RETIRED_RULES: Readonly<Record<string, RetiredRule>>;
17
17
 
18
18
  type RuleCategory = "architecture" | "correctness" | "maintainability" | "performance" | "security" | "style" | "testing";
19
19
  type AutofixPolicy = "none" | "safe" | "suggestion";
20
+ type DefaultLevel = "error" | "warning";
20
21
  type ExampleOutcome = "match" | "no-match";
21
22
  interface ExampleFile {
22
23
  readonly path: string;
@@ -40,6 +41,7 @@ interface RuleDocumentation {
40
41
  readonly rationale: string;
41
42
  readonly remediation: string;
42
43
  readonly category: RuleCategory;
44
+ readonly defaultLevel?: DefaultLevel;
43
45
  readonly languages?: readonly "typescript"[];
44
46
  readonly autofix?: AutofixPolicy;
45
47
  readonly aliases?: readonly string[];
@@ -69,6 +71,7 @@ interface PublicRuleSpec {
69
71
  readonly rationale: string;
70
72
  readonly remediation: string;
71
73
  readonly category: RuleCategory;
74
+ readonly defaultLevel: DefaultLevel;
72
75
  readonly languages: readonly "typescript"[];
73
76
  readonly autofix: AutofixPolicy;
74
77
  readonly aliases: readonly string[];
@@ -172,11 +175,6 @@ interface RuleOptions$1 {
172
175
  readonly max?: number;
173
176
  }
174
177
 
175
- /**
176
- * @fileoverview no-dynamic-sql — bind runtime values separately from SQL string literals.
177
- *
178
- * Examples: https://github.com/sarj-ai/code-standards/blob/main/packages/typescript/tests/rules/no-dynamic-sql.test.ts
179
- */
180
178
  interface RuleOptions {
181
179
  readonly methods?: readonly string[];
182
180
  }
@@ -244,7 +242,7 @@ declare const RULES: {
244
242
  readonly "no-cors-wildcard-with-credentials": DocumentedRule<readonly [], "corsWildcardWithCredentials">;
245
243
  readonly "no-duplicate-lifecycle-refresh-listeners": DocumentedRule<readonly [], "duplicateLifecycleRefresh">;
246
244
  readonly "no-dangerously-allow-svg": DocumentedRule<readonly [], "noDangerouslyAllowSvg">;
247
- readonly "no-dynamic-sql": DocumentedRule<readonly [RuleOptions?], "dynamicSql">;
245
+ readonly "no-dynamic-sql": DocumentedRule<readonly [RuleOptions?], "dynamicSql" | "dynamicFragment">;
248
246
  readonly "no-enum": DocumentedRule<readonly [{
249
247
  ignoreFiles?: readonly string[];
250
248
  }?], "noEnum">;
@@ -998,7 +996,7 @@ type FlatPreset = {
998
996
  declare const PLUGIN: {
999
997
  readonly meta: {
1000
998
  readonly name: "@sarj/eslint-plugin";
1001
- readonly version: "15.26.0";
999
+ readonly version: "15.26.2";
1002
1000
  };
1003
1001
  readonly rules: {
1004
1002
  readonly "no-conditional-empty-object-spread": DocumentedRule<[], "avoid">;
@@ -1034,7 +1032,7 @@ declare const PLUGIN: {
1034
1032
  readonly "no-cors-wildcard-with-credentials": DocumentedRule<readonly [], "corsWildcardWithCredentials">;
1035
1033
  readonly "no-duplicate-lifecycle-refresh-listeners": DocumentedRule<readonly [], "duplicateLifecycleRefresh">;
1036
1034
  readonly "no-dangerously-allow-svg": DocumentedRule<readonly [], "noDangerouslyAllowSvg">;
1037
- readonly "no-dynamic-sql": DocumentedRule<readonly [RuleOptions?], "dynamicSql">;
1035
+ readonly "no-dynamic-sql": DocumentedRule<readonly [RuleOptions?], "dynamicSql" | "dynamicFragment">;
1038
1036
  readonly "no-enum": DocumentedRule<readonly [{
1039
1037
  ignoreFiles?: readonly string[];
1040
1038
  }?], "noEnum">;
package/dist/index.js CHANGED
@@ -45,6 +45,7 @@ ${missing.join("\n")}`);
45
45
  rationale: spec.rationale,
46
46
  remediation: spec.remediation,
47
47
  category: spec.category,
48
+ defaultLevel: spec.defaultLevel,
48
49
  languages: spec.languages,
49
50
  autofix: spec.autofix,
50
51
  aliases: spec.aliases,
@@ -126,6 +127,7 @@ function nativeSpec(config, documentation) {
126
127
  rationale: documentation.rationale,
127
128
  remediation: documentation.remediation,
128
129
  category: documentation.category,
130
+ defaultLevel: documentation.defaultLevel ?? "error",
129
131
  languages: [...documentation.languages ?? ["typescript"]],
130
132
  autofix: documentation.autofix ?? "none",
131
133
  aliases,
@@ -979,6 +981,7 @@ var require_button_accessible_name_default = createRule({
979
981
  // src/rules/require-camelcase-properties.ts
980
982
  import { AST_NODE_TYPES as AST_NODE_TYPES8 } from "@typescript-eslint/utils";
981
983
  var REQUIRE_CAMELCASE_PROPERTIES_DOCUMENTATION = {
984
+ defaultLevel: "warning",
982
985
  summary: "Require unquoted lower snake_case TypeScript properties and dot access to use camelCase.",
983
986
  rationale: "Unquoted snake_case property syntax makes external wire naming indistinguishable from application-domain naming and lets inconsistent contracts spread through typed code.",
984
987
  remediation: "Rename application properties to camelCase. At an external wire boundary, make the exception explicit with a quoted key and bracket access.",
@@ -2309,6 +2312,7 @@ var FILE_SELF_REFERENCE_RE = /\bthis (?:file|module)\b/iu;
2309
2312
  var CHANGE_HISTORY_RE = /\b(?:used to|previously|histor(?:y|ical)|keep moving|models? (?:move|moved|change|changed)|still under review)\b/iu;
2310
2313
  var IMPLEMENTATION_MAP_RE = /\b(?:everything (?:above|below)|nothing else|only thing|write half|maps? between|seam between)\b/iu;
2311
2314
  var EXCESSIVE_COMMENTARY_DOCUMENTATION = {
2315
+ defaultLevel: "warning",
2312
2316
  summary: "Flag long standalone implementation commentary that should be expressed by code.",
2313
2317
  rationale: "Narrative implementation paragraphs compete with the code and can drift independently from behavior.",
2314
2318
  remediation: "Delete narration and clarify names, types, or structure; retain only durable constraints and external contracts.",
@@ -4109,12 +4113,12 @@ function createSqlListener(handler) {
4109
4113
 
4110
4114
  // src/rules/no-dynamic-sql.ts
4111
4115
  var NO_DYNAMIC_SQL_DOCUMENTATION = {
4112
- summary: "Disallow runtime values embedded inside quoted SQL values passed to statement-execution methods.",
4113
- rationale: "Embedding runtime values in SQL bypasses driver parameterization and can introduce injection defects or unstable query plans.",
4114
- remediation: "Use SQL placeholders and pass runtime values through the driver's binding API.",
4116
+ summary: "Disallow runtime values and fragments interpolated into SQL passed to statement-execution methods.",
4117
+ rationale: "Embedding runtime values or fragments in SQL bypasses driver parameterization or an explicit allowlist and can introduce injection defects or unstable query plans.",
4118
+ remediation: "Bind data values through SQL placeholders. Select non-bindable identifiers or clauses from fixed, reviewed fragments instead of interpolating runtime text.",
4115
4119
  category: "security",
4116
4120
  limitations: [
4117
- "Only single-quoted SQL values are inspected. Double-quoted identifiers, comments, dollar strings, and unquoted fragments are excluded; this is not a general SQL injection detector.",
4121
+ "Template and concatenation fragments are inspected at recognized execution calls. Double-quoted identifiers, comments, and dollar strings are excluded; this is not a general SQL injection detector.",
4118
4122
  "Literal fragments, legacy uppercase fragment names, and parameterizing tagged templates are exempt; uppercase spelling does not prove a value is static.",
4119
4123
  "The bounded lexer recognizes doubled quotes, comments, and PostgreSQL dollar strings; dialect-specific escape modes and SQL generated through other APIs require separate security review."
4120
4124
  ],
@@ -4136,6 +4140,26 @@ var NO_DYNAMIC_SQL_DOCUMENTATION = {
4136
4140
  focusPath: "src/users.ts",
4137
4141
  expectedCount: 1,
4138
4142
  public: true
4143
+ },
4144
+ {
4145
+ id: "bound-unquoted-value",
4146
+ scenarioId: "unquoted",
4147
+ title: "An unquoted runtime value is bound separately",
4148
+ outcome: "no-match",
4149
+ files: [{ path: "src/users.ts", source: "db.prepare('select id from users where id = ?').bind(userId);" }],
4150
+ focusPath: "src/users.ts",
4151
+ expectedCount: 0,
4152
+ public: true
4153
+ },
4154
+ {
4155
+ id: "unquoted-runtime-fragment",
4156
+ scenarioId: "unquoted",
4157
+ title: "A runtime fragment is interpolated into SQL",
4158
+ outcome: "match",
4159
+ files: [{ path: "src/users.ts", source: "db.prepare(`select id from users where id = ${userId}`);" }],
4160
+ focusPath: "src/users.ts",
4161
+ expectedCount: 1,
4162
+ public: true
4139
4163
  }
4140
4164
  ]
4141
4165
  };
@@ -4158,14 +4182,21 @@ function isStaticFragment(expression) {
4158
4182
  }
4159
4183
  function runtimeInterpolations(template) {
4160
4184
  const parts = template.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw);
4161
- const ranges = sqlSingleQuotedRanges(parts.join(RUNTIME_MARKER));
4185
+ const statement = parts.join(RUNTIME_MARKER);
4186
+ const ranges = sqlSingleQuotedRanges(statement);
4187
+ const visible = stripSqlNoise(statement);
4162
4188
  let offset = 0;
4163
- return template.expressions.filter(
4189
+ return template.expressions.flatMap(
4164
4190
  (expression, index) => {
4165
4191
  offset += parts[index]?.length ?? 0;
4166
4192
  const inValue = ranges.some(([start, end]) => start < offset && offset < end);
4193
+ const unquoted = visible.slice(offset, offset + RUNTIME_MARKER.length) === RUNTIME_MARKER;
4167
4194
  offset += RUNTIME_MARKER.length;
4168
- return inValue && !isStaticFragment(expression) && endsWithSqlQuote(parts[index] ?? "") && startsWithSqlQuote(parts[index + 1] ?? "");
4195
+ if (isStaticFragment(expression)) return [];
4196
+ if (inValue && endsWithSqlQuote(parts[index] ?? "") && startsWithSqlQuote(parts[index + 1] ?? "")) {
4197
+ return [{ expression, messageId: "dynamicSql" }];
4198
+ }
4199
+ return unquoted ? [{ expression, messageId: "dynamicFragment" }] : [];
4169
4200
  }
4170
4201
  );
4171
4202
  }
@@ -4196,16 +4227,21 @@ function runtimeConcatOperands(node) {
4196
4227
  return [];
4197
4228
  }
4198
4229
  const parts = operands.map((operand) => staticLiteralText(operand) ?? RUNTIME_MARKER);
4199
- const ranges = sqlSingleQuotedRanges(parts.join(""));
4230
+ const statement = parts.join("");
4231
+ const ranges = sqlSingleQuotedRanges(statement);
4232
+ const visible = stripSqlNoise(statement);
4200
4233
  let offset = 0;
4201
- return operands.filter((operand, index) => {
4234
+ return operands.flatMap((operand, index) => {
4202
4235
  const inValue = ranges.some(([start, end]) => start < offset && offset < end);
4236
+ const unquoted = visible.slice(offset, offset + RUNTIME_MARKER.length) === RUNTIME_MARKER;
4203
4237
  offset += parts[index]?.length ?? 0;
4204
- if (!inValue) return false;
4205
- if (isStaticFragment(operand)) return false;
4238
+ if (isStaticFragment(operand)) return [];
4206
4239
  const before = operands[index - 1];
4207
4240
  const after = operands[index + 1];
4208
- return before !== void 0 && after !== void 0 && endsWithSqlQuote(staticLiteralText(before) ?? "") && startsWithSqlQuote(staticLiteralText(after) ?? "");
4241
+ if (inValue && before !== void 0 && after !== void 0 && endsWithSqlQuote(staticLiteralText(before) ?? "") && startsWithSqlQuote(staticLiteralText(after) ?? "")) {
4242
+ return [{ expression: operand, messageId: "dynamicSql" }];
4243
+ }
4244
+ return unquoted ? [{ expression: operand, messageId: "dynamicFragment" }] : [];
4209
4245
  });
4210
4246
  }
4211
4247
  function concatOperands(node) {
@@ -4261,7 +4297,8 @@ var no_dynamic_sql_default = createRule({
4261
4297
  }
4262
4298
  ],
4263
4299
  messages: {
4264
- dynamicSql: "Runtime value embedded inside a quoted SQL value passed to `{{method}}()`. Replace the quoted interpolation with a placeholder and bind the value separately."
4300
+ dynamicSql: "Runtime value embedded inside a quoted SQL value passed to `{{method}}()`. Replace the quoted interpolation with a placeholder and bind the value separately.",
4301
+ dynamicFragment: "Runtime fragment interpolated into SQL passed to `{{method}}()`. Bind data values; select non-bindable SQL fragments from fixed, reviewed alternatives."
4265
4302
  }
4266
4303
  },
4267
4304
  defaultOptions: [{}],
@@ -4280,8 +4317,8 @@ var no_dynamic_sql_default = createRule({
4280
4317
  const offenders = statement.type === AST_NODE_TYPES25.TemplateLiteral ? runtimeInterpolations(statement) : runtimeConcatOperands(statement);
4281
4318
  for (const offender of offenders) {
4282
4319
  context.report({
4283
- node: offender,
4284
- messageId: "dynamicSql",
4320
+ node: offender.expression,
4321
+ messageId: offender.messageId,
4285
4322
  data: { method }
4286
4323
  });
4287
4324
  }
@@ -5921,6 +5958,7 @@ import {
5921
5958
  } from "@typescript-eslint/utils";
5922
5959
  import ts5 from "typescript";
5923
5960
  var NO_JSON_STRINGIFY_OBJECT_EQUALITY_DOCUMENTATION = {
5961
+ defaultLevel: "warning",
5924
5962
  summary: "Do not use JSON serialization as structural object equality.",
5925
5963
  rationale: "JSON text equality depends on property insertion order and serialization behavior, so semantically equal objects can compare unequal and distinct values can collapse together.",
5926
5964
  remediation: "Compare an explicit domain projection structurally, or use a reviewed canonical serializer when JSON semantics are required.",
@@ -7191,6 +7229,7 @@ var no_bare_return_from_test_catch_default = createRule({
7191
7229
  // src/rules/no-bespoke-api-case-conversion.ts
7192
7230
  import { AST_NODE_TYPES as AST_NODE_TYPES38, ASTUtils as ASTUtils15 } from "@typescript-eslint/utils";
7193
7231
  var NO_BESPOKE_API_CASE_CONVERSION_DOCUMENTATION = {
7232
+ defaultLevel: "warning",
7194
7233
  summary: "Review direct snake_case/camelCase mirror mappings on explicitly API-typed adapter values.",
7195
7234
  rationale: "Duplicating wire-name translation can drift from an API client contract. When the SDK owns application-facing names, centralizing conversion avoids maintaining another mirror by hand.",
7196
7235
  remediation: "Move wire-name ownership and case conversion into the generated SDK/model layer; keep application adapters on the generated typed surface.",
@@ -8937,6 +8976,7 @@ var WALL_NARRATION_RE2 = /^(?:(?:\d+[.)]|(?:phase|step)\s+\d+\s*:?)\s*)?(?:add|b
8937
8976
  var WALL_CLUSTER_MAX_LINE_GAP = 8;
8938
8977
  var WALL_CLUSTER_MIN_COMMENTS = 3;
8939
8978
  var NO_RESTATED_COMMENT_DOCUMENTATION = {
8979
+ defaultLevel: "warning",
8940
8980
  summary: "Flag short standalone comments that repeat the adjacent statement's identifiers.",
8941
8981
  rationale: "A comment that only repeats code adds no context and can become stale independently.",
8942
8982
  remediation: "Remove a genuine restatement; retain conditions, constraints, rationale, and information absent from the statement.",
@@ -9086,6 +9126,7 @@ function isRestatableLabel(body2) {
9086
9126
  // src/rules/no-restated-jsdoc.ts
9087
9127
  import { AST_NODE_TYPES as AST_NODE_TYPES47 } from "@typescript-eslint/utils";
9088
9128
  var NO_RESTATED_JSDOC_DOCUMENTATION = {
9129
+ defaultLevel: "warning",
9089
9130
  summary: "Flag JSDoc prose that appears to repeat declaration names without adding behavioral information.",
9090
9131
  rationale: "Signature-only JSDoc duplicates type information and drifts without helping callers.",
9091
9132
  remediation: "Delete the block or document behavior, constraints, failures, or context the signature cannot express.",
@@ -13658,6 +13699,7 @@ import {
13658
13699
  AST_NODE_TYPES as AST_NODE_TYPES68
13659
13700
  } from "@typescript-eslint/utils";
13660
13701
  var PREFER_MILLISECOND_CONTROL_DURATION_SCHEMA_DOCUMENTATION = {
13702
+ defaultLevel: "warning",
13661
13703
  summary: "Require application-owned Zod control-duration fields to use millisecond granularity.",
13662
13704
  rationale: "Second-granularity timeout and scheduling controls lose precision and invite implicit unit conversion at API boundaries. Encoding milliseconds in the schema keeps the unit explicit and composes with platform timing APIs.",
13663
13705
  remediation: "Rename the field with an `Ms`/`_ms` suffix and express its bounds and default in milliseconds; update the owning API contract rather than converting in application code.",
@@ -15280,6 +15322,7 @@ var FUNCTION_TYPES9 = /* @__PURE__ */ new Set([
15280
15322
  AST_NODE_TYPES73.FunctionExpression
15281
15323
  ]);
15282
15324
  var PREFER_MODULE_LEVEL_REFINED_SCHEMA_DOCUMENTATION = {
15325
+ defaultLevel: "warning",
15283
15326
  summary: "Declare closed Zod scalar, format, and wrapper schemas at module scope.",
15284
15327
  rationale: "A closed validation pipeline created inside a function is rebuilt on every invocation and obscures a reusable constraint.",
15285
15328
  remediation: "Move the validation schema to module scope, name it with a PascalCase Schema suffix, and call parse on the shared schema.",
@@ -15556,6 +15599,7 @@ import {
15556
15599
  ASTUtils as ASTUtils34
15557
15600
  } from "@typescript-eslint/utils";
15558
15601
  var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
15602
+ defaultLevel: "warning",
15559
15603
  summary: "Use the Zod 4 multi-value literal API instead of a union of literal schemas.",
15560
15604
  rationale: "One multi-value literal expresses the same closed value domain without repeated schema wrappers.",
15561
15605
  remediation: "Replace the union with z.literal([value1, value2, ...]).",
@@ -15742,6 +15786,7 @@ var prefer_named_callback_domain_default = createRule({
15742
15786
  // src/rules/prefer-named-complex-return-type.ts
15743
15787
  import { AST_NODE_TYPES as AST_NODE_TYPES76 } from "@typescript-eslint/utils";
15744
15788
  var PREFER_NAMED_COMPLEX_RETURN_TYPE_DOCUMENTATION = {
15789
+ defaultLevel: "warning",
15745
15790
  summary: "Prefer a named contract for structurally complex function return types.",
15746
15791
  rationale: "A large inline return annotation hides a reusable domain concept and makes signatures difficult to scan.",
15747
15792
  remediation: "Name the complex nested shape while preserving its generic wrappers and type parameters; reference the named contract from the return annotation.",
@@ -15902,6 +15947,7 @@ var prefer_native_random_uuid_default = createRule({
15902
15947
  // src/rules/prefer-node-crypto-hash.ts
15903
15948
  import { AST_NODE_TYPES as AST_NODE_TYPES78, ASTUtils as ASTUtils36 } from "@typescript-eslint/utils";
15904
15949
  var PREFER_NODE_CRYPTO_HASH_DOCUMENTATION = {
15950
+ defaultLevel: "warning",
15905
15951
  summary: "Prefer the modern one-shot node:crypto hash API when streaming state is unnecessary.",
15906
15952
  rationale: "A createHash-update-digest chain allocates mutable streaming state for a single in-memory value; Node's built-in hash function expresses the one-shot operation directly and can use its optimized fast path.",
15907
15953
  remediation: "On a supported Node runtime, consider hash(algorithm, value, encoding). Preserve the output encoding explicitly: digest() returns a Buffer, while hash defaults to hex. Keep createHash for streams or multiple updates.",
@@ -16034,6 +16080,7 @@ function isCreateHashCall(node, directBindings, namespaceBindings, resolve2) {
16034
16080
  // src/rules/prefer-node-fs-promises.ts
16035
16081
  import { AST_NODE_TYPES as AST_NODE_TYPES79, ASTUtils as ASTUtils37 } from "@typescript-eslint/utils";
16036
16082
  var PREFER_NODE_FS_PROMISES_DOCUMENTATION = {
16083
+ defaultLevel: "warning",
16037
16084
  summary: "Prefer promise-based Node.js filesystem APIs over synchronous calls in production modules.",
16038
16085
  rationale: "Synchronous filesystem work blocks the event loop and can stall unrelated daemon, server, and worker tasks.",
16039
16086
  remediation: "Import the promise API from node:fs/promises and await it; use FileHandle.sync only where a documented durability boundary requires it.",
@@ -17308,6 +17355,7 @@ function ifGuardDominates(use, branch, positive) {
17308
17355
  // src/rules/prefer-shared-zod-enum.ts
17309
17356
  import { AST_NODE_TYPES as AST_NODE_TYPES84, ASTUtils as ASTUtils42 } from "@typescript-eslint/utils";
17310
17357
  var PREFER_SHARED_ZOD_ENUM_DOCUMENTATION = {
17358
+ defaultLevel: "warning",
17311
17359
  summary: "Give repeated literal Zod enum domains one reusable module-level schema.",
17312
17360
  rationale: "Repeated literal domains hide a shared contract and allow equivalent fields to drift independently.",
17313
17361
  remediation: "Declare a module-level named Zod enum schema and reuse it at each field or contract site.",
@@ -17395,6 +17443,7 @@ var prefer_shared_zod_enum_default = createRule({
17395
17443
  // src/rules/prefer-switch-for-repeated-equality.ts
17396
17444
  import { AST_NODE_TYPES as AST_NODE_TYPES85 } from "@typescript-eslint/utils";
17397
17445
  var PREFER_SWITCH_FOR_REPEATED_EQUALITY_DOCUMENTATION = {
17446
+ defaultLevel: "warning",
17398
17447
  summary: "Prefer switch over long if/else-if chains that compare one value for strict equality.",
17399
17448
  rationale: "A switch makes finite dispatch cases visually uniform and easier to extend without duplicating the discriminant.",
17400
17449
  remediation: "Replace three or more strict-equality branches over the same discriminant with a switch; keep if statements for ranges, guards, and heterogeneous predicates.",
@@ -18253,6 +18302,7 @@ var LITERAL_KEY_HAZARDS = /* @__PURE__ */ new Set(["__proto__"]);
18253
18302
  var NUMERIC_SIGNS2 = /* @__PURE__ */ new Set(["-", "+"]);
18254
18303
  var MIN_RUN_LENGTH = 2;
18255
18304
  var PREFER_WHOLE_OBJECT_ASSERTION_DOCUMENTATION = {
18305
+ defaultLevel: "warning",
18256
18306
  summary: "Collapse consecutive assertions on one object into a whole-object assertion so related mismatches are reported together.",
18257
18307
  rationale: "One whole-object assertion presents related expectations together and produces a complete structural diff.",
18258
18308
  remediation: "Consider one `toMatchObject` assertion for ordinary data objects. Preserve missing-property checks, identity, and getter or proxy behavior when deciding whether to combine assertions.",
@@ -20086,6 +20136,7 @@ var require_fetch_timeout_default = createRule({
20086
20136
  // src/rules/require-interface-for-exported-class.ts
20087
20137
  import { AST_NODE_TYPES as AST_NODE_TYPES93 } from "@typescript-eslint/utils";
20088
20138
  var REQUIRE_INTERFACE_FOR_EXPORTED_CLASS_DOCUMENTATION = {
20139
+ defaultLevel: "warning",
20089
20140
  summary: "Require exported concrete classes with public behavior to declare a contract.",
20090
20141
  rationale: "An explicit contract names the intended public capability separately from implementation details. TypeScript already supports structural compatibility; this is an architecture policy, not a prerequisite for substitution.",
20091
20142
  remediation: "Declare a focused interface and add an implements clause, or inherit from an intentional base contract.",
@@ -20865,6 +20916,7 @@ var MODEL_EXECUTION_METHODS = /* @__PURE__ */ new Set([
20865
20916
  ]);
20866
20917
  var DATABASE_NAMES = /^(?:db|database|connection|pool|prisma|query|transaction|tx)$/iu;
20867
20918
  var REQUIRE_SQL_ACCESS_CLASS_DOCUMENTATION = {
20919
+ defaultLevel: "warning",
20868
20920
  summary: "Keep SQL reads and writes inside a class that receives its database dependency.",
20869
20921
  rationale: "An injected repository class is the preferred ownership boundary for database access under this architectural policy; free functions can also express explicit dependencies.",
20870
20922
  remediation: "Move the query into a repository or store class and inject the pool, connection, transaction, or typed database binding through its constructor.",
@@ -22583,6 +22635,7 @@ function importedAssertionKind(specifier) {
22583
22635
  // src/rules/sole-export-matches-filename.ts
22584
22636
  import { AST_NODE_TYPES as AST_NODE_TYPES101 } from "@typescript-eslint/utils";
22585
22637
  var SOLE_EXPORT_MATCHES_FILENAME_DOCUMENTATION = {
22638
+ defaultLevel: "warning",
22586
22639
  summary: "Make a module filename reflect its sole named public runtime export.",
22587
22640
  rationale: "When a module owns one runtime responsibility, matching names make that responsibility directly discoverable.",
22588
22641
  remediation: "Name the module for the exported responsibility, using either the full export name or a clear leading or trailing domain phrase; otherwise colocate genuinely related exports.",
@@ -23253,7 +23306,7 @@ var RULES = {
23253
23306
  };
23254
23307
  var meta = {
23255
23308
  name: "@sarj/eslint-plugin",
23256
- version: "15.26.0"
23309
+ version: "15.26.2"
23257
23310
  };
23258
23311
  var APPLICATION_ONLY_RULES = [];
23259
23312
  var LIBRARY_IMPORT_POLICY = ["error", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "15.26.0",
3
+ "version": "15.26.2",
4
4
  "packageManager": "npm@12.0.2",
5
5
  "description": "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
6
6
  "type": "module",