test-inventory 0.6.0 → 0.6.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/dist/config.js CHANGED
@@ -140,6 +140,68 @@ function spread(base, over, at) {
140
140
  return { kind: 'object', keys: new Map(over.keys), rest: over.rest };
141
141
  return { kind: 'object', keys: new Map([...base.keys, ...over.keys]), rest: base.rest };
142
142
  }
143
+ /** `{ ...a, ...b }` of two objects either of which may be unset, as Playwright merges `use` and `expect`. */
144
+ function assign(a, b, at) {
145
+ return [a, b].reduce((merged, over) => (over ? spread(merged, over, at) : merged), emptyObject());
146
+ }
147
+ /**
148
+ * Playwright's `defineConfig(base, over)`: `over`'s keys replace `base`'s, but `expect`, `use` and `build` merge key by
149
+ * key, and `projects` by name. `webServer`, whose lists Playwright joins, isn't read.
150
+ */
151
+ function playwrightMerge(base, over, at) {
152
+ const merged = spread(base, over, at);
153
+ if (over.kind !== 'object' || over.rest)
154
+ return merged;
155
+ for (const key of ['expect', 'use', 'build']) {
156
+ if (merged.keys.has(key))
157
+ merged.keys.set(key, assign(child(base, key), child(over, key), at));
158
+ }
159
+ const overrides = child(over, 'projects');
160
+ // Without projects of its own, `over` keeps those of `base`; with `projects: undefined`, it drops them.
161
+ if (overrides === null || isNullish(overrides))
162
+ return merged;
163
+ const projects = child(base, 'projects');
164
+ const items = projects === null || isNullish(projects) ? [] : projects.kind === 'list' ? projects.items : null;
165
+ const isKnown = items !== null && overrides.kind === 'list';
166
+ merged.keys.set('projects', isKnown ? mergeProjects(items, overrides.items, at) : unknown(at));
167
+ return merged;
168
+ }
169
+ /**
170
+ * Playwright's projects of a config with those of a config over it merged in by name, with their `use` key by key; the
171
+ * others are added after, the last of each name. Unknown when the scan can't tell a project's name.
172
+ */
173
+ function mergeProjects(base, overrides, at) {
174
+ const byName = new Map();
175
+ for (const project of overrides) {
176
+ const name = projectName(project);
177
+ if (name === null)
178
+ return unknown(at);
179
+ byName.set(name, project);
180
+ }
181
+ const projects = [];
182
+ for (const project of base) {
183
+ const name = projectName(project);
184
+ if (name === null)
185
+ return unknown(at);
186
+ const override = byName.get(name);
187
+ byName.delete(name);
188
+ if (!override) {
189
+ projects.push(project);
190
+ continue;
191
+ }
192
+ const merged = spread(spread(emptyObject(), project, at), override, at);
193
+ merged.keys.set('use', assign(child(project, 'use'), child(override, 'use'), at));
194
+ projects.push(merged);
195
+ }
196
+ return { kind: 'list', items: [...projects, ...byName.values()] };
197
+ }
198
+ /** The name Playwright merges a project by: undefined when unset, null when the scan can't tell. */
199
+ function projectName(project) {
200
+ const name = child(project, 'name');
201
+ if (name === null || (name.kind === 'value' && name.value === undefined))
202
+ return undefined;
203
+ return name.kind === 'value' && typeof name.value === 'string' ? name.value : null;
204
+ }
143
205
  /**
144
206
  * One of two settings, as the two sides of a condition: what both agree on is known, and the rest is unknown at `at`,
145
207
  * where the condition is written. Null is a setting neither side sets.
@@ -479,9 +541,9 @@ class ConfigCode {
479
541
  // Vitest's defineConfig() and defineProject() return the config they are given, or the one a function returns.
480
542
  return isFunctionNode(args[0]) ? this.returned(args[0], frame, scopes) : this.evaluate(args[0], frame, scopes);
481
543
  }
482
- // Playwright's defineConfig() merges the configs it is given, each over the ones before it. It also merges their
483
- // `use`, `expect`, `build`, `webServer` and `projects`, which the scan doesn't read yet.
484
- return args.reduce((merged, arg) => spread(merged, this.evaluate(arg, frame, scopes), { frame, node: arg }), emptyObject());
544
+ // Playwright's defineConfig() merges the configs it is given, each over the ones before it.
545
+ const [first, ...others] = args;
546
+ return others.reduce((merged, arg) => playwrightMerge(merged, this.evaluate(arg, frame, scopes), { frame, node: arg }), spread(emptyObject(), this.evaluate(first, frame, scopes), { frame, node: first }));
485
547
  }
486
548
  /**
487
549
  * Where a function a config calls comes from: `name` of `import { name } from 'module'` or
@@ -2,17 +2,20 @@ import { type Node } from 'oxc-parser';
2
2
  import type { ConfigGap, RunsWith } from './config-dependencies.ts';
3
3
  import type { Extraction, Frame, PendingTest } from './extract.ts';
4
4
  import type { ModuleReader, ModuleSource, Problem, Span } from './model.ts';
5
- import type { ParsedFile } from './parse.ts';
5
+ import { type ParsedFile } from './parse.ts';
6
6
  import { type SourceText } from './source.ts';
7
7
  import type { Framework } from './types.ts';
8
8
  import { Values } from './values.ts';
9
9
  /** A file whose code a test can run: the scanned file, a module it imports, or a data file such as JSON. */
10
10
  interface Module {
11
11
  relativeFilePath: string;
12
- /** Null for a file that isn't JavaScript or TypeScript, which is taken by its text. */
12
+ /** Null for a file that isn't JavaScript or TypeScript, which is taken by its text; for a component, its scripts. */
13
13
  parsed: ParsedFile | null;
14
- /** The file's text with line endings read as `\n`, for a file that isn't code. */
15
- text: string | null;
14
+ /**
15
+ * The file's text with line endings read as `\n`, for a file that isn't code or a component; its bytes when it isn't
16
+ * UTF-8 text, such as an image.
17
+ */
18
+ text: string | Uint8Array | null;
16
19
  values: Values;
17
20
  readModule: ModuleReader | null;
18
21
  /** Its top-level code that runs when it is imported, and the modules it imports, which run then too. */
@@ -191,6 +194,11 @@ export declare class DependencyGraph {
191
194
  * takes the whole module, so that nothing it may run is left out.
192
195
  */
193
196
  private exported;
197
+ /**
198
+ * A name a module passes on from another, as `export { quote } from './nasdaq'` does, with the re-export itself: which
199
+ * module the name comes from is code too, so the barrel is among the files a change to which affects the test.
200
+ */
201
+ private passedOn;
194
202
  /** What `export * from source` gives for a name; null when the module behind it doesn't export it. */
195
203
  private reexported;
196
204
  /**
@@ -4,6 +4,7 @@ import { childNodes, isFunctionNode, propertyKey, unwrap } from "./ast.js";
4
4
  import { exportName, parseModule } from "./frameworks/bindings.js";
5
5
  import { destructuredNames, problem, takesWhole } from "./frameworks/common.js";
6
6
  import { shortHash, hashNodes } from "./hash.js";
7
+ import { parseFile } from "./parse.js";
7
8
  import { snippet } from "./source.js";
8
9
  import { bindingNames, isScopeNode, Values } from "./values.js";
9
10
  /** Statements that only declare something, unless code in them runs while they are evaluated. */
@@ -157,6 +158,50 @@ function isAutoOption(property) {
157
158
  property.value.value === true);
158
159
  }
159
160
  const CODE_FILE = /\.[cm]?[jt]sx?$/;
161
+ /** A Vue or Svelte single-file component: markup with its code in `<script>` blocks. */
162
+ const COMPONENT_FILE = /\.(?:vue|svelte)$/;
163
+ /**
164
+ * A comment, which may hold a `<script>` that isn't one, or a script block with its attributes and code. An attribute
165
+ * may hold `>`, as Svelte's `generics="T extends Record<string, number>"` does. Both end where HTML ends them: a
166
+ * comment also at `--!>`, a script at `</script` with anything before its `>`.
167
+ */
168
+ const COMPONENT_BLOCK = /<!--[\s\S]*?--!?>|<script\b((?:[^>"']|"[^"]*"|'[^']*')*)>([\s\S]*?)<\/script(?:[\s/][^>]*)?>/gi;
169
+ /**
170
+ * A file's bytes with line endings as `\n`, as git may check out a text file with either; one git takes for binary, with
171
+ * a NUL byte in its first 8,000, stays as it is.
172
+ */
173
+ function withLineFeeds(bytes) {
174
+ if (bytes.subarray(0, 8000).includes(0))
175
+ return bytes;
176
+ // Latin-1 maps each byte to one character and back, so only the line endings change.
177
+ return Buffer.from(Buffer.from(bytes).toString('latin1').replace(/\r\n?/g, '\n'), 'latin1');
178
+ }
179
+ /** Whether a script block holds code: one with a `type` other than a JavaScript one, as JSON-LD, holds data. */
180
+ function isScriptCode(attributes) {
181
+ const type = /(?:^|\s)type\s*=\s*["']?([^"'\s>]*)/i.exec(attributes)?.[1] ?? '';
182
+ return /^(?:module|(?:text|application)\/(?:java|ecma)script)?$/i.test(type);
183
+ }
184
+ /**
185
+ * A component's script blocks as one module, the rest of the file blanked so that offsets stay those of the file, and
186
+ * a file name for the language they are in: `lang="ts"` is TypeScript, no `lang` JavaScript.
187
+ */
188
+ function componentScript(code) {
189
+ // A byte order mark stays, for the parser to leave out as it does from any file.
190
+ const blank = (text) => text.replace(/[^\r\n\uFEFF]/g, ' ');
191
+ let script = '';
192
+ let end = 0;
193
+ let lang = 'js';
194
+ for (const match of code.matchAll(COMPONENT_BLOCK)) {
195
+ const [whole, attributes, body] = match;
196
+ if (whole.startsWith('<!--') || !isScriptCode(attributes))
197
+ continue;
198
+ const start = match.index + '<script'.length + attributes.length + 1;
199
+ script += blank(code.slice(end, start)) + body;
200
+ end = start + body.length;
201
+ lang = /(?:^|\s)lang\s*=\s*["']?(\w+)/.exec(attributes)?.[1] ?? lang;
202
+ }
203
+ return { fileName: `component.${lang}`, code: script + blank(code.slice(end)) };
204
+ }
160
205
  const DECLARATION_FILE = /\.d\.[cm]?ts$/;
161
206
  /**
162
207
  * What an import being looked up stands for while the lookup leads back to it, through re-exports: nothing found
@@ -469,7 +514,8 @@ export class DependencyGraph {
469
514
  hashOf(unit) {
470
515
  if (unit.hash === undefined) {
471
516
  if (unit.module.text !== null) {
472
- unit.hash = shortHash(unit.module.text);
517
+ // Counted once, through the unit of all of it, however many of a component's units a test reaches.
518
+ unit.hash = unit.module.whole.includes(unit) ? shortHash(unit.module.text) : null;
473
519
  }
474
520
  else {
475
521
  const cut = [...unit.cut].map(([start, end]) => ({ start, end }));
@@ -490,7 +536,12 @@ export class DependencyGraph {
490
536
  this.modules.set(parsed, module);
491
537
  }
492
538
  else {
493
- module = this.newModule(source.relativeFilePath, null, source.code.replace(/\r\n?/g, '\n'), new Values(), null);
539
+ const text = source.bytes ? withLineFeeds(source.bytes) : source.code.replace(/\r\n?/g, '\n');
540
+ // A component's markup and styles shape what it renders, so it is taken by its text, and its scripts are
541
+ // followed for the modules they import.
542
+ const script = COMPONENT_FILE.test(source.relativeFilePath) ? componentScript(source.code) : null;
543
+ const parsed = script && parseFile(script.fileName, script.code);
544
+ module = this.newModule(source.relativeFilePath, parsed, text, new Values(), parsed && source.readModule);
494
545
  }
495
546
  this.modules.set(source, module);
496
547
  }
@@ -572,7 +623,7 @@ export class DependencyGraph {
572
623
  importUnit(module, statement, specifier) {
573
624
  let unit = this.imports.get(specifier);
574
625
  if (!unit) {
575
- const roots = [statement.source, specifier].map((node) => ({ node, scopes: [] }));
626
+ const roots = [...new Set([statement.source, specifier])].map((node) => ({ node, scopes: [] }));
576
627
  unit = this.newUnit(module, roots, new Map(), false);
577
628
  unit.edges = new Set();
578
629
  this.imports.set(specifier, unit);
@@ -682,8 +733,12 @@ export class DependencyGraph {
682
733
  ];
683
734
  }
684
735
  if (module.parsed.error) {
685
- return [this.placeUnit(from, at, `'${specifier}' could not be parsed, so dependencyHash doesn't cover it.`)];
736
+ const place = this.placeUnit(from, at, `'${specifier}' could not be parsed, so dependencyHash doesn't cover it.`);
737
+ return module.text === null ? [place] : [this.textUnit(module), place];
686
738
  }
739
+ // A component's markup may use anything its scripts declare or import, its types included.
740
+ if (module.text !== null)
741
+ return [this.wholeUnit(module, false)];
687
742
  return name === '' ? [this.loadUnit(module, types)] : this.exported(module, name, types, new Set());
688
743
  }
689
744
  /**
@@ -711,7 +766,7 @@ export class DependencyGraph {
711
766
  : [this.declarationUnit(module, declaration, types)];
712
767
  }
713
768
  if (statement.type === 'ExportAllDeclaration' && statement.exported && exportName(statement.exported) === name) {
714
- return this.imported(module, statement.source.value, '*', types, statement);
769
+ return this.passedOn(module, statement, statement.source, statement.exported, '*', types);
715
770
  }
716
771
  if (statement.type !== 'ExportNamedDeclaration')
717
772
  continue;
@@ -735,7 +790,7 @@ export class DependencyGraph {
735
790
  continue;
736
791
  const localName = exportName(specifier.local);
737
792
  return statement.source
738
- ? this.imported(module, statement.source.value, localName, types, statement)
793
+ ? this.passedOn(module, statement, statement.source, specifier, localName, types)
739
794
  : local(localName);
740
795
  }
741
796
  // `export *` passes on every name but `default`, and only when the module doesn't export the name itself.
@@ -745,11 +800,19 @@ export class DependencyGraph {
745
800
  continue;
746
801
  const found = this.reexported(module, statement.source.value, name, types, searched);
747
802
  if (found)
748
- return found;
803
+ return [...found, this.importUnit(module, statement, statement.source)];
749
804
  }
750
805
  }
751
806
  return [this.wholeUnit(module, types)];
752
807
  }
808
+ /**
809
+ * A name a module passes on from another, as `export { quote } from './nasdaq'` does, with the re-export itself: which
810
+ * module the name comes from is code too, so the barrel is among the files a change to which affects the test.
811
+ */
812
+ passedOn(module, statement, source, specifier, name, types) {
813
+ const units = this.imported(module, source.value, name, types, statement);
814
+ return units === IN_PROGRESS ? units : [...units, this.importUnit(module, { source }, specifier)];
815
+ }
753
816
  /** What `export * from source` gives for a name; null when the module behind it doesn't export it. */
754
817
  reexported(from, source, name, types, searched) {
755
818
  const read = this.read(from, source);
@@ -1161,10 +1224,22 @@ export class DependencyGraph {
1161
1224
  }
1162
1225
  break;
1163
1226
  }
1164
- case 'ImportDeclaration':
1165
- if (node.importKind !== 'type' || types)
1166
- add(this.imported(module, node.source.value, '', types, node));
1227
+ case 'ImportDeclaration': {
1228
+ if (module.text === null) {
1229
+ if (node.importKind !== 'type' || types)
1230
+ add(this.imported(module, node.source.value, '', types, node));
1231
+ return;
1232
+ }
1233
+ // A component's markup uses what its scripts import, where the scan doesn't look, and Vue builds the props
1234
+ // of `defineProps<Props>()` from the types, so every import counts, types with their types.
1235
+ const isType = node.importKind === 'type';
1236
+ add(this.imported(module, node.source.value, '', isType, node));
1237
+ for (const specifier of node.specifiers) {
1238
+ const isTypeName = isType || (specifier.type === 'ImportSpecifier' && specifier.importKind === 'type');
1239
+ add(this.toDeclaration(module, specifier, specifier.local.name, null, isTypeName));
1240
+ }
1167
1241
  return;
1242
+ }
1168
1243
  case 'TSImportEqualsDeclaration':
1169
1244
  if (node.moduleReference.type !== 'TSExternalModuleReference')
1170
1245
  break;
package/dist/extract.js CHANGED
@@ -753,13 +753,16 @@ class Walker {
753
753
  mergeRun(call.target.hookRun, settings);
754
754
  }
755
755
  }
756
- /** Counts `expect()`, `expect.soft()` and `expect.poll()`, and reads `expect.assertions(n)` and `expect.hasAssertions()`. */
756
+ /**
757
+ * Counts `expect()`, `expect.soft()`, `expect.poll()` and `expect.element()`, and reads `expect.assertions(n)` and
758
+ * `expect.hasAssertions()`.
759
+ */
757
760
  expectCall(call, test) {
758
761
  const callee = unwrap(call.callee);
759
762
  const [root, member] = callee.type === 'MemberExpression' ? [unwrap(callee.object), propertyName(callee)] : [callee, 'call'];
760
763
  if (root.type !== 'Identifier' || root.name !== 'expect')
761
764
  return;
762
- if (member === 'call' || member === 'soft' || member === 'poll')
765
+ if (member === 'call' || member === 'soft' || member === 'poll' || member === 'element')
763
766
  test.expectCount += 1;
764
767
  if (member === 'hasAssertions')
765
768
  test.expectsAssertions = true;
@@ -75,7 +75,7 @@ export declare class VitestAdapter implements Adapter {
75
75
  /** Vitest's order: `only` wins over `skip`, which wins over `todo`. */
76
76
  private states;
77
77
  private readDeclarationOptions;
78
- /** `retry` as a number, or since Vitest 4.1 as `{ count, delay, condition }`; the runner ignores an object before. */
78
+ /** `retry` as a number, or since Vitest 4.1 as `{ count, delay, condition }`. */
79
79
  private retry;
80
80
  private meta;
81
81
  private caseCount;
@@ -27,6 +27,7 @@ const MODIFIERS = {
27
27
  };
28
28
  const TABLES = new Set(['each', 'for']);
29
29
  const CONDITIONS = new Set(['skipIf', 'runIf']);
30
+ const STATE_FLAGS = ['only', 'todo', 'fails'];
30
31
  const DERIVATIONS = new Set(['extend', 'override', 'scoped']);
31
32
  /** Hooks, with the position of the test context among their callback's parameters. */
32
33
  const HOOKS = new Map([
@@ -634,6 +635,17 @@ export class VitestAdapter {
634
635
  const chain = new Set(links.filter((link) => MODIFIERS[kind].has(link.name)).map((link) => link.name));
635
636
  const flags = new Set(chain);
636
637
  let conditions = links.filter((link) => CONDITIONS.has(link.name)).map((link) => this.linkCondition(link, context));
638
+ // From Vitest 5.0.5 a test's `skip`, `skipIf` and `runIf` each set its `skip`, so the last of them decides, and a
639
+ // `skipIf` or `runIf` replaces the skip of a tag or of a describe around. The scan applies that to all of Vitest 5.
640
+ const lastSkip = links.filter((link) => link.name === 'skip' || CONDITIONS.has(link.name)).at(-1);
641
+ const skipsByChain = kind === 'test' && lastSkip !== undefined && atLeast(this.file.version, 5, 0);
642
+ const conditionSetsSkip = skipsByChain && CONDITIONS.has(lastSkip.name);
643
+ if (skipsByChain) {
644
+ flags.delete('skip');
645
+ conditions = conditionSetsSkip ? [this.linkCondition(lastSkip, context)] : [];
646
+ if (!conditionSetsSkip)
647
+ flags.add('skip');
648
+ }
637
649
  // A test's own options win over its chain, so `test.skip('title', { skip: false }, fn)` runs. The chain wins for a
638
650
  // test's mode, and for everything on a describe.
639
651
  for (const [flag, value] of read.flags) {
@@ -653,7 +665,7 @@ export class VitestAdapter {
653
665
  // win. A `skip` passed down skips them, and turns todo into skip, as skip goes before todo.
654
666
  const line = this.file.source.line(call.start);
655
667
  const around = context.passedDown;
656
- const passed = read.skip ? null : around;
668
+ const passed = read.skip || conditionSetsSkip ? null : around;
657
669
  const skips = passed?.condition.applies === 'always' && !flags.has('only');
658
670
  const table = links.find((link) => TABLES.has(link.name)) ?? null;
659
671
  const isTodo = flags.has('todo') || body === undefined;
@@ -704,6 +716,10 @@ export class VitestAdapter {
704
716
  fixtures: fixturesOf(base.hashed, index, this.file.version),
705
717
  takesAllFixtures: takesWhole(base.hashed, index),
706
718
  usesBench: atLeast(this.file.version, 5, 0) && destructuredNames(base.hashed, index).includes('bench'),
719
+ ownStateOptions: [
720
+ ...(read.skip || conditionSetsSkip ? ['skip'] : []),
721
+ ...STATE_FLAGS.filter((flag) => read.flags.has(flag)),
722
+ ],
707
723
  };
708
724
  }
709
725
  return refusal ? refused(call, refusal, context, () => declared) : declared;
@@ -763,8 +779,14 @@ export class VitestAdapter {
763
779
  return result;
764
780
  const options = readOptions(this.file, context.scopes, node);
765
781
  problems.push(...unresolvedOptions(this.file, options, 'its flags, tags and meta are missing'));
782
+ // From Vitest 5.0.5 an option set to `undefined` replaces nothing; the scan applies that to all of Vitest 5.
783
+ const given = (key) => {
784
+ const option = options.values.get(key);
785
+ const isUndefined = option?.resolved.ok === true && option.resolved.value === undefined;
786
+ return isUndefined && atLeast(this.file.version, 5, 0) ? undefined : option;
787
+ };
766
788
  for (const flag of ['only', 'todo', 'fails', 'concurrent', 'sequential', 'shuffle']) {
767
- const option = options.values.get(flag);
789
+ const option = given(flag);
768
790
  if (option && !option.resolved.ok) {
769
791
  const { importedFrom } = option.resolved;
770
792
  problems.push(unresolvedOption(this.file, flag, option.node, importedFrom, 'it is not applied'));
@@ -776,7 +798,7 @@ export class VitestAdapter {
776
798
  result.flags.set(flag, Boolean(option.resolved.value));
777
799
  }
778
800
  }
779
- const skip = options.values.get('skip');
801
+ const skip = given('skip');
780
802
  if (skip) {
781
803
  result.skip = skip.resolved.ok
782
804
  ? { applies: skip.resolved.value ? 'always' : 'never', text: null }
@@ -806,7 +828,7 @@ export class VitestAdapter {
806
828
  this.retry(retry, details, context, problems);
807
829
  return result;
808
830
  }
809
- /** `retry` as a number, or since Vitest 4.1 as `{ count, delay, condition }`; the runner ignores an object before. */
831
+ /** `retry` as a number, or since Vitest 4.1 as `{ count, delay, condition }`. */
810
832
  retry(option, details, context, problems) {
811
833
  const node = unwrap(option.node);
812
834
  if (node.type !== 'ObjectExpression') {
@@ -815,8 +837,14 @@ export class VitestAdapter {
815
837
  details.run.retry = { count, delay: null, condition: null };
816
838
  return;
817
839
  }
818
- if (!atLeast(this.file.version, 4, 1))
840
+ if (!atLeast(this.file.version, 4, 1)) {
841
+ // Before 4.1 the runner compares the attempt number with the object, so it never runs the test.
842
+ const source = snippet(text(this.file, node));
843
+ const message = `Option retry: ${source} is an object, which Vitest reads from 4.1; before, it never runs the test.`;
844
+ problems.push(problem('unresolved-option', node, message, source));
845
+ details.run.retry = null;
819
846
  return;
847
+ }
820
848
  const values = readOptions(this.file, context.scopes, node);
821
849
  problems.push(...unresolvedOptions(this.file, values, 'the retry is not applied'));
822
850
  const read = (key) => {
package/dist/model.d.ts CHANGED
@@ -2,6 +2,8 @@ import type { CallExpression, Expression, Node } from 'oxc-parser';
2
2
  import type { FunctionNode } from './ast.ts';
3
3
  import type { Annotation, DiagnosticCode, DiagnosticLevel, HookCounts, MetaValue } from './types.ts';
4
4
  export type State = 'skip' | 'fixme' | 'todo' | 'expectedToFail';
5
+ /** The Vitest options that set a test's state. */
6
+ export type StateOption = 'skip' | 'only' | 'todo' | 'fails';
5
7
  export type Mode = 'parallel' | 'serial' | 'default';
6
8
  /** A state set by a declaration, a describe or a call such as `test.skip(condition)`. */
7
9
  export interface StateSetting {
@@ -51,12 +53,15 @@ export interface Details {
51
53
  /** Run settings written in one place; a key that is missing is not set there. */
52
54
  export interface RunSettings {
53
55
  timeout?: number;
54
- /** A retry setting replaces the whole `retry` of the describes around, delay and condition included. */
56
+ /**
57
+ * A retry setting replaces the whole `retry` of the describes around, delay and condition included; null when it is
58
+ * set to a value the runner can't use.
59
+ */
55
60
  retry?: {
56
61
  count: number;
57
62
  delay: number | null;
58
63
  condition: string | null;
59
- };
64
+ } | null;
60
65
  repeats?: number;
61
66
  /** Playwright `test.slow()`: the conditions of the calls, any of which makes the test slow; null when one always does. */
62
67
  slow?: {
@@ -90,6 +95,8 @@ export interface TestDeclaration extends Declaration {
90
95
  takesAllFixtures: boolean;
91
96
  /** Uses Vitest 5's `bench` fixture. */
92
97
  usesBench: boolean;
98
+ /** Vitest: the state options the test sets itself, to `false` too, which replace those of its tags. */
99
+ ownStateOptions?: StateOption[];
93
100
  }
94
101
  export interface SuiteDeclaration extends Declaration {
95
102
  kind: 'describe';
@@ -186,6 +193,8 @@ export interface Returns {
186
193
  /** A module read to see whether it exports a custom `test` object. */
187
194
  export interface ModuleSource {
188
195
  code: string;
196
+ /** The file's bytes when it isn't UTF-8 text, such as an image. */
197
+ bytes?: Uint8Array;
189
198
  relativeFilePath: string;
190
199
  /** Reads the modules this module imports in turn. */
191
200
  readModule: ModuleReader;
package/dist/parse.d.ts CHANGED
@@ -15,6 +15,12 @@ export interface ParsedFile {
15
15
  end: number;
16
16
  } | null;
17
17
  }
18
+ /**
19
+ * The offset of the first bracket nested more than `limit` deep, or null. Parentheses, brackets, braces and template
20
+ * substitutions count; strings, comments, regular expressions and template text are skipped. A closing bracket closes
21
+ * up to the one it matches, so a literal read wrongly unbalances only the brackets around it.
22
+ */
23
+ export declare function tooDeeplyNested(text: string, limit: number): number | null;
18
24
  /** The first parser error, located at its first label, or at the start of the file when it has none. */
19
25
  export declare function firstError(errors: readonly OxcError[]): ParsedFile['error'];
20
26
  export declare function parseFile(fileName: string, code: string): ParsedFile;