@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.
- package/README.md +54 -53
- package/dist/config.d.mts +13 -4
- package/dist/config.mjs +70 -70
- package/dist/config.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +50 -16
- package/dist/index.mjs.map +1 -1
- package/dist/verify.d.mts +2 -2
- package/dist/verify.mjs +4 -4
- package/dist/verify.mjs.map +1 -1
- package/fixtures/README.md +4 -4
- package/fixtures/no-array-filter-map.fixture.ts +1 -1
- package/fixtures/no-chained-type-assertions.fixture.ts +1 -1
- package/fixtures/no-conditional-empty-object-spread.fixture.ts +1 -1
- package/fixtures/no-hardcoded-jsx-text.fixture.tsx +1 -1
- package/fixtures/no-known-value-widening.fixture.ts +1 -1
- package/fixtures/no-module-mocking.fixture.ts +1 -1
- package/fixtures/no-object-parameters.fixture.ts +1 -1
- package/fixtures/no-reduce-accumulator-copy.fixture.ts +1 -1
- package/fixtures/no-reflect-apply.fixture.ts +1 -1
- package/fixtures/no-reflect-get.fixture.ts +1 -1
- package/fixtures/no-runtime-typeof.fixture.ts +1 -1
- package/fixtures/no-shape-in-symbol-names.fixture.ts +1 -1
- package/fixtures/no-unknown-parameters.fixture.ts +1 -1
- package/fixtures/no-unknown-returns.fixture.ts +1 -1
- package/fixtures/no-unknown-type-aliases.fixture.ts +1 -1
- package/fixtures/no-unsafe-dictionary-type.fixture.ts +1 -1
- package/fixtures/no-use-effect.fixture.tsx +1 -1
- package/fixtures/no-widen-then-assert.fixture.ts +1 -1
- package/fixtures/require-safety-comment-for-type-assertion.fixture.ts +1 -1
- package/fixtures/static-public-env.fixture.ts +1 -1
- package/package.json +3 -4
- package/docs/rules/README.md +0 -43
- package/docs/rules/no-array-filter-map.md +0 -60
- package/docs/rules/no-chained-type-assertions.md +0 -52
- package/docs/rules/no-conditional-empty-object-spread.md +0 -50
- package/docs/rules/no-hardcoded-jsx-text.md +0 -52
- package/docs/rules/no-known-value-widening.md +0 -62
- package/docs/rules/no-module-mocking.md +0 -53
- package/docs/rules/no-object-parameters.md +0 -55
- package/docs/rules/no-reduce-accumulator-copy.md +0 -57
- package/docs/rules/no-reflect-apply.md +0 -56
- package/docs/rules/no-reflect-get.md +0 -51
- package/docs/rules/no-runtime-typeof.md +0 -61
- package/docs/rules/no-shape-in-symbol-names.md +0 -53
- package/docs/rules/no-unknown-parameters.md +0 -55
- package/docs/rules/no-unknown-returns.md +0 -61
- package/docs/rules/no-unknown-type-aliases.md +0 -52
- package/docs/rules/no-unsafe-dictionary-type.md +0 -56
- package/docs/rules/no-use-effect.md +0 -55
- package/docs/rules/no-widen-then-assert.md +0 -53
- package/docs/rules/require-safety-comment-for-type-assertion.md +0 -59
- 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().
|
|
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
|
|
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
|
|
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
|
|
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"
|
|
1727
|
-
const
|
|
1728
|
-
return
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
1836
|
+
const problem = readProblem(node, parent);
|
|
1837
|
+
if (problem !== null) context.report({
|
|
1804
1838
|
node: parent,
|
|
1805
|
-
messageId:
|
|
1839
|
+
messageId: problem
|
|
1806
1840
|
});
|
|
1807
1841
|
return;
|
|
1808
1842
|
}
|
|
1809
|
-
if (
|
|
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
|
|
1826
|
-
meta: { name: "
|
|
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 {
|
|
1885
|
+
export { rdjperronPlugin as default };
|
|
1852
1886
|
|
|
1853
1887
|
//# sourceMappingURL=index.mjs.map
|