From a5078225aee53cb2ff066b9b40ed5eb5eb1ed459 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 29 Feb 2024 15:10:02 -0800 Subject: [PATCH] [New] add types --- .eslintrc | 2 + .github/workflows/node-aught.yml | 1 + bin/import-or-require.js | 1 + bin/tape | 2 + example/array.js | 5 +- example/fail.js | 5 +- example/nested.js | 5 +- example/nested_fail.js | 5 +- example/not_enough_fail.js | 5 +- example/too_many_fail.js | 5 +- index.d.ts | 92 +++++++++ index.js | 61 ++++-- lib/default_stream.d.ts | 5 + lib/default_stream.js | 11 +- lib/results.d.ts | 52 +++++ lib/results.js | 40 +++- lib/test.d.ts | 271 ++++++++++++++++++++++++++ lib/test.js | 133 ++++++++++--- package.json | 29 ++- test/anonymous-fn/test-wrapper.js | 1 + test/array.js | 5 +- test/assertion.js | 10 +- test/async-await.js | 4 +- test/async-await/async-bug.js | 3 + test/async-await/async4.js | 2 +- test/async-await/async5.js | 3 +- test/capture.js | 9 + test/captureFn.js | 3 + test/comment.js | 17 +- test/common.js | 16 +- test/deep-equal-failure.js | 6 +- test/deep.js | 12 ++ test/default_stream.js | 1 + test/double_end.js | 8 +- test/end-as-callback.js | 11 +- test/exit.js | 17 +- test/exit/fail.js | 5 +- test/exit/ok.js | 5 +- test/exit/too_few.js | 5 +- test/fail.js | 5 +- test/ignore-pattern.js | 6 +- test/ignore_from_gitignore.js | 8 +- test/import.js | 13 +- test/import/mjs-a.mjs | 1 + test/import/mjs-b.mjs | 2 + test/import/mjs-c.mjs | 2 + test/import/mjs-d.mjs | 2 + test/import/mjs-e.mjs | 2 + test/import/mjs-f.mjs | 2 + test/import/mjs-g.mjs | 2 + test/import/mjs-h.mjs | 1 + test/import/package_type/package-a.js | 1 + test/import/package_type/package-b.js | 2 + test/import/package_type/package-c.js | 1 + test/intercept.js | 21 +- test/match.js | 8 + test/max_listeners.js | 2 +- test/nested.js | 5 +- test/no_only.js | 8 +- test/not-deep-equal-failure.js | 6 +- test/objectMode.js | 11 +- test/objectModeWithComment.js | 12 +- test/onFailure.js | 2 +- test/only.js | 1 + test/promises/fail.js | 2 +- test/promises/subTests.js | 2 +- test/require.js | 7 +- test/require/a.js | 1 + test/require/b.js | 1 + test/require/test-a.js | 1 + test/require/test-b.js | 1 + test/skip.js | 1 + test/stackTrace.js | 33 ++-- test/strict.js | 2 +- test/subtest_and_async.js | 3 +- test/teardown.js | 7 +- test/throws.js | 17 +- test/timeoutAfter.js | 8 +- test/todo.js | 2 +- test/too_many.js | 5 +- tsconfig.json | 12 ++ types/ecstatic/index.d.ts | 7 + types/tap-parser/index.d.ts | 85 ++++++++ 83 files changed, 1032 insertions(+), 164 deletions(-) create mode 100644 index.d.ts create mode 100644 lib/default_stream.d.ts create mode 100644 lib/results.d.ts create mode 100644 lib/test.d.ts create mode 100644 tsconfig.json create mode 100644 types/ecstatic/index.d.ts create mode 100644 types/tap-parser/index.d.ts diff --git a/.eslintrc b/.eslintrc index a315a748..c0eac2a8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -23,6 +23,8 @@ "no-underscore-dangle": "warn", "object-curly-newline": "off", "sort-keys": "off", + + "no-extra-parens": "off", }, "ignorePatterns": ["syntax-error.*"], "overrides": [ diff --git a/.github/workflows/node-aught.yml b/.github/workflows/node-aught.yml index 4213896b..48b6d3ce 100644 --- a/.github/workflows/node-aught.yml +++ b/.github/workflows/node-aught.yml @@ -9,3 +9,4 @@ jobs: range: '< 10' type: minors command: npm run tests-only + skip-ls-check: true diff --git a/bin/import-or-require.js b/bin/import-or-require.js index be9e8e50..3bc7cb53 100644 --- a/bin/import-or-require.js +++ b/bin/import-or-require.js @@ -4,6 +4,7 @@ const { extname: extnamePath } = require('path'); const { pathToFileURL } = require('url'); const getPackageType = require('get-package-type'); +/** @type {(file: string) => undefined | Promise} */ // eslint-disable-next-line consistent-return module.exports = function importOrRequire(file) { const ext = extnamePath(file); diff --git a/bin/tape b/bin/tape index 17ec3d47..b18d7ff8 100755 --- a/bin/tape +++ b/bin/tape @@ -100,9 +100,11 @@ var hasImport = require('has-dynamic-import'); var tape = require('../'); +/** @type {(hasSupport: boolean) => Promise | void} */ function importFiles(hasSupport) { tape.wait(); + /** @type {null | undefined | Promise} */ var filesPromise; if (hasSupport) { var importOrRequire = require('./import-or-require'); diff --git a/example/array.js b/example/array.js index 1e1059e6..400fc9f0 100644 --- a/example/array.js +++ b/example/array.js @@ -9,6 +9,7 @@ test('array', function (t) { var src = '(' + function () { var xs = [1, 2, [3, 4]]; var ys = [5, 6]; + // @ts-expect-error g([xs, ys]); } + ')()'; @@ -26,11 +27,11 @@ test('array', function (t) { ]; Function('fn', 'g', String(output))( - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(arrays.shift(), xs); return xs; }, - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(xs, [[1, 2, [3, 4]], [5, 6]]); } ); diff --git a/example/fail.js b/example/fail.js index e9fb451e..112dfb32 100644 --- a/example/fail.js +++ b/example/fail.js @@ -9,6 +9,7 @@ test('array', function (t) { var src = '(' + function () { var xs = [1, 2, [3, 4]]; var ys = [5, 6]; + // @ts-expect-error g([xs, ys]); } + ')()'; @@ -26,11 +27,11 @@ test('array', function (t) { ]; Function('fn', 'g', String(output))( - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(arrays.shift(), xs); return xs; }, - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(xs, [[1, 2, [3, 4444]], [5, 6]]); } ); diff --git a/example/nested.js b/example/nested.js index 13d9a3f0..46d7171b 100644 --- a/example/nested.js +++ b/example/nested.js @@ -9,6 +9,7 @@ test('nested array test', function (t) { var src = '(' + function () { var xs = [1, 2, [3, 4]]; var ys = [5, 6]; + // @ts-expect-error g([xs, ys]); } + ')()'; @@ -35,11 +36,11 @@ test('nested array test', function (t) { ]; Function('fn', 'g', String(output))( - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(arrays.shift(), xs); return xs; }, - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(xs, [[1, 2, [3, 4]], [5, 6]]); } ); diff --git a/example/nested_fail.js b/example/nested_fail.js index 3ef50096..9a9b415a 100644 --- a/example/nested_fail.js +++ b/example/nested_fail.js @@ -9,6 +9,7 @@ test('nested array test', function (t) { var src = '(' + function () { var xs = [1, 2, [3, 4]]; var ys = [5, 6]; + // @ts-expect-error g([xs, ys]); } + ')()'; @@ -35,11 +36,11 @@ test('nested array test', function (t) { ]; Function('fn', 'g', String(output))( - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(arrays.shift(), xs); return xs; }, - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(xs, [[1, 2, [3, 4]], [5, 6]]); } ); diff --git a/example/not_enough_fail.js b/example/not_enough_fail.js index 08fcc5c4..4ca2cfee 100644 --- a/example/not_enough_fail.js +++ b/example/not_enough_fail.js @@ -9,6 +9,7 @@ test('array', function (t) { var src = '(' + function () { var xs = [1, 2, [3, 4]]; var ys = [5, 6]; + // @ts-expect-error g([xs, ys]); } + ')()'; @@ -26,11 +27,11 @@ test('array', function (t) { ]; Function('fn', 'g', String(output))( - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(arrays.shift(), xs); return xs; }, - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(xs, [[1, 2, [3, 4]], [5, 6]]); } ); diff --git a/example/too_many_fail.js b/example/too_many_fail.js index e131a903..9362b828 100644 --- a/example/too_many_fail.js +++ b/example/too_many_fail.js @@ -9,6 +9,7 @@ test('array', function (t) { var src = '(' + function () { var xs = [1, 2, [3, 4]]; var ys = [5, 6]; + // @ts-expect-error g([xs, ys]); } + ')()'; @@ -26,11 +27,11 @@ test('array', function (t) { ]; Function('fn', 'g', String(output))( - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(arrays.shift(), xs); return xs; }, - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(xs, [[1, 2, [3, 4]], [5, 6]]); } ); diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 00000000..d6745c0d --- /dev/null +++ b/index.d.ts @@ -0,0 +1,92 @@ +import type { ThroughStream } from '@ljharb/through'; + +import type Test from './lib/test'; +import type Results from './lib/results'; + +declare function harnessFunction(this: Test, name: string, opts: tape.TestOptions, cb: Test.TestCase): Test; +declare function harnessFunction(this: Test, name: string, opts: tape.TestOptions): Test; +declare function harnessFunction(this: Test, name: string, cb: Test.TestCase): Test; +declare function harnessFunction(this: Test, name: string): Test; +declare function harnessFunction(this: Test, opts: tape.TestOptions, cb: Test.TestCase): Test; +declare function harnessFunction(this: Test, opts: tape.TestOptions): Test; +declare function harnessFunction(this: Test, cb: Test.TestCase): Test; + +declare function harnessFunction(this: void, name: string, opts: tape.TestOptions, cb: Test.TestCase): Test; +declare function harnessFunction(this: void, name: string, opts: tape.TestOptions): Test; +declare function harnessFunction(this: void, name: string, cb: Test.TestCase): Test; +declare function harnessFunction(this: void, name: string): Test; +declare function harnessFunction(this: void, opts: tape.TestOptions, cb: Test.TestCase): Test; +declare function harnessFunction(this: void, opts: tape.TestOptions): Test; +declare function harnessFunction(this: void, cb: Test.TestCase): Test; + +declare namespace tape { + export type TestOptions = { + objectPrintDepth?: number | undefined; + skip?: boolean | string | undefined; + timeout?: number | undefined; + todo?: boolean | string | undefined; + }; + + export interface AssertOptions { + skip?: boolean | string | undefined; + todo?: boolean | string | undefined; + message?: string | undefined; + actual?: unknown; + expected?: unknown; + exiting?: boolean; + } + + export interface StreamOptions { + objectMode?: boolean | undefined; + } + + function createStream(opts?: StreamOptions): ThroughStream; + + export type CreateStream = typeof createStream; + + export type HarnessEventHandler = (cb: Test.SyncCallback, ...rest: unknown[]) => void; + + function only(name: string, cb: Test.TestCase): void; + function only(name: string, opts: tape.TestOptions, cb: Test.TestCase): void; + function only(cb: Test.TestCase): void; + function only(opts: tape.TestOptions, cb?: Test.TestCase): void; + + export type Harness = typeof harnessFunction & { + run?: () => void; + only: typeof only; + _exitCode: number; + _results: Results; + _tests: Test[]; + close: () => void; + createStream: CreateStream; + onFailure: HarnessEventHandler; + onFinish: HarnessEventHandler; + } + + export type HarnessConfig = { + autoclose?: boolean; + noOnly?: boolean; + stream?: NodeJS.WritableStream | ThroughStream; + exit?: boolean; + } & StreamOptions; + + function createHarness(conf_?: HarnessConfig): Harness; + const Test: Test; + const test: Harness; + const skip: Test['skip']; + + function getHarness(opts?: HarnessConfig): Harness; + function run(): void; + function onFailure(cb: Test.SyncCallback, ...rest: unknown[]): void; + function onFinish(cb: Test.SyncCallback, ...rest: unknown[]): void + function wait(): void; +} + +declare function tape(this: void | tape.Harness, name: string, opts: tape.TestOptions, cb: Test.TestCase): Test; +declare function tape(this: void | tape.Harness, name: string, cb: Test.TestCase): Test; +declare function tape(this: void | tape.Harness, opts?: tape.TestOptions): Test; +declare function tape(this: void | tape.Harness, opts: tape.TestOptions, cb: Test.TestCase): Test; +declare function tape(this: void | tape.Harness, cb: Test.TestCase): Test; +declare function tape(this: void | tape.Harness, name: string): Test; + +export = tape; diff --git a/index.js b/index.js index 8076e4c9..8458ea2a 100644 --- a/index.js +++ b/index.js @@ -1,20 +1,33 @@ 'use strict'; var defined = require('defined'); +var through = require('@ljharb/through'); + var createDefaultStream = require('./lib/default_stream'); var Test = require('./lib/test'); var Results = require('./lib/results'); -var through = require('@ljharb/through'); var canEmitExit = typeof process !== 'undefined' && process - && typeof process.on === 'function' && process.browser !== true; + && typeof process.on === 'function' && /** @type {{ browser?: boolean }} */ (process).browser !== true; var canExit = typeof process !== 'undefined' && process && typeof process.exit === 'function'; -module.exports = (function () { +/** @typedef {import('.')} Tape */ +/** @typedef {import('.').Harness} Harness */ +/** @typedef {import('.').HarnessConfig} HarnessConfig */ +/** @typedef {import('.').TestOptions} TestOptions */ +/** @typedef {import('.').HarnessEventHandler} HarnessEventHandler */ +/** @typedef {import('.').CreateStream} CreateStream */ +/** @typedef {import('.').createHarness} CreateHarness */ +/** @typedef {import('./lib/results').Result} Result */ +/** @typedef {import('stream').Writable} WritableStream */ + +var tape = (function () { var wait = false; + /** @type {undefined | Harness} */ var harness; + /** @type {(opts?: HarnessConfig) => Harness} */ function getHarness(opts) { // this override is here since tests fail via nyc if createHarness is moved upwards if (!harness) { @@ -24,6 +37,7 @@ module.exports = (function () { return harness; } + /** @type {(this: Harness, ...args: Parameters) => ReturnType} */ function lazyLoad() { // eslint-disable-next-line no-invalid-this return getHarness().apply(this, arguments); @@ -43,6 +57,7 @@ module.exports = (function () { return getHarness().only.apply(this, arguments); }; + /** @type {CreateStream} */ lazyLoad.createStream = function (opts) { var options = opts || {}; if (!harness) { @@ -66,21 +81,23 @@ module.exports = (function () { return lazyLoad; }()); +/** @type {CreateHarness} */ function createHarness(conf_) { var results = new Results({ todoIsOK: !!(process.env.TODO_IS_OK === '1') }); if (!conf_ || conf_.autoclose !== false) { results.once('done', function () { results.close(); }); } + /** @type {(name: string, conf: TestOptions, cb: Test.TestCase) => Test} */ function test(name, conf, cb) { var t = new Test(name, conf, cb); test._tests.push(t); (function inspectCode(st) { - st.on('test', function sub(st_) { + st.on('test', /** @type {(st: Test) => void} */ function sub(st_) { inspectCode(st_); }); - st.on('result', function (r) { + st.on('result', /** @type {(r: Result) => void} */ function (r) { if (!r.todo && !r.ok && typeof r !== 'string') { test._exitCode = 1; } }); }(t)); @@ -90,21 +107,25 @@ function createHarness(conf_) { } test._results = results; - test._tests = []; + /** @type {Test[]} */ test._tests = []; + /** @type {CreateStream} */ test.createStream = function (opts) { return results.createStream(opts); }; + /** @type {HarnessEventHandler} */ test.onFinish = function (cb) { results.on('done', cb); }; + /** @type {HarnessEventHandler} */ test.onFailure = function (cb) { results.on('fail', cb); }; var only = false; + /** @type {() => Test} */ test.only = function () { if (only) { throw new Error('there can only be one only test'); } if (conf_ && conf_.noOnly) { throw new Error('`only` tests are prohibited'); } @@ -117,9 +138,11 @@ function createHarness(conf_) { test.close = function () { results.close(); }; + // @ts-expect-error TODO FIXME: why is `test` not assignable to `Harness`??? return test; } +/** @type {(conf: Omit, wait?: boolean) => Harness} */ function createExitHarness(config, wait) { var noOnly = config.noOnly; var objectMode = config.objectMode; @@ -139,9 +162,8 @@ function createExitHarness(config, wait) { var stream = harness.createStream({ objectMode: objectMode }); var es = stream.pipe(cStream || createDefaultStream()); if (canEmitExit && es) { // in node v0.4, `es` is `undefined` - // TODO: use `err` arg? // eslint-disable-next-line no-unused-vars - es.on('error', function (err) { harness._exitCode = 1; }); + es.on('error', function (_) { harness._exitCode = 1; }); } stream.on('end', function () { ended = true; }); } @@ -155,7 +177,7 @@ function createExitHarness(config, wait) { if (exit === false) { return harness; } if (!canEmitExit || !canExit) { return harness; } - process.on('exit', function (code) { + process.on('exit', /** @param {number | undefined} code */ function (code) { // let the process exit cleanly. if (typeof code === 'number' && code !== 0) { return; @@ -179,7 +201,24 @@ function createExitHarness(config, wait) { return harness; } +module.exports = tape; + module.exports.createHarness = createHarness; module.exports.Test = Test; -module.exports.test = module.exports; // tap compat -module.exports.test.skip = Test.skip; +module.exports.test = tape; // tap compat +module.exports.skip = Test.skip; + +// @ts-expect-error TODO FIXME: attw errors without this line +module.exports.createStream = tape.createStream; +// @ts-expect-error TODO FIXME: attw errors without this line +module.exports.only = tape.only; +// @ts-expect-error TODO FIXME: attw errors without this line +module.exports.getHarness = tape.getHarness; +// @ts-expect-error TODO FIXME: attw errors without this line +module.exports.run = tape.run; +// @ts-expect-error TODO FIXME: attw errors without this line +module.exports.wait = tape.wait; +// @ts-expect-error TODO FIXME: attw errors without this line +module.exports.onFinish = tape.onFinish; +// @ts-expect-error TODO FIXME: attw errors without this line +module.exports.onFailure = tape.onFailure; diff --git a/lib/default_stream.d.ts b/lib/default_stream.d.ts new file mode 100644 index 00000000..8ddb205a --- /dev/null +++ b/lib/default_stream.d.ts @@ -0,0 +1,5 @@ +import type { ThroughStream } from "@ljharb/through"; + +declare function defaultStream(): ThroughStream; + +export = defaultStream; \ No newline at end of file diff --git a/lib/default_stream.js b/lib/default_stream.js index ffc2ad11..87e63aba 100644 --- a/lib/default_stream.js +++ b/lib/default_stream.js @@ -3,11 +3,13 @@ var through = require('@ljharb/through'); var fs = require('fs'); +/** @type {import('./default_stream')} */ module.exports = function () { var line = ''; var stream = through(write, flush); return stream; + /** @type {(buf: unknown) => void} */ function write(buf) { if ( buf == null // eslint-disable-line eqeqeq @@ -16,10 +18,11 @@ module.exports = function () { flush(); return; } - for (var i = 0; i < buf.length; i++) { - var c = typeof buf === 'string' - ? buf.charAt(i) - : String.fromCharCode(buf[i]); + var b = /** @type {string | ArrayLike} */ (buf); + for (var i = 0; i < b.length; i++) { + var c = typeof b === 'string' + ? b.charAt(i) + : String.fromCharCode(b[i]); if (c === '\n') { flush(); } else { diff --git a/lib/results.d.ts b/lib/results.d.ts new file mode 100644 index 00000000..2f97b970 --- /dev/null +++ b/lib/results.d.ts @@ -0,0 +1,52 @@ +import through from '@ljharb/through'; +import type { EventEmitter } from 'events'; + +import type { StreamOptions } from '../'; +import Test = require('./test'); + +declare class Results extends EventEmitter { + constructor(options?: { todoIsOK?: boolean }); + + count: number; + fail: number; + pass: number; + tests: Test[]; + todo: number; + todoIsOK: boolean; + closed?: boolean; + + _isRunning: boolean; + _only: Test | null; + _stream: through.ThroughStream; + + close(this: Results): void; + createStream(this: Results, opts?: StreamOptions): through.ThroughStream; + only(this: Results, t: Test): void; + push(this: Results, t: Test): void; + + _watch(this: Results, t: Test): void; +} + +declare namespace Results { + export type Operator = string; + + export type Result = { + id: number; + ok: boolean; + skip: unknown; + todo: unknown; + name?: string; + operator: undefined | Operator; + objectPrintDepth?: number; + actual?: unknown; + expected?: unknown; + error?: unknown; + functionName?: string; + file?: string; + line?: number; + column?: number; + at?: string; + }; +} + +export = Results; diff --git a/lib/results.js b/lib/results.js index b2709721..aa715903 100644 --- a/lib/results.js +++ b/lib/results.js @@ -18,10 +18,19 @@ var nextTick = typeof setImmediate !== 'undefined' ? setImmediate : process.nextTick; +/** @typedef {through.ThroughStream} Stream */ +/** @typedef {{ ok: boolean, name: string, skip?: unknown, todo?: unknown, operator: unknown, objectPrintDepth?: number, expected: unknown, actual: unknown, at?: string, error?: Error, test: unknown, type: unknown}} Result */ +/** @typedef {import('./test')} Test */ +/** @typedef {import('./results')} ResultsType */ +/** @typedef {import('../').StreamOptions} StreamOptions */ +/** @typedef {import('stream').Writable} WritableStream */ + +/** @type {(str: string) => string} */ function coalesceWhiteSpaces(str) { return $replace(String(str), /\s+/g, ' '); } +/** @type {(results: ResultsType) => Test | undefined} */ function getNextTest(results) { if (!results._only) { return $shift(results.tests); @@ -37,10 +46,12 @@ function getNextTest(results) { return void undefined; } +/** @type {(str: string) => boolean} */ function invalidYaml(str) { return $exec(yamlIndicators, str) !== null; } +/** @type {(res: Result, count: number, todoIsOK?: boolean) => string} */ function encodeResult(res, count, todoIsOK) { var output = ''; output += (res.ok || (todoIsOK && res.todo) ? 'ok ' : 'not ok ') + count; @@ -76,7 +87,7 @@ function encodeResult(res, count, todoIsOK) { output += inner + 'at: ' + res.at + '\n'; } - var actualStack = res.actual && (typeof res.actual === 'object' || typeof res.actual === 'function') ? res.actual.stack : undefined; + var actualStack = res.actual && (typeof res.actual === 'object' || typeof res.actual === 'function') ? /** @type {Error} */ (res.actual).stack : undefined; var errorStack = res.error && res.error.stack; var stack = defined(actualStack, errorStack); if (stack) { @@ -91,6 +102,10 @@ function encodeResult(res, count, todoIsOK) { return output; } +/** + * @constructor + * @param {{ todoIsOK?: boolean }} [options] + */ function Results(options) { if (!(this instanceof Results)) { return new Results(options); } var opts = (arguments.length > 0 ? arguments[0] : options) || {}; @@ -107,16 +122,18 @@ function Results(options) { inherits(Results, EventEmitter); +/** @type {(this: ResultsType, opts?: StreamOptions) => Stream} */ Results.prototype.createStream = function (opts) { if (!opts) { opts = {}; } var self = this; - var output; + /** @type {Stream} */ var output; var testId = 0; if (opts.objectMode) { output = through(); - self.on('_push', function ontest(t, extra) { + self.on('_push', /** @type {(t: Test, extra: unknown ) => void} */ function ontest(t, extra) { var id = testId++; t.once('prerun', function () { + /** @type {{ parent?: unknown, type: string, name: string, id: number, skip: unknown, todo: unknown }} */ var row = { type: 'test', name: t.name, @@ -124,15 +141,15 @@ Results.prototype.createStream = function (opts) { skip: t._skip, todo: t._todo }; - if (extra && hasOwn(extra, 'parent')) { + if (extra && typeof extra === 'object' && 'parent' in extra && hasOwn(extra, 'parent')) { row.parent = extra.parent; } output.queue(row); }); - t.on('test', function (st) { + t.on('test', /** @type {(st: Test) => void} */ function (st) { ontest(st, { parent: id }); }); - t.on('result', function (res) { + t.on('result', /** @type {(res: Result) => void} */ function (res) { if (res && typeof res === 'object') { res.test = id; res.type = 'assert'; @@ -168,19 +185,22 @@ Results.prototype.createStream = function (opts) { return output; }; +/** @type {import('./results').prototype.push} */ Results.prototype.push = function (t) { $push(this.tests, t); this._watch(t); this.emit('_push', t); }; +/** @type {import('./results').prototype.only} */ Results.prototype.only = function (t) { this._only = t; }; +/** @type {import('./results').prototype._watch} */ Results.prototype._watch = function (t) { var self = this; - function write(s) { self._stream.queue(s); } + /** @type {(s: string) => void} */ function write(s) { self._stream.queue(s); } t.once('prerun', function () { var premsg = ''; @@ -194,7 +214,7 @@ Results.prototype._watch = function (t) { write('# ' + premsg + coalesceWhiteSpaces(t.name) + postmsg + '\n'); }); - t.on('result', function (res) { + t.on('result', /** @type {(res: Result | string) => void} */ function (res) { if (typeof res === 'string') { write('# ' + res + '\n'); return; @@ -210,14 +230,16 @@ Results.prototype._watch = function (t) { } }); - t.on('test', function (st) { self._watch(st); }); + t.on('test', /** @type {(st: Test) => void} */ function (st) { self._watch(st); }); }; +/** @type {import('./results').prototype.close} */ Results.prototype.close = function () { var self = this; if (self.closed) { self._stream.emit('error', new Error('ALREADY CLOSED')); } self.closed = true; + /** @type {(s: string) => void} */ function write(s) { self._stream.queue(s); } write('\n1..' + self.count + '\n'); diff --git a/lib/test.d.ts b/lib/test.d.ts new file mode 100644 index 00000000..a30e00d9 --- /dev/null +++ b/lib/test.d.ts @@ -0,0 +1,271 @@ +import type { EventEmitter } from 'events'; +import mockProperty = require('mock-property'); + +import type { + AssertOptions, + TestOptions, +} from '../'; +import type { + Operator, +} from './results'; + +type WithRequired = T & { [P in K]-?: T[P] }; + +declare class Test extends EventEmitter { + constructor(name: string, opts?: TestOptions, cb?: Test.TestCase); + constructor(name: string, cb: Test.TestCase); + constructor(opts: TestOptions, cb?: Test.TestCase); + constructor(cb: Test.TestCase); + + readable: boolean; + name: string; + assertCount: number; + pendingCount: number; + calledEnd?: boolean; + ended: boolean; + + // "private" properties + _cb: Test.TestCase | undefined; + _objectPrintDepth: number | undefined; + _ok: boolean; + _plan: number | undefined; + _planError: boolean | undefined; + _progeny: Test[]; + _skip: boolean | undefined; + _teardown: Test.TeardownHandler[]; + _timeout: number | undefined; + _todo: boolean | string | undefined; + + captureFn( + this: void | Test, + original: X, + ): Test.WrappedFn; + capture( + this: void | Test, + obj: Record | unknown[], + method: PropertyKey, + implementation?: T, + ): Test.WrapResults; + end(this: void | Test, err?: unknown): void; + fail(this: void | Test, msg?: string, extra?: AssertOptions): void; + intercept( + this: void | Test, + obj: Record | object, + property: PropertyKey, + desc?: PropertyDescriptor & { __proto__?: object | null; configurable?: true }, + strictMode?: boolean, + ): Test.InterceptResults; + pass(this: void | Test, msg?: string, extra?: AssertOptions): void; + run(this: void | Test): void; + + skip(this: void | Test, msg?: string, extra?: AssertOptions): void; + // skip(this: void | Test, msg?: string, extra?: AssertOptions): void; + + timeoutAfter(this: void | Test, ms: number): void; + plan(this: void | Test, n: number): void; + comment(this: void | Test, msg: string): void; + teardown(this: void | Test, fn: Test.TeardownHandler): void; + + test(this: void | Test, name: string): void; + test(this: void | Test, cb: Test.TestCase): void; + test(this: void | Test, name: string, cb: Test.TestCase): void; + test(this: void | Test, name: string, opts: TestOptions, cb: Test.TestCase): void; + test(this: void | Test, name: string, opts: WithRequired | WithRequired, cb?: Test.TestCase): void; + + // assertions + + ok(this: void | Test, value: unknown, msg?: string, extra?: AssertOptions): void; + true: typeof this.ok; + assert: typeof this.ok; + + notOk(this: void | Test, value: unknown, msg?: string, extra?: AssertOptions): void; + false: typeof this.notOk; + notok: typeof this.notOk; + + error(this: void | Test, err?: unknown, msg?: string, extra?: AssertOptions): void; + ifError: typeof this.error; + ifErr: typeof this.error; + iferror: typeof this.error; + + equal(this: void | Test, a: unknown, b: unknown, msg?: string, extra?: AssertOptions): void; + equals: typeof this.equal; + isEqual: typeof this.equal; + is: typeof this.equal; + strictEqual: typeof this.equal; + strictEquals: typeof this.equal; + + notEqual(this: void | Test, a: unknown, b: unknown, msg?: string, extra?: AssertOptions): void; + notEquals: typeof this.notEqual; + notStrictEqual: typeof this.notEqual; + notStrictEquals: typeof this.notEqual; + isNotEqual: typeof this.notEqual; + isNot: typeof this.notEqual; + not: typeof this.notEqual; + doesNotEqual: typeof this.notEqual; + isInequal: typeof this.notEqual; + + looseEqual(this: void | Test, a: unknown, b: unknown, msg?: string, extra?: AssertOptions): void; + looseEquals: typeof this.looseEqual; + + notLooseEqual(this: void | Test, a: unknown, b: unknown, msg?: string, extra?: AssertOptions): void; + notLooseEquals: typeof this.notLooseEqual; + + deepEqual(this: void | Test, a: unknown, b: unknown, msg?: string, extra?: AssertOptions): void; + deepEquals: typeof this.deepEqual; + isEquivalent: typeof this.deepEqual; + same: typeof this.deepEqual; + + notDeepEqual(this: void | Test, a: unknown, b: unknown, msg?: string, extra?: AssertOptions): void; + notDeepEquals: typeof this.notDeepEqual; + notEquivalent: typeof this.notDeepEqual; + notDeeply: typeof this.notDeepEqual; + notSame: typeof this.notDeepEqual; + isNotDeepEqual: typeof this.notDeepEqual; + isNotDeeply: typeof this.notDeepEqual; + isNotEquivalent: typeof this.notDeepEqual; + isInequivalent: typeof this.notDeepEqual; + + deepLooseEqual(this: void | Test, a: unknown, b: unknown, msg?: string, extra?: AssertOptions): void; + + notDeepLooseEqual(this: void | Test, a: unknown, b: unknown, msg?: string, extra?: AssertOptions): void; + + throws( + this: void | Test, + fn: () => void, + exceptionExpected: Test.ThrowsExpected, + msg?: string, + extra?: AssertOptions, + ): void; + throws( + this: void | Test, + fn: () => void, + msg?: string, + extra?: AssertOptions, + ): void; + + doesNotThrow( + this: void | Test, + fn: () => void, + exceptionExpected: RegExp | Function | undefined, + msg?: string, + extra?: AssertOptions, + ): void; + doesNotThrow( + this: void | Test, + fn: () => void, + msg?: string, + extra?: AssertOptions, + ): void; + + match( + this: void | Test, + actual: string, + expected: RegExp, + msg?: string, + extra?: AssertOptions, + ): void; + + doesNotMatch( + this: void | Test, + actual: string, + expected: RegExp, + msg?: string, + extra?: AssertOptions, + ): void; + + assertion( + this: void | Test, + fn: Test.AssertionFunction, + ...args: Args, + ): R; + + static skip( + name: string, + opts: TestOptions, + cb: Test.TestCase, + ): Test; + + // "private" methods + + _assert( + this: Test, + maybeOK: boolean | unknown, + opts: TestOptions & { + message?: string; + operator?: Operator; + error?: unknown; + actual?: unknown; + expected?: unknown; + extra?: AssertOptions & { + operator?: Operator; + error?: unknown; + }; + }, + ): void; + _end(this: Test, err?: unknown): void; + _exit(this: Test): void; + _pendingAsserts(this: Test): number; +} + +declare namespace Test { + export interface TestCase { + (test: Test): void | any | Promise | Promise; + } + + export type SyncCallback = (...args: unknown[]) => unknown; + export type Callback = (...args: unknown[]) => unknown | Promise; + + export type ReturnCall = { + args: unknown[]; + receiver: {}; + returned: unknown; + }; + + export type ThrowCall = { + args: unknown[]; + receiver: {}; + threw: true; + }; + + export type Call = { + type: 'get' | 'set'; + success: boolean; + value: unknown; + args: unknown[]; + receiver: unknown; + } + + export type RestoreFunction = ReturnType; + + export type WrapResults = { + (): WrappedCall[]; + restore?: RestoreFunction; + }; + + export type WrappedFn = { + (this: ThisParameterType, ...args: Parameters): ReturnType; + calls?: WrappedCall[]; + }; + + export type WrapObject = { + __proto__: null; + wrapped: WrappedFn; + calls: WrappedCall[]; + results: Test.WrapResults; + }; + + export type WrappedCall = ReturnCall | ThrowCall; + + export type InterceptResults = { + (): Call[]; + restore: RestoreFunction; + } + + export type AssertionFunction = (this: Test, ...args: Args) => R; + + export type ThrowsExpected = object | RegExp | ((err: unknown) => true | never) | Error | string | undefined; + + export type TeardownHandler = () => void | any | Promise | Promise; +} + +export = Test; diff --git a/lib/test.js b/lib/test.js index 487e5468..24f3e012 100644 --- a/lib/test.js +++ b/lib/test.js @@ -35,11 +35,20 @@ var nextTick = typeof setImmediate !== 'undefined' var safeSetTimeout = setTimeout; var safeClearTimeout = clearTimeout; +/** @typedef {((c: unknown) => c is ErrorConstructor | TypeErrorConstructor | RangeErrorConstructor | EvalErrorConstructor | URIErrorConstructor | ReferenceErrorConstructor | SyntaxErrorConstructor)} IsErrorConstructor */ +/** @typedef {import('../').TestOptions} TestOptions */ +/** @typedef {import('./test').TestCase} TestCase */ +/** @typedef {import('./test').Callback} Callback */ +/** @typedef {import('./test').WrappedCall} WrappedCall */ +/** @typedef {import('./results').Result} Result */ +/** @typedef {import('./test').Call} Call */ +/** @typedef {import('./test')} TestType */ + var isErrorConstructor = isProto(Error, TypeError) // IE 8 is `false` here - ? function isErrorConstructor(C) { + ? /** @type {IsErrorConstructor} */ function isErrorConstructor(C) { return isProto(Error, C); } - : function isErrorConstructor(C) { + : /** @type {IsErrorConstructor} */ function isErrorConstructor(C) { return isProto(Error, C) || isProto(TypeError, C) || isProto(RangeError, C) @@ -49,8 +58,8 @@ var isErrorConstructor = isProto(Error, TypeError) // IE 8 is `false` here || isProto(URIError, C); }; -// eslint-disable-next-line no-unused-vars -function getTestArgs(name_, opts_, cb_) { +/** @type {(...args: [name: string, opts?: TestOptions, cb?: TestCase] | [opts: TestOptions, cb?: TestCase] | [name: string, cb: TestCase] | [cb: TestCase]) => { name: string, opts: TestOptions, cb: TestCase }} */ +function getTestArgs() { var name = '(anonymous)'; var opts = {}; var cb; @@ -72,12 +81,18 @@ function getTestArgs(name_, opts_, cb_) { }; } -function Test(name_, opts_, cb_) { +/** + * @constructor + * @param {string} name + * @param {TestOptions} opts + * @param {TestCase} [cb] + */ +function Test(name, opts, cb) { if (!(this instanceof Test)) { - return new Test(name_, opts_, cb_); + return new Test(name, opts, cb); } - var args = getTestArgs(name_, opts_, cb_); + var args = getTestArgs(name, opts, cb); this.readable = true; this.name = args.name || '(anonymous)'; @@ -100,12 +115,13 @@ function Test(name_, opts_, cb_) { if (toLowerCase(depthEnvVar) === 'infinity') { this._objectPrintDepth = Infinity; } else { - this._objectPrintDepth = depthEnvVar; + this._objectPrintDepth = Number(depthEnvVar); } } for (var prop in this) { if (typeof this[prop] === 'function') { + // @ts-expect-error TODO: FIXME this[prop] = callBind(this[prop], this); } } @@ -113,6 +129,7 @@ function Test(name_, opts_, cb_) { inherits(Test, EventEmitter); +/** @type {import('./test').prototype.run} @this {import('./test')} */ Test.prototype.run = function run() { this.emit('prerun'); if (!this._cb || this._skip) { @@ -128,7 +145,7 @@ Test.prototype.run = function run() { if ( typeof Promise === 'function' && callbackReturn - && typeof callbackReturn.then === 'function' + && typeof /** @type {PromiseLike} */ (callbackReturn).then === 'function' ) { var self = this; Promise.resolve(callbackReturn).then( @@ -152,9 +169,10 @@ Test.prototype.run = function run() { this.emit('run'); }; +/** @type {import('./test').prototype.test} @this {import('./test')} */ Test.prototype.test = function test(name, opts, cb) { var self = this; - var t = new Test(name, opts, cb); + var t = /** @type {TestType} */ (/** @type {unknown} */ (new Test(name, opts, cb))); $push(this._progeny, t); this.pendingCount++; this.emit('test', t); @@ -175,18 +193,21 @@ Test.prototype.test = function test(name, opts, cb) { }); }; +/** @type {import('./test').prototype.comment} @this {import('./test')} */ Test.prototype.comment = function comment(msg) { var that = this; - forEach($split(trim(msg), '\n'), function (aMsg) { + forEach($split(trim(msg), '\n'), /** @type {(aMsg: string) => void} */ function (aMsg) { that.emit('result', $replace(trim(aMsg), /^#\s*/, '')); }); }; +/** @type {import('./test').prototype.plan} @this {import('./test')} */ Test.prototype.plan = function plan(n) { this._plan = n; this.emit('plan', n); }; +/** @type {import('./test').prototype.timeoutAfter} @this {import('./test')} */ Test.prototype.timeoutAfter = function timeoutAfter(ms) { if (!ms) { throw new Error('timeoutAfter requires a timespan'); } var self = this; @@ -199,6 +220,7 @@ Test.prototype.timeoutAfter = function timeoutAfter(ms) { }); }; +/** @type {import('./test').prototype.end} @this {import('./test')} */ Test.prototype.end = function end(err) { if (arguments.length >= 1 && !!err) { this.ifError(err); @@ -211,6 +233,7 @@ Test.prototype.end = function end(err) { this._end(); }; +/** @type {import('./test').prototype.teardown} @this {import('./test')} */ Test.prototype.teardown = function teardown(fn) { if (typeof fn !== 'function') { this.fail('teardown: ' + inspect(fn) + ' is not a function'); @@ -219,6 +242,7 @@ Test.prototype.teardown = function teardown(fn) { } }; +/** @type {(original: undefined | T) => import('./test').WrapObject} */ function wrapFunction(original) { if (typeof original !== 'undefined' && typeof original !== 'function') { throw new TypeError('`original` must be a function or `undefined`'); @@ -226,15 +250,19 @@ function wrapFunction(original) { var bound = original && callBind.apply(original); + /** @type {WrappedCall[]} */ var calls = []; + /** @type {import('./test').WrapObject>} */ var wrapObject = { __proto__: null, - wrapped: function wrapped() { + wrapped: /** @type {() => ReturnType} */ function wrapped() { var args = $slice(arguments); var completed = false; try { - var returned = bound ? bound(this, arguments) : void undefined; + var returned = bound + ? bound(this, /** @type {readonly[]} */ (/** @type {unknown} */ (arguments))) + : void undefined; $push(calls, { args: args, receiver: this, returned: returned }); completed = true; return returned; @@ -257,6 +285,7 @@ function wrapFunction(original) { return wrapObject; } +/** @type {import('./test').prototype.capture} @this {import('./test')} */ Test.prototype.capture = function capture(obj, method) { if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) { throw new TypeError('`obj` must be an object'); @@ -264,6 +293,7 @@ Test.prototype.capture = function capture(obj, method) { if (typeof method !== 'string' && typeof method !== 'symbol') { throw new TypeError('`method` must be a string or a symbol'); } + /** @type {Parameters[0]} */ var implementation = arguments.length > 2 ? arguments[2] : void undefined; if (typeof implementation !== 'undefined' && typeof implementation !== 'function') { throw new TypeError('`implementation`, if provided, must be a function'); @@ -278,6 +308,7 @@ Test.prototype.capture = function capture(obj, method) { return wrapper.results; }; +/** @type {import('./test').prototype.captureFn} @this {import('./test')} */ Test.prototype.captureFn = function captureFn(original) { if (typeof original !== 'function') { throw new TypeError('`original` must be a function'); @@ -288,6 +319,7 @@ Test.prototype.captureFn = function captureFn(original) { return wrapObject.wrapped; }; +/** @type {import('./test').prototype.intercept} @this {import('./test')} */ Test.prototype.intercept = function intercept(obj, property) { if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) { throw new TypeError('`obj` must be an object'); @@ -295,6 +327,7 @@ Test.prototype.intercept = function intercept(obj, property) { if (typeof property !== 'string' && typeof property !== 'symbol') { throw new TypeError('`property` must be a string or a symbol'); } + /** @type {PropertyDescriptor} */ var desc = arguments.length > 2 ? arguments[2] : { __proto__: null }; if (typeof desc !== 'undefined' && (!desc || typeof desc !== 'object')) { throw new TypeError('`desc`, if provided, must be an object'); @@ -312,19 +345,25 @@ Test.prototype.intercept = function intercept(obj, property) { throw new TypeError('`strictMode`, if provided, must be a boolean'); } + /** @type {Call[]} */ var calls = []; var getter = desc.get && callBind.apply(desc.get); var setter = desc.set && callBind.apply(desc.set); var value = !isAccessor ? desc.value : void undefined; var writable = !!desc.writable; + /** @type {(this: T, ...args: unknown[]) => unknown} */ function getInterceptor() { + /** @type {unknown[]} */ var args = $slice(arguments); if (isAccessor) { if (getter) { var completed = false; try { - var returned = getter(this, arguments); + var returned = getter( + this, + /** @type {readonly []} */ (/** @type {unknown} */ (arguments)) + ); completed = true; $push(calls, { type: 'get', success: true, value: returned, args: args, receiver: this }); return returned; @@ -339,12 +378,13 @@ Test.prototype.intercept = function intercept(obj, property) { return value; } + /** @type {(this: T, v: unknown) => unknown} */ function setInterceptor(v) { var args = $slice(arguments); if (isAccessor && setter) { var completed = false; try { - var returned = setter(this, arguments); + var returned = setter(this, /** @type {readonly [v: unknown]} */ (/** @type {unknown} */ (arguments))); completed = true; $push(calls, { type: 'set', success: true, value: v, args: args, receiver: this }); return returned; @@ -366,7 +406,7 @@ Test.prototype.intercept = function intercept(obj, property) { return value; } - var restore = mockProperty(obj, property, { + var restore = mockProperty(/** @type {Parameters[0]} */ (obj), property, { nonEnumerable: !!desc.enumerable, get: getInterceptor, set: setInterceptor @@ -385,6 +425,7 @@ Test.prototype.intercept = function intercept(obj, property) { return results; }; +/** @type {import('./test').prototype._end} @this {import('./test')} */ Test.prototype._end = function _end(err) { var self = this; @@ -417,15 +458,17 @@ Test.prototype._end = function _end(err) { completeEnd(); return; } - var fn = self._teardown.shift(); + var fn = /** @type {import('./test').TeardownHandler} */ (self._teardown.shift()); var res; try { res = fn(); } catch (e) { + // @ts-expect-error `e` will be stringified self.fail(e); } - if (res && typeof res.then === 'function') { - res.then(next, function (_err) { + if (res && typeof /** @type {PromiseLike} */ (res).then === 'function') { + /** @type {PromiseLike} */ + (res).then(next, /** @type {(_err: unknown) => void} */ function (_err) { // TODO: wth? err = err || _err; }); @@ -437,6 +480,7 @@ Test.prototype._end = function _end(err) { next(); }; +/** @type {import('./test').prototype._exit} @this {import('./test')} */ Test.prototype._exit = function _exit() { if (this._plan !== undefined && !this._planError && this.assertCount !== this._plan) { this._planError = true; @@ -452,6 +496,7 @@ Test.prototype._exit = function _exit() { } }; +/** @type {import('./test').prototype._pendingAsserts} */ Test.prototype._pendingAsserts = function _pendingAsserts() { if (this._plan === undefined) { return 1; @@ -459,6 +504,7 @@ Test.prototype._pendingAsserts = function _pendingAsserts() { return this._plan - (this._progeny.length + this.assertCount); }; +/** @type {import('./test').prototype._assert} */ Test.prototype._assert = function assert(ok, opts) { var self = this; var extra = opts.extra || {}; @@ -471,6 +517,7 @@ Test.prototype._assert = function assert(ok, opts) { return; } + /** @type {Result} */ var res = { id: self.assertCount++, ok: actualOK, @@ -591,11 +638,12 @@ Test.prototype._assert = function assert(ok, opts) { self._planError = true; self.fail('plan != count', { expected: self._plan, - actual: self._plan - pendingAsserts + actual: /** @type {number} */ (self._plan) - pendingAsserts }); } }; +/** @type {import('./test').prototype.fail} @this {import('./test')} */ Test.prototype.fail = function fail(msg, extra) { this._assert(false, { message: msg, @@ -604,6 +652,7 @@ Test.prototype.fail = function fail(msg, extra) { }); }; +/** @type {import('./test').prototype.pass} @this {import('./test')} */ Test.prototype.pass = function pass(msg, extra) { this._assert(true, { message: msg, @@ -612,6 +661,7 @@ Test.prototype.pass = function pass(msg, extra) { }); }; +/** @type {import('./test').prototype.skip} @this {import('./test')} */ Test.prototype.skip = function skip(msg, extra) { this._assert(true, { message: msg, @@ -621,6 +671,7 @@ Test.prototype.skip = function skip(msg, extra) { }); }; +/** @type {import('./test').prototype.ok} @this {import('./test')} */ var testAssert = function assert(value, msg, extra) { // eslint-disable-line func-style this._assert(value, { message: defined(msg, 'should be truthy'), @@ -635,6 +686,7 @@ Test.prototype.ok = Test.prototype.assert = testAssert; +/** @type {import('./test').prototype.notOk} @this {import('./test')} */ function notOK(value, msg, extra) { this._assert(!value, { message: defined(msg, 'should be falsy'), @@ -649,6 +701,7 @@ Test.prototype.notOk = Test.prototype.notok = notOK; +/** @type {import('./test').prototype.error} @this {import('./test')} */ function error(err, msg, extra) { this._assert(!err, { message: defined(msg, String(err)), @@ -663,10 +716,12 @@ Test.prototype.error = Test.prototype.iferror = error; +/** @type {import('./test').prototype.equal} @this {import('./test')} */ function strictEqual(a, b, msg, extra) { if (arguments.length < 2) { throw new TypeError('two arguments must be provided to compare'); } + this._assert(is(a, b), { message: defined(msg, 'should be strictly equal'), operator: 'equal', @@ -683,6 +738,7 @@ Test.prototype.equal = Test.prototype.is = strictEqual; +/** @type {import('./test').prototype.notEqual} @this {import('./test')} */ function notStrictEqual(a, b, msg, extra) { if (arguments.length < 2) { throw new TypeError('two arguments must be provided to compare'); @@ -707,6 +763,7 @@ Test.prototype.notEqual = Test.prototype.not = notStrictEqual; +/** @type {import('./test').prototype.looseEqual} @this {import('./test')} */ function looseEqual(a, b, msg, extra) { if (arguments.length < 2) { throw new TypeError('two arguments must be provided to compare'); @@ -724,6 +781,7 @@ Test.prototype.looseEqual = Test.prototype.looseEquals = looseEqual; +/** @type {import('./test').prototype.notLooseEqual} @this {import('./test')} */ function notLooseEqual(a, b, msg, extra) { if (arguments.length < 2) { throw new TypeError('two arguments must be provided to compare'); @@ -740,6 +798,7 @@ Test.prototype.notLooseEqual = Test.prototype.notLooseEquals = notLooseEqual; +/** @type {import('./test').prototype.deepEqual} @this {import('./test')} */ function tapeDeepEqual(a, b, msg, extra) { if (arguments.length < 2) { throw new TypeError('two arguments must be provided to compare'); @@ -758,6 +817,7 @@ Test.prototype.deepEqual = Test.prototype.same = tapeDeepEqual; +/** @type {import('./test').prototype.notDeepEqual} @this {import('./test')} */ function notDeepEqual(a, b, msg, extra) { if (arguments.length < 2) { throw new TypeError('two arguments must be provided to compare'); @@ -781,6 +841,7 @@ Test.prototype.notDeepEqual = Test.prototype.isInequivalent = notDeepEqual; +/** @type {import('./test').prototype.deepLooseEqual} @this {import('./test')} */ function deepLooseEqual(a, b, msg, extra) { if (arguments.length < 2) { throw new TypeError('two arguments must be provided to compare'); @@ -797,6 +858,7 @@ function deepLooseEqual(a, b, msg, extra) { Test.prototype.deepLooseEqual = deepLooseEqual; +/** @type {import('./test').prototype.notDeepLooseEqual} @this {import('./test')} */ function notDeepLooseEqual(a, b, msg, extra) { if (arguments.length < 2) { throw new TypeError('two arguments must be provided to compare'); @@ -812,19 +874,26 @@ function notDeepLooseEqual(a, b, msg, extra) { Test.prototype.notDeepLooseEqual = notDeepLooseEqual; +/** @type {(x: unknown) => x is object} */ +function isObject(x) { + return Object(x) === x; +} + +/** @type {import('./test').prototype.throws} @this {import('./test')} */ Test.prototype['throws'] = function (fn, expected, msg, extra) { if (typeof expected === 'string') { msg = expected; expected = undefined; } + /** @type {undefined | { error: unknown | Error }} */ var caught; try { fn(); } catch (err) { caught = { error: err }; - if (Object(err) === err && 'message' in err && (!isEnumerable(err, 'message') || !hasOwn(err, 'message'))) { + if (isObject(err) && 'message' in err && (!isEnumerable(err, 'message') || !hasOwn(err, 'message'))) { try { var message = err.message; delete err.message; @@ -833,10 +902,11 @@ Test.prototype['throws'] = function (fn, expected, msg, extra) { } } + /** @type {typeof caught | boolean} */ var passed = caught; if (caught) { - if (typeof expected === 'string' && caught.error && caught.error.message === expected) { + if (typeof expected === 'string' && caught.error && /** @type {Error} */ (caught.error).message === expected) { throw new TypeError('The "error/message" argument is ambiguous. The error message ' + inspect(expected) + ' is identical to the message.'); } if (typeof expected === 'function') { @@ -859,10 +929,13 @@ Test.prototype['throws'] = function (fn, expected, msg, extra) { } else if (keys.length === 0) { throw new TypeError('`throws` validation object must not be empty'); } - passed = every(keys, function (key) { + // TS TODO: `caught.error` and `expected` should both be `object` here + passed = every(keys, /** @type {(key: PropertyKey) => boolean} */ function (key) { + // @ts-expect-error `caught-error` and `expected` are already narrowed to `object` if (typeof caught.error[key] === 'string' && isRegExp(expected[key]) && $exec(expected[key], caught.error[key]) !== null) { return true; } + // @ts-expect-error `caught.error` and `expected` are already narrowed to `object` if (key in caught.error && deepEqual(caught.error[key], expected[key], { strict: true })) { return true; } @@ -884,6 +957,7 @@ Test.prototype['throws'] = function (fn, expected, msg, extra) { }); }; +/** @type {import('./test').prototype.doesNotThrow} @this {import('./test')} */ Test.prototype.doesNotThrow = function doesNotThrow(fn, expected, msg, extra) { if (typeof expected === 'string') { msg = expected; @@ -905,6 +979,7 @@ Test.prototype.doesNotThrow = function doesNotThrow(fn, expected, msg, extra) { }); }; +/** @type {import('./test').prototype.match} @this {import('./test')} */ Test.prototype.match = function match(string, regexp, msg, extra) { if (!isRegExp(regexp)) { this._assert(false, { @@ -938,6 +1013,7 @@ Test.prototype.match = function match(string, regexp, msg, extra) { } }; +/** @type {import('./test').prototype.doesNotMatch} @this {import('./test')} */ Test.prototype.doesNotMatch = function doesNotMatch(string, regexp, msg, extra) { if (!isRegExp(regexp)) { this._assert(false, { @@ -971,17 +1047,20 @@ Test.prototype.doesNotMatch = function doesNotMatch(string, regexp, msg, extra) } }; +/** @type {import('./test').prototype.assertion} @this {import('./test')} */ Test.prototype.assertion = function assertion(fn) { return callBind.apply(fn)(this, $slice(arguments, 1)); }; +module.exports = Test; + +// this must be assigned to module.exports or else attw complains +/** @type {import('./test').skip} */ // eslint-disable-next-line no-unused-vars -Test.skip = function skip(name_, _opts, _cb) { +module.exports.skip = function skip(_, _opts, _cb) { var args = getTestArgs.apply(null, arguments); args.opts.skip = true; - return new Test(args.name, args.opts, args.cb); + return /** @type {ReturnType} */ (/** @type {unknown} */ (new Test(args.name, args.opts, args.cb))); }; -module.exports = Test; - // vim: set softtabstop=4 shiftwidth=4: diff --git a/package.json b/package.json index 8f72d318..b17b0037 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,29 @@ "string.prototype.trim": "^1.2.10" }, "devDependencies": { + "@arethetypeswrong/cli": "^0.17.3", "@ljharb/eslint-config": "^21.1.1", + "@ljharb/tsconfig": "^0.2.3", + "@types/array.prototype.every": "^1.1.1", + "@types/array.prototype.flatmap": "^1.2.6", + "@types/call-bind": "^1.0.5", + "@types/concat-stream": "^2.0.3", + "@types/deep-equal": "^1.0.4", + "@types/defined": "^1.0.2", + "@types/falafel": "^2.2.2", + "@types/for-each": "^0.3.3", + "@types/get-package-type": "^0.1.0", + "@types/inherits": "^0.0.33", + "@types/js-yaml": "^3.12.10", + "@types/node": "^10.17.60", + "@types/object-inspect": "^1.13.0", + "@types/object-is": "^1.1.0", + "@types/object-keys": "^1.0.3", + "@types/object.assign": "^4.1.0", + "@types/readable-stream": "^4.0.18", + "@types/semver": "^7.5.8", + "@types/string.prototype.trim": "^1.2.0", + "@types/tap": "^14.10.3", "array.prototype.flatmap": "^1.3.3", "auto-changelog": "^2.5.0", "concat-stream": "^1.6.2", @@ -64,7 +86,8 @@ "safe-publish-latest": "^2.0.0", "semver": "^6.3.1", "tap": "^8.0.1", - "tap-parser": "^5.4.0" + "tap-parser": "^5.4.0", + "typescript": "next" }, "scripts": { "prepack": "npmignore --auto --commentLines=autogenerated", @@ -77,6 +100,7 @@ "eclint:windows": "eclint check *.js", "prelint": "npm-run-posix-or-windows eclint", "lint": "eslint --ext .js,.cjs,.mjs . bin/*", + "postlint": "tsc && attw -P", "pretest": "npm run lint", "test": "npm-run-posix-or-windows tests-only", "posttest": "npx npm@'>=10.2' audit --production", @@ -123,7 +147,8 @@ }, "publishConfig": { "ignore": [ - ".github/workflows" + ".github/workflows", + "types" ] } } diff --git a/test/anonymous-fn/test-wrapper.js b/test/anonymous-fn/test-wrapper.js index 7a35bae9..e86f2697 100644 --- a/test/anonymous-fn/test-wrapper.js +++ b/test/anonymous-fn/test-wrapper.js @@ -9,6 +9,7 @@ function tearDown() { } // Example of wrapper function that would invoke tape +/** @type {(this: import('../../lib/test') | void, testCase: import('../../lib/test').TestCase) => import('../../lib/test').TestCase} */ module.exports = function (testCase) { return function (t) { setUp(); diff --git a/test/array.js b/test/array.js index 247206ba..ee6a0efe 100644 --- a/test/array.js +++ b/test/array.js @@ -34,6 +34,7 @@ tap.test('array test', function (tt) { var src = '(' + function () { var xs = [1, 2, [3, 4]]; var ys = [5, 6]; + // @ts-expect-error g([xs, ys]); } + ')()'; @@ -51,11 +52,11 @@ tap.test('array test', function (tt) { ]; Function('fn', 'g', String(output))( - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(arrays.shift(), xs); return xs; }, - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(xs, [[1, 2, [3, 4]], [5, 6]]); } ); diff --git a/test/assertion.js b/test/assertion.js index f11cc857..24ce7d5d 100644 --- a/test/assertion.js +++ b/test/assertion.js @@ -14,6 +14,7 @@ tap.test('using a custom assertion', function (tt) { var count = 0; test.createStream().pipe(concat({ encoding: 'string' }, function (body) { tt.same(stripFullStack(body), [].concat( + // @ts-expect-error TS sucks with concat 'TAP version 13', '# with a custom assertion', 'ok ' + ++count + ' true is ok', @@ -36,6 +37,7 @@ tap.test('using a custom assertion', function (tt) { typeof Promise === 'undefined' ? '# SKIP custom assertion returns a promise' : [].concat( + // @ts-expect-error TS sucks with concat '# custom assertion returns a promise', 'ok ' + ++count + ' promise rejected!', 'not ok ' + ++count + ' SyntaxError: expected promise to reject; it fulfilled', @@ -59,8 +61,8 @@ tap.test('using a custom assertion', function (tt) { )); })); + /** @type {import('../lib/test').AssertionFunction<[number, string?], void>} */ var isAnswer = function (value, msg) { - this.equal(value, 42, msg || 'value must be the answer to life, the universe, and everything'); }; @@ -73,6 +75,12 @@ tap.test('using a custom assertion', function (tt) { t.end(); }); + /** @typedef {() => unknown} RejectsFn */ + /** @typedef {import('../lib/test').ThrowsExpected} ThrowsExpected */ + /** @typedef {import('../').AssertOptions} AssertOptions */ + /** @typedef {[fn: RejectsFn, expected: ThrowsExpected, msg?: string, extra?: AssertOptions]} RejectsArgs */ + + /** @type {import('../lib/test').AssertionFunction>} */ var rejects = function assertRejects(fn, expected, msg, extra) { var t = this; /* eslint no-invalid-this: 0 */ diff --git a/test/async-await.js b/test/async-await.js index ad852f0a..52e56cae 100644 --- a/test/async-await.js +++ b/test/async-await.js @@ -18,9 +18,10 @@ var node17 = node15 && Number(majorVersion) >= 17; var lengthMessage = 'Cannot read property \'length\' of null'; try { + // @ts-expect-error lengthMessage = null.length; } catch (e) { - lengthMessage = e.message; // differs in v8 6.9+ (node 16.9+) + lengthMessage = /** @type {Error} */ (e).message; // differs in v8 6.9+ (node 16.9+) } tap.test('async1', function (t) { @@ -205,6 +206,7 @@ tap.test('sync-error', function (t) { var stderr = lines.join('\n'); t.same(stripFullStack(stripDeprecations(stderr)), [].concat( + // @ts-expect-error TS sucks with concat '$TEST/async-await/sync-error.js:7', ' throw new Error(\'oopsie\');', ' ^', diff --git a/test/async-await/async-bug.js b/test/async-await/async-bug.js index 8c16ccf4..db75f44d 100644 --- a/test/async-await/async-bug.js +++ b/test/async-await/async-bug.js @@ -2,6 +2,7 @@ var test = require('../../'); +/** @param {number[]} arr */ function myCode(arr) { let sum = 0; // oops forgot to handle null @@ -11,6 +12,7 @@ function myCode(arr) { return sum; } +/** @param {number} ms */ function sleep(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); @@ -24,6 +26,7 @@ test('async-error', async function myTest(t) { const sum = myCode([1, 2, 3]); t.equal(sum, 6); + // @ts-expect-error const sum2 = myCode(null); t.equal(sum2, 0); diff --git a/test/async-await/async4.js b/test/async-await/async4.js index f7f80bde..88253c99 100644 --- a/test/async-await/async4.js +++ b/test/async-await/async4.js @@ -5,7 +5,7 @@ var test = require('../../'); test('async4', async function myTest(t) { try { t.ok(true, 'before await'); - await new Promise((resolve, reject) => { + await new Promise((_resolve, reject) => { setTimeout(function myTimeout() { reject(new Error('oops')); }, 10); diff --git a/test/async-await/async5.js b/test/async-await/async5.js index 5e40cae4..8e9ce2f6 100644 --- a/test/async-await/async5.js +++ b/test/async-await/async5.js @@ -10,7 +10,7 @@ test('async5', async function myTest(t) { t.ok(true, 'before server'); var mockDb = { state: 'old' }; - var server = http.createServer(function (req, res) { + var server = http.createServer(function (_req, res) { res.end('OK'); // Pretend we write to the DB and it takes time. @@ -30,6 +30,7 @@ test('async5', async function myTest(t) { var res = await util.promisify(function (cb) { var req = http.request({ hostname: 'localhost', + // @ts-expect-error TODO FIXME port: server.address().port, path: '/', method: 'GET' diff --git a/test/capture.js b/test/capture.js index 09958677..38ae2e88 100644 --- a/test/capture.js +++ b/test/capture.js @@ -16,6 +16,7 @@ tap.test('capture: output', function (tt) { var count = 0; test.createStream().pipe(concat({ encoding: 'string' }, function (body) { tt.same(stripFullStack(body), [].concat( + // @ts-expect-error TS sucks with concat 'TAP version 13', '# argument validation', v.primitives.map(function (x) { @@ -52,6 +53,7 @@ tap.test('capture: output', function (tt) { test('argument validation', function (t) { forEach(v.primitives, function (primitive) { t.throws( + // @ts-expect-error function () { t.capture(primitive, ''); }, TypeError, inspect(primitive) + ' is not an Object' @@ -60,6 +62,7 @@ tap.test('capture: output', function (tt) { forEach(v.nonPropertyKeys, function (nonPropertyKey) { t.throws( + // @ts-expect-error function () { t.capture({}, nonPropertyKey); }, TypeError, inspect(nonPropertyKey) + ' is not a valid property key' @@ -69,6 +72,7 @@ tap.test('capture: output', function (tt) { forEach(v.nonFunctions, function (nonFunction) { if (typeof nonFunction !== 'undefined') { t.throws( + // @ts-expect-error function () { t.capture({}, '', nonFunction); }, TypeError, inspect(nonFunction) + ' is not a function' @@ -90,10 +94,14 @@ tap.test('capture: output', function (tt) { var up = new SyntaxError('foo'); var resultsThrow = st.capture(o, 'fooThrow', function () { throw up; }); + // @ts-expect-error o.foo(1, 2, 3); + // @ts-expect-error o.foo(3, 4, 5); + // @ts-expect-error o.foo2.call(sentinel, 1); st.throws( + // @ts-expect-error function () { o.fooThrow(1, 2, 3); }, SyntaxError, 'throwing implementation throws' @@ -105,6 +113,7 @@ tap.test('capture: output', function (tt) { ]); st.deepEqual(results(), []); + // @ts-expect-error o.foo(6, 7, 8); st.deepEqual(results(), [ { args: [6, 7, 8], receiver: o, returned: sentinel } diff --git a/test/captureFn.js b/test/captureFn.js index 72cb088c..5440722f 100644 --- a/test/captureFn.js +++ b/test/captureFn.js @@ -16,6 +16,7 @@ tap.test('captureFn: output', function (tt) { var count = 0; test.createStream().pipe(concat({ encoding: 'string' }, function (body) { tt.same(stripFullStack(body), [].concat( + // @ts-expect-error TS sucks with concat 'TAP version 13', '# argument validation', v.nonFunctions.map(function (x) { @@ -39,6 +40,7 @@ tap.test('captureFn: output', function (tt) { test('argument validation', function (t) { forEach(v.nonFunctions, function (nonFunction) { t.throws( + // @ts-expect-error function () { t.captureFn(nonFunction); }, TypeError, inspect(nonFunction) + ' is not a function' @@ -55,6 +57,7 @@ tap.test('captureFn: output', function (tt) { var up = new SyntaxError('foo'); var wrappedThrower = t.captureFn(function () { throw up; }); + // @ts-expect-error t.equal(wrappedSentinelThunk(1, 2), sentinel, 'return value is passed through'); t.throws( function () { wrappedThrower.call(sentinel, 1, 2, 3); }, diff --git a/test/comment.js b/test/comment.js index e6b0954f..106d0911 100644 --- a/test/comment.js +++ b/test/comment.js @@ -35,10 +35,11 @@ tap.test('missing argument', function (assert) { test.createStream(); test('missing argument', function (t) { try { + // @ts-expect-error t.comment(); t.end(); } catch (err) { - assert.equal(err.constructor, TypeError); + assert.equal(/** @type {Error} */ (err).constructor, TypeError); } finally { assert.end(); } @@ -52,10 +53,11 @@ tap.test('null argument', function (assert) { test.createStream(); test('null argument', function (t) { try { + // @ts-expect-error t.comment(null); t.end(); } catch (err) { - assert.equal(err.constructor, TypeError); + assert.equal(/** @type {Error} */ (err).constructor, TypeError); } finally { assert.end(); } @@ -121,14 +123,23 @@ tap.test('non-string types', function (assert) { })); test('non-string types', function (t) { + // @ts-expect-error t.comment(true); + // @ts-expect-error t.comment(false); + // @ts-expect-error t.comment(42); + // @ts-expect-error t.comment(6.66); + // @ts-expect-error t.comment({}); + // @ts-expect-error t.comment({ answer: 42 }); + /** @constructor */ function ConstructorFunction() {} + // @ts-expect-error t.comment(new ConstructorFunction()); + // @ts-expect-error t.comment(ConstructorFunction); t.end(); }); @@ -172,7 +183,7 @@ tap.test('comment with createStream/objectMode', function (assert) { assert.plan(1); var test = tape.createHarness(); - test.createStream({ objectMode: true }).on('data', function (row) { + test.createStream({ objectMode: true }).on('data', /** @param {unknown} row */ function (row) { if (typeof row === 'string') { assert.equal(row, 'comment message'); } diff --git a/test/common.js b/test/common.js index 68bbb411..738fc38b 100644 --- a/test/common.js +++ b/test/common.js @@ -5,6 +5,10 @@ var spawn = require('child_process').spawn; var concat = require('concat-stream'); var yaml = require('js-yaml'); +/** @typedef {import('../lib/results').Result} Result */ +/** @typedef {import('../lib/test').SyncCallback} SyncCallback */ + +/** @type {(body: string, includeStack?: boolean) => Result} */ module.exports.getDiag = function (body, includeStack) { var yamlStart = body.indexOf(' ---'); var yamlEnd = body.indexOf(' ...\n'); @@ -14,7 +18,7 @@ module.exports.getDiag = function (body, includeStack) { // The stack trace and at variable will vary depending on where the code // is run, so just strip it out. - var withStack = yaml.safeLoad(diag); + var withStack = /** @type {Result & { stack?: unknown }} */ (yaml.safeLoad(diag)); if (!includeStack) { delete withStack.stack; } @@ -38,6 +42,7 @@ module.exports.getDiag = function (body, includeStack) { // strip out all stack frames that aren't directly under our test directory, // and replace them with placeholders. +/** @type {(line: string) => null | string} */ var stripChangingData = function (line) { var withoutTestDir = line.replace(__dirname, '$TEST'); var withoutPackageDir = withoutTestDir.replace(path.dirname(__dirname), '$TAPE'); @@ -62,6 +67,7 @@ var stripChangingData = function (line) { }; module.exports.stripChangingData = stripChangingData; +/** @type {(output: string) => string[]} */ module.exports.stripFullStack = function (output) { var stripped = ' [... stack stripped ...]'; var withDuplicates = output.split(/\r?\n/g).map(stripChangingData).map(function (line) { @@ -106,7 +112,11 @@ module.exports.stripFullStack = function (output) { .split(/\r?\n/g); }; +/** @typedef {{ stdout: string; stderr: string; exitCode: number; }} ProgramResult */ + +/** @type {(folderName: string, fileName: string, cb: (result: ProgramResult) => void) => void} */ module.exports.runProgram = function (folderName, fileName, cb) { + /** @type {{ stdout: string | null, stderr: string | null, exitCode: number | null }} */ var result = { stdout: null, stderr: null, @@ -123,12 +133,14 @@ module.exports.runProgram = function (folderName, fileName, cb) { result.stderr = stderrRows; })); - ps.on('exit', function (code) { + ps.on('exit', /** @param {number} code */ function (code) { result.exitCode = code; + // @ts-expect-error i can't make the declared type `satisfies` cb(result); }); }; +/** @type {(msg: string | Buffer) => string} */ module.exports.stripDeprecations = function (msg) { return String(msg) .replace(/^\s*\(node:\d+\) ExperimentalWarning: The ESM module loader is experimental\.\s*$/g, '') diff --git a/test/deep-equal-failure.js b/test/deep-equal-failure.js index fe54fa75..b0738e97 100644 --- a/test/deep-equal-failure.js +++ b/test/deep-equal-failure.js @@ -49,7 +49,7 @@ tap.test('deep equal failure', function (assert) { }); })); - parser.once('assert', function (data) { + parser.once('assert', /** @param {{ diag: { stack?: unknown; at?: unknown} }} data */ function (data) { assert.deepEqual(data, { ok: false, id: 1, @@ -111,7 +111,7 @@ tap.test('deep equal failure, depth 6, with option', function (assert) { }); })); - parser.once('assert', function (data) { + parser.once('assert', /** @param {{ diag: { stack?: unknown; at?: unknown} }} data */function (data) { assert.deepEqual(data, { ok: false, id: 1, @@ -173,7 +173,7 @@ tap.test('deep equal failure, depth 6, without option', function (assert) { }); })); - parser.once('assert', function (data) { + parser.once('assert', /** @param {{ diag: { stack?: unknown; at?: unknown} }} data */function (data) { assert.deepEqual(data, { ok: false, id: 1, diff --git a/test/deep.js b/test/deep.js index 944b519d..725471c4 100644 --- a/test/deep.js +++ b/test/deep.js @@ -20,17 +20,29 @@ test('deep loose equal', function (t) { test('requires 2 arguments', function (t) { var err = /^TypeError: two arguments must be provided/; + // @ts-expect-error t.throws(function () { t.deepEqual(); }, err, 'deepEqual: no args'); + // @ts-expect-error t.throws(function () { t.deepEqual(undefined); }, err, 'deepEqual: one arg'); + // @ts-expect-error t.throws(function () { t.deepLooseEqual(); }, err, 'deepLooseEqual: no args'); + // @ts-expect-error t.throws(function () { t.deepLooseEqual(undefined); }, err, 'deepLooseEqual: one arg'); + // @ts-expect-error t.throws(function () { t.notDeepEqual(); }, err, 'notDeepEqual: no args'); + // @ts-expect-error t.throws(function () { t.notDeepEqual(undefined); }, err, 'notDeepEqual: one arg'); + // @ts-expect-error t.throws(function () { t.notDeepLooseEqual(); }, err, 'notDeepLooseEqual: no args'); + // @ts-expect-error t.throws(function () { t.notDeepLooseEqual(undefined); }, err, 'notDeepLooseEqual: one arg'); + // @ts-expect-error t.throws(function () { t.equal(); }, err, 'equal: no args'); + // @ts-expect-error t.throws(function () { t.equal(undefined); }, err, 'equal: one arg'); + // @ts-expect-error t.throws(function () { t.notEqual(); }, err, 'notEqual: no args'); + // @ts-expect-error t.throws(function () { t.notEqual(undefined); }, err, 'notEqual: one arg'); t.end(); diff --git a/test/default_stream.js b/test/default_stream.js index eb1435f9..49975ac4 100644 --- a/test/default_stream.js +++ b/test/default_stream.js @@ -12,6 +12,7 @@ tap.test('getDefaultStream', function (tt) { tt.doesNotThrow(function () { stream.write('# ok'); }, 'strings are fine'); tt.doesNotThrow(function () { stream.write(123); }, 'numbers are fine'); tt.doesNotThrow(function () { stream.write(undefined); }, 'undefined is fine'); + // @ts-expect-error tt.doesNotThrow(function () { stream.write(); }, 'no args is fine'); tt.doesNotThrow(function () { stream.write(null); }, 'null is fine'); // TODO: figure out why writing to the stream after writing null fails tests diff --git a/test/double_end.js b/test/double_end.js index ffcbff03..054ac264 100644 --- a/test/double_end.js +++ b/test/double_end.js @@ -7,10 +7,10 @@ var spawn = require('child_process').spawn; var stripFullStack = require('./common').stripFullStack; -test(function (tt) { +test('(unnamed test)', function (tt) { tt.plan(2); var ps = spawn(process.execPath, [path.join(__dirname, 'double_end', 'double.js')]); - ps.on('exit', function (code) { + ps.on('exit', /** @param {number} code */ function (code) { tt.equal(code, 1); }); ps.stdout.pipe(concat({ encoding: 'string' }, function (body) { @@ -23,14 +23,16 @@ test(function (tt) { function doEnd() { throw new Error(); } var to = setTimeout(doEnd, 5000); clearTimeout(to); + // @ts-expect-error to._onTimeout = doEnd; var stackExpected; var atExpected; try { + // @ts-expect-error to._onTimeout(); } catch (e) { - stackExpected = stripFullStack(e.stack)[1]; + stackExpected = stripFullStack(/** @type {Error & { stack: string }} */ (e).stack)[1]; stackExpected = stackExpected.replace('double_end.js', 'double_end/double.js'); stackExpected = stackExpected.trim(); atExpected = stackExpected.replace(/^at\s+/, 'at: '); diff --git a/test/end-as-callback.js b/test/end-as-callback.js index 35a0bacb..3ccb8110 100644 --- a/test/end-as-callback.js +++ b/test/end-as-callback.js @@ -10,8 +10,8 @@ var concat = require('concat-stream'); * this will change dependent on the environment * so no point hard-coding it in the test assertion * see: https://git.io/v6hGG for example - * @param String rows - the tap output lines - * @returns String stacktrace - just the error stack part + * @param {string} rows - the tap output lines + * @returns {string} stacktrace - just the error stack part */ function getStackTrace(rows) { var stacktrace = ' ---\n'; @@ -32,15 +32,18 @@ function getStackTrace(rows) { return stacktrace; } +/** @param {string} name @param {(err: unknown, data?: string) => void} cb*/ function fakeAsyncTask(name, cb) { cb(null, 'task' + name); } -function fakeAsyncWrite(name, cb) { +/** @param {string} _name @param {(err: unknown, data?: string) => void} cb*/ +function fakeAsyncWrite(_name, cb) { cb(null); } -function fakeAsyncWriteFail(name, cb) { +/** @param {string} _name @param {(err: unknown, data?: string) => void} cb*/ +function fakeAsyncWriteFail(_name, cb) { cb(new Error('fail')); } diff --git a/test/exit.js b/test/exit.js index 0443ebae..08acc085 100644 --- a/test/exit.js +++ b/test/exit.js @@ -31,7 +31,8 @@ tap.test('exit ok', function (t) { '' // if you can figure out how to remove them, please do! ].join('\n')); })); - ps.on('exit', function (code) { + + ps.on('exit', /** @param {number} code */ function (code) { t.equal(code, 0); }); }); @@ -72,7 +73,8 @@ tap.test('exit fail', function (t) { '' ]); })); - ps.on('exit', function (code) { + + ps.on('exit', /** @param {number} code */ function (code) { t.notEqual(code, 0); }); }); @@ -109,7 +111,8 @@ tap.test('too few exit', function (t) { '' ]); })); - ps.on('exit', function (code) { + + ps.on('exit', /** @param {number} code */ function (code) { t.notEqual(code, 0); }); }); @@ -144,7 +147,7 @@ tap.test('more planned in a second test', function (t) { '' ]); })); - ps.on('exit', function (code) { + ps.on('exit', /** @param {number} code */ function (code) { t.notEqual(code, 0); }); }); @@ -168,7 +171,7 @@ tap.test('todo passing', function (t) { '' ]); })); - ps.on('exit', function (code) { + ps.on('exit', /** @param {number} code */ function (code) { t.equal(code, 0); }); }); @@ -198,7 +201,7 @@ tap.test('todo failing', function (t) { '' ]); })); - ps.on('exit', function (code) { + ps.on('exit', /** @param {number} code */ function (code) { t.equal(code, 0); }); }); @@ -231,7 +234,7 @@ tap.test('forgot to call t.end()', function (t) { '' ]); })); - ps.on('exit', function (code) { + ps.on('exit', /** @param {number} code */ function (code) { t.notEqual(code, 0); }); }); diff --git a/test/exit/fail.js b/test/exit/fail.js index 38a23f89..d8a3606f 100644 --- a/test/exit/fail.js +++ b/test/exit/fail.js @@ -9,6 +9,7 @@ test('array', function (t) { var src = '(' + function () { var xs = [1, 2, [3, 4]]; var ys = [5, 6]; + // @ts-expect-error g([xs, ys]); } + ')()'; @@ -26,11 +27,11 @@ test('array', function (t) { ]; Function('fn', 'g', String(output))( - function (xs) { + /** @param {number[]} xs */ function (xs) { t.same(arrays.shift(), xs); return xs; }, - function (xs) { + /** @param {number[]} xs */ function (xs) { t.same(xs, [[1, 2, [3, 4444]], [5, 6]]); } ); diff --git a/test/exit/ok.js b/test/exit/ok.js index a5d88136..f2e8ed16 100644 --- a/test/exit/ok.js +++ b/test/exit/ok.js @@ -10,6 +10,7 @@ test('array', function (t) { var src = '(' + function () { var xs = [1, 2, [3, 4]]; var ys = [5, 6]; + // @ts-expect-error g([xs, ys]); } + ')()'; @@ -27,11 +28,11 @@ test('array', function (t) { ]; Function('fn', 'g', String(output))( - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(arrays.shift(), xs); return xs; }, - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(xs, [[1, 2, [3, 4]], [5, 6]]); } ); diff --git a/test/exit/too_few.js b/test/exit/too_few.js index c30003bb..b38c498a 100644 --- a/test/exit/too_few.js +++ b/test/exit/too_few.js @@ -9,6 +9,7 @@ test('array', function (t) { var src = '(' + function () { var xs = [1, 2, [3, 4]]; var ys = [5, 6]; + // @ts-expect-error g([xs, ys]); } + ')()'; @@ -26,11 +27,11 @@ test('array', function (t) { ]; Function('fn', 'g', String(output))( - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(arrays.shift(), xs); return xs; }, - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(xs, [[1, 2, [3, 4]], [5, 6]]); } ); diff --git a/test/fail.js b/test/fail.js index ce4789bc..26ef0fa0 100644 --- a/test/fail.js +++ b/test/fail.js @@ -50,6 +50,7 @@ tap.test('array test', function (tt) { var src = '(' + function () { var xs = [1, 2, [3, 4]]; var ys = [5, 6]; + // @ts-expect-error g([xs, ys]); } + ')()'; @@ -67,11 +68,11 @@ tap.test('array test', function (tt) { ]; Function('fn', 'g', String(output))( - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(arrays.shift(), xs); return xs; }, - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(xs, [[1, 2, [3, 4444]], [5, 6]]); } ); diff --git a/test/ignore-pattern.js b/test/ignore-pattern.js index ea1811ba..9f63a66f 100644 --- a/test/ignore-pattern.js +++ b/test/ignore-pattern.js @@ -10,7 +10,7 @@ tap.test('should allow ignore file together with --ignore-pattern', function (tt tt.plan(1); var proc = execFile(process.execPath, [tapeBin, '--ignore', '.ignore', '--ignore-pattern', 'fake_other_ignored_dir', '**/*.js'], { cwd: path.join(__dirname, 'ignore-pattern') }); - proc.on('exit', function (code) { + proc.on('exit', /** @param {number} code */ function (code) { tt.equals(code, 0); }); }); @@ -19,7 +19,7 @@ tap.test('should allow --ignore-pattern without ignore file', function (tt) { tt.plan(1); var proc = execFile(process.execPath, [tapeBin, '--ignore-pattern', 'fake_*', '**/*.js'], { cwd: path.join(__dirname, 'ignore-pattern') }); - proc.on('exit', function (code) { + proc.on('exit', /** @param {number} code */ function (code) { tt.equals(code, 0); }); }); @@ -28,7 +28,7 @@ tap.test('should fail if not ignoring', function (tt) { tt.plan(1); var proc = execFile(process.execPath, [tapeBin, '**/*.js'], { cwd: path.join(__dirname, 'ignore-pattern') }); - proc.on('exit', function (code) { + proc.on('exit', /** @param {number} code */ function (code) { tt.equals(code, 1); }); }); diff --git a/test/ignore_from_gitignore.js b/test/ignore_from_gitignore.js index 8cd33ed2..8082f4dd 100644 --- a/test/ignore_from_gitignore.js +++ b/test/ignore_from_gitignore.js @@ -9,6 +9,8 @@ var stripFullStack = require('./common').stripFullStack; var tapeBin = path.join(process.cwd(), 'bin/tape'); +/** @typedef {(code: number) => void} ExitHandler */ + tap.test('Should pass with ignoring', function (tt) { tt.plan(2); @@ -38,7 +40,7 @@ tap.test('Should pass with ignoring', function (tt) { '' ]); })); - ps.on('exit', function (code) { + ps.on('exit', /** @type {ExitHandler} */ function (code) { tt.equal(code, 0); // code 0 }); }); @@ -93,7 +95,7 @@ tap.test('Should pass', function (tt) { '' ]); })); - ps.on('exit', function (code) { + ps.on('exit', /** @type {ExitHandler} */ function (code) { tt.equal(code, 1); }); }); @@ -108,7 +110,7 @@ tap.test('Should fail when ignore file does not exist', function (tt) { ps.stderr.pipe(concat({ encoding: 'string' }, function (rows) { tt.ok((/^ENOENT[:,] no such file or directory,? (?:open )?'\$TEST\/ignore\/.gitignore'\n$/m).test(stripFullStack(rows).join('\n'))); })); - ps.on('exit', function (code) { + ps.on('exit', /** @type {ExitHandler} */ function (code) { tt.equal(code, 2); }); }); diff --git a/test/import.js b/test/import.js index 4da37ebb..c47b46b7 100644 --- a/test/import.js +++ b/test/import.js @@ -6,6 +6,7 @@ var concat = require('concat-stream'); var hasDynamicImport = require('has-dynamic-import'); var assign = require('object.assign'); +/** @type {(args: string, options?: Parameters[2]) => import('child_process').ChildProcess} */ function tape(args, options) { var bin = __dirname + '/../bin/tape'; @@ -44,7 +45,7 @@ tap.test('importing mjs files', function (t) { ].join('\n') + '\n\n'); })); ps.stderr.pipe(process.stderr); - ps.on('exit', function (code) { + ps.on('exit', /** @param {number} code */ function (code) { t.equal(code, 0); t.end(); }); @@ -77,7 +78,7 @@ tap.test('importing type: "module" files', function (t) { ].join('\n') + '\n\n'); })); ps.stderr.pipe(process.stderr); - ps.on('exit', function (code) { + ps.on('exit', /** @param {number} code */ function (code) { t.equal(code, 0); t.end(); }); @@ -90,22 +91,26 @@ tap.test('importing type: "module" files', function (t) { tap.test('errors importing test files', function (t) { hasDynamicImport().then(function (hasSupport) { - var createTest = function (options) { + var createTest = /** @param {(typeof tests)[number]} options */ function (options) { var message = options.error + ' in `' + options.mode + '` mode`'; var ps = tape(options.files, { env: { NODE_OPTIONS: '--unhandled-rejections=' + options.mode } }); ps.stderr.pipe(concat({ encoding: 'string' }, options.unhandledRejection(message))); - ps.on('exit', function (code/* , sig */) { + ps.on('exit', /** @param {number} code */ function (code/* , sig */) { t.equal(code, options.exitCode, message + ' has exit code ' + options.exitCode); }); }; + /** @param {string} message */ var warning = function (message) { + /** @param {Buffer} rows */ return function (rows) { t.match(rows, 'UnhandledPromiseRejectionWarning', 'should have unhandled rejection warning: ' + message); }; }; + /** @param {string} message */ var noWarning = function (message) { + /** @param {Buffer} rows */ return function (rows) { t.notMatch(rows, 'UnhandledPromiseRejectionWarning', 'should not have unhandled rejection warning: ' + message); }; diff --git a/test/import/mjs-a.mjs b/test/import/mjs-a.mjs index f0319055..eb7b580b 100644 --- a/test/import/mjs-a.mjs +++ b/test/import/mjs-a.mjs @@ -3,6 +3,7 @@ import tape from '../../index.js'; tape.test('mjs-a', function (t) { t.pass('test ran'); t.end(); + // @ts-expect-error global.mjs_a = true; }); diff --git a/test/import/mjs-b.mjs b/test/import/mjs-b.mjs index 88b207b8..57d724ad 100644 --- a/test/import/mjs-b.mjs +++ b/test/import/mjs-b.mjs @@ -1,7 +1,9 @@ import tape from '../../index.js'; tape.test('mjs-b', function (t) { + // @ts-expect-error t.ok(global.mjs_a, 'test ran after mjs-a'); t.end(); + // @ts-expect-error global.mjs_b = true; }); diff --git a/test/import/mjs-c.mjs b/test/import/mjs-c.mjs index da57d4e6..9b591996 100644 --- a/test/import/mjs-c.mjs +++ b/test/import/mjs-c.mjs @@ -1,7 +1,9 @@ import tape from '../../index.js'; tape.test('mjs-c', function (t) { + // @ts-expect-error t.ok(global.mjs_b, 'test ran after mjs-b'); t.end(); + // @ts-expect-error global.mjs_c = true; }); diff --git a/test/import/mjs-d.mjs b/test/import/mjs-d.mjs index 7cf54cb8..71654fa4 100644 --- a/test/import/mjs-d.mjs +++ b/test/import/mjs-d.mjs @@ -1,7 +1,9 @@ import tape from '../../index.js'; tape.test('mjs-d', function (t) { + // @ts-expect-error t.ok(global.mjs_c, 'test ran after mjs-c'); t.end(); + // @ts-expect-error global.mjs_d = true; }); diff --git a/test/import/mjs-e.mjs b/test/import/mjs-e.mjs index 29a7c63e..0d256849 100644 --- a/test/import/mjs-e.mjs +++ b/test/import/mjs-e.mjs @@ -1,7 +1,9 @@ import tape from '../../index.js'; tape.test('mjs-e', function (t) { + // @ts-expect-error t.ok(global.mjs_d, 'test ran after mjs-d'); t.end(); + // @ts-expect-error global.mjs_e = true; }); diff --git a/test/import/mjs-f.mjs b/test/import/mjs-f.mjs index 0e428bee..bf2e05f2 100644 --- a/test/import/mjs-f.mjs +++ b/test/import/mjs-f.mjs @@ -1,7 +1,9 @@ import tape from '../../index.js'; tape.test('mjs-f', function (t) { + // @ts-expect-error t.ok(global.mjs_e, 'test ran after mjs-e'); t.end(); + // @ts-expect-error global.mjs_f = true; }); diff --git a/test/import/mjs-g.mjs b/test/import/mjs-g.mjs index 1a7a0c16..562da8bf 100644 --- a/test/import/mjs-g.mjs +++ b/test/import/mjs-g.mjs @@ -1,7 +1,9 @@ import tape from '../../index.js'; tape.test('mjs-g', function (t) { + // @ts-expect-error t.ok(global.mjs_f, 'test ran after mjs-f'); t.end(); + // @ts-expect-error global.mjs_g = true; }); diff --git a/test/import/mjs-h.mjs b/test/import/mjs-h.mjs index 67046721..d47b6698 100644 --- a/test/import/mjs-h.mjs +++ b/test/import/mjs-h.mjs @@ -1,6 +1,7 @@ import tape from '../../index.js'; tape.test('mjs-h', function (t) { + // @ts-expect-error t.ok(global.mjs_g, 'test ran after mjs-g'); t.end(); }); diff --git a/test/import/package_type/package-a.js b/test/import/package_type/package-a.js index 0471914c..507fd8aa 100644 --- a/test/import/package_type/package-a.js +++ b/test/import/package_type/package-a.js @@ -3,6 +3,7 @@ import tape from '../../../index.js'; tape.test('package-type-a', function (t) { t.pass('test ran'); t.end(); + // @ts-expect-error global.package_type_a = true; }); diff --git a/test/import/package_type/package-b.js b/test/import/package_type/package-b.js index 10b6dfcf..069f41cd 100644 --- a/test/import/package_type/package-b.js +++ b/test/import/package_type/package-b.js @@ -1,7 +1,9 @@ import tape from '../../../index.js'; tape.test('package-type-b', function (t) { + // @ts-expect-error t.ok(global.package_type_a, 'test ran after package-type-a'); t.end(); + // @ts-expect-error global.package_type_b = true; }); diff --git a/test/import/package_type/package-c.js b/test/import/package_type/package-c.js index bb8807b3..8cc50cad 100644 --- a/test/import/package_type/package-c.js +++ b/test/import/package_type/package-c.js @@ -1,6 +1,7 @@ import tape from '../../../index.js'; tape.test('package-type-c', function (t) { + // @ts-expect-error t.ok(global.package_type_b, 'test ran after package-type-b'); t.end(); }); diff --git a/test/intercept.js b/test/intercept.js index 10b077a3..7a6d26f8 100644 --- a/test/intercept.js +++ b/test/intercept.js @@ -16,6 +16,7 @@ tap.test('intercept: output', function (tt) { var count = 0; test.createStream().pipe(concat({ encoding: 'string' }, function (body) { tt.same(stripFullStack(body), [].concat( + // @ts-expect-error TS sucks with concat 'TAP version 13', '# argument validation', v.primitives.map(function (x) { @@ -90,6 +91,7 @@ tap.test('intercept: output', function (tt) { test('argument validation', function (t) { forEach(v.primitives, function (primitive) { t.throws( + // @ts-expect-error function () { t.intercept(primitive, ''); }, TypeError, 'obj: ' + inspect(primitive) + ' is not an Object' @@ -98,6 +100,7 @@ tap.test('intercept: output', function (tt) { forEach(v.nonPropertyKeys, function (nonPropertyKey) { t.throws( + // @ts-expect-error function () { t.intercept({}, nonPropertyKey); }, TypeError, inspect(nonPropertyKey) + ' is not a valid property key' @@ -106,12 +109,14 @@ tap.test('intercept: output', function (tt) { forEach(v.primitives, function (primitive) { t.throws( + // @ts-expect-error function () { t.intercept({}, '', primitive); }, TypeError, 'desc: ' + inspect(primitive) + ' is not an Object' ); }); t.throws( + // @ts-expect-error function () { t.intercept({}, '', { configurable: false }); }, TypeError, 'configurable false is not allowed' @@ -129,6 +134,7 @@ tap.test('intercept: output', function (tt) { forEach(v.nonBooleans, function (nonBoolean) { t.throws( + // @ts-expect-error function () { t.intercept({}, '', {}, nonBoolean); }, TypeError, inspect(nonBoolean) + ' is not a Boolean' @@ -138,8 +144,12 @@ tap.test('intercept: output', function (tt) { t.end(); }); + /** @typedef {PropertyDescriptor & { get(): any }} GetDescriptor */ + /** @typedef {PropertyDescriptor & { set(): any }} SetDescriptor */ + test('intercepts gets/sets', function (t) { var sentinel = { sentinel: true, inspect: function () { return '{ SENTINEL OBJECT }'; } }; + /** @type {Record} */ var o = { foo: sentinel, inspect: function () { return '{ o OBJECT }'; } }; t.equal(o.foo, sentinel, 'property has expected initial value'); @@ -150,7 +160,7 @@ tap.test('intercept: output', function (tt) { st.equal(o.foo, sentinel, 'sentinel is returned from Get'); st.equal(o.foo, sentinel, 'sentinel is returned from Get again'); st.equal( - Object.getOwnPropertyDescriptor(o, 'foo').get.call(o, 1, 2, 3), + /** @type {GetDescriptor} */ (Object.getOwnPropertyDescriptor(o, 'foo')).get.call(o, 1, 2, 3), sentinel, 'sentinel is returned from Get with .call' ); @@ -160,7 +170,7 @@ tap.test('intercept: output', function (tt) { var results2 = st.intercept(o, 'foo2'); st.equal(o.foo2, undefined, 'undefined is returned from Get'); st.equal( - Object.getOwnPropertyDescriptor(o, 'foo2').get.call(o, 4, 5), + /** @type {GetDescriptor} */ (Object.getOwnPropertyDescriptor(o, 'foo2')).get.call(o, 4, 5), undefined, 'undefined is returned from Get with .call' ); @@ -198,6 +208,7 @@ tap.test('intercept: output', function (tt) { 'throwing get implementation throws' ); st.throws( + // @ts-expect-error function () { Object.getOwnPropertyDescriptor(o, 'fooThrowGet').get.call(sentinel, 1, 2, 3); }, SyntaxError, 'throwing get implementation throws with .call' @@ -214,6 +225,7 @@ tap.test('intercept: output', function (tt) { 'throwing set implementation throws' ); st.throws( + // @ts-expect-error function () { Object.getOwnPropertyDescriptor(o, 'fooThrowSet').set.call(sentinel, 4, 5, 6); }, SyntaxError, 'throwing set implementation throws with .call' @@ -227,9 +239,10 @@ tap.test('intercept: output', function (tt) { var resultsGetter = st.intercept(o, 'getter', { get: function () { return sentinel; } }); st.equal(o.getter, sentinel, 'getter: sentinel is returned from Get'); - st.equal(Object.getOwnPropertyDescriptor(o, 'getter').get.call(sentinel, 1, 2, 3), sentinel, 'getter: sentinel is returned from Get with .call'); + st.equal(/** @type {GetDescriptor} */ (Object.getOwnPropertyDescriptor(o, 'getter')).get.call(sentinel, 1, 2, 3), sentinel, 'getter: sentinel is returned from Get with .call'); resultsGetter.restore(); + /** @type {unknown} */ var val; var resultsSetter = st.intercept(o, 'setter', { get: function () { return val; }, @@ -237,7 +250,7 @@ tap.test('intercept: output', function (tt) { }); o.setter = sentinel; st.equal(o.setter, sentinel, 'setter: setted value is returned from Get'); - Object.getOwnPropertyDescriptor(o, 'setter').set.call(sentinel, 1, 2, 3); + /** @type {SetDescriptor} */ (Object.getOwnPropertyDescriptor(o, 'setter')).set.call(sentinel, 1, 2, 3); st.equal(o.setter, 1, 'setter: setted value is returned from Get with .call'); resultsSetter.restore(); diff --git a/test/match.js b/test/match.js index 6e7ec8c4..9012a118 100644 --- a/test/match.js +++ b/test/match.js @@ -101,10 +101,14 @@ tap.test('match', function (tt) { test('match', function (t) { t.plan(8); + // @ts-expect-error t.match(/abc/, 'string'); + // @ts-expect-error t.match(/abc/, 'string', 'regex arg must not be a string'); + // @ts-expect-error t.match({ abc: 123 }, /abc/); + // @ts-expect-error t.match({ abc: 123 }, /abc/, 'string arg must not be an object'); t.match('string', /abc/); @@ -236,10 +240,14 @@ tap.test('doesNotMatch', function (tt) { test('doesNotMatch', function (t) { t.plan(10); + // @ts-expect-error t.doesNotMatch(/abc/, 'string'); + // @ts-expect-error t.doesNotMatch(/abc/, 'string', 'regex arg must not be a string'); + // @ts-expect-error t.doesNotMatch({ abc: 123 }, /abc/); + // @ts-expect-error t.doesNotMatch({ abc: 123 }, /abc/, 'string arg must not be an object'); t.doesNotMatch('string', /string/); diff --git a/test/max_listeners.js b/test/max_listeners.js index 7a6ecdb6..74c50a32 100644 --- a/test/max_listeners.js +++ b/test/max_listeners.js @@ -9,7 +9,7 @@ var ps = spawn(process.execPath, [path.join(__dirname, 'max_listeners', 'source. ps.stdout.pipe(process.stdout, { end: false }); -ps.stderr.on('data', function (buf) { +ps.stderr.on('data', /** @param {Buffer} buf */ function (buf) { if (stripDeprecations(buf)) { console.log('not ok ' + buf); } diff --git a/test/nested.js b/test/nested.js index c27ec531..1cb5a16a 100644 --- a/test/nested.js +++ b/test/nested.js @@ -40,6 +40,7 @@ tap.test('array test', function (tt) { var src = '(' + function () { var xs = [1, 2, [3, 4]]; var ys = [5, 6]; + // @ts-expect-error g([xs, ys]); } + ')()'; @@ -66,11 +67,11 @@ tap.test('array test', function (tt) { ]; Function('fn', 'g', String(output))( - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(arrays.shift(), xs); return xs; }, - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(xs, [[1, 2, [3, 4]], [5, 6]]); } ); diff --git a/test/no_only.js b/test/no_only.js index 92ed6d21..116904b9 100644 --- a/test/no_only.js +++ b/test/no_only.js @@ -24,11 +24,13 @@ tap.test( }, function (err, stdout, stderr) { tt.same(stdout, ''); tt.match(stripFullStack(stderr).join('\n'), /Error: `only` tests are prohibited\n/); - tt.equal(err.code, expectedExitCodeOnError); + tt.equal(err && err.code, expectedExitCodeOnError); }); } ); +/** @typedef {Error & { code?: number }} ExecError */ + tap.test( 'Should throw error when NODE_TAPE_NO_ONLY_TEST is passed via envs and there is an .only test', { todo: expectedStackTraceBug ? 'Fails on these node versions' : false }, @@ -42,7 +44,7 @@ tap.test( }, function (err, stdout, stderr) { tt.same(stdout, ''); tt.match(stripFullStack(stderr).join('\n'), /Error: `only` tests are prohibited\n/); - tt.equal(err.code, expectedExitCodeOnError); + tt.equal(/** @type {ExecError} */ (err).code, expectedExitCodeOnError); }); } ); @@ -60,7 +62,7 @@ tap.test( }, function (err, stdout, stderr) { tt.same(stdout, ''); tt.match(stripFullStack(stderr).join('\n'), /Error: `only` tests are prohibited\n/); - tt.equal(err.code, expectedExitCodeOnError); + tt.equal(/** @type {ExecError} */ (err).code, expectedExitCodeOnError); }); } ); diff --git a/test/not-deep-equal-failure.js b/test/not-deep-equal-failure.js index 0f4482bc..6ba69ba1 100644 --- a/test/not-deep-equal-failure.js +++ b/test/not-deep-equal-failure.js @@ -49,7 +49,7 @@ tap.test('deep equal failure', function (assert) { }); })); - parser.once('assert', function (data) { + parser.once('assert', /** @param {{ diag: { at: string, stack: string} }} data */ function (data) { assert.deepEqual(data, { ok: false, id: 1, @@ -111,7 +111,7 @@ tap.test('not deep equal failure, depth 6, with option', function (assert) { }); })); - parser.once('assert', function (data) { + parser.once('assert', /** @param {{ diag: { at: string, stack: string} }} data */ function (data) { assert.deepEqual(data, { ok: false, id: 1, @@ -173,7 +173,7 @@ tap.test('not deep equal failure, depth 6, without option', function (assert) { }); })); - parser.once('assert', function (data) { + parser.once('assert', /** @param {{ diag: { at: string, stack: string} }} data */ function (data) { assert.deepEqual(data, { ok: false, id: 1, diff --git a/test/objectMode.js b/test/objectMode.js index cddbe5fb..6e0d8a81 100644 --- a/test/objectMode.js +++ b/test/objectMode.js @@ -6,18 +6,21 @@ var forEach = require('for-each'); var through = require('@ljharb/through'); tap.test('object results', function (assert) { - var printer = through(null, null, { objectMode: true }); + var printer = through(); + /** @type {{ type?: string; id: string; skip: boolean; todo: boolean; text: string; test: string }[]} */ var objects = []; - printer.write = function (obj) { + printer.write = /** @param {typeof objects[number]} obj */ function (obj) { objects.push(obj); + return true; }; - printer.end = function (obj) { + printer.end = /** @param {typeof objects[number] | null} obj */ function (obj) { if (obj) { objects.push(obj); } var todos = 0; var skips = 0; + /** @type {string[]} */ var testIds = []; var endIds = []; var asserts = 0; @@ -47,6 +50,8 @@ tap.test('object results', function (assert) { assert.equal(todos, 2); assert.equal(testIds.length, endIds.length); assert.end(); + + return void undefined; }; tape.createStream({ objectMode: true }).pipe(printer); diff --git a/test/objectModeWithComment.js b/test/objectModeWithComment.js index 197e9fe5..0857e9e5 100644 --- a/test/objectModeWithComment.js +++ b/test/objectModeWithComment.js @@ -5,17 +5,19 @@ var tape = require('../'); var through = require('@ljharb/through'); tap.test('test.comment() in objectMode', function (assert) { - var printer = through(null, null, { objectMode: true }); + var printer = through(); + /** @type {object[]} */ var objects = []; - printer.on('error', function (e) { + printer.on('error', /** @param {string} e */ function (e) { assert.fail(e); }); printer.write = function (obj) { - objects.push(obj); + objects.push(/** @type {typeof objects[number]} */ (obj)); + return true; }; printer.end = function (obj) { - if (obj) { objects.push(obj); } + if (obj) { objects.push(/** @type {typeof objects[number]} */ (obj)); } assert.equal(objects.length, 3); assert.deepEqual(objects, [ @@ -30,6 +32,8 @@ tap.test('test.comment() in objectMode', function (assert) { { type: 'end', test: 0 } ]); assert.end(); + + return void undefined; }; tape.createStream({ objectMode: true }).pipe(printer); diff --git a/test/onFailure.js b/test/onFailure.js index 884963c5..c9ef03a3 100644 --- a/test/onFailure.js +++ b/test/onFailure.js @@ -11,7 +11,7 @@ var mockSink = { emit: noop, end: noop }; -tape.createStream().pipe(mockSink); +tape.createStream().pipe(/** @type {NodeJS.WritableStream} */ (/** @type {unknown} */ (mockSink))); tap.test('on failure', { timeout: 1000 }, function (tt) { tt.plan(1); diff --git a/test/only.js b/test/only.js index bf55ecdd..8f3db404 100644 --- a/test/only.js +++ b/test/only.js @@ -6,6 +6,7 @@ var concat = require('concat-stream'); tap.test('tape only test', function (tt) { var test = tape.createHarness({ exit: false }); + /** @type {number[]} */ var ran = []; test.createStream().pipe(concat({ encoding: 'string' }, function (rows) { diff --git a/test/promises/fail.js b/test/promises/fail.js index d3ed599f..e60c831d 100644 --- a/test/promises/fail.js +++ b/test/promises/fail.js @@ -4,7 +4,7 @@ var test = require('../../'); if (typeof Promise === 'function' && typeof Promise.resolve === 'function') { test('promise', function () { - return new Promise(function (resolve, reject) { + return new Promise(function (_resolve, reject) { reject(new Error('rejection message')); }); }); diff --git a/test/promises/subTests.js b/test/promises/subTests.js index 4f8a5873..d0c3f7b4 100644 --- a/test/promises/subTests.js +++ b/test/promises/subTests.js @@ -5,7 +5,7 @@ var test = require('../../'); if (typeof Promise === 'function' && typeof Promise.resolve === 'function') { test('promise', function (t) { t.test('sub test that should fail', function () { - return new Promise(function (resolve, reject) { + return new Promise(function (_resolve, reject) { reject(new Error('rejection message')); }); }); diff --git a/test/require.js b/test/require.js index 37c0356b..848c9273 100644 --- a/test/require.js +++ b/test/require.js @@ -5,6 +5,7 @@ var spawn = require('child_process').spawn; var concat = require('concat-stream'); var stripFullStack = require('./common').stripFullStack; +/** @param {string} args */ function tape(args) { var bin = __dirname + '/../bin/tape'; @@ -33,7 +34,8 @@ tap.test('requiring a single module', function (t) { '' ]); })); - ps.on('exit', function (code) { + + ps.on('exit', /** @param {number} code */ function (code) { t.equal(code, 0); }); }); @@ -63,7 +65,8 @@ tap.test('requiring multiple modules', function (t) { '# ok' ].join('\n') + '\n\n'); })); - ps.on('exit', function (code) { + + ps.on('exit', /** @param {number} code */ function (code) { t.equal(code, 0); }); }); diff --git a/test/require/a.js b/test/require/a.js index f1c23c74..a5223705 100644 --- a/test/require/a.js +++ b/test/require/a.js @@ -7,4 +7,5 @@ tape.test('module-a', function (t) { t.pass('loaded module a'); }); +// @ts-expect-error global.module_a = true; diff --git a/test/require/b.js b/test/require/b.js index 6a2d1b52..6620bfce 100644 --- a/test/require/b.js +++ b/test/require/b.js @@ -7,4 +7,5 @@ tape.test('module-b', function (t) { t.pass('loaded module b'); }); +// @ts-expect-error global.module_b = true; diff --git a/test/require/test-a.js b/test/require/test-a.js index 04834510..e0991041 100644 --- a/test/require/test-a.js +++ b/test/require/test-a.js @@ -3,6 +3,7 @@ var tape = require('../..'); tape.test('test-a', function (t) { + // @ts-expect-error t.ok(global.module_a, 'module-a loaded in same context'); t.pass('test ran after module-a was loaded'); t.end(); diff --git a/test/require/test-b.js b/test/require/test-b.js index 552565f7..76337750 100644 --- a/test/require/test-b.js +++ b/test/require/test-b.js @@ -3,6 +3,7 @@ var tape = require('../..'); tape.test('test-b', function (t) { + // @ts-expect-error t.ok(global.module_b, 'module-b loaded in same context'); t.pass('test ran after module-b was loaded'); t.end(); diff --git a/test/skip.js b/test/skip.js index e5fd3f79..8cae3123 100644 --- a/test/skip.js +++ b/test/skip.js @@ -32,6 +32,7 @@ test('skip this', { skip: true }, function (t) { t.end(); }); +// @ts-expect-error TODO FIXME not sure if this should be a valid type, because the cb is actually being treated as `extra` here test.skip('skip this too', function (t) { t.fail('this should not even run'); t.end(); diff --git a/test/stackTrace.js b/test/stackTrace.js index ace34267..35275189 100644 --- a/test/stackTrace.js +++ b/test/stackTrace.js @@ -12,10 +12,12 @@ var common = require('./common'); var getDiag = common.getDiag; +/** @param {string} body */ function stripAt(body) { return body.replace(/^\s*at:\s+Test.*$\n/m, ''); } +/** @type {(x: unknown) => x is string} */ function isString(x) { return typeof x === 'string'; } @@ -28,7 +30,7 @@ tap.test('preserves stack trace with newlines', function (tt) { var parser = stream.pipe(tapParser()); var stackTrace = 'foo\n bar'; - parser.once('assert', function (data) { + parser.once('assert', /** @param {{ diag: { at: string, stack: string} }} data */ function (data) { tt.deepEqual(data, { ok: false, id: 1, @@ -82,7 +84,7 @@ tap.test('parses function info from original stack', function (tt) { test.createStream(); test._results._watch = function (t) { - t.on('result', function (res) { + t.on('result', /** @param {{ functionName: string, file: string, line: number, column: number }} res */ function (res) { tt.equal('Test.testFunctionNameParsing', res.functionName); tt.match(res.file, /stackTrace.js/i); tt.ok(Number(res.line) > 0); @@ -103,7 +105,7 @@ tap.test('parses function info from original stack for anonymous function', func test.createStream(); test._results._watch = function (t) { - t.on('result', function (res) { + t.on('result', /** @param {{ functionName: string, file: string, line: number, column: number }} res */ function (res) { tt.equal('Test.', res.functionName); tt.match(res.file, /stackTrace.js/i); tt.ok(Number(res.line) > 0); @@ -118,7 +120,6 @@ tap.test('parses function info from original stack for anonymous function', func }); if (typeof Promise === 'function' && typeof Promise.resolve === 'function') { - tap.test('parses function info from original stack for Promise scenario', function (tt) { tt.plan(4); @@ -126,7 +127,7 @@ if (typeof Promise === 'function' && typeof Promise.resolve === 'function') { test.createStream(); test._results._watch = function (t) { - t.on('result', function (res) { + t.on('result', /** @param {{ functionName: string, file: string, line: number, column: number }} res */ function (res) { tt.equal('onfulfilled', res.functionName); tt.match(res.file, /stackTrace.js/i); tt.ok(Number(res.line) > 0); @@ -135,9 +136,7 @@ if (typeof Promise === 'function' && typeof Promise.resolve === 'function') { }; test('t.equal stack trace', function testFunctionNameParsing(t) { - new Promise(function (resolve) { - resolve(); - }).then(function onfulfilled() { + Promise.resolve().then(function onfulfilled() { t.equal(true, false, 'true should be false'); t.end(); }); @@ -151,7 +150,7 @@ if (typeof Promise === 'function' && typeof Promise.resolve === 'function') { test.createStream(); test._results._watch = function (t) { - t.on('result', function (res) { + t.on('result', /** @param {{ functionName: string, file: string, line: number, column: number }} res */ function (res) { tt.equal('', res.functionName); tt.match(res.file, /stackTrace.js/i); tt.ok(Number(res.line) > 0); @@ -160,9 +159,7 @@ if (typeof Promise === 'function' && typeof Promise.resolve === 'function') { }; test('t.equal stack trace', function testFunctionNameParsing(t) { - new Promise(function (resolve) { - resolve(); - }).then(function () { + Promise.resolve().then(function () { t.equal(true, false, 'true should be false'); t.end(); }); @@ -179,7 +176,7 @@ tap.test('preserves stack trace for failed assertions', function (tt) { var parser = stream.pipe(tapParser()); var stack = ''; - parser.once('assert', function (data) { + parser.once('assert', /** @param {{ diag: { at: string, stack: string} }} data */ function (data) { tt.equal(typeof data.diag.at, 'string'); tt.equal(typeof data.diag.stack, 'string'); var at = data.diag.at || ''; @@ -202,6 +199,7 @@ tap.test('preserves stack trace for failed assertions', function (tt) { stream.pipe(concat({ encoding: 'string' }, function (body) { var strippedBody = stripAt(body); tt.deepEqual(strippedBody.split('\n'), [].concat( + // @ts-expect-error TS sucks with concat 'TAP version 13', '# t.equal stack trace', 'not ok 1 true should be false', @@ -242,7 +240,7 @@ tap.test('preserves stack trace for failed assertions where actual===falsy', fun var parser = stream.pipe(tapParser()); var stack = ''; - parser.once('assert', function (data) { + parser.once('assert', /** @param {{ diag: { at: string, stack: string} }} data */ function (data) { tt.equal(typeof data.diag.at, 'string'); tt.equal(typeof data.diag.stack, 'string'); var at = data.diag.at || ''; @@ -265,6 +263,7 @@ tap.test('preserves stack trace for failed assertions where actual===falsy', fun stream.pipe(concat({ encoding: 'string' }, function (body) { var strippedBody = stripAt(body); tt.deepEqual(strippedBody.split('\n'), [].concat( + // @ts-expect-error TS sucks with concat 'TAP version 13', '# t.equal stack trace', 'not ok 1 false should be true', @@ -297,12 +296,14 @@ tap.test('preserves stack trace for failed assertions where actual===falsy', fun }); }); +/** @param {string} args @param {Parameters[2]} [options] */ function spawnTape(args, options) { var bin = __dirname + '/../bin/tape'; return spawn(process.execPath, [bin].concat(args.split(' ')), assign({ cwd: __dirname }, options)); } +/** @param {string | string[]} rows */ function processRows(rows) { return (typeof rows === 'string' ? rows.split('\n') : rows).map(common.stripChangingData).filter(isString).join('\n'); } @@ -343,7 +344,7 @@ tap.test('CJS vs ESM: `at`', function (tt) { ])); })); ps.stderr.pipe(process.stderr); - ps.on('exit', function (code) { + ps.on('exit', /** @param {number} code */ function (code) { ttt.notEqual(code, 0); ttt.end(); }); @@ -385,7 +386,7 @@ tap.test('CJS vs ESM: `at`', function (tt) { ])); })); ps.stderr.pipe(process.stderr); - ps.on('exit', function (code) { + ps.on('exit', /** @param {number} code */ function (code) { ttt.equal(code, 1); ttt.end(); }); diff --git a/test/strict.js b/test/strict.js index 798d88ea..3ebd706a 100644 --- a/test/strict.js +++ b/test/strict.js @@ -20,7 +20,7 @@ tap.test( exec(tapeBin + ' --strict "no*files*found"', { cwd: path.join(__dirname), encoding: 'utf8' }, function (err, stdout, stderr) { tt.same(stdout, ''); tt.match(stripFullStack(stderr).join('\n'), /^No test files found!\n$/); - tt.equal(err.code, 127); + tt.equal(/** @type {Error & { code?: number }} */ (err).code, 127); }); } ); diff --git a/test/subtest_and_async.js b/test/subtest_and_async.js index 6f26a689..e67d15d5 100644 --- a/test/subtest_and_async.js +++ b/test/subtest_and_async.js @@ -2,8 +2,9 @@ var test = require('../'); +/** @param {import('../lib/test').Callback} callback */ var asyncFunction = function (callback) { - setTimeout(callback, Math.random * 50); + setTimeout(callback, Math.random() * 50); }; test('master test', function (t) { diff --git a/test/teardown.js b/test/teardown.js index 97c1048b..53794808 100644 --- a/test/teardown.js +++ b/test/teardown.js @@ -16,6 +16,7 @@ tap.test('teardowns', function (tt) { var test = tape.createHarness(); test.createStream().pipe(concat({ encoding: 'string' }, function (body) { tt.same(stripFullStack(body), [].concat( + // @ts-expect-error TS sucks with concat 'TAP version 13', '# success', 'ok 1 should be truthy', @@ -84,6 +85,7 @@ tap.test('teardowns', function (tt) { flatMap(v.nonFunctions, function (nonFunction, i) { var offset = 10; return [].concat( + // @ts-expect-error TS sucks with concat 'not ok ' + (offset + (i > 0 ? i + 1 : i)) + ' teardown: ' + inspect(nonFunction) + ' is not a function', ' ---', ' operator: fail', @@ -230,6 +232,7 @@ tap.test('teardowns', function (tt) { t.ok('non-function test'); forEach(v.nonFunctions, function (nonFunction) { + // @ts-expect-error t.teardown(nonFunction); }); }); @@ -291,7 +294,7 @@ tap.test('teardown only runs once', { skip: typeof Promise !== 'function', timeo var test = tape.createHarness(); test.createStream().pipe(concat({ encoding: 'string' }, function (body) { - tt.same(stripFullStack(body), [].concat( + tt.same(stripFullStack(body), /** @type {const} */ ([ 'TAP version 13', '# teardown is only called once, even with a plan', 'ok 1 passes', @@ -303,7 +306,7 @@ tap.test('teardown only runs once', { skip: typeof Promise !== 'function', timeo '', '# ok', '' - )); + ])); })); test('teardown is only called once, even with a plan', function (t) { diff --git a/test/throws.js b/test/throws.js index ae9cc42b..6f7435b3 100644 --- a/test/throws.js +++ b/test/throws.js @@ -238,13 +238,21 @@ tap.test('failures', function (tt) { test('non functions', function (t) { t.plan(8); + // @ts-expect-error t.throws(); + // @ts-expect-error t.throws(null); + // @ts-expect-error t.throws(true); + // @ts-expect-error t.throws(false); + // @ts-expect-error t.throws('abc'); + // @ts-expect-error t.throws(/a/g); + // @ts-expect-error t.throws([]); + // @ts-expect-error t.throws({}); }); @@ -257,6 +265,7 @@ tap.test('failures', function (tt) { t.plan(3); t.equal(Object.prototype.propertyIsEnumerable.call(messageGetterError, 'message'), true, '"message" is enumerable'); t.throws(thrower, "{ custom: 'error', message: 'message' }"); + // @ts-expect-error t.equal(Object.getOwnPropertyDescriptor(messageGetterError, 'message').get, getter, 'getter is still the same'); }); @@ -273,6 +282,7 @@ tap.test('failures', function (tt) { t.end(); }); + /** @type {TypeError & Partial<{ code: number, foo: string, info: { nested: boolean, baz: string }, reg: RegExp }>} */ // taken from https://nodejs.org/api/assert.html#assert_assert_throws_fn_error_message var err = new TypeError('Wrong value'); err.code = 404; @@ -364,7 +374,9 @@ tap.test('failures', function (tt) { function () { throw new SyntaxError('Wrong value'); }, function (error) { t.ok(error instanceof SyntaxError, 'error is SyntaxError'); - t.ok((/value/).test(String(error)), 'error matches /value/'); + if (error instanceof SyntaxError) { + t.ok((/value/).test(String(error)), 'error matches /value/'); + } // Avoid returning anything from validation functions besides `true`. // Otherwise, it's not clear what part of the validation failed. Instead, // throw an error about the specific validation that failed (as done in this @@ -424,12 +436,14 @@ tap.test('failures', function (tt) { test('non-extensible throw match', { skip: !Object.seal }, function (t) { var error = { foo: 1 }; Object.seal(error); + // @ts-expect-error t.throws(function () { error.x = 1; }, TypeError, 'error is non-extensible'); t.throws(function () { throw error; }, error, 'non-extensible error matches'); var errorWithMessage = { message: 'abc' }; Object.seal(errorWithMessage); + // @ts-expect-error t.throws(function () { errorWithMessage.x = 1; }, TypeError, 'errorWithMessage is non-extensible'); t.throws(function () { throw errorWithMessage; }, error, 'non-extensible error with message matches'); @@ -442,6 +456,7 @@ tap.test('failures', function (tt) { Object.defineProperty(error, 'message', { configurable: false, enumerable: false, writable: false }); t.throws(function () { error.message = 'def'; }, TypeError, 'error is non-writable'); + // @ts-expect-error FIXME why can't you delete an optional property, typescript?!? t.throws(function () { delete error.message; }, TypeError, 'error is non-configurable'); t.throws(function () { throw error; }, { message: 'abc' }, 'non-writable error matches'); diff --git a/test/timeoutAfter.js b/test/timeoutAfter.js index 552b0748..7965c0fe 100644 --- a/test/timeoutAfter.js +++ b/test/timeoutAfter.js @@ -78,22 +78,22 @@ tap.test('timeoutAfter with Promises', { skip: typeof Promise === 'undefined' }, st.plan(1); st.timeoutAfter(1); - return new Promise(function (resolve) { + return /** @type {Promise} */ (new Promise(function (resolve) { setTimeout(function () { resolve(); }, 10); - }); + })); }); t.test('rejected promise', function (st) { st.plan(1); st.timeoutAfter(1); - return new Promise(function (reject) { + return /** @type {Promise} */ (new Promise(function (reject) { setTimeout(function () { reject(); }, 10); - }); + })); }); }); }); diff --git a/test/todo.js b/test/todo.js index 39805948..4dbf0c2d 100644 --- a/test/todo.js +++ b/test/todo.js @@ -45,7 +45,7 @@ tap.test('tape todo test', function (assert) { }); tap.test('tape todo test with TODO_IS_OK', function (assert) { - assert.teardown(mockProperty(process.env, 'TODO_IS_OK', { value: '1' })); + assert.teardown(mockProperty(/** @type {Record} */ (process.env), 'TODO_IS_OK', { value: '1' })); var test = tape.createHarness({ exit: false }); assert.plan(1); diff --git a/test/too_many.js b/test/too_many.js index 0e3987e8..a639528d 100644 --- a/test/too_many.js +++ b/test/too_many.js @@ -51,6 +51,7 @@ tap.test('array test', function (tt) { var src = '(' + function () { var xs = [1, 2, [3, 4]]; var ys = [5, 6]; + // @ts-expect-error g([xs, ys]); } + ')()'; @@ -68,11 +69,11 @@ tap.test('array test', function (tt) { ]; Function('fn', 'g', String(output))( - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(arrays.shift(), xs); return xs; }, - function (xs) { + /** @param {(number | number[])[]} xs */ function (xs) { t.same(xs, [[1, 2, [3, 4]], [5, 6]]); } ); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..a714c9f6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@ljharb/tsconfig", + "compilerOptions": { + "target": "es2021", + "maxNodeModuleJsDepth": 0, + "strictBindCallApply": false, + }, + "exclude": [ + "coverage/**", + "test/import/syntax-error.*", + ], +} diff --git a/types/ecstatic/index.d.ts b/types/ecstatic/index.d.ts new file mode 100644 index 00000000..fd7c2a73 --- /dev/null +++ b/types/ecstatic/index.d.ts @@ -0,0 +1,7 @@ +declare module 'ecstatic' { + import HTTP from 'http'; + + function ecstatic(root: string): typeof HTTP; + + export = ecstatic; +} diff --git a/types/tap-parser/index.d.ts b/types/tap-parser/index.d.ts new file mode 100644 index 00000000..8c3909a7 --- /dev/null +++ b/types/tap-parser/index.d.ts @@ -0,0 +1,85 @@ +declare module 'tap-parser' { + import { Writable } from 'readable-stream'; + + type Parsed = [any, ok: boolean, id: number, rest: string, buffered: string]; + + class Result { + constructor(parsed: Parsed, count: number); + } + + type OnComplete = (results: FinalResults) => void; + type ParserOptions = { + parent?: object; + passes?: Result[]; + level?: number; + bail?: boolean; + omitVersion?: boolean; + buffered?: string; + preserveWhitespace?: boolean; + strict?: boolean; + + } + + class FinalResults { + constructor(skipAll: boolean, self: Result); + } + + class Parser extends Writable { + constructor(options: ParserOptions, onComplete?: OnComplete); + constructor(onComplete?: OnComplete); + + tapError(error: unknown, line: string): void; + + parseTestPoint(testPoint: Parsed, line: string): void; + + nonTap(data: string, didLine: boolean): void; + + plan(start: number, end: number, comment: string, line: string): void; + + resetYamlish(): void; + + yamlGarbage(): void; + + yamlishLine(line: string): void; + + processYamlish(): void; + + write(chunk: string | Buffer, encoding: string, cb: (error: Error | null | undefined) => void): boolean; + write(chunk: string | Buffer, cb?: (error: Error | null | undefined) => void): boolean; + + end(chunk: string | Buffer, encoding?: string, cb?: () => void): this; + end(chunk: string | Buffer, cb?: () => void): this; + end(cb: () => void): this; + + emitComplete(skipAll?: boolean): void; + + version(version: string, line: string): void; + + pragma(key: string, value: unknown, line: string): void; + + bailout(reason: string, synthetic: boolean): void; + + clearExtraQueue(): void; + + endChild(): void; + + emitResult(): void; + + startChild(line: string): void; + + abort(message: string, extra?: object): void; + + emitComment(line: string, skipLine: boolean, noDuplicate: boolean): void; + + parse(line: string): void; + + parseIndent(line: string, indent: string): void; + } + + function tapParser(options: ParserOptions, onComplete?: OnComplete): Parser; + function tapParser(onComplete?: OnComplete): Parser; + + + export = tapParser; +} +