@rdjperron/lint 0.1.0 → 0.2.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 +146 -57
- package/dist/config.d.mts +113 -0
- package/dist/config.mjs +218 -0
- package/dist/config.mjs.map +1 -0
- package/dist/index.d.mts +5 -0
- package/dist/{anti-slop.mjs → index.mjs} +253 -94
- package/dist/index.mjs.map +1 -0
- package/dist/verify.d.mts +92 -0
- package/dist/verify.mjs +400 -0
- package/dist/verify.mjs.map +1 -0
- package/docs/rules/README.md +43 -0
- package/docs/rules/no-array-filter-map.md +60 -0
- package/docs/rules/no-chained-type-assertions.md +52 -0
- package/docs/rules/no-conditional-empty-object-spread.md +50 -0
- package/docs/rules/no-hardcoded-jsx-text.md +52 -0
- package/docs/rules/no-known-value-widening.md +62 -0
- package/docs/rules/no-module-mocking.md +53 -0
- package/docs/rules/no-object-parameters.md +55 -0
- package/docs/rules/no-reduce-accumulator-copy.md +57 -0
- package/docs/rules/no-reflect-apply.md +56 -0
- package/docs/rules/no-reflect-get.md +51 -0
- package/docs/rules/no-runtime-typeof.md +61 -0
- package/docs/rules/no-shape-in-symbol-names.md +53 -0
- package/docs/rules/no-unknown-parameters.md +55 -0
- package/docs/rules/no-unknown-returns.md +61 -0
- package/docs/rules/no-unknown-type-aliases.md +52 -0
- package/docs/rules/no-unsafe-dictionary-type.md +56 -0
- package/docs/rules/no-use-effect.md +55 -0
- package/docs/rules/no-widen-then-assert.md +53 -0
- package/docs/rules/require-safety-comment-for-type-assertion.md +59 -0
- package/docs/rules/static-public-env.md +55 -0
- package/fixtures/README.md +22 -23
- package/fixtures/{anti-slop/no-array-filter-map.fixture.ts → no-array-filter-map.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-chained-type-assertions.fixture.ts → no-chained-type-assertions.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-conditional-empty-object-spread.fixture.ts → no-conditional-empty-object-spread.fixture.ts} +1 -1
- package/fixtures/{i18n/no-hardcoded-jsx-text.fixture.tsx → no-hardcoded-jsx-text.fixture.tsx} +1 -1
- package/fixtures/{anti-slop/no-known-value-widening.fixture.ts → no-known-value-widening.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-module-mocking.fixture.ts → no-module-mocking.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-object-parameters.fixture.ts → no-object-parameters.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-reduce-accumulator-copy.fixture.ts → no-reduce-accumulator-copy.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-reflect-apply.fixture.ts → no-reflect-apply.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-reflect-get.fixture.ts → no-reflect-get.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-runtime-typeof.fixture.ts → no-runtime-typeof.fixture.ts} +1 -1
- package/fixtures/no-shape-in-symbol-names.fixture.ts +2 -0
- package/fixtures/{anti-slop/no-unknown-parameters.fixture.ts → no-unknown-parameters.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-unknown-returns.fixture.ts → no-unknown-returns.fixture.ts} +1 -1
- package/fixtures/no-unknown-type-aliases.fixture.ts +2 -0
- package/fixtures/no-unsafe-dictionary-type.fixture.ts +2 -0
- package/fixtures/{react/no-use-effect.fixture.tsx → no-use-effect.fixture.tsx} +1 -1
- package/fixtures/{anti-slop/no-widen-then-assert.fixture.ts → no-widen-then-assert.fixture.ts} +1 -1
- package/fixtures/{anti-slop/require-safety-comment-for-type-assertion.fixture.ts → require-safety-comment-for-type-assertion.fixture.ts} +1 -1
- package/fixtures/{expo/static-public-env.fixture.ts → static-public-env.fixture.ts} +1 -1
- package/package.json +27 -26
- package/dist/anti-slop.d.mts +0 -6
- package/dist/anti-slop.mjs.map +0 -1
- package/dist/expo.d.mts +0 -5
- package/dist/expo.mjs +0 -51
- package/dist/expo.mjs.map +0 -1
- package/dist/i18n.d.mts +0 -5
- package/dist/i18n.mjs +0 -50
- package/dist/i18n.mjs.map +0 -1
- package/dist/react.d.mts +0 -5
- package/dist/react.mjs +0 -73
- package/dist/react.mjs.map +0 -1
- package/dist/rule-options-DrAXz8BD.mjs +0 -18
- package/dist/rule-options-DrAXz8BD.mjs.map +0 -1
- package/fixtures/anti-slop/no-shape-in-symbol-names.fixture.ts +0 -2
- package/fixtures/anti-slop/no-unknown-type-aliases.fixture.ts +0 -2
- 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;
|
|
@@ -1664,31 +1664,190 @@ const requireSafetyCommentForTypeAssertionRule = defineRule({
|
|
|
1664
1664
|
}
|
|
1665
1665
|
});
|
|
1666
1666
|
//#endregion
|
|
1667
|
-
//#region src/
|
|
1668
|
-
/**
|
|
1669
|
-
|
|
1670
|
-
|
|
1667
|
+
//#region src/shared/rule-options.ts
|
|
1668
|
+
/**
|
|
1669
|
+
* Rule options arrive as parsed JSON from the linter config, so this is the one
|
|
1670
|
+
* place their shape is checked. Returns the first option when it is an object.
|
|
1671
|
+
*/
|
|
1672
|
+
function firstOptionObject(options) {
|
|
1673
|
+
const first = options[0];
|
|
1674
|
+
return typeof first === "object" && first !== null && !Array.isArray(first) ? first : {};
|
|
1675
|
+
}
|
|
1676
|
+
/** A string-valued key of the first option object, or undefined when absent or not a string. */
|
|
1677
|
+
function stringOption(options, key) {
|
|
1678
|
+
const value = firstOptionObject(options)[key];
|
|
1679
|
+
return typeof value === "string" ? value : void 0;
|
|
1680
|
+
}
|
|
1681
|
+
//#endregion
|
|
1682
|
+
//#region src/rules/no-hardcoded-jsx-text.ts
|
|
1683
|
+
const defaultWordPattern = String.raw`\p{L}{3,}`;
|
|
1684
|
+
/** A string literal that is the whole expression of `{...}` placed among an element's children. */
|
|
1685
|
+
function isChildStringLiteral(node) {
|
|
1686
|
+
const { expression, parent } = node;
|
|
1687
|
+
if (expression.type !== "Literal" || typeof expression.value !== "string") return false;
|
|
1688
|
+
return parent.type === "JSXElement" || parent.type === "JSXFragment";
|
|
1689
|
+
}
|
|
1690
|
+
/** Reject words written straight into JSX instead of coming from the translation dictionary. */
|
|
1691
|
+
const noHardcodedJsxTextRule = defineRule({
|
|
1692
|
+
meta: {
|
|
1693
|
+
type: "problem",
|
|
1694
|
+
docs: { description: "Disallow literal words in JSX children; every string on screen comes from the translation dictionary." },
|
|
1695
|
+
messages: { hardcodedText: "Do not write words straight into JSX. Every string on screen comes from the translation dictionary." },
|
|
1696
|
+
schema: [{
|
|
1697
|
+
type: "object",
|
|
1698
|
+
properties: { pattern: { type: "string" } },
|
|
1699
|
+
additionalProperties: false
|
|
1700
|
+
}]
|
|
1701
|
+
},
|
|
1702
|
+
create(context) {
|
|
1703
|
+
const word = new RegExp(stringOption(context.options, "pattern") ?? defaultWordPattern, "u");
|
|
1704
|
+
return {
|
|
1705
|
+
JSXText(node) {
|
|
1706
|
+
if (word.test(node.value)) context.report({
|
|
1707
|
+
node,
|
|
1708
|
+
messageId: "hardcodedText"
|
|
1709
|
+
});
|
|
1710
|
+
},
|
|
1711
|
+
JSXExpressionContainer(node) {
|
|
1712
|
+
if (isChildStringLiteral(node) && word.test(node.expression.value)) context.report({
|
|
1713
|
+
node: node.expression,
|
|
1714
|
+
messageId: "hardcodedText"
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
});
|
|
1720
|
+
//#endregion
|
|
1721
|
+
//#region src/rules/no-use-effect.ts
|
|
1722
|
+
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
|
+
function calledEffectHook(callee) {
|
|
1725
|
+
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;
|
|
1729
|
+
}
|
|
1730
|
+
function importedName(specifier) {
|
|
1731
|
+
return specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
|
|
1732
|
+
}
|
|
1733
|
+
function isTypeOnly(specifier) {
|
|
1734
|
+
const declaration = specifier.parent;
|
|
1735
|
+
return specifier.importKind === "type" || declaration.type === "ImportDeclaration" && declaration.importKind === "type";
|
|
1736
|
+
}
|
|
1737
|
+
/** Reject `useEffect` and `useLayoutEffect`, called or imported. */
|
|
1738
|
+
const noUseEffectRule = defineRule({
|
|
1739
|
+
meta: {
|
|
1740
|
+
type: "problem",
|
|
1741
|
+
docs: { description: "Disallow useEffect and useLayoutEffect; derive during render, act in the event handler, or subscribe through a reactive query." },
|
|
1742
|
+
messages: {
|
|
1743
|
+
noEffect: "Do not call {{name}}. Derive the value during render, do the work in the event handler, or subscribe through a reactive query.",
|
|
1744
|
+
noEffectWithHint: "Do not call {{name}}. Derive the value during render, do the work in the event handler, or subscribe through a reactive query. {{hint}}"
|
|
1745
|
+
},
|
|
1746
|
+
schema: [{
|
|
1747
|
+
type: "object",
|
|
1748
|
+
properties: { hint: { type: "string" } },
|
|
1749
|
+
additionalProperties: false
|
|
1750
|
+
}]
|
|
1751
|
+
},
|
|
1752
|
+
create(context) {
|
|
1753
|
+
const hint = stringOption(context.options, "hint");
|
|
1754
|
+
const report = (node, name) => {
|
|
1755
|
+
if (hint === void 0) context.report({
|
|
1756
|
+
node,
|
|
1757
|
+
messageId: "noEffect",
|
|
1758
|
+
data: { name }
|
|
1759
|
+
});
|
|
1760
|
+
else context.report({
|
|
1761
|
+
node,
|
|
1762
|
+
messageId: "noEffectWithHint",
|
|
1763
|
+
data: {
|
|
1764
|
+
name,
|
|
1765
|
+
hint
|
|
1766
|
+
}
|
|
1767
|
+
});
|
|
1768
|
+
};
|
|
1769
|
+
return {
|
|
1770
|
+
CallExpression(node) {
|
|
1771
|
+
const name = calledEffectHook(node.callee);
|
|
1772
|
+
if (name !== null) report(node, name);
|
|
1773
|
+
},
|
|
1774
|
+
ImportSpecifier(node) {
|
|
1775
|
+
if (isTypeOnly(node)) return;
|
|
1776
|
+
const name = importedName(node);
|
|
1777
|
+
if (effectHooks.has(name)) report(node, name);
|
|
1778
|
+
}
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
});
|
|
1782
|
+
//#endregion
|
|
1783
|
+
//#region src/rules/static-public-env.ts
|
|
1784
|
+
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";
|
|
1786
|
+
}
|
|
1787
|
+
/** Reject every use of `process.env` that Expo's textual inlining cannot see. */
|
|
1788
|
+
const staticPublicEnvRule = defineRule({
|
|
1789
|
+
meta: {
|
|
1790
|
+
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." },
|
|
1792
|
+
messages: {
|
|
1793
|
+
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.",
|
|
1795
|
+
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
|
+
}
|
|
1797
|
+
},
|
|
1798
|
+
create(context) {
|
|
1799
|
+
return { MemberExpression(node) {
|
|
1800
|
+
if (!isProcessEnv(node)) return;
|
|
1801
|
+
const { parent } = node;
|
|
1802
|
+
if (parent.type === "MemberExpression" && parent.object === node) {
|
|
1803
|
+
if (parent.computed) context.report({
|
|
1804
|
+
node: parent,
|
|
1805
|
+
messageId: "computed"
|
|
1806
|
+
});
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
if (parent.type === "VariableDeclarator" && parent.id.type === "ObjectPattern") {
|
|
1810
|
+
context.report({
|
|
1811
|
+
node: parent,
|
|
1812
|
+
messageId: "destructured"
|
|
1813
|
+
});
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
context.report({
|
|
1817
|
+
node,
|
|
1818
|
+
messageId: "bareValue"
|
|
1819
|
+
});
|
|
1820
|
+
} };
|
|
1821
|
+
}
|
|
1822
|
+
});
|
|
1823
|
+
//#endregion
|
|
1824
|
+
//#region src/index.ts
|
|
1825
|
+
const rdjPlugin = eslintCompatPlugin({
|
|
1826
|
+
meta: { name: "rdj" },
|
|
1671
1827
|
rules: {
|
|
1672
1828
|
"no-array-filter-map": noArrayFilterMapRule,
|
|
1673
|
-
"no-reduce-accumulator-copy": noReduceAccumulatorCopyRule,
|
|
1674
1829
|
"no-chained-type-assertions": noChainedTypeAssertionsRule,
|
|
1675
1830
|
"no-conditional-empty-object-spread": noConditionalEmptyObjectSpreadRule,
|
|
1831
|
+
"no-hardcoded-jsx-text": noHardcodedJsxTextRule,
|
|
1676
1832
|
"no-known-value-widening": noKnownValueWideningRule,
|
|
1677
1833
|
"no-module-mocking": noModuleMockingRule,
|
|
1678
1834
|
"no-object-parameters": noObjectParametersRule,
|
|
1835
|
+
"no-reduce-accumulator-copy": noReduceAccumulatorCopyRule,
|
|
1679
1836
|
"no-reflect-apply": noReflectApplyRule,
|
|
1680
1837
|
"no-reflect-get": noReflectGetRule,
|
|
1681
1838
|
"no-runtime-typeof": noRuntimeTypeofRule,
|
|
1682
|
-
"no-unsafe-dictionary-type": noUnsafeDictionaryTypeRule,
|
|
1683
1839
|
"no-shape-in-symbol-names": noForbiddenTermInSymbolNamesRule,
|
|
1684
1840
|
"no-unknown-parameters": noUnknownParametersRule,
|
|
1685
1841
|
"no-unknown-returns": noUnknownReturnsRule,
|
|
1686
1842
|
"no-unknown-type-aliases": noUnknownTypeAliasesRule,
|
|
1843
|
+
"no-unsafe-dictionary-type": noUnsafeDictionaryTypeRule,
|
|
1844
|
+
"no-use-effect": noUseEffectRule,
|
|
1687
1845
|
"no-widen-then-assert": noWidenThenAssertRule,
|
|
1688
|
-
"require-safety-comment-for-type-assertion": requireSafetyCommentForTypeAssertionRule
|
|
1846
|
+
"require-safety-comment-for-type-assertion": requireSafetyCommentForTypeAssertionRule,
|
|
1847
|
+
"static-public-env": staticPublicEnvRule
|
|
1689
1848
|
}
|
|
1690
1849
|
});
|
|
1691
1850
|
//#endregion
|
|
1692
|
-
export {
|
|
1851
|
+
export { rdjPlugin as default };
|
|
1693
1852
|
|
|
1694
|
-
//# sourceMappingURL=
|
|
1853
|
+
//# sourceMappingURL=index.mjs.map
|