@rdjperron/lint 0.2.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 (53) hide show
  1. package/README.md +54 -53
  2. package/dist/config.d.mts +13 -4
  3. package/dist/config.mjs +70 -70
  4. package/dist/config.mjs.map +1 -1
  5. package/dist/index.d.mts +2 -2
  6. package/dist/index.mjs +50 -16
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/verify.d.mts +2 -2
  9. package/dist/verify.mjs +4 -4
  10. package/dist/verify.mjs.map +1 -1
  11. package/fixtures/README.md +4 -4
  12. package/fixtures/no-array-filter-map.fixture.ts +1 -1
  13. package/fixtures/no-chained-type-assertions.fixture.ts +1 -1
  14. package/fixtures/no-conditional-empty-object-spread.fixture.ts +1 -1
  15. package/fixtures/no-hardcoded-jsx-text.fixture.tsx +1 -1
  16. package/fixtures/no-known-value-widening.fixture.ts +1 -1
  17. package/fixtures/no-module-mocking.fixture.ts +1 -1
  18. package/fixtures/no-object-parameters.fixture.ts +1 -1
  19. package/fixtures/no-reduce-accumulator-copy.fixture.ts +1 -1
  20. package/fixtures/no-reflect-apply.fixture.ts +1 -1
  21. package/fixtures/no-reflect-get.fixture.ts +1 -1
  22. package/fixtures/no-runtime-typeof.fixture.ts +1 -1
  23. package/fixtures/no-shape-in-symbol-names.fixture.ts +1 -1
  24. package/fixtures/no-unknown-parameters.fixture.ts +1 -1
  25. package/fixtures/no-unknown-returns.fixture.ts +1 -1
  26. package/fixtures/no-unknown-type-aliases.fixture.ts +1 -1
  27. package/fixtures/no-unsafe-dictionary-type.fixture.ts +1 -1
  28. package/fixtures/no-use-effect.fixture.tsx +1 -1
  29. package/fixtures/no-widen-then-assert.fixture.ts +1 -1
  30. package/fixtures/require-safety-comment-for-type-assertion.fixture.ts +1 -1
  31. package/fixtures/static-public-env.fixture.ts +1 -1
  32. package/package.json +3 -4
  33. package/docs/rules/README.md +0 -43
  34. package/docs/rules/no-array-filter-map.md +0 -60
  35. package/docs/rules/no-chained-type-assertions.md +0 -52
  36. package/docs/rules/no-conditional-empty-object-spread.md +0 -50
  37. package/docs/rules/no-hardcoded-jsx-text.md +0 -52
  38. package/docs/rules/no-known-value-widening.md +0 -62
  39. package/docs/rules/no-module-mocking.md +0 -53
  40. package/docs/rules/no-object-parameters.md +0 -55
  41. package/docs/rules/no-reduce-accumulator-copy.md +0 -57
  42. package/docs/rules/no-reflect-apply.md +0 -56
  43. package/docs/rules/no-reflect-get.md +0 -51
  44. package/docs/rules/no-runtime-typeof.md +0 -61
  45. package/docs/rules/no-shape-in-symbol-names.md +0 -53
  46. package/docs/rules/no-unknown-parameters.md +0 -55
  47. package/docs/rules/no-unknown-returns.md +0 -61
  48. package/docs/rules/no-unknown-type-aliases.md +0 -52
  49. package/docs/rules/no-unsafe-dictionary-type.md +0 -56
  50. package/docs/rules/no-use-effect.md +0 -55
  51. package/docs/rules/no-widen-then-assert.md +0 -53
  52. package/docs/rules/require-safety-comment-for-type-assertion.md +0 -59
  53. package/docs/rules/static-public-env.md +0 -55
package/dist/index.mjs CHANGED
@@ -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) {
@@ -1720,12 +1721,18 @@ const noHardcodedJsxTextRule = defineRule({
1720
1721
  //#endregion
1721
1722
  //#region src/rules/no-use-effect.ts
1722
1723
  const effectHooks = /* @__PURE__ */ new Set(["useEffect", "useLayoutEffect"]);
1723
- /** The effect hook a call reaches, whether called bare (`useEffect(...)`) or through a member (`React.useEffect(...)`). */
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"](...)`). */
1724
1731
  function calledEffectHook(callee) {
1725
1732
  if (callee.type === "Identifier") return effectHooks.has(callee.name) ? callee.name : null;
1726
- if (callee.type !== "MemberExpression" || callee.computed) return null;
1727
- const { property } = callee;
1728
- return property.type === "Identifier" && effectHooks.has(property.name) ? property.name : null;
1733
+ if (callee.type !== "MemberExpression") return null;
1734
+ const name = memberPropertyName(callee);
1735
+ return name !== null && effectHooks.has(name) ? name : null;
1729
1736
  }
1730
1737
  function importedName(specifier) {
1731
1738
  return specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
@@ -1781,17 +1788,43 @@ const noUseEffectRule = defineRule({
1781
1788
  });
1782
1789
  //#endregion
1783
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. */
1784
1798
  function isProcessEnv(node) {
1785
- return node.type === "MemberExpression" && !node.computed && node.object.type === "Identifier" && node.object.name === "process" && node.property.type === "Identifier" && node.property.name === "env";
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";
1786
1816
  }
1787
1817
  /** Reject every use of `process.env` that Expo's textual inlining cannot see. */
1788
1818
  const staticPublicEnvRule = defineRule({
1789
1819
  meta: {
1790
1820
  type: "problem",
1791
- docs: { description: "Allow only static `process.env.NAME` reads; Expo replaces each one textually at bundle time and everything else is undefined on the device." },
1821
+ docs: { description: "Allow only the `process.env` reads Expo replaces at bundle time; everything else is undefined on the device." },
1792
1822
  messages: {
1793
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`.",
1794
- computed: "Expo replaces each `process.env.EXPO_PUBLIC_*` read textually at bundle time, so a computed key is undefined on the device. Read the variable as `process.env.NAME` with the name spelled out.",
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.",
1795
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`."
1796
1829
  }
1797
1830
  },
@@ -1800,13 +1833,14 @@ const staticPublicEnvRule = defineRule({
1800
1833
  if (!isProcessEnv(node)) return;
1801
1834
  const { parent } = node;
1802
1835
  if (parent.type === "MemberExpression" && parent.object === node) {
1803
- if (parent.computed) context.report({
1836
+ const problem = readProblem(node, parent);
1837
+ if (problem !== null) context.report({
1804
1838
  node: parent,
1805
- messageId: "computed"
1839
+ messageId: problem
1806
1840
  });
1807
1841
  return;
1808
1842
  }
1809
- if (parent.type === "VariableDeclarator" && parent.id.type === "ObjectPattern") {
1843
+ if (isDestructured(node)) {
1810
1844
  context.report({
1811
1845
  node: parent,
1812
1846
  messageId: "destructured"
@@ -1822,8 +1856,8 @@ const staticPublicEnvRule = defineRule({
1822
1856
  });
1823
1857
  //#endregion
1824
1858
  //#region src/index.ts
1825
- const rdjPlugin = eslintCompatPlugin({
1826
- meta: { name: "rdj" },
1859
+ const rdjperronPlugin = eslintCompatPlugin({
1860
+ meta: { name: "rdjperron" },
1827
1861
  rules: {
1828
1862
  "no-array-filter-map": noArrayFilterMapRule,
1829
1863
  "no-chained-type-assertions": noChainedTypeAssertionsRule,
@@ -1848,6 +1882,6 @@ const rdjPlugin = eslintCompatPlugin({
1848
1882
  }
1849
1883
  });
1850
1884
  //#endregion
1851
- export { rdjPlugin as default };
1885
+ export { rdjperronPlugin as default };
1852
1886
 
1853
1887
  //# sourceMappingURL=index.mjs.map