@rdjperron/lint 0.3.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 +179 -63
- package/dist/config-CD2iCM6L.mjs +346 -0
- package/dist/config-CD2iCM6L.mjs.map +1 -0
- package/dist/config.d.mts +109 -11
- package/dist/config.mjs +2 -218
- package/dist/index.mjs +44 -10
- package/dist/index.mjs.map +1 -1
- package/dist/verify.d.mts +1 -92
- package/dist/verify.mjs +124 -256
- package/dist/verify.mjs.map +1 -1
- package/fixtures/README.md +17 -24
- package/package.json +26 -17
- package/dist/config.mjs.map +0 -1
package/dist/config.mjs
CHANGED
|
@@ -1,218 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* The plugin entry. The alias is `rdjperron`, 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: "rdjperron",
|
|
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
|
-
"rdjperron/no-array-filter-map",
|
|
14
|
-
"rdjperron/no-chained-type-assertions",
|
|
15
|
-
"rdjperron/no-conditional-empty-object-spread",
|
|
16
|
-
"rdjperron/no-hardcoded-jsx-text",
|
|
17
|
-
"rdjperron/no-known-value-widening",
|
|
18
|
-
"rdjperron/no-module-mocking",
|
|
19
|
-
"rdjperron/no-object-parameters",
|
|
20
|
-
"rdjperron/no-reduce-accumulator-copy",
|
|
21
|
-
"rdjperron/no-reflect-apply",
|
|
22
|
-
"rdjperron/no-reflect-get",
|
|
23
|
-
"rdjperron/no-runtime-typeof",
|
|
24
|
-
"rdjperron/no-shape-in-symbol-names",
|
|
25
|
-
"rdjperron/no-unknown-parameters",
|
|
26
|
-
"rdjperron/no-unknown-returns",
|
|
27
|
-
"rdjperron/no-unknown-type-aliases",
|
|
28
|
-
"rdjperron/no-unsafe-dictionary-type",
|
|
29
|
-
"rdjperron/no-use-effect",
|
|
30
|
-
"rdjperron/no-widen-then-assert",
|
|
31
|
-
"rdjperron/require-safety-comment-for-type-assertion",
|
|
32
|
-
"rdjperron/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
|
-
"rdjperron/no-array-filter-map": "error",
|
|
47
|
-
"rdjperron/no-chained-type-assertions": "error",
|
|
48
|
-
"rdjperron/no-conditional-empty-object-spread": "error",
|
|
49
|
-
"rdjperron/no-known-value-widening": "error",
|
|
50
|
-
"rdjperron/no-module-mocking": "error",
|
|
51
|
-
"rdjperron/no-object-parameters": "error",
|
|
52
|
-
"rdjperron/no-reduce-accumulator-copy": "error",
|
|
53
|
-
"rdjperron/no-reflect-apply": "error",
|
|
54
|
-
"rdjperron/no-reflect-get": "error",
|
|
55
|
-
"rdjperron/no-runtime-typeof": ["error", { allowInTypeGuards: true }],
|
|
56
|
-
"rdjperron/no-shape-in-symbol-names": "error",
|
|
57
|
-
"rdjperron/no-unknown-parameters": "error",
|
|
58
|
-
"rdjperron/no-unknown-returns": "error",
|
|
59
|
-
"rdjperron/no-unknown-type-aliases": "error",
|
|
60
|
-
"rdjperron/no-unsafe-dictionary-type": "error",
|
|
61
|
-
"rdjperron/no-widen-then-assert": "error",
|
|
62
|
-
"rdjperron/require-safety-comment-for-type-assertion": "error",
|
|
63
|
-
"rdjperron/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: the 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 { env = { browser: true }, 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,
|
|
109
|
-
rules
|
|
110
|
-
}];
|
|
111
|
-
if (useEffectAllowedIn !== void 0) {
|
|
112
|
-
rules["rdjperron/no-use-effect"] = ["error", { hint: `The one sanctioned wrapper is ${useEffectAllowedIn}.` }];
|
|
113
|
-
overrides.push({
|
|
114
|
-
files: [useEffectAllowedIn],
|
|
115
|
-
rules: { "rdjperron/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: { "rdjperron/no-hardcoded-jsx-text": pattern === void 0 ? "error" : ["error", { pattern }] }
|
|
126
|
-
}];
|
|
127
|
-
}
|
|
128
|
-
/** Files Expo's bundler inlines `process.env` into, where only the reads it replaces survive. */
|
|
129
|
-
function expo(files) {
|
|
130
|
-
return [{
|
|
131
|
-
files,
|
|
132
|
-
rules: { "rdjperron/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
|
-
"rdjperron/no-runtime-typeof": "off",
|
|
143
|
-
"rdjperron/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
|
-
"rdjperron/no-array-filter-map": "error",
|
|
150
|
-
"rdjperron/no-chained-type-assertions": "error",
|
|
151
|
-
"rdjperron/no-conditional-empty-object-spread": "error",
|
|
152
|
-
"rdjperron/no-hardcoded-jsx-text": "error",
|
|
153
|
-
"rdjperron/no-known-value-widening": "error",
|
|
154
|
-
"rdjperron/no-module-mocking": "error",
|
|
155
|
-
"rdjperron/no-object-parameters": "error",
|
|
156
|
-
"rdjperron/no-reduce-accumulator-copy": "error",
|
|
157
|
-
"rdjperron/no-reflect-apply": "error",
|
|
158
|
-
"rdjperron/no-reflect-get": "error",
|
|
159
|
-
"rdjperron/no-runtime-typeof": "error",
|
|
160
|
-
"rdjperron/no-shape-in-symbol-names": "error",
|
|
161
|
-
"rdjperron/no-unknown-parameters": "error",
|
|
162
|
-
"rdjperron/no-unknown-returns": "error",
|
|
163
|
-
"rdjperron/no-unknown-type-aliases": "error",
|
|
164
|
-
"rdjperron/no-unsafe-dictionary-type": "error",
|
|
165
|
-
"rdjperron/no-use-effect": "error",
|
|
166
|
-
"rdjperron/no-widen-then-assert": "error",
|
|
167
|
-
"rdjperron/require-safety-comment-for-type-assertion": "error",
|
|
168
|
-
"rdjperron/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.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,11 +1121,20 @@ 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
|
});
|
|
@@ -1169,21 +1185,39 @@ const noUnknownParametersRule = defineRule({
|
|
|
1169
1185
|
meta: {
|
|
1170
1186
|
type: "problem",
|
|
1171
1187
|
docs: { description: "Disallow explicitly unknown function parameters except `cause` and type-predicate subjects; decode unknown input at its I/O boundary instead." },
|
|
1172
|
-
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
|
+
}]
|
|
1173
1197
|
},
|
|
1174
1198
|
createOnce(context) {
|
|
1175
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;
|
|
1176
1202
|
for (const parameter of node.params) {
|
|
1177
1203
|
const annotation = functionParameterTypeAnnotation(parameter);
|
|
1178
1204
|
if (annotation === null || annotation === void 0) continue;
|
|
1179
1205
|
if (!containsUnknownType(annotation.typeAnnotation)) continue;
|
|
1180
1206
|
const name = functionParameterBindingName(parameter, context.sourceCode);
|
|
1181
1207
|
if (name === "cause" || isTypePredicateSubject(node, name)) continue;
|
|
1182
|
-
context.report({
|
|
1208
|
+
if (hint === void 0) context.report({
|
|
1183
1209
|
node: annotation.typeAnnotation,
|
|
1184
1210
|
messageId: "unknownParameter",
|
|
1185
1211
|
data: { parameter: name }
|
|
1186
1212
|
});
|
|
1213
|
+
else context.report({
|
|
1214
|
+
node: annotation.typeAnnotation,
|
|
1215
|
+
messageId: "unknownParameterWithHint",
|
|
1216
|
+
data: {
|
|
1217
|
+
parameter: name,
|
|
1218
|
+
hint
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1187
1221
|
}
|
|
1188
1222
|
};
|
|
1189
1223
|
return {
|