@ls-stack/fast-lint 0.2.0 → 0.4.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 CHANGED
@@ -13,6 +13,40 @@ only these rules run, against one shared program per tsconfig. Type-aware rules
13
13
  | `strict-casting` | moved from `@ls-stack/extended-lint` (see below) |
14
14
  | `no-unnecessary-void-on-promise` | moved from `@ls-stack/extended-lint` |
15
15
  | `no-leaked-conditional-rendering` | port of `@eslint-react/no-leaked-conditional-rendering` (v2.3.13); option `reactVersion` (default `"19.0.0"`, also used for `""` and `"detect"`: the installed version isn't read) |
16
+ | `no-duplicate-key` | stricter version of `@eslint-react/no-duplicate-key` (v2.3.13, see below) |
17
+ | `no-restricted-syntax` | ESLint's `no-restricted-syntax` merged with `@ls-stack/advanced-no-restricted-syntax` (moved from `@ls-stack/extended-lint`; see below) |
18
+ | `naming-convention` | new, replaces `no-restricted-syntax` naming selectors (see below) |
19
+ | `no-default-export` | moved from `@ls-stack/extended-lint` |
20
+ | `no-reexport` | moved from `@ls-stack/extended-lint` |
21
+ | `no-relative-imports` | moved from `@ls-stack/extended-lint` (see below) |
22
+ | `no-sync-dynamic-import` | moved from `@ls-stack/extended-lint` |
23
+ | `no-leaked-text-in-jsx` | moved from `@ls-stack/extended-lint` |
24
+ | `no-static-style-prop` | moved from `@ls-stack/extended-lint` (see below) |
25
+ | `no-non-camel-case-functions` | moved from `@ls-stack/extended-lint` (see below) |
26
+ | `prefer-named-functions` | moved from `@ls-stack/extended-lint` (see below) |
27
+ | `no-call-with-explicit-generics` | moved from `@ls-stack/extended-lint` (see below) |
28
+ | `no-call-with-inferred-generics` | moved from `@ls-stack/extended-lint` (see below) |
29
+ | `no-unnecessary-typing` | moved from `@ls-stack/extended-lint` (see below) |
30
+ | `no-type-guards` | moved from `@ls-stack/extended-lint` (see below) |
31
+ | `use-state-setter-naming` | moved from `@ls-stack/extended-lint` (see below) |
32
+ | `require-lazy-execution` | moved from `@ls-stack/extended-lint` (see below) |
33
+ | `no-unnecessary-iife` | moved from `@ls-stack/extended-lint` (see below) |
34
+ | `require-description` | moved from `@ls-stack/extended-lint` (see below) |
35
+ | `no-write-only-ref` | moved from `@ls-stack/extended-lint` (see below) |
36
+ | `no-commented-out-code` | moved from `@ls-stack/extended-lint` (see below) |
37
+ | `react-single-export` | moved from `@ls-stack/extended-lint` (see below) |
38
+ | `no-unused-optional-args` | moved from `@ls-stack/extended-lint` (see below) |
39
+ | `no-unused-type-props-in-args` | moved from `@ls-stack/extended-lint` (see below) |
40
+ | `no-unused-t-state-field` | moved from `@ls-stack/extended-lint` (see below) |
41
+ | `no-unused-selected-values` | moved from `@ls-stack/extended-lint` (see below) |
42
+ | `no-optional-root-props` | moved from `@ls-stack/extended-lint` (see below) |
43
+ | `no-redundant-function-params` | moved from `@ls-stack/extended-lint` (see below) |
44
+ | `react-no-module-level-single-use-values` | moved from `@ls-stack/extended-lint` (see below) |
45
+ | `require-reads-to-var-prop` | moved from `@ls-stack/extended-lint` (see below) |
46
+ | `prefer-react-hook-alternative` | moved from `@ls-stack/extended-lint` (see below) |
47
+ | `sort-react-components-and-styles` | moved from `@ls-stack/extended-lint` (see below) |
48
+ | `use-types-directly-above-usage` | moved from `@ls-stack/extended-lint` (see below) |
49
+ | `react-compiler-extra` | moved from `@ls-stack/extended-lint` (see below) |
16
50
 
17
51
  Options are the same as the former ESLint rules' (see `lib/index.d.ts`). Rule names may carry a plugin prefix, so
18
52
  existing `@ls-stack/strict-casting` configs and disable comments keep working.
@@ -69,6 +103,600 @@ type Options = [
69
103
 
70
104
  For `String(obj)` producing `"[object Object]"`, use `@typescript-eslint/no-base-to-string`, which covers that case well.
71
105
 
106
+ ### `no-duplicate-key`
107
+
108
+ Based on `@eslint-react/no-duplicate-key` (v2.3.13), with the same message (`A key must be unique. 'key="1"' is
109
+ duplicated.`, `noDuplicateKey`) and no options, but stricter:
110
+
111
+ - Keys of sibling elements (children of one JSX element or fragment, or items of one array literal) are compared by
112
+ static value, as React stringifies keys: `key="a"`, `key='a'`, `key={"a"}`, `` key={`a`} `` and `key={("a")}` are
113
+ all the same key, and so are `key={1}`, `key={1.0}` and `key="1"`. Upstream only compares string literals.
114
+ - Other key expressions are duplicates when their source text is the same, ignoring whitespace: `key={item.id}` on
115
+ two siblings is reported. Upstream never compares them.
116
+ - Every key that duplicates another key of its group is reported, wherever it is:
117
+ `[<a key="x" />, <b key="x" />, <c key="y" />]` reports the two `x` keys. Upstream only reports a group when its
118
+ last key duplicates an earlier one, and then reports every key of the group, unique ones too.
119
+ - In the callback of a `.map()` call (or of a method ending with `Map`, e.g. `flatMap`) or of `Array.from(items,
120
+ callback)`, an element is reported when its key is the same for every item: a static value (string, number or
121
+ template literal without expressions) or an identifier bound to a `const` declared outside the callback. Every such
122
+ key is reported, in all returns and branches. Upstream only checks string literals in `.map()`-like callbacks, and
123
+ only the last key of each call.
124
+ - Files without `key=` are skipped.
125
+
126
+ ### `no-restricted-syntax`
127
+
128
+ Reports code matching [ESQuery](https://github.com/estools/esquery) selectors over an ESTree view of the TS AST, plus
129
+ the function-call and per-file checks of the former `@ls-stack/advanced-no-restricted-syntax`.
130
+ `advanced-no-restricted-syntax` (with any plugin prefix) is an alias of this rule, in configs and in disable comments,
131
+ and so is `eslint-core/no-restricted-syntax` and any other `<prefix>/no-restricted-syntax`. One config block can't
132
+ set both names.
133
+
134
+ ```ts
135
+ type Options = [
136
+ {
137
+ /** ESLint's `no-restricted-syntax` entries, plus an optional fix */
138
+ disallow?: {
139
+ selector: string
140
+ message: string
141
+ /** Replacement text, or a JS regex replacement of the matched text (`$1`, `$<name>`, `$&`...) */
142
+ replace?: string | { regex: string; with: string }
143
+ /** default: 'suggestion' */
144
+ replaceType?: 'suggestion' | 'autofix'
145
+ }[]
146
+ /** Calls of `fn` (also through import and `const` aliases), optionally only with these literal arguments */
147
+ disallowFnCalls?: {
148
+ fn: string
149
+ withArgs?: { atIndex: number; value: string | number | boolean }[]
150
+ message: string
151
+ /** Autofix replacing the whole call */
152
+ replaceWith?: string
153
+ /** Files to skip */
154
+ ignoreRegex?: string
155
+ }[]
156
+ /**
157
+ * Requirements for files whose name matches `includeRegex`. `$1`, `$1_lowercase`, `$1_capitalize` and
158
+ * `$1_uncapitalize` in the values below are its capture groups.
159
+ */
160
+ mustMatchSyntax?: {
161
+ includeRegex: string
162
+ excludeRegex?: string
163
+ /** At least one call of each `anyCall` list, with these literal arguments */
164
+ mustCallFn?: {
165
+ anyCall: { fn: string; withArgs: { atIndex: number; literal: string | number | boolean }[] }[]
166
+ message?: string
167
+ }[]
168
+ /** At least one node matching the selector */
169
+ mustMatchSelector?: { selector: string; message: string }[]
170
+ /** An export named `name`; type defaults to 'any' */
171
+ mustHaveExport?: { name: string; type?: 'function' | 'variable' | 'any'; message: string }[]
172
+ }[]
173
+ },
174
+ ]
175
+ ```
176
+
177
+ ESLint's array form (`['error', { selector, message }, ...]`) isn't accepted: put the entries in `disallow`. Since an
178
+ override that sets options replaces all of them, spread the base options into overrides that add `mustMatchSyntax`,
179
+ and turn off one group of checks for some files by setting only the other groups, instead of `'off'`.
180
+
181
+ Supported selector syntax: node types (`CallExpression`, `*`), attributes (`[attr]`, `[a.b.c="x"]`, `!=`, `=/regex/`,
182
+ `!=/regex/`, `=type(string)`, `<`, `<=`, `>`, `>=`), field selectors (`.id`), `:not(...)`, `:matches(...)`/`:is(...)`,
183
+ `:function`, the descendant (` `) and child (`>`) combinators and comma lists. Like ESQuery, type names ignore case,
184
+ `=` compares the value as a string and regexes only match strings.
185
+
186
+ These are config errors, reported when the config is loaded:
187
+
188
+ - sibling combinators (`~`, `+`), subject indicators (`!`), `:has`, `:first-child`, `:last-child`, `:nth-child`,
189
+ `:nth-last-child`, `:statement`, `:expression`, `:declaration`, `:pattern`
190
+ - node types and fields the ESTree view doesn't model (see `src/esquery/estree.rs`), and fields set on types that
191
+ don't have them (`Identifier[callee.name="x"]`)
192
+ - regexes using JS-only syntax (lookarounds, backreferences)
193
+ - selectors whose matched node isn't narrowed to node types (`*`, `:not(Identifier)`; `[name="x"]` alone is fine:
194
+ it narrows to the types with a `name` field)
195
+
196
+ Selectors are compiled once and indexed by the node type they match: only the TS node kinds that can produce those
197
+ types are visited, `Identifier[name="x"]` and `Literal[value="x"]` are hash lookups, and the left side of a descendant
198
+ or child combinator is checked only on matching nodes, walking up through ancestors of the possible kinds.
199
+
200
+ Differences from ESLint:
201
+
202
+ - `TSTypeAnnotation` isn't modeled, so `returnType` and the `typeAnnotation` of identifiers, parameters and
203
+ properties are config errors (a type alias's or an `as` expression's `typeAnnotation` works).
204
+ - A shorthand property's key and value are one node, reported once (ESLint reports both).
205
+ - `JSXEmptyExpression`, `MetaProperty.meta`, abstract and `accessor` class members aren't modeled; `TSEnumDeclaration`
206
+ has `members` directly.
207
+ - Several `disallow` entries with the same selector each report (the ESLint plugin kept only the last).
208
+
209
+ ### `naming-convention`
210
+
211
+ Reports declared names: type names (classes, interfaces, type aliases, enums and type parameters) matching `/^[a-z]|_/`,
212
+ and values (variables, functions, parameters and destructured names) starting with `__`. Each check's regex and
213
+ message can be changed, and each can be turned off with `false`.
214
+
215
+ ```ts
216
+ type Check = boolean | { /** Rust regex the name must not match */ disallow?: string; message?: string }
217
+
218
+ type Options = [
219
+ {
220
+ /** default: { disallow: '^[a-z]|_', message: 'Type names must be PascalCase' } */
221
+ typeNames?: Check
222
+ /** default: { disallow: '^__', message: 'Variable, function and parameter names must not start with `__`' } */
223
+ values?: Check
224
+ },
225
+ ]
226
+ ```
227
+
228
+ It replaces these `no-restricted-syntax` selectors, so `eslint-core/naming-convention` disable comments keep working:
229
+
230
+ ```ts
231
+ ':matches(ClassDeclaration, ClassExpression, TSInterfaceDeclaration, TSTypeAliasDeclaration, TSEnumDeclaration) > Identifier.id[name=/^[a-z]|_/], TSTypeParameter > Identifier.name[name=/^[a-z]|_/]'
232
+ ':matches(VariableDeclarator > Identifier.id, :function > Identifier.id, :function > Identifier.params, ObjectPattern > Property > Identifier.value, ArrayPattern > Identifier, AssignmentPattern > Identifier.left, RestElement > Identifier.argument, TSParameterProperty > Identifier.parameter)[name=/^__/]'
233
+ ```
234
+
235
+ ### `no-default-export`
236
+
237
+ Disallows `export default` (declarations, expressions and `export { x as default }`), use named exports instead.
238
+ `export =` is allowed.
239
+
240
+ ### `no-reexport`
241
+
242
+ Disallows re-exports: `export ... from`, `export { local }` lists, and exporting an imported binding directly
243
+ (`export const x = imported`, `export const y = imported.prop`, `export default imported`,
244
+ `export type T = ImportedType`). Use direct exports only.
245
+
246
+ ### `no-relative-imports`
247
+
248
+ Disallows relative imports (`./` or `../`) and auto-fixes them to the matching alias.
249
+
250
+ ```ts
251
+ type Options = [
252
+ {
253
+ /** Path aliases, the first whose `replacement` (relative to `rootDir`) contains the import wins */
254
+ aliases: { find: string; replacement: string }[]
255
+ /** Directory the replacements are relative to (default: the current working directory) */
256
+ rootDir?: string
257
+ /** Allow relative imports that match no alias (default: false) */
258
+ allowNotFoundAliases?: boolean
259
+ },
260
+ ]
261
+ ```
262
+
263
+ ```ts
264
+ // aliases: [{ find: '@src', replacement: '/src' }], file: src/components/Component.ts
265
+ import { Button } from './Button' // fixed to '@src/components/Button'
266
+ ```
267
+
268
+ ### `no-sync-dynamic-import`
269
+
270
+ Disallows synchronous `import()` usage (including `import('./x').Type` type imports) and `require()` /
271
+ `require.resolve()`. `await import()`, `import().then()`, `() => import()` and `Promise.all([import()])` are allowed.
272
+
273
+ ### `no-leaked-text-in-jsx`
274
+
275
+ Reports JSX text that looks like leaked code: `,`, `;`, `[`, `]`, `(`, `)`, or text containing `&&`, `||` or ending
276
+ with `? (`. Wrap it in an expression container (`{','}`) if it is intended.
277
+
278
+ ### `no-static-style-prop`
279
+
280
+ Disallows static `style` props (string literals, or object/array literals with only literal values), use CSS instead.
281
+ Option `customMessage` is appended to the error message.
282
+
283
+ ### `no-non-camel-case-functions`
284
+
285
+ Function declarations must be named in camelCase (an optional `$` prefix is allowed). Functions with an explicit
286
+ `JSX.Element` return type (or a union containing it) are skipped, so React components can keep PascalCase names.
287
+ Arrow functions and function expressions aren't checked. No options.
288
+
289
+ ### `prefer-named-functions`
290
+
291
+ Reports `const fn = () => {}` declarations without a type annotation and suggests converting them to
292
+ `function fn() {}`. Arrow functions with an implicit return are allowed unless `disallowArrowFnWithImplicitReturns` is
293
+ set.
294
+
295
+ ```ts
296
+ type Options = [
297
+ {
298
+ /** Names matching this regex are allowed to be arrow functions */
299
+ ignoreRegex?: string
300
+ /** Also report arrow functions with an implicit return (default: false) */
301
+ disallowArrowFnWithImplicitReturns?: boolean
302
+ },
303
+ ]
304
+ ```
305
+
306
+ ### `no-call-with-explicit-generics`
307
+
308
+ Disallows explicit type arguments on calls to the configured functions, so their generics are inferred. Entries can be
309
+ a function name (`useForm`), a method (`api.fetch`) or a wildcard method (`*.fetch`), optionally with a custom
310
+ message.
311
+
312
+ ```ts
313
+ type Options = [{ functions: (string | { name: string; message: string })[] }]
314
+ ```
315
+
316
+ ### `no-call-with-inferred-generics`
317
+
318
+ Requires the configured functions to be called with explicit type arguments, and can forbid `any`, `unknown`,
319
+ `typeof` or specific type names in them.
320
+
321
+ ```ts
322
+ type Options = [
323
+ {
324
+ functions: {
325
+ name: string
326
+ /** Minimum number of type arguments (default: 1) */
327
+ minGenerics?: number
328
+ /** Skip the `any`/`unknown`/`typeof` checks unless `disallowTypes` is set */
329
+ allowAny?: boolean
330
+ /** Type names to report as `any`, replaces the global `disallowTypes` */
331
+ disallowTypes?: string[]
332
+ /** Report `typeof x` type arguments, also through local type aliases */
333
+ disallowTypeOf?: boolean
334
+ /** Report `unknown` type arguments */
335
+ disallowUnknown?: boolean
336
+ }[]
337
+ /** Type names to report as `any` for every function */
338
+ disallowTypes?: string[]
339
+ },
340
+ ]
341
+ ```
342
+
343
+ Bad:
344
+
345
+ ```ts
346
+ // ["error", { functions: [{ name: "foo" }] }]
347
+ foo('bar') // missing generic
348
+ foo<any>('bar') // 'any' in generics
349
+ ```
350
+
351
+ Good:
352
+
353
+ ```ts
354
+ foo<string>('bar')
355
+ ```
356
+
357
+ ### `no-unnecessary-typing`
358
+
359
+ Reports type annotations on callback parameters passed as the first argument of array methods, where the type is
360
+ inferred, and removes them (auto-fixable).
361
+
362
+ ```ts
363
+ type Options = [
364
+ {
365
+ /** Method names to check, or 'array' for find, filter, map, forEach, some, every, reduce, findIndex and sort (default) */
366
+ methods?: string[] | 'array'
367
+ },
368
+ ]
369
+ ```
370
+
371
+ ### `no-type-guards`
372
+
373
+ Disallows type predicates (`value is T`) on function declarations, arrow functions and class methods outside
374
+ `*.typeGuards.ts(x)` / `*.type-guards.ts(x)` files. Assertion signatures (`asserts value is T`) are allowed.
375
+
376
+ ```ts
377
+ type Options = [
378
+ {
379
+ /** Custom messages for type guards in `.find()`/`.filter()` callbacks */
380
+ alternativeMsgs?: { inArrayFind?: string; inArrayFilter?: string }
381
+ },
382
+ ]
383
+ ```
384
+
385
+ ### `use-state-setter-naming`
386
+
387
+ Requires the setter of `const [value, setter] = useState(...)` (or `React.useState`) to be named `set` + the
388
+ capitalized value name. A suggestion renames the setter.
389
+
390
+ ```ts
391
+ const [count, setCount] = useState(0) // ok
392
+ const [records, setGroups] = useState([]) // Error: expected "setRecords"
393
+ ```
394
+
395
+ Option `ignorePrefixes` (default `[]`): prefixes stripped from both names before comparing, so with `['_']`
396
+ `const [_value, _setValue]` and `const [_value, setValue]` are accepted.
397
+
398
+ ### `require-lazy-execution`
399
+
400
+ Reports calls (and tagged templates) of the configured functions at module level: they must run inside a function,
401
+ method, getter, setter or constructor. A call's name is its identifier or the property of a member callee
402
+ (`i18n.__()` matches `__`).
403
+
404
+ ```ts
405
+ // require-lazy-execution: ["error", { functions: ["__"] }]
406
+ const label = __('Save') // Error
407
+ const getLabel = () => __('Save') // ok
408
+ ```
409
+
410
+ Option `functions` (default `[]`). The ESLint rule's `selectors` option (ESQuery selectors) is not supported: a
411
+ config using it is rejected, list the function names in `functions` instead.
412
+
413
+ ### `no-unnecessary-iife`
414
+
415
+ Reports a zero-argument, non-async IIFE without parameters whose body only returns an expression, and autofixes it
416
+ to that expression (`(() => { return a.b })()` becomes `a.b`). An IIFE with more statements is reported only when
417
+ `return (() => { ... })()` is the whole body of a function, and the fix moves the IIFE body into that function. An
418
+ IIFE containing comments is not reported.
419
+
420
+ ### `require-description`
421
+
422
+ Requires a description (after ` -- `) on directive comments: `eslint-*`, `oxlint-*` and `fast-lint-*` disable/enable
423
+ comments, plus `/* eslint */`, `/* eslint-env */`, `/* global(s) */` and `/* exported */`.
424
+
425
+ ```ts
426
+ // fast-lint-disable-next-line strict-casting -- the value is checked above
427
+ ```
428
+
429
+ Option `ignore`: directive kinds to skip, e.g. `['eslint']` (see `RequireDescriptionDirective` in
430
+ `lib/index.d.ts`).
431
+
432
+ ### `no-write-only-ref`
433
+
434
+ Reports a `useRef` (imported from `react`, or any `X.useRef`) ref whose variable is never used except in `ref={...}`
435
+ JSX props: it is written by React but never read.
436
+
437
+ ### `no-commented-out-code`
438
+
439
+ Reports comments that look like commented-out code. Line comments are matched against code patterns (`const `,
440
+ `return `, `if (`, `=>`, `foo.bar(`, ...); block comments only against JSX/HTML tags. JSDoc tags, directives
441
+ (`eslint-disable`, `@ts-`, `prettier-ignore`), URLs, descriptive `Header: text` comments and inline code in backticks
442
+ surrounded by prose are ignored.
443
+
444
+ Bad:
445
+
446
+ ```ts
447
+ // function foo() {
448
+ // // ...
449
+ // }
450
+ ```
451
+
452
+ Good:
453
+
454
+ ```ts
455
+ // TODO: re-enable once the API is stable
456
+ // function foo() {
457
+ // // ...
458
+ // }
459
+ ```
460
+
461
+ Comments starting with `INFO:`, `TODO:`, `DOCS:`, `FIX:`, `FIXME:`, `HACK:`, `NOTE:`, `WARNING:`, `WARN:`, `BUG:`,
462
+ `ISSUE:`, `TEMP:`, `TEMPORARY:`, `XXX:`, `REVIEW:`, `eslint`, `cspell:` or `spell-checker:` are allowed. The rule is
463
+ heuristic: it can miss commented code.
464
+
465
+ ### `react-single-export`
466
+
467
+ In React component files (by default `*.tsx`), a file that exports a component (a PascalCase function, arrow,
468
+ `memo`/`forwardRef` call, or a HOC returning JSX) may have no other value export, so fast refresh keeps working. The
469
+ first other value export is reported; type-only exports are allowed.
470
+
471
+ ```ts
472
+ type Options = [{ /** File name suffixes checked (default: ['tsx']) */ extensions?: string[] }]
473
+ ```
474
+
475
+ ### `no-unused-optional-args`
476
+
477
+ Reports optional parameters of non-exported functions that no call passes, and optional props of an object parameter
478
+ (or of an `FC<{ ... }>` component's props) that no call or JSX usage provides. Functions passed as an argument,
479
+ returned, or used as a JSX attribute value are skipped, as are functions called with spread arguments or JSX spread
480
+ attributes.
481
+
482
+ ```ts
483
+ function f(a?: string, b?: string) {} // Error: Optional parameter 'b' is never used
484
+ f('x')
485
+ ```
486
+
487
+ Options:
488
+
489
+ ```ts
490
+ type Options = [
491
+ {
492
+ /** Regex of parameter names to ignore */
493
+ ignoreArgsMatching?: string
494
+ },
495
+ ]
496
+ ```
497
+
498
+ ### `no-unused-type-props-in-args`
499
+
500
+ Reports properties of a destructured parameter's object type that aren't destructured, in functions, arrow functions
501
+ and `FC<Props>` components. Auto-fixable: the missing properties are added to the destructuring pattern. Types
502
+ referenced more than once are ignored, as are exported types referenced from a function's parameters when
503
+ `alwaysCheckFunctionOptionTypes` is `false`.
504
+
505
+ ```ts
506
+ function foo({ a }: { a: string; b: string }) {} // Error: 'b' is defined but never used
507
+ const Foo: FC<{ a: string; b: string }> = ({ a }) => null // Error: 'b' is defined but never used
508
+ ```
509
+
510
+ Options:
511
+
512
+ ```ts
513
+ type Options = [
514
+ {
515
+ /** Regexes of FC prop type names that are checked even when referenced more than once */
516
+ forceCheckOnFCPropTypesWithName?: string[]
517
+ /** Check exported types used in function parameters (default: true) */
518
+ alwaysCheckFunctionOptionTypes?: boolean
519
+ },
520
+ ]
521
+ ```
522
+
523
+ ### `no-unused-t-state-field`
524
+
525
+ In `.tsx`/`.jsx` files importing `t-state-form`, reports fields declared in the first `useForm({ initialConfig })` that
526
+ are never read through the `formFields` of the following `useFormState(...)`. Returning `formFields` skips the check.
527
+
528
+ ### `no-unused-selected-values`
529
+
530
+ Reports values a configured selector hook selects but the caller never uses, either by destructuring the result or by
531
+ reading its properties.
532
+
533
+ ```ts
534
+ // selectors: [{ name: 'useSelector' }]
535
+ const { a } = useSelector((state) => ({ a: state.a, b: state.b })) // Error: "b" is not being used
536
+ ```
537
+
538
+ Options:
539
+
540
+ ```ts
541
+ type Options = [
542
+ {
543
+ selectors: {
544
+ /** Name of the selector function (or method) */
545
+ name: string
546
+ /** Property of an options object argument holding the selector function */
547
+ selectorProp?: string
548
+ /** Index of the argument holding the selector (default: 0) */
549
+ selectorArgPos?: number
550
+ /** Property of the result holding the selected values */
551
+ returnProp?: string
552
+ }[]
553
+ },
554
+ ]
555
+ ```
556
+
557
+ ### `no-optional-root-props`
558
+
559
+ Disallows optional properties (`prop?: T`) at the root of a local, non-exported type alias or interface that is
560
+ referenced only once, as the parameter type of a function that is itself used once (and not exported), or as the
561
+ props of a non-exported `FC<...>` component used once (and not wrapped in `memo`). Inline `FC<{ ... }>` props are
562
+ checked the same way. Suggests `prop: undefined | T` instead, so every caller has to pass the prop explicitly.
563
+
564
+ ```ts
565
+ type Props = { a?: number } // Error: optional property "a"
566
+
567
+ function test(arg: Props) {}
568
+
569
+ console.log(test)
570
+ ```
571
+
572
+ ### `no-redundant-function-params`
573
+
574
+ Reports calls passing arguments equal to the configured defaults of a function (matched by its name, or by the
575
+ property name of a member call), with an autofix that removes trailing redundant arguments and redundant properties
576
+ of object arguments. Only literals, arrays of literals and object literals with literal values are compared.
577
+
578
+ ```ts
579
+ // { functions: [{ name: 'inline', defaults: [{ align: 'center', justify: 'left' }] }] }
580
+ inline({ align: 'center' }) // Error, fixed to `inline()`
581
+ inline({ align: 'center', justify: 'right' }) // Error, fixed to `inline({ justify: 'right' })`
582
+ ```
583
+
584
+ Options: `{ functions: { name: string; defaults: unknown[] }[] }`. The fix removes the arguments between the call's
585
+ own parentheses, so `fn<T>(a)` and `fn?.(a)` are fixed correctly (the ESLint rule broke them).
586
+
587
+ ### `react-no-module-level-single-use-values`
588
+
589
+ In `.tsx`/`.jsx` files, reports module-level non-exported variables holding allocated data (array, object and class
590
+ expressions, `new Set`/`new Map`) or strings of 80+ characters that are only used inside one top-level React component
591
+ (a PascalCase function, or a PascalCase variable holding a function, possibly wrapped in `memo`/`forwardRef`): moving
592
+ them inside the component frees their memory when it isn't rendered. Aliases (`a`, `a.b`), functions, regexes and
593
+ other class instantiations are ignored.
594
+
595
+ Options: `{ ignoreConstCaseVarRegex?: string }`: CONSTANT_CASE variables whose name matches the regex are ignored.
596
+
597
+ ### `require-reads-to-var-prop`
598
+
599
+ Requires reading some properties of the value returned by a call, either through member access (`result.data`) or
600
+ destructuring (`const { data } = result`). Passing the variable itself anywhere (`fn(result)`, `{ result }`) counts
601
+ as reading every property.
602
+
603
+ ```ts
604
+ // { varsToCheck: [{ fromFnCall: 'useActionFn', prop: 'call' }] }
605
+ const onConfirm = useActionFn(fn) // Error: "useActionFn" requires reads to the prop "call".
606
+ console.log(onConfirm.isInProgress)
607
+ ```
608
+
609
+ Options: `{ varsToCheck: { fromFnCall?: string; selector?: string; prop: string | string[]; errorMsg?: string }[] }`.
610
+ `fromFnCall` is a function name (`useQuery`), `*.method` or `object.method`. `selector` only supports
611
+ `VariableDeclarator` with attribute filters (`VariableDeclarator[init.callee.name="createTest"]`, `!=`, `[path]`,
612
+ over `type`, `name`, `id`, `init`, `callee`, `object`, `property`, `computed`, `optional`, `arguments`, `argument`,
613
+ `expression` and `value`); other selectors are a config error.
614
+
615
+ ### `prefer-react-hook-alternative`
616
+
617
+ Flags calls to configured functions made directly inside React components and hooks (PascalCase or `use*` functions,
618
+ `memo`/`forwardRef` callbacks, including callbacks passed to hooks and JSX props), with a suggestion to use the hook
619
+ alternative instead.
620
+
621
+ ```ts
622
+ type Options = [
623
+ {
624
+ disallowedFunctions: {
625
+ /** Function name, matched on the callee or on the member property (`obj.name()`) */
626
+ name: string
627
+ /** Allow calls with arguments other than `undefined` */
628
+ allowUsingWithArgs?: boolean
629
+ /** Hook to use instead, offered as a suggestion */
630
+ hookAlternative?: string
631
+ /** Custom message, replaces `use <hookAlternative> instead` */
632
+ message?: string
633
+ /** Allow calls inside these functions (by declared name or call name), even when nested */
634
+ allowUseInside?: string[]
635
+ }[]
636
+ },
637
+ ]
638
+ ```
639
+
640
+ ### `sort-react-components-and-styles`
641
+
642
+ In `.tsx` files, places the main component before the other components (auto-fixable), and each `styled`/`css`
643
+ style above the first component or style that uses it.
644
+
645
+ The main component is the one selected by `mainComponentSelector`, else the one whose name matches
646
+ `mainComponentRegex` (falling back to the exported components when none matches), else the exported ones.
647
+
648
+ ```ts
649
+ type Options = [
650
+ {
651
+ /** Regex (Rust `regex` syntax, no look-around) matched against the component names */
652
+ mainComponentRegex?: string
653
+ /**
654
+ * Selector of the main component's declaration. Only `VariableDeclarator`, `FunctionDeclaration` or
655
+ * `ClassDeclaration`, optionally followed by `[id.name='Name']`, are supported
656
+ */
657
+ mainComponentSelector?: string
658
+ },
659
+ ]
660
+ ```
661
+
662
+ ### `use-types-directly-above-usage`
663
+
664
+ Requires top-level type aliases and interfaces to be placed directly above their first usage (auto-fixable).
665
+
666
+ ```ts
667
+ type Options = [
668
+ {
669
+ /** Only check usages in these contexts (default: all usages) */
670
+ checkOnly?: ('function-args' | 'FC' | 'generic-args-at-fn-calls')[]
671
+ /** Accepted for compatibility, unused */
672
+ checkTypesFromSelectors?: string[]
673
+ },
674
+ ]
675
+ ```
676
+
677
+ ### `react-compiler-extra`
678
+
679
+ Extra checks for code optimized by the React compiler:
680
+
681
+ - object methods (`{ method() {} }`) in objects passed to or returned from hooks should be arrow functions
682
+ (auto-fixable unless the method uses `this`)
683
+ - React components and hooks should create JSX, call hooks or use the `"use memo"` directive (with a suggestion to
684
+ add it)
685
+ - functions calling hooks must be components or hooks, and functions using `"use memo"` must be named like one
686
+ - with `disallowManualMemoization`, `useMemo` and `useCallback` calls are reported, as the compiler memoizes for you
687
+ - functions starting with the `"use no memo"` directive are skipped (only the nearest enclosing function counts)
688
+
689
+ ```ts
690
+ type Options = [
691
+ {
692
+ /** Only run in files with an `eslint react-compiler/react-compiler: ["error"` comment (default: false) */
693
+ runOnlyWithEnableCompilerDirective?: boolean
694
+ /** Report `useMemo` and `useCallback` calls (default: false) */
695
+ disallowManualMemoization?: boolean
696
+ },
697
+ ]
698
+ ```
699
+
72
700
  ## Usage
73
701
 
74
702
  ```sh
@@ -100,7 +728,9 @@ that isn't UTF-8 (its problems are still reported).
100
728
  ## Config
101
729
 
102
730
  `fast-lint.config.ts` (or `.mts`/`.mjs`/`.js`, evaluated by the Node launcher) or `fast-lint.config.json`, in the
103
- current directory or passed with `--config`. Without a config every rule runs as `error` with default options.
731
+ current directory or passed with `--config`. Without a config the type-aware rules (`improved-no-unnecessary-condition`,
732
+ `strict-casting`, `no-unnecessary-void-on-promise` and `no-leaked-conditional-rendering`) run as `error` with default
733
+ options; every other rule only runs when configured.
104
734
 
105
735
  ```ts
106
736
  import { defineConfig } from '@ls-stack/fast-lint'
@@ -130,7 +760,8 @@ semantics: a pattern matching a directory (`src/gen`, `**/out`, `dist/`, `dist/*
130
760
  `!pattern` un-ignores files ignored by earlier patterns, but not files inside an ignored directory: to keep a file of
131
761
  an ignored directory, ignore its contents with `dir/**/*` instead. An override's `ignores` also accept `!pattern`,
132
762
  applied in order. Of the extglobs, only `@(a|b)` is supported (write the others with `{a,b}` or several globs). Rule
133
- names may carry a plugin prefix (`@ls-stack/strict-casting`), but not both forms of one rule in one block.
763
+ names may carry a plugin prefix (`@ls-stack/strict-casting`), but one block can't name a rule twice (two prefixes, or
764
+ `no-restricted-syntax` and its alias `advanced-no-restricted-syntax`).
134
765
 
135
766
  A script config's default export must be the config object, or a promise of it.
136
767
 
@@ -138,7 +769,10 @@ A script config's default export must be the config object, or a promise of it.
138
769
 
139
770
  `eslint-disable`, `eslint-enable`, `eslint-disable-line` and `eslint-disable-next-line` comments (and their
140
771
  `oxlint-` and `fast-lint-` forms) are honored, with or without a rule list and a `-- description`, so existing
141
- directives such as `// eslint-disable-next-line @ls-stack/improved-no-unnecessary-condition` keep working. They are
772
+ directives such as `// eslint-disable-next-line @ls-stack/improved-no-unnecessary-condition` keep working. Rule names
773
+ are matched without their prefix, so `eslint-core/no-restricted-syntax` and `eslint-core/naming-convention` (or
774
+ `no-restricted-syntax` meant for ESLint core) disable these rules, and so does the former name
775
+ `@ls-stack/advanced-no-restricted-syntax`. They are
142
776
  parsed like ESLint does: the comment text must start with the directive, `disable` and `enable` need a block comment
143
777
  (`/* */`), and a multi-line `disable-next-line` block comment applies to the line after the comment ends. Inline
144
778
  config comments (`/* eslint rule: "off" */`) are ignored.
@@ -164,10 +798,15 @@ reporting directives that are unused for the remaining ESLint rules.
164
798
  - `--fix` applies one pass of non-overlapping fixes (ESLint repeats up to 10 passes).
165
799
  - `improved-no-unnecessary-condition` reports nothing in a project without `strictNullChecks` (or `strict`), where
166
800
  every type may be nullish.
801
+ - `no-leaked-text-in-jsx` matches the raw JSX text, so HTML entities (`&amp;&amp;`) are not decoded.
167
802
  - Types come from tsrs (TypeScript 7.1-dev semantics); printed types in messages can differ slightly from TS 5.x.
168
803
  - Prebuilt binaries ship for macOS (arm64, x64), Linux (x64, arm64; glibc >= 2.17, not musl/Alpine) and Windows
169
804
  x64 (also used on Windows arm64, through emulation). Elsewhere, set `FAST_LINT_BINARY` to a `cargo build --release`
170
805
  binary. The launcher prints the package version for `--version`; the binary itself prints the crate version.
806
+ - The regex options of `no-unused-optional-args` (`ignoreArgsMatching`) and `no-unused-type-props-in-args`
807
+ (`forceCheckOnFCPropTypesWithName`) use Rust `regex` syntax, without lookaround or backreferences.
808
+ - The regex options of `no-unused-optional-args` (`ignoreArgsMatching`) and `no-unused-type-props-in-args`
809
+ (`forceCheckOnFCPropTypesWithName`) use Rust `regex` syntax, without lookaround or backreferences.
171
810
 
172
811
  ## Development
173
812
 
Binary file
Binary file
Binary file
Binary file
Binary file
package/lib/index.d.ts CHANGED
@@ -21,11 +21,233 @@ export type NoLeakedConditionalRenderingOptions = {
21
21
  reactVersion?: string
22
22
  }
23
23
 
24
+ export type NamingConventionCheck =
25
+ | boolean
26
+ | {
27
+ disallow?: string
28
+ message?: string
29
+ }
30
+
31
+ export type NamingConventionOptions = {
32
+ typeNames?: NamingConventionCheck
33
+ values?: NamingConventionCheck
34
+ }
35
+
36
+ type LiteralOptionValue = string | number | boolean
37
+
38
+ export type NoRestrictedSyntaxOptions = {
39
+ disallow?: {
40
+ selector: string
41
+ message: string
42
+ replace?: string | { regex: string; with: string }
43
+ replaceType?: 'suggestion' | 'autofix'
44
+ }[]
45
+ disallowFnCalls?: {
46
+ fn: string
47
+ withArgs?: { atIndex: number; value: LiteralOptionValue }[]
48
+ message: string
49
+ replaceWith?: string
50
+ ignoreRegex?: string
51
+ }[]
52
+ mustMatchSyntax?: {
53
+ includeRegex: string
54
+ excludeRegex?: string
55
+ mustCallFn?: {
56
+ anyCall: {
57
+ fn: string
58
+ withArgs: { atIndex: number; literal: LiteralOptionValue }[]
59
+ }[]
60
+ message?: string
61
+ }[]
62
+ mustMatchSelector?: { selector: string; message: string }[]
63
+ mustHaveExport?: {
64
+ name: string
65
+ type?: 'function' | 'variable' | 'any'
66
+ message: string
67
+ }[]
68
+ }[]
69
+ __dev_simulateFileName?: string
70
+ }
71
+
72
+ export type NoRelativeImportsOptions = {
73
+ aliases: { find: string; replacement: string }[]
74
+ rootDir?: string
75
+ allowNotFoundAliases?: boolean
76
+ _dev_simulateFileName?: string
77
+ }
78
+
79
+ export type NoStaticStylePropOptions = {
80
+ customMessage?: string
81
+ }
82
+
83
+ export type PreferNamedFunctionsOptions = {
84
+ ignoreRegex?: string
85
+ disallowArrowFnWithImplicitReturns?: boolean
86
+ }
87
+
88
+ export type NoCallWithExplicitGenericsOptions = {
89
+ functions: (string | { name: string; message: string })[]
90
+ }
91
+
92
+ export type NoCallWithInferredGenericsOptions = {
93
+ functions: {
94
+ name: string
95
+ minGenerics?: number
96
+ allowAny?: boolean
97
+ disallowTypes?: string[]
98
+ disallowTypeOf?: boolean
99
+ disallowUnknown?: boolean
100
+ }[]
101
+ disallowTypes?: string[]
102
+ anyAliases?: string[]
103
+ }
104
+
105
+ export type NoUnnecessaryTypingOptions = {
106
+ methods?: string[] | 'array'
107
+ }
108
+
109
+ export type NoTypeGuardsOptions = {
110
+ alternativeMsgs?: {
111
+ inArrayFind?: string
112
+ inArrayFilter?: string
113
+ }
114
+ }
115
+
116
+ export type UseStateSetterNamingOptions = {
117
+ ignorePrefixes?: string[]
118
+ }
119
+
120
+ export type RequireLazyExecutionOptions = {
121
+ functions?: string[]
122
+ }
123
+
124
+ export type RequireDescriptionDirective =
125
+ | 'eslint'
126
+ | 'eslint-disable'
127
+ | 'eslint-disable-line'
128
+ | 'eslint-disable-next-line'
129
+ | 'eslint-enable'
130
+ | 'eslint-env'
131
+ | 'oxlint-disable'
132
+ | 'oxlint-disable-line'
133
+ | 'oxlint-disable-next-line'
134
+ | 'oxlint-enable'
135
+ | 'fast-lint-disable'
136
+ | 'fast-lint-disable-line'
137
+ | 'fast-lint-disable-next-line'
138
+ | 'fast-lint-enable'
139
+ | 'exported'
140
+ | 'global'
141
+ | 'globals'
142
+
143
+ export type RequireDescriptionOptions = {
144
+ ignore?: RequireDescriptionDirective[]
145
+ }
146
+
147
+ export type ReactSingleExportOptions = {
148
+ extensions?: string[]
149
+ }
150
+
151
+ export type NoUnusedOptionalArgsOptions = {
152
+ ignoreArgsMatching?: string
153
+ }
154
+
155
+ export type NoUnusedTypePropsInArgsOptions = {
156
+ forceCheckOnFCPropTypesWithName?: string[]
157
+ alwaysCheckFunctionOptionTypes?: boolean
158
+ }
159
+
160
+ export type NoUnusedSelectedValuesOptions = {
161
+ selectors: {
162
+ name: string
163
+ selectorProp?: string
164
+ selectorArgPos?: number
165
+ returnProp?: string
166
+ }[]
167
+ }
168
+
169
+ export type NoRedundantFunctionParamsOptions = {
170
+ functions: { name: string; defaults: unknown[] }[]
171
+ }
172
+
173
+ export type ReactNoModuleLevelSingleUseValuesOptions = {
174
+ ignoreConstCaseVarRegex?: string
175
+ }
176
+
177
+ export type RequireReadsToVarPropOptions = {
178
+ varsToCheck: {
179
+ selector?: string
180
+ fromFnCall?: string
181
+ prop: string | string[]
182
+ errorMsg?: string
183
+ }[]
184
+ }
185
+
186
+ export type PreferReactHookAlternativeOptions = {
187
+ disallowedFunctions: {
188
+ name: string
189
+ allowUsingWithArgs?: boolean
190
+ hookAlternative?: string
191
+ message?: string
192
+ allowUseInside?: string[]
193
+ }[]
194
+ }
195
+
196
+ export type SortReactComponentsAndStylesOptions = {
197
+ mainComponentRegex?: string
198
+ mainComponentSelector?: string
199
+ }
200
+
201
+ export type UseTypesDirectlyAboveUsageOptions = {
202
+ checkOnly?: ('function-args' | 'FC' | 'generic-args-at-fn-calls')[]
203
+ checkTypesFromSelectors?: string[]
204
+ }
205
+
206
+ export type ReactCompilerExtraOptions = {
207
+ runOnlyWithEnableCompilerDirective?: boolean
208
+ disallowManualMemoization?: boolean
209
+ }
210
+
24
211
  type RuleOptions = {
25
212
  'improved-no-unnecessary-condition': ImprovedNoUnnecessaryConditionOptions
26
213
  'strict-casting': StrictCastingOptions
27
214
  'no-unnecessary-void-on-promise': Record<string, never>
28
215
  'no-leaked-conditional-rendering': NoLeakedConditionalRenderingOptions
216
+ 'no-duplicate-key': Record<string, never>
217
+ 'naming-convention': NamingConventionOptions
218
+ 'no-restricted-syntax': NoRestrictedSyntaxOptions
219
+ 'advanced-no-restricted-syntax': NoRestrictedSyntaxOptions
220
+ 'no-default-export': Record<string, never>
221
+ 'no-reexport': Record<string, never>
222
+ 'no-relative-imports': NoRelativeImportsOptions
223
+ 'no-sync-dynamic-import': Record<string, never>
224
+ 'no-leaked-text-in-jsx': Record<string, never>
225
+ 'no-static-style-prop': NoStaticStylePropOptions
226
+ 'no-non-camel-case-functions': Record<string, never>
227
+ 'prefer-named-functions': PreferNamedFunctionsOptions
228
+ 'no-call-with-explicit-generics': NoCallWithExplicitGenericsOptions
229
+ 'no-call-with-inferred-generics': NoCallWithInferredGenericsOptions
230
+ 'no-unnecessary-typing': NoUnnecessaryTypingOptions
231
+ 'no-type-guards': NoTypeGuardsOptions
232
+ 'use-state-setter-naming': UseStateSetterNamingOptions
233
+ 'require-lazy-execution': RequireLazyExecutionOptions
234
+ 'no-unnecessary-iife': Record<string, never>
235
+ 'require-description': RequireDescriptionOptions
236
+ 'no-write-only-ref': Record<string, never>
237
+ 'no-commented-out-code': Record<string, never>
238
+ 'react-single-export': ReactSingleExportOptions
239
+ 'no-unused-optional-args': NoUnusedOptionalArgsOptions
240
+ 'no-unused-type-props-in-args': NoUnusedTypePropsInArgsOptions
241
+ 'no-unused-t-state-field': Record<string, never>
242
+ 'no-unused-selected-values': NoUnusedSelectedValuesOptions
243
+ 'no-optional-root-props': Record<string, never>
244
+ 'no-redundant-function-params': NoRedundantFunctionParamsOptions
245
+ 'react-no-module-level-single-use-values': ReactNoModuleLevelSingleUseValuesOptions
246
+ 'require-reads-to-var-prop': RequireReadsToVarPropOptions
247
+ 'prefer-react-hook-alternative': PreferReactHookAlternativeOptions
248
+ 'sort-react-components-and-styles': SortReactComponentsAndStylesOptions
249
+ 'use-types-directly-above-usage': UseTypesDirectlyAboveUsageOptions
250
+ 'react-compiler-extra': ReactCompilerExtraOptions
29
251
  }
30
252
 
31
253
  export type RulesConfig = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ls-stack/fast-lint",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "license": "MIT",
5
5
  "description": "Type-aware lint rules of @ls-stack/extended-lint running natively on tsrs, the Rust port of the TypeScript checker",
6
6
  "type": "module",