eslint-plugin-test-governance 0.3.0 → 0.3.1

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/CHANGELOG.md CHANGED
@@ -6,6 +6,19 @@ All notable changes to this project are documented here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.1] - 2026-10-06
10
+
11
+ ### Fixed
12
+
13
+ - A `test` bound to another test runner without an import, as in `const test = require('node:test')`,
14
+ `const test = require('ava')` or `const test = anyTest as TestFn<Context>`, is no longer mistaken
15
+ for Playwright's `test`. Before, skipped tests in those files were reported as missing a ticket.
16
+ - `require-ticket-in-comments` reads a ticket in parentheses after a space, as in `// TODO (WEB-123)`.
17
+ Before, such comments were reported as having no ticket at all.
18
+ - A test or describe whose body is not written inline, such as `test.skip('pays', payWithCard)` or
19
+ `test('pays @new', withPage(async () => {}))`, is checked like any other. Before, it was ignored,
20
+ so a skipped test written this way needed no ticket.
21
+
9
22
  ## [0.3.0] - 2026-10-06
10
23
 
11
24
  ### Added
@@ -121,7 +134,8 @@ First release.
121
134
  - Support for ESLint 9 and 10 flat config, JavaScript and TypeScript test files, `.extend()`
122
135
  fixtures, describe blocks, runtime skips and tags in titles or the `tag` option.
123
136
 
124
- [Unreleased]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.3.0...HEAD
137
+ [Unreleased]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.3.1...HEAD
138
+ [0.3.1]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.3.0...v0.3.1
125
139
  [0.3.0]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.2.1...v0.3.0
126
140
  [0.2.1]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.2.0...v0.2.1
127
141
  [0.2.0]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.1.1...v0.2.0
package/README.md CHANGED
@@ -172,8 +172,9 @@ so keep it free of nested quantifiers.
172
172
  - Any name in `testFunctions`, for fixtures imported from your own modules. The default `['test']`
173
173
  covers the common `import { test } from './fixtures'`.
174
174
 
175
- A name imported from another test runner (`vitest`, `@jest/globals`, `node:test`, `bun:test`,
176
- `mocha`, `ava`, `tap`, `uvu`) is never treated as Playwright's `test`, and neither is a local
175
+ A name imported or required from another test runner (`vitest`, `@jest/globals`, `node:test`,
176
+ `bun:test`, `mocha`, `ava`, `tap`, `uvu`), or a variable set from one such as
177
+ `const test = require('ava')`, is never treated as Playwright's `test`, and neither is a local
177
178
  variable declared inside a function that happens to be called `test`.
178
179
 
179
180
  ### Syntax the rules understand
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { ESLint, Rule } from 'eslint';
2
- import { type GovernanceOptions } from './utils/options.js';
3
- export type { CustomState, GovernanceOptions, StateOverride } from './utils/options.js';
4
- export type { TicketSpec } from './utils/tickets.js';
2
+ import { type GovernanceOptions } from './utils/options.ts';
3
+ export type { CustomState, GovernanceOptions, StateOverride } from './utils/options.ts';
4
+ export type { TicketSpec } from './utils/tickets.ts';
5
5
  export type RuleName = 'require-ticket' | 'marker-matches-state' | 'no-orphaned-marker' | 'no-conflicting-states' | 'require-ticket-in-comments';
6
6
  export declare const rules: Record<RuleName, Rule.RuleModule>;
7
7
  export interface FlatConfig {
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { createRequire } from 'node:module';
2
- import markerMatchesState from './rules/marker-matches-state.js';
3
- import noConflictingStates from './rules/no-conflicting-states.js';
4
- import noOrphanedMarker from './rules/no-orphaned-marker.js';
5
- import requireTicketInComments from './rules/require-ticket-in-comments.js';
6
- import requireTicket from './rules/require-ticket.js';
7
- import { compileOptions, SETTINGS_KEY } from './utils/options.js';
2
+ import markerMatchesState from "./rules/marker-matches-state.js";
3
+ import noConflictingStates from "./rules/no-conflicting-states.js";
4
+ import noOrphanedMarker from "./rules/no-orphaned-marker.js";
5
+ import requireTicketInComments from "./rules/require-ticket-in-comments.js";
6
+ import requireTicket from "./rules/require-ticket.js";
7
+ import { compileOptions, SETTINGS_KEY } from "./utils/options.js";
8
8
  const PLUGIN_NAME = 'test-governance';
9
9
  // Read at load time so the reported version always matches the published package.
10
10
  const { version: VERSION } = createRequire(import.meta.url)('../package.json');
@@ -1,3 +1,3 @@
1
- declare const _default: import("../utils/create-rule.js").Rule<"wrongMarker" | "sharedMarker" | "renameMarker" | "addMarker">;
1
+ declare const _default: import("../utils/create-rule.ts").Rule<"wrongMarker" | "sharedMarker" | "renameMarker" | "addMarker">;
2
2
  export default _default;
3
3
  //# sourceMappingURL=marker-matches-state.d.ts.map
@@ -1,6 +1,6 @@
1
- import { appliesTo, describeSubject, isStateMarker, missingStates, strayMarkers } from '../utils/analyze.js';
2
- import { createRule } from '../utils/create-rule.js';
3
- import { indentOf, keywordRange } from '../utils/fix.js';
1
+ import { appliesTo, describeSubject, isStateMarker, missingStates, strayMarkers } from "../utils/analyze.js";
2
+ import { createRule } from "../utils/create-rule.js";
3
+ import { indentOf, keywordRange } from "../utils/fix.js";
4
4
  export default createRule({
5
5
  name: 'marker-matches-state',
6
6
  meta: {
@@ -1,3 +1,3 @@
1
- declare const _default: import("../utils/create-rule.js").Rule<"newAndUnstable" | "newSkipped" | "tagCase" | "tagTypo">;
1
+ declare const _default: import("../utils/create-rule.ts").Rule<"newAndUnstable" | "newSkipped" | "tagCase" | "tagTypo">;
2
2
  export default _default;
3
3
  //# sourceMappingURL=no-conflicting-states.d.ts.map
@@ -1,8 +1,8 @@
1
- import { headLoc } from '../utils/analyze.js';
2
- import { createRule } from '../utils/create-rule.js';
3
- import { isTagState } from '../utils/options.js';
4
- import { editDistance, typoThreshold } from '../utils/distance.js';
5
- import { tagRange } from '../utils/fix.js';
1
+ import { headLoc } from "../utils/analyze.js";
2
+ import { createRule } from "../utils/create-rule.js";
3
+ import { isTagState } from "../utils/options.js";
4
+ import { editDistance, typoThreshold } from "../utils/distance.js";
5
+ import { tagRange } from "../utils/fix.js";
6
6
  export default createRule({
7
7
  name: 'no-conflicting-states',
8
8
  meta: {
@@ -1,3 +1,3 @@
1
- declare const _default: import("../utils/create-rule.js").Rule<"orphaned" | "detached" | "removeMarker">;
1
+ declare const _default: import("../utils/create-rule.ts").Rule<"orphaned" | "detached" | "removeMarker">;
2
2
  export default _default;
3
3
  //# sourceMappingURL=no-orphaned-marker.d.ts.map
@@ -1,6 +1,6 @@
1
- import { describeSubject, isProse, missingStates, strayMarkers } from '../utils/analyze.js';
2
- import { createRule } from '../utils/create-rule.js';
3
- import { removeComment } from '../utils/fix.js';
1
+ import { describeSubject, isProse, missingStates, strayMarkers } from "../utils/analyze.js";
2
+ import { createRule } from "../utils/create-rule.js";
3
+ import { removeComment } from "../utils/fix.js";
4
4
  export default createRule({
5
5
  name: 'no-orphaned-marker',
6
6
  meta: {
@@ -1,3 +1,3 @@
1
- declare const _default: import("../utils/create-rule.js").Rule<"missingTicket" | "invalidTicket" | "placeholderTicket" | "extraText" | "removeExtraText">;
1
+ declare const _default: import("../utils/create-rule.ts").Rule<"missingTicket" | "invalidTicket" | "placeholderTicket" | "extraText" | "removeExtraText">;
2
2
  export default _default;
3
3
  //# sourceMappingURL=require-ticket-in-comments.d.ts.map
@@ -1,6 +1,6 @@
1
- import { appliesTo, isStateMarker, parseTickets } from '../utils/analyze.js';
2
- import { createRule } from '../utils/create-rule.js';
3
- import { compileTicketSpec } from '../utils/tickets.js';
1
+ import { appliesTo, isStateMarker, parseTickets } from "../utils/analyze.js";
2
+ import { createRule } from "../utils/create-rule.js";
3
+ import { compileTicketSpec } from "../utils/tickets.js";
4
4
  /** Recognises anything shaped like a ticket, to tell a wrong-format ticket from plain prose. */
5
5
  const ticketShape = compileTicketSpec([{ preset: 'any' }], []);
6
6
  function escapeRegExp(text) {
@@ -40,9 +40,9 @@ export default createRule({
40
40
  // `TODO`/`FIXME` in capitals counts with or without a colon. Other casings need the colon, so a
41
41
  // sentence that happens to start with "Todo" is not mistaken for a work comment. The keyword can
42
42
  // carry its ticket in parentheses, `TODO(WEB-123): ...`, a common style that tools also use for
43
- // a name, `TODO(alice)`, which is not a ticket.
43
+ // a name, `TODO(alice)`, which is not a ticket. Some teams put a space before the parenthesis.
44
44
  const alternatives = keywords.map(escapeRegExp).join('|');
45
- const keywordRe = new RegExp(`^(\\s*\\*?\\s*)(${alternatives})(?:\\(([^)]*)\\))?(?:\\s*:|(?=\\s|$))`, 'i');
45
+ const keywordRe = new RegExp(`^(\\s*\\*?\\s*)(${alternatives})(?:\\s*\\(([^)]*)\\))?(?:\\s*:|(?=\\s|$))`, 'i');
46
46
  /**
47
47
  * `TODO(WEB-1, WEB-2): text`: the tickets are in the parentheses, and anything after them, from the
48
48
  * closing parenthesis on, is a note.
@@ -78,7 +78,7 @@ export default createRule({
78
78
  continue;
79
79
  const { tickets, extra } = inParens === undefined
80
80
  ? parseTickets(line.slice(whole.length), start + whole.length, matcher)
81
- : parseParenthesized(line, start, indent.length + keyword.length, inParens);
81
+ : parseParenthesized(line, start, whole.indexOf('(', indent.length + keyword.length), inParens);
82
82
  const loc = {
83
83
  start: sourceCode.getLocFromIndex(start + indent.length),
84
84
  end: sourceCode.getLocFromIndex(start + line.trimEnd().length),
@@ -1,3 +1,3 @@
1
- declare const _default: import("../utils/create-rule.js").Rule<"missingTicket" | "invalidTicket" | "placeholderTicket" | "extraText" | "removeExtraText" | "missingMarker" | "markerCase" | "dynamicTitle">;
1
+ declare const _default: import("../utils/create-rule.ts").Rule<"missingTicket" | "invalidTicket" | "placeholderTicket" | "extraText" | "removeExtraText" | "missingMarker" | "markerCase" | "dynamicTitle">;
2
2
  export default _default;
3
3
  //# sourceMappingURL=require-ticket.d.ts.map
@@ -1,5 +1,5 @@
1
- import { appliesTo, describeSubject, evaluate, headLoc } from '../utils/analyze.js';
2
- import { createRule } from '../utils/create-rule.js';
1
+ import { appliesTo, describeSubject, evaluate, headLoc } from "../utils/analyze.js";
2
+ import { createRule } from "../utils/create-rule.js";
3
3
  export default createRule({
4
4
  name: 'require-ticket',
5
5
  meta: {
@@ -1,6 +1,6 @@
1
1
  import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
2
- import type { ResolvedOptions, StateDef } from './options.js';
3
- import type { TicketCheck, TicketMatcher } from './tickets.js';
2
+ import type { ResolvedOptions, StateDef } from './options.ts';
3
+ import type { TicketCheck, TicketMatcher } from './tickets.ts';
4
4
  type Node = TSESTree.Node;
5
5
  type Comment = TSESTree.Comment;
6
6
  type SourceCode = Readonly<TSESLint.SourceCode>;
@@ -49,6 +49,8 @@ function isFunction(node) {
49
49
  function collectTestNames(program, configured) {
50
50
  const names = new Set(configured);
51
51
  const mergeTests = new Set();
52
+ /** Names bound to another test runner, such as `ava` in `import ava from 'ava'`. */
53
+ const others = new Set();
52
54
  const isTestExpression = (node) => {
53
55
  const target = unwrap(node);
54
56
  if (target.type === 'Identifier')
@@ -62,12 +64,24 @@ function collectTestNames(program, configured) {
62
64
  return false;
63
65
  return memberName(target.callee) === 'extend' && isTestExpression(target.callee.object);
64
66
  };
67
+ // require('node:test'), ava, ava.serial or `ava as TestFn<Context>`
68
+ const isOtherFramework = (node) => {
69
+ const target = unwrap(node);
70
+ const source = requireSource(target);
71
+ if (source !== undefined)
72
+ return OTHER_FRAMEWORKS.has(source);
73
+ if (target.type === 'Identifier')
74
+ return others.has(target.name);
75
+ return target.type === 'MemberExpression' && isOtherFramework(target.object);
76
+ };
65
77
  for (const statement of program.body) {
66
78
  if (statement.type === 'ImportDeclaration') {
67
79
  const source = statement.source.value;
68
80
  if (OTHER_FRAMEWORKS.has(source)) {
69
- for (const spec of statement.specifiers)
81
+ for (const spec of statement.specifiers) {
70
82
  names.delete(spec.local.name);
83
+ others.add(spec.local.name);
84
+ }
71
85
  }
72
86
  else if (isPlaywrightModule(source)) {
73
87
  for (const spec of statement.specifiers) {
@@ -91,6 +105,11 @@ function collectTestNames(program, configured) {
91
105
  if (declarator.id.type === 'Identifier' && init.type === 'CallExpression' && isTestExpression(init)) {
92
106
  names.add(declarator.id.name);
93
107
  }
108
+ else if (declarator.id.type === 'Identifier' && isOtherFramework(init)) {
109
+ // `const test = require('node:test')` is another runner's `test`, like an import from it.
110
+ names.delete(declarator.id.name);
111
+ others.add(declarator.id.name);
112
+ }
94
113
  else if (declarator.id.type === 'ObjectPattern') {
95
114
  const source = requireSource(init);
96
115
  if (source === undefined)
@@ -98,8 +117,10 @@ function collectTestNames(program, configured) {
98
117
  for (const prop of declarator.id.properties) {
99
118
  if (prop.type !== 'Property' || prop.key.type !== 'Identifier' || prop.value.type !== 'Identifier')
100
119
  continue;
101
- if (OTHER_FRAMEWORKS.has(source))
120
+ if (OTHER_FRAMEWORKS.has(source)) {
102
121
  names.delete(prop.value.name);
122
+ others.add(prop.value.name);
123
+ }
103
124
  else if (isPlaywrightModule(source) && prop.key.name === 'test')
104
125
  names.add(prop.value.name);
105
126
  }
@@ -374,11 +395,16 @@ function runAnalysis(sourceCode, options) {
374
395
  if (param?.type === 'Identifier')
375
396
  testInfo = param.name;
376
397
  }
377
- const isDeclaration = args.length >= 2 && isFunction(last) && !isFunction(args[0]);
398
+ // The body is usually written inline, but can be a function defined elsewhere:
399
+ // `test.skip('pays', payWithCard)`. A string title tells that apart from `test.skip(cond, 'why')`.
400
+ const titled = args.length >= 2 && isStaticText(args[0]);
401
+ const isDeclaration = args.length >= 2 && !isFunction(args[0]) && (isFunction(last) || titled);
378
402
  if (path.every((p) => TEST_MODIFIERS.has(p)) && isDeclaration) {
379
403
  return { subject: makeSubject('test', call, path, collectTags(call, true)), testInfo };
380
404
  }
381
- if (path[0] === 'describe' && path.slice(1).every((p) => DESCRIBE_MODIFIERS.has(p)) && isFunction(last)) {
405
+ if (path[0] === 'describe' &&
406
+ path.slice(1).every((p) => DESCRIBE_MODIFIERS.has(p)) &&
407
+ (isFunction(last) || titled)) {
382
408
  const tagInfo = collectTags(call, args.length >= 2);
383
409
  return { subject: makeSubject('describe', call, path.slice(1), tagInfo), testInfo };
384
410
  }
@@ -433,7 +459,7 @@ function runAnalysis(sourceCode, options) {
433
459
  }
434
460
  }
435
461
  // Parsers provide visitor keys for every node type they produce; the fallback is only defensive.
436
- /* v8 ignore next */
462
+ /* node:coverage ignore next */
437
463
  for (const key of keys[node.type] ?? []) {
438
464
  const child = node[key];
439
465
  if (Array.isArray(child)) {
@@ -1,6 +1,6 @@
1
1
  import type { TSESLint } from '@typescript-eslint/utils';
2
- import { type Analysis } from './analyze.js';
3
- import { type ResolvedOptions } from './options.js';
2
+ import { type Analysis } from './analyze.ts';
3
+ import { type ResolvedOptions } from './options.ts';
4
4
  export declare const REPO_URL = "https://github.com/Huldoser/eslint-plugin-test-governance";
5
5
  /** Rules take no options of their own: everything is shared through `settings['test-governance']`. */
6
6
  export type RuleOptions = [];
@@ -1,5 +1,5 @@
1
- import { analyze } from './analyze.js';
2
- import { resolveOptions } from './options.js';
1
+ import { analyze } from "./analyze.js";
2
+ import { resolveOptions } from "./options.js";
3
3
  export const REPO_URL = 'https://github.com/Huldoser/eslint-plugin-test-governance';
4
4
  /**
5
5
  * Builds a rule that reads its options from `settings['test-governance']`.
@@ -1,5 +1,5 @@
1
- import { type BuiltinStateName } from './constants.js';
2
- import { type TicketMatcher, type TicketSpec } from './tickets.js';
1
+ import { type BuiltinStateName } from './constants.ts';
2
+ import { type TicketMatcher, type TicketSpec } from './tickets.ts';
3
3
  export declare const SETTINGS_KEY = "test-governance";
4
4
  /** A state override in `states`. `false` turns a state off, `true` turns it on. */
5
5
  export interface StateOverride {
@@ -14,7 +14,7 @@ export interface CustomState {
14
14
  marker: string;
15
15
  ticket?: TicketSpec | TicketSpec[];
16
16
  }
17
- export type { BuiltinStateName } from './constants.js';
17
+ export type { BuiltinStateName } from './constants.ts';
18
18
  /** Options shared by every rule. Set them once in `settings['test-governance']`. */
19
19
  export interface GovernanceOptions {
20
20
  /** Names that are treated as the Playwright `test` function. Defaults to `['test']`. */
@@ -78,7 +78,7 @@ export interface ResolvedOptions {
78
78
  /** Ticket format for comments: the shared default format. */
79
79
  commentTicket: TicketMatcher;
80
80
  }
81
- export { ConfigError } from './errors.js';
81
+ export { ConfigError } from './errors.ts';
82
82
  /**
83
83
  * Compiles `settings['test-governance']`. Every rule reads the same settings, so they always agree,
84
84
  * and the result is cached per settings object, so the rules share one compile.
@@ -1,8 +1,8 @@
1
- import { BUILTIN_STATE_NAMES, DEFAULT_COMMENT_KEYWORDS } from './constants.js';
2
- import { ConfigError } from './errors.js';
3
- import { optionsSchema } from './schema.js';
4
- import { compileTicketSpec, DEFAULT_PLACEHOLDERS } from './tickets.js';
5
- import { validate } from './validate.js';
1
+ import { BUILTIN_STATE_NAMES, DEFAULT_COMMENT_KEYWORDS } from "./constants.js";
2
+ import { ConfigError } from "./errors.js";
3
+ import { optionsSchema } from "./schema.js";
4
+ import { compileTicketSpec, DEFAULT_PLACEHOLDERS } from "./tickets.js";
5
+ import { validate } from "./validate.js";
6
6
  export const SETTINGS_KEY = 'test-governance';
7
7
  export function isTagState(state) {
8
8
  return state.tag !== undefined;
@@ -15,7 +15,7 @@ const BUILTIN_STATES = {
15
15
  new: { marker: 'NEW', tag: '@new', lifecycle: true, enabled: false },
16
16
  unstable: { marker: 'UNSTABLE', tag: '@unstable', lifecycle: true, enabled: false },
17
17
  };
18
- export { ConfigError } from './errors.js';
18
+ export { ConfigError } from "./errors.js";
19
19
  function toSpecs(spec) {
20
20
  return Array.isArray(spec) ? spec : [spec];
21
21
  }
@@ -1,4 +1,4 @@
1
- import type { Schema } from './validate.js';
1
+ import type { Schema } from './validate.ts';
2
2
  /** The shape of `settings['test-governance']`, the options of `configure()`. */
3
3
  export declare const optionsSchema: Schema;
4
4
  //# sourceMappingURL=schema.d.ts.map
@@ -1,4 +1,4 @@
1
- import { BUILTIN_STATE_NAMES, MARKER_RE, TAG_RE, TICKET_PRESETS } from './constants.js';
1
+ import { BUILTIN_STATE_NAMES, MARKER_RE, TAG_RE, TICKET_PRESETS } from "./constants.js";
2
2
  const MARKER_HINT = 'an uppercase keyword such as NEEDS-DATA (letters, digits, "-" and "_")';
3
3
  const TAG_HINT = 'a tag such as @needs-data';
4
4
  /** Jira project and Linear team keys, as they appear in tickets: `WEB` in `WEB-123`. */
@@ -1,4 +1,4 @@
1
- import { ConfigError } from './errors.js';
1
+ import { ConfigError } from "./errors.js";
2
2
  export const DEFAULT_PLACEHOLDERS = ['TODO', 'TBD', 'XXX-*', '0', '123', '1234', '12345'];
3
3
  // Every pattern below is anchored and has no nested or overlapping quantifiers, so matching is
4
4
  // linear in the token length. URLs are parsed with `new URL` rather than a regex.
@@ -1,4 +1,4 @@
1
- import { editDistance } from './distance.js';
1
+ import { editDistance } from "./distance.js";
2
2
  function kindOf(value) {
3
3
  if (value === null)
4
4
  return 'null';
@@ -26,8 +26,8 @@ How comments are read:
26
26
  - The keyword starts the comment (or a line of a block comment). `TODO` and `FIXME` in capitals
27
27
  count with or without a colon. Other casings, such as `todo:`, need the colon, so a sentence that
28
28
  happens to start with "Todo" is left alone.
29
- - The tickets can also go in parentheses straight after the keyword, `TODO(WEB-512)`, a style many
30
- teams already use. Anything after the parentheses is a note, as it would be after `TODO: WEB-512`.
29
+ - The tickets can also go in parentheses after the keyword, `TODO(WEB-512)` or `TODO (WEB-512)`, a
30
+ style many teams already use. Anything after the parentheses is a note, as it would be after `TODO: WEB-512`.
31
31
  - The tickets follow the same rules as markers: the shared ticket format, placeholders such as `TBD`
32
32
  rejected, and no text after the tickets unless `allowNotes` is set.
33
33
  - A `// FIXME:` marker directly above a `test.fixme(...)` is checked by
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-test-governance",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "ESLint rules that require a ticket comment above skipped, fixme and tagged Playwright tests.",
5
5
  "keywords": [
6
6
  "eslint",
@@ -45,8 +45,8 @@
45
45
  "scripts": {
46
46
  "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
47
47
  "typecheck": "tsc -p tsconfig.json",
48
- "test": "vitest run",
49
- "test:coverage": "vitest run --coverage",
48
+ "test": "node --test 'tests/**/*.test.ts'",
49
+ "test:coverage": "node --test --experimental-test-coverage --test-coverage-include='src/rules/**' --test-coverage-include='src/utils/**' --test-coverage-branches=100 --test-coverage-functions=100 --test-coverage-lines=100 'tests/**/*.test.ts'",
50
50
  "test:pack": "node scripts/pack-smoke.mjs",
51
51
  "docs": "npm run build && eslint-doc-generator",
52
52
  "docs:check": "npm run build && eslint-doc-generator --check",
@@ -63,15 +63,11 @@
63
63
  "@types/node": "^26.6.3",
64
64
  "@typescript-eslint/parser": "^8.71.0",
65
65
  "@typescript-eslint/utils": "^8.71.0",
66
- "@vitest/coverage-v8": "^5.0.2",
67
66
  "eslint": "^10.11.0",
68
- "eslint-config-prettier": "^10.1.8",
69
67
  "eslint-doc-generator": "^3.7.1",
70
68
  "eslint-plugin-eslint-plugin": "^7.6.2",
71
- "globals": "^17.13.0",
72
69
  "prettier": "^3.9.9",
73
70
  "typescript": "6.0",
74
- "typescript-eslint": "^8.71.0",
75
- "vitest": "^5.0.2"
71
+ "typescript-eslint": "^8.71.0"
76
72
  }
77
73
  }