@rdjperron/lint 0.1.0 → 0.3.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.
Files changed (48) hide show
  1. package/README.md +147 -57
  2. package/dist/config.d.mts +122 -0
  3. package/dist/config.mjs +218 -0
  4. package/dist/config.mjs.map +1 -0
  5. package/dist/index.d.mts +5 -0
  6. package/dist/{anti-slop.mjs → index.mjs} +290 -97
  7. package/dist/index.mjs.map +1 -0
  8. package/dist/verify.d.mts +92 -0
  9. package/dist/verify.mjs +400 -0
  10. package/dist/verify.mjs.map +1 -0
  11. package/fixtures/README.md +22 -23
  12. package/fixtures/{anti-slop/no-array-filter-map.fixture.ts → no-array-filter-map.fixture.ts} +1 -1
  13. package/fixtures/{anti-slop/no-chained-type-assertions.fixture.ts → no-chained-type-assertions.fixture.ts} +1 -1
  14. package/fixtures/{anti-slop/no-conditional-empty-object-spread.fixture.ts → no-conditional-empty-object-spread.fixture.ts} +1 -1
  15. package/fixtures/{i18n/no-hardcoded-jsx-text.fixture.tsx → no-hardcoded-jsx-text.fixture.tsx} +1 -1
  16. package/fixtures/{anti-slop/no-known-value-widening.fixture.ts → no-known-value-widening.fixture.ts} +1 -1
  17. package/fixtures/{anti-slop/no-module-mocking.fixture.ts → no-module-mocking.fixture.ts} +1 -1
  18. package/fixtures/{anti-slop/no-object-parameters.fixture.ts → no-object-parameters.fixture.ts} +1 -1
  19. package/fixtures/{anti-slop/no-reduce-accumulator-copy.fixture.ts → no-reduce-accumulator-copy.fixture.ts} +1 -1
  20. package/fixtures/{anti-slop/no-reflect-apply.fixture.ts → no-reflect-apply.fixture.ts} +1 -1
  21. package/fixtures/{anti-slop/no-reflect-get.fixture.ts → no-reflect-get.fixture.ts} +1 -1
  22. package/fixtures/{anti-slop/no-runtime-typeof.fixture.ts → no-runtime-typeof.fixture.ts} +1 -1
  23. package/fixtures/no-shape-in-symbol-names.fixture.ts +2 -0
  24. package/fixtures/{anti-slop/no-unknown-parameters.fixture.ts → no-unknown-parameters.fixture.ts} +1 -1
  25. package/fixtures/{anti-slop/no-unknown-returns.fixture.ts → no-unknown-returns.fixture.ts} +1 -1
  26. package/fixtures/no-unknown-type-aliases.fixture.ts +2 -0
  27. package/fixtures/no-unsafe-dictionary-type.fixture.ts +2 -0
  28. package/fixtures/{react/no-use-effect.fixture.tsx → no-use-effect.fixture.tsx} +1 -1
  29. package/fixtures/{anti-slop/no-widen-then-assert.fixture.ts → no-widen-then-assert.fixture.ts} +1 -1
  30. package/fixtures/{anti-slop/require-safety-comment-for-type-assertion.fixture.ts → require-safety-comment-for-type-assertion.fixture.ts} +1 -1
  31. package/fixtures/{expo/static-public-env.fixture.ts → static-public-env.fixture.ts} +1 -1
  32. package/package.json +26 -26
  33. package/dist/anti-slop.d.mts +0 -6
  34. package/dist/anti-slop.mjs.map +0 -1
  35. package/dist/expo.d.mts +0 -5
  36. package/dist/expo.mjs +0 -51
  37. package/dist/expo.mjs.map +0 -1
  38. package/dist/i18n.d.mts +0 -5
  39. package/dist/i18n.mjs +0 -50
  40. package/dist/i18n.mjs.map +0 -1
  41. package/dist/react.d.mts +0 -5
  42. package/dist/react.mjs +0 -73
  43. package/dist/react.mjs.map +0 -1
  44. package/dist/rule-options-DrAXz8BD.mjs +0 -18
  45. package/dist/rule-options-DrAXz8BD.mjs.map +0 -1
  46. package/fixtures/anti-slop/no-shape-in-symbol-names.fixture.ts +0 -2
  47. package/fixtures/anti-slop/no-unknown-type-aliases.fixture.ts +0 -2
  48. package/fixtures/anti-slop/no-unsafe-dictionary-type.fixture.ts +0 -2
@@ -97,89 +97,6 @@ const noArrayFilterMapRule = defineRule({
97
97
  }
98
98
  });
99
99
  //#endregion
100
- //#region src/anti-slop/rules/no-reduce-accumulator-copy.ts
101
- function enclosingReducer(node) {
102
- let parent = node.parent;
103
- while (parent !== null) {
104
- if (parent.type === "FunctionDeclaration") return null;
105
- if (parent.type === "ArrowFunctionExpression" || parent.type === "FunctionExpression") {
106
- const callback = parent;
107
- let owner = callback.parent;
108
- while (owner !== null && unwrapArrayExpression(owner) === callback) owner = owner.parent;
109
- if (owner?.type !== "CallExpression") return null;
110
- const method = arrayMethodTarget(owner.callee);
111
- const firstArgument = owner.arguments[0];
112
- if (method === null || method.name !== "reduce" && method.name !== "reduceRight" || owner.arguments.length > 2 || firstArgument === void 0 || unwrapArrayExpression(firstArgument) !== callback) return null;
113
- const firstParameter = callback.params[0];
114
- const accumulator = firstParameter?.type === "AssignmentPattern" ? firstParameter.left : firstParameter;
115
- if (accumulator?.type !== "Identifier") return null;
116
- return {
117
- callback,
118
- accumulator,
119
- initialValue: owner.arguments[1]
120
- };
121
- }
122
- parent = parent.parent;
123
- }
124
- return null;
125
- }
126
- function referencesAccumulator(sourceCode, node, accumulator, visited = /* @__PURE__ */ new Set()) {
127
- const variable = resolveArrayBinding(sourceCode, node);
128
- if (variable === null || visited.has(variable)) return false;
129
- if (variable === accumulator) return true;
130
- visited.add(variable);
131
- if (variable.references.some((reference) => reference.isWrite() && !reference.init)) return false;
132
- for (const definition of variable.defs) if (definition.type === "Variable" && definition.node.type === "VariableDeclarator" && definition.node.id.type === "Identifier" && definition.node.init !== null && definition.node.parent.type === "VariableDeclaration" && definition.node.parent.kind === "const") return referencesAccumulator(sourceCode, definition.node.init, accumulator, visited);
133
- return false;
134
- }
135
- function isGlobalCopyOwner(sourceCode, node, name) {
136
- node = unwrapArrayExpression(node);
137
- if (node.type !== "Identifier" || node.name !== name) return false;
138
- const variable = resolveArrayBinding(sourceCode, node);
139
- return variable === null || variable.defs.length === 0;
140
- }
141
- /** Reject non-spread copies of reducer accumulators; pair with oxc/no-accumulating-spread. */
142
- const noReduceAccumulatorCopyRule = defineRule({
143
- meta: {
144
- type: "problem",
145
- docs: { description: "Disallow copying growing reducer accumulators with Object.assign, Array.from, or array copy methods." },
146
- messages: { accumulatorCopy: "Do not copy the reducer accumulator on every iteration; growing copies can cause quadratic work. Mutate a fresh, locally owned accumulator and return it, or use an iterator pipeline/flatMap." }
147
- },
148
- createOnce(context) {
149
- return { CallExpression(node) {
150
- const method = arrayMethodTarget(node.callee);
151
- if (method === null) return;
152
- const reducer = enclosingReducer(node);
153
- if (reducer === null) return;
154
- const accumulator = context.sourceCode.getDeclaredVariables(reducer.callback).find((variable) => variable.identifiers.some((identifier) => identifier.start === reducer.accumulator.start));
155
- if (accumulator === void 0) return;
156
- const isAccumulator = (expression) => referencesAccumulator(context.sourceCode, expression, accumulator);
157
- let copiesAccumulator = false;
158
- if (method.name === "assign" && isGlobalCopyOwner(context.sourceCode, method.object, "Object")) {
159
- const target = node.arguments[0];
160
- copiesAccumulator = target !== void 0 && unwrapArrayExpression(target).type === "ObjectExpression" && node.arguments.slice(1).some(isAccumulator);
161
- } else if (method.name === "from" && isGlobalCopyOwner(context.sourceCode, method.object, "Array")) {
162
- const source = node.arguments[0];
163
- copiesAccumulator = source !== void 0 && isAccumulator(source);
164
- } else if ([
165
- "concat",
166
- "slice",
167
- "toSpliced",
168
- "toSorted",
169
- "toReversed",
170
- "with"
171
- ].includes(method.name)) {
172
- const initialValue = reducer.initialValue;
173
- copiesAccumulator = initialValue !== void 0 && isKnownArrayExpression(context.sourceCode, initialValue) && isAccumulator(method.object);
174
- }
175
- if (copiesAccumulator) context.report({
176
- node,
177
- messageId: "accumulatorCopy"
178
- });
179
- } };
180
- }
181
- });
182
- //#endregion
183
100
  //#region src/anti-slop/rules/no-chained-type-assertions.ts
184
101
  function isTypeAssertionExpression(node) {
185
102
  return node.type === "TSAsExpression" || node.type === "TSTypeAssertion";
@@ -934,7 +851,7 @@ const moduleMockMethods = /* @__PURE__ */ new Set([
934
851
  "mock",
935
852
  "unstable_mockModule"
936
853
  ]);
937
- function importedName(node) {
854
+ function importedName$1(node) {
938
855
  if (node.type !== "ImportSpecifier") return null;
939
856
  return node.imported.type === "Identifier" ? node.imported.name : node.imported.value;
940
857
  }
@@ -946,7 +863,7 @@ function isTestFrameworkObject(sourceCode, expression) {
946
863
  return variable.defs.some((definition) => {
947
864
  if (definition.type !== "ImportBinding" || definition.parent?.type !== "ImportDeclaration") return false;
948
865
  const source = definition.parent.source.value;
949
- const name = importedName(definition.node);
866
+ const name = importedName$1(definition.node);
950
867
  return source === "vitest" && name === "vi" || source === "@jest/globals" && name === "jest";
951
868
  });
952
869
  }
@@ -1020,6 +937,89 @@ const noObjectParametersRule = defineRule({
1020
937
  }
1021
938
  });
1022
939
  //#endregion
940
+ //#region src/anti-slop/rules/no-reduce-accumulator-copy.ts
941
+ function enclosingReducer(node) {
942
+ let parent = node.parent;
943
+ while (parent !== null) {
944
+ if (parent.type === "FunctionDeclaration") return null;
945
+ if (parent.type === "ArrowFunctionExpression" || parent.type === "FunctionExpression") {
946
+ const callback = parent;
947
+ let owner = callback.parent;
948
+ while (owner !== null && unwrapArrayExpression(owner) === callback) owner = owner.parent;
949
+ if (owner?.type !== "CallExpression") return null;
950
+ const method = arrayMethodTarget(owner.callee);
951
+ const firstArgument = owner.arguments[0];
952
+ if (method === null || method.name !== "reduce" && method.name !== "reduceRight" || owner.arguments.length > 2 || firstArgument === void 0 || unwrapArrayExpression(firstArgument) !== callback) return null;
953
+ const firstParameter = callback.params[0];
954
+ const accumulator = firstParameter?.type === "AssignmentPattern" ? firstParameter.left : firstParameter;
955
+ if (accumulator?.type !== "Identifier") return null;
956
+ return {
957
+ callback,
958
+ accumulator,
959
+ initialValue: owner.arguments[1]
960
+ };
961
+ }
962
+ parent = parent.parent;
963
+ }
964
+ return null;
965
+ }
966
+ function referencesAccumulator(sourceCode, node, accumulator, visited = /* @__PURE__ */ new Set()) {
967
+ const variable = resolveArrayBinding(sourceCode, node);
968
+ if (variable === null || visited.has(variable)) return false;
969
+ if (variable === accumulator) return true;
970
+ visited.add(variable);
971
+ if (variable.references.some((reference) => reference.isWrite() && !reference.init)) return false;
972
+ for (const definition of variable.defs) if (definition.type === "Variable" && definition.node.type === "VariableDeclarator" && definition.node.id.type === "Identifier" && definition.node.init !== null && definition.node.parent.type === "VariableDeclaration" && definition.node.parent.kind === "const") return referencesAccumulator(sourceCode, definition.node.init, accumulator, visited);
973
+ return false;
974
+ }
975
+ function isGlobalCopyOwner(sourceCode, node, name) {
976
+ node = unwrapArrayExpression(node);
977
+ if (node.type !== "Identifier" || node.name !== name) return false;
978
+ const variable = resolveArrayBinding(sourceCode, node);
979
+ return variable === null || variable.defs.length === 0;
980
+ }
981
+ /** Reject non-spread copies of reducer accumulators; pair with oxc/no-accumulating-spread. */
982
+ const noReduceAccumulatorCopyRule = defineRule({
983
+ meta: {
984
+ type: "problem",
985
+ docs: { description: "Disallow copying growing reducer accumulators with Object.assign, Array.from, or array copy methods." },
986
+ messages: { accumulatorCopy: "Do not copy the reducer accumulator on every iteration; growing copies can cause quadratic work. Mutate a fresh, locally owned accumulator and return it, or use an iterator pipeline/flatMap." }
987
+ },
988
+ createOnce(context) {
989
+ return { CallExpression(node) {
990
+ const method = arrayMethodTarget(node.callee);
991
+ if (method === null) return;
992
+ const reducer = enclosingReducer(node);
993
+ if (reducer === null) return;
994
+ const accumulator = context.sourceCode.getDeclaredVariables(reducer.callback).find((variable) => variable.identifiers.some((identifier) => identifier.start === reducer.accumulator.start));
995
+ if (accumulator === void 0) return;
996
+ const isAccumulator = (expression) => referencesAccumulator(context.sourceCode, expression, accumulator);
997
+ let copiesAccumulator = false;
998
+ if (method.name === "assign" && isGlobalCopyOwner(context.sourceCode, method.object, "Object")) {
999
+ const target = node.arguments[0];
1000
+ copiesAccumulator = target !== void 0 && unwrapArrayExpression(target).type === "ObjectExpression" && node.arguments.slice(1).some(isAccumulator);
1001
+ } else if (method.name === "from" && isGlobalCopyOwner(context.sourceCode, method.object, "Array")) {
1002
+ const source = node.arguments[0];
1003
+ copiesAccumulator = source !== void 0 && isAccumulator(source);
1004
+ } else if ([
1005
+ "concat",
1006
+ "slice",
1007
+ "toSpliced",
1008
+ "toSorted",
1009
+ "toReversed",
1010
+ "with"
1011
+ ].includes(method.name)) {
1012
+ const initialValue = reducer.initialValue;
1013
+ copiesAccumulator = initialValue !== void 0 && isKnownArrayExpression(context.sourceCode, initialValue) && isAccumulator(method.object);
1014
+ }
1015
+ if (copiesAccumulator) context.report({
1016
+ node,
1017
+ messageId: "accumulatorCopy"
1018
+ });
1019
+ } };
1020
+ }
1021
+ });
1022
+ //#endregion
1023
1023
  //#region src/anti-slop/shared/reflect-method.ts
1024
1024
  function isGlobalReflect(sourceCode, expression) {
1025
1025
  if (expression.type !== "Identifier" || expression.name !== "Reflect") return false;
@@ -1125,8 +1125,9 @@ const noRuntimeTypeofRule = defineRule({
1125
1125
  //#endregion
1126
1126
  //#region src/anti-slop/rules/no-shape-in-symbol-names.ts
1127
1127
  const FORBIDDEN_SYMBOL_NAME = "shape";
1128
+ const IDENTIFIER_WORD = /[A-Z]+(?![a-z])|[A-Z]?[a-z]+|\d+/gu;
1128
1129
  function containsForbiddenSymbolName(name) {
1129
- return name.toLowerCase().includes(FORBIDDEN_SYMBOL_NAME);
1130
+ return (name.match(IDENTIFIER_WORD) ?? []).some((word) => word.toLowerCase().startsWith(FORBIDDEN_SYMBOL_NAME));
1130
1131
  }
1131
1132
  /** Return whether an identifier names a statically accessed member owned by another value. */
1132
1133
  function isBorrowedMemberName(node) {
@@ -1134,11 +1135,11 @@ function isBorrowedMemberName(node) {
1134
1135
  if (parent === null || parent.type !== "MemberExpression") return false;
1135
1136
  return parent.property === node && parent.computed === false;
1136
1137
  }
1137
- /** Ban the case-insensitive substring "shape" in every JavaScript and TypeScript symbol name. */
1138
+ /** Ban the word "shape" in every JavaScript and TypeScript symbol name. */
1138
1139
  const noForbiddenTermInSymbolNamesRule = defineRule({
1139
1140
  meta: {
1140
1141
  type: "problem",
1141
- docs: { description: "Disallow the case-insensitive substring \"shape\" in JavaScript, TypeScript, private, and JSX symbol names." },
1142
+ docs: { description: "Disallow the word \"shape\" in JavaScript, TypeScript, private, and JSX symbol names." },
1142
1143
  messages: { forbiddenSymbolName: "Rename symbol \"{{name}}\" for its domain role; \"shape\" describes structure rather than ownership." }
1143
1144
  },
1144
1145
  createOnce(context) {
@@ -1664,31 +1665,223 @@ const requireSafetyCommentForTypeAssertionRule = defineRule({
1664
1665
  }
1665
1666
  });
1666
1667
  //#endregion
1667
- //#region src/anti-slop/index.ts
1668
- /** Generic Oxlint rules that reject low-evidence and low-signal implementation patterns. */
1669
- const antiSlopPlugin = eslintCompatPlugin({
1670
- meta: { name: "anti-slop" },
1668
+ //#region src/shared/rule-options.ts
1669
+ /**
1670
+ * Rule options arrive as parsed JSON from the linter config, so this is the one
1671
+ * place their shape is checked. Returns the first option when it is an object.
1672
+ */
1673
+ function firstOptionObject(options) {
1674
+ const first = options[0];
1675
+ return typeof first === "object" && first !== null && !Array.isArray(first) ? first : {};
1676
+ }
1677
+ /** A string-valued key of the first option object, or undefined when absent or not a string. */
1678
+ function stringOption(options, key) {
1679
+ const value = firstOptionObject(options)[key];
1680
+ return typeof value === "string" ? value : void 0;
1681
+ }
1682
+ //#endregion
1683
+ //#region src/rules/no-hardcoded-jsx-text.ts
1684
+ const defaultWordPattern = String.raw`\p{L}{3,}`;
1685
+ /** A string literal that is the whole expression of `{...}` placed among an element's children. */
1686
+ function isChildStringLiteral(node) {
1687
+ const { expression, parent } = node;
1688
+ if (expression.type !== "Literal" || typeof expression.value !== "string") return false;
1689
+ return parent.type === "JSXElement" || parent.type === "JSXFragment";
1690
+ }
1691
+ /** Reject words written straight into JSX instead of coming from the translation dictionary. */
1692
+ const noHardcodedJsxTextRule = defineRule({
1693
+ meta: {
1694
+ type: "problem",
1695
+ docs: { description: "Disallow literal words in JSX children; every string on screen comes from the translation dictionary." },
1696
+ messages: { hardcodedText: "Do not write words straight into JSX. Every string on screen comes from the translation dictionary." },
1697
+ schema: [{
1698
+ type: "object",
1699
+ properties: { pattern: { type: "string" } },
1700
+ additionalProperties: false
1701
+ }]
1702
+ },
1703
+ create(context) {
1704
+ const word = new RegExp(stringOption(context.options, "pattern") ?? defaultWordPattern, "u");
1705
+ return {
1706
+ JSXText(node) {
1707
+ if (word.test(node.value)) context.report({
1708
+ node,
1709
+ messageId: "hardcodedText"
1710
+ });
1711
+ },
1712
+ JSXExpressionContainer(node) {
1713
+ if (isChildStringLiteral(node) && word.test(node.expression.value)) context.report({
1714
+ node: node.expression,
1715
+ messageId: "hardcodedText"
1716
+ });
1717
+ }
1718
+ };
1719
+ }
1720
+ });
1721
+ //#endregion
1722
+ //#region src/rules/no-use-effect.ts
1723
+ const effectHooks = /* @__PURE__ */ new Set(["useEffect", "useLayoutEffect"]);
1724
+ /** The name a member expression reads, written `.useEffect` or `["useEffect"]`; null when the key is not a literal name. */
1725
+ function memberPropertyName(node) {
1726
+ const { property } = node;
1727
+ if (!node.computed) return property.type === "Identifier" ? property.name : null;
1728
+ return property.type === "Literal" && typeof property.value === "string" ? property.value : null;
1729
+ }
1730
+ /** The effect hook a call reaches, called bare (`useEffect(...)`) or through a member (`React.useEffect(...)`, `React["useEffect"](...)`). */
1731
+ function calledEffectHook(callee) {
1732
+ if (callee.type === "Identifier") return effectHooks.has(callee.name) ? callee.name : null;
1733
+ if (callee.type !== "MemberExpression") return null;
1734
+ const name = memberPropertyName(callee);
1735
+ return name !== null && effectHooks.has(name) ? name : null;
1736
+ }
1737
+ function importedName(specifier) {
1738
+ return specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
1739
+ }
1740
+ function isTypeOnly(specifier) {
1741
+ const declaration = specifier.parent;
1742
+ return specifier.importKind === "type" || declaration.type === "ImportDeclaration" && declaration.importKind === "type";
1743
+ }
1744
+ /** Reject `useEffect` and `useLayoutEffect`, called or imported. */
1745
+ const noUseEffectRule = defineRule({
1746
+ meta: {
1747
+ type: "problem",
1748
+ docs: { description: "Disallow useEffect and useLayoutEffect; derive during render, act in the event handler, or subscribe through a reactive query." },
1749
+ messages: {
1750
+ noEffect: "Do not call {{name}}. Derive the value during render, do the work in the event handler, or subscribe through a reactive query.",
1751
+ noEffectWithHint: "Do not call {{name}}. Derive the value during render, do the work in the event handler, or subscribe through a reactive query. {{hint}}"
1752
+ },
1753
+ schema: [{
1754
+ type: "object",
1755
+ properties: { hint: { type: "string" } },
1756
+ additionalProperties: false
1757
+ }]
1758
+ },
1759
+ create(context) {
1760
+ const hint = stringOption(context.options, "hint");
1761
+ const report = (node, name) => {
1762
+ if (hint === void 0) context.report({
1763
+ node,
1764
+ messageId: "noEffect",
1765
+ data: { name }
1766
+ });
1767
+ else context.report({
1768
+ node,
1769
+ messageId: "noEffectWithHint",
1770
+ data: {
1771
+ name,
1772
+ hint
1773
+ }
1774
+ });
1775
+ };
1776
+ return {
1777
+ CallExpression(node) {
1778
+ const name = calledEffectHook(node.callee);
1779
+ if (name !== null) report(node, name);
1780
+ },
1781
+ ImportSpecifier(node) {
1782
+ if (isTypeOnly(node)) return;
1783
+ const name = importedName(node);
1784
+ if (effectHooks.has(name)) report(node, name);
1785
+ }
1786
+ };
1787
+ }
1788
+ });
1789
+ //#endregion
1790
+ //#region src/rules/static-public-env.ts
1791
+ /** The name a member expression reads, the way Babel's `toComputedKey` does: `.NAME` and `["NAME"]` only. */
1792
+ function literalKeyName(node) {
1793
+ const { property } = node;
1794
+ if (!node.computed) return property.type === "Identifier" ? property.name : null;
1795
+ return property.type === "Literal" && typeof property.value === "string" ? property.value : null;
1796
+ }
1797
+ /** Whether a member expression is `process.env` or `process["env"]`, the object the inliner matches. */
1798
+ function isProcessEnv(node) {
1799
+ return node.object.type === "Identifier" && node.object.name === "process" && literalKeyName(node) === "env";
1800
+ }
1801
+ /** Whether a pattern takes `process.env` apart, in a declaration or in an assignment. */
1802
+ function isDestructured(node) {
1803
+ const { parent } = node;
1804
+ let target = null;
1805
+ if (parent.type === "VariableDeclarator" && parent.init === node) target = parent.id;
1806
+ if (parent.type === "AssignmentExpression" && parent.right === node) target = parent.left;
1807
+ return target !== null && (target.type === "ObjectPattern" || target.type === "ArrayPattern");
1808
+ }
1809
+ /** What is wrong with a `process.env.KEY` read, or null when Expo replaces that exact shape. */
1810
+ function readProblem(env, read) {
1811
+ const { parent } = read;
1812
+ if (parent.type === "AssignmentExpression" && parent.left === read) return "assigned";
1813
+ if (env.optional || read.optional) return "optionalChain";
1814
+ if (literalKeyName(read) !== null) return null;
1815
+ return read.property.type === "Literal" || read.property.type === "TemplateLiteral" ? "literalKey" : "computed";
1816
+ }
1817
+ /** Reject every use of `process.env` that Expo's textual inlining cannot see. */
1818
+ const staticPublicEnvRule = defineRule({
1819
+ meta: {
1820
+ type: "problem",
1821
+ docs: { description: "Allow only the `process.env` reads Expo replaces at bundle time; everything else is undefined on the device." },
1822
+ messages: {
1823
+ destructured: "Expo replaces each `process.env.EXPO_PUBLIC_*` read textually at bundle time, so there is no `process.env` object to destructure on the device. Read each variable as `process.env.NAME`.",
1824
+ computed: "Expo replaces each `process.env.EXPO_PUBLIC_*` read textually at bundle time, so a key it has to evaluate is undefined on the device. Read the variable as `process.env.NAME` with the name spelled out.",
1825
+ literalKey: "Expo reads a computed key only when it is a string literal, so a template or a number leaves the read in the bundle and it is undefined on the device. Read the variable as `process.env.NAME`.",
1826
+ optionalChain: "Expo's inliner never sees an optional chain, so `process.env?.NAME` survives into the bundle and is undefined on the device. Read the variable as `process.env.NAME`.",
1827
+ assigned: "Expo replaces reads, not writes: an assignment to `process.env` stays in the bundle and no inlined read will ever see it. Set the variable in the environment the bundler reads.",
1828
+ bareValue: "Expo replaces each `process.env.EXPO_PUBLIC_*` read textually at bundle time, so `process.env` passed around as a value is empty on the device. Read each variable as `process.env.NAME`."
1829
+ }
1830
+ },
1831
+ create(context) {
1832
+ return { MemberExpression(node) {
1833
+ if (!isProcessEnv(node)) return;
1834
+ const { parent } = node;
1835
+ if (parent.type === "MemberExpression" && parent.object === node) {
1836
+ const problem = readProblem(node, parent);
1837
+ if (problem !== null) context.report({
1838
+ node: parent,
1839
+ messageId: problem
1840
+ });
1841
+ return;
1842
+ }
1843
+ if (isDestructured(node)) {
1844
+ context.report({
1845
+ node: parent,
1846
+ messageId: "destructured"
1847
+ });
1848
+ return;
1849
+ }
1850
+ context.report({
1851
+ node,
1852
+ messageId: "bareValue"
1853
+ });
1854
+ } };
1855
+ }
1856
+ });
1857
+ //#endregion
1858
+ //#region src/index.ts
1859
+ const rdjperronPlugin = eslintCompatPlugin({
1860
+ meta: { name: "rdjperron" },
1671
1861
  rules: {
1672
1862
  "no-array-filter-map": noArrayFilterMapRule,
1673
- "no-reduce-accumulator-copy": noReduceAccumulatorCopyRule,
1674
1863
  "no-chained-type-assertions": noChainedTypeAssertionsRule,
1675
1864
  "no-conditional-empty-object-spread": noConditionalEmptyObjectSpreadRule,
1865
+ "no-hardcoded-jsx-text": noHardcodedJsxTextRule,
1676
1866
  "no-known-value-widening": noKnownValueWideningRule,
1677
1867
  "no-module-mocking": noModuleMockingRule,
1678
1868
  "no-object-parameters": noObjectParametersRule,
1869
+ "no-reduce-accumulator-copy": noReduceAccumulatorCopyRule,
1679
1870
  "no-reflect-apply": noReflectApplyRule,
1680
1871
  "no-reflect-get": noReflectGetRule,
1681
1872
  "no-runtime-typeof": noRuntimeTypeofRule,
1682
- "no-unsafe-dictionary-type": noUnsafeDictionaryTypeRule,
1683
1873
  "no-shape-in-symbol-names": noForbiddenTermInSymbolNamesRule,
1684
1874
  "no-unknown-parameters": noUnknownParametersRule,
1685
1875
  "no-unknown-returns": noUnknownReturnsRule,
1686
1876
  "no-unknown-type-aliases": noUnknownTypeAliasesRule,
1877
+ "no-unsafe-dictionary-type": noUnsafeDictionaryTypeRule,
1878
+ "no-use-effect": noUseEffectRule,
1687
1879
  "no-widen-then-assert": noWidenThenAssertRule,
1688
- "require-safety-comment-for-type-assertion": requireSafetyCommentForTypeAssertionRule
1880
+ "require-safety-comment-for-type-assertion": requireSafetyCommentForTypeAssertionRule,
1881
+ "static-public-env": staticPublicEnvRule
1689
1882
  }
1690
1883
  });
1691
1884
  //#endregion
1692
- export { antiSlopPlugin as default };
1885
+ export { rdjperronPlugin as default };
1693
1886
 
1694
- //# sourceMappingURL=anti-slop.mjs.map
1887
+ //# sourceMappingURL=index.mjs.map