@rdjperron/lint 0.3.0 → 0.6.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/dist/config.mjs CHANGED
@@ -1,218 +1,2 @@
1
- //#region src/config.ts
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
- return unsafeMembers.length > 0 && unsafeMembers.every((member) => member !== null) ? unsafeMembers[0] : null;
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: { runtimeTypeof: "A `typeof` check narrows a representation without establishing its contract. Parse input at its I/O boundary, then branch on the domain value." },
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: { allowInTypeGuards: { type: "boolean" } },
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 allowInTypeGuards = typeof option === "object" && option !== null && !Array.isArray(option) && option.allowInTypeGuards === true;
1118
- if (node.operator === "typeof" && !isExistenceProbe(node) && (!allowInTypeGuards || !isInsideTypeGuard(node))) context.report({
1119
- node,
1120
- messageId: "runtimeTypeof"
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: { 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." }
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 {