@violetflux/eslint-plugin-kerros 0.3.1 → 0.3.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 +96 -26
- package/dist/index.mjs +96 -26
- 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,96 @@ 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: {
|
|
740
|
+
includeObjectFields: { type: "boolean" },
|
|
741
|
+
includeStoreModels: { type: "boolean" }
|
|
742
|
+
},
|
|
743
|
+
additionalProperties: false
|
|
744
|
+
}],
|
|
745
|
+
messages: {
|
|
746
|
+
broadAccess: "Do not enumerate, serialize, or spread a complete selector-free Store snapshot.",
|
|
747
|
+
broadObjectField: "Do not enumerate, serialize, or spread an object field from a selector-free Store snapshot."
|
|
748
|
+
}
|
|
739
749
|
},
|
|
740
|
-
defaultOptions: [
|
|
741
|
-
|
|
742
|
-
|
|
750
|
+
defaultOptions: [{
|
|
751
|
+
includeObjectFields: false,
|
|
752
|
+
includeStoreModels: false
|
|
753
|
+
}],
|
|
754
|
+
create(context, [options]) {
|
|
755
|
+
const { getFactoryKind, getIdentifierSymbol, getType, isStoreHookCall } = createKerrosTypeTools(context);
|
|
743
756
|
const origins = createReferenceOriginTracker(context.sourceCode.ast);
|
|
744
|
-
|
|
745
|
-
const
|
|
757
|
+
const storeModels = /* @__PURE__ */ new Set();
|
|
758
|
+
const pendingReports = [];
|
|
759
|
+
/** Test whether an expression belongs to a function registered as a createStore model. */
|
|
760
|
+
const isInsideStoreModel = (expression) => {
|
|
761
|
+
let node = expression.parent;
|
|
762
|
+
while (node) {
|
|
763
|
+
if (node.type === "FunctionDeclaration" && node.id) {
|
|
764
|
+
const symbol = getIdentifierSymbol(node.id);
|
|
765
|
+
return symbol ? storeModels.has(symbol) : false;
|
|
766
|
+
}
|
|
767
|
+
if ((node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression") && node.parent.type === "VariableDeclarator" && node.parent.id.type === "Identifier") {
|
|
768
|
+
const symbol = getIdentifierSymbol(node.parent.id);
|
|
769
|
+
return symbol ? storeModels.has(symbol) : false;
|
|
770
|
+
}
|
|
771
|
+
node = node.parent;
|
|
772
|
+
}
|
|
773
|
+
return false;
|
|
774
|
+
};
|
|
775
|
+
/** Classify whether an expression is a complete Store snapshot or one object field from it. */
|
|
776
|
+
const readStoreOrigin = (input, seen = /* @__PURE__ */ new Set()) => {
|
|
746
777
|
const node = unwrapExpression(input);
|
|
747
778
|
if (node.type === "CallExpression") {
|
|
748
779
|
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);
|
|
780
|
+
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
781
|
}
|
|
751
|
-
if (node.type === "AssignmentExpression") return
|
|
752
|
-
if (node.type === "MemberExpression") return
|
|
753
|
-
if (node.type !== "Identifier") return
|
|
782
|
+
if (node.type === "AssignmentExpression") return readStoreOrigin(node.right, seen);
|
|
783
|
+
if (node.type === "MemberExpression") return readStoreOrigin(node.object, seen) ? "objectField" : void 0;
|
|
784
|
+
if (node.type !== "Identifier") return;
|
|
754
785
|
const symbol = getIdentifierSymbol(node);
|
|
755
|
-
if (!symbol || seen.has(symbol)) return
|
|
786
|
+
if (!symbol || seen.has(symbol)) return;
|
|
756
787
|
seen.add(symbol);
|
|
757
|
-
|
|
788
|
+
let result;
|
|
789
|
+
for (const source of origins.resolve(symbol, node)) {
|
|
790
|
+
const origin = readStoreOrigin(source.expression, seen);
|
|
791
|
+
if (!origin) continue;
|
|
792
|
+
if (!source.objectField && origin === "snapshot") {
|
|
793
|
+
result = "snapshot";
|
|
794
|
+
break;
|
|
795
|
+
}
|
|
796
|
+
result = "objectField";
|
|
797
|
+
}
|
|
758
798
|
seen.delete(symbol);
|
|
759
|
-
return
|
|
799
|
+
return result;
|
|
760
800
|
};
|
|
761
801
|
/** Report one operation that subscribes to every enumerable field. */
|
|
762
802
|
const reportBroadAccess = (expression) => {
|
|
763
|
-
|
|
764
|
-
|
|
803
|
+
const origin = readStoreOrigin(expression);
|
|
804
|
+
if (origin === "snapshot") pendingReports.push({
|
|
805
|
+
expression,
|
|
765
806
|
messageId: "broadAccess"
|
|
766
807
|
});
|
|
808
|
+
else if (origin === "objectField" && options.includeObjectFields) pendingReports.push({
|
|
809
|
+
expression,
|
|
810
|
+
messageId: "broadObjectField"
|
|
811
|
+
});
|
|
767
812
|
};
|
|
768
813
|
/** Track object-valued bindings destructured from a snapshot. */
|
|
769
|
-
const recordObjectBindings = (pattern, source, write) => {
|
|
814
|
+
const recordObjectBindings = (pattern, source, write, objectField = false) => {
|
|
770
815
|
if (pattern.type === "Identifier") {
|
|
771
816
|
if ((getType(pattern).flags & typescript.default.TypeFlags.Object) === 0) return;
|
|
772
817
|
const symbol = getIdentifierSymbol(pattern);
|
|
773
|
-
if (symbol) origins.record(symbol,
|
|
818
|
+
if (symbol) origins.record(symbol, {
|
|
819
|
+
expression: source,
|
|
820
|
+
objectField
|
|
821
|
+
}, write);
|
|
774
822
|
return;
|
|
775
823
|
}
|
|
776
824
|
if (pattern.type === "AssignmentPattern") {
|
|
777
|
-
recordObjectBindings(pattern.left, source, write);
|
|
825
|
+
recordObjectBindings(pattern.left, source, write, objectField);
|
|
778
826
|
return;
|
|
779
827
|
}
|
|
780
828
|
if (pattern.type === "RestElement") return;
|
|
@@ -782,12 +830,19 @@ const noBroadStoreAccess = createRule({
|
|
|
782
830
|
const entries = pattern.type === "ObjectPattern" ? pattern.properties : pattern.elements;
|
|
783
831
|
for (const entry of entries) {
|
|
784
832
|
if (!entry) continue;
|
|
785
|
-
if (entry.type === "Property") recordObjectBindings(entry.value, source, write);
|
|
786
|
-
else recordObjectBindings(entry, source, write);
|
|
833
|
+
if (entry.type === "Property") recordObjectBindings(entry.value, source, write, true);
|
|
834
|
+
else recordObjectBindings(entry, source, write, true);
|
|
787
835
|
}
|
|
788
836
|
};
|
|
789
837
|
return {
|
|
790
838
|
CallExpression(node) {
|
|
839
|
+
if (getFactoryKind(node) === "createStore") {
|
|
840
|
+
const model = node.arguments[0];
|
|
841
|
+
if (model?.type === "Identifier") {
|
|
842
|
+
const symbol = getIdentifierSymbol(model);
|
|
843
|
+
if (symbol) storeModels.add(symbol);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
791
846
|
const argument = getBroadArgument(node);
|
|
792
847
|
if (argument) reportBroadAccess(argument);
|
|
793
848
|
},
|
|
@@ -798,7 +853,10 @@ const noBroadStoreAccess = createRule({
|
|
|
798
853
|
if (!node.init) return;
|
|
799
854
|
if (node.id.type === "Identifier") {
|
|
800
855
|
const symbol = getIdentifierSymbol(node.id);
|
|
801
|
-
if (symbol) origins.record(symbol,
|
|
856
|
+
if (symbol) origins.record(symbol, {
|
|
857
|
+
expression: node.init,
|
|
858
|
+
objectField: false
|
|
859
|
+
}, node);
|
|
802
860
|
return;
|
|
803
861
|
}
|
|
804
862
|
if (node.id.type === "ObjectPattern" && node.id.properties.some((property) => property.type === "RestElement")) reportBroadAccess(node.init);
|
|
@@ -807,7 +865,19 @@ const noBroadStoreAccess = createRule({
|
|
|
807
865
|
AssignmentExpression(node) {
|
|
808
866
|
if (node.left.type !== "Identifier") return;
|
|
809
867
|
const symbol = getIdentifierSymbol(node.left);
|
|
810
|
-
if (symbol) origins.record(symbol,
|
|
868
|
+
if (symbol) origins.record(symbol, {
|
|
869
|
+
expression: node.right,
|
|
870
|
+
objectField: false
|
|
871
|
+
}, node);
|
|
872
|
+
},
|
|
873
|
+
"Program:exit"() {
|
|
874
|
+
for (const { expression, messageId } of pendingReports) {
|
|
875
|
+
if (!options.includeStoreModels && isInsideStoreModel(expression)) continue;
|
|
876
|
+
context.report({
|
|
877
|
+
node: expression,
|
|
878
|
+
messageId
|
|
879
|
+
});
|
|
880
|
+
}
|
|
811
881
|
}
|
|
812
882
|
};
|
|
813
883
|
}
|
|
@@ -2471,7 +2541,7 @@ const rules = {
|
|
|
2471
2541
|
const plugin = {
|
|
2472
2542
|
meta: {
|
|
2473
2543
|
name: "@violetflux/eslint-plugin-kerros",
|
|
2474
|
-
version: "0.3.
|
|
2544
|
+
version: "0.3.3"
|
|
2475
2545
|
},
|
|
2476
2546
|
rules
|
|
2477
2547
|
};
|
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,96 @@ 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: {
|
|
712
|
+
includeObjectFields: { type: "boolean" },
|
|
713
|
+
includeStoreModels: { type: "boolean" }
|
|
714
|
+
},
|
|
715
|
+
additionalProperties: false
|
|
716
|
+
}],
|
|
717
|
+
messages: {
|
|
718
|
+
broadAccess: "Do not enumerate, serialize, or spread a complete selector-free Store snapshot.",
|
|
719
|
+
broadObjectField: "Do not enumerate, serialize, or spread an object field from a selector-free Store snapshot."
|
|
720
|
+
}
|
|
711
721
|
},
|
|
712
|
-
defaultOptions: [
|
|
713
|
-
|
|
714
|
-
|
|
722
|
+
defaultOptions: [{
|
|
723
|
+
includeObjectFields: false,
|
|
724
|
+
includeStoreModels: false
|
|
725
|
+
}],
|
|
726
|
+
create(context, [options]) {
|
|
727
|
+
const { getFactoryKind, getIdentifierSymbol, getType, isStoreHookCall } = createKerrosTypeTools(context);
|
|
715
728
|
const origins = createReferenceOriginTracker(context.sourceCode.ast);
|
|
716
|
-
|
|
717
|
-
const
|
|
729
|
+
const storeModels = /* @__PURE__ */ new Set();
|
|
730
|
+
const pendingReports = [];
|
|
731
|
+
/** Test whether an expression belongs to a function registered as a createStore model. */
|
|
732
|
+
const isInsideStoreModel = (expression) => {
|
|
733
|
+
let node = expression.parent;
|
|
734
|
+
while (node) {
|
|
735
|
+
if (node.type === "FunctionDeclaration" && node.id) {
|
|
736
|
+
const symbol = getIdentifierSymbol(node.id);
|
|
737
|
+
return symbol ? storeModels.has(symbol) : false;
|
|
738
|
+
}
|
|
739
|
+
if ((node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression") && node.parent.type === "VariableDeclarator" && node.parent.id.type === "Identifier") {
|
|
740
|
+
const symbol = getIdentifierSymbol(node.parent.id);
|
|
741
|
+
return symbol ? storeModels.has(symbol) : false;
|
|
742
|
+
}
|
|
743
|
+
node = node.parent;
|
|
744
|
+
}
|
|
745
|
+
return false;
|
|
746
|
+
};
|
|
747
|
+
/** Classify whether an expression is a complete Store snapshot or one object field from it. */
|
|
748
|
+
const readStoreOrigin = (input, seen = /* @__PURE__ */ new Set()) => {
|
|
718
749
|
const node = unwrapExpression(input);
|
|
719
750
|
if (node.type === "CallExpression") {
|
|
720
751
|
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);
|
|
752
|
+
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
753
|
}
|
|
723
|
-
if (node.type === "AssignmentExpression") return
|
|
724
|
-
if (node.type === "MemberExpression") return
|
|
725
|
-
if (node.type !== "Identifier") return
|
|
754
|
+
if (node.type === "AssignmentExpression") return readStoreOrigin(node.right, seen);
|
|
755
|
+
if (node.type === "MemberExpression") return readStoreOrigin(node.object, seen) ? "objectField" : void 0;
|
|
756
|
+
if (node.type !== "Identifier") return;
|
|
726
757
|
const symbol = getIdentifierSymbol(node);
|
|
727
|
-
if (!symbol || seen.has(symbol)) return
|
|
758
|
+
if (!symbol || seen.has(symbol)) return;
|
|
728
759
|
seen.add(symbol);
|
|
729
|
-
|
|
760
|
+
let result;
|
|
761
|
+
for (const source of origins.resolve(symbol, node)) {
|
|
762
|
+
const origin = readStoreOrigin(source.expression, seen);
|
|
763
|
+
if (!origin) continue;
|
|
764
|
+
if (!source.objectField && origin === "snapshot") {
|
|
765
|
+
result = "snapshot";
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
result = "objectField";
|
|
769
|
+
}
|
|
730
770
|
seen.delete(symbol);
|
|
731
|
-
return
|
|
771
|
+
return result;
|
|
732
772
|
};
|
|
733
773
|
/** Report one operation that subscribes to every enumerable field. */
|
|
734
774
|
const reportBroadAccess = (expression) => {
|
|
735
|
-
|
|
736
|
-
|
|
775
|
+
const origin = readStoreOrigin(expression);
|
|
776
|
+
if (origin === "snapshot") pendingReports.push({
|
|
777
|
+
expression,
|
|
737
778
|
messageId: "broadAccess"
|
|
738
779
|
});
|
|
780
|
+
else if (origin === "objectField" && options.includeObjectFields) pendingReports.push({
|
|
781
|
+
expression,
|
|
782
|
+
messageId: "broadObjectField"
|
|
783
|
+
});
|
|
739
784
|
};
|
|
740
785
|
/** Track object-valued bindings destructured from a snapshot. */
|
|
741
|
-
const recordObjectBindings = (pattern, source, write) => {
|
|
786
|
+
const recordObjectBindings = (pattern, source, write, objectField = false) => {
|
|
742
787
|
if (pattern.type === "Identifier") {
|
|
743
788
|
if ((getType(pattern).flags & ts.TypeFlags.Object) === 0) return;
|
|
744
789
|
const symbol = getIdentifierSymbol(pattern);
|
|
745
|
-
if (symbol) origins.record(symbol,
|
|
790
|
+
if (symbol) origins.record(symbol, {
|
|
791
|
+
expression: source,
|
|
792
|
+
objectField
|
|
793
|
+
}, write);
|
|
746
794
|
return;
|
|
747
795
|
}
|
|
748
796
|
if (pattern.type === "AssignmentPattern") {
|
|
749
|
-
recordObjectBindings(pattern.left, source, write);
|
|
797
|
+
recordObjectBindings(pattern.left, source, write, objectField);
|
|
750
798
|
return;
|
|
751
799
|
}
|
|
752
800
|
if (pattern.type === "RestElement") return;
|
|
@@ -754,12 +802,19 @@ const noBroadStoreAccess = createRule({
|
|
|
754
802
|
const entries = pattern.type === "ObjectPattern" ? pattern.properties : pattern.elements;
|
|
755
803
|
for (const entry of entries) {
|
|
756
804
|
if (!entry) continue;
|
|
757
|
-
if (entry.type === "Property") recordObjectBindings(entry.value, source, write);
|
|
758
|
-
else recordObjectBindings(entry, source, write);
|
|
805
|
+
if (entry.type === "Property") recordObjectBindings(entry.value, source, write, true);
|
|
806
|
+
else recordObjectBindings(entry, source, write, true);
|
|
759
807
|
}
|
|
760
808
|
};
|
|
761
809
|
return {
|
|
762
810
|
CallExpression(node) {
|
|
811
|
+
if (getFactoryKind(node) === "createStore") {
|
|
812
|
+
const model = node.arguments[0];
|
|
813
|
+
if (model?.type === "Identifier") {
|
|
814
|
+
const symbol = getIdentifierSymbol(model);
|
|
815
|
+
if (symbol) storeModels.add(symbol);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
763
818
|
const argument = getBroadArgument(node);
|
|
764
819
|
if (argument) reportBroadAccess(argument);
|
|
765
820
|
},
|
|
@@ -770,7 +825,10 @@ const noBroadStoreAccess = createRule({
|
|
|
770
825
|
if (!node.init) return;
|
|
771
826
|
if (node.id.type === "Identifier") {
|
|
772
827
|
const symbol = getIdentifierSymbol(node.id);
|
|
773
|
-
if (symbol) origins.record(symbol,
|
|
828
|
+
if (symbol) origins.record(symbol, {
|
|
829
|
+
expression: node.init,
|
|
830
|
+
objectField: false
|
|
831
|
+
}, node);
|
|
774
832
|
return;
|
|
775
833
|
}
|
|
776
834
|
if (node.id.type === "ObjectPattern" && node.id.properties.some((property) => property.type === "RestElement")) reportBroadAccess(node.init);
|
|
@@ -779,7 +837,19 @@ const noBroadStoreAccess = createRule({
|
|
|
779
837
|
AssignmentExpression(node) {
|
|
780
838
|
if (node.left.type !== "Identifier") return;
|
|
781
839
|
const symbol = getIdentifierSymbol(node.left);
|
|
782
|
-
if (symbol) origins.record(symbol,
|
|
840
|
+
if (symbol) origins.record(symbol, {
|
|
841
|
+
expression: node.right,
|
|
842
|
+
objectField: false
|
|
843
|
+
}, node);
|
|
844
|
+
},
|
|
845
|
+
"Program:exit"() {
|
|
846
|
+
for (const { expression, messageId } of pendingReports) {
|
|
847
|
+
if (!options.includeStoreModels && isInsideStoreModel(expression)) continue;
|
|
848
|
+
context.report({
|
|
849
|
+
node: expression,
|
|
850
|
+
messageId
|
|
851
|
+
});
|
|
852
|
+
}
|
|
783
853
|
}
|
|
784
854
|
};
|
|
785
855
|
}
|
|
@@ -2443,7 +2513,7 @@ const rules = {
|
|
|
2443
2513
|
const plugin = {
|
|
2444
2514
|
meta: {
|
|
2445
2515
|
name: "@violetflux/eslint-plugin-kerros",
|
|
2446
|
-
version: "0.3.
|
|
2516
|
+
version: "0.3.3"
|
|
2447
2517
|
},
|
|
2448
2518
|
rules
|
|
2449
2519
|
};
|