@sarj/eslint-plugin 16.5.1 → 16.5.3
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 +61 -17
- package/dist/index.js +61 -17
- 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.5.
|
|
55
|
+
version: "16.5.3",
|
|
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: [
|
|
@@ -12751,11 +12794,12 @@ var import_utils73 = require("@typescript-eslint/utils");
|
|
|
12751
12794
|
var NO_TAUTOLOGICAL_EXPECT_DOCUMENTATION = {
|
|
12752
12795
|
summary: "Disallow supported assertions known to pass from literals or immutable scalar setup.",
|
|
12753
12796
|
rationale: "An assertion determined entirely by literals does not observe the code under test and can keep passing after that code is removed.",
|
|
12754
|
-
remediation: "Assert on a value produced by the behavior under test, or remove the assertion.",
|
|
12797
|
+
remediation: "Assert on a value produced by the behavior under test against an independently specified expectation, or remove only the redundant assertion while preserving coverage.",
|
|
12755
12798
|
category: "testing",
|
|
12756
12799
|
limitations: [
|
|
12757
12800
|
"Only direct supported `expect` matcher calls in recognized test files are inspected. Local expect bindings, regular expressions, and unsupported coercions are excluded; failing constant assertions are not tautologies.",
|
|
12758
|
-
"Scalar const bindings and aliases resolve through lexical scope; mutable bindings, objects, arrays, destructuring, runtime initializers and forward references are excluded."
|
|
12801
|
+
"Scalar const bindings and aliases resolve through lexical scope; mutable bindings, objects, arrays, destructuring, runtime initializers and forward references are excluded.",
|
|
12802
|
+
"Expected-side runtime dependence, including a complete inline snapshot computed from its actual value, remains manual review; independent static interpolation and intentional partial masking are valid."
|
|
12759
12803
|
],
|
|
12760
12804
|
examples: [
|
|
12761
12805
|
{
|
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.5.
|
|
4
|
+
version: "16.5.3",
|
|
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: [
|
|
@@ -12735,11 +12778,12 @@ import { AST_NODE_TYPES as AST_NODE_TYPES60, ASTUtils as ASTUtils35 } from "@typ
|
|
|
12735
12778
|
var NO_TAUTOLOGICAL_EXPECT_DOCUMENTATION = {
|
|
12736
12779
|
summary: "Disallow supported assertions known to pass from literals or immutable scalar setup.",
|
|
12737
12780
|
rationale: "An assertion determined entirely by literals does not observe the code under test and can keep passing after that code is removed.",
|
|
12738
|
-
remediation: "Assert on a value produced by the behavior under test, or remove the assertion.",
|
|
12781
|
+
remediation: "Assert on a value produced by the behavior under test against an independently specified expectation, or remove only the redundant assertion while preserving coverage.",
|
|
12739
12782
|
category: "testing",
|
|
12740
12783
|
limitations: [
|
|
12741
12784
|
"Only direct supported `expect` matcher calls in recognized test files are inspected. Local expect bindings, regular expressions, and unsupported coercions are excluded; failing constant assertions are not tautologies.",
|
|
12742
|
-
"Scalar const bindings and aliases resolve through lexical scope; mutable bindings, objects, arrays, destructuring, runtime initializers and forward references are excluded."
|
|
12785
|
+
"Scalar const bindings and aliases resolve through lexical scope; mutable bindings, objects, arrays, destructuring, runtime initializers and forward references are excluded.",
|
|
12786
|
+
"Expected-side runtime dependence, including a complete inline snapshot computed from its actual value, remains manual review; independent static interpolation and intentional partial masking are valid."
|
|
12743
12787
|
],
|
|
12744
12788
|
examples: [
|
|
12745
12789
|
{
|