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 +15 -1
- package/README.md +3 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +6 -6
- package/dist/rules/marker-matches-state.d.ts +1 -1
- package/dist/rules/marker-matches-state.js +3 -3
- package/dist/rules/no-conflicting-states.d.ts +1 -1
- package/dist/rules/no-conflicting-states.js +5 -5
- package/dist/rules/no-orphaned-marker.d.ts +1 -1
- package/dist/rules/no-orphaned-marker.js +3 -3
- package/dist/rules/require-ticket-in-comments.d.ts +1 -1
- package/dist/rules/require-ticket-in-comments.js +6 -6
- package/dist/rules/require-ticket.d.ts +1 -1
- package/dist/rules/require-ticket.js +2 -2
- package/dist/utils/analyze.d.ts +2 -2
- package/dist/utils/analyze.js +31 -5
- package/dist/utils/create-rule.d.ts +2 -2
- package/dist/utils/create-rule.js +2 -2
- package/dist/utils/options.d.ts +4 -4
- package/dist/utils/options.js +6 -6
- package/dist/utils/schema.d.ts +1 -1
- package/dist/utils/schema.js +1 -1
- package/dist/utils/tickets.js +1 -1
- package/dist/utils/validate.js +1 -1
- package/docs/rules/require-ticket-in-comments.md +2 -2
- package/package.json +4 -8
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.
|
|
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`,
|
|
176
|
-
`mocha`, `ava`, `tap`, `uvu`)
|
|
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.
|
|
3
|
-
export type { CustomState, GovernanceOptions, StateOverride } from './utils/options.
|
|
4
|
-
export type { TicketSpec } from './utils/tickets.
|
|
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
|
|
3
|
-
import noConflictingStates from
|
|
4
|
-
import noOrphanedMarker from
|
|
5
|
-
import requireTicketInComments from
|
|
6
|
-
import requireTicket from
|
|
7
|
-
import { compileOptions, SETTINGS_KEY } from
|
|
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.
|
|
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
|
|
2
|
-
import { createRule } from
|
|
3
|
-
import { indentOf, keywordRange } from
|
|
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.
|
|
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
|
|
2
|
-
import { createRule } from
|
|
3
|
-
import { isTagState } from
|
|
4
|
-
import { editDistance, typoThreshold } from
|
|
5
|
-
import { tagRange } from
|
|
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.
|
|
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
|
|
2
|
-
import { createRule } from
|
|
3
|
-
import { removeComment } from
|
|
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.
|
|
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
|
|
2
|
-
import { createRule } from
|
|
3
|
-
import { compileTicketSpec } from
|
|
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.
|
|
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
|
|
2
|
-
import { createRule } from
|
|
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: {
|
package/dist/utils/analyze.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
|
|
2
|
-
import type { ResolvedOptions, StateDef } from './options.
|
|
3
|
-
import type { TicketCheck, TicketMatcher } from './tickets.
|
|
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>;
|
package/dist/utils/analyze.js
CHANGED
|
@@ -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
|
-
|
|
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' &&
|
|
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
|
-
/*
|
|
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.
|
|
3
|
-
import { type ResolvedOptions } from './options.
|
|
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
|
|
2
|
-
import { resolveOptions } from
|
|
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']`.
|
package/dist/utils/options.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type BuiltinStateName } from './constants.
|
|
2
|
-
import { type TicketMatcher, type TicketSpec } from './tickets.
|
|
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.
|
|
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.
|
|
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.
|
package/dist/utils/options.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { BUILTIN_STATE_NAMES, DEFAULT_COMMENT_KEYWORDS } from
|
|
2
|
-
import { ConfigError } from
|
|
3
|
-
import { optionsSchema } from
|
|
4
|
-
import { compileTicketSpec, DEFAULT_PLACEHOLDERS } from
|
|
5
|
-
import { validate } from
|
|
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
|
|
18
|
+
export { ConfigError } from "./errors.js";
|
|
19
19
|
function toSpecs(spec) {
|
|
20
20
|
return Array.isArray(spec) ? spec : [spec];
|
|
21
21
|
}
|
package/dist/utils/schema.d.ts
CHANGED
package/dist/utils/schema.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BUILTIN_STATE_NAMES, MARKER_RE, TAG_RE, TICKET_PRESETS } from
|
|
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`. */
|
package/dist/utils/tickets.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConfigError } from
|
|
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.
|
package/dist/utils/validate.js
CHANGED
|
@@ -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
|
|
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.
|
|
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": "
|
|
49
|
-
"test: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
|
}
|