@violetflux/eslint-plugin-kerros 0.3.1 → 0.3.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 +54 -24
- package/dist/index.mjs +54 -24
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -718,7 +718,7 @@ const objectEnumerationMethods = /* @__PURE__ */ new Set([
|
|
|
718
718
|
"keys",
|
|
719
719
|
"values"
|
|
720
720
|
]);
|
|
721
|
-
/** Read the
|
|
721
|
+
/** Read the tracked value argument from a broad enumeration or serialization call. */
|
|
722
722
|
function getBroadArgument(node) {
|
|
723
723
|
const [argument] = node.arguments;
|
|
724
724
|
if (!argument || argument.type === "SpreadElement") return false;
|
|
@@ -733,48 +733,72 @@ const noBroadStoreAccess = createRule({
|
|
|
733
733
|
name: "no-broad-store-access",
|
|
734
734
|
meta: {
|
|
735
735
|
type: "problem",
|
|
736
|
-
docs: { description: "Prevent
|
|
737
|
-
schema: [
|
|
738
|
-
|
|
736
|
+
docs: { description: "Prevent broad enumeration and serialization of complete Store snapshots." },
|
|
737
|
+
schema: [{
|
|
738
|
+
type: "object",
|
|
739
|
+
properties: { includeObjectFields: { type: "boolean" } },
|
|
740
|
+
additionalProperties: false
|
|
741
|
+
}],
|
|
742
|
+
messages: {
|
|
743
|
+
broadAccess: "Do not enumerate, serialize, or spread a complete selector-free Store snapshot.",
|
|
744
|
+
broadObjectField: "Do not enumerate, serialize, or spread an object field from a selector-free Store snapshot."
|
|
745
|
+
}
|
|
739
746
|
},
|
|
740
|
-
defaultOptions: [],
|
|
741
|
-
create(context) {
|
|
747
|
+
defaultOptions: [{ includeObjectFields: false }],
|
|
748
|
+
create(context, [options]) {
|
|
742
749
|
const { getIdentifierSymbol, getType, isStoreHookCall } = createKerrosTypeTools(context);
|
|
743
750
|
const origins = createReferenceOriginTracker(context.sourceCode.ast);
|
|
744
|
-
/**
|
|
745
|
-
const
|
|
751
|
+
/** Classify whether an expression is a complete Store snapshot or one object field from it. */
|
|
752
|
+
const readStoreOrigin = (input, seen = /* @__PURE__ */ new Set()) => {
|
|
746
753
|
const node = unwrapExpression(input);
|
|
747
754
|
if (node.type === "CallExpression") {
|
|
748
755
|
const selector = node.arguments[0];
|
|
749
|
-
return (node.arguments.length === 0 || node.arguments.length === 1 && selector?.type !== "SpreadElement" && (getType(selector).flags & typescript.default.TypeFlags.Undefined) !== 0) && isStoreHookCall(node);
|
|
756
|
+
return (node.arguments.length === 0 || node.arguments.length === 1 && selector?.type !== "SpreadElement" && (getType(selector).flags & typescript.default.TypeFlags.Undefined) !== 0) && isStoreHookCall(node) ? "snapshot" : void 0;
|
|
750
757
|
}
|
|
751
|
-
if (node.type === "AssignmentExpression") return
|
|
752
|
-
if (node.type === "MemberExpression") return
|
|
753
|
-
if (node.type !== "Identifier") return
|
|
758
|
+
if (node.type === "AssignmentExpression") return readStoreOrigin(node.right, seen);
|
|
759
|
+
if (node.type === "MemberExpression") return readStoreOrigin(node.object, seen) ? "objectField" : void 0;
|
|
760
|
+
if (node.type !== "Identifier") return;
|
|
754
761
|
const symbol = getIdentifierSymbol(node);
|
|
755
|
-
if (!symbol || seen.has(symbol)) return
|
|
762
|
+
if (!symbol || seen.has(symbol)) return;
|
|
756
763
|
seen.add(symbol);
|
|
757
|
-
|
|
764
|
+
let result;
|
|
765
|
+
for (const source of origins.resolve(symbol, node)) {
|
|
766
|
+
const origin = readStoreOrigin(source.expression, seen);
|
|
767
|
+
if (!origin) continue;
|
|
768
|
+
if (!source.objectField && origin === "snapshot") {
|
|
769
|
+
result = "snapshot";
|
|
770
|
+
break;
|
|
771
|
+
}
|
|
772
|
+
result = "objectField";
|
|
773
|
+
}
|
|
758
774
|
seen.delete(symbol);
|
|
759
|
-
return
|
|
775
|
+
return result;
|
|
760
776
|
};
|
|
761
777
|
/** Report one operation that subscribes to every enumerable field. */
|
|
762
778
|
const reportBroadAccess = (expression) => {
|
|
763
|
-
|
|
779
|
+
const origin = readStoreOrigin(expression);
|
|
780
|
+
if (origin === "snapshot") context.report({
|
|
764
781
|
node: expression,
|
|
765
782
|
messageId: "broadAccess"
|
|
766
783
|
});
|
|
784
|
+
else if (origin === "objectField" && options.includeObjectFields) context.report({
|
|
785
|
+
node: expression,
|
|
786
|
+
messageId: "broadObjectField"
|
|
787
|
+
});
|
|
767
788
|
};
|
|
768
789
|
/** Track object-valued bindings destructured from a snapshot. */
|
|
769
|
-
const recordObjectBindings = (pattern, source, write) => {
|
|
790
|
+
const recordObjectBindings = (pattern, source, write, objectField = false) => {
|
|
770
791
|
if (pattern.type === "Identifier") {
|
|
771
792
|
if ((getType(pattern).flags & typescript.default.TypeFlags.Object) === 0) return;
|
|
772
793
|
const symbol = getIdentifierSymbol(pattern);
|
|
773
|
-
if (symbol) origins.record(symbol,
|
|
794
|
+
if (symbol) origins.record(symbol, {
|
|
795
|
+
expression: source,
|
|
796
|
+
objectField
|
|
797
|
+
}, write);
|
|
774
798
|
return;
|
|
775
799
|
}
|
|
776
800
|
if (pattern.type === "AssignmentPattern") {
|
|
777
|
-
recordObjectBindings(pattern.left, source, write);
|
|
801
|
+
recordObjectBindings(pattern.left, source, write, objectField);
|
|
778
802
|
return;
|
|
779
803
|
}
|
|
780
804
|
if (pattern.type === "RestElement") return;
|
|
@@ -782,8 +806,8 @@ const noBroadStoreAccess = createRule({
|
|
|
782
806
|
const entries = pattern.type === "ObjectPattern" ? pattern.properties : pattern.elements;
|
|
783
807
|
for (const entry of entries) {
|
|
784
808
|
if (!entry) continue;
|
|
785
|
-
if (entry.type === "Property") recordObjectBindings(entry.value, source, write);
|
|
786
|
-
else recordObjectBindings(entry, source, write);
|
|
809
|
+
if (entry.type === "Property") recordObjectBindings(entry.value, source, write, true);
|
|
810
|
+
else recordObjectBindings(entry, source, write, true);
|
|
787
811
|
}
|
|
788
812
|
};
|
|
789
813
|
return {
|
|
@@ -798,7 +822,10 @@ const noBroadStoreAccess = createRule({
|
|
|
798
822
|
if (!node.init) return;
|
|
799
823
|
if (node.id.type === "Identifier") {
|
|
800
824
|
const symbol = getIdentifierSymbol(node.id);
|
|
801
|
-
if (symbol) origins.record(symbol,
|
|
825
|
+
if (symbol) origins.record(symbol, {
|
|
826
|
+
expression: node.init,
|
|
827
|
+
objectField: false
|
|
828
|
+
}, node);
|
|
802
829
|
return;
|
|
803
830
|
}
|
|
804
831
|
if (node.id.type === "ObjectPattern" && node.id.properties.some((property) => property.type === "RestElement")) reportBroadAccess(node.init);
|
|
@@ -807,7 +834,10 @@ const noBroadStoreAccess = createRule({
|
|
|
807
834
|
AssignmentExpression(node) {
|
|
808
835
|
if (node.left.type !== "Identifier") return;
|
|
809
836
|
const symbol = getIdentifierSymbol(node.left);
|
|
810
|
-
if (symbol) origins.record(symbol,
|
|
837
|
+
if (symbol) origins.record(symbol, {
|
|
838
|
+
expression: node.right,
|
|
839
|
+
objectField: false
|
|
840
|
+
}, node);
|
|
811
841
|
}
|
|
812
842
|
};
|
|
813
843
|
}
|
|
@@ -2471,7 +2501,7 @@ const rules = {
|
|
|
2471
2501
|
const plugin = {
|
|
2472
2502
|
meta: {
|
|
2473
2503
|
name: "@violetflux/eslint-plugin-kerros",
|
|
2474
|
-
version: "0.3.
|
|
2504
|
+
version: "0.3.2"
|
|
2475
2505
|
},
|
|
2476
2506
|
rules
|
|
2477
2507
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -690,7 +690,7 @@ const objectEnumerationMethods = /* @__PURE__ */ new Set([
|
|
|
690
690
|
"keys",
|
|
691
691
|
"values"
|
|
692
692
|
]);
|
|
693
|
-
/** Read the
|
|
693
|
+
/** Read the tracked value argument from a broad enumeration or serialization call. */
|
|
694
694
|
function getBroadArgument(node) {
|
|
695
695
|
const [argument] = node.arguments;
|
|
696
696
|
if (!argument || argument.type === "SpreadElement") return false;
|
|
@@ -705,48 +705,72 @@ const noBroadStoreAccess = createRule({
|
|
|
705
705
|
name: "no-broad-store-access",
|
|
706
706
|
meta: {
|
|
707
707
|
type: "problem",
|
|
708
|
-
docs: { description: "Prevent
|
|
709
|
-
schema: [
|
|
710
|
-
|
|
708
|
+
docs: { description: "Prevent broad enumeration and serialization of complete Store snapshots." },
|
|
709
|
+
schema: [{
|
|
710
|
+
type: "object",
|
|
711
|
+
properties: { includeObjectFields: { type: "boolean" } },
|
|
712
|
+
additionalProperties: false
|
|
713
|
+
}],
|
|
714
|
+
messages: {
|
|
715
|
+
broadAccess: "Do not enumerate, serialize, or spread a complete selector-free Store snapshot.",
|
|
716
|
+
broadObjectField: "Do not enumerate, serialize, or spread an object field from a selector-free Store snapshot."
|
|
717
|
+
}
|
|
711
718
|
},
|
|
712
|
-
defaultOptions: [],
|
|
713
|
-
create(context) {
|
|
719
|
+
defaultOptions: [{ includeObjectFields: false }],
|
|
720
|
+
create(context, [options]) {
|
|
714
721
|
const { getIdentifierSymbol, getType, isStoreHookCall } = createKerrosTypeTools(context);
|
|
715
722
|
const origins = createReferenceOriginTracker(context.sourceCode.ast);
|
|
716
|
-
/**
|
|
717
|
-
const
|
|
723
|
+
/** Classify whether an expression is a complete Store snapshot or one object field from it. */
|
|
724
|
+
const readStoreOrigin = (input, seen = /* @__PURE__ */ new Set()) => {
|
|
718
725
|
const node = unwrapExpression(input);
|
|
719
726
|
if (node.type === "CallExpression") {
|
|
720
727
|
const selector = node.arguments[0];
|
|
721
|
-
return (node.arguments.length === 0 || node.arguments.length === 1 && selector?.type !== "SpreadElement" && (getType(selector).flags & ts.TypeFlags.Undefined) !== 0) && isStoreHookCall(node);
|
|
728
|
+
return (node.arguments.length === 0 || node.arguments.length === 1 && selector?.type !== "SpreadElement" && (getType(selector).flags & ts.TypeFlags.Undefined) !== 0) && isStoreHookCall(node) ? "snapshot" : void 0;
|
|
722
729
|
}
|
|
723
|
-
if (node.type === "AssignmentExpression") return
|
|
724
|
-
if (node.type === "MemberExpression") return
|
|
725
|
-
if (node.type !== "Identifier") return
|
|
730
|
+
if (node.type === "AssignmentExpression") return readStoreOrigin(node.right, seen);
|
|
731
|
+
if (node.type === "MemberExpression") return readStoreOrigin(node.object, seen) ? "objectField" : void 0;
|
|
732
|
+
if (node.type !== "Identifier") return;
|
|
726
733
|
const symbol = getIdentifierSymbol(node);
|
|
727
|
-
if (!symbol || seen.has(symbol)) return
|
|
734
|
+
if (!symbol || seen.has(symbol)) return;
|
|
728
735
|
seen.add(symbol);
|
|
729
|
-
|
|
736
|
+
let result;
|
|
737
|
+
for (const source of origins.resolve(symbol, node)) {
|
|
738
|
+
const origin = readStoreOrigin(source.expression, seen);
|
|
739
|
+
if (!origin) continue;
|
|
740
|
+
if (!source.objectField && origin === "snapshot") {
|
|
741
|
+
result = "snapshot";
|
|
742
|
+
break;
|
|
743
|
+
}
|
|
744
|
+
result = "objectField";
|
|
745
|
+
}
|
|
730
746
|
seen.delete(symbol);
|
|
731
|
-
return
|
|
747
|
+
return result;
|
|
732
748
|
};
|
|
733
749
|
/** Report one operation that subscribes to every enumerable field. */
|
|
734
750
|
const reportBroadAccess = (expression) => {
|
|
735
|
-
|
|
751
|
+
const origin = readStoreOrigin(expression);
|
|
752
|
+
if (origin === "snapshot") context.report({
|
|
736
753
|
node: expression,
|
|
737
754
|
messageId: "broadAccess"
|
|
738
755
|
});
|
|
756
|
+
else if (origin === "objectField" && options.includeObjectFields) context.report({
|
|
757
|
+
node: expression,
|
|
758
|
+
messageId: "broadObjectField"
|
|
759
|
+
});
|
|
739
760
|
};
|
|
740
761
|
/** Track object-valued bindings destructured from a snapshot. */
|
|
741
|
-
const recordObjectBindings = (pattern, source, write) => {
|
|
762
|
+
const recordObjectBindings = (pattern, source, write, objectField = false) => {
|
|
742
763
|
if (pattern.type === "Identifier") {
|
|
743
764
|
if ((getType(pattern).flags & ts.TypeFlags.Object) === 0) return;
|
|
744
765
|
const symbol = getIdentifierSymbol(pattern);
|
|
745
|
-
if (symbol) origins.record(symbol,
|
|
766
|
+
if (symbol) origins.record(symbol, {
|
|
767
|
+
expression: source,
|
|
768
|
+
objectField
|
|
769
|
+
}, write);
|
|
746
770
|
return;
|
|
747
771
|
}
|
|
748
772
|
if (pattern.type === "AssignmentPattern") {
|
|
749
|
-
recordObjectBindings(pattern.left, source, write);
|
|
773
|
+
recordObjectBindings(pattern.left, source, write, objectField);
|
|
750
774
|
return;
|
|
751
775
|
}
|
|
752
776
|
if (pattern.type === "RestElement") return;
|
|
@@ -754,8 +778,8 @@ const noBroadStoreAccess = createRule({
|
|
|
754
778
|
const entries = pattern.type === "ObjectPattern" ? pattern.properties : pattern.elements;
|
|
755
779
|
for (const entry of entries) {
|
|
756
780
|
if (!entry) continue;
|
|
757
|
-
if (entry.type === "Property") recordObjectBindings(entry.value, source, write);
|
|
758
|
-
else recordObjectBindings(entry, source, write);
|
|
781
|
+
if (entry.type === "Property") recordObjectBindings(entry.value, source, write, true);
|
|
782
|
+
else recordObjectBindings(entry, source, write, true);
|
|
759
783
|
}
|
|
760
784
|
};
|
|
761
785
|
return {
|
|
@@ -770,7 +794,10 @@ const noBroadStoreAccess = createRule({
|
|
|
770
794
|
if (!node.init) return;
|
|
771
795
|
if (node.id.type === "Identifier") {
|
|
772
796
|
const symbol = getIdentifierSymbol(node.id);
|
|
773
|
-
if (symbol) origins.record(symbol,
|
|
797
|
+
if (symbol) origins.record(symbol, {
|
|
798
|
+
expression: node.init,
|
|
799
|
+
objectField: false
|
|
800
|
+
}, node);
|
|
774
801
|
return;
|
|
775
802
|
}
|
|
776
803
|
if (node.id.type === "ObjectPattern" && node.id.properties.some((property) => property.type === "RestElement")) reportBroadAccess(node.init);
|
|
@@ -779,7 +806,10 @@ const noBroadStoreAccess = createRule({
|
|
|
779
806
|
AssignmentExpression(node) {
|
|
780
807
|
if (node.left.type !== "Identifier") return;
|
|
781
808
|
const symbol = getIdentifierSymbol(node.left);
|
|
782
|
-
if (symbol) origins.record(symbol,
|
|
809
|
+
if (symbol) origins.record(symbol, {
|
|
810
|
+
expression: node.right,
|
|
811
|
+
objectField: false
|
|
812
|
+
}, node);
|
|
783
813
|
}
|
|
784
814
|
};
|
|
785
815
|
}
|
|
@@ -2443,7 +2473,7 @@ const rules = {
|
|
|
2443
2473
|
const plugin = {
|
|
2444
2474
|
meta: {
|
|
2445
2475
|
name: "@violetflux/eslint-plugin-kerros",
|
|
2446
|
-
version: "0.3.
|
|
2476
|
+
version: "0.3.2"
|
|
2447
2477
|
},
|
|
2448
2478
|
rules
|
|
2449
2479
|
};
|