@ls-stack/fast-lint 0.0.0-stage → 0.3.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 +821 -2
- package/bin/fast-lint.mjs +182 -0
- package/dist/darwin-arm64/fast-lint +0 -0
- package/dist/darwin-x64/fast-lint +0 -0
- package/dist/linux-arm64/fast-lint +0 -0
- package/dist/linux-x64/fast-lint +0 -0
- package/dist/win32-x64/fast-lint.exe +0 -0
- package/lib/index.d.ts +272 -0
- package/lib/index.mjs +3 -0
- package/package.json +33 -3
package/README.md
CHANGED
|
@@ -1,3 +1,822 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ls-stack/fast-lint
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Type-aware lint rules running natively on [tsrs](https://github.com/maschwenk/tsrs), the Rust port of the TypeScript 7
|
|
4
|
+
checker, through [tsrslint](https://github.com/maschwenk/tsrslint)'s rule framework. No ESLint, no Node type checker:
|
|
5
|
+
only these rules run, against one shared program per tsconfig. Type-aware rules live here, not in
|
|
6
|
+
[`@ls-stack/extended-lint`](../extended-lint).
|
|
7
|
+
|
|
8
|
+
## Rules
|
|
9
|
+
|
|
10
|
+
| rule | origin |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| `improved-no-unnecessary-condition` | moved from `@ls-stack/extended-lint` |
|
|
13
|
+
| `strict-casting` | moved from `@ls-stack/extended-lint` (see below) |
|
|
14
|
+
| `no-unnecessary-void-on-promise` | moved from `@ls-stack/extended-lint` |
|
|
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) |
|
|
50
|
+
|
|
51
|
+
Options are the same as the former ESLint rules' (see `lib/index.d.ts`). Rule names may carry a plugin prefix, so
|
|
52
|
+
existing `@ls-stack/strict-casting` configs and disable comments keep working.
|
|
53
|
+
|
|
54
|
+
### `strict-casting`
|
|
55
|
+
|
|
56
|
+
Enforces safe and necessary `String()`/`Number()` casts.
|
|
57
|
+
|
|
58
|
+
Checks performed:
|
|
59
|
+
|
|
60
|
+
- **Unnecessary casts** (auto-fixable): casting a value that is already of the target type
|
|
61
|
+
- **Nullish casts**: `String(value)` produces the strings `"null"`/`"undefined"`, and `Number(value)` produces `0` for `null` and `NaN` for `undefined`. A suggestion is provided to add a `?? ''`/`?? 0` fallback
|
|
62
|
+
- **Invalid `Number()` casts**: casting a `symbol` (throws at runtime) or an object without a numeric `valueOf` (always results in `NaN`)
|
|
63
|
+
- **`any`/`unknown` casts**: casting untyped values is unsafe, type or narrow the value before casting
|
|
64
|
+
|
|
65
|
+
Bad:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
declare const str: string
|
|
69
|
+
declare const nullable: string | null
|
|
70
|
+
declare const obj: { a: string }
|
|
71
|
+
|
|
72
|
+
String(str) // Error: unnecessary cast
|
|
73
|
+
String(nullable) // Error: may produce "null"
|
|
74
|
+
Number(obj) // Error: results in NaN
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Good:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
String(num)
|
|
81
|
+
String(nullable ?? '')
|
|
82
|
+
Number(date) // Date has a numeric valueOf
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Options:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
type Options = [
|
|
89
|
+
{
|
|
90
|
+
/** Extra functions checked for unnecessary casts */
|
|
91
|
+
additionalCastFunctions?: { name: string; expectedType: 'string' | 'number' }[]
|
|
92
|
+
/** Allow casting possibly nullish values (default: false) */
|
|
93
|
+
allowNullishCasting?: boolean
|
|
94
|
+
/** Allow symbol/object Number() casts (default: false) */
|
|
95
|
+
allowInvalidCasting?: boolean
|
|
96
|
+
/** Allow casting `any` values (default: false) */
|
|
97
|
+
allowAnyCasting?: boolean
|
|
98
|
+
/** Allow casting `unknown` values, e.g. `String(err)` in catch blocks (default: false) */
|
|
99
|
+
allowUnknownCasting?: boolean
|
|
100
|
+
},
|
|
101
|
+
]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
For `String(obj)` producing `"[object Object]"`, use `@typescript-eslint/no-base-to-string`, which covers that case well.
|
|
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
|
+
- functions starting with the `"use no memo"` directive are skipped
|
|
687
|
+
|
|
688
|
+
```ts
|
|
689
|
+
type Options = [
|
|
690
|
+
{
|
|
691
|
+
/** Only run in files with an `eslint react-compiler/react-compiler: ["error"` comment (default: false) */
|
|
692
|
+
runOnlyWithEnableCompilerDirective?: boolean
|
|
693
|
+
},
|
|
694
|
+
]
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
## Usage
|
|
698
|
+
|
|
699
|
+
```sh
|
|
700
|
+
fast-lint # every file of ./tsconfig.json
|
|
701
|
+
fast-lint src/feature # only files under these paths
|
|
702
|
+
fast-lint --fix # apply autofixes (strict-casting's unnecessary casts)
|
|
703
|
+
fast-lint --format json # stylish (default), compact or json, in ESLint's formats
|
|
704
|
+
fast-lint --max-warnings 0 --timing # --max-warnings -1: no limit
|
|
705
|
+
fast-lint -p packages/app # a tsconfig, or a directory with a tsconfig.json
|
|
706
|
+
fast-lint --color -- -odd-name.ts # force colors; `--` ends the options
|
|
707
|
+
fast-lint --report-unused-disable-directives # warn about fast-lint-disable comments that suppress nothing
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
A solution-style tsconfig (empty `files` plus `references`) lints its referenced projects; a file in several
|
|
711
|
+
projects is linted once.
|
|
712
|
+
|
|
713
|
+
Paths are printed relative to the current directory. A file with a syntax error gets a single `Parsing error`
|
|
714
|
+
instead of rule diagnostics. `--quiet` hides warnings and doesn't apply their fixes; like ESLint, it skips `warn` rules
|
|
715
|
+
entirely unless `--max-warnings` is given, in which case warnings still count. `--format json` prints ESLint's JSON
|
|
716
|
+
result shape, listing only files with problems or fixes, with the file's `source` when it has problems and wasn't
|
|
717
|
+
fixed.
|
|
718
|
+
|
|
719
|
+
Exit code: `0` clean, `1` errors (or more warnings than `--max-warnings`), `2` fatal error: a tsconfig with a missing
|
|
720
|
+
`files` entry or `extends` target, no files to lint, a path argument that matches no project file, a directory
|
|
721
|
+
argument whose files are all ignored, or a fixed file that couldn't be written (reported after the results). A file
|
|
722
|
+
argument that is ignored, a declaration file or in `node_modules` is skipped with a warning, and so is fixing a file
|
|
723
|
+
that isn't UTF-8 (its problems are still reported).
|
|
724
|
+
|
|
725
|
+
## Config
|
|
726
|
+
|
|
727
|
+
`fast-lint.config.ts` (or `.mts`/`.mjs`/`.js`, evaluated by the Node launcher) or `fast-lint.config.json`, in the
|
|
728
|
+
current directory or passed with `--config`. Without a config the type-aware rules (`improved-no-unnecessary-condition`,
|
|
729
|
+
`strict-casting`, `no-unnecessary-void-on-promise` and `no-leaked-conditional-rendering`) run as `error` with default
|
|
730
|
+
options; every other rule only runs when configured.
|
|
731
|
+
|
|
732
|
+
```ts
|
|
733
|
+
import { defineConfig } from '@ls-stack/fast-lint'
|
|
734
|
+
|
|
735
|
+
export default defineConfig({
|
|
736
|
+
projects: ['tsconfig.json'],
|
|
737
|
+
ignores: ['dist/**'],
|
|
738
|
+
rules: {
|
|
739
|
+
'improved-no-unnecessary-condition': ['error', { alternativeForInCondition: 'use objectHasKey' }],
|
|
740
|
+
'strict-casting': ['warn', { additionalCastFunctions: [{ name: 'castToString', expectedType: 'string' }] }],
|
|
741
|
+
'no-unnecessary-void-on-promise': 'error',
|
|
742
|
+
},
|
|
743
|
+
overrides: [
|
|
744
|
+
{
|
|
745
|
+
files: ['src/**'],
|
|
746
|
+
ignores: ['**/*.test.ts'],
|
|
747
|
+
rules: { 'strict-casting': 'error', 'no-leaked-conditional-rendering': 'error' },
|
|
748
|
+
},
|
|
749
|
+
],
|
|
750
|
+
reportUnusedDisableDirectives: true,
|
|
751
|
+
})
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
Overrides apply in order, like ESLint flat config blocks: a severity-only entry keeps the options set by an earlier
|
|
755
|
+
block. Globs are relative to the config file's directory (`../` for files outside it). Global `ignores` follow ESLint's
|
|
756
|
+
semantics: a pattern matching a directory (`src/gen`, `**/out`, `dist/`, `dist/**`) ignores everything in it, and
|
|
757
|
+
`!pattern` un-ignores files ignored by earlier patterns, but not files inside an ignored directory: to keep a file of
|
|
758
|
+
an ignored directory, ignore its contents with `dir/**/*` instead. An override's `ignores` also accept `!pattern`,
|
|
759
|
+
applied in order. Of the extglobs, only `@(a|b)` is supported (write the others with `{a,b}` or several globs). Rule
|
|
760
|
+
names may carry a plugin prefix (`@ls-stack/strict-casting`), but one block can't name a rule twice (two prefixes, or
|
|
761
|
+
`no-restricted-syntax` and its alias `advanced-no-restricted-syntax`).
|
|
762
|
+
|
|
763
|
+
A script config's default export must be the config object, or a promise of it.
|
|
764
|
+
|
|
765
|
+
## Disable comments
|
|
766
|
+
|
|
767
|
+
`eslint-disable`, `eslint-enable`, `eslint-disable-line` and `eslint-disable-next-line` comments (and their
|
|
768
|
+
`oxlint-` and `fast-lint-` forms) are honored, with or without a rule list and a `-- description`, so existing
|
|
769
|
+
directives such as `// eslint-disable-next-line @ls-stack/improved-no-unnecessary-condition` keep working. Rule names
|
|
770
|
+
are matched without their prefix, so `eslint-core/no-restricted-syntax` and `eslint-core/naming-convention` (or
|
|
771
|
+
`no-restricted-syntax` meant for ESLint core) disable these rules, and so does the former name
|
|
772
|
+
`@ls-stack/advanced-no-restricted-syntax`. They are
|
|
773
|
+
parsed like ESLint does: the comment text must start with the directive, `disable` and `enable` need a block comment
|
|
774
|
+
(`/* */`), and a multi-line `disable-next-line` block comment applies to the line after the comment ends. Inline
|
|
775
|
+
config comments (`/* eslint rule: "off" */`) are ignored.
|
|
776
|
+
|
|
777
|
+
`--report-unused-disable-directives` (or `reportUnusedDisableDirectives: true` in the config) warns about
|
|
778
|
+
`fast-lint-disable*` directives that suppressed nothing, like ESLint's option of the same name: a directive without a
|
|
779
|
+
rule list when no problem was suppressed, else each listed rule that ran on the file and reported nothing there. Only
|
|
780
|
+
`fast-lint-` directives are checked, since `eslint-` and `oxlint-` ones may belong to rules of those linters. The
|
|
781
|
+
warnings have no rule id and no fix. Under `--quiet` they are hidden, and they aren't computed unless
|
|
782
|
+
`--max-warnings` is given (warn rules don't run then, so their directives would look unused).
|
|
783
|
+
|
|
784
|
+
### Running alongside ESLint
|
|
785
|
+
|
|
786
|
+
When these rules are moved out of ESLint (set to `off` there, or their plugin removed), ESLint sees their
|
|
787
|
+
`eslint-disable` comments as unused (`reportUnusedDisableDirectives`, a warning by default in ESLint 9), and
|
|
788
|
+
`eslint --fix` deletes them, or as errors for an unknown rule when the plugin isn't loaded. Then fast-lint reports the
|
|
789
|
+
problems again. Either write these rules' directives as `fast-lint-disable*` (or `oxlint-disable*`), which ESLint
|
|
790
|
+
ignores, or set `linterOptions: { reportUnusedDisableDirectives: 'off' }` in the ESLint config, which also stops
|
|
791
|
+
reporting directives that are unused for the remaining ESLint rules.
|
|
792
|
+
|
|
793
|
+
## Differences from ESLint
|
|
794
|
+
|
|
795
|
+
- `--fix` applies one pass of non-overlapping fixes (ESLint repeats up to 10 passes).
|
|
796
|
+
- `improved-no-unnecessary-condition` reports nothing in a project without `strictNullChecks` (or `strict`), where
|
|
797
|
+
every type may be nullish.
|
|
798
|
+
- `no-leaked-text-in-jsx` matches the raw JSX text, so HTML entities (`&&`) are not decoded.
|
|
799
|
+
- Types come from tsrs (TypeScript 7.1-dev semantics); printed types in messages can differ slightly from TS 5.x.
|
|
800
|
+
- Prebuilt binaries ship for macOS (arm64, x64), Linux (x64, arm64; glibc >= 2.17, not musl/Alpine) and Windows
|
|
801
|
+
x64 (also used on Windows arm64, through emulation). Elsewhere, set `FAST_LINT_BINARY` to a `cargo build --release`
|
|
802
|
+
binary. The launcher prints the package version for `--version`; the binary itself prints the crate version.
|
|
803
|
+
- The regex options of `no-unused-optional-args` (`ignoreArgsMatching`) and `no-unused-type-props-in-args`
|
|
804
|
+
(`forceCheckOnFCPropTypesWithName`) use Rust `regex` syntax, without lookaround or backreferences.
|
|
805
|
+
- The regex options of `no-unused-optional-args` (`ignoreArgsMatching`) and `no-unused-type-props-in-args`
|
|
806
|
+
(`forceCheckOnFCPropTypesWithName`) use Rust `regex` syntax, without lookaround or backreferences.
|
|
807
|
+
|
|
808
|
+
## Development
|
|
809
|
+
|
|
810
|
+
```sh
|
|
811
|
+
cargo test # rule tests (tests/*.rs)
|
|
812
|
+
cargo clippy --all-targets
|
|
813
|
+
node scripts/build.mjs # release binaries for every platform into dist/<platform>-<arch>/
|
|
814
|
+
node scripts/build.mjs --clean linux-x64 win32-x64 # some platforms, deleting their build dirs afterwards
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
Cross-compiling from macOS needs the Rust targets (`rustup target add x86_64-apple-darwin
|
|
818
|
+
x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu x86_64-pc-windows-msvc`), `zig` + `cargo-zigbuild` for Linux, and
|
|
819
|
+
`cargo-xwin` + Homebrew `llvm` (for `llvm-lib`) for Windows. Each platform takes about 2 minutes (fat LTO).
|
|
820
|
+
|
|
821
|
+
The first build fetches tsrslint's git dependency, including its `typescript-go` submodule (~2 GB, cached in
|
|
822
|
+
`~/.cargo/git`). tsrslint is pinned by commit in `Cargo.toml`; `Cargo.lock` pins the tsrs commit it was tested with.
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process'
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
4
|
+
import { constants } from 'node:os'
|
|
5
|
+
import { dirname, resolve } from 'node:path'
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
7
|
+
|
|
8
|
+
const scriptConfigNames = [
|
|
9
|
+
'fast-lint.config.ts',
|
|
10
|
+
'fast-lint.config.mts',
|
|
11
|
+
'fast-lint.config.mjs',
|
|
12
|
+
'fast-lint.config.js',
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
const scriptConfigExtensions = ['.ts', '.mts', '.cts', '.mjs', '.cjs', '.js']
|
|
16
|
+
|
|
17
|
+
function binaryPath() {
|
|
18
|
+
if (process.env.FAST_LINT_BINARY) return process.env.FAST_LINT_BINARY
|
|
19
|
+
|
|
20
|
+
const platform = `${process.platform}-${process.arch}`
|
|
21
|
+
const exe = process.platform === 'win32' ? 'fast-lint.exe' : 'fast-lint'
|
|
22
|
+
const candidates =
|
|
23
|
+
platform === 'win32-arm64' ? [platform, 'win32-x64'] : [platform]
|
|
24
|
+
|
|
25
|
+
for (const candidate of candidates) {
|
|
26
|
+
const path = fileURLToPath(
|
|
27
|
+
new URL(`../dist/${candidate}/${exe}`, import.meta.url),
|
|
28
|
+
)
|
|
29
|
+
if (existsSync(path)) return path
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
throw new Error(
|
|
33
|
+
`no prebuilt binary for ${platform}. Build one with \`cargo build --release\` in packages/fast-lint and point FAST_LINT_BINARY at it`,
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function findConfigFlag(args) {
|
|
38
|
+
let found
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < args.length; i++) {
|
|
41
|
+
const arg = args[i]
|
|
42
|
+
if (arg === undefined) continue
|
|
43
|
+
|
|
44
|
+
if (arg === '-c' || arg === '--config') {
|
|
45
|
+
found = { index: i, length: 2, path: args[i + 1] }
|
|
46
|
+
i++
|
|
47
|
+
} else if (arg.startsWith('--config=')) {
|
|
48
|
+
found = { index: i, length: 1, path: arg.slice('--config='.length) }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return found
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isScriptConfig(path) {
|
|
56
|
+
return scriptConfigExtensions.some((ext) => path.endsWith(ext))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function loadScriptConfig(path) {
|
|
60
|
+
const module = await import(pathToFileURL(path).href)
|
|
61
|
+
|
|
62
|
+
if (!('default' in module)) {
|
|
63
|
+
throw new Error(`${path} has no default export`)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const config = await module.default
|
|
67
|
+
|
|
68
|
+
if (typeof config !== 'object' || config === null || Array.isArray(config)) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`${path}: the default export must be a config object or a promise of one`,
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return JSON.stringify(config)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function hasInlineConfig(args) {
|
|
78
|
+
return args.some(
|
|
79
|
+
(arg) => arg === '--config-json' || arg.startsWith('--config-json='),
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function packageVersion() {
|
|
84
|
+
const packageJson = JSON.parse(
|
|
85
|
+
readFileSync(new URL('../package.json', import.meta.url), 'utf8'),
|
|
86
|
+
)
|
|
87
|
+
return packageJson.version
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function resolveArgs(args) {
|
|
91
|
+
if (hasInlineConfig(args)) return args
|
|
92
|
+
|
|
93
|
+
const flag = findConfigFlag(args)
|
|
94
|
+
|
|
95
|
+
if (flag) {
|
|
96
|
+
if (!flag.path || !isScriptConfig(flag.path)) return args
|
|
97
|
+
|
|
98
|
+
const path = resolve(flag.path)
|
|
99
|
+
const rest = [...args]
|
|
100
|
+
rest.splice(flag.index, flag.length)
|
|
101
|
+
|
|
102
|
+
return [
|
|
103
|
+
...rest,
|
|
104
|
+
'--config-json',
|
|
105
|
+
await loadScriptConfig(path),
|
|
106
|
+
'--config-root',
|
|
107
|
+
dirname(path),
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const found = scriptConfigNames
|
|
112
|
+
.map((name) => resolve(name))
|
|
113
|
+
.find((path) => existsSync(path))
|
|
114
|
+
|
|
115
|
+
if (!found) return args
|
|
116
|
+
|
|
117
|
+
return [
|
|
118
|
+
...args,
|
|
119
|
+
'--config-json',
|
|
120
|
+
await loadScriptConfig(found),
|
|
121
|
+
'--config-root',
|
|
122
|
+
dirname(found),
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const rawArgs = process.argv.slice(2)
|
|
127
|
+
|
|
128
|
+
if (rawArgs.includes('-v') || rawArgs.includes('--version')) {
|
|
129
|
+
console.log(packageVersion())
|
|
130
|
+
process.exit(0)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const isHelp = rawArgs.includes('-h') || rawArgs.includes('--help')
|
|
134
|
+
|
|
135
|
+
let exe
|
|
136
|
+
let args
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
exe = binaryPath()
|
|
140
|
+
args = isHelp ? rawArgs : await resolveArgs(rawArgs)
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error(
|
|
143
|
+
`fast-lint: ${error instanceof Error ? error.message : String(error)}`,
|
|
144
|
+
)
|
|
145
|
+
process.exit(2)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const child = spawn(exe, args, { stdio: 'inherit' })
|
|
149
|
+
|
|
150
|
+
const signals = ['SIGINT', 'SIGTERM', 'SIGHUP']
|
|
151
|
+
|
|
152
|
+
for (const signal of signals) {
|
|
153
|
+
process.on(signal, () => child.kill(signal))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
child.on('error', (error) => {
|
|
157
|
+
const hint =
|
|
158
|
+
process.platform === 'linux' &&
|
|
159
|
+
!process.env.FAST_LINT_BINARY &&
|
|
160
|
+
'code' in error &&
|
|
161
|
+
error.code === 'ENOENT'
|
|
162
|
+
? ' (the prebuilt Linux binaries need glibc >= 2.17; on musl/Alpine point FAST_LINT_BINARY at a binary built there)'
|
|
163
|
+
: ''
|
|
164
|
+
console.error(`fast-lint: failed to run ${exe}: ${error.message}${hint}`)
|
|
165
|
+
process.exit(2)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
child.on('exit', (code, signal) => {
|
|
169
|
+
if (signal) {
|
|
170
|
+
process.exitCode = 128 + (constants.signals[signal] ?? 0)
|
|
171
|
+
|
|
172
|
+
if (signals.includes(signal)) {
|
|
173
|
+
process.removeAllListeners(signal)
|
|
174
|
+
process.kill(process.pid, signal)
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
process.exit()
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
process.exit(code ?? 2)
|
|
182
|
+
})
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
export type Severity = 'off' | 'warn' | 'error' | 0 | 1 | 2
|
|
2
|
+
|
|
3
|
+
type RuleEntry<Options> = Severity | [Severity] | [Severity, Options]
|
|
4
|
+
|
|
5
|
+
export type ImprovedNoUnnecessaryConditionOptions = {
|
|
6
|
+
alternativeForInCondition?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type StrictCastingOptions = {
|
|
10
|
+
additionalCastFunctions?: {
|
|
11
|
+
name: string
|
|
12
|
+
expectedType: 'string' | 'number'
|
|
13
|
+
}[]
|
|
14
|
+
allowNullishCasting?: boolean
|
|
15
|
+
allowInvalidCasting?: boolean
|
|
16
|
+
allowAnyCasting?: boolean
|
|
17
|
+
allowUnknownCasting?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type NoLeakedConditionalRenderingOptions = {
|
|
21
|
+
reactVersion?: string
|
|
22
|
+
}
|
|
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
|
+
}
|
|
209
|
+
|
|
210
|
+
type RuleOptions = {
|
|
211
|
+
'improved-no-unnecessary-condition': ImprovedNoUnnecessaryConditionOptions
|
|
212
|
+
'strict-casting': StrictCastingOptions
|
|
213
|
+
'no-unnecessary-void-on-promise': Record<string, never>
|
|
214
|
+
'no-leaked-conditional-rendering': NoLeakedConditionalRenderingOptions
|
|
215
|
+
'no-duplicate-key': Record<string, never>
|
|
216
|
+
'naming-convention': NamingConventionOptions
|
|
217
|
+
'no-restricted-syntax': NoRestrictedSyntaxOptions
|
|
218
|
+
'advanced-no-restricted-syntax': NoRestrictedSyntaxOptions
|
|
219
|
+
'no-default-export': Record<string, never>
|
|
220
|
+
'no-reexport': Record<string, never>
|
|
221
|
+
'no-relative-imports': NoRelativeImportsOptions
|
|
222
|
+
'no-sync-dynamic-import': Record<string, never>
|
|
223
|
+
'no-leaked-text-in-jsx': Record<string, never>
|
|
224
|
+
'no-static-style-prop': NoStaticStylePropOptions
|
|
225
|
+
'no-non-camel-case-functions': Record<string, never>
|
|
226
|
+
'prefer-named-functions': PreferNamedFunctionsOptions
|
|
227
|
+
'no-call-with-explicit-generics': NoCallWithExplicitGenericsOptions
|
|
228
|
+
'no-call-with-inferred-generics': NoCallWithInferredGenericsOptions
|
|
229
|
+
'no-unnecessary-typing': NoUnnecessaryTypingOptions
|
|
230
|
+
'no-type-guards': NoTypeGuardsOptions
|
|
231
|
+
'use-state-setter-naming': UseStateSetterNamingOptions
|
|
232
|
+
'require-lazy-execution': RequireLazyExecutionOptions
|
|
233
|
+
'no-unnecessary-iife': Record<string, never>
|
|
234
|
+
'require-description': RequireDescriptionOptions
|
|
235
|
+
'no-write-only-ref': Record<string, never>
|
|
236
|
+
'no-commented-out-code': Record<string, never>
|
|
237
|
+
'react-single-export': ReactSingleExportOptions
|
|
238
|
+
'no-unused-optional-args': NoUnusedOptionalArgsOptions
|
|
239
|
+
'no-unused-type-props-in-args': NoUnusedTypePropsInArgsOptions
|
|
240
|
+
'no-unused-t-state-field': Record<string, never>
|
|
241
|
+
'no-unused-selected-values': NoUnusedSelectedValuesOptions
|
|
242
|
+
'no-optional-root-props': Record<string, never>
|
|
243
|
+
'no-redundant-function-params': NoRedundantFunctionParamsOptions
|
|
244
|
+
'react-no-module-level-single-use-values': ReactNoModuleLevelSingleUseValuesOptions
|
|
245
|
+
'require-reads-to-var-prop': RequireReadsToVarPropOptions
|
|
246
|
+
'prefer-react-hook-alternative': PreferReactHookAlternativeOptions
|
|
247
|
+
'sort-react-components-and-styles': SortReactComponentsAndStylesOptions
|
|
248
|
+
'use-types-directly-above-usage': UseTypesDirectlyAboveUsageOptions
|
|
249
|
+
'react-compiler-extra': ReactCompilerExtraOptions
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export type RulesConfig = {
|
|
253
|
+
[Name in keyof RuleOptions as Name | `${string}/${Name}`]?: RuleEntry<
|
|
254
|
+
RuleOptions[Name]
|
|
255
|
+
>
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export type Override = {
|
|
259
|
+
files: string[]
|
|
260
|
+
ignores?: string[]
|
|
261
|
+
rules?: RulesConfig
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export type FastLintConfig = {
|
|
265
|
+
projects?: string[]
|
|
266
|
+
ignores?: string[]
|
|
267
|
+
rules?: RulesConfig
|
|
268
|
+
overrides?: Override[]
|
|
269
|
+
reportUnusedDisableDirectives?: boolean
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function defineConfig(config: FastLintConfig): FastLintConfig
|
package/lib/index.mjs
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ls-stack/fast-lint",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Type-aware lint rules of @ls-stack/extended-lint running natively on tsrs, the Rust port of the TypeScript checker",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"fast-lint": "./bin/fast-lint.mjs"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./lib/index.d.ts",
|
|
13
|
+
"default": "./lib/index.mjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"bin",
|
|
18
|
+
"lib",
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "github:lucaslos/eslint-plugin-extended-lint"
|
|
24
|
+
},
|
|
25
|
+
"author": "Lucas Santos",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=22.18.0"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"test": "cargo test",
|
|
31
|
+
"lint": "cargo fmt --check && cargo clippy --all-targets -- -D warnings",
|
|
32
|
+
"build": "pnpm test && pnpm lint && pnpm run build:no-test",
|
|
33
|
+
"build:no-test": "node scripts/build.mjs",
|
|
34
|
+
"pre-publish": "pnpm build"
|
|
35
|
+
}
|
|
6
36
|
}
|