@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 CHANGED
@@ -718,7 +718,7 @@ const objectEnumerationMethods = /* @__PURE__ */ new Set([
718
718
  "keys",
719
719
  "values"
720
720
  ]);
721
- /** Read the snapshot argument from a complete enumeration or serialization call. */
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 complete Store enumeration and serialization." },
737
- schema: [],
738
- messages: { broadAccess: "Do not enumerate or spread the complete Store snapshot." }
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
- /** Test whether an expression originates from a selector-free Store snapshot. */
745
- const isSnapshotDerived = (input, seen = /* @__PURE__ */ new Set()) => {
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 isSnapshotDerived(node.right, seen);
752
- if (node.type === "MemberExpression") return isSnapshotDerived(node.object, seen);
753
- if (node.type !== "Identifier") return false;
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 false;
762
+ if (!symbol || seen.has(symbol)) return;
756
763
  seen.add(symbol);
757
- const derived = origins.resolve(symbol, node).some((source) => isSnapshotDerived(source, seen));
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 derived;
775
+ return result;
760
776
  };
761
777
  /** Report one operation that subscribes to every enumerable field. */
762
778
  const reportBroadAccess = (expression) => {
763
- if (isSnapshotDerived(expression)) context.report({
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, source, write);
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, node.init, node);
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, node.right, node);
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.1"
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 snapshot argument from a complete enumeration or serialization call. */
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 complete Store enumeration and serialization." },
709
- schema: [],
710
- messages: { broadAccess: "Do not enumerate or spread the complete Store snapshot." }
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
- /** Test whether an expression originates from a selector-free Store snapshot. */
717
- const isSnapshotDerived = (input, seen = /* @__PURE__ */ new Set()) => {
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 isSnapshotDerived(node.right, seen);
724
- if (node.type === "MemberExpression") return isSnapshotDerived(node.object, seen);
725
- if (node.type !== "Identifier") return false;
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 false;
734
+ if (!symbol || seen.has(symbol)) return;
728
735
  seen.add(symbol);
729
- const derived = origins.resolve(symbol, node).some((source) => isSnapshotDerived(source, seen));
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 derived;
747
+ return result;
732
748
  };
733
749
  /** Report one operation that subscribes to every enumerable field. */
734
750
  const reportBroadAccess = (expression) => {
735
- if (isSnapshotDerived(expression)) context.report({
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, source, write);
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, node.init, node);
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, node.right, node);
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.1"
2476
+ version: "0.3.2"
2447
2477
  },
2448
2478
  rules
2449
2479
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@violetflux/eslint-plugin-kerros",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Type-aware ESLint rules for Kerros stores.",
5
5
  "repository": {
6
6
  "type": "git",