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 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$6 = [{
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$5 = [JsxShorthandFragmentOptions?];
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$4 = [MaxLinesPerFunctionOptions?];
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$3 = Array<NamingSelector>;
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$6, MessageIds$5, Readonly<TSESLint.SourceCode>>;
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$5, MessageIds$4, Readonly<TSESLint.SourceCode>>;
311
- "max-lines-per-function": FlawlessRuleModule<Options$4, "exceed", Readonly<TSESLint.SourceCode>>;
312
- "naming-convention": TSESLint.RuleModule<MessageIds$3, Options$3, PluginDocumentation, TSESLint.RuleListener> & {
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$6, MessageIds$5, Readonly<import("${configDir}").SourceCode>>;
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$5, MessageIds$4, Readonly<import("${configDir}").SourceCode>>;
356
- "max-lines-per-function": FlawlessRuleModule<Options$4, "exceed", Readonly<import("${configDir}").SourceCode>>;
357
- "naming-convention": import("${configDir}").RuleModule<MessageIds$3, Options$3, PluginDocumentation, import("${configDir}").RuleListener> & {
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$6, MessageIds$5, Readonly<import("${configDir}").SourceCode>>;
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$5, MessageIds$4, Readonly<import("${configDir}").SourceCode>>;
398
- "max-lines-per-function": FlawlessRuleModule<Options$4, "exceed", Readonly<import("${configDir}").SourceCode>>;
399
- "naming-convention": import("${configDir}").RuleModule<MessageIds$3, Options$3, PluginDocumentation, import("${configDir}").RuleListener> & {
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
@@ -1,4 +1,4 @@
1
- import { n as plugin, t as PLUGIN_NAME } from "./plugin-DfRbpogP.mjs";
1
+ import { n as plugin, t as PLUGIN_NAME } from "./plugin-BVNImiZB.mjs";
2
2
  //#region src/configs/index.ts
3
3
  const configs = { recommended: {
4
4
  plugins: { [PLUGIN_NAME]: plugin },
package/dist/oxlint.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as plugin, t as PLUGIN_NAME } from "./plugin-DfRbpogP.mjs";
1
+ import { n as plugin, t as PLUGIN_NAME } from "./plugin-BVNImiZB.mjs";
2
2
  import { definePlugin } from "@oxlint/plugins";
3
3
  //#region src/oxlint.ts
4
4
  /**
@@ -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.2.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$16 = "arrow-return-style";
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$1 = {
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$16,
235
+ name: RULE_NAME$19,
236
236
  createOnce(context) {
237
- let config = DEFAULTS$1;
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$1,
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$1],
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$15 = "jsx-shorthand-boolean";
602
- const MESSAGE_ID$7 = "setAttributeValue";
603
- const messages$15 = { [MESSAGE_ID$7]: "Set an explicit value for boolean attribute '{{name}}'." };
604
- function createOnce$10(context) {
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$7,
610
+ messageId: MESSAGE_ID$10,
611
611
  node
612
612
  });
613
613
  } };
614
614
  }
615
615
  const jsxShorthandBoolean = createFlawlessRule({
616
- name: RULE_NAME$15,
617
- createOnce: createOnce$10,
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$15,
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$14 = "jsx-shorthand-fragment";
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$14 = {
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$3 = [{
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$9(context) {
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$14,
723
- createOnce: createOnce$9,
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$14,
741
- schema: schema$3,
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$13 = "max-lines-per-function";
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$13 = { [MESSAGE_ID_EXCEED]: "{{name}} has too many lines ({{lineCount}}). Maximum allowed is {{maxLines}}." };
892
- const schema$2 = [{
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$8(context) {
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$13,
1051
- createOnce: createOnce$8,
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$13,
1062
- schema: schema$2,
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$12 = "naming-convention";
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$12 = {
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$12,
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$12,
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$11 = "no-export-default-arrow";
3026
- const MESSAGE_ID$6 = "disallowExportDefaultArrow";
3027
- const messages$11 = { [MESSAGE_ID$6]: "Assign the arrow function to a named constant instead of exporting it anonymously." };
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$7(context) {
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$6,
3338
+ messageId: MESSAGE_ID$8,
3117
3339
  node
3118
3340
  });
3119
3341
  } };
3120
3342
  }
3121
3343
  const noExportDefaultArrow = createFlawlessRule({
3122
- name: RULE_NAME$11,
3123
- createOnce: createOnce$7,
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$11,
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$10 = "no-redundant-tsconfig-options";
3299
- const MESSAGE_ID$5 = "redundant";
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$10 = { [MESSAGE_ID$5]: "'{{option}}' is redundant: it is already set to this value by {{source}}. Remove it." };
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$5
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$10,
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$10,
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$9 = "no-unnecessary-use-callback";
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$9 = {
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$9,
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$9,
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$8 = "no-unnecessary-use-memo";
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$8 = {
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$8,
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$8,
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$7 = "padding-after-expect-assertions";
3823
- const MESSAGE_ID$4 = "missingPadding";
3824
- const messages$7 = { [MESSAGE_ID$4]: "Expected a blank line after '{{name}}'." };
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
- * Detection is purely syntactic (a non-computed `expect.assertions` /
3830
- * `expect.hasAssertions` call) with no scope analysis, mirroring the deliberate
3831
- * looseness of `@vitest/eslint-plugin`'s own padding rules. A shadowed local
3832
- * `expect` is vanishingly rare, and the only consequence is a harmless blank
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.object.name !== "expect" || callee.property.type !== AST_NODE_TYPES.Identifier) return;
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$4(context) {
4149
+ function createOnce$6(context) {
3900
4150
  return { ExpressionStatement(node) {
3901
- const name = getAssertionName(node);
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$4
4169
+ messageId: MESSAGE_ID$6
3920
4170
  });
3921
4171
  } };
3922
4172
  }
3923
4173
  const paddingAfterExpectAssertions = createFlawlessRule({
3924
- name: RULE_NAME$7,
3925
- createOnce: createOnce$4,
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$7,
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$6 = "prefer-destructuring-assignment";
3943
- const MESSAGE_ID$3 = "default";
3944
- const messages$6 = { [MESSAGE_ID$3]: "Use destructuring assignment for component props." };
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$3,
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$3,
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$3(context) {
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$6,
4133
- createOnce: createOnce$3,
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$6,
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-flawless",
3
- "version": "1.2.1",
3
+ "version": "1.4.0",
4
4
  "description": "Your ESLint plugin description",
5
5
  "keywords": [
6
6
  "eslint",