eslint-plugin-test-governance 0.8.1 → 0.10.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/CHANGELOG.md CHANGED
@@ -6,6 +6,45 @@ All notable changes to this project are documented here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.10.0] - 2026-10-30
10
+
11
+ ### Added
12
+
13
+ - Ticket presets for more trackers: `trello`, `notion`, `asana`, `clickup`, `redmine`, `monday`,
14
+ `youtrack`, `basecamp` and `shortcut`. Each accepts its tracker's ticket keys and links to one ticket,
15
+ and rejects links to boards, projects, views and other sites. `prefixes` limits the keys Notion,
16
+ ClickUp and monday.com accept, and `projects` the YouTrack projects; `host` sets a custom domain for
17
+ Notion, a server for YouTrack, and the server whose links Redmine accepts.
18
+ - A `url` preset for any other tracker: it accepts links to the `hosts` you list, and `*.example.com`
19
+ matches subdomains.
20
+ - The `azure-devops` preset accepts `#123`, which Azure Repos links to work item 123 in commit messages
21
+ and pull requests.
22
+
23
+ ### Fixed
24
+
25
+ - A link whose title ends in `-0`, as in `https://linear.app/acme/issue/ENG-1/upgrade-to-node-0`, was
26
+ reported as a placeholder ticket. Only the ticket's own ID makes it a placeholder now, which also
27
+ catches links where a title, suffix or query follows a zero ID, as in
28
+ `https://linear.app/acme/issue/ENG-0/fix-rounding`.
29
+ - `host` takes `*.example.com` to match every subdomain, as in `{ preset: 'jira', host: '*.atlassian.net' }`.
30
+ Before, such a host matched no link.
31
+ - `projects: []` (Jira) and `teams: []` (Linear) are config errors. An empty list rejected every key, and
32
+ the messages read "in project ,".
33
+ - Links whose host ends in a dot, as in `https://dev.azure.com./acme/web/_workitems/edit/1`, are read like
34
+ the same link without it.
35
+
36
+ ## [0.9.0] - 2026-10-27
37
+
38
+ ### Added
39
+
40
+ - Jasmine support. `configs.jasmine`, or `configure({ framework: 'jasmine' })`, checks tests run by
41
+ Jasmine on its own, on Karma with karma-jasmine (as Angular's karma builder sets up) or by WebdriverIO's
42
+ Jasmine framework: the globals `it`, `xit`, `fit`, `describe`, `xdescribe` and `fdescribe`, or the same
43
+ names from `jasmine-core`. `xit` and `xdescribe` skip a test, and `it('title')` with no function is a
44
+ todo. `pending()` and Jasmine 7's `notApplicable()` skip the running test in a test, `beforeEach` or
45
+ `afterEach`; like `t.skip('reason')`, `pending('reason')` needs a ticket unless a condition guards it.
46
+ `testFunctions` defaults to `['it']`, since Jasmine has no `test`.
47
+
9
48
  ## [0.8.1] - 2026-10-22
10
49
 
11
50
  ### Fixed
@@ -357,7 +396,9 @@ First release.
357
396
  - Support for ESLint 9 and 10 flat config, JavaScript and TypeScript test files, `.extend()`
358
397
  fixtures, describe blocks, runtime skips and tags in titles or the `tag` option.
359
398
 
360
- [Unreleased]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.8.1...HEAD
399
+ [Unreleased]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.10.0...HEAD
400
+ [0.10.0]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.9.0...v0.10.0
401
+ [0.9.0]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.8.1...v0.9.0
361
402
  [0.8.1]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.8.0...v0.8.1
362
403
  [0.8.0]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.7.0...v0.8.0
363
404
  [0.7.0]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.6.2...v0.7.0
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![CI](https://github.com/Huldoser/eslint-plugin-test-governance/actions/workflows/ci.yml/badge.svg)](https://github.com/Huldoser/eslint-plugin-test-governance/actions/workflows/ci.yml)
5
5
  [![License: MIT](https://img.shields.io/npm/l/eslint-plugin-test-governance)](LICENSE)
6
6
 
7
- ESLint rules that keep a Playwright, Jest, Vitest, Mocha, Cypress, WebdriverIO or `node:test` suite honest. Every skipped, fixme, todo, `@new`
7
+ ESLint rules that keep a Playwright, Jest, Vitest, Mocha, Cypress, WebdriverIO, `node:test` or Jasmine suite honest. Every skipped, fixme, todo, `@new`
8
8
  or `@quarantined` test must point at the ticket that tracks it:
9
9
 
10
10
  <!-- example: valid -->
@@ -35,8 +35,8 @@ Requires ESLint 9 or 10 with flat config, and Node.js 22.12 or later. For TypeSc
35
35
  ## Usage
36
36
 
37
37
  Pick the config for the framework your tests use, `configs.playwright`, `configs.jest`,
38
- `configs.vitest`, `configs.mocha`, `configs.cypress`, `configs.webdriverio` or `configs['node-test']` for
39
- Node's built-in runner, and point it at those test files with `files`:
38
+ `configs.vitest`, `configs.mocha`, `configs.cypress`, `configs.webdriverio`, `configs['node-test']` for
39
+ Node's built-in runner or `configs.jasmine`, and point it at those test files with `files`:
40
40
 
41
41
  ```js
42
42
  // eslint.config.js
@@ -53,16 +53,17 @@ export default [
53
53
  ```
54
54
 
55
55
  There is no default config. Each framework declares, skips and tags tests its own way, so the rules
56
- need to know which one a file uses. In a Jest, Vitest or Mocha config, `test`, `it` and `describe` belong
57
- to that framework, whether they are globals or imported from `@jest/globals`, `vitest` or `mocha`, the
58
- way the Jest, Vitest and Mocha ESLint plugins are set up. `node:test` has no globals, so in its config
56
+ need to know which one a file uses. In a Jest, Vitest, Mocha or Jasmine config, the framework's functions,
57
+ such as `it` and `describe`, belong to it, whether they are globals or imported from `@jest/globals`,
58
+ `vitest`, `mocha` or `jasmine-core`, the way the Jest, Vitest and Mocha ESLint plugins are set up. `node:test` has no globals, so in its config
59
59
  they are the names imported or required from `node:test`, and `test` and `it` from `testFunctions`.
60
60
 
61
61
  Cypress and WebdriverIO run their tests on Mocha, so `configs.cypress` and `configs.webdriverio` read the
62
- same calls as `configs.mocha`. `webdriverio` means WebdriverIO's Mocha framework; its Jasmine framework
63
- isn't supported yet. Puppeteer has no test functions of its own, so use the config of the runner its
64
- tests run on: `configs.jest` for jest-puppeteer, or `configs.vitest`, `configs.mocha` or
65
- `configs['node-test']`.
62
+ same calls as `configs.mocha`. `webdriverio` means WebdriverIO's Mocha framework; for its Jasmine
63
+ framework, use `configs.jasmine`. Karma runs Jasmine or Mocha tests, so use `configs.jasmine` with
64
+ karma-jasmine, which Angular's karma builder sets up, and `configs.mocha` with karma-mocha. Puppeteer has
65
+ no test functions of its own, so use the config of the runner its tests run on: `configs.jest` for
66
+ jest-puppeteer, or `configs.vitest`, `configs.mocha`, `configs['node-test']` or `configs.jasmine`.
66
67
 
67
68
  With ESLint's `defineConfig()` (ESLint 9.22 or later), put the config in `extends`:
68
69
 
@@ -133,26 +134,30 @@ export default [
133
134
  ];
134
135
  ```
135
136
 
137
+ This matters most for Jasmine next to Jest or Mocha, whose `it`, `xit` and `describe` look the same:
138
+ only the `files` of each config tell them apart.
139
+
136
140
  Each framework's config, such as `configs.playwright`, sets only the framework, so one listed after a
137
141
  `configure()` for the same files keeps that config's options and changes only the framework. They all
138
142
  turn on the same rules. What puts a test in a state in each framework:
139
143
 
140
- | State (marker) | Playwright | Jest | Vitest | Mocha | Cypress | WebdriverIO | node-test |
141
- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
142
- | `skip` (`// SKIP:`) | `test.skip(...)`, `test.describe.skip`, `test.skip()` in a body, `testInfo.skip()`, `test.step.skip`, `step.skip()` | `test.skip`, `it.skip`, `describe.skip`, `xit`, `xtest`, `xdescribe` | `test.skip`, `it.skip`, `describe.skip`, `suite.skip`, `skipIf(true)`, `runIf(false)`, `{ skip: true }`, `context.skip()`, `context.skip('note')` or `skip()` in a body or in `beforeEach` | `it.skip`, `specify.skip`, `describe.skip`, `context.skip`, `xit`, `xspecify`, `xdescribe`, `xcontext`, TDD `test.skip` and `suite.skip`, `this.skip()` in a test or hook | as Mocha | as Mocha | `test.skip`, `it.skip`, `describe.skip`, `suite.skip`, `skip()`, `{ skip: true }` or `{ skip: 'reason' }`, `t.skip()`, `t.skip('reason')` or `this.skip()` in a test, `beforeEach` or `afterEach` |
143
- | `todo` (`// TODO:`) | | `test.todo`, `it.todo` | `test.todo`, `describe.todo`, `{ todo: true }` | a test with only a title, `it('title')`, which Mocha runs as pending | as Mocha | as Mocha | `test.todo`, `it.todo`, `describe.todo`, `suite.todo`, `todo()`, `{ todo: true }` or `{ todo: 'reason' }`, `t.todo()`, `this.todo()` |
144
- | `fixme` (`// FIXME:`) | `test.fixme(...)`, `test.describe.fixme`, `testInfo.fixme()` | | | | | | |
145
- | `fail` (`// FAIL:`), off by default | `test.fail(...)`, `testInfo.fail()` | `test.failing` | `test.fails`, `{ fails: true }` | | | | `test.expectFailure`, `expectFailure()`, `{ expectFailure }` |
146
- | `slow` (`// SLOW:`), off by default | `test.slow()`, `testInfo.slow()` | | | | | | |
147
- | conditional skip, no ticket by default | `test.skip(condition)`, `if (...) test.skip()`, `(condition ? test.skip : test)(...)` | `(condition ? it.skip : it)(...)` | `skipIf(condition)`, `runIf(condition)`, `{ skip: condition }`, `context.skip(condition)`, `if (...) context.skip()`, `(condition ? it.skip : it)(...)` | `if (...) this.skip()`, `(condition ? it.skip : it)(...)` | as Mocha, and `{ browser: '!webkit' }` | as Mocha | `{ skip: condition }`, `{ todo: condition }`, `if (...) t.skip()`, `(condition ? it.skip : it)(...)` |
148
- | tags such as `@new` (`// NEW:`) | the title, `{ tag }` | the title | the title, `{ tags }`, where `'new'` and `'@new'` both mean `@new` | the title | the title, `{ tags }` from @cypress/grep | the title | the title |
144
+ | State (marker) | Playwright | Jest | Vitest | Mocha | Cypress | WebdriverIO | node-test | Jasmine |
145
+ | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
146
+ | `skip` (`// SKIP:`) | `test.skip(...)`, `test.describe.skip`, `test.skip()` in a body, `testInfo.skip()`, `test.step.skip`, `step.skip()` | `test.skip`, `it.skip`, `describe.skip`, `xit`, `xtest`, `xdescribe` | `test.skip`, `it.skip`, `describe.skip`, `suite.skip`, `skipIf(true)`, `runIf(false)`, `{ skip: true }`, `context.skip()`, `context.skip('note')` or `skip()` in a body or in `beforeEach` | `it.skip`, `specify.skip`, `describe.skip`, `context.skip`, `xit`, `xspecify`, `xdescribe`, `xcontext`, TDD `test.skip` and `suite.skip`, `this.skip()` in a test or hook | as Mocha | as Mocha | `test.skip`, `it.skip`, `describe.skip`, `suite.skip`, `skip()`, `{ skip: true }` or `{ skip: 'reason' }`, `t.skip()`, `t.skip('reason')` or `this.skip()` in a test, `beforeEach` or `afterEach` | `xit`, `xdescribe`, `pending()`, `pending('reason')` or `notApplicable('reason')` in a test, `beforeEach` or `afterEach` |
147
+ | `todo` (`// TODO:`) | | `test.todo`, `it.todo` | `test.todo`, `describe.todo`, `{ todo: true }` | a test with only a title, `it('title')`, which Mocha runs as pending | as Mocha | as Mocha | `test.todo`, `it.todo`, `describe.todo`, `suite.todo`, `todo()`, `{ todo: true }` or `{ todo: 'reason' }`, `t.todo()`, `this.todo()` | a test with only a title, `it('title')`, which Jasmine reports as pending |
148
+ | `fixme` (`// FIXME:`) | `test.fixme(...)`, `test.describe.fixme`, `testInfo.fixme()` | | | | | | | |
149
+ | `fail` (`// FAIL:`), off by default | `test.fail(...)`, `testInfo.fail()` | `test.failing` | `test.fails`, `{ fails: true }` | | | | `test.expectFailure`, `expectFailure()`, `{ expectFailure }` | |
150
+ | `slow` (`// SLOW:`), off by default | `test.slow()`, `testInfo.slow()` | | | | | | | |
151
+ | conditional skip, no ticket by default | `test.skip(condition)`, `if (...) test.skip()`, `(condition ? test.skip : test)(...)` | `(condition ? it.skip : it)(...)` | `skipIf(condition)`, `runIf(condition)`, `{ skip: condition }`, `context.skip(condition)`, `if (...) context.skip()`, `(condition ? it.skip : it)(...)` | `if (...) this.skip()`, `(condition ? it.skip : it)(...)` | as Mocha, and `{ browser: '!webkit' }` | as Mocha | `{ skip: condition }`, `{ todo: condition }`, `if (...) t.skip()`, `(condition ? it.skip : it)(...)` | `if (...) pending()`, `(condition ? xit : it)(...)` |
152
+ | tags such as `@new` (`// NEW:`) | the title, `{ tag }` | the title | the title, `{ tags }`, where `'new'` and `'@new'` both mean `@new` | the title | the title, `{ tags }` from @cypress/grep | the title | the title | the title |
149
153
 
150
154
  Table-driven tests count too: `test.skip.each(table)(...)`, `describe.skip.each` and Vitest's `.for`.
151
155
  A test or describe whose function a condition picks, as in `(isMobile ? test.skip : test)('title', fn)`,
152
156
  is a conditional skip in every framework.
153
157
  A literal condition decides on its own: `skipIf(true)` and `runIf(false)` always skip, and `skipIf(false)`,
154
158
  `runIf(true)` and `{ skip: false }` never do.
155
- In Vitest, Mocha and `node:test`, `test.skip('title')` and `describe.skip('title')` with no body are skipped too.
159
+ In Vitest, Mocha and `node:test`, `test.skip('title')` and `describe.skip('title')` with no body are skipped too,
160
+ as is `xit('title')` in Jasmine.
156
161
  In Mocha, `this.skip()` counts in a test or hook written as a `function`; an arrow function gets no
157
162
  Mocha context, so its `this` is not the test's. `this.slow(ms)` only sets Mocha's speed threshold, so
158
163
  it isn't the `slow` state.
@@ -167,6 +172,10 @@ A test with no function, `test('title')`, passes in Node, so it isn't a todo. A
167
172
  `before` and `after` inside it, get the suite's context, which can't skip. Node reports a test in one
168
173
  state only, skip before todo before `expectFailure`, so `describe.skip('title', { todo: 'why' }, fn)`
169
174
  needs only a `// SKIP:` marker.
175
+ In Jasmine, `pending()` and Jasmine 7's `notApplicable()` stop the running test, so they count in a test,
176
+ `beforeEach` or `afterEach`; in `beforeAll`, `afterAll` or a describe's body Jasmine fails the suite
177
+ instead, so they are left alone there. Like `t.skip('reason')`, `pending('reason')` takes a reason, not a
178
+ condition. A function or variable of your own called `pending` is not Jasmine's.
170
179
 
171
180
  ## Rules
172
181
 
@@ -175,6 +184,7 @@ needs only a `// SKIP:` marker.
175
184
  💼 [Configurations](https://github.com/Huldoser/eslint-plugin-test-governance#usage) enabled in.\
176
185
  ⚠️ [Configurations](https://github.com/Huldoser/eslint-plugin-test-governance#usage) set to warn in.\
177
186
  🌲 Set in the `cypress` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
187
+ 🌼 Set in the `jasmine` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
178
188
  🃏 Set in the `jest` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
179
189
  ☕ Set in the `mocha` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
180
190
  🟢 Set in the `node-test` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
@@ -184,13 +194,13 @@ needs only a `// SKIP:` marker.
184
194
  🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
185
195
  💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
186
196
 
187
- | Name                       | Description | 💼 | ⚠️ | 🔧 | 💡 |
188
- | :--------------------------------------------------------------------- | :-------------------------------------------------------------------------------- | :------------------- | :------------------- | :-- | :-- |
189
- | [marker-matches-state](docs/rules/marker-matches-state.md) | Require the marker keyword to match the test's state | 🌲 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
190
- | [no-conflicting-states](docs/rules/no-conflicting-states.md) | Disallow state tags that contradict each other, differ in case or look like typos | | 🌲 🃏 ☕ 🟢 🎭 ⚡ 🤖 | 🔧 | |
191
- | [no-orphaned-marker](docs/rules/no-orphaned-marker.md) | Disallow marker comments that no longer match a test state | 🌲 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
192
- | [require-ticket](docs/rules/require-ticket.md) | Require a ticket marker comment above skipped, fixme and tagged tests | 🌲 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
193
- | [require-ticket-in-comments](docs/rules/require-ticket-in-comments.md) | Require FIXME and TODO comments in test files to start with a ticket | 🌲 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
197
+ | Name                       | Description | 💼 | ⚠️ | 🔧 | 💡 |
198
+ | :--------------------------------------------------------------------- | :-------------------------------------------------------------------------------- | :---------------------- | :---------------------- | :-- | :-- |
199
+ | [marker-matches-state](docs/rules/marker-matches-state.md) | Require the marker keyword to match the test's state | 🌲 🌼 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
200
+ | [no-conflicting-states](docs/rules/no-conflicting-states.md) | Disallow state tags that contradict each other, differ in case or look like typos | | 🌲 🌼 🃏 ☕ 🟢 🎭 ⚡ 🤖 | 🔧 | |
201
+ | [no-orphaned-marker](docs/rules/no-orphaned-marker.md) | Disallow marker comments that no longer match a test state | 🌲 🌼 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
202
+ | [require-ticket](docs/rules/require-ticket.md) | Require a ticket marker comment above skipped, fixme and tagged tests | 🌲 🌼 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
203
+ | [require-ticket-in-comments](docs/rules/require-ticket-in-comments.md) | Require FIXME and TODO comments in test files to start with a ticket | 🌲 🌼 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
194
204
 
195
205
  <!-- end auto-generated rules list -->
196
206
 
@@ -220,20 +230,20 @@ yourself. `framework` is required, and every other option has a default. The rul
220
230
  of their own: they all read these shared settings, so they always agree on ticket formats, states
221
231
  and markers. To turn a rule off, set it to `'off'` as usual.
222
232
 
223
- | Option | Type | Default | Description |
224
- | ----------------------------- | ----------------------------------------------------------------------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
225
- | `framework` | `'playwright' \| 'jest' \| 'vitest' \| 'mocha' \| 'cypress' \| 'webdriverio'` | required | The test framework of the files the config applies to. Each framework's config, such as `configs.playwright`, sets it, and `configure()` needs it. See [Frameworks](#frameworks). |
226
- | `ticket` | `TicketSpec \| TicketSpec[]` | `{ preset: 'any' }` | Accepted ticket formats. An array accepts a ticket that matches any entry. See [Ticket presets](#ticket-presets). |
227
- | `placeholders` | `string[]` | see above | Tickets rejected as placeholders. `*` matches any characters. Case-insensitive; a leading `#` is ignored. Your list replaces the default one, so repeat the defaults you want to keep. Tickets numbered 0 are always rejected. |
228
- | `lifecycleTags` | `boolean` | `false` | Turns on the `new` (`@new`) and `quarantined` (`@quarantined`) states. |
229
- | `states` | `{ [state]: boolean \| { enabled?, tag?, marker?, ticket? } }` | `skip`, `fixme` and `todo` on | Turns built-in states (`skip`, `fixme`, `fail`, `slow`, `todo`, `new`, `quarantined`) on or off, renames their tag or marker, or gives them their own ticket format. `fail` and `slow` are off by default; a setting for `new` or `quarantined` here wins over `lifecycleTags`. Only `new` and `quarantined` have a tag to rename; see [Tag names](#tag-names). `fixme` and `slow` exist only in Playwright, `todo` in every framework but Playwright, and `fail` in Playwright, Jest and Vitest, so turning one on elsewhere has no effect. |
230
- | `customStates` | `{ [name]: { when, marker, ticket? } }` | `{}` | Adds a state for a tag, e.g. `{ 'needs-data': { when: '@needs-data', marker: 'NEEDS-DATA' } }`. `when` is a tag and `marker` an uppercase keyword; neither can be shared with another state. |
231
- | `requireTicketForConditional` | `boolean` | `false` | Require a ticket for conditional skips too: `test.skip(condition, ...)`, `if (...) test.skip()`, `(condition ? test.skip : test)(...)`, a skip in a `catch` block, after other code in a `try` block with a `catch`, or after an early `return` or `throw`. |
232
- | `allowBlankLine` | `boolean` | `false` | Let blank lines separate a marker from its test. |
233
- | `allowNotes` | `boolean` | `false` | Allow free text after the tickets, e.g. `// SKIP: TRADE-123 flaky on CI`. Applies to markers and to `FIXME` / `TODO` comments. |
234
- | `comments` | `false \| { keywords?: string[] }` | `{ keywords: ['FIXME', 'TODO'] }` | Comment keywords that must start with a ticket anywhere in a test file, e.g. `// TODO: TRADE-123`. Your keywords replace the default ones. `false` allows free-form comments. |
235
- | `testFunctions` | `string[]` | `['test']` | Names treated as the framework's `test`. Jest and Vitest default to `['test', 'it']`, and Mocha, Cypress and WebdriverIO to `['it', 'specify', 'test']`. Your list replaces the default one, so include those names if you still need them. See below. |
236
- | `reportUnreadableTags` | `boolean` | `false` | When a tag state is on, report titles that aren't static text, and tags or details that can't be read in the file, such as ones imported from another module, since their tags can't be checked. |
233
+ | Option | Type | Default | Description |
234
+ | ----------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
235
+ | `framework` | `'playwright' \| 'jest' \| 'vitest' \| 'mocha' \| 'cypress' \| 'webdriverio' \| 'node-test' \| 'jasmine'` | required | The test framework of the files the config applies to. Each framework's config, such as `configs.playwright`, sets it, and `configure()` needs it. See [Frameworks](#frameworks). |
236
+ | `ticket` | `TicketSpec \| TicketSpec[]` | `{ preset: 'any' }` | Accepted ticket formats. An array accepts a ticket that matches any entry. See [Ticket presets](#ticket-presets). |
237
+ | `placeholders` | `string[]` | see above | Tickets rejected as placeholders. `*` matches any characters. Case-insensitive; a leading `#` is ignored. Your list replaces the default one, so repeat the defaults you want to keep. Tickets numbered 0 are always rejected. |
238
+ | `lifecycleTags` | `boolean` | `false` | Turns on the `new` (`@new`) and `quarantined` (`@quarantined`) states. |
239
+ | `states` | `{ [state]: boolean \| { enabled?, tag?, marker?, ticket? } }` | `skip`, `fixme` and `todo` on | Turns built-in states (`skip`, `fixme`, `fail`, `slow`, `todo`, `new`, `quarantined`) on or off, renames their tag or marker, or gives them their own ticket format. `fail` and `slow` are off by default; a setting for `new` or `quarantined` here wins over `lifecycleTags`. Only `new` and `quarantined` have a tag to rename; see [Tag names](#tag-names). `fixme` and `slow` exist only in Playwright, `todo` in every framework but Playwright, and `fail` in Playwright, Jest and Vitest, so turning one on elsewhere has no effect. |
240
+ | `customStates` | `{ [name]: { when, marker, ticket? } }` | `{}` | Adds a state for a tag, e.g. `{ 'needs-data': { when: '@needs-data', marker: 'NEEDS-DATA' } }`. `when` is a tag and `marker` an uppercase keyword; neither can be shared with another state. |
241
+ | `requireTicketForConditional` | `boolean` | `false` | Require a ticket for conditional skips too: `test.skip(condition, ...)`, `if (...) test.skip()`, `(condition ? test.skip : test)(...)`, a skip in a `catch` block, after other code in a `try` block with a `catch`, or after an early `return` or `throw`. |
242
+ | `allowBlankLine` | `boolean` | `false` | Let blank lines separate a marker from its test. |
243
+ | `allowNotes` | `boolean` | `false` | Allow free text after the tickets, e.g. `// SKIP: TRADE-123 flaky on CI`. Applies to markers and to `FIXME` / `TODO` comments. |
244
+ | `comments` | `false \| { keywords?: string[] }` | `{ keywords: ['FIXME', 'TODO'] }` | Comment keywords that must start with a ticket anywhere in a test file, e.g. `// TODO: TRADE-123`. Your keywords replace the default ones. `false` allows free-form comments. |
245
+ | `testFunctions` | `string[]` | `['test']` | Names treated as the framework's `test`. Jest and Vitest default to `['test', 'it']`, Mocha, Cypress and WebdriverIO to `['it', 'specify', 'test']`, and Jasmine to `['it']`. Your list replaces the default one, so include those names if you still need them. See below. |
246
+ | `reportUnreadableTags` | `boolean` | `false` | When a tag state is on, report titles that aren't static text, and tags or details that can't be read in the file, such as ones imported from another module, since their tags can't be checked. |
237
247
 
238
248
  ### Tag names
239
249
 
@@ -264,21 +274,45 @@ a `customStates` entry.
264
274
 
265
275
  ### Ticket presets
266
276
 
267
- | Preset | Accepts | Options |
268
- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
269
- | `any` | `PROJ-123`, `#123`, `owner/repo#123`, any http(s) URL | |
270
- | `jira` | `TRADE-123`, `https://<host>/browse/TRADE-123` (also under a path, as in `/jira/browse/TRADE-123`), board URLs with `selectedIssue=TRADE-123` | `projects`; `host` (any host by default) |
271
- | `github` | `#123`, `owner/repo#123`, `https://github.com/owner/repo/issues/123` (or `/pull/`) | `host` for GitHub Enterprise (`github.com` by default) |
272
- | `gitlab` | `#123`, `group/project#123`, `https://gitlab.com/group/project/-/issues/123` | `host` for self-managed (`gitlab.com` by default) |
273
- | `linear` | `RISK-123`, `https://linear.app/<workspace>/issue/RISK-123/...` | `teams` |
274
- | `azure-devops` | `AB#123`, `https://dev.azure.com/<org>/<project>/_workitems/edit/123` | `host` for Azure DevOps Server (`dev.azure.com` and `*.visualstudio.com` by default) |
275
- | `numeric` | `4821` | `minLength` (1), `maxLength` (20) |
276
- | `pattern` | anything matching your regex, anchored at both ends | `pattern`, `flags` |
277
-
278
- `projects` (Jira) and `teams` (Linear) are keys as they appear in tickets, in uppercase: `TRADE` for
279
- `TRADE-123`. Each preset takes only its own options, so `host` on the `any` preset fails with a clear
280
- message instead of being ignored. A `pattern` always matches the whole ticket, so the `g` and `y`
281
- flags make no difference and are ignored.
277
+ | Preset | Accepts | Options |
278
+ | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
279
+ | `any` | `PROJ-123`, `#123`, `owner/repo#123`, any http(s) URL | |
280
+ | `jira` | `TRADE-123`, `https://<host>/browse/TRADE-123` (also under a path, as in `/jira/browse/TRADE-123`), board URLs with `selectedIssue=TRADE-123` | `projects`; `host` (any host by default) |
281
+ | `github` | `#123`, `owner/repo#123`, `https://github.com/owner/repo/issues/123` (or `/pull/`) | `host` for GitHub Enterprise (`github.com` by default) |
282
+ | `gitlab` | `#123`, `group/project#123`, `https://gitlab.com/group/project/-/issues/123` | `host` for self-managed (`gitlab.com` by default) |
283
+ | `linear` | `RISK-123`, `https://linear.app/<workspace>/issue/RISK-123/...` | `teams` |
284
+ | `azure-devops` | `AB#123`, `#123`, `https://dev.azure.com/<org>/<project>/_workitems/edit/123` | `host` for Azure DevOps Server (`dev.azure.com` and `*.visualstudio.com` by default) |
285
+ | `trello` | card links, `https://trello.com/c/nqPiDKmw` with or without the `/9-title` after it | |
286
+ | `notion` | Unique IDs such as `TASK-123`, `https://www.notion.so/TASK-123`, page links that end in the page ID (also `https://app.notion.com/p/...`), database links with `?p=<page ID>` | `prefixes`; `host` for a custom domain (`notion.so`, `www.notion.so`, `app.notion.com` and `*.notion.site` by default) |
287
+ | `asana` | task links, `https://app.asana.com/0/<project>/<task>` and the newer `https://app.asana.com/1/<workspace>/.../task/<task>` | |
288
+ | `clickup` | `CU-86abc123`, `#86abc123`, custom IDs such as `DEV-123`, `https://app.clickup.com/t/86abc123` or `/t/<workspace>/DEV-123`, view links with `?p=<task>` | `prefixes` for custom IDs |
289
+ | `redmine` | `#123`, `#123-4` and `#123#note-4` (a note), `##123`; with `host`, `https://<host>/issues/123` (also under a path) | `host`, needed for links |
290
+ | `monday` | item links, `https://<account>.monday.com/boards/<board>/pulses/<item>`, and custom keys such as `TIX-001` | `prefixes` for custom keys |
291
+ | `youtrack` | `KT-123`, `https://<host>/issue/KT-123/...` or `/projects/KT/issues/KT-123` (also under a path, as in `/youtrack/issue/KT-123`) | `projects`; `host` for Server or a custom domain (`youtrack.jetbrains.com`, `*.youtrack.cloud` and `*.myjetbrains.com` by default) |
292
+ | `basecamp` | to-do and card links, `https://3.basecamp.com/<account>/buckets/<project>/todos/<id>` or `/card_tables/cards/<id>` | |
293
+ | `shortcut` | `sc-123`, `[sc-123]`, `https://app.shortcut.com/<workspace>/story/123/...` | |
294
+ | `url` | links to the hosts you list, and nothing else | `hosts`, required: host names such as `tasks.example.com`, with no scheme, port or path; `*.example.com` matches its subdomains |
295
+ | `numeric` | `4821` | `minLength` (1), `maxLength` (20) |
296
+ | `pattern` | anything matching your regex, anchored at both ends | `pattern`, `flags` |
297
+
298
+ `projects` (Jira, YouTrack), `teams` (Linear) and `prefixes` (Notion, ClickUp, monday.com) are keys as
299
+ they appear in tickets, in uppercase: `TRADE` for `TRADE-123`. YouTrack project IDs may also start with a digit, as `4DM`. Without them, any key passes. `host` is a host name such as `jira.example.com`, or
300
+ `*.example.com` for every subdomain, as in `{ preset: 'jira', host: '*.atlassian.net' }`. Each preset
301
+ takes only its own options, so `host` on the `any` preset fails with a clear message instead of being
302
+ ignored. A `pattern` always matches the whole ticket, so the `g` and `y` flags make no difference and are
303
+ ignored.
304
+
305
+ A preset rejects links that aren't one ticket in its tracker, such as a Trello board, an Asana project or
306
+ a Notion database view. Redmine runs on your own server, and `/issues/123` is also GitHub's path, so its
307
+ links pass only once `host` is set.
308
+
309
+ For a tracker without a preset, use `url` with its host, for example
310
+ `{ preset: 'url', hosts: ['www.wrike.com'] }` for Wrike links such as
311
+ `https://www.wrike.com/open.htm?id=4821`. It works the same for Microsoft Planner (`tasks.office.com`),
312
+ Airtable (`airtable.com`), Smartsheet (`app.smartsheet.com`) and trackers of your own. For another
313
+ tracker's keys, such as `WEB-12`, `any` or `pattern` works. Gitea, Forgejo and Codeberg use GitHub's link layout,
314
+ so the `github` preset with their `host` covers them. Bitbucket has no issue tracker since August 2026,
315
+ so it has no preset.
282
316
 
283
317
  Presets combine, and each state can have its own format. For example, bugs in Jira and flaky-test
284
318
  reports in GitHub:
@@ -337,8 +371,17 @@ In `node:test` files:
337
371
  - Subtests: `t.test(...)` on the context a test's body receives.
338
372
  - Any name in `testFunctions`, which defaults to `['test', 'it']`. `node:test` has no globals.
339
373
 
374
+ In Jasmine files:
375
+
376
+ - The globals `it`, `xit`, `fit`, `describe`, `xdescribe`, `fdescribe`, and the hooks `beforeEach`,
377
+ `afterEach`, `beforeAll` and `afterAll`. With the runner's `globals: false`, Jasmine 7 takes the same
378
+ names from `jasmine-core`, so names imported or required from it count too, including aliases and
379
+ the whole module, as `core.xit` after `const core = require('jasmine-core')`.
380
+ - `pending()` and `notApplicable()`, as globals or imported by name.
381
+ - Any name in `testFunctions`, which defaults to `['it']`. Jasmine has no `test`.
382
+
340
383
  A name imported or required from another test runner (Playwright, `vitest`, `@jest/globals`,
341
- `node:test`, `bun:test`, `mocha`, `ava`, `tap`, `uvu`), or a variable set from one such as
384
+ `node:test`, `jasmine-core`, `bun:test`, `mocha`, `ava`, `tap`, `uvu`), or a variable set from one such as
342
385
  `const test = require('ava')`, is never treated as the framework's `test`, and neither is a local
343
386
  variable or function declared inside a function that happens to be called `test`.
344
387
 
@@ -366,8 +409,9 @@ Not covered in this version: tests generated in loops with dynamic titles, and t
366
409
  from other modules (see `reportUnreadableTags`), fixtures imported from other modules under a name not in
367
410
  `testFunctions`, a Vitest test with only a title, which Vitest runs as a todo but which looks the same as a
368
411
  helper called `test`, Playwright annotations as markers, Cypress tests skipped with @cypress/skip-test's
369
- `skipOn()` and `onlyOn()`, `node:test`'s experimental `tags` option and `getTestContext()`, and other
370
- runners such as Jasmine and Bun.
412
+ `skipOn()` and `onlyOn()`, `node:test`'s experimental `tags` option and `getTestContext()`, Jasmine's
413
+ `pending()` in a helper function that tests call, Jasmine 5 and 6's `require('jasmine-core').noGlobals()`,
414
+ and other runners such as Bun.
371
415
 
372
416
  ## TypeScript
373
417
 
package/dist/index.d.ts CHANGED
@@ -33,5 +33,5 @@ declare const plugin: TestGovernancePlugin;
33
33
  * so all rules read the same ticket formats and states.
34
34
  */
35
35
  export declare function configure(options: GovernanceOptions): FlatConfig;
36
- export declare const configs: Record<"playwright" | "jest" | "vitest" | "mocha" | "cypress" | "webdriverio" | "node-test", FlatConfig>;
36
+ export declare const configs: Record<"playwright" | "jest" | "vitest" | "mocha" | "cypress" | "webdriverio" | "node-test" | "jasmine", FlatConfig>;
37
37
  export default plugin;
package/dist/index.js CHANGED
@@ -52,7 +52,7 @@ for (const framework of FRAMEWORK_NAMES) {
52
52
  // tools that list the configs don't see it.
53
53
  Object.defineProperty(plugin.configs, 'recommended', {
54
54
  get() {
55
- throw new ConfigError("configs.recommended was removed in 0.5.0. Use the config for your framework: configs.playwright, configs.jest, configs.vitest, configs.mocha, configs.cypress, configs.webdriverio or configs['node-test']. See https://github.com/Huldoser/eslint-plugin-test-governance/blob/main/docs/upgrading.md");
55
+ throw new ConfigError("configs.recommended was removed in 0.5.0. Use the config for your framework: configs.playwright, configs.jest, configs.vitest, configs.mocha, configs.cypress, configs.webdriverio, configs['node-test'] or configs.jasmine. See https://github.com/Huldoser/eslint-plugin-test-governance/blob/main/docs/upgrading.md");
56
56
  },
57
57
  });
58
58
  export const configs = plugin.configs;
@@ -73,6 +73,8 @@ export interface Subject {
73
73
  ownSkip: boolean;
74
74
  markers: Marker[];
75
75
  dynamicTitle: boolean;
76
+ /** For a runtime function such as Jasmine's `pending()`, its name; messages call the others by their state. */
77
+ functionName?: string;
76
78
  }
77
79
  export interface Analysis {
78
80
  subjects: Subject[];
@@ -82,6 +82,7 @@ function collectTestNames(program, framework, configured) {
82
82
  const roots = new Map(framework.globals);
83
83
  for (const name of configured)
84
84
  roots.set(name, TEST);
85
+ const runtime = new Map();
85
86
  const modules = new Set();
86
87
  const mergeTests = new Set();
87
88
  /** Names bound to another test runner, such as `ava` in `import ava from 'ava'`. */
@@ -123,6 +124,12 @@ function collectTestNames(program, framework, configured) {
123
124
  roots.delete(name);
124
125
  others.add(name);
125
126
  };
127
+ /** Takes `name` for the runtime function `imported`, as in `const { pending: pend } = require('jasmine-core')`. */
128
+ const bindRuntime = (name, imported) => {
129
+ const modifier = imported === undefined ? undefined : framework.runtimeFunctions.get(imported);
130
+ if (imported !== undefined && modifier !== undefined)
131
+ runtime.set(name, { name: imported, modifier });
132
+ };
126
133
  for (const statement of program.body) {
127
134
  if (statement.type === 'ImportDeclaration') {
128
135
  const source = statement.source.value;
@@ -144,6 +151,7 @@ function collectTestNames(program, framework, configured) {
144
151
  }
145
152
  else {
146
153
  const root = framework.exports.get(importedName(spec));
154
+ bindRuntime(local, importedName(spec));
147
155
  if (root)
148
156
  roots.set(local, root);
149
157
  else if (importedName(spec) === 'mergeTests')
@@ -200,6 +208,7 @@ function collectTestNames(program, framework, configured) {
200
208
  setOther(prop.value.name);
201
209
  else if (framework.ownsModule(source)) {
202
210
  const root = framework.exports.get(propertyName(prop) ?? '');
211
+ bindRuntime(prop.value.name, propertyName(prop));
203
212
  if (root)
204
213
  roots.set(prop.value.name, root);
205
214
  }
@@ -207,7 +216,7 @@ function collectTestNames(program, framework, configured) {
207
216
  }
208
217
  }
209
218
  }
210
- return { roots, modules };
219
+ return { roots, modules, runtime };
211
220
  }
212
221
  /** The name a property key spells out: `test` in `{ test }`, `{ 'test': t }` or `{ ['test']: t }`. */
213
222
  function propertyName(prop) {
@@ -225,6 +234,21 @@ function requireSource(node) {
225
234
  const first = node.arguments.at(0);
226
235
  return first?.type === 'Literal' && typeof first.value === 'string' ? first.value : undefined;
227
236
  }
237
+ /**
238
+ * Where the file declares what `id` refers to: `inside` a function or block, a parameter included, at the
239
+ * `top` level, or nowhere, for a `global`.
240
+ */
241
+ function declaredWhere(sourceCode, id) {
242
+ for (let scope = sourceCode.getScope(id); scope; scope = scope.upper) {
243
+ const variable = scope.set.get(id.name);
244
+ if (!variable)
245
+ continue;
246
+ if (scope.block.type !== 'Program')
247
+ return 'inside';
248
+ return variable.defs.length > 0 ? 'top' : 'global';
249
+ }
250
+ return 'global';
251
+ }
228
252
  /**
229
253
  * Whether `id` refers to a variable or function declared inside a function or block, such as a local
230
254
  * helper that happens to be called `test`. Top-level variables, imports, parameters and globals still count.
@@ -816,14 +840,31 @@ function runAnalysis(sourceCode, options) {
816
840
  if (body?.type === 'FunctionExpression')
817
841
  thisBodies.add(body);
818
842
  };
819
- /** What a body can change the test's state with: Playwright's `testInfo` or Vitest's test context. */
843
+ /**
844
+ * What a body can change the test's state with: Playwright's `testInfo`, Vitest's test context, or
845
+ * Jasmine's `pending()`.
846
+ */
820
847
  const bodyInfos = (args, path) => {
821
848
  const body = args.findLast(isFunction);
822
- // `test.each(table)` passes the row to the body, and `test.for(cases)` passes it before the context.
823
- if (!framework.info || !body || path.includes('each()'))
849
+ if (!body)
824
850
  return [];
851
+ const functions = [
852
+ ...[...framework.runtimeFunctions].map(([name, modifier]) => ({
853
+ name,
854
+ method: modifier,
855
+ runtimeFunction: { name, imported: false },
856
+ })),
857
+ ...[...testNames.runtime].map(([local, fn]) => ({
858
+ name: local,
859
+ method: fn.modifier,
860
+ runtimeFunction: { name: fn.name, imported: true },
861
+ })),
862
+ ];
863
+ // `test.each(table)` passes the row to the body, and `test.for(cases)` passes it before the context.
864
+ if (!framework.info || path.includes('each()'))
865
+ return functions;
825
866
  const param = body.params.at(framework.info.param + (path.includes('for()') ? 1 : 0));
826
- return infoNames(param, framework.info.methods);
867
+ return [...functions, ...infoNames(param, framework.info.methods)];
827
868
  };
828
869
  /**
829
870
  * What a callee declares and with which members: `describe` and `['skip']` for `test.describe.skip`.
@@ -834,12 +875,16 @@ function runAnalysis(sourceCode, options) {
834
875
  return undefined;
835
876
  const playwrightDescribe = framework.playwright && path[0] === 'describe';
836
877
  const kind = playwrightDescribe ? 'describe' : root.kind;
837
- return { kind, members: [...root.modifiers, ...(playwrightDescribe ? path.slice(1) : path)], args };
878
+ const written = playwrightDescribe ? path.slice(1) : path;
879
+ return { kind, members: [...root.modifiers, ...written], written, args };
838
880
  };
839
- /** Whether a call with these members and arguments declares a test or describe of this kind. */
840
- const declares = ({ kind, members }, args) => {
881
+ /**
882
+ * Whether a call with these members and arguments declares a test or describe of this kind. Only the
883
+ * members written after the name must exist: Jasmine's `xit` skips, but it has no `it.skip`.
884
+ */
885
+ const declares = ({ kind, members, written }, args) => {
841
886
  const allowed = kind === 'test' ? framework.testMembers : framework.describeMembers;
842
- return members.every((m) => allowed.has(m)) && isDeclaration(sourceCode, framework, kind, args, members);
887
+ return written.every((m) => allowed.has(m)) && isDeclaration(sourceCode, framework, kind, args, members);
843
888
  };
844
889
  /**
845
890
  * `(isCI ? describe.skip : describe)('order entry', ...)`: a test or describe whose function is picked by
@@ -893,7 +938,7 @@ function runAnalysis(sourceCode, options) {
893
938
  const { object } = callee;
894
939
  if (object.type !== 'Identifier' || !infos.findLast((info) => info.name === object.name)?.methods)
895
940
  return undefined;
896
- const declaration = { kind: 'test', members: [], args: new Map() };
941
+ const declaration = { kind: 'test', members: [], written: [], args: new Map() };
897
942
  return declares(declaration, call.arguments)
898
943
  ? declare(call, declaration, bodyInfos(call.arguments, []))
899
944
  : undefined;
@@ -938,7 +983,7 @@ function runAnalysis(sourceCode, options) {
938
983
  };
939
984
  /**
940
985
  * `testInfo.skip()`, `step.skip()`, `test.info().skip()`, Vitest's `context.skip()` or `skip()`,
941
- * Mocha's `this.skip()`, and node:test's `t.skip()` and `t.todo()`.
986
+ * Mocha's `this.skip()`, node:test's `t.skip()` and `t.todo()`, and Jasmine's `pending()`.
942
987
  */
943
988
  const runtimeCall = (call) => {
944
989
  const { callee } = call;
@@ -946,7 +991,17 @@ function runAnalysis(sourceCode, options) {
946
991
  const innermost = (name) => infos.findLast((info) => info.name === name);
947
992
  if (callee.type === 'Identifier') {
948
993
  const info = innermost(callee.name);
949
- return info?.method !== undefined ? runtimeSubject(call, info.method, info.step) : undefined;
994
+ if (info?.method === undefined)
995
+ return undefined;
996
+ // A global such as Jasmine's `pending` counts unless the file declares the name itself; an import of
997
+ // one counts unless a function or block declares the name again.
998
+ const fn = info.runtimeFunction;
999
+ if (fn && declaredWhere(sourceCode, callee) !== (fn.imported ? 'top' : 'global'))
1000
+ return undefined;
1001
+ const subject = runtimeSubject(call, info.method, info.step);
1002
+ if (subject)
1003
+ subject.functionName = fn?.name;
1004
+ return subject;
950
1005
  }
951
1006
  if (callee.type !== 'MemberExpression')
952
1007
  return undefined;
@@ -1105,6 +1160,6 @@ export function describeSubject(subject) {
1105
1160
  if (subject.kind === 'step')
1106
1161
  return 'This step';
1107
1162
  if (subject.kind === 'runtime')
1108
- return `This ${subject.states[0].state.modifier} call`;
1163
+ return `This ${subject.functionName ?? subject.states[0].state.modifier} call`;
1109
1164
  return 'This test';
1110
1165
  }
@@ -2,14 +2,16 @@
2
2
  export declare const MARKER_RE: RegExp;
3
3
  /** A tag as written in options: `@` followed by letters, digits, `-` and `_`. */
4
4
  export declare const TAG_RE: RegExp;
5
+ /** A host with no scheme, port or path: a host name, or `*.` and a domain of two or more labels for its subdomains. */
6
+ export declare const HOST_ENTRY_RE: RegExp;
5
7
  export declare const BUILTIN_STATE_NAMES: readonly ["skip", "fixme", "fail", "slow", "todo", "new", "quarantined"];
6
8
  export type BuiltinStateName = (typeof BUILTIN_STATE_NAMES)[number];
7
9
  /** The built-in states a tag puts a test in, so the only ones whose tag can be renamed. */
8
10
  export declare const TAG_STATE_NAMES: readonly ["new", "quarantined"];
9
11
  export type TagStateName = (typeof TAG_STATE_NAMES)[number];
10
12
  /** The test frameworks the rules understand. */
11
- export declare const FRAMEWORK_NAMES: readonly ["playwright", "jest", "vitest", "mocha", "cypress", "webdriverio", "node-test"];
13
+ export declare const FRAMEWORK_NAMES: readonly ["playwright", "jest", "vitest", "mocha", "cypress", "webdriverio", "node-test", "jasmine"];
12
14
  export type FrameworkName = (typeof FRAMEWORK_NAMES)[number];
13
- export declare const TICKET_PRESETS: readonly ["any", "jira", "github", "gitlab", "linear", "azure-devops", "numeric", "pattern"];
15
+ export declare const TICKET_PRESETS: readonly ["any", "jira", "github", "gitlab", "linear", "azure-devops", "trello", "notion", "asana", "clickup", "redmine", "monday", "youtrack", "basecamp", "shortcut", "url", "numeric", "pattern"];
14
16
  /** Comment keywords that must reference a ticket by default (`// TODO: TRADE-123`). */
15
17
  export declare const DEFAULT_COMMENT_KEYWORDS: string[];