eslint-plugin-flawless 1.2.1 → 1.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 +3 -0
- package/dist/index.d.mts +59 -16
- package/dist/index.mjs +1 -1
- package/dist/oxlint.mjs +1 -1
- package/dist/{plugin-DfRbpogP.mjs → plugin-BVNImiZB.mjs} +625 -91
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -205,12 +205,15 @@ Requires [type information](https://typescript-eslint.io/linting/typed-linting).
|
|
|
205
205
|
| [jsx-shorthand-fragment](src/rules/jsx-shorthand-fragment/documentation.md) | Enforce a consistent fragment form: the shorthand `<>...</>` or a named fragment | 🔧 | |
|
|
206
206
|
| [max-lines-per-function](src/rules/max-lines-per-function/documentation.md) | Enforce a maximum number of lines of code in a function | | |
|
|
207
207
|
| [naming-convention](src/rules/naming-convention/documentation.md) | Enforce naming conventions for everything across a codebase | | 💭 |
|
|
208
|
+
| [no-conditional-in-test](src/rules/no-conditional-in-test/documentation.md) | Disallow conditional logic in tests | 🔧 | |
|
|
208
209
|
| [no-export-default-arrow](src/rules/no-export-default-arrow/documentation.md) | Disallow anonymous arrow functions as export default declarations | 🔧 | |
|
|
209
210
|
| [no-redundant-tsconfig-options](src/rules/no-redundant-tsconfig-options/documentation.md) | Disallow tsconfig options that redundantly re-set a value already provided by an extended config | 🔧 | |
|
|
210
211
|
| [no-unnecessary-use-callback](src/rules/no-unnecessary-use-callback/documentation.md) | Disallow unnecessary usage of 'useCallback' | | |
|
|
211
212
|
| [no-unnecessary-use-memo](src/rules/no-unnecessary-use-memo/documentation.md) | Disallow unnecessary usage of 'useMemo' | | |
|
|
212
213
|
| [padding-after-expect-assertions](src/rules/padding-after-expect-assertions/documentation.md) | Enforce a blank line after `expect.assertions` and `expect.hasAssertions` | 🔧 | |
|
|
213
214
|
| [prefer-destructuring-assignment](src/rules/prefer-destructuring-assignment/documentation.md) | Enforce destructuring assignment for component props | 🔧 | |
|
|
215
|
+
| [prefer-ending-with-an-expect](src/rules/prefer-ending-with-an-expect/documentation.md) | Prefer having the last statement in a test be an assertion | | |
|
|
216
|
+
| [prefer-expect-assertions-count](src/rules/prefer-expect-assertions-count/documentation.md) | Prefer `expect.assertions(<count>)` over `expect.hasAssertions()` | | |
|
|
214
217
|
| [prefer-parameter-destructuring](src/rules/prefer-parameter-destructuring/documentation.md) | Enforce destructuring parameters in the function signature | 🔧 | |
|
|
215
218
|
| [prefer-read-only-props](src/rules/prefer-read-only-props/documentation.md) | Enforce that function component props are read-only | 🔧 | 💭 |
|
|
216
219
|
| [purity](src/rules/purity/documentation.md) | Disallow impure calls such as `math.random` or `os.clock` during render | | |
|
package/dist/index.d.mts
CHANGED
|
@@ -51,7 +51,7 @@ declare const EXPLICIT = "useExplicitReturn";
|
|
|
51
51
|
declare const COMPLEX_EXPLICIT = "useExplicitReturnComplex";
|
|
52
52
|
type MessageIds$5 = typeof COMPLEX_EXPLICIT | typeof EXPLICIT | typeof IMPLICIT;
|
|
53
53
|
type ObjectReturnStyle = "always-explicit" | "complex-explicit" | "off";
|
|
54
|
-
type Options$
|
|
54
|
+
type Options$8 = [{
|
|
55
55
|
/** Always use explicit returns for JSX bodies. */
|
|
56
56
|
jsxAlwaysUseExplicitReturn?: boolean;
|
|
57
57
|
/** Maximum emitted line length (tab-expanded) before requiring an explicit return. */
|
|
@@ -86,7 +86,7 @@ interface JsxShorthandFragmentOptions {
|
|
|
86
86
|
*/
|
|
87
87
|
readonly mode?: Mode;
|
|
88
88
|
}
|
|
89
|
-
type Options$
|
|
89
|
+
type Options$7 = [JsxShorthandFragmentOptions?];
|
|
90
90
|
type Mode = "element" | "syntax";
|
|
91
91
|
//#endregion
|
|
92
92
|
//#region src/rules/max-lines-per-function/rule.d.ts
|
|
@@ -119,7 +119,7 @@ interface MaxLinesPerFunctionOptions {
|
|
|
119
119
|
*/
|
|
120
120
|
readonly skipComments?: boolean;
|
|
121
121
|
}
|
|
122
|
-
type Options$
|
|
122
|
+
type Options$6 = [MaxLinesPerFunctionOptions?];
|
|
123
123
|
//#endregion
|
|
124
124
|
//#region src/rules/naming-convention/utils/enums.d.ts
|
|
125
125
|
declare const Selector: {
|
|
@@ -246,7 +246,23 @@ interface NamingSelector {
|
|
|
246
246
|
//#endregion
|
|
247
247
|
//#region src/rules/naming-convention/rule.d.ts
|
|
248
248
|
type MessageIds$3 = "doesNotMatchFormat" | "doesNotMatchFormatForeignContract" | "doesNotMatchFormatTrimmed" | "doesNotMatchFormatTrimmedForeignContract" | "missingAffix" | "missingAffixForeignContract" | "missingUnderscore" | "missingUnderscoreForeignContract" | "satisfyCustom" | "satisfyCustomForeignContract" | "unexpectedUnderscore" | "unexpectedUnderscoreForeignContract";
|
|
249
|
-
type Options$
|
|
249
|
+
type Options$5 = Array<NamingSelector>;
|
|
250
|
+
//#endregion
|
|
251
|
+
//#region src/rules/no-conditional-in-test/rule.d.ts
|
|
252
|
+
interface NoConditionalInTestOptions {
|
|
253
|
+
/**
|
|
254
|
+
* Callee names, in addition to a resolved vitest `it`/`test`, whose call is
|
|
255
|
+
* treated as a test block. Matched by exact dotted name (no scope resolution),
|
|
256
|
+
* for libraries with custom test blocks such as `myTest` or `each.test`.
|
|
257
|
+
*/
|
|
258
|
+
readonly additionalTestBlockFunctions?: ReadonlyArray<string>;
|
|
259
|
+
/**
|
|
260
|
+
* Whether optional chaining (`?.`) is allowed in tests. When `false`, optional
|
|
261
|
+
* chains are reported and auto-fixed to a non-null assertion (`a?.b` -> `a!.b`).
|
|
262
|
+
*/
|
|
263
|
+
readonly allowOptionalChaining?: boolean;
|
|
264
|
+
}
|
|
265
|
+
type Options$4 = [NoConditionalInTestOptions?];
|
|
250
266
|
//#endregion
|
|
251
267
|
//#region src/rules/no-unnecessary-use-callback/rule.d.ts
|
|
252
268
|
declare const MESSAGE_ID_DEFAULT$1 = "default";
|
|
@@ -258,6 +274,24 @@ declare const MESSAGE_ID_DEFAULT = "default";
|
|
|
258
274
|
declare const MESSAGE_ID_INSIDE_USE_EFFECT = "noUnnecessaryUseMemoInsideUseEffect";
|
|
259
275
|
type MessageIds$1 = typeof MESSAGE_ID_DEFAULT | typeof MESSAGE_ID_INSIDE_USE_EFFECT;
|
|
260
276
|
//#endregion
|
|
277
|
+
//#region src/rules/prefer-ending-with-an-expect/rule.d.ts
|
|
278
|
+
interface PreferEndingWithAnExpectOptions {
|
|
279
|
+
/**
|
|
280
|
+
* Callee names, in addition to a resolved vitest `it`/`test`, whose second
|
|
281
|
+
* argument is treated as a test body. Matched by exact dotted name (no scope
|
|
282
|
+
* resolution), for libraries with custom test blocks such as `each.test`.
|
|
283
|
+
*/
|
|
284
|
+
readonly additionalTestBlockFunctions?: ReadonlyArray<string>;
|
|
285
|
+
/**
|
|
286
|
+
* Function names, in addition to a resolved vitest `expect`, that count as an
|
|
287
|
+
* assertion when they end a test. Matched by name against the callee chain,
|
|
288
|
+
* so no scope resolution applies. A `*` matches a single dotted segment and
|
|
289
|
+
* `**` matches any number, allowing patterns such as `request.*.expect`.
|
|
290
|
+
*/
|
|
291
|
+
readonly assertFunctionNames?: ReadonlyArray<string>;
|
|
292
|
+
}
|
|
293
|
+
type Options$3 = [PreferEndingWithAnExpectOptions?];
|
|
294
|
+
//#endregion
|
|
261
295
|
//#region src/rules/prefer-parameter-destructuring/rule.d.ts
|
|
262
296
|
type Options$2 = [{
|
|
263
297
|
/**
|
|
@@ -305,13 +339,14 @@ declare const plugin: {
|
|
|
305
339
|
version: string;
|
|
306
340
|
};
|
|
307
341
|
rules: {
|
|
308
|
-
"arrow-return-style": FlawlessRuleModule<Options$
|
|
342
|
+
"arrow-return-style": FlawlessRuleModule<Options$8, MessageIds$5, Readonly<TSESLint.SourceCode>>;
|
|
309
343
|
"jsx-shorthand-boolean": FlawlessRuleModule<[], "setAttributeValue", Readonly<TSESLint.SourceCode>>;
|
|
310
|
-
"jsx-shorthand-fragment": FlawlessRuleModule<Options$
|
|
311
|
-
"max-lines-per-function": FlawlessRuleModule<Options$
|
|
312
|
-
"naming-convention": TSESLint.RuleModule<MessageIds$3, Options$
|
|
344
|
+
"jsx-shorthand-fragment": FlawlessRuleModule<Options$7, MessageIds$4, Readonly<TSESLint.SourceCode>>;
|
|
345
|
+
"max-lines-per-function": FlawlessRuleModule<Options$6, "exceed", Readonly<TSESLint.SourceCode>>;
|
|
346
|
+
"naming-convention": TSESLint.RuleModule<MessageIds$3, Options$5, PluginDocumentation, TSESLint.RuleListener> & {
|
|
313
347
|
name: string;
|
|
314
348
|
};
|
|
349
|
+
"no-conditional-in-test": FlawlessRuleModule<Options$4, "conditionalInTest", Readonly<TSESLint.SourceCode>>;
|
|
315
350
|
"no-export-default-arrow": FlawlessRuleModule<[], "disallowExportDefaultArrow", Readonly<TSESLint.SourceCode>>;
|
|
316
351
|
"no-redundant-tsconfig-options": TSESLint.RuleModule<"redundant", [], PluginDocumentation, TSESLint.RuleListener> & {
|
|
317
352
|
name: string;
|
|
@@ -320,6 +355,8 @@ declare const plugin: {
|
|
|
320
355
|
"no-unnecessary-use-memo": FlawlessRuleModule<[], MessageIds$1, Readonly<TSESLint.SourceCode>>;
|
|
321
356
|
"padding-after-expect-assertions": FlawlessRuleModule<[], "missingPadding", Readonly<TSESLint.SourceCode>>;
|
|
322
357
|
"prefer-destructuring-assignment": FlawlessRuleModule<[], "default", Readonly<TSESLint.SourceCode>>;
|
|
358
|
+
"prefer-ending-with-an-expect": FlawlessRuleModule<Options$3, "mustEndWithExpect", Readonly<TSESLint.SourceCode>>;
|
|
359
|
+
"prefer-expect-assertions-count": FlawlessRuleModule<[], "preferCount", Readonly<TSESLint.SourceCode>>;
|
|
323
360
|
"prefer-parameter-destructuring": FlawlessRuleModule<Options$2, "default", Readonly<TSESLint.SourceCode>>;
|
|
324
361
|
"prefer-read-only-props": TSESLint.RuleModule<"preferReadOnlyProps", [{
|
|
325
362
|
fixStyle?: "modifier" | "wrap";
|
|
@@ -350,13 +387,14 @@ declare const _default: {
|
|
|
350
387
|
version: string;
|
|
351
388
|
};
|
|
352
389
|
rules: {
|
|
353
|
-
"arrow-return-style": FlawlessRuleModule<Options$
|
|
390
|
+
"arrow-return-style": FlawlessRuleModule<Options$8, MessageIds$5, Readonly<import("${configDir}").SourceCode>>;
|
|
354
391
|
"jsx-shorthand-boolean": FlawlessRuleModule<[], "setAttributeValue", Readonly<import("${configDir}").SourceCode>>;
|
|
355
|
-
"jsx-shorthand-fragment": FlawlessRuleModule<Options$
|
|
356
|
-
"max-lines-per-function": FlawlessRuleModule<Options$
|
|
357
|
-
"naming-convention": import("${configDir}").RuleModule<MessageIds$3, Options$
|
|
392
|
+
"jsx-shorthand-fragment": FlawlessRuleModule<Options$7, MessageIds$4, Readonly<import("${configDir}").SourceCode>>;
|
|
393
|
+
"max-lines-per-function": FlawlessRuleModule<Options$6, "exceed", Readonly<import("${configDir}").SourceCode>>;
|
|
394
|
+
"naming-convention": import("${configDir}").RuleModule<MessageIds$3, Options$5, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
358
395
|
name: string;
|
|
359
396
|
};
|
|
397
|
+
"no-conditional-in-test": FlawlessRuleModule<Options$4, "conditionalInTest", Readonly<import("${configDir}").SourceCode>>;
|
|
360
398
|
"no-export-default-arrow": FlawlessRuleModule<[], "disallowExportDefaultArrow", Readonly<import("${configDir}").SourceCode>>;
|
|
361
399
|
"no-redundant-tsconfig-options": import("${configDir}").RuleModule<"redundant", [], PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
362
400
|
name: string;
|
|
@@ -365,6 +403,8 @@ declare const _default: {
|
|
|
365
403
|
"no-unnecessary-use-memo": FlawlessRuleModule<[], MessageIds$1, Readonly<import("${configDir}").SourceCode>>;
|
|
366
404
|
"padding-after-expect-assertions": FlawlessRuleModule<[], "missingPadding", Readonly<import("${configDir}").SourceCode>>;
|
|
367
405
|
"prefer-destructuring-assignment": FlawlessRuleModule<[], "default", Readonly<import("${configDir}").SourceCode>>;
|
|
406
|
+
"prefer-ending-with-an-expect": FlawlessRuleModule<Options$3, "mustEndWithExpect", Readonly<import("${configDir}").SourceCode>>;
|
|
407
|
+
"prefer-expect-assertions-count": FlawlessRuleModule<[], "preferCount", Readonly<import("${configDir}").SourceCode>>;
|
|
368
408
|
"prefer-parameter-destructuring": FlawlessRuleModule<Options$2, "default", Readonly<import("${configDir}").SourceCode>>;
|
|
369
409
|
"prefer-read-only-props": import("${configDir}").RuleModule<"preferReadOnlyProps", [{
|
|
370
410
|
fixStyle?: "modifier" | "wrap";
|
|
@@ -392,13 +432,14 @@ declare const _default: {
|
|
|
392
432
|
version: string;
|
|
393
433
|
};
|
|
394
434
|
rules: {
|
|
395
|
-
"arrow-return-style": FlawlessRuleModule<Options$
|
|
435
|
+
"arrow-return-style": FlawlessRuleModule<Options$8, MessageIds$5, Readonly<import("${configDir}").SourceCode>>;
|
|
396
436
|
"jsx-shorthand-boolean": FlawlessRuleModule<[], "setAttributeValue", Readonly<import("${configDir}").SourceCode>>;
|
|
397
|
-
"jsx-shorthand-fragment": FlawlessRuleModule<Options$
|
|
398
|
-
"max-lines-per-function": FlawlessRuleModule<Options$
|
|
399
|
-
"naming-convention": import("${configDir}").RuleModule<MessageIds$3, Options$
|
|
437
|
+
"jsx-shorthand-fragment": FlawlessRuleModule<Options$7, MessageIds$4, Readonly<import("${configDir}").SourceCode>>;
|
|
438
|
+
"max-lines-per-function": FlawlessRuleModule<Options$6, "exceed", Readonly<import("${configDir}").SourceCode>>;
|
|
439
|
+
"naming-convention": import("${configDir}").RuleModule<MessageIds$3, Options$5, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
400
440
|
name: string;
|
|
401
441
|
};
|
|
442
|
+
"no-conditional-in-test": FlawlessRuleModule<Options$4, "conditionalInTest", Readonly<import("${configDir}").SourceCode>>;
|
|
402
443
|
"no-export-default-arrow": FlawlessRuleModule<[], "disallowExportDefaultArrow", Readonly<import("${configDir}").SourceCode>>;
|
|
403
444
|
"no-redundant-tsconfig-options": import("${configDir}").RuleModule<"redundant", [], PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
404
445
|
name: string;
|
|
@@ -407,6 +448,8 @@ declare const _default: {
|
|
|
407
448
|
"no-unnecessary-use-memo": FlawlessRuleModule<[], MessageIds$1, Readonly<import("${configDir}").SourceCode>>;
|
|
408
449
|
"padding-after-expect-assertions": FlawlessRuleModule<[], "missingPadding", Readonly<import("${configDir}").SourceCode>>;
|
|
409
450
|
"prefer-destructuring-assignment": FlawlessRuleModule<[], "default", Readonly<import("${configDir}").SourceCode>>;
|
|
451
|
+
"prefer-ending-with-an-expect": FlawlessRuleModule<Options$3, "mustEndWithExpect", Readonly<import("${configDir}").SourceCode>>;
|
|
452
|
+
"prefer-expect-assertions-count": FlawlessRuleModule<[], "preferCount", Readonly<import("${configDir}").SourceCode>>;
|
|
410
453
|
"prefer-parameter-destructuring": FlawlessRuleModule<Options$2, "default", Readonly<import("${configDir}").SourceCode>>;
|
|
411
454
|
"prefer-read-only-props": import("${configDir}").RuleModule<"preferReadOnlyProps", [{
|
|
412
455
|
fixStyle?: "modifier" | "wrap";
|
package/dist/index.mjs
CHANGED
package/dist/oxlint.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import * as core from "@eslint-react/core";
|
|
|
14
14
|
import { getStaticTOMLValue } from "toml-eslint-parser";
|
|
15
15
|
//#region package.json
|
|
16
16
|
var name = "eslint-plugin-flawless";
|
|
17
|
-
var version = "1.
|
|
17
|
+
var version = "1.4.0";
|
|
18
18
|
var repository = {
|
|
19
19
|
"url": "git+https://github.com/christopher-buss/eslint-plugin-flawless.git",
|
|
20
20
|
"type": "git"
|
|
@@ -84,11 +84,11 @@ function createFlawlessRule({ createOnce, ...meta }) {
|
|
|
84
84
|
}
|
|
85
85
|
//#endregion
|
|
86
86
|
//#region src/rules/arrow-return-style/rule.ts
|
|
87
|
-
const RULE_NAME$
|
|
87
|
+
const RULE_NAME$19 = "arrow-return-style";
|
|
88
88
|
const IMPLICIT = "useImplicitReturn";
|
|
89
89
|
const EXPLICIT = "useExplicitReturn";
|
|
90
90
|
const COMPLEX_EXPLICIT = "useExplicitReturnComplex";
|
|
91
|
-
const DEFAULTS$
|
|
91
|
+
const DEFAULTS$3 = {
|
|
92
92
|
jsxAlwaysUseExplicitReturn: false,
|
|
93
93
|
maxLen: 80,
|
|
94
94
|
maxObjectProperties: 4,
|
|
@@ -232,9 +232,9 @@ function collectArrows(root) {
|
|
|
232
232
|
return arrows.sort((a, b) => a.range[0] - b.range[0]);
|
|
233
233
|
}
|
|
234
234
|
const arrowReturnStyle = createFlawlessRule({
|
|
235
|
-
name: RULE_NAME$
|
|
235
|
+
name: RULE_NAME$19,
|
|
236
236
|
createOnce(context) {
|
|
237
|
-
let config = DEFAULTS$
|
|
237
|
+
let config = DEFAULTS$3;
|
|
238
238
|
let sourceCode;
|
|
239
239
|
let lines;
|
|
240
240
|
let pendingConsults;
|
|
@@ -533,7 +533,7 @@ const arrowReturnStyle = createFlawlessRule({
|
|
|
533
533
|
},
|
|
534
534
|
"before": function() {
|
|
535
535
|
config = {
|
|
536
|
-
...DEFAULTS$
|
|
536
|
+
...DEFAULTS$3,
|
|
537
537
|
...context.options[0]
|
|
538
538
|
};
|
|
539
539
|
({sourceCode} = context);
|
|
@@ -545,7 +545,7 @@ const arrowReturnStyle = createFlawlessRule({
|
|
|
545
545
|
}
|
|
546
546
|
};
|
|
547
547
|
},
|
|
548
|
-
defaultOptions: [DEFAULTS$
|
|
548
|
+
defaultOptions: [DEFAULTS$3],
|
|
549
549
|
meta: {
|
|
550
550
|
docs: {
|
|
551
551
|
description: "Enforce arrow function return style based on line length",
|
|
@@ -598,23 +598,23 @@ const arrowReturnStyle = createFlawlessRule({
|
|
|
598
598
|
});
|
|
599
599
|
//#endregion
|
|
600
600
|
//#region src/rules/jsx-shorthand-boolean/rule.ts
|
|
601
|
-
const RULE_NAME$
|
|
602
|
-
const MESSAGE_ID$
|
|
603
|
-
const messages$
|
|
604
|
-
function createOnce$
|
|
601
|
+
const RULE_NAME$18 = "jsx-shorthand-boolean";
|
|
602
|
+
const MESSAGE_ID$10 = "setAttributeValue";
|
|
603
|
+
const messages$18 = { [MESSAGE_ID$10]: "Set an explicit value for boolean attribute '{{name}}'." };
|
|
604
|
+
function createOnce$13(context) {
|
|
605
605
|
return { JSXAttribute(node) {
|
|
606
606
|
if (node.value !== null) return;
|
|
607
607
|
context.report({
|
|
608
608
|
data: { name: context.sourceCode.getText(node.name) },
|
|
609
609
|
fix: (fixer) => fixer.insertTextAfter(node.name, "={true}"),
|
|
610
|
-
messageId: MESSAGE_ID$
|
|
610
|
+
messageId: MESSAGE_ID$10,
|
|
611
611
|
node
|
|
612
612
|
});
|
|
613
613
|
} };
|
|
614
614
|
}
|
|
615
615
|
const jsxShorthandBoolean = createFlawlessRule({
|
|
616
|
-
name: RULE_NAME$
|
|
617
|
-
createOnce: createOnce$
|
|
616
|
+
name: RULE_NAME$18,
|
|
617
|
+
createOnce: createOnce$13,
|
|
618
618
|
defaultOptions: [],
|
|
619
619
|
meta: {
|
|
620
620
|
docs: {
|
|
@@ -624,23 +624,23 @@ const jsxShorthandBoolean = createFlawlessRule({
|
|
|
624
624
|
},
|
|
625
625
|
fixable: "code",
|
|
626
626
|
hasSuggestions: false,
|
|
627
|
-
messages: messages$
|
|
627
|
+
messages: messages$18,
|
|
628
628
|
schema: [],
|
|
629
629
|
type: "suggestion"
|
|
630
630
|
}
|
|
631
631
|
});
|
|
632
632
|
//#endregion
|
|
633
633
|
//#region src/rules/jsx-shorthand-fragment/rule.ts
|
|
634
|
-
const RULE_NAME$
|
|
634
|
+
const RULE_NAME$17 = "jsx-shorthand-fragment";
|
|
635
635
|
const MESSAGE_ID_NAMED = "useNamedFragment";
|
|
636
636
|
const MESSAGE_ID_SHORTHAND = "useShorthandFragment";
|
|
637
637
|
const DEFAULT_MODE = "syntax";
|
|
638
638
|
const DEFAULT_FRAGMENT_NAME = "Fragment";
|
|
639
|
-
const messages$
|
|
639
|
+
const messages$17 = {
|
|
640
640
|
[MESSAGE_ID_NAMED]: "Use the '{{name}}' component instead of fragment shorthand syntax.",
|
|
641
641
|
[MESSAGE_ID_SHORTHAND]: "Use the fragment shorthand syntax '<>...</>' instead of the '{{name}}' component."
|
|
642
642
|
};
|
|
643
|
-
const schema$
|
|
643
|
+
const schema$5 = [{
|
|
644
644
|
additionalProperties: false,
|
|
645
645
|
properties: {
|
|
646
646
|
fragmentName: {
|
|
@@ -672,7 +672,7 @@ function jsxNameToString(node) {
|
|
|
672
672
|
}
|
|
673
673
|
return null;
|
|
674
674
|
}
|
|
675
|
-
function createOnce$
|
|
675
|
+
function createOnce$12(context) {
|
|
676
676
|
let mode;
|
|
677
677
|
let fragmentName;
|
|
678
678
|
let namedFragments;
|
|
@@ -719,8 +719,8 @@ function createOnce$9(context) {
|
|
|
719
719
|
};
|
|
720
720
|
}
|
|
721
721
|
const jsxShorthandFragment = createFlawlessRule({
|
|
722
|
-
name: RULE_NAME$
|
|
723
|
-
createOnce: createOnce$
|
|
722
|
+
name: RULE_NAME$17,
|
|
723
|
+
createOnce: createOnce$12,
|
|
724
724
|
defaultOptions: [{
|
|
725
725
|
fragmentName: DEFAULT_FRAGMENT_NAME,
|
|
726
726
|
mode: DEFAULT_MODE
|
|
@@ -737,8 +737,8 @@ const jsxShorthandFragment = createFlawlessRule({
|
|
|
737
737
|
},
|
|
738
738
|
fixable: "code",
|
|
739
739
|
hasSuggestions: false,
|
|
740
|
-
messages: messages$
|
|
741
|
-
schema: schema$
|
|
740
|
+
messages: messages$17,
|
|
741
|
+
schema: schema$5,
|
|
742
742
|
type: "suggestion"
|
|
743
743
|
}
|
|
744
744
|
});
|
|
@@ -877,19 +877,19 @@ function getOpeningParenOfParameters(node, sourceCode) {
|
|
|
877
877
|
}
|
|
878
878
|
//#endregion
|
|
879
879
|
//#region src/rules/max-lines-per-function/rule.ts
|
|
880
|
-
const RULE_NAME$
|
|
880
|
+
const RULE_NAME$16 = "max-lines-per-function";
|
|
881
881
|
const MESSAGE_ID_EXCEED = "exceed";
|
|
882
882
|
/** A line that is empty or holds only whitespace. */
|
|
883
883
|
const BLANK_LINE = /^\s*$/u;
|
|
884
|
-
const DEFAULTS = {
|
|
884
|
+
const DEFAULTS$2 = {
|
|
885
885
|
countFrom: "body",
|
|
886
886
|
IIFEs: false,
|
|
887
887
|
max: 50,
|
|
888
888
|
skipBlankLines: false,
|
|
889
889
|
skipComments: false
|
|
890
890
|
};
|
|
891
|
-
const messages$
|
|
892
|
-
const schema$
|
|
891
|
+
const messages$16 = { [MESSAGE_ID_EXCEED]: "{{name}} has too many lines ({{lineCount}}). Maximum allowed is {{maxLines}}." };
|
|
892
|
+
const schema$4 = [{
|
|
893
893
|
additionalProperties: false,
|
|
894
894
|
properties: {
|
|
895
895
|
countFrom: {
|
|
@@ -991,7 +991,7 @@ function getCountedLoc(countFrom, funcNode, reportNode) {
|
|
|
991
991
|
* @param context - The rule context.
|
|
992
992
|
* @returns The visitors measuring each function.
|
|
993
993
|
*/
|
|
994
|
-
function createOnce$
|
|
994
|
+
function createOnce$11(context) {
|
|
995
995
|
let commentLineNumbers;
|
|
996
996
|
let config;
|
|
997
997
|
let lines;
|
|
@@ -1032,11 +1032,11 @@ function createOnce$8(context) {
|
|
|
1032
1032
|
before() {
|
|
1033
1033
|
const options = context.options[0];
|
|
1034
1034
|
config = {
|
|
1035
|
-
countFrom: options?.countFrom ?? DEFAULTS.countFrom,
|
|
1036
|
-
IIFEs: options?.IIFEs ?? DEFAULTS.IIFEs,
|
|
1037
|
-
max: options?.max ?? DEFAULTS.max,
|
|
1038
|
-
skipBlankLines: options?.skipBlankLines ?? DEFAULTS.skipBlankLines,
|
|
1039
|
-
skipComments: options?.skipComments ?? DEFAULTS.skipComments
|
|
1035
|
+
countFrom: options?.countFrom ?? DEFAULTS$2.countFrom,
|
|
1036
|
+
IIFEs: options?.IIFEs ?? DEFAULTS$2.IIFEs,
|
|
1037
|
+
max: options?.max ?? DEFAULTS$2.max,
|
|
1038
|
+
skipBlankLines: options?.skipBlankLines ?? DEFAULTS$2.skipBlankLines,
|
|
1039
|
+
skipComments: options?.skipComments ?? DEFAULTS$2.skipComments
|
|
1040
1040
|
};
|
|
1041
1041
|
({sourceCode} = context);
|
|
1042
1042
|
lines = [...sourceCode.lines];
|
|
@@ -1047,19 +1047,19 @@ function createOnce$8(context) {
|
|
|
1047
1047
|
};
|
|
1048
1048
|
}
|
|
1049
1049
|
const maxLinesPerFunction = createFlawlessRule({
|
|
1050
|
-
name: RULE_NAME$
|
|
1051
|
-
createOnce: createOnce$
|
|
1052
|
-
defaultOptions: [DEFAULTS],
|
|
1050
|
+
name: RULE_NAME$16,
|
|
1051
|
+
createOnce: createOnce$11,
|
|
1052
|
+
defaultOptions: [DEFAULTS$2],
|
|
1053
1053
|
meta: {
|
|
1054
|
-
defaultOptions: [DEFAULTS],
|
|
1054
|
+
defaultOptions: [DEFAULTS$2],
|
|
1055
1055
|
docs: {
|
|
1056
1056
|
description: "Enforce a maximum number of lines of code in a function",
|
|
1057
1057
|
recommended: false,
|
|
1058
1058
|
requiresTypeChecking: false
|
|
1059
1059
|
},
|
|
1060
1060
|
hasSuggestions: false,
|
|
1061
|
-
messages: messages$
|
|
1062
|
-
schema: schema$
|
|
1061
|
+
messages: messages$16,
|
|
1062
|
+
schema: schema$4,
|
|
1063
1063
|
type: "suggestion"
|
|
1064
1064
|
}
|
|
1065
1065
|
});
|
|
@@ -2459,9 +2459,9 @@ const SCHEMA = {
|
|
|
2459
2459
|
};
|
|
2460
2460
|
//#endregion
|
|
2461
2461
|
//#region src/rules/naming-convention/rule.ts
|
|
2462
|
-
const RULE_NAME$
|
|
2462
|
+
const RULE_NAME$15 = "naming-convention";
|
|
2463
2463
|
const FOREIGN_CONTRACT_HINT = " If this is data conforming to an external shape, declare it with `satisfies` instead.";
|
|
2464
|
-
const messages$
|
|
2464
|
+
const messages$15 = {
|
|
2465
2465
|
doesNotMatchFormat: "{{type}} name `{{name}}` must match one of the following formats: {{formats}}",
|
|
2466
2466
|
doesNotMatchFormatForeignContract: `{{type}} name \`{{name}}\` must match one of the following formats: {{formats}}${FOREIGN_CONTRACT_HINT}`,
|
|
2467
2467
|
doesNotMatchFormatTrimmed: "{{type}} name `{{name}}` trimmed as `{{processedName}}` must match one of the following formats: {{formats}}",
|
|
@@ -2770,7 +2770,7 @@ function create$4(contextWithoutDefaults) {
|
|
|
2770
2770
|
}));
|
|
2771
2771
|
}
|
|
2772
2772
|
const namingConvention = createEslintRule({
|
|
2773
|
-
name: RULE_NAME$
|
|
2773
|
+
name: RULE_NAME$15,
|
|
2774
2774
|
create: create$4,
|
|
2775
2775
|
defaultOptions: camelCaseNamingConfig,
|
|
2776
2776
|
meta: {
|
|
@@ -2781,7 +2781,7 @@ const namingConvention = createEslintRule({
|
|
|
2781
2781
|
},
|
|
2782
2782
|
fixable: void 0,
|
|
2783
2783
|
hasSuggestions: false,
|
|
2784
|
-
messages: messages$
|
|
2784
|
+
messages: messages$15,
|
|
2785
2785
|
schema: SCHEMA,
|
|
2786
2786
|
type: "suggestion"
|
|
2787
2787
|
}
|
|
@@ -3021,10 +3021,232 @@ function requiresQuoting(node, target) {
|
|
|
3021
3021
|
return !isValidIdentifierText(node.type === AST_NODE_TYPES.Identifier || node.type === AST_NODE_TYPES.PrivateIdentifier ? node.name : `${node.value}`, target ?? ScriptTarget.Latest);
|
|
3022
3022
|
}
|
|
3023
3023
|
//#endregion
|
|
3024
|
+
//#region src/rules/no-conditional-in-test/rule.ts
|
|
3025
|
+
const RULE_NAME$14 = "no-conditional-in-test";
|
|
3026
|
+
const MESSAGE_ID$9 = "conditionalInTest";
|
|
3027
|
+
const DEFAULTS$1 = {
|
|
3028
|
+
additionalTestBlockFunctions: [],
|
|
3029
|
+
allowOptionalChaining: true
|
|
3030
|
+
};
|
|
3031
|
+
/** Callee identifiers that name a vitest test block (`describe` is excluded). */
|
|
3032
|
+
const TEST_BLOCK_NAMES$1 = /* @__PURE__ */ new Set(["it", "test"]);
|
|
3033
|
+
const messages$14 = { [MESSAGE_ID$9]: "Avoid having conditionals in tests." };
|
|
3034
|
+
const schema$3 = [{
|
|
3035
|
+
additionalProperties: false,
|
|
3036
|
+
properties: {
|
|
3037
|
+
additionalTestBlockFunctions: {
|
|
3038
|
+
description: "Callee names, besides a resolved vitest it/test, whose call is a test block (matched by exact dotted name).",
|
|
3039
|
+
items: { type: "string" },
|
|
3040
|
+
type: "array"
|
|
3041
|
+
},
|
|
3042
|
+
allowOptionalChaining: {
|
|
3043
|
+
description: "Allow optional chaining (?.) in tests. When false, it is reported and auto-fixed to a non-null assertion (a?.b -> a!.b).",
|
|
3044
|
+
type: "boolean"
|
|
3045
|
+
}
|
|
3046
|
+
},
|
|
3047
|
+
type: "object"
|
|
3048
|
+
}];
|
|
3049
|
+
/**
|
|
3050
|
+
* Builds the dotted name of a callee chain, unwrapping intervening calls
|
|
3051
|
+
* (`each.test` -> `each.test`, `request(app).get` -> `request.get`). A computed
|
|
3052
|
+
* member access yields `null`, since its property is not a static name. Ported
|
|
3053
|
+
* from eslint-plugin-jest's `getNodeName`.
|
|
3054
|
+
*
|
|
3055
|
+
* @param node - The callee node.
|
|
3056
|
+
* @returns The dotted name, or `null` when it cannot be built statically.
|
|
3057
|
+
*/
|
|
3058
|
+
function getNodeName$1(node) {
|
|
3059
|
+
if (node.type === AST_NODE_TYPES.Identifier) return node.name;
|
|
3060
|
+
if (node.type === AST_NODE_TYPES.CallExpression) return getNodeName$1(node.callee);
|
|
3061
|
+
if (node.type === AST_NODE_TYPES.MemberExpression && !node.computed && node.property.type === AST_NODE_TYPES.Identifier) {
|
|
3062
|
+
const objectName = getNodeName$1(node.object);
|
|
3063
|
+
return objectName === null ? null : `${objectName}.${node.property.name}`;
|
|
3064
|
+
}
|
|
3065
|
+
return null;
|
|
3066
|
+
}
|
|
3067
|
+
/**
|
|
3068
|
+
* Walks a callee chain down to the identifier it is rooted at, stepping through
|
|
3069
|
+
* member accesses (`it.each` -> `it`) and intervening calls
|
|
3070
|
+
* (`it.each(cases)()` -> `it`).
|
|
3071
|
+
*
|
|
3072
|
+
* @param node - The callee node.
|
|
3073
|
+
* @returns The root identifier, or `null` when the chain is not rooted at one.
|
|
3074
|
+
*/
|
|
3075
|
+
function getRootIdentifier$1(node) {
|
|
3076
|
+
let current = node;
|
|
3077
|
+
for (;;) {
|
|
3078
|
+
if (current.type === AST_NODE_TYPES.Identifier) return current;
|
|
3079
|
+
if (current.type === AST_NODE_TYPES.CallExpression) {
|
|
3080
|
+
current = current.callee;
|
|
3081
|
+
continue;
|
|
3082
|
+
}
|
|
3083
|
+
if (current.type === AST_NODE_TYPES.MemberExpression) {
|
|
3084
|
+
current = current.object;
|
|
3085
|
+
continue;
|
|
3086
|
+
}
|
|
3087
|
+
return null;
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
/**
|
|
3091
|
+
* Resolves the vitest name an identifier refers to. An unresolved reference is a
|
|
3092
|
+
* global (vitest's `globals: true` / `@vitest/globals`); a named import from
|
|
3093
|
+
* `"vitest"` resolves to its imported name (so aliases work); anything bound to
|
|
3094
|
+
* a local variable, function, or parameter resolves to `null` and is ignored.
|
|
3095
|
+
* Ported from eslint-plugin-flawless's `prefer-ending-with-an-expect`.
|
|
3096
|
+
*
|
|
3097
|
+
* @param sourceCode - Provides the scope used to look up the binding.
|
|
3098
|
+
* @param identifier - The identifier to resolve.
|
|
3099
|
+
* @returns The vitest name (`it`/`test`/...), or `null` when the identifier is a
|
|
3100
|
+
* local binding rather than a vitest global or import.
|
|
3101
|
+
*/
|
|
3102
|
+
function resolveVitestName$1(sourceCode, identifier) {
|
|
3103
|
+
const variable = findVariable(sourceCode.getScope(identifier), identifier);
|
|
3104
|
+
if (variable === null) return identifier.name;
|
|
3105
|
+
const definition = variable.defs.at(0);
|
|
3106
|
+
if (definition === void 0) return identifier.name;
|
|
3107
|
+
if (definition.type !== DefinitionType.ImportBinding) return null;
|
|
3108
|
+
const importDefinition = definition;
|
|
3109
|
+
const declaration = importDefinition.parent;
|
|
3110
|
+
if (declaration.type !== AST_NODE_TYPES.ImportDeclaration || declaration.source.value !== "vitest") return null;
|
|
3111
|
+
const { node } = importDefinition;
|
|
3112
|
+
if (node.type === AST_NODE_TYPES.ImportSpecifier && node.imported.type === AST_NODE_TYPES.Identifier) return node.imported.name;
|
|
3113
|
+
return null;
|
|
3114
|
+
}
|
|
3115
|
+
/**
|
|
3116
|
+
* Collects the `?.` tokens along an optional chain's primary spine and the text
|
|
3117
|
+
* that replaces each with a non-null assertion. A non-computed member becomes
|
|
3118
|
+
* `!.` (`a?.b` -> `a!.b`); a computed member or optional call becomes `!`
|
|
3119
|
+
* (`a?.[x]` -> `a![x]`, `fn?.()` -> `fn!()`). Only the object/callee spine is
|
|
3120
|
+
* walked — optional chains inside computed keys or call arguments are their own
|
|
3121
|
+
* `ChainExpression` nodes and are visited (and fixed) separately.
|
|
3122
|
+
*
|
|
3123
|
+
* @param chain - The chain expression to convert.
|
|
3124
|
+
* @param sourceCode - Provides token lookups.
|
|
3125
|
+
* @returns The per-token replacements, outermost link first.
|
|
3126
|
+
*/
|
|
3127
|
+
function collectOptionalTokenFixes(chain, sourceCode) {
|
|
3128
|
+
const fixes = [];
|
|
3129
|
+
let node = chain.expression;
|
|
3130
|
+
for (;;) {
|
|
3131
|
+
if (node.type === AST_NODE_TYPES.MemberExpression) {
|
|
3132
|
+
if (node.optional) {
|
|
3133
|
+
const token = sourceCode.getTokenAfter(node.object, { filter: (candidate) => candidate.value === "?." });
|
|
3134
|
+
if (token !== null) fixes.push({
|
|
3135
|
+
text: node.computed ? "!" : "!.",
|
|
3136
|
+
token
|
|
3137
|
+
});
|
|
3138
|
+
}
|
|
3139
|
+
node = node.object;
|
|
3140
|
+
continue;
|
|
3141
|
+
}
|
|
3142
|
+
if (node.type === AST_NODE_TYPES.CallExpression) {
|
|
3143
|
+
if (node.optional) {
|
|
3144
|
+
const token = sourceCode.getTokenAfter(node.callee, { filter: (candidate) => candidate.value === "?." });
|
|
3145
|
+
if (token !== null) fixes.push({
|
|
3146
|
+
text: "!",
|
|
3147
|
+
token
|
|
3148
|
+
});
|
|
3149
|
+
}
|
|
3150
|
+
node = node.callee;
|
|
3151
|
+
continue;
|
|
3152
|
+
}
|
|
3153
|
+
return fixes;
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
/**
|
|
3157
|
+
* Disallows conditional logic inside vitest test bodies (`it`/`test`), which
|
|
3158
|
+
* makes a test's behavior depend on runtime state. Ported from
|
|
3159
|
+
* eslint-plugin-jest's `no-conditional-in-test` with vitest-aware resolution and
|
|
3160
|
+
* an auto-fix that rewrites disallowed optional chaining to a non-null assertion.
|
|
3161
|
+
*
|
|
3162
|
+
* @param context - The rule context.
|
|
3163
|
+
* @returns The rule listener.
|
|
3164
|
+
*/
|
|
3165
|
+
function createOnce$10(context) {
|
|
3166
|
+
let config;
|
|
3167
|
+
let sourceCode;
|
|
3168
|
+
let inTestCase = false;
|
|
3169
|
+
/**
|
|
3170
|
+
* Determines whether a call opens a test block (a resolved vitest `it`/`test`,
|
|
3171
|
+
* or a configured `additionalTestBlockFunctions` name).
|
|
3172
|
+
*
|
|
3173
|
+
* @param node - The call to inspect.
|
|
3174
|
+
* @returns `true` when the call opens a test.
|
|
3175
|
+
*/
|
|
3176
|
+
function isTestBlock(node) {
|
|
3177
|
+
const root = getRootIdentifier$1(node.callee);
|
|
3178
|
+
if (root !== null && TEST_BLOCK_NAMES$1.has(resolveVitestName$1(sourceCode, root) ?? "")) return true;
|
|
3179
|
+
const name = getNodeName$1(node.callee);
|
|
3180
|
+
return name !== null && config.additionalTestBlockFunctions.includes(name);
|
|
3181
|
+
}
|
|
3182
|
+
/**
|
|
3183
|
+
* Reports a conditional node when the traversal is inside a test body.
|
|
3184
|
+
*
|
|
3185
|
+
* @param node - The `if`/`switch`/ternary/logical construct to flag.
|
|
3186
|
+
*/
|
|
3187
|
+
function maybeReportConditional(node) {
|
|
3188
|
+
if (inTestCase) context.report({
|
|
3189
|
+
messageId: MESSAGE_ID$9,
|
|
3190
|
+
node
|
|
3191
|
+
});
|
|
3192
|
+
}
|
|
3193
|
+
return {
|
|
3194
|
+
"before": function() {
|
|
3195
|
+
const options = context.options[0];
|
|
3196
|
+
config = {
|
|
3197
|
+
additionalTestBlockFunctions: options?.additionalTestBlockFunctions ?? DEFAULTS$1.additionalTestBlockFunctions,
|
|
3198
|
+
allowOptionalChaining: options?.allowOptionalChaining ?? DEFAULTS$1.allowOptionalChaining
|
|
3199
|
+
};
|
|
3200
|
+
({sourceCode} = context);
|
|
3201
|
+
inTestCase = false;
|
|
3202
|
+
},
|
|
3203
|
+
"CallExpression": function(node) {
|
|
3204
|
+
if (isTestBlock(node)) inTestCase = true;
|
|
3205
|
+
},
|
|
3206
|
+
"CallExpression:exit": function(node) {
|
|
3207
|
+
if (isTestBlock(node)) inTestCase = false;
|
|
3208
|
+
},
|
|
3209
|
+
"ChainExpression": function(node) {
|
|
3210
|
+
if (!inTestCase || config.allowOptionalChaining) return;
|
|
3211
|
+
context.report({
|
|
3212
|
+
fix: (fixer) => {
|
|
3213
|
+
return collectOptionalTokenFixes(node, sourceCode).map((optionalFix) => {
|
|
3214
|
+
return fixer.replaceText(optionalFix.token, optionalFix.text);
|
|
3215
|
+
});
|
|
3216
|
+
},
|
|
3217
|
+
messageId: MESSAGE_ID$9,
|
|
3218
|
+
node
|
|
3219
|
+
});
|
|
3220
|
+
},
|
|
3221
|
+
"ConditionalExpression": maybeReportConditional,
|
|
3222
|
+
"IfStatement": maybeReportConditional,
|
|
3223
|
+
"LogicalExpression": maybeReportConditional,
|
|
3224
|
+
"SwitchStatement": maybeReportConditional
|
|
3225
|
+
};
|
|
3226
|
+
}
|
|
3227
|
+
const noConditionalInTest = createFlawlessRule({
|
|
3228
|
+
name: RULE_NAME$14,
|
|
3229
|
+
createOnce: createOnce$10,
|
|
3230
|
+
defaultOptions: [DEFAULTS$1],
|
|
3231
|
+
meta: {
|
|
3232
|
+
defaultOptions: [DEFAULTS$1],
|
|
3233
|
+
docs: {
|
|
3234
|
+
description: "Disallow conditional logic in tests",
|
|
3235
|
+
recommended: false,
|
|
3236
|
+
requiresTypeChecking: false
|
|
3237
|
+
},
|
|
3238
|
+
fixable: "code",
|
|
3239
|
+
hasSuggestions: false,
|
|
3240
|
+
messages: messages$14,
|
|
3241
|
+
schema: schema$3,
|
|
3242
|
+
type: "problem"
|
|
3243
|
+
}
|
|
3244
|
+
});
|
|
3245
|
+
//#endregion
|
|
3024
3246
|
//#region src/rules/no-export-default-arrow/rule.ts
|
|
3025
|
-
const RULE_NAME$
|
|
3026
|
-
const MESSAGE_ID$
|
|
3027
|
-
const messages$
|
|
3247
|
+
const RULE_NAME$13 = "no-export-default-arrow";
|
|
3248
|
+
const MESSAGE_ID$8 = "disallowExportDefaultArrow";
|
|
3249
|
+
const messages$13 = { [MESSAGE_ID$8]: "Assign the arrow function to a named constant instead of exporting it anonymously." };
|
|
3028
3250
|
/**
|
|
3029
3251
|
* Converts a filename stem to camelCase, treating `-`, `_`, and whitespace as
|
|
3030
3252
|
* word separators (`use-mouse` -> `useMouse`).
|
|
@@ -3102,7 +3324,7 @@ function createFixFunction({ arrowFunction, context, exportDeclaration, sourceCo
|
|
|
3102
3324
|
* @param context - The rule context.
|
|
3103
3325
|
* @returns The rule listener.
|
|
3104
3326
|
*/
|
|
3105
|
-
function createOnce$
|
|
3327
|
+
function createOnce$9(context) {
|
|
3106
3328
|
return { ArrowFunctionExpression(node) {
|
|
3107
3329
|
const { parent } = node;
|
|
3108
3330
|
if (parent.type !== AST_NODE_TYPES.ExportDefaultDeclaration) return;
|
|
@@ -3113,14 +3335,14 @@ function createOnce$7(context) {
|
|
|
3113
3335
|
exportDeclaration: parent,
|
|
3114
3336
|
sourceCode: context.sourceCode
|
|
3115
3337
|
}),
|
|
3116
|
-
messageId: MESSAGE_ID$
|
|
3338
|
+
messageId: MESSAGE_ID$8,
|
|
3117
3339
|
node
|
|
3118
3340
|
});
|
|
3119
3341
|
} };
|
|
3120
3342
|
}
|
|
3121
3343
|
const noExportDefaultArrow = createFlawlessRule({
|
|
3122
|
-
name: RULE_NAME$
|
|
3123
|
-
createOnce: createOnce$
|
|
3344
|
+
name: RULE_NAME$13,
|
|
3345
|
+
createOnce: createOnce$9,
|
|
3124
3346
|
defaultOptions: [],
|
|
3125
3347
|
meta: {
|
|
3126
3348
|
docs: {
|
|
@@ -3130,7 +3352,7 @@ const noExportDefaultArrow = createFlawlessRule({
|
|
|
3130
3352
|
},
|
|
3131
3353
|
fixable: "code",
|
|
3132
3354
|
hasSuggestions: false,
|
|
3133
|
-
messages: messages$
|
|
3355
|
+
messages: messages$13,
|
|
3134
3356
|
schema: [],
|
|
3135
3357
|
type: "suggestion"
|
|
3136
3358
|
}
|
|
@@ -3295,8 +3517,8 @@ function flatten(file, context) {
|
|
|
3295
3517
|
}
|
|
3296
3518
|
//#endregion
|
|
3297
3519
|
//#region src/rules/no-redundant-tsconfig-options/rule.ts
|
|
3298
|
-
const RULE_NAME$
|
|
3299
|
-
const MESSAGE_ID$
|
|
3520
|
+
const RULE_NAME$12 = "no-redundant-tsconfig-options";
|
|
3521
|
+
const MESSAGE_ID$7 = "redundant";
|
|
3300
3522
|
/**
|
|
3301
3523
|
* Compiler options TypeScript resolves case-insensitively; a child that re-sets
|
|
3302
3524
|
* one differing only in case is still redundant.
|
|
@@ -3331,7 +3553,7 @@ const PATH_KEYS = /* @__PURE__ */ new Set([
|
|
|
3331
3553
|
"tsBuildInfoFile",
|
|
3332
3554
|
"typeRoots"
|
|
3333
3555
|
]);
|
|
3334
|
-
const messages$
|
|
3556
|
+
const messages$12 = { [MESSAGE_ID$7]: "'{{option}}' is redundant: it is already set to this value by {{source}}. Remove it." };
|
|
3335
3557
|
/**
|
|
3336
3558
|
* Structural equality between two static JSON values. Strings compare
|
|
3337
3559
|
* case-insensitively when `caseInsensitive` is set (used for enum-valued
|
|
@@ -3445,7 +3667,7 @@ function create$3(context) {
|
|
|
3445
3667
|
},
|
|
3446
3668
|
fix: buildFix(property),
|
|
3447
3669
|
loc: property.key.loc,
|
|
3448
|
-
messageId: MESSAGE_ID$
|
|
3670
|
+
messageId: MESSAGE_ID$7
|
|
3449
3671
|
});
|
|
3450
3672
|
}
|
|
3451
3673
|
function buildFix(property) {
|
|
@@ -3503,7 +3725,7 @@ function create$3(context) {
|
|
|
3503
3725
|
} };
|
|
3504
3726
|
}
|
|
3505
3727
|
const noRedundantTsconfigOptions = createEslintRule({
|
|
3506
|
-
name: RULE_NAME$
|
|
3728
|
+
name: RULE_NAME$12,
|
|
3507
3729
|
create: create$3,
|
|
3508
3730
|
defaultOptions: [],
|
|
3509
3731
|
meta: {
|
|
@@ -3514,7 +3736,7 @@ const noRedundantTsconfigOptions = createEslintRule({
|
|
|
3514
3736
|
},
|
|
3515
3737
|
fixable: "code",
|
|
3516
3738
|
hasSuggestions: false,
|
|
3517
|
-
messages: messages$
|
|
3739
|
+
messages: messages$12,
|
|
3518
3740
|
schema: [],
|
|
3519
3741
|
type: "suggestion"
|
|
3520
3742
|
}
|
|
@@ -3757,15 +3979,15 @@ function resolveFactory(sourceCode, node) {
|
|
|
3757
3979
|
}
|
|
3758
3980
|
//#endregion
|
|
3759
3981
|
//#region src/rules/no-unnecessary-use-callback/rule.ts
|
|
3760
|
-
const RULE_NAME$
|
|
3982
|
+
const RULE_NAME$11 = "no-unnecessary-use-callback";
|
|
3761
3983
|
const MESSAGE_ID_DEFAULT$1 = "default";
|
|
3762
3984
|
const MESSAGE_ID_INSIDE_USE_EFFECT$1 = "noUnnecessaryUseCallbackInsideUseEffect";
|
|
3763
|
-
const messages$
|
|
3985
|
+
const messages$11 = {
|
|
3764
3986
|
[MESSAGE_ID_DEFAULT$1]: "An 'useCallback' with empty deps and no references to the component scope may be unnecessary.",
|
|
3765
3987
|
[MESSAGE_ID_INSIDE_USE_EFFECT$1]: "'{{name}}' is only used inside 1 useEffect, which may be unnecessary. You can move the computation into useEffect directly and merge the dependency arrays."
|
|
3766
3988
|
};
|
|
3767
3989
|
const noUnnecessaryUseCallback = createFlawlessRule({
|
|
3768
|
-
name: RULE_NAME$
|
|
3990
|
+
name: RULE_NAME$11,
|
|
3769
3991
|
createOnce: createUnnecessaryHookRule({
|
|
3770
3992
|
hook: "useCallback",
|
|
3771
3993
|
messageIds: {
|
|
@@ -3781,22 +4003,22 @@ const noUnnecessaryUseCallback = createFlawlessRule({
|
|
|
3781
4003
|
requiresTypeChecking: false
|
|
3782
4004
|
},
|
|
3783
4005
|
hasSuggestions: false,
|
|
3784
|
-
messages: messages$
|
|
4006
|
+
messages: messages$11,
|
|
3785
4007
|
schema: [],
|
|
3786
4008
|
type: "suggestion"
|
|
3787
4009
|
}
|
|
3788
4010
|
});
|
|
3789
4011
|
//#endregion
|
|
3790
4012
|
//#region src/rules/no-unnecessary-use-memo/rule.ts
|
|
3791
|
-
const RULE_NAME$
|
|
4013
|
+
const RULE_NAME$10 = "no-unnecessary-use-memo";
|
|
3792
4014
|
const MESSAGE_ID_DEFAULT = "default";
|
|
3793
4015
|
const MESSAGE_ID_INSIDE_USE_EFFECT = "noUnnecessaryUseMemoInsideUseEffect";
|
|
3794
|
-
const messages$
|
|
4016
|
+
const messages$10 = {
|
|
3795
4017
|
[MESSAGE_ID_DEFAULT]: "An 'useMemo' with empty deps and no references to the component scope may be unnecessary.",
|
|
3796
4018
|
[MESSAGE_ID_INSIDE_USE_EFFECT]: "'{{name}}' is only used inside 1 useEffect, which may be unnecessary. You can move the computation into useEffect directly and merge the dependency arrays."
|
|
3797
4019
|
};
|
|
3798
4020
|
const noUnnecessaryUseMemo = createFlawlessRule({
|
|
3799
|
-
name: RULE_NAME$
|
|
4021
|
+
name: RULE_NAME$10,
|
|
3800
4022
|
createOnce: createUnnecessaryHookRule({
|
|
3801
4023
|
hook: "useMemo",
|
|
3802
4024
|
messageIds: {
|
|
@@ -3812,36 +4034,64 @@ const noUnnecessaryUseMemo = createFlawlessRule({
|
|
|
3812
4034
|
requiresTypeChecking: false
|
|
3813
4035
|
},
|
|
3814
4036
|
hasSuggestions: false,
|
|
3815
|
-
messages: messages$
|
|
4037
|
+
messages: messages$10,
|
|
3816
4038
|
schema: [],
|
|
3817
4039
|
type: "suggestion"
|
|
3818
4040
|
}
|
|
3819
4041
|
});
|
|
3820
4042
|
//#endregion
|
|
3821
4043
|
//#region src/rules/padding-after-expect-assertions/rule.ts
|
|
3822
|
-
const RULE_NAME$
|
|
3823
|
-
const MESSAGE_ID$
|
|
3824
|
-
const messages$
|
|
4044
|
+
const RULE_NAME$9 = "padding-after-expect-assertions";
|
|
4045
|
+
const MESSAGE_ID$6 = "missingPadding";
|
|
4046
|
+
const messages$9 = { [MESSAGE_ID$6]: "Expected a blank line after '{{name}}'." };
|
|
4047
|
+
/**
|
|
4048
|
+
* Resolves the name an identifier refers to, mirroring how eslint-plugin-jest
|
|
4049
|
+
* resolves test functions. An unresolved reference is a global (vitest's
|
|
4050
|
+
* `globals: true` / jest's injected globals); a named import from `"vitest"` or
|
|
4051
|
+
* `"@jest/globals"` resolves to its imported name (so aliases work); anything
|
|
4052
|
+
* bound to a local variable, function, or parameter resolves to `null` and is
|
|
4053
|
+
* ignored.
|
|
4054
|
+
*
|
|
4055
|
+
* @param sourceCode - Provides the scope used to look up the binding.
|
|
4056
|
+
* @param identifier - The identifier to resolve.
|
|
4057
|
+
* @returns The resolved name (`expect`/...), or `null` when the identifier is a
|
|
4058
|
+
* local binding rather than a test global or a vitest/jest import.
|
|
4059
|
+
*/
|
|
4060
|
+
function resolveTestGlobalName$1(sourceCode, identifier) {
|
|
4061
|
+
const variable = findVariable(sourceCode.getScope(identifier), identifier);
|
|
4062
|
+
if (variable === null) return identifier.name;
|
|
4063
|
+
const definition = variable.defs.at(0);
|
|
4064
|
+
if (definition === void 0) return identifier.name;
|
|
4065
|
+
if (definition.type !== DefinitionType.ImportBinding) return null;
|
|
4066
|
+
const importDefinition = definition;
|
|
4067
|
+
const declaration = importDefinition.parent;
|
|
4068
|
+
const source = declaration.type === AST_NODE_TYPES.ImportDeclaration && declaration.source.value;
|
|
4069
|
+
if (source !== "vitest" && source !== "@jest/globals") return null;
|
|
4070
|
+
const { node } = importDefinition;
|
|
4071
|
+
if (node.type === AST_NODE_TYPES.ImportSpecifier && node.imported.type === AST_NODE_TYPES.Identifier) return node.imported.name;
|
|
4072
|
+
return null;
|
|
4073
|
+
}
|
|
3825
4074
|
/**
|
|
3826
4075
|
* Matches a statement that declares the expected assertion count at the top of
|
|
3827
4076
|
* a test: `expect.assertions(n)` or `expect.hasAssertions()`.
|
|
3828
4077
|
*
|
|
3829
|
-
*
|
|
3830
|
-
* `
|
|
3831
|
-
*
|
|
3832
|
-
* `
|
|
3833
|
-
* line.
|
|
4078
|
+
* The `expect` object is resolved so a locally shadowed `expect` is ignored,
|
|
4079
|
+
* while a global or a `"vitest"` / `"@jest/globals"` import is recognized (the
|
|
4080
|
+
* property access is otherwise syntactic — a non-computed `assertions` /
|
|
4081
|
+
* `hasAssertions`).
|
|
3834
4082
|
*
|
|
4083
|
+
* @param sourceCode - Provides the scope used to resolve `expect`.
|
|
3835
4084
|
* @param node - The expression statement to inspect.
|
|
3836
4085
|
* @returns The matched member name (`expect.assertions` / `expect.hasAssertions`)
|
|
3837
4086
|
* for the message, or `undefined` when the statement is not an assertion count.
|
|
3838
4087
|
*/
|
|
3839
|
-
function getAssertionName({ expression }) {
|
|
4088
|
+
function getAssertionName(sourceCode, { expression }) {
|
|
3840
4089
|
if (expression.type !== AST_NODE_TYPES.CallExpression) return;
|
|
3841
4090
|
const { callee } = expression;
|
|
3842
|
-
if (callee.type !== AST_NODE_TYPES.MemberExpression || callee.computed || callee.object.type !== AST_NODE_TYPES.Identifier || callee.
|
|
4091
|
+
if (callee.type !== AST_NODE_TYPES.MemberExpression || callee.computed || callee.object.type !== AST_NODE_TYPES.Identifier || callee.property.type !== AST_NODE_TYPES.Identifier) return;
|
|
3843
4092
|
const { name } = callee.property;
|
|
3844
4093
|
if (name !== "assertions" && name !== "hasAssertions") return;
|
|
4094
|
+
if (resolveTestGlobalName$1(sourceCode, callee.object) !== "expect") return;
|
|
3845
4095
|
return `expect.${name}`;
|
|
3846
4096
|
}
|
|
3847
4097
|
/**
|
|
@@ -3896,15 +4146,15 @@ function findPaddingAnchor(sourceCode, node, nextNode) {
|
|
|
3896
4146
|
* @param context - The rule context.
|
|
3897
4147
|
* @returns The rule listener.
|
|
3898
4148
|
*/
|
|
3899
|
-
function createOnce$
|
|
4149
|
+
function createOnce$6(context) {
|
|
3900
4150
|
return { ExpressionStatement(node) {
|
|
3901
|
-
const
|
|
4151
|
+
const { sourceCode } = context;
|
|
4152
|
+
const name = getAssertionName(sourceCode, node);
|
|
3902
4153
|
if (name === void 0) return;
|
|
3903
4154
|
const list = getContainingList(node);
|
|
3904
4155
|
if (list === void 0) return;
|
|
3905
4156
|
const nextNode = list[list.indexOf(node) + 1];
|
|
3906
4157
|
if (nextNode === void 0) return;
|
|
3907
|
-
const { sourceCode } = context;
|
|
3908
4158
|
const anchor = findPaddingAnchor(sourceCode, node, nextNode);
|
|
3909
4159
|
if (anchor === void 0) return;
|
|
3910
4160
|
const { nextToken, previousToken } = anchor;
|
|
@@ -3916,13 +4166,13 @@ function createOnce$4(context) {
|
|
|
3916
4166
|
return fixer.insertTextAfter(previousToken, gap === 0 ? "\n\n" : "\n");
|
|
3917
4167
|
},
|
|
3918
4168
|
loc: node.loc,
|
|
3919
|
-
messageId: MESSAGE_ID$
|
|
4169
|
+
messageId: MESSAGE_ID$6
|
|
3920
4170
|
});
|
|
3921
4171
|
} };
|
|
3922
4172
|
}
|
|
3923
4173
|
const paddingAfterExpectAssertions = createFlawlessRule({
|
|
3924
|
-
name: RULE_NAME$
|
|
3925
|
-
createOnce: createOnce$
|
|
4174
|
+
name: RULE_NAME$9,
|
|
4175
|
+
createOnce: createOnce$6,
|
|
3926
4176
|
defaultOptions: [],
|
|
3927
4177
|
meta: {
|
|
3928
4178
|
docs: {
|
|
@@ -3932,16 +4182,16 @@ const paddingAfterExpectAssertions = createFlawlessRule({
|
|
|
3932
4182
|
},
|
|
3933
4183
|
fixable: "whitespace",
|
|
3934
4184
|
hasSuggestions: false,
|
|
3935
|
-
messages: messages$
|
|
4185
|
+
messages: messages$9,
|
|
3936
4186
|
schema: [],
|
|
3937
4187
|
type: "layout"
|
|
3938
4188
|
}
|
|
3939
4189
|
});
|
|
3940
4190
|
//#endregion
|
|
3941
4191
|
//#region src/rules/prefer-destructuring-assignment/rule.ts
|
|
3942
|
-
const RULE_NAME$
|
|
3943
|
-
const MESSAGE_ID$
|
|
3944
|
-
const messages$
|
|
4192
|
+
const RULE_NAME$8 = "prefer-destructuring-assignment";
|
|
4193
|
+
const MESSAGE_ID$5 = "default";
|
|
4194
|
+
const messages$8 = { [MESSAGE_ID$5]: "Use destructuring assignment for component props." };
|
|
3945
4195
|
/**
|
|
3946
4196
|
* Identifiers that cannot be used as a binding name in strict-mode module code.
|
|
3947
4197
|
* A property may be accessed with such a name (`props.default`), but a shorthand
|
|
@@ -4067,7 +4317,7 @@ function reportComponent(context, scope, propsParameter, propertyVariable) {
|
|
|
4067
4317
|
const propertyNames = collectPropertyNames(memberReferences);
|
|
4068
4318
|
if (!(!hasNonMemberReference && propertyNames !== null && !wouldShadowExistingBinding(context.sourceCode, scope, propertyVariable, memberReferences))) {
|
|
4069
4319
|
for (const { member } of memberReferences) context.report({
|
|
4070
|
-
messageId: MESSAGE_ID$
|
|
4320
|
+
messageId: MESSAGE_ID$5,
|
|
4071
4321
|
node: member
|
|
4072
4322
|
});
|
|
4073
4323
|
return;
|
|
@@ -4084,7 +4334,7 @@ function reportComponent(context, scope, propsParameter, propertyVariable) {
|
|
|
4084
4334
|
if (index === 0) return [fixer.replaceTextRange([propsParameter.range[0], nameEnd], pattern), replaceAccess];
|
|
4085
4335
|
return replaceAccess;
|
|
4086
4336
|
},
|
|
4087
|
-
messageId: MESSAGE_ID$
|
|
4337
|
+
messageId: MESSAGE_ID$5,
|
|
4088
4338
|
node: member
|
|
4089
4339
|
});
|
|
4090
4340
|
}
|
|
@@ -4103,7 +4353,7 @@ function reportComponent(context, scope, propsParameter, propertyVariable) {
|
|
|
4103
4353
|
* @param context - The rule context.
|
|
4104
4354
|
* @returns The rule listener.
|
|
4105
4355
|
*/
|
|
4106
|
-
function createOnce$
|
|
4356
|
+
function createOnce$5(context) {
|
|
4107
4357
|
let collector = core.getFunctionComponentCollector(context);
|
|
4108
4358
|
const selectorKeys = Object.keys(collector.visitor);
|
|
4109
4359
|
const listener = {
|
|
@@ -4129,8 +4379,8 @@ function createOnce$3(context) {
|
|
|
4129
4379
|
return listener;
|
|
4130
4380
|
}
|
|
4131
4381
|
const preferDestructuringAssignment = createFlawlessRule({
|
|
4132
|
-
name: RULE_NAME$
|
|
4133
|
-
createOnce: createOnce$
|
|
4382
|
+
name: RULE_NAME$8,
|
|
4383
|
+
createOnce: createOnce$5,
|
|
4134
4384
|
defaultOptions: [],
|
|
4135
4385
|
meta: {
|
|
4136
4386
|
docs: {
|
|
@@ -4140,12 +4390,293 @@ const preferDestructuringAssignment = createFlawlessRule({
|
|
|
4140
4390
|
},
|
|
4141
4391
|
fixable: "code",
|
|
4142
4392
|
hasSuggestions: false,
|
|
4143
|
-
messages: messages$
|
|
4393
|
+
messages: messages$8,
|
|
4144
4394
|
schema: [],
|
|
4145
4395
|
type: "problem"
|
|
4146
4396
|
}
|
|
4147
4397
|
});
|
|
4148
4398
|
//#endregion
|
|
4399
|
+
//#region src/rules/prefer-ending-with-an-expect/rule.ts
|
|
4400
|
+
const RULE_NAME$7 = "prefer-ending-with-an-expect";
|
|
4401
|
+
const MESSAGE_ID$4 = "mustEndWithExpect";
|
|
4402
|
+
const DEFAULTS = {
|
|
4403
|
+
additionalTestBlockFunctions: [],
|
|
4404
|
+
assertFunctionNames: ["expect"]
|
|
4405
|
+
};
|
|
4406
|
+
/** Callee identifiers that name a vitest test block (`describe` is excluded). */
|
|
4407
|
+
const TEST_BLOCK_NAMES = /* @__PURE__ */ new Set(["it", "test"]);
|
|
4408
|
+
const messages$7 = { [MESSAGE_ID$4]: "Test should end with an assertion." };
|
|
4409
|
+
const schema$2 = [{
|
|
4410
|
+
additionalProperties: false,
|
|
4411
|
+
properties: {
|
|
4412
|
+
additionalTestBlockFunctions: {
|
|
4413
|
+
description: "Callee names, besides a resolved vitest it/test, whose second argument is a test body (matched by exact dotted name).",
|
|
4414
|
+
items: { type: "string" },
|
|
4415
|
+
type: "array"
|
|
4416
|
+
},
|
|
4417
|
+
assertFunctionNames: {
|
|
4418
|
+
description: "Function names, besides a resolved vitest expect, that count as an assertion (matched by name; * and ** are wildcards).",
|
|
4419
|
+
items: { type: "string" },
|
|
4420
|
+
type: "array"
|
|
4421
|
+
}
|
|
4422
|
+
},
|
|
4423
|
+
type: "object"
|
|
4424
|
+
}];
|
|
4425
|
+
/**
|
|
4426
|
+
* Tests a dotted callee name against the `assertFunctionNames` patterns. A `*`
|
|
4427
|
+
* stands for a single dotted segment and `**` for any run of segments, so
|
|
4428
|
+
* `request.*.expect` and `request.**.expect` both match a chained assertion.
|
|
4429
|
+
* Ported from eslint-plugin-jest.
|
|
4430
|
+
*
|
|
4431
|
+
* @param nodeName - The dotted callee name (e.g. `expect.toBe`).
|
|
4432
|
+
* @param patterns - The configured assertion-name patterns.
|
|
4433
|
+
* @returns `true` when any pattern matches the name.
|
|
4434
|
+
*/
|
|
4435
|
+
function matchesAssertFunctionName(nodeName, patterns) {
|
|
4436
|
+
return patterns.some((pattern) => {
|
|
4437
|
+
return new RegExp(`^${pattern.split(".").map((segment) => {
|
|
4438
|
+
if (segment === "**") return "[a-z\\d\\.]*";
|
|
4439
|
+
return segment.replace(/\*/gu, "[a-z\\d]*");
|
|
4440
|
+
}).join("\\.")}(\\.|$)`, "ui").test(nodeName);
|
|
4441
|
+
});
|
|
4442
|
+
}
|
|
4443
|
+
/**
|
|
4444
|
+
* Builds the dotted name of a callee chain, unwrapping intervening calls
|
|
4445
|
+
* (`request(app).get("/").expect` -> `request.get.expect`) the way
|
|
4446
|
+
* eslint-plugin-jest's `getNodeChain` does. A computed member access yields
|
|
4447
|
+
* `null`, since its property is not a static name.
|
|
4448
|
+
*
|
|
4449
|
+
* @param node - The callee node.
|
|
4450
|
+
* @returns The dotted name, or `null` when it cannot be built statically.
|
|
4451
|
+
*/
|
|
4452
|
+
function getNodeName(node) {
|
|
4453
|
+
if (node.type === AST_NODE_TYPES.Identifier) return node.name;
|
|
4454
|
+
if (node.type === AST_NODE_TYPES.CallExpression) return getNodeName(node.callee);
|
|
4455
|
+
if (node.type === AST_NODE_TYPES.MemberExpression && !node.computed && node.property.type === AST_NODE_TYPES.Identifier) {
|
|
4456
|
+
const objectName = getNodeName(node.object);
|
|
4457
|
+
return objectName === null ? null : `${objectName}.${node.property.name}`;
|
|
4458
|
+
}
|
|
4459
|
+
return null;
|
|
4460
|
+
}
|
|
4461
|
+
/**
|
|
4462
|
+
* Walks a callee chain down to the identifier it is rooted at, stepping through
|
|
4463
|
+
* member accesses (`it.each` -> `it`) and intervening calls
|
|
4464
|
+
* (`it.each(cases)()` -> `it`).
|
|
4465
|
+
*
|
|
4466
|
+
* @param node - The callee node.
|
|
4467
|
+
* @returns The root identifier, or `null` when the chain is not rooted at one.
|
|
4468
|
+
*/
|
|
4469
|
+
function getRootIdentifier(node) {
|
|
4470
|
+
let current = node;
|
|
4471
|
+
for (;;) {
|
|
4472
|
+
if (current.type === AST_NODE_TYPES.Identifier) return current;
|
|
4473
|
+
if (current.type === AST_NODE_TYPES.CallExpression) {
|
|
4474
|
+
current = current.callee;
|
|
4475
|
+
continue;
|
|
4476
|
+
}
|
|
4477
|
+
if (current.type === AST_NODE_TYPES.MemberExpression) {
|
|
4478
|
+
current = current.object;
|
|
4479
|
+
continue;
|
|
4480
|
+
}
|
|
4481
|
+
return null;
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4484
|
+
/**
|
|
4485
|
+
* Resolves the vitest name an identifier refers to, mirroring how
|
|
4486
|
+
* eslint-plugin-jest resolves jest functions. An unresolved reference is a
|
|
4487
|
+
* global (vitest's `globals: true` / `@vitest/globals`); a named import from
|
|
4488
|
+
* `"vitest"` resolves to its imported name (so aliases work); anything bound to
|
|
4489
|
+
* a local variable, function, or parameter resolves to `null` and is ignored.
|
|
4490
|
+
*
|
|
4491
|
+
* @param sourceCode - Provides the scope used to look up the binding.
|
|
4492
|
+
* @param identifier - The identifier to resolve.
|
|
4493
|
+
* @returns The vitest name (`it`/`test`/`expect`/...), or `null` when the
|
|
4494
|
+
* identifier is a local binding rather than a vitest global or import.
|
|
4495
|
+
*/
|
|
4496
|
+
function resolveVitestName(sourceCode, identifier) {
|
|
4497
|
+
const variable = findVariable(sourceCode.getScope(identifier), identifier);
|
|
4498
|
+
if (variable === null) return identifier.name;
|
|
4499
|
+
const definition = variable.defs.at(0);
|
|
4500
|
+
if (definition === void 0) return identifier.name;
|
|
4501
|
+
if (definition.type !== DefinitionType.ImportBinding) return null;
|
|
4502
|
+
const importDefinition = definition;
|
|
4503
|
+
const declaration = importDefinition.parent;
|
|
4504
|
+
if (declaration.type !== AST_NODE_TYPES.ImportDeclaration || declaration.source.value !== "vitest") return null;
|
|
4505
|
+
const { node } = importDefinition;
|
|
4506
|
+
if (node.type === AST_NODE_TYPES.ImportSpecifier && node.imported.type === AST_NODE_TYPES.Identifier) return node.imported.name;
|
|
4507
|
+
return null;
|
|
4508
|
+
}
|
|
4509
|
+
/**
|
|
4510
|
+
* Returns the node that ends a test body: the last statement of a block body
|
|
4511
|
+
* (unwrapped to its expression when it is an expression statement), or the
|
|
4512
|
+
* expression of a concise arrow body. Ported from eslint-plugin-jest.
|
|
4513
|
+
*
|
|
4514
|
+
* @param func - The test callback.
|
|
4515
|
+
* @returns The ending node, or `null` for an empty block body.
|
|
4516
|
+
*/
|
|
4517
|
+
function getLastStatement(func) {
|
|
4518
|
+
if (func.body.type !== AST_NODE_TYPES.BlockStatement) return func.body;
|
|
4519
|
+
const lastStatement = func.body.body.at(-1);
|
|
4520
|
+
if (lastStatement === void 0) return null;
|
|
4521
|
+
if (lastStatement.type === AST_NODE_TYPES.ExpressionStatement) return lastStatement.expression;
|
|
4522
|
+
return lastStatement;
|
|
4523
|
+
}
|
|
4524
|
+
/**
|
|
4525
|
+
* Flags a test whose last statement is not an assertion, a common sign of an
|
|
4526
|
+
* unfinished test. Ported from eslint-plugin-jest's
|
|
4527
|
+
* `prefer-ending-with-an-expect`, with vitest-aware resolution of `it`/`test`
|
|
4528
|
+
* and `expect`.
|
|
4529
|
+
*
|
|
4530
|
+
* @param context - The rule context.
|
|
4531
|
+
* @returns The rule listener.
|
|
4532
|
+
*/
|
|
4533
|
+
function createOnce$4(context) {
|
|
4534
|
+
let config;
|
|
4535
|
+
let sourceCode;
|
|
4536
|
+
/**
|
|
4537
|
+
* Determines whether a call is a test block whose second argument is a body.
|
|
4538
|
+
*
|
|
4539
|
+
* @param node - The call to inspect.
|
|
4540
|
+
* @returns `true` when the call opens a vitest (or configured) test.
|
|
4541
|
+
*/
|
|
4542
|
+
function isTestBlock(node) {
|
|
4543
|
+
const root = getRootIdentifier(node.callee);
|
|
4544
|
+
if (root !== null && TEST_BLOCK_NAMES.has(resolveVitestName(sourceCode, root) ?? "")) return true;
|
|
4545
|
+
const name = getNodeName(node.callee);
|
|
4546
|
+
return name !== null && config.additionalTestBlockFunctions.includes(name);
|
|
4547
|
+
}
|
|
4548
|
+
/**
|
|
4549
|
+
* Determines whether a call is an assertion: a resolved vitest `expect`, or a
|
|
4550
|
+
* call whose dotted name matches an `assertFunctionNames` pattern.
|
|
4551
|
+
*
|
|
4552
|
+
* @param node - The call ending the test body.
|
|
4553
|
+
* @returns `true` when the call counts as an assertion.
|
|
4554
|
+
*/
|
|
4555
|
+
function isAssertion(node) {
|
|
4556
|
+
const root = getRootIdentifier(node.callee);
|
|
4557
|
+
if (root !== null && resolveVitestName(sourceCode, root) === "expect") return true;
|
|
4558
|
+
return matchesAssertFunctionName(getNodeName(node.callee) ?? "", config.assertFunctionNames);
|
|
4559
|
+
}
|
|
4560
|
+
return {
|
|
4561
|
+
before() {
|
|
4562
|
+
const options = context.options[0];
|
|
4563
|
+
config = {
|
|
4564
|
+
additionalTestBlockFunctions: options?.additionalTestBlockFunctions ?? DEFAULTS.additionalTestBlockFunctions,
|
|
4565
|
+
assertFunctionNames: options?.assertFunctionNames ?? DEFAULTS.assertFunctionNames
|
|
4566
|
+
};
|
|
4567
|
+
({sourceCode} = context);
|
|
4568
|
+
},
|
|
4569
|
+
CallExpression(node) {
|
|
4570
|
+
if (!isTestBlock(node)) return;
|
|
4571
|
+
const callback = node.arguments[1];
|
|
4572
|
+
if (callback === void 0 || !ASTUtils.isFunction(callback)) return;
|
|
4573
|
+
let lastStatement = getLastStatement(callback);
|
|
4574
|
+
if (lastStatement?.type === AST_NODE_TYPES.AwaitExpression) lastStatement = lastStatement.argument;
|
|
4575
|
+
if (lastStatement?.type === AST_NODE_TYPES.CallExpression && isAssertion(lastStatement)) return;
|
|
4576
|
+
context.report({
|
|
4577
|
+
messageId: MESSAGE_ID$4,
|
|
4578
|
+
node: node.callee
|
|
4579
|
+
});
|
|
4580
|
+
}
|
|
4581
|
+
};
|
|
4582
|
+
}
|
|
4583
|
+
const preferEndingWithAnExpect = createFlawlessRule({
|
|
4584
|
+
name: RULE_NAME$7,
|
|
4585
|
+
createOnce: createOnce$4,
|
|
4586
|
+
defaultOptions: [DEFAULTS],
|
|
4587
|
+
meta: {
|
|
4588
|
+
defaultOptions: [DEFAULTS],
|
|
4589
|
+
docs: {
|
|
4590
|
+
description: "Prefer having the last statement in a test be an assertion",
|
|
4591
|
+
recommended: false,
|
|
4592
|
+
requiresTypeChecking: false
|
|
4593
|
+
},
|
|
4594
|
+
hasSuggestions: false,
|
|
4595
|
+
messages: messages$7,
|
|
4596
|
+
schema: schema$2,
|
|
4597
|
+
type: "suggestion"
|
|
4598
|
+
}
|
|
4599
|
+
});
|
|
4600
|
+
//#endregion
|
|
4601
|
+
//#region src/rules/prefer-expect-assertions-count/rule.ts
|
|
4602
|
+
const RULE_NAME$6 = "prefer-expect-assertions-count";
|
|
4603
|
+
const MESSAGE_ID$3 = "preferCount";
|
|
4604
|
+
const messages$6 = { [MESSAGE_ID$3]: "Use `expect.assertions(<count>)` with an explicit count instead of `expect.hasAssertions()`." };
|
|
4605
|
+
/**
|
|
4606
|
+
* Resolves the name an identifier refers to, mirroring how eslint-plugin-jest
|
|
4607
|
+
* resolves test functions. An unresolved reference is a global (vitest's
|
|
4608
|
+
* `globals: true` / jest's injected globals); a named import from `"vitest"` or
|
|
4609
|
+
* `"@jest/globals"` resolves to its imported name (so aliases work); anything
|
|
4610
|
+
* bound to a local variable, function, or parameter resolves to `null` and is
|
|
4611
|
+
* ignored.
|
|
4612
|
+
*
|
|
4613
|
+
* @param sourceCode - Provides the scope used to look up the binding.
|
|
4614
|
+
* @param identifier - The identifier to resolve.
|
|
4615
|
+
* @returns The resolved name (`expect`/...), or `null` when the identifier is a
|
|
4616
|
+
* local binding rather than a test global or a vitest/jest import.
|
|
4617
|
+
*/
|
|
4618
|
+
function resolveTestGlobalName(sourceCode, identifier) {
|
|
4619
|
+
const variable = findVariable(sourceCode.getScope(identifier), identifier);
|
|
4620
|
+
if (variable === null) return identifier.name;
|
|
4621
|
+
const definition = variable.defs.at(0);
|
|
4622
|
+
if (definition === void 0) return identifier.name;
|
|
4623
|
+
if (definition.type !== DefinitionType.ImportBinding) return null;
|
|
4624
|
+
const importDefinition = definition;
|
|
4625
|
+
const declaration = importDefinition.parent;
|
|
4626
|
+
const source = declaration.type === AST_NODE_TYPES.ImportDeclaration && declaration.source.value;
|
|
4627
|
+
if (source !== "vitest" && source !== "@jest/globals") return null;
|
|
4628
|
+
const { node } = importDefinition;
|
|
4629
|
+
if (node.type === AST_NODE_TYPES.ImportSpecifier && node.imported.type === AST_NODE_TYPES.Identifier) return node.imported.name;
|
|
4630
|
+
return null;
|
|
4631
|
+
}
|
|
4632
|
+
/**
|
|
4633
|
+
* Matches a `expect.hasAssertions()` call. The callee must be a non-computed
|
|
4634
|
+
* `<expect>.hasAssertions` member access whose object resolves to a vitest/jest
|
|
4635
|
+
* `expect` (a global or an import from `"vitest"` / `"@jest/globals"`); a locally
|
|
4636
|
+
* shadowed `expect` is ignored. Covers either framework, since both name the
|
|
4637
|
+
* global `expect`.
|
|
4638
|
+
*
|
|
4639
|
+
* @param sourceCode - Provides the scope used to resolve `expect`.
|
|
4640
|
+
* @param callee - The call's callee.
|
|
4641
|
+
* @returns `true` when the callee is a resolved `expect.hasAssertions`.
|
|
4642
|
+
*/
|
|
4643
|
+
function isExpectHasAssertions(sourceCode, callee) {
|
|
4644
|
+
return callee.type === AST_NODE_TYPES.MemberExpression && !callee.computed && callee.object.type === AST_NODE_TYPES.Identifier && callee.property.type === AST_NODE_TYPES.Identifier && callee.property.name === "hasAssertions" && resolveTestGlobalName(sourceCode, callee.object) === "expect";
|
|
4645
|
+
}
|
|
4646
|
+
/**
|
|
4647
|
+
* Flags `expect.hasAssertions()`, which only asserts that at least one assertion
|
|
4648
|
+
* ran, in favour of `expect.assertions(<count>)`, which pins the exact count and
|
|
4649
|
+
* so catches an expectation skipped by an early return or a branch never taken.
|
|
4650
|
+
*
|
|
4651
|
+
* @param context - The rule context.
|
|
4652
|
+
* @returns The rule listener.
|
|
4653
|
+
*/
|
|
4654
|
+
function createOnce$3(context) {
|
|
4655
|
+
return { CallExpression(node) {
|
|
4656
|
+
if (!isExpectHasAssertions(context.sourceCode, node.callee)) return;
|
|
4657
|
+
context.report({
|
|
4658
|
+
messageId: MESSAGE_ID$3,
|
|
4659
|
+
node
|
|
4660
|
+
});
|
|
4661
|
+
} };
|
|
4662
|
+
}
|
|
4663
|
+
const preferExpectAssertionsCount = createFlawlessRule({
|
|
4664
|
+
name: RULE_NAME$6,
|
|
4665
|
+
createOnce: createOnce$3,
|
|
4666
|
+
defaultOptions: [],
|
|
4667
|
+
meta: {
|
|
4668
|
+
docs: {
|
|
4669
|
+
description: "Prefer `expect.assertions(<count>)` over `expect.hasAssertions()`",
|
|
4670
|
+
recommended: false,
|
|
4671
|
+
requiresTypeChecking: false
|
|
4672
|
+
},
|
|
4673
|
+
hasSuggestions: false,
|
|
4674
|
+
messages: messages$6,
|
|
4675
|
+
schema: [],
|
|
4676
|
+
type: "suggestion"
|
|
4677
|
+
}
|
|
4678
|
+
});
|
|
4679
|
+
//#endregion
|
|
4149
4680
|
//#region src/rules/prefer-parameter-destructuring/rule.ts
|
|
4150
4681
|
const RULE_NAME$5 = "prefer-parameter-destructuring";
|
|
4151
4682
|
const MESSAGE_ID$2 = "default";
|
|
@@ -5823,12 +6354,15 @@ const plugin = {
|
|
|
5823
6354
|
"jsx-shorthand-fragment": jsxShorthandFragment,
|
|
5824
6355
|
"max-lines-per-function": maxLinesPerFunction,
|
|
5825
6356
|
"naming-convention": namingConvention,
|
|
6357
|
+
"no-conditional-in-test": noConditionalInTest,
|
|
5826
6358
|
"no-export-default-arrow": noExportDefaultArrow,
|
|
5827
6359
|
"no-redundant-tsconfig-options": noRedundantTsconfigOptions,
|
|
5828
6360
|
"no-unnecessary-use-callback": noUnnecessaryUseCallback,
|
|
5829
6361
|
"no-unnecessary-use-memo": noUnnecessaryUseMemo,
|
|
5830
6362
|
"padding-after-expect-assertions": paddingAfterExpectAssertions,
|
|
5831
6363
|
"prefer-destructuring-assignment": preferDestructuringAssignment,
|
|
6364
|
+
"prefer-ending-with-an-expect": preferEndingWithAnExpect,
|
|
6365
|
+
"prefer-expect-assertions-count": preferExpectAssertionsCount,
|
|
5832
6366
|
"prefer-parameter-destructuring": preferParameterDestructuring,
|
|
5833
6367
|
"prefer-read-only-props": preferReadOnlyProps,
|
|
5834
6368
|
"purity": purity,
|