@rdjperron/lint 0.2.0 → 0.5.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 +210 -93
- package/dist/config-CD2iCM6L.mjs +346 -0
- package/dist/config-CD2iCM6L.mjs.map +1 -0
- package/dist/config.d.mts +121 -14
- package/dist/config.mjs +2 -218
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +94 -26
- package/dist/index.mjs.map +1 -1
- package/dist/verify.d.mts +1 -92
- package/dist/verify.mjs +125 -257
- package/dist/verify.mjs.map +1 -1
- package/fixtures/README.md +18 -25
- 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 +28 -20
- package/dist/config.mjs.map +0 -1
- 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/config.mjs
CHANGED
|
@@ -1,218 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* The plugin entry. The alias is `rdj`, which is also the prefix in every
|
|
4
|
-
* diagnostic and every disable comment, so a consumer that renames it renames
|
|
5
|
-
* its own rule keys too.
|
|
6
|
-
*/
|
|
7
|
-
const plugin = {
|
|
8
|
-
name: "rdj",
|
|
9
|
-
specifier: "@rdjperron/lint"
|
|
10
|
-
};
|
|
11
|
-
/** Every rule the plugin registers, prefixed. `fixtures()` and the verify bin read this list. */
|
|
12
|
-
const RULE_IDS = [
|
|
13
|
-
"rdj/no-array-filter-map",
|
|
14
|
-
"rdj/no-chained-type-assertions",
|
|
15
|
-
"rdj/no-conditional-empty-object-spread",
|
|
16
|
-
"rdj/no-hardcoded-jsx-text",
|
|
17
|
-
"rdj/no-known-value-widening",
|
|
18
|
-
"rdj/no-module-mocking",
|
|
19
|
-
"rdj/no-object-parameters",
|
|
20
|
-
"rdj/no-reduce-accumulator-copy",
|
|
21
|
-
"rdj/no-reflect-apply",
|
|
22
|
-
"rdj/no-reflect-get",
|
|
23
|
-
"rdj/no-runtime-typeof",
|
|
24
|
-
"rdj/no-shape-in-symbol-names",
|
|
25
|
-
"rdj/no-unknown-parameters",
|
|
26
|
-
"rdj/no-unknown-returns",
|
|
27
|
-
"rdj/no-unknown-type-aliases",
|
|
28
|
-
"rdj/no-unsafe-dictionary-type",
|
|
29
|
-
"rdj/no-use-effect",
|
|
30
|
-
"rdj/no-widen-then-assert",
|
|
31
|
-
"rdj/require-safety-comment-for-type-assertion",
|
|
32
|
-
"rdj/static-public-env"
|
|
33
|
-
];
|
|
34
|
-
/**
|
|
35
|
-
* The base config, for `lint: { extends: [recommended] }`.
|
|
36
|
-
*
|
|
37
|
-
* Measured through `extends` on vite-plus 0.3.1 / oxlint 1.81.0 (2026-09-17):
|
|
38
|
-
* `jsPlugins`, `rules`, `overrides`, `plugins`, `categories` and `options` are
|
|
39
|
-
* inherited by the consumer's config, and the consumer's own entries win.
|
|
40
|
-
* `env`, `settings` and `ignorePatterns` are not inherited, which is why this
|
|
41
|
-
* object carries none of them and the consumer keeps writing its own.
|
|
42
|
-
*/
|
|
43
|
-
const recommended = {
|
|
44
|
-
jsPlugins: [plugin],
|
|
45
|
-
rules: {
|
|
46
|
-
"rdj/no-array-filter-map": "error",
|
|
47
|
-
"rdj/no-chained-type-assertions": "error",
|
|
48
|
-
"rdj/no-conditional-empty-object-spread": "error",
|
|
49
|
-
"rdj/no-known-value-widening": "error",
|
|
50
|
-
"rdj/no-module-mocking": "error",
|
|
51
|
-
"rdj/no-object-parameters": "error",
|
|
52
|
-
"rdj/no-reduce-accumulator-copy": "error",
|
|
53
|
-
"rdj/no-reflect-apply": "error",
|
|
54
|
-
"rdj/no-reflect-get": "error",
|
|
55
|
-
"rdj/no-runtime-typeof": ["error", { allowInTypeGuards: true }],
|
|
56
|
-
"rdj/no-shape-in-symbol-names": "error",
|
|
57
|
-
"rdj/no-unknown-parameters": "error",
|
|
58
|
-
"rdj/no-unknown-returns": "error",
|
|
59
|
-
"rdj/no-unknown-type-aliases": "error",
|
|
60
|
-
"rdj/no-unsafe-dictionary-type": "error",
|
|
61
|
-
"rdj/no-widen-then-assert": "error",
|
|
62
|
-
"rdj/require-safety-comment-for-type-assertion": "error",
|
|
63
|
-
"rdj/no-use-effect": "error",
|
|
64
|
-
"typescript/no-floating-promises": "error",
|
|
65
|
-
"typescript/no-misused-promises": "error",
|
|
66
|
-
"typescript/await-thenable": "error",
|
|
67
|
-
"typescript/no-unnecessary-type-assertion": "error",
|
|
68
|
-
"typescript/no-misused-spread": "error",
|
|
69
|
-
"typescript/require-array-sort-compare": "error",
|
|
70
|
-
"typescript/no-explicit-any": "warn",
|
|
71
|
-
"prefer-const": ["error", { destructuring: "all" }],
|
|
72
|
-
"no-unused-vars": ["error", {
|
|
73
|
-
argsIgnorePattern: "^_",
|
|
74
|
-
varsIgnorePattern: "^_"
|
|
75
|
-
}],
|
|
76
|
-
"no-underscore-dangle": "off",
|
|
77
|
-
eqeqeq: [
|
|
78
|
-
"error",
|
|
79
|
-
"always",
|
|
80
|
-
{ null: "ignore" }
|
|
81
|
-
],
|
|
82
|
-
"typescript/no-unsafe-type-assertion": "off",
|
|
83
|
-
"no-await-in-loop": "off",
|
|
84
|
-
"unicorn/consistent-function-scoping": "off",
|
|
85
|
-
"promise/always-return": "off"
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
/**
|
|
89
|
-
* The React file set: browser globals, the hooks rules, and the compiler's own
|
|
90
|
-
* judgement. A consumer appends its own entries after these, because an override
|
|
91
|
-
* that changes a `react/*` rule must repeat `plugins: ["react"]` or oxlint
|
|
92
|
-
* silently drops the entry.
|
|
93
|
-
*/
|
|
94
|
-
function react(files, options = {}) {
|
|
95
|
-
const { useEffectAllowedIn } = options;
|
|
96
|
-
const rules = {
|
|
97
|
-
"react/rules-of-hooks": "error",
|
|
98
|
-
"react/exhaustive-deps": "warn",
|
|
99
|
-
"react/react-in-jsx-scope": "off",
|
|
100
|
-
"react/immutability": "error",
|
|
101
|
-
"react/refs": "error",
|
|
102
|
-
"react/purity": "error",
|
|
103
|
-
"react/only-export-components": "error"
|
|
104
|
-
};
|
|
105
|
-
const overrides = [{
|
|
106
|
-
files,
|
|
107
|
-
plugins: ["react"],
|
|
108
|
-
env: { browser: true },
|
|
109
|
-
rules
|
|
110
|
-
}];
|
|
111
|
-
if (useEffectAllowedIn !== void 0) {
|
|
112
|
-
rules["rdj/no-use-effect"] = ["error", { hint: `The one sanctioned wrapper is ${useEffectAllowedIn}.` }];
|
|
113
|
-
overrides.push({
|
|
114
|
-
files: [useEffectAllowedIn],
|
|
115
|
-
rules: { "rdj/no-use-effect": "off" }
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
return overrides;
|
|
119
|
-
}
|
|
120
|
-
/** Files whose JSX text belongs in a catalogue rather than in the source. */
|
|
121
|
-
function i18n(files, options = {}) {
|
|
122
|
-
const { pattern } = options;
|
|
123
|
-
return [{
|
|
124
|
-
files,
|
|
125
|
-
rules: { "rdj/no-hardcoded-jsx-text": pattern === void 0 ? "error" : ["error", { pattern }] }
|
|
126
|
-
}];
|
|
127
|
-
}
|
|
128
|
-
/** Files Expo's bundler inlines `process.env` into, where only `process.env.NAME` survives. */
|
|
129
|
-
function expo(files) {
|
|
130
|
-
return [{
|
|
131
|
-
files,
|
|
132
|
-
rules: { "rdj/static-public-env": "error" }
|
|
133
|
-
}];
|
|
134
|
-
}
|
|
135
|
-
/** Test files: four rules whose premise a test does not hold. */
|
|
136
|
-
function tests(files) {
|
|
137
|
-
return [{
|
|
138
|
-
files,
|
|
139
|
-
rules: {
|
|
140
|
-
"typescript/no-explicit-any": "off",
|
|
141
|
-
"typescript/no-non-null-assertion": "off",
|
|
142
|
-
"rdj/no-runtime-typeof": "off",
|
|
143
|
-
"rdj/require-safety-comment-for-type-assertion": "off"
|
|
144
|
-
}
|
|
145
|
-
}];
|
|
146
|
-
}
|
|
147
|
-
/** Every rule of the plugin at `error`, so none of them can be missing from the list. */
|
|
148
|
-
const FIXTURE_RULES = {
|
|
149
|
-
"rdj/no-array-filter-map": "error",
|
|
150
|
-
"rdj/no-chained-type-assertions": "error",
|
|
151
|
-
"rdj/no-conditional-empty-object-spread": "error",
|
|
152
|
-
"rdj/no-hardcoded-jsx-text": "error",
|
|
153
|
-
"rdj/no-known-value-widening": "error",
|
|
154
|
-
"rdj/no-module-mocking": "error",
|
|
155
|
-
"rdj/no-object-parameters": "error",
|
|
156
|
-
"rdj/no-reduce-accumulator-copy": "error",
|
|
157
|
-
"rdj/no-reflect-apply": "error",
|
|
158
|
-
"rdj/no-reflect-get": "error",
|
|
159
|
-
"rdj/no-runtime-typeof": "error",
|
|
160
|
-
"rdj/no-shape-in-symbol-names": "error",
|
|
161
|
-
"rdj/no-unknown-parameters": "error",
|
|
162
|
-
"rdj/no-unknown-returns": "error",
|
|
163
|
-
"rdj/no-unknown-type-aliases": "error",
|
|
164
|
-
"rdj/no-unsafe-dictionary-type": "error",
|
|
165
|
-
"rdj/no-use-effect": "error",
|
|
166
|
-
"rdj/no-widen-then-assert": "error",
|
|
167
|
-
"rdj/require-safety-comment-for-type-assertion": "error",
|
|
168
|
-
"rdj/static-public-env": "error"
|
|
169
|
-
};
|
|
170
|
-
/**
|
|
171
|
-
* The view the verify command lints the shipped fixtures through. Without this
|
|
172
|
-
* override every rule the consumer leaves off reports nothing there, and "no
|
|
173
|
-
* findings" is exactly what a dead rule looks like.
|
|
174
|
-
*/
|
|
175
|
-
function fixtures(view = "scripts/.lint-fixtures/**") {
|
|
176
|
-
return [{
|
|
177
|
-
files: [view],
|
|
178
|
-
rules: { ...FIXTURE_RULES }
|
|
179
|
-
}];
|
|
180
|
-
}
|
|
181
|
-
/** The formatting both consumers settled on. Spread it and add what is yours. */
|
|
182
|
-
const fmt = {
|
|
183
|
-
printWidth: 100,
|
|
184
|
-
singleQuote: false,
|
|
185
|
-
semi: true,
|
|
186
|
-
tabWidth: 2,
|
|
187
|
-
useTabs: false
|
|
188
|
-
};
|
|
189
|
-
/**
|
|
190
|
-
* Paths no linter and no formatter should walk: build output, dependencies, the
|
|
191
|
-
* directories AI coding agents scribble in, and the Vitest JSON report. Spread
|
|
192
|
-
* it into `ignorePatterns` and append the repo's own.
|
|
193
|
-
*/
|
|
194
|
-
const ignore = [
|
|
195
|
-
"node_modules/**",
|
|
196
|
-
"**/node_modules/**",
|
|
197
|
-
"dist/**",
|
|
198
|
-
"**/dist/**",
|
|
199
|
-
...[
|
|
200
|
-
".agents",
|
|
201
|
-
".claude",
|
|
202
|
-
".codex",
|
|
203
|
-
".continue",
|
|
204
|
-
".cursor",
|
|
205
|
-
".gemini",
|
|
206
|
-
".opencode",
|
|
207
|
-
".pi",
|
|
208
|
-
".roo",
|
|
209
|
-
".windsurf"
|
|
210
|
-
].flatMap((dir) => [`${dir}/**`, `**/${dir}/**`]),
|
|
211
|
-
"**/test-results.json"
|
|
212
|
-
];
|
|
213
|
-
/** The pre-commit entry: `vp check --fix` is defined for JS and TS sources, nothing else. */
|
|
214
|
-
const staged = { "*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}": "vp check --fix" };
|
|
215
|
-
//#endregion
|
|
216
|
-
export { RULE_IDS, expo, fixtures, fmt, i18n, ignore, plugin, react, recommended, staged, tests };
|
|
217
|
-
|
|
218
|
-
//# sourceMappingURL=config.mjs.map
|
|
1
|
+
import { a as i18n, c as react, d as staged, f as tests, i as fmt, l as recommended, n as decoders, o as ignore, r as expo, s as plugin, t as RULE_IDS, u as restrictedImports } from "./config-CD2iCM6L.mjs";
|
|
2
|
+
export { RULE_IDS, decoders, expo, fmt, i18n, ignore, plugin, react, recommended, restrictedImports, staged, tests };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/index.d.ts
|
|
2
|
-
declare const
|
|
2
|
+
declare const rdjperronPlugin: import("@oxlint/plugins").Plugin;
|
|
3
3
|
//#endregion
|
|
4
|
-
export {
|
|
4
|
+
export { rdjperronPlugin as default };
|
|
5
5
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -454,7 +454,8 @@ function unsafeDirectValue(type, environment, substitutions, resolvingAliases) {
|
|
|
454
454
|
if (unwrapped.type === "TSIntersectionType") {
|
|
455
455
|
const unsafeMembers = unwrapped.types.map((member) => unsafeDirectValue(member, environment, substitutions, resolvingAliases));
|
|
456
456
|
if (unsafeMembers.includes("any")) return "any";
|
|
457
|
-
|
|
457
|
+
const first = unsafeMembers[0];
|
|
458
|
+
return first !== void 0 && unsafeMembers.every((member) => member !== null) ? first : null;
|
|
458
459
|
}
|
|
459
460
|
if (unwrapped.type !== "TSTypeReference") return null;
|
|
460
461
|
const name = typeReferenceName$2(unwrapped);
|
|
@@ -1103,10 +1104,16 @@ const noRuntimeTypeofRule = defineRule({
|
|
|
1103
1104
|
meta: {
|
|
1104
1105
|
type: "problem",
|
|
1105
1106
|
docs: { description: "Disallow runtime typeof checks; external values must be decoded into meaningful types at their I/O boundary." },
|
|
1106
|
-
messages: {
|
|
1107
|
+
messages: {
|
|
1108
|
+
runtimeTypeof: "A `typeof` check narrows a representation without establishing its contract. Parse input at its I/O boundary, then branch on the domain value.",
|
|
1109
|
+
runtimeTypeofWithHint: "A `typeof` check narrows a representation without establishing its contract. Parse input at its I/O boundary, then branch on the domain value. {{hint}}"
|
|
1110
|
+
},
|
|
1107
1111
|
schema: [{
|
|
1108
1112
|
type: "object",
|
|
1109
|
-
properties: {
|
|
1113
|
+
properties: {
|
|
1114
|
+
allowInTypeGuards: { type: "boolean" },
|
|
1115
|
+
hint: { type: "string" }
|
|
1116
|
+
},
|
|
1110
1117
|
additionalProperties: false
|
|
1111
1118
|
}],
|
|
1112
1119
|
defaultOptions: [{ allowInTypeGuards: false }]
|
|
@@ -1114,19 +1121,29 @@ const noRuntimeTypeofRule = defineRule({
|
|
|
1114
1121
|
createOnce(context) {
|
|
1115
1122
|
return { UnaryExpression(node) {
|
|
1116
1123
|
const option = context.options?.[0];
|
|
1117
|
-
const
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1124
|
+
const configured = typeof option === "object" && option !== null && !Array.isArray(option);
|
|
1125
|
+
const allowInTypeGuards = configured && option.allowInTypeGuards === true;
|
|
1126
|
+
const hint = configured && typeof option.hint === "string" ? option.hint : void 0;
|
|
1127
|
+
if (node.operator === "typeof" && !isExistenceProbe(node) && (!allowInTypeGuards || !isInsideTypeGuard(node))) {
|
|
1128
|
+
if (hint === void 0) context.report({
|
|
1129
|
+
node,
|
|
1130
|
+
messageId: "runtimeTypeof"
|
|
1131
|
+
});
|
|
1132
|
+
else context.report({
|
|
1133
|
+
node,
|
|
1134
|
+
messageId: "runtimeTypeofWithHint",
|
|
1135
|
+
data: { hint }
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1122
1138
|
} };
|
|
1123
1139
|
}
|
|
1124
1140
|
});
|
|
1125
1141
|
//#endregion
|
|
1126
1142
|
//#region src/anti-slop/rules/no-shape-in-symbol-names.ts
|
|
1127
1143
|
const FORBIDDEN_SYMBOL_NAME = "shape";
|
|
1144
|
+
const IDENTIFIER_WORD = /[A-Z]+(?![a-z])|[A-Z]?[a-z]+|\d+/gu;
|
|
1128
1145
|
function containsForbiddenSymbolName(name) {
|
|
1129
|
-
return name.toLowerCase().
|
|
1146
|
+
return (name.match(IDENTIFIER_WORD) ?? []).some((word) => word.toLowerCase().startsWith(FORBIDDEN_SYMBOL_NAME));
|
|
1130
1147
|
}
|
|
1131
1148
|
/** Return whether an identifier names a statically accessed member owned by another value. */
|
|
1132
1149
|
function isBorrowedMemberName(node) {
|
|
@@ -1134,11 +1151,11 @@ function isBorrowedMemberName(node) {
|
|
|
1134
1151
|
if (parent === null || parent.type !== "MemberExpression") return false;
|
|
1135
1152
|
return parent.property === node && parent.computed === false;
|
|
1136
1153
|
}
|
|
1137
|
-
/** Ban the
|
|
1154
|
+
/** Ban the word "shape" in every JavaScript and TypeScript symbol name. */
|
|
1138
1155
|
const noForbiddenTermInSymbolNamesRule = defineRule({
|
|
1139
1156
|
meta: {
|
|
1140
1157
|
type: "problem",
|
|
1141
|
-
docs: { description: "Disallow the
|
|
1158
|
+
docs: { description: "Disallow the word \"shape\" in JavaScript, TypeScript, private, and JSX symbol names." },
|
|
1142
1159
|
messages: { forbiddenSymbolName: "Rename symbol \"{{name}}\" for its domain role; \"shape\" describes structure rather than ownership." }
|
|
1143
1160
|
},
|
|
1144
1161
|
createOnce(context) {
|
|
@@ -1168,21 +1185,39 @@ const noUnknownParametersRule = defineRule({
|
|
|
1168
1185
|
meta: {
|
|
1169
1186
|
type: "problem",
|
|
1170
1187
|
docs: { description: "Disallow explicitly unknown function parameters except `cause` and type-predicate subjects; decode unknown input at its I/O boundary instead." },
|
|
1171
|
-
messages: {
|
|
1188
|
+
messages: {
|
|
1189
|
+
unknownParameter: "Parameter `{{parameter}}` leaves input unparsed. Accept a named domain type; run the expected schema or parser at the I/O boundary before calling this function.",
|
|
1190
|
+
unknownParameterWithHint: "Parameter `{{parameter}}` leaves input unparsed. Accept a named domain type; run the expected schema or parser at the I/O boundary before calling this function. {{hint}}"
|
|
1191
|
+
},
|
|
1192
|
+
schema: [{
|
|
1193
|
+
type: "object",
|
|
1194
|
+
properties: { hint: { type: "string" } },
|
|
1195
|
+
additionalProperties: false
|
|
1196
|
+
}]
|
|
1172
1197
|
},
|
|
1173
1198
|
createOnce(context) {
|
|
1174
1199
|
const checkParameters = (node) => {
|
|
1200
|
+
const option = context.options?.[0];
|
|
1201
|
+
const hint = typeof option === "object" && option !== null && !Array.isArray(option) && typeof option.hint === "string" ? option.hint : void 0;
|
|
1175
1202
|
for (const parameter of node.params) {
|
|
1176
1203
|
const annotation = functionParameterTypeAnnotation(parameter);
|
|
1177
1204
|
if (annotation === null || annotation === void 0) continue;
|
|
1178
1205
|
if (!containsUnknownType(annotation.typeAnnotation)) continue;
|
|
1179
1206
|
const name = functionParameterBindingName(parameter, context.sourceCode);
|
|
1180
1207
|
if (name === "cause" || isTypePredicateSubject(node, name)) continue;
|
|
1181
|
-
context.report({
|
|
1208
|
+
if (hint === void 0) context.report({
|
|
1182
1209
|
node: annotation.typeAnnotation,
|
|
1183
1210
|
messageId: "unknownParameter",
|
|
1184
1211
|
data: { parameter: name }
|
|
1185
1212
|
});
|
|
1213
|
+
else context.report({
|
|
1214
|
+
node: annotation.typeAnnotation,
|
|
1215
|
+
messageId: "unknownParameterWithHint",
|
|
1216
|
+
data: {
|
|
1217
|
+
parameter: name,
|
|
1218
|
+
hint
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1186
1221
|
}
|
|
1187
1222
|
};
|
|
1188
1223
|
return {
|
|
@@ -1720,12 +1755,18 @@ const noHardcodedJsxTextRule = defineRule({
|
|
|
1720
1755
|
//#endregion
|
|
1721
1756
|
//#region src/rules/no-use-effect.ts
|
|
1722
1757
|
const effectHooks = /* @__PURE__ */ new Set(["useEffect", "useLayoutEffect"]);
|
|
1723
|
-
/** The
|
|
1758
|
+
/** The name a member expression reads, written `.useEffect` or `["useEffect"]`; null when the key is not a literal name. */
|
|
1759
|
+
function memberPropertyName(node) {
|
|
1760
|
+
const { property } = node;
|
|
1761
|
+
if (!node.computed) return property.type === "Identifier" ? property.name : null;
|
|
1762
|
+
return property.type === "Literal" && typeof property.value === "string" ? property.value : null;
|
|
1763
|
+
}
|
|
1764
|
+
/** The effect hook a call reaches, called bare (`useEffect(...)`) or through a member (`React.useEffect(...)`, `React["useEffect"](...)`). */
|
|
1724
1765
|
function calledEffectHook(callee) {
|
|
1725
1766
|
if (callee.type === "Identifier") return effectHooks.has(callee.name) ? callee.name : null;
|
|
1726
|
-
if (callee.type !== "MemberExpression"
|
|
1727
|
-
const
|
|
1728
|
-
return
|
|
1767
|
+
if (callee.type !== "MemberExpression") return null;
|
|
1768
|
+
const name = memberPropertyName(callee);
|
|
1769
|
+
return name !== null && effectHooks.has(name) ? name : null;
|
|
1729
1770
|
}
|
|
1730
1771
|
function importedName(specifier) {
|
|
1731
1772
|
return specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
|
|
@@ -1781,17 +1822,43 @@ const noUseEffectRule = defineRule({
|
|
|
1781
1822
|
});
|
|
1782
1823
|
//#endregion
|
|
1783
1824
|
//#region src/rules/static-public-env.ts
|
|
1825
|
+
/** The name a member expression reads, the way Babel's `toComputedKey` does: `.NAME` and `["NAME"]` only. */
|
|
1826
|
+
function literalKeyName(node) {
|
|
1827
|
+
const { property } = node;
|
|
1828
|
+
if (!node.computed) return property.type === "Identifier" ? property.name : null;
|
|
1829
|
+
return property.type === "Literal" && typeof property.value === "string" ? property.value : null;
|
|
1830
|
+
}
|
|
1831
|
+
/** Whether a member expression is `process.env` or `process["env"]`, the object the inliner matches. */
|
|
1784
1832
|
function isProcessEnv(node) {
|
|
1785
|
-
return node.
|
|
1833
|
+
return node.object.type === "Identifier" && node.object.name === "process" && literalKeyName(node) === "env";
|
|
1834
|
+
}
|
|
1835
|
+
/** Whether a pattern takes `process.env` apart, in a declaration or in an assignment. */
|
|
1836
|
+
function isDestructured(node) {
|
|
1837
|
+
const { parent } = node;
|
|
1838
|
+
let target = null;
|
|
1839
|
+
if (parent.type === "VariableDeclarator" && parent.init === node) target = parent.id;
|
|
1840
|
+
if (parent.type === "AssignmentExpression" && parent.right === node) target = parent.left;
|
|
1841
|
+
return target !== null && (target.type === "ObjectPattern" || target.type === "ArrayPattern");
|
|
1842
|
+
}
|
|
1843
|
+
/** What is wrong with a `process.env.KEY` read, or null when Expo replaces that exact shape. */
|
|
1844
|
+
function readProblem(env, read) {
|
|
1845
|
+
const { parent } = read;
|
|
1846
|
+
if (parent.type === "AssignmentExpression" && parent.left === read) return "assigned";
|
|
1847
|
+
if (env.optional || read.optional) return "optionalChain";
|
|
1848
|
+
if (literalKeyName(read) !== null) return null;
|
|
1849
|
+
return read.property.type === "Literal" || read.property.type === "TemplateLiteral" ? "literalKey" : "computed";
|
|
1786
1850
|
}
|
|
1787
1851
|
/** Reject every use of `process.env` that Expo's textual inlining cannot see. */
|
|
1788
1852
|
const staticPublicEnvRule = defineRule({
|
|
1789
1853
|
meta: {
|
|
1790
1854
|
type: "problem",
|
|
1791
|
-
docs: { description: "Allow only
|
|
1855
|
+
docs: { description: "Allow only the `process.env` reads Expo replaces at bundle time; everything else is undefined on the device." },
|
|
1792
1856
|
messages: {
|
|
1793
1857
|
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
|
|
1858
|
+
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.",
|
|
1859
|
+
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`.",
|
|
1860
|
+
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`.",
|
|
1861
|
+
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
1862
|
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
1863
|
}
|
|
1797
1864
|
},
|
|
@@ -1800,13 +1867,14 @@ const staticPublicEnvRule = defineRule({
|
|
|
1800
1867
|
if (!isProcessEnv(node)) return;
|
|
1801
1868
|
const { parent } = node;
|
|
1802
1869
|
if (parent.type === "MemberExpression" && parent.object === node) {
|
|
1803
|
-
|
|
1870
|
+
const problem = readProblem(node, parent);
|
|
1871
|
+
if (problem !== null) context.report({
|
|
1804
1872
|
node: parent,
|
|
1805
|
-
messageId:
|
|
1873
|
+
messageId: problem
|
|
1806
1874
|
});
|
|
1807
1875
|
return;
|
|
1808
1876
|
}
|
|
1809
|
-
if (
|
|
1877
|
+
if (isDestructured(node)) {
|
|
1810
1878
|
context.report({
|
|
1811
1879
|
node: parent,
|
|
1812
1880
|
messageId: "destructured"
|
|
@@ -1822,8 +1890,8 @@ const staticPublicEnvRule = defineRule({
|
|
|
1822
1890
|
});
|
|
1823
1891
|
//#endregion
|
|
1824
1892
|
//#region src/index.ts
|
|
1825
|
-
const
|
|
1826
|
-
meta: { name: "
|
|
1893
|
+
const rdjperronPlugin = eslintCompatPlugin({
|
|
1894
|
+
meta: { name: "rdjperron" },
|
|
1827
1895
|
rules: {
|
|
1828
1896
|
"no-array-filter-map": noArrayFilterMapRule,
|
|
1829
1897
|
"no-chained-type-assertions": noChainedTypeAssertionsRule,
|
|
@@ -1848,6 +1916,6 @@ const rdjPlugin = eslintCompatPlugin({
|
|
|
1848
1916
|
}
|
|
1849
1917
|
});
|
|
1850
1918
|
//#endregion
|
|
1851
|
-
export {
|
|
1919
|
+
export { rdjperronPlugin as default };
|
|
1852
1920
|
|
|
1853
1921
|
//# sourceMappingURL=index.mjs.map
|