@sarj/eslint-plugin 16.5.2 → 16.6.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 +81 -19
- package/dist/index.js +81 -19
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -52,7 +52,7 @@ module.exports = __toCommonJS(src_exports);
|
|
|
52
52
|
// package.json
|
|
53
53
|
var package_default = {
|
|
54
54
|
name: "@sarj/eslint-plugin",
|
|
55
|
-
version: "16.
|
|
55
|
+
version: "16.6.0",
|
|
56
56
|
packageManager: "npm@12.1.0",
|
|
57
57
|
description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
|
|
58
58
|
type: "module",
|
|
@@ -716,6 +716,9 @@ var REQUIRE_PORT_FOR_SERVICE_DOCUMENTATION = {
|
|
|
716
716
|
remediation: "Declare and implement an interface covering the service's public methods.",
|
|
717
717
|
category: "architecture",
|
|
718
718
|
aliases: ["require-interface-for-injected-service"],
|
|
719
|
+
limitations: [
|
|
720
|
+
"Retained injection requires an untouched parameter property or a single direct, unconditional constructor assignment. Conditional, repeated, replaced, updated, or deleted stores are unresolved rather than proof of retained injection."
|
|
721
|
+
],
|
|
719
722
|
examples: [
|
|
720
723
|
{ id: "declared-service-port", title: "Implement the service port", outcome: "no-match", files: [{ path: "src/service.ts", source: "interface Handler { handle(): void }\nexport class RequestHandler implements Handler { constructor(private readonly store: TaskStore) {} handle(): void { this.store.handle(); } }" }], focusPath: "src/service.ts", expectedCount: 0, public: true },
|
|
721
724
|
{ id: "concrete-injected-service", title: "Do not expose only the concrete service", outcome: "match", files: [{ path: "src/service.ts", source: "export class RequestHandler { constructor(private readonly store: TaskStore) {} handle(): void { this.store.handle(); } }" }], focusPath: "src/service.ts", expectedCount: 1, public: true }
|
|
@@ -842,6 +845,8 @@ var readConstructor = (ctor, declared, typeParameters) => {
|
|
|
842
845
|
const storedFieldsFrom = /* @__PURE__ */ new Map();
|
|
843
846
|
const storedMemberFieldsFrom = /* @__PURE__ */ new Map();
|
|
844
847
|
const constructedFieldNames = /* @__PURE__ */ new Set();
|
|
848
|
+
const writtenFieldNames = /* @__PURE__ */ new Set();
|
|
849
|
+
const unstableFieldNames = /* @__PURE__ */ new Set();
|
|
845
850
|
const localBindings2 = /* @__PURE__ */ new Set();
|
|
846
851
|
let constructedFields = 0;
|
|
847
852
|
if (body2 !== null && body2 !== void 0) scanStoredFields(body2);
|
|
@@ -850,14 +855,18 @@ var readConstructor = (ctor, declared, typeParameters) => {
|
|
|
850
855
|
function recordStoredField(current) {
|
|
851
856
|
if (current.type === import_utils4.AST_NODE_TYPES.ArrowFunctionExpression || current.type === import_utils4.AST_NODE_TYPES.FunctionExpression || current.type === import_utils4.AST_NODE_TYPES.FunctionDeclaration || current.type === import_utils4.AST_NODE_TYPES.ClassExpression || current.type === import_utils4.AST_NODE_TYPES.ClassDeclaration) return;
|
|
852
857
|
if (current.type === import_utils4.AST_NODE_TYPES.VariableDeclarator && current.id.type === import_utils4.AST_NODE_TYPES.Identifier) localBindings2.add(current.id.name);
|
|
853
|
-
const expression = current.type === import_utils4.AST_NODE_TYPES.
|
|
854
|
-
const storedField =
|
|
858
|
+
const expression = current.type === import_utils4.AST_NODE_TYPES.AssignmentExpression ? current : null;
|
|
859
|
+
const storedField = writtenInstanceField(current);
|
|
860
|
+
if (storedField !== null) {
|
|
861
|
+
if (writtenFieldNames.has(storedField)) unstableFieldNames.add(storedField);
|
|
862
|
+
writtenFieldNames.add(storedField);
|
|
863
|
+
if (expression?.operator !== "=" || expression.parent.type !== import_utils4.AST_NODE_TYPES.ExpressionStatement || expression.parent.parent !== body3) unstableFieldNames.add(storedField);
|
|
864
|
+
}
|
|
855
865
|
if (expression?.type !== import_utils4.AST_NODE_TYPES.AssignmentExpression || !STORAGE_ASSIGNMENT_OPERATORS.has(expression.operator) || expression.left.type !== import_utils4.AST_NODE_TYPES.MemberExpression || expression.left.object.type !== import_utils4.AST_NODE_TYPES.ThisExpression || storedField === null) {
|
|
856
866
|
enqueueChildren(current);
|
|
857
867
|
return;
|
|
858
868
|
}
|
|
859
|
-
|
|
860
|
-
while (source.type === import_utils4.AST_NODE_TYPES.TSNonNullExpression || source.type === import_utils4.AST_NODE_TYPES.TSAsExpression || source.type === import_utils4.AST_NODE_TYPES.TSSatisfiesExpression || source.type === import_utils4.AST_NODE_TYPES.TSTypeAssertion) source = source.expression;
|
|
869
|
+
const source = unwrapExpression(expression.right);
|
|
861
870
|
if (source.type === import_utils4.AST_NODE_TYPES.NewExpression) {
|
|
862
871
|
constructedFields += 1;
|
|
863
872
|
constructedFieldNames.add(storedField);
|
|
@@ -878,6 +887,7 @@ var readConstructor = (ctor, declared, typeParameters) => {
|
|
|
878
887
|
storedMemberFieldsFrom.set(source.object.name, members);
|
|
879
888
|
}
|
|
880
889
|
}
|
|
890
|
+
enqueueChildren(current);
|
|
881
891
|
}
|
|
882
892
|
function enqueueChildren(current) {
|
|
883
893
|
forEachOwnAstChild(current, (child) => {
|
|
@@ -895,16 +905,15 @@ var readConstructor = (ctor, declared, typeParameters) => {
|
|
|
895
905
|
for (const reference of parameterCollaborators(parameter, declared, storedMemberFieldsFrom)) {
|
|
896
906
|
if (localBindings2.has(reference.name)) continue;
|
|
897
907
|
let fields = reference.fields;
|
|
898
|
-
if (parameter.type === import_utils4.AST_NODE_TYPES.TSParameterProperty)
|
|
899
|
-
|
|
900
|
-
|
|
908
|
+
if (parameter.type === import_utils4.AST_NODE_TYPES.TSParameterProperty) {
|
|
909
|
+
if (writtenFieldNames.has(reference.name)) continue;
|
|
910
|
+
fields = [reference.name];
|
|
911
|
+
} else if (fields.length === 0) fields = [...storedFieldsFrom.get(reference.name) ?? []];
|
|
912
|
+
const injectedFields = fields.filter((field) => !constructedFieldNames.has(field) && !unstableFieldNames.has(field));
|
|
901
913
|
if (injectedFields.length === 0) continue;
|
|
902
914
|
if (CONFIGISH_TYPE_RE.test(reference.typeName)) continue;
|
|
903
915
|
if (CONFIGISH_NAME_RE.test(reference.name)) continue;
|
|
904
|
-
if (typeParameters.has(reference.typeName)) continue;
|
|
905
|
-
if (BUILTIN_CONTAINER_TYPE_RE.test(reference.typeName)) continue;
|
|
906
|
-
if (VALUE_TYPE_RE.test(reference.typeName)) continue;
|
|
907
|
-
if (declared().functionAliases.has(reference.typeName)) continue;
|
|
916
|
+
if (typeParameters.has(reference.typeName) || BUILTIN_CONTAINER_TYPE_RE.test(reference.typeName) || VALUE_TYPE_RE.test(reference.typeName) || declared().functionAliases.has(reference.typeName)) continue;
|
|
908
917
|
collaborators.push({ ...reference, fields: injectedFields });
|
|
909
918
|
}
|
|
910
919
|
}
|
|
@@ -1015,6 +1024,29 @@ var instanceField = (candidate2) => {
|
|
|
1015
1024
|
while (node.type === import_utils4.AST_NODE_TYPES.ChainExpression || node.type === import_utils4.AST_NODE_TYPES.TSAsExpression || node.type === import_utils4.AST_NODE_TYPES.TSNonNullExpression || node.type === import_utils4.AST_NODE_TYPES.TSSatisfiesExpression || node.type === import_utils4.AST_NODE_TYPES.TSTypeAssertion) node = node.expression;
|
|
1016
1025
|
return node.type === import_utils4.AST_NODE_TYPES.MemberExpression && node.object.type === import_utils4.AST_NODE_TYPES.ThisExpression ? staticMemberName(node) : null;
|
|
1017
1026
|
};
|
|
1027
|
+
function writtenInstanceField(node) {
|
|
1028
|
+
if (node.type === import_utils4.AST_NODE_TYPES.AssignmentExpression || node.type === import_utils4.AST_NODE_TYPES.ForOfStatement || node.type === import_utils4.AST_NODE_TYPES.ForInStatement) return instanceField(node.left);
|
|
1029
|
+
if (node.type === import_utils4.AST_NODE_TYPES.UpdateExpression) return instanceField(node.argument);
|
|
1030
|
+
if (node.type === import_utils4.AST_NODE_TYPES.UnaryExpression && node.operator === "delete") return instanceField(node.argument);
|
|
1031
|
+
if (node.type === import_utils4.AST_NODE_TYPES.MemberExpression && isDestructuredWriteTarget(node)) return instanceField(node);
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
1034
|
+
function isDestructuredWriteTarget(node) {
|
|
1035
|
+
const access = outerExpression(node);
|
|
1036
|
+
let target = access;
|
|
1037
|
+
for (; ; ) {
|
|
1038
|
+
const parent = target.parent;
|
|
1039
|
+
if (parent.type === import_utils4.AST_NODE_TYPES.Property && parent.value === target && parent.parent.type === import_utils4.AST_NODE_TYPES.ObjectPattern) {
|
|
1040
|
+
target = parent.parent;
|
|
1041
|
+
continue;
|
|
1042
|
+
}
|
|
1043
|
+
if (parent.type === import_utils4.AST_NODE_TYPES.ArrayPattern || parent.type === import_utils4.AST_NODE_TYPES.ObjectPattern || parent.type === import_utils4.AST_NODE_TYPES.RestElement && parent.argument === target || parent.type === import_utils4.AST_NODE_TYPES.AssignmentPattern && parent.left === target) {
|
|
1044
|
+
target = parent;
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
return target !== access && (parent.type === import_utils4.AST_NODE_TYPES.AssignmentExpression || parent.type === import_utils4.AST_NODE_TYPES.ForOfStatement || parent.type === import_utils4.AST_NODE_TYPES.ForInStatement) && parent.left === target;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1018
1050
|
var behaviorallyInvokedFields = (body2) => {
|
|
1019
1051
|
const invoked = /* @__PURE__ */ new Set();
|
|
1020
1052
|
const visit = (current) => {
|
|
@@ -1331,13 +1363,23 @@ function createServiceOperationResolver(program) {
|
|
|
1331
1363
|
}
|
|
1332
1364
|
function serviceOperationBodies(node) {
|
|
1333
1365
|
const bodies = /* @__PURE__ */ new Map();
|
|
1366
|
+
const written = /* @__PURE__ */ new Set();
|
|
1367
|
+
const pending = [node.body];
|
|
1368
|
+
while (pending.length > 0) {
|
|
1369
|
+
const current = pending.pop();
|
|
1370
|
+
if (current === void 0) break;
|
|
1371
|
+
const name = writtenInstanceField(current);
|
|
1372
|
+
if (name !== null) written.add(name);
|
|
1373
|
+
forEachOwnAstChild(current, (child) => {
|
|
1374
|
+
pending.push(child);
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1334
1377
|
for (const member of node.body.body) {
|
|
1335
1378
|
if (member.type !== import_utils4.AST_NODE_TYPES.MethodDefinition && member.type !== import_utils4.AST_NODE_TYPES.PropertyDefinition) continue;
|
|
1336
|
-
if (member.static || member.decorators.length > 0) continue;
|
|
1337
|
-
if (member.type === import_utils4.AST_NODE_TYPES.MethodDefinition && member.kind !== "method") continue;
|
|
1379
|
+
if (member.static || member.decorators.length > 0 || member.type === import_utils4.AST_NODE_TYPES.MethodDefinition && member.kind !== "method") continue;
|
|
1338
1380
|
if (member.value?.type !== import_utils4.AST_NODE_TYPES.FunctionExpression && member.value?.type !== import_utils4.AST_NODE_TYPES.ArrowFunctionExpression) continue;
|
|
1339
1381
|
const name = declaredMemberName(member);
|
|
1340
|
-
if (name !== null && member.value.body !== null) bodies.set(name, member.value.body);
|
|
1382
|
+
if (name !== null && !written.has(name) && member.value.body !== null) bodies.set(name, member.value.body);
|
|
1341
1383
|
}
|
|
1342
1384
|
return bodies;
|
|
1343
1385
|
}
|
|
@@ -1450,6 +1492,7 @@ var REQUIRE_EXPLICIT_SERVICE_CONTRACT_DOCUMENTATION = {
|
|
|
1450
1492
|
"The rule checks service, store, and provider class declarations whose constructor stores a typed behavioral collaborator and whose public method reaches that collaborator.",
|
|
1451
1493
|
"Existing local interfaces, type aliases, and inherited contracts can cover the operations; the compiler owns signature compatibility.",
|
|
1452
1494
|
"Imported or dynamic base classes with unavailable declarations and decorated classes or operations are unresolved and do not produce a finding.",
|
|
1495
|
+
"Constructor retention requires an untouched parameter property or one direct, unconditional assignment. Written or replaced operation members do not prove their declared bodies execute; external aliases, mutators, and callbacks are not resolved.",
|
|
1453
1496
|
"Generated files and JavaScript without implements syntax are excluded."
|
|
1454
1497
|
],
|
|
1455
1498
|
examples: [
|
|
@@ -7575,7 +7618,8 @@ var NO_IMPOSSIBLE_ZOD_LITERAL_BOUNDS_DOCUMENTATION = {
|
|
|
7575
7618
|
remediation: "Choose compatible lower and upper bounds, or remove the constraint that does not express the intended domain.",
|
|
7576
7619
|
category: "correctness",
|
|
7577
7620
|
limitations: [
|
|
7578
|
-
"Only finite numeric literals in a single number, string, or array schema chain are compared.",
|
|
7621
|
+
"Only finite numeric literals in a single number, string, or array schema chain are compared; explicit int() chains also require an admitted integer.",
|
|
7622
|
+
"Integer-domain comparisons abstain near the JavaScript safe-integer boundary.",
|
|
7579
7623
|
"Chains with dynamic bounds, non-bound validators, transforms, pipes, or preprocessors are skipped.",
|
|
7580
7624
|
"Test and generated files are excluded."
|
|
7581
7625
|
],
|
|
@@ -7614,6 +7658,7 @@ var KINDS = /* @__PURE__ */ new Set(["array", "number", "string"]);
|
|
|
7614
7658
|
var NUMBER_METHODS = /* @__PURE__ */ new Set([
|
|
7615
7659
|
"gt",
|
|
7616
7660
|
"gte",
|
|
7661
|
+
"int",
|
|
7617
7662
|
"lt",
|
|
7618
7663
|
"lte",
|
|
7619
7664
|
"max",
|
|
@@ -7655,9 +7700,20 @@ function strongerUpper(current, candidate2) {
|
|
|
7655
7700
|
}
|
|
7656
7701
|
return current;
|
|
7657
7702
|
}
|
|
7658
|
-
function isEmpty(lower, upper) {
|
|
7703
|
+
function isEmpty(lower, upper, integer) {
|
|
7659
7704
|
if (lower === null || upper === null) return false;
|
|
7660
|
-
|
|
7705
|
+
const realEmpty = lower.value > upper.value || lower.value === upper.value && (lower.exclusive || upper.exclusive);
|
|
7706
|
+
if (integer) {
|
|
7707
|
+
if (realEmpty) return false;
|
|
7708
|
+
const safeMargin = Number.MAX_SAFE_INTEGER - 1;
|
|
7709
|
+
if (Math.abs(lower.value) >= safeMargin || Math.abs(upper.value) >= safeMargin) {
|
|
7710
|
+
return false;
|
|
7711
|
+
}
|
|
7712
|
+
const minimum = lower.exclusive ? Math.floor(lower.value) + 1 : Math.ceil(lower.value);
|
|
7713
|
+
const maximum = upper.exclusive ? Math.ceil(upper.value) - 1 : Math.floor(upper.value);
|
|
7714
|
+
return minimum > maximum;
|
|
7715
|
+
}
|
|
7716
|
+
return realEmpty;
|
|
7661
7717
|
}
|
|
7662
7718
|
function isOutermostCall(node) {
|
|
7663
7719
|
const parent = node.parent;
|
|
@@ -7751,6 +7807,7 @@ var no_impossible_zod_literal_bounds_default = createRule({
|
|
|
7751
7807
|
const allowed = chain.kind === "number" ? NUMBER_METHODS : LENGTH_METHODS;
|
|
7752
7808
|
let lower = null;
|
|
7753
7809
|
let upper = null;
|
|
7810
|
+
let integer = false;
|
|
7754
7811
|
function addBound(method, semantic, value) {
|
|
7755
7812
|
const label = `${method}(${String(value)})`;
|
|
7756
7813
|
if (semantic !== null) {
|
|
@@ -7786,6 +7843,11 @@ var no_impossible_zod_literal_bounds_default = createRule({
|
|
|
7786
7843
|
}
|
|
7787
7844
|
for (const { method, node } of chain.calls) {
|
|
7788
7845
|
if (!allowed.has(method)) return null;
|
|
7846
|
+
if (method === "int") {
|
|
7847
|
+
if (node.arguments.length !== 0) return null;
|
|
7848
|
+
integer = true;
|
|
7849
|
+
continue;
|
|
7850
|
+
}
|
|
7789
7851
|
const semantic = zeroBoundSemantic(method);
|
|
7790
7852
|
const value = semantic?.value ?? finiteNumber(node.arguments[0]);
|
|
7791
7853
|
if (value === null) return null;
|
|
@@ -7795,7 +7857,7 @@ var no_impossible_zod_literal_bounds_default = createRule({
|
|
|
7795
7857
|
if (semantic !== null && node.arguments.length !== 0) return null;
|
|
7796
7858
|
addBound(method, semantic, value);
|
|
7797
7859
|
}
|
|
7798
|
-
return isEmpty(lower, upper) && lower !== null && upper !== null ? { lower, upper } : null;
|
|
7860
|
+
return isEmpty(lower, upper, integer) && lower !== null && upper !== null ? { lower, upper } : null;
|
|
7799
7861
|
}
|
|
7800
7862
|
return {
|
|
7801
7863
|
ImportDeclaration(node) {
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// package.json
|
|
2
2
|
var package_default = {
|
|
3
3
|
name: "@sarj/eslint-plugin",
|
|
4
|
-
version: "16.
|
|
4
|
+
version: "16.6.0",
|
|
5
5
|
packageManager: "npm@12.1.0",
|
|
6
6
|
description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
|
|
7
7
|
type: "module",
|
|
@@ -665,6 +665,9 @@ var REQUIRE_PORT_FOR_SERVICE_DOCUMENTATION = {
|
|
|
665
665
|
remediation: "Declare and implement an interface covering the service's public methods.",
|
|
666
666
|
category: "architecture",
|
|
667
667
|
aliases: ["require-interface-for-injected-service"],
|
|
668
|
+
limitations: [
|
|
669
|
+
"Retained injection requires an untouched parameter property or a single direct, unconditional constructor assignment. Conditional, repeated, replaced, updated, or deleted stores are unresolved rather than proof of retained injection."
|
|
670
|
+
],
|
|
668
671
|
examples: [
|
|
669
672
|
{ id: "declared-service-port", title: "Implement the service port", outcome: "no-match", files: [{ path: "src/service.ts", source: "interface Handler { handle(): void }\nexport class RequestHandler implements Handler { constructor(private readonly store: TaskStore) {} handle(): void { this.store.handle(); } }" }], focusPath: "src/service.ts", expectedCount: 0, public: true },
|
|
670
673
|
{ id: "concrete-injected-service", title: "Do not expose only the concrete service", outcome: "match", files: [{ path: "src/service.ts", source: "export class RequestHandler { constructor(private readonly store: TaskStore) {} handle(): void { this.store.handle(); } }" }], focusPath: "src/service.ts", expectedCount: 1, public: true }
|
|
@@ -791,6 +794,8 @@ var readConstructor = (ctor, declared, typeParameters) => {
|
|
|
791
794
|
const storedFieldsFrom = /* @__PURE__ */ new Map();
|
|
792
795
|
const storedMemberFieldsFrom = /* @__PURE__ */ new Map();
|
|
793
796
|
const constructedFieldNames = /* @__PURE__ */ new Set();
|
|
797
|
+
const writtenFieldNames = /* @__PURE__ */ new Set();
|
|
798
|
+
const unstableFieldNames = /* @__PURE__ */ new Set();
|
|
794
799
|
const localBindings2 = /* @__PURE__ */ new Set();
|
|
795
800
|
let constructedFields = 0;
|
|
796
801
|
if (body2 !== null && body2 !== void 0) scanStoredFields(body2);
|
|
@@ -799,14 +804,18 @@ var readConstructor = (ctor, declared, typeParameters) => {
|
|
|
799
804
|
function recordStoredField(current) {
|
|
800
805
|
if (current.type === AST_NODE_TYPES3.ArrowFunctionExpression || current.type === AST_NODE_TYPES3.FunctionExpression || current.type === AST_NODE_TYPES3.FunctionDeclaration || current.type === AST_NODE_TYPES3.ClassExpression || current.type === AST_NODE_TYPES3.ClassDeclaration) return;
|
|
801
806
|
if (current.type === AST_NODE_TYPES3.VariableDeclarator && current.id.type === AST_NODE_TYPES3.Identifier) localBindings2.add(current.id.name);
|
|
802
|
-
const expression = current.type === AST_NODE_TYPES3.
|
|
803
|
-
const storedField =
|
|
807
|
+
const expression = current.type === AST_NODE_TYPES3.AssignmentExpression ? current : null;
|
|
808
|
+
const storedField = writtenInstanceField(current);
|
|
809
|
+
if (storedField !== null) {
|
|
810
|
+
if (writtenFieldNames.has(storedField)) unstableFieldNames.add(storedField);
|
|
811
|
+
writtenFieldNames.add(storedField);
|
|
812
|
+
if (expression?.operator !== "=" || expression.parent.type !== AST_NODE_TYPES3.ExpressionStatement || expression.parent.parent !== body3) unstableFieldNames.add(storedField);
|
|
813
|
+
}
|
|
804
814
|
if (expression?.type !== AST_NODE_TYPES3.AssignmentExpression || !STORAGE_ASSIGNMENT_OPERATORS.has(expression.operator) || expression.left.type !== AST_NODE_TYPES3.MemberExpression || expression.left.object.type !== AST_NODE_TYPES3.ThisExpression || storedField === null) {
|
|
805
815
|
enqueueChildren(current);
|
|
806
816
|
return;
|
|
807
817
|
}
|
|
808
|
-
|
|
809
|
-
while (source.type === AST_NODE_TYPES3.TSNonNullExpression || source.type === AST_NODE_TYPES3.TSAsExpression || source.type === AST_NODE_TYPES3.TSSatisfiesExpression || source.type === AST_NODE_TYPES3.TSTypeAssertion) source = source.expression;
|
|
818
|
+
const source = unwrapExpression(expression.right);
|
|
810
819
|
if (source.type === AST_NODE_TYPES3.NewExpression) {
|
|
811
820
|
constructedFields += 1;
|
|
812
821
|
constructedFieldNames.add(storedField);
|
|
@@ -827,6 +836,7 @@ var readConstructor = (ctor, declared, typeParameters) => {
|
|
|
827
836
|
storedMemberFieldsFrom.set(source.object.name, members);
|
|
828
837
|
}
|
|
829
838
|
}
|
|
839
|
+
enqueueChildren(current);
|
|
830
840
|
}
|
|
831
841
|
function enqueueChildren(current) {
|
|
832
842
|
forEachOwnAstChild(current, (child) => {
|
|
@@ -844,16 +854,15 @@ var readConstructor = (ctor, declared, typeParameters) => {
|
|
|
844
854
|
for (const reference of parameterCollaborators(parameter, declared, storedMemberFieldsFrom)) {
|
|
845
855
|
if (localBindings2.has(reference.name)) continue;
|
|
846
856
|
let fields = reference.fields;
|
|
847
|
-
if (parameter.type === AST_NODE_TYPES3.TSParameterProperty)
|
|
848
|
-
|
|
849
|
-
|
|
857
|
+
if (parameter.type === AST_NODE_TYPES3.TSParameterProperty) {
|
|
858
|
+
if (writtenFieldNames.has(reference.name)) continue;
|
|
859
|
+
fields = [reference.name];
|
|
860
|
+
} else if (fields.length === 0) fields = [...storedFieldsFrom.get(reference.name) ?? []];
|
|
861
|
+
const injectedFields = fields.filter((field) => !constructedFieldNames.has(field) && !unstableFieldNames.has(field));
|
|
850
862
|
if (injectedFields.length === 0) continue;
|
|
851
863
|
if (CONFIGISH_TYPE_RE.test(reference.typeName)) continue;
|
|
852
864
|
if (CONFIGISH_NAME_RE.test(reference.name)) continue;
|
|
853
|
-
if (typeParameters.has(reference.typeName)) continue;
|
|
854
|
-
if (BUILTIN_CONTAINER_TYPE_RE.test(reference.typeName)) continue;
|
|
855
|
-
if (VALUE_TYPE_RE.test(reference.typeName)) continue;
|
|
856
|
-
if (declared().functionAliases.has(reference.typeName)) continue;
|
|
865
|
+
if (typeParameters.has(reference.typeName) || BUILTIN_CONTAINER_TYPE_RE.test(reference.typeName) || VALUE_TYPE_RE.test(reference.typeName) || declared().functionAliases.has(reference.typeName)) continue;
|
|
857
866
|
collaborators.push({ ...reference, fields: injectedFields });
|
|
858
867
|
}
|
|
859
868
|
}
|
|
@@ -964,6 +973,29 @@ var instanceField = (candidate2) => {
|
|
|
964
973
|
while (node.type === AST_NODE_TYPES3.ChainExpression || node.type === AST_NODE_TYPES3.TSAsExpression || node.type === AST_NODE_TYPES3.TSNonNullExpression || node.type === AST_NODE_TYPES3.TSSatisfiesExpression || node.type === AST_NODE_TYPES3.TSTypeAssertion) node = node.expression;
|
|
965
974
|
return node.type === AST_NODE_TYPES3.MemberExpression && node.object.type === AST_NODE_TYPES3.ThisExpression ? staticMemberName(node) : null;
|
|
966
975
|
};
|
|
976
|
+
function writtenInstanceField(node) {
|
|
977
|
+
if (node.type === AST_NODE_TYPES3.AssignmentExpression || node.type === AST_NODE_TYPES3.ForOfStatement || node.type === AST_NODE_TYPES3.ForInStatement) return instanceField(node.left);
|
|
978
|
+
if (node.type === AST_NODE_TYPES3.UpdateExpression) return instanceField(node.argument);
|
|
979
|
+
if (node.type === AST_NODE_TYPES3.UnaryExpression && node.operator === "delete") return instanceField(node.argument);
|
|
980
|
+
if (node.type === AST_NODE_TYPES3.MemberExpression && isDestructuredWriteTarget(node)) return instanceField(node);
|
|
981
|
+
return null;
|
|
982
|
+
}
|
|
983
|
+
function isDestructuredWriteTarget(node) {
|
|
984
|
+
const access = outerExpression(node);
|
|
985
|
+
let target = access;
|
|
986
|
+
for (; ; ) {
|
|
987
|
+
const parent = target.parent;
|
|
988
|
+
if (parent.type === AST_NODE_TYPES3.Property && parent.value === target && parent.parent.type === AST_NODE_TYPES3.ObjectPattern) {
|
|
989
|
+
target = parent.parent;
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
if (parent.type === AST_NODE_TYPES3.ArrayPattern || parent.type === AST_NODE_TYPES3.ObjectPattern || parent.type === AST_NODE_TYPES3.RestElement && parent.argument === target || parent.type === AST_NODE_TYPES3.AssignmentPattern && parent.left === target) {
|
|
993
|
+
target = parent;
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
return target !== access && (parent.type === AST_NODE_TYPES3.AssignmentExpression || parent.type === AST_NODE_TYPES3.ForOfStatement || parent.type === AST_NODE_TYPES3.ForInStatement) && parent.left === target;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
967
999
|
var behaviorallyInvokedFields = (body2) => {
|
|
968
1000
|
const invoked = /* @__PURE__ */ new Set();
|
|
969
1001
|
const visit = (current) => {
|
|
@@ -1280,13 +1312,23 @@ function createServiceOperationResolver(program) {
|
|
|
1280
1312
|
}
|
|
1281
1313
|
function serviceOperationBodies(node) {
|
|
1282
1314
|
const bodies = /* @__PURE__ */ new Map();
|
|
1315
|
+
const written = /* @__PURE__ */ new Set();
|
|
1316
|
+
const pending = [node.body];
|
|
1317
|
+
while (pending.length > 0) {
|
|
1318
|
+
const current = pending.pop();
|
|
1319
|
+
if (current === void 0) break;
|
|
1320
|
+
const name = writtenInstanceField(current);
|
|
1321
|
+
if (name !== null) written.add(name);
|
|
1322
|
+
forEachOwnAstChild(current, (child) => {
|
|
1323
|
+
pending.push(child);
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1283
1326
|
for (const member of node.body.body) {
|
|
1284
1327
|
if (member.type !== AST_NODE_TYPES3.MethodDefinition && member.type !== AST_NODE_TYPES3.PropertyDefinition) continue;
|
|
1285
|
-
if (member.static || member.decorators.length > 0) continue;
|
|
1286
|
-
if (member.type === AST_NODE_TYPES3.MethodDefinition && member.kind !== "method") continue;
|
|
1328
|
+
if (member.static || member.decorators.length > 0 || member.type === AST_NODE_TYPES3.MethodDefinition && member.kind !== "method") continue;
|
|
1287
1329
|
if (member.value?.type !== AST_NODE_TYPES3.FunctionExpression && member.value?.type !== AST_NODE_TYPES3.ArrowFunctionExpression) continue;
|
|
1288
1330
|
const name = declaredMemberName(member);
|
|
1289
|
-
if (name !== null && member.value.body !== null) bodies.set(name, member.value.body);
|
|
1331
|
+
if (name !== null && !written.has(name) && member.value.body !== null) bodies.set(name, member.value.body);
|
|
1290
1332
|
}
|
|
1291
1333
|
return bodies;
|
|
1292
1334
|
}
|
|
@@ -1399,6 +1441,7 @@ var REQUIRE_EXPLICIT_SERVICE_CONTRACT_DOCUMENTATION = {
|
|
|
1399
1441
|
"The rule checks service, store, and provider class declarations whose constructor stores a typed behavioral collaborator and whose public method reaches that collaborator.",
|
|
1400
1442
|
"Existing local interfaces, type aliases, and inherited contracts can cover the operations; the compiler owns signature compatibility.",
|
|
1401
1443
|
"Imported or dynamic base classes with unavailable declarations and decorated classes or operations are unresolved and do not produce a finding.",
|
|
1444
|
+
"Constructor retention requires an untouched parameter property or one direct, unconditional assignment. Written or replaced operation members do not prove their declared bodies execute; external aliases, mutators, and callbacks are not resolved.",
|
|
1402
1445
|
"Generated files and JavaScript without implements syntax are excluded."
|
|
1403
1446
|
],
|
|
1404
1447
|
examples: [
|
|
@@ -7549,7 +7592,8 @@ var NO_IMPOSSIBLE_ZOD_LITERAL_BOUNDS_DOCUMENTATION = {
|
|
|
7549
7592
|
remediation: "Choose compatible lower and upper bounds, or remove the constraint that does not express the intended domain.",
|
|
7550
7593
|
category: "correctness",
|
|
7551
7594
|
limitations: [
|
|
7552
|
-
"Only finite numeric literals in a single number, string, or array schema chain are compared.",
|
|
7595
|
+
"Only finite numeric literals in a single number, string, or array schema chain are compared; explicit int() chains also require an admitted integer.",
|
|
7596
|
+
"Integer-domain comparisons abstain near the JavaScript safe-integer boundary.",
|
|
7553
7597
|
"Chains with dynamic bounds, non-bound validators, transforms, pipes, or preprocessors are skipped.",
|
|
7554
7598
|
"Test and generated files are excluded."
|
|
7555
7599
|
],
|
|
@@ -7588,6 +7632,7 @@ var KINDS = /* @__PURE__ */ new Set(["array", "number", "string"]);
|
|
|
7588
7632
|
var NUMBER_METHODS = /* @__PURE__ */ new Set([
|
|
7589
7633
|
"gt",
|
|
7590
7634
|
"gte",
|
|
7635
|
+
"int",
|
|
7591
7636
|
"lt",
|
|
7592
7637
|
"lte",
|
|
7593
7638
|
"max",
|
|
@@ -7629,9 +7674,20 @@ function strongerUpper(current, candidate2) {
|
|
|
7629
7674
|
}
|
|
7630
7675
|
return current;
|
|
7631
7676
|
}
|
|
7632
|
-
function isEmpty(lower, upper) {
|
|
7677
|
+
function isEmpty(lower, upper, integer) {
|
|
7633
7678
|
if (lower === null || upper === null) return false;
|
|
7634
|
-
|
|
7679
|
+
const realEmpty = lower.value > upper.value || lower.value === upper.value && (lower.exclusive || upper.exclusive);
|
|
7680
|
+
if (integer) {
|
|
7681
|
+
if (realEmpty) return false;
|
|
7682
|
+
const safeMargin = Number.MAX_SAFE_INTEGER - 1;
|
|
7683
|
+
if (Math.abs(lower.value) >= safeMargin || Math.abs(upper.value) >= safeMargin) {
|
|
7684
|
+
return false;
|
|
7685
|
+
}
|
|
7686
|
+
const minimum = lower.exclusive ? Math.floor(lower.value) + 1 : Math.ceil(lower.value);
|
|
7687
|
+
const maximum = upper.exclusive ? Math.ceil(upper.value) - 1 : Math.floor(upper.value);
|
|
7688
|
+
return minimum > maximum;
|
|
7689
|
+
}
|
|
7690
|
+
return realEmpty;
|
|
7635
7691
|
}
|
|
7636
7692
|
function isOutermostCall(node) {
|
|
7637
7693
|
const parent = node.parent;
|
|
@@ -7725,6 +7781,7 @@ var no_impossible_zod_literal_bounds_default = createRule({
|
|
|
7725
7781
|
const allowed = chain.kind === "number" ? NUMBER_METHODS : LENGTH_METHODS;
|
|
7726
7782
|
let lower = null;
|
|
7727
7783
|
let upper = null;
|
|
7784
|
+
let integer = false;
|
|
7728
7785
|
function addBound(method, semantic, value) {
|
|
7729
7786
|
const label = `${method}(${String(value)})`;
|
|
7730
7787
|
if (semantic !== null) {
|
|
@@ -7760,6 +7817,11 @@ var no_impossible_zod_literal_bounds_default = createRule({
|
|
|
7760
7817
|
}
|
|
7761
7818
|
for (const { method, node } of chain.calls) {
|
|
7762
7819
|
if (!allowed.has(method)) return null;
|
|
7820
|
+
if (method === "int") {
|
|
7821
|
+
if (node.arguments.length !== 0) return null;
|
|
7822
|
+
integer = true;
|
|
7823
|
+
continue;
|
|
7824
|
+
}
|
|
7763
7825
|
const semantic = zeroBoundSemantic(method);
|
|
7764
7826
|
const value = semantic?.value ?? finiteNumber(node.arguments[0]);
|
|
7765
7827
|
if (value === null) return null;
|
|
@@ -7769,7 +7831,7 @@ var no_impossible_zod_literal_bounds_default = createRule({
|
|
|
7769
7831
|
if (semantic !== null && node.arguments.length !== 0) return null;
|
|
7770
7832
|
addBound(method, semantic, value);
|
|
7771
7833
|
}
|
|
7772
|
-
return isEmpty(lower, upper) && lower !== null && upper !== null ? { lower, upper } : null;
|
|
7834
|
+
return isEmpty(lower, upper, integer) && lower !== null && upper !== null ? { lower, upper } : null;
|
|
7773
7835
|
}
|
|
7774
7836
|
return {
|
|
7775
7837
|
ImportDeclaration(node) {
|