eslint-plugin-test-governance 0.13.1 → 0.13.3
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 +74 -1
- package/dist/rules/marker-matches-state.js +4 -1
- package/dist/rules/require-ticket-for-commented-out-tests.js +4 -4
- package/dist/rules/require-ticket-in-comments.js +3 -3
- package/dist/rules/require-ticket.js +5 -5
- package/dist/utils/analyze.js +490 -187
- package/dist/utils/fix.js +9 -6
- package/dist/utils/tickets.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,77 @@ All notable changes to this project are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.13.3] - 2026-11-09
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Vitest conditional factories can clear an earlier skip in a modifier chain. Runtime context methods
|
|
14
|
+
retain their state through TypeScript wrappers on the receiver or method.
|
|
15
|
+
- Mutation detection follows wrapped references and aliases, and recognizes writes through the global
|
|
16
|
+
`Object` and `Reflect` mutation methods without treating shadowed helpers as those built-ins.
|
|
17
|
+
- Retry counts are read through local constants and object members, with effective properties after
|
|
18
|
+
spreads and overrides. Unread spreads remain possible retries unless every relevant count is known.
|
|
19
|
+
- Commented-out tests no longer hide a broken marker or a disallowed note behind another valid marker.
|
|
20
|
+
- Negative numeric and bigint template interpolations, `void`, and unshadowed `undefined`, `NaN` and
|
|
21
|
+
`Infinity` retain their rendered text when checking tag boundaries.
|
|
22
|
+
- Mutable constant objects and arrays no longer supply stale tag or annotation values after detected
|
|
23
|
+
property writes, deletion, array mutation or writes through a local alias.
|
|
24
|
+
- Runtime context methods keep their original test owner inside nested subtests. Vitest skip notes
|
|
25
|
+
supplied through constants remain unconditional skips.
|
|
26
|
+
- Test callees retain their meaning through TypeScript assertions and type instantiations, sequence
|
|
27
|
+
expressions and defaulted local destructuring. Repeated conditional factories use the final condition.
|
|
28
|
+
- Nested conditional returns and throws, and conditional loop breaks and continues, guard later runtime
|
|
29
|
+
skips. Jest retry calls are recognized through CommonJS object bindings and method aliases, including
|
|
30
|
+
bound arguments.
|
|
31
|
+
- Primitive template interpolations retain their rendered text when checking tag boundaries. Object
|
|
32
|
+
prototype setters do not count as own tag or metadata properties, and suggestions use computed keys
|
|
33
|
+
for metadata named `__proto__`.
|
|
34
|
+
- Test functions are read through aliases of whole modules, local destructuring, bound functions and
|
|
35
|
+
nested conditional selections. Reassigning an alias to a helper no longer leaves it treated as a test.
|
|
36
|
+
- Runtime test contexts and their methods keep their meaning through chains of aliases, `bind()` and
|
|
37
|
+
default parameter values. Arguments supplied by `bind()` retain their condition and reason, and a
|
|
38
|
+
bound title no longer gives a diagnostic whose end is before its start.
|
|
39
|
+
- A runtime skip in a conditional loop, or after a conditional early exit inside a nested block, is
|
|
40
|
+
read as conditional. A TypeScript assertion around a literal boolean keeps that boolean's meaning.
|
|
41
|
+
- With the retry state on, Jest's retry calls are read through renamed and namespace imports, and a
|
|
42
|
+
Mocha describe chosen by a condition still supplies its suite context for `this.retries()`.
|
|
43
|
+
- Chains of constants retain their tags, details and annotation tickets. An unread computed property
|
|
44
|
+
no longer leaves an earlier member of a tag object treated as certain.
|
|
45
|
+
- Tags from literal and nested template interpolations are checked using the rendered text's boundaries,
|
|
46
|
+
so joining text around an `@` doesn't invent a state tag. Plain Vitest tags written as templates count
|
|
47
|
+
like quoted strings. Cyclic constant and template definitions remain unread instead of recursing.
|
|
48
|
+
- `annotations.only` checks every supplied issue ticket, including an invalid one next to a valid one.
|
|
49
|
+
- The commented-out-test rule recognizes renamed test imports.
|
|
50
|
+
- Removing an orphaned marker removes its complete line with CR or Unicode line endings too.
|
|
51
|
+
- A suggestion to add another state's marker no longer copies a ticket that state's format rejects.
|
|
52
|
+
- Suggestions that move a ticket into annotations escape line breaks in the annotation type or key,
|
|
53
|
+
keeping the suggested JavaScript valid.
|
|
54
|
+
|
|
55
|
+
## [0.13.2] - 2026-11-08
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- Local aliases such as `const check = it; check.skip(...)`, and aliases of conditional test functions,
|
|
60
|
+
are checked like the functions they keep.
|
|
61
|
+
- A helper's parameter or block variable named like a test context no longer makes its `skip()` or
|
|
62
|
+
`test()` calls count as runtime skips or subtests.
|
|
63
|
+
- Runtime calls with a literal `false` condition, such as Playwright's `test.skip(false)`, need neither
|
|
64
|
+
a ticket nor a reason.
|
|
65
|
+
- Details, options and annotations use the last value of a property, including values from object
|
|
66
|
+
spreads. An unread spread or computed key no longer leaves an earlier value treated as certain.
|
|
67
|
+
- A tag read beyond an array's bounds no longer crashes linting. An index after a spread, or a string
|
|
68
|
+
such as `'01'` that isn't an array index, is left unread instead of using the wrong tag.
|
|
69
|
+
- Circular object spreads no longer overflow the stack during analysis.
|
|
70
|
+
- Playwright annotation arrays read tickets from spreads of arrays declared in the same file, and
|
|
71
|
+
report unread spreads with `reportUnreadableTags`.
|
|
72
|
+
- A ticket marker in a block comment no longer hides unrelated `TODO` or `FIXME` work comments on
|
|
73
|
+
other lines of that block.
|
|
74
|
+
- The `pattern` preset matches the whole ticket even with the multiline flag, and no longer accepts
|
|
75
|
+
trailing line breaks outside the pattern.
|
|
76
|
+
- Moving a ticket into an annotation preserves comments inside an empty details object.
|
|
77
|
+
- node:test's `todo` state no longer makes `no-conflicting-states` call a new test skipped: Node still
|
|
78
|
+
runs its body.
|
|
79
|
+
|
|
9
80
|
## [0.13.1] - 2026-11-06
|
|
10
81
|
|
|
11
82
|
### Fixed
|
|
@@ -573,7 +644,9 @@ First release.
|
|
|
573
644
|
- Support for ESLint 9 and 10 flat config, JavaScript and TypeScript test files, `.extend()`
|
|
574
645
|
fixtures, describe blocks, runtime skips and tags in titles or the `tag` option.
|
|
575
646
|
|
|
576
|
-
[Unreleased]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.13.
|
|
647
|
+
[Unreleased]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.13.3...HEAD
|
|
648
|
+
[0.13.3]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.13.2...v0.13.3
|
|
649
|
+
[0.13.2]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.13.1...v0.13.2
|
|
577
650
|
[0.13.1]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.13.0...v0.13.1
|
|
578
651
|
[0.13.0]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.12.1...v0.13.0
|
|
579
652
|
[0.12.1]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.12.0...v0.12.1
|
|
@@ -70,7 +70,10 @@ export default createRule({
|
|
|
70
70
|
state: state.name,
|
|
71
71
|
subject: subjectName,
|
|
72
72
|
},
|
|
73
|
-
suggest: present.comment.type === 'Line' &&
|
|
73
|
+
suggest: present.comment.type === 'Line' &&
|
|
74
|
+
tickets !== '' &&
|
|
75
|
+
present.tickets.every((ticket) => state.ticket.check(ticket.text) === 'ok') &&
|
|
76
|
+
directive?.kind !== 'eslint-disable-line'
|
|
74
77
|
? [
|
|
75
78
|
{
|
|
76
79
|
messageId: 'addMarker',
|
|
@@ -32,9 +32,9 @@ export default createRule({
|
|
|
32
32
|
context.report({ loc, messageId: 'missingMarker', data });
|
|
33
33
|
continue;
|
|
34
34
|
}
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
37
|
-
const
|
|
35
|
+
const broken = own.find((m) => m.tickets.length === 0 || m.tickets.some((t) => t.result !== 'ok'));
|
|
36
|
+
if (broken === undefined) {
|
|
37
|
+
const extra = own.find((marker) => marker.extra !== undefined)?.extra;
|
|
38
38
|
if (extra === undefined || options.allowNotes)
|
|
39
39
|
continue;
|
|
40
40
|
context.report({
|
|
@@ -51,7 +51,7 @@ export default createRule({
|
|
|
51
51
|
});
|
|
52
52
|
continue;
|
|
53
53
|
}
|
|
54
|
-
const
|
|
54
|
+
const marker = broken;
|
|
55
55
|
const bad = marker.tickets.find((t) => t.result !== 'ok');
|
|
56
56
|
if (bad === undefined) {
|
|
57
57
|
context.report({ loc: marker.comment.loc, messageId: 'missingTicket', data });
|
|
@@ -37,7 +37,7 @@ export default createRule({
|
|
|
37
37
|
for (const marker of [...subject.markers, ...subject.regionMarkers]) {
|
|
38
38
|
const state = marker.state ?? marker.caseOf;
|
|
39
39
|
if (state && appliesTo(subject, state.name))
|
|
40
|
-
owned.add(marker.
|
|
40
|
+
owned.add(marker.keywordStart);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
// `TODO`/`FIXME` in capitals counts with or without a colon. Other casings need the colon, so a
|
|
@@ -62,13 +62,13 @@ export default createRule({
|
|
|
62
62
|
return { ...parsed, extra: { text: note, range: [start + close, start + line.trimEnd().length] } };
|
|
63
63
|
}
|
|
64
64
|
for (const comment of sourceCode.getAllComments()) {
|
|
65
|
-
if (owned.has(comment))
|
|
66
|
-
continue;
|
|
67
65
|
for (const { text: line, start } of commentLines(comment)) {
|
|
68
66
|
const match = keywordRe.exec(line);
|
|
69
67
|
if (!match)
|
|
70
68
|
continue;
|
|
71
69
|
const [whole, indent, keyword] = match;
|
|
70
|
+
if (owned.has(start + indent.length))
|
|
71
|
+
continue;
|
|
72
72
|
// The parenthesised group is optional, so it can be undefined despite RegExpExecArray's typing.
|
|
73
73
|
const inParens = match[3];
|
|
74
74
|
const upper = keyword.toUpperCase();
|
|
@@ -107,11 +107,6 @@ export default createRule({
|
|
|
107
107
|
reportReason(subject, state);
|
|
108
108
|
continue;
|
|
109
109
|
}
|
|
110
|
-
if (annotationOnly) {
|
|
111
|
-
reportAnnotationOnly(subject, state, annotationOnly);
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
const result = evaluate(subject, state);
|
|
115
110
|
// Annotations are checked whenever a ticket is needed, whatever the markers say.
|
|
116
111
|
for (const ticket of annotationTicketsOf(subject)) {
|
|
117
112
|
if (state.ticket.check(ticket.text) !== 'ok' && reportOnce(`annotation:${ticket.node.range[0]}`)) {
|
|
@@ -122,6 +117,11 @@ export default createRule({
|
|
|
122
117
|
});
|
|
123
118
|
}
|
|
124
119
|
}
|
|
120
|
+
if (annotationOnly) {
|
|
121
|
+
reportAnnotationOnly(subject, state, annotationOnly);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const result = evaluate(subject, state);
|
|
125
125
|
if (result.kind === 'missing' && hasAnnotationTicket(subject, state, options))
|
|
126
126
|
continue;
|
|
127
127
|
if (result.kind !== 'ok' &&
|