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 aa8cea32..2d8d110c 100755 --- a/bin/tape +++ b/bin/tape @@ -95,6 +95,7 @@ var hasImport = require('has-dynamic-import'); var tape = require('../'); +/** @type {(hasSupport: boolean) => Promise | void} */ function importFiles(hasSupport) { if (!hasSupport) { return files.forEach(function (x) { require(x); }); @@ -104,6 +105,7 @@ function importFiles(hasSupport) { tape.wait(); + /** @type {null | undefined | Promise} */ var filesPromise = files.reduce(function (promise, file) { return promise ? promise.then(function () { return importOrRequire(file); diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 00000000..51bd7e6b --- /dev/null +++ b/index.d.ts @@ -0,0 +1,45 @@ +import through = require('@ljharb/through'); + +import { + Fn, + StreamOptions, +} from './tape'; +import Test = require('./lib/test'); + +type CreateStream = (opts?: StreamOptions) => ReturnType + +type HarnessEventHandler = (cb: Fn, ...rest: unknown[]) => void; + +type Harness = { + (this: ThisType, ...args: Parameters): Test; + run?: () => void; + only: () => Test; + _exitCode: number; + _results: Results; + _tests: Test[]; + close: () => void; + createStream: CreateStream; + onFailure: HarnessEventHandler; + onFinish: HarnessEventHandler; +}; + +type HarnessConfig = { + autoclose?: boolean; + noOnly?: boolean; + objectMode?: boolean; + stream?: NodeJS.WritableStream; + exit?: boolean; +}; + +declare function createHarness(conf_?: HarnessConfig): Harness; + +declare function tape(this: Harness, opts?: HarnessConfig): Test; + +declare const Tape: typeof tape & { + createHarness: typeof createHarness; + Test: Test; + test: typeof tape; + skip: Test['skip']; +} + +export = Tape; \ No newline at end of file diff --git a/index.js b/index.js index 8076e4c9..9183ac01 100644 --- a/index.js +++ b/index.js @@ -1,20 +1,29 @@ '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 + // @ts-expect-error i think old browserify uses `process.browser` && typeof process.on === 'function' && process.browser !== true; var canExit = typeof process !== 'undefined' && process && typeof process.exit === 'function'; +/** @typedef {import('./tape').Fn} Fn */ +/** @typedef {import('.')} Tape */ +/** @typedef {ThisParameterType} Harness */ +/** @typedef {NonNullable[0]>} HarnessConfig */ + module.exports = (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 +33,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,11 +53,13 @@ module.exports = (function () { return getHarness().only.apply(this, arguments); }; + /** @type {Harness['createStream']} */ lazyLoad.createStream = function (opts) { var options = opts || {}; if (!harness) { var output = through(); - getHarness({ stream: output, objectMode: options.objectMode }); + // eslint-disable-next-line no-extra-parens + getHarness({ stream: /** @type {import('stream').Writable} */ (output), objectMode: options.objectMode }); return output; } return harness.createStream(options); @@ -66,21 +78,23 @@ module.exports = (function () { return lazyLoad; }()); +/** @type {Tape['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: import('./tape').Options, cb: Fn) => 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: import('./tape').Result) => void} */ function (r) { if (!r.todo && !r.ok && typeof r !== 'string') { test._exitCode = 1; } }); }(t)); @@ -90,21 +104,25 @@ function createHarness(conf_) { } test._results = results; - test._tests = []; + /** @type {Test[]} */ test._tests = []; + /** @type {Harness['createStream']} */ test.createStream = function (opts) { return results.createStream(opts); }; + /** @type {(cb: Fn) => void} */ test.onFinish = function (cb) { results.on('done', cb); }; + /** @type {(cb: Fn) => void} */ test.onFailure = function (cb) { results.on('fail', cb); }; var only = false; + /** @type {() => ReturnType} */ 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 +135,12 @@ function createHarness(conf_) { test.close = function () { results.close(); }; + test.run = function () {}; + return test; } +/** @type {(conf: Omit, wait?: boolean) => Harness} */ function createExitHarness(config, wait) { var noOnly = config.noOnly; var objectMode = config.objectMode; @@ -140,6 +161,7 @@ function createExitHarness(config, wait) { var es = stream.pipe(cStream || createDefaultStream()); if (canEmitExit && es) { // in node v0.4, `es` is `undefined` // TODO: use `err` arg? + // @ts-expect-error // eslint-disable-next-line no-unused-vars es.on('error', function (err) { harness._exitCode = 1; }); } @@ -180,6 +202,7 @@ function createExitHarness(config, wait) { } module.exports.createHarness = createHarness; -module.exports.Test = Test; -module.exports.test = module.exports; // tap compat -module.exports.test.skip = Test.skip; +var moduleExports = module.exports; // this hack is needed because TS has a bug with seemingly circular exports +moduleExports.Test = Test; +moduleExports.test = module.exports; // tap compat +moduleExports.skip = Test.skip; 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..cf0f9586 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); // eslint-disable-line no-extra-parens + 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..a4ebd207 --- /dev/null +++ b/lib/results.d.ts @@ -0,0 +1,31 @@ +import through = require('@ljharb/through'); +import type { EventEmitter } from 'events'; + +import Test = require('./test'); + +type Stream = ReturnType; + +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: Stream; + + close(this: Results): void; + createStream(this: Results, opts?: StreamOptions): Stream; + only(this: Results, t: Test): void; + push(this: Results, t: Test): void; + + _watch(this: Results, t: Test): void; +} + +export = Results; \ No newline at end of file diff --git a/lib/results.js b/lib/results.js index 3c44b01b..bb77a63d 100644 --- a/lib/results.js +++ b/lib/results.js @@ -18,10 +18,15 @@ var nextTick = typeof setImmediate !== 'undefined' ? setImmediate : process.nextTick; +/** @typedef {ReturnType} 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 */ + +/** @type {(str: string) => string} */ function coalesceWhiteSpaces(str) { return $replace(String(str), /\s+/g, ' '); } +/** @type {(results: import('./results')) => import('./test') | undefined} */ function getNextTest(results) { if (!results._only) { return $shift(results.tests); @@ -37,10 +42,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,6 +83,7 @@ function encodeResult(res, count, todoIsOK) { output += inner + 'at: ' + res.at + '\n'; } + // @ts-expect-error it's fine if `res.actual` is not an Error; the stack would just be undefined. var actualStack = res.actual && (typeof res.actual === 'object' || typeof res.actual === 'function') ? res.actual.stack : undefined; var errorStack = res.error && res.error.stack; var stack = defined(actualStack, errorStack); @@ -91,6 +99,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 +119,18 @@ function Results(options) { inherits(Results, EventEmitter); +/** @type {(this: import('./results'), opts?: import('../tape').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: import('./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 +138,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: import('./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 +182,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 +211,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 +227,16 @@ Results.prototype._watch = function (t) { } }); - t.on('test', function (st) { self._watch(st); }); + t.on('test', /** @type {(st: import('./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..4e3badd2 --- /dev/null +++ b/lib/test.d.ts @@ -0,0 +1,174 @@ +import type { EventEmitter } from 'events'; +import { + Extra, + Fn, + InterceptResults, + Operator, + Options, + WrappedFn, + WrapResults, +} from '../tape'; + +declare class Test extends EventEmitter { + constructor(name: string, opts: Options, cb: Fn); + + readable: boolean; + name: string; + assertCount: number; + pendingCount: number; + calledEnd?: boolean; + ended: boolean; + + // "private" properties + _cb: Fn | undefined; + _objectPrintDepth: number | undefined; + _ok: boolean; + _plan: number | undefined; + _planError: boolean | undefined; + _progeny: Test[]; + _skip: boolean | undefined; + _teardown: Fn[]; + _timeout: number | undefined; + _todo: boolean | undefined; + + captureFn(this: Test, original: X): WrappedFn; + capture(this: Test, obj: object, method: PropertyKey, implementation?: T): WrapResults; + end(this: Test, err?: unknown): void; + fail(this: Test, msg: string, extra?: Extra): void; + intercept(this: Test, obj: object, property: PropertyKey, desc?: PropertyDescriptor): InterceptResults; + pass(this: Test, msg: string, extra?: Extra): void; + run(this: Test): void; + skip(this: Test, msg: string, extra?: Extra): void; + timeoutAfter(this: Test, ms: number): void; + plan(this: Test, n: number): void; + comment(this: Test, msg: string): void; + teardown(this: Test, fn: Fn): void; + test(this: Test, name: string, opts: Options, cb: Fn): void; + + // assertions + + ok(this: Test, value: unknown, msg: string, extra?: Extra): void; + true: typeof this.ok; + assert: typeof this.ok; + + notOK(this: Test, value: unknown, msg: string, extra?: Extra): void; + false: typeof this.notOK; + notok: typeof this.notOK; + + error(this: Test, err?: unknown, msg?: string, extra?: Extra): void; + ifError: typeof this.error; + ifErr: typeof this.error; + iferror: typeof this.error; + + equal(this: Test, a: unknown, b: unknown, msg: string, extra?: Extra): void; + equals: typeof this.equal; + isEqual: typeof this.equal; + strictEqual: typeof this.equal; + strictEquals: typeof this.equal; + is: typeof this.equal; + + notEqual(this: Test, a: unknown, b: unknown, msg: string, extra?: Extra): void; + notEquals: typeof this.notEqual; + isNotEqual: typeof this.notEqual; + doesNotEqual: typeof this.notEqual; + isInequal: typeof this.notEqual; + notStrictEqual: typeof this.notEqual; + notStrictEquals: typeof this.notEqual; + isNot: typeof this.notEqual; + not: typeof this.notEqual; + + looseEqual(this: Test, a: unknown, b: unknown, msg: string, extra?: Extra): void; + looseEquals: typeof this.looseEqual; + + notLooseEqual(this: Test, a: unknown, b: unknown, msg: string, extra?: Extra): void; + notLooseEquals: typeof this.notLooseEqual; + + deepEqual(this: Test, a: unknown, b: unknown, msg: string, extra?: Extra): void; + deepEquals: typeof this.deepEqual; + isEquivalent: typeof this.deepEqual; + same: typeof this.deepEqual; + + notDeepEqual(this: Test, a: unknown, b: unknown, msg: string, extra?: Extra): 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: Test, a: unknown, b: unknown, msg: string, extra?: Extra): void; + + notDeepLooseEqual(this: Test, a: unknown, b: unknown, msg: string, extra?: Extra): void; + + throws( + this: Test, + fn: Fn, + expected: unknown, + msg: string, + extra?: Extra, + ): void; + throws( + this: Test, + fn: Fn, + msg: string, + extra?: Extra, + ): void; + + doesNotThrow( + this: Test, + fn: Fn, + expected: unknown, + msg: string, + extra?: Extra, + ): void; + doesNotThrow( + this: Test, + fn: Fn, + msg: string, + extra?: Extra, + ): void; + + match( + this: Test, + string: string, + regexp: RegExp, + msg: string, + extra?: Extra, + ): void; + + doesNotMatch( + this: Test, + string: string, + regexp: RegExp, + msg: string, + extra?: Extra, + ): void; + + static skip( + name: string, + opts: Options, + cb: Fn, + ): Test; + + // "private" methods + + _assert( + this: Test, + maybeOK: boolean | unknown, + opts: Options & { + message?: string; + operator?: Operator; + error?: unknown; + actual?: unknown; + expected?: unknown; + extra?: Extra; + }, + ): void; + _end(this: Test, err?: unknown): void; + _exit(this: Test): void; + _pendingAsserts(this: Test): number; +} + +export = Test; diff --git a/lib/test.js b/lib/test.js index c6c73b9e..4dc73ef7 100644 --- a/lib/test.js +++ b/lib/test.js @@ -12,7 +12,7 @@ var callBind = require('call-bind'); var callBound = require('call-bind/callBound'); var forEach = require('for-each'); var inspect = require('object-inspect'); -var is = require('object-is'); +var is = require('object-is/polyfill')(); var objectKeys = require('object-keys'); var every = require('array.prototype.every'); var mockProperty = require('mock-property'); @@ -35,11 +35,13 @@ var nextTick = typeof setImmediate !== 'undefined' var safeSetTimeout = setTimeout; var safeClearTimeout = clearTimeout; +/** @typedef {((c: unknown) => c is ErrorConstructor | TypeErrorConstructor | RangeErrorConstructor | EvalErrorConstructor | URIErrorConstructor | ReferenceErrorConstructor | SyntaxErrorConstructor)} IsErrorConstructor */ + 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 +51,12 @@ 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_) { +/** @typedef {import('../tape').Fn} Fn */ + +/** @typedef {import('../tape').Options} Options */ + +/** @type {(name: unknown, opts: unknown, cb: unknown) => { name: string, opts: Options, cb: Fn }} */ +function getTestArgs(name_, opts_, cb_) { // eslint-disable-line no-unused-vars var name = '(anonymous)'; var opts = {}; var cb; @@ -72,12 +78,18 @@ function getTestArgs(name_, opts_, cb_) { }; } -function Test(name_, opts_, cb_) { +/** + * @constructor + * @param {string} name + * @param {import('../tape').Options} opts + * @param {import('../tape').Fn} 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 +112,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 +126,7 @@ function Test(name_, opts_, cb_) { inherits(Test, EventEmitter); +/** @type {import('./test').prototype.run} */ Test.prototype.run = function run() { this.emit('prerun'); if (!this._cb || this._skip) { @@ -128,6 +142,7 @@ Test.prototype.run = function run() { if ( typeof Promise === 'function' && callbackReturn + // @ts-expect-error it's fine that `then` isn't on `{}`, because of the typeof check && typeof callbackReturn.then === 'function' ) { var self = this; @@ -152,9 +167,11 @@ Test.prototype.run = function run() { this.emit('run'); }; +/** @type {import('./test').prototype.test} */ Test.prototype.test = function test(name, opts, cb) { var self = this; - var t = new Test(name, opts, cb); + // eslint-disable-next-line no-extra-parens + var t = /** @type {import('./test')} */ (/** @type {unknown} */ (new Test(name, opts, cb))); $push(this._progeny, t); this.pendingCount++; this.emit('test', t); @@ -175,18 +192,21 @@ Test.prototype.test = function test(name, opts, cb) { }); }; +/** @type {import('./test').prototype.comment} */ 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} */ Test.prototype.plan = function plan(n) { this._plan = n; this.emit('plan', n); }; +/** @type {import('./test').prototype.timeoutAfter} */ Test.prototype.timeoutAfter = function timeoutAfter(ms) { if (!ms) { throw new Error('timeoutAfter requires a timespan'); } var self = this; @@ -199,6 +219,7 @@ Test.prototype.timeoutAfter = function timeoutAfter(ms) { }); }; +/** @type {import('./test').prototype.end} */ Test.prototype.end = function end(err) { if (arguments.length >= 1 && !!err) { this.ifError(err); @@ -211,6 +232,7 @@ Test.prototype.end = function end(err) { this._end(); }; +/** @type {import('./test').prototype.teardown} */ Test.prototype.teardown = function teardown(fn) { if (typeof fn !== 'function') { this.fail('teardown: ' + inspect(fn) + ' is not a function'); @@ -219,6 +241,7 @@ Test.prototype.teardown = function teardown(fn) { } }; +/** @type {(original: undefined | T) => import('../tape').WrapObject} */ function wrapFunction(original) { if (typeof original !== 'undefined' && typeof original !== 'function') { throw new TypeError('`original` must be a function or `undefined`'); @@ -226,14 +249,17 @@ function wrapFunction(original) { var bound = original && callBind.apply(original); + /** @type {import('../tape').WrappedCall[]} */ var calls = []; + /** @type {import('../tape').WrapObject>} */ var wrapObject = { __proto__: null, - wrapped: function wrapped() { + wrapped: /** @type {() => ReturnType} */ function wrapped() { var args = $slice(arguments); var completed = false; try { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/57164 var returned = bound ? bound(this, arguments) : void undefined; $push(calls, { args: args, receiver: this, returned: returned }); completed = true; @@ -257,6 +283,7 @@ function wrapFunction(original) { return wrapObject; } +/** @type {import('./test').prototype.capture} */ 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 +291,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 +306,7 @@ Test.prototype.capture = function capture(obj, method) { return wrapper.results; }; +/** @type {import('./test').prototype.captureFn} */ Test.prototype.captureFn = function captureFn(original) { if (typeof original !== 'function') { throw new TypeError('`original` must be a function'); @@ -288,6 +317,7 @@ Test.prototype.captureFn = function captureFn(original) { return wrapObject.wrapped; }; +/** @type {import('./test').prototype.intercept} */ 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 +325,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 +343,23 @@ Test.prototype.intercept = function intercept(obj, property) { throw new TypeError('`strictMode`, if provided, must be a boolean'); } + /** @type {import('../tape').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); + // eslint-disable-next-line no-extra-parens + 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,11 +374,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 { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/57164 var returned = setter(this, arguments); completed = true; $push(calls, { type: 'set', success: true, value: v, args: args, receiver: this }); @@ -385,6 +422,7 @@ Test.prototype.intercept = function intercept(obj, property) { return results; }; +/** @type {import('./test').prototype._end} */ Test.prototype._end = function _end(err) { var self = this; @@ -417,15 +455,18 @@ Test.prototype._end = function _end(err) { completeEnd(); return; } - var fn = self._teardown.shift(); + var fn = /** @type {Fn} */ (self._teardown.shift()); // eslint-disable-line no-extra-parens var res; try { res = fn(); } catch (e) { + // @ts-expect-error `e` will be stringified self.fail(e); } + // @ts-expect-error it's fine that `then` isn't on `{}`, because of the typeof check if (res && typeof res.then === 'function') { - res.then(next, function (_err) { + // @ts-expect-error in this scope, `res.then` is known to be a function + res.then(next, /** @type {(_err: unknown) => void} */ function (_err) { // TODO: wth? err = err || _err; }); @@ -437,6 +478,7 @@ Test.prototype._end = function _end(err) { next(); }; +/** @type {import('./test').prototype._exit} */ Test.prototype._exit = function _exit() { if (this._plan !== undefined && !this._planError && this.assertCount !== this._plan) { this._planError = true; @@ -452,6 +494,7 @@ Test.prototype._exit = function _exit() { } }; +/** @type {import('./test').prototype._pendingAsserts} */ Test.prototype._pendingAsserts = function _pendingAsserts() { if (this._plan === undefined) { return 1; @@ -459,6 +502,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 +515,7 @@ Test.prototype._assert = function assert(ok, opts) { return; } + /** @type {import('../tape').Result} */ var res = { id: self.assertCount++, ok: actualOK, @@ -591,11 +636,13 @@ Test.prototype._assert = function assert(ok, opts) { self._planError = true; self.fail('plan != count', { expected: self._plan, + // @ts-expect-error `_planError` being false ensures `._plan` is a number actual: self._plan - pendingAsserts }); } }; +/** @type {import('./test').prototype.fail} */ Test.prototype.fail = function fail(msg, extra) { this._assert(false, { message: msg, @@ -604,6 +651,7 @@ Test.prototype.fail = function fail(msg, extra) { }); }; +/** @type {import('./test').prototype.pass} */ Test.prototype.pass = function pass(msg, extra) { this._assert(true, { message: msg, @@ -612,6 +660,7 @@ Test.prototype.pass = function pass(msg, extra) { }); }; +/** @type {import('./test').prototype.skip} */ Test.prototype.skip = function skip(msg, extra) { this._assert(true, { message: msg, @@ -621,6 +670,7 @@ Test.prototype.skip = function skip(msg, extra) { }); }; +/** @type {import('./test').prototype.ok} */ var testAssert = function assert(value, msg, extra) { // eslint-disable-line func-style this._assert(value, { message: defined(msg, 'should be truthy'), @@ -635,6 +685,7 @@ Test.prototype.ok = Test.prototype.assert = testAssert; +/** @type {import('./test').prototype.notOK} */ function notOK(value, msg, extra) { this._assert(!value, { message: defined(msg, 'should be falsy'), @@ -649,6 +700,7 @@ Test.prototype.notOk = Test.prototype.notok = notOK; +/** @type {import('./test').prototype.error} */ function error(err, msg, extra) { this._assert(!err, { message: defined(msg, String(err)), @@ -663,6 +715,7 @@ Test.prototype.error = Test.prototype.iferror = error; +/** @type {import('./test').prototype.equal} */ function strictEqual(a, b, msg, extra) { if (arguments.length < 2) { throw new TypeError('two arguments must be provided to compare'); @@ -683,6 +736,7 @@ Test.prototype.equal = Test.prototype.is = strictEqual; +/** @type {import('./test').prototype.notEqual} */ function notStrictEqual(a, b, msg, extra) { if (arguments.length < 2) { throw new TypeError('two arguments must be provided to compare'); @@ -707,6 +761,7 @@ Test.prototype.notEqual = Test.prototype.not = notStrictEqual; +/** @type {import('./test').prototype.looseEqual} */ function looseEqual(a, b, msg, extra) { if (arguments.length < 2) { throw new TypeError('two arguments must be provided to compare'); @@ -724,6 +779,7 @@ Test.prototype.looseEqual = Test.prototype.looseEquals = looseEqual; +/** @type {import('./test').prototype.notLooseEqual} */ function notLooseEqual(a, b, msg, extra) { if (arguments.length < 2) { throw new TypeError('two arguments must be provided to compare'); @@ -740,6 +796,7 @@ Test.prototype.notLooseEqual = Test.prototype.notLooseEquals = notLooseEqual; +/** @type {import('./test').prototype.deepEqual} */ function tapeDeepEqual(a, b, msg, extra) { if (arguments.length < 2) { throw new TypeError('two arguments must be provided to compare'); @@ -758,6 +815,7 @@ Test.prototype.deepEqual = Test.prototype.same = tapeDeepEqual; +/** @type {import('./test').prototype.notDeepEqual} */ function notDeepEqual(a, b, msg, extra) { if (arguments.length < 2) { throw new TypeError('two arguments must be provided to compare'); @@ -781,6 +839,7 @@ Test.prototype.notDeepEqual = Test.prototype.isInequivalent = notDeepEqual; +/** @type {import('./test').prototype.deepLooseEqual} */ function deepLooseEqual(a, b, msg, extra) { if (arguments.length < 2) { throw new TypeError('two arguments must be provided to compare'); @@ -797,6 +856,7 @@ function deepLooseEqual(a, b, msg, extra) { Test.prototype.deepLooseEqual = deepLooseEqual; +/** @type {import('./test').prototype.notDeepLooseEqual} */ function notDeepLooseEqual(a, b, msg, extra) { if (arguments.length < 2) { throw new TypeError('two arguments must be provided to compare'); @@ -812,18 +872,21 @@ function notDeepLooseEqual(a, b, msg, extra) { Test.prototype.notDeepLooseEqual = notDeepLooseEqual; +/** @type {import('./test').prototype.throws} */ 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 }; + // @ts-expect-error TS doesn't understand that `Object(err) === err` narrows `err` to `object` if (Object(err) === err && 'message' in err && (!isEnumerable(err, 'message') || !hasOwn(err, 'message'))) { try { var message = err.message; @@ -833,9 +896,11 @@ Test.prototype['throws'] = function (fn, expected, msg, extra) { } } + /** @type {typeof caught | boolean} */ var passed = caught; if (caught) { + // @ts-expect-error TS complains because `{}.message` isn't a thing, but Errors have `.message` if (typeof expected === 'string' && caught.error && caught.error.message === expected) { throw new TypeError('The "error/message" argument is ambiguous. The error message ' + inspect(expected) + ' is identical to the message.'); } @@ -859,10 +924,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 +952,7 @@ Test.prototype['throws'] = function (fn, expected, msg, extra) { }); }; +/** @type {import('./test').prototype.doesNotThrow} */ Test.prototype.doesNotThrow = function doesNotThrow(fn, expected, msg, extra) { if (typeof expected === 'string') { msg = expected; @@ -905,6 +974,7 @@ Test.prototype.doesNotThrow = function doesNotThrow(fn, expected, msg, extra) { }); }; +/** @type {import('./test').prototype.match} */ Test.prototype.match = function match(string, regexp, msg, extra) { if (!isRegExp(regexp)) { this._assert(false, { @@ -938,6 +1008,7 @@ Test.prototype.match = function match(string, regexp, msg, extra) { } }; +/** @type {import('./test').prototype.doesNotMatch} */ Test.prototype.doesNotMatch = function doesNotMatch(string, regexp, msg, extra) { if (!isRegExp(regexp)) { this._assert(false, { @@ -971,10 +1042,12 @@ Test.prototype.doesNotMatch = function doesNotMatch(string, regexp, msg, extra) } }; +/** @type {import('./test').skip} */ // eslint-disable-next-line no-unused-vars Test.skip = function skip(name_, _opts, _cb) { var args = getTestArgs.apply(null, arguments); args.opts.skip = true; + // @ts-expect-error TODO FIXME: no idea why TS errors here return new Test(args.name, args.opts, args.cb); }; diff --git a/package.json b/package.json index 1752bdf5..1309fd66 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,31 @@ "string.prototype.trim": "^1.2.8" }, "devDependencies": { + "@arethetypeswrong/cli": "^0.15.0", "@ljharb/eslint-config": "^21.1.0", + "@types/array.prototype.every": "^1.1.1", + "@types/array.prototype.find": "^2.2.0", + "@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/eslint": "~8.4", + "@types/falafel": "^2.2.2", + "@types/for-each": "^0.3.3", + "@types/get-package-type": "^0.1.0", + "@types/glob": "^8.1.0", + "@types/inherits": "^0.0.33", + "@types/is-regex": "^1.0.2", + "@types/js-yaml": "^4.0.9", + "@types/minimist": "^1.2.5", + "@types/mock-property": "^1.0.2", + "@types/object-inspect": "^1.8.4", + "@types/object-is": "^1.1.0", + "@types/object-keys": "^1.0.3", + "@types/resolve": "^1.20.6", + "@types/string.prototype.trim": "^1.2.0", + "@types/tape": "^5.6.4", "array.prototype.flatmap": "^1.3.2", "aud": "^2.0.4", "auto-changelog": "^2.4.0", @@ -63,7 +87,8 @@ "nyc": "^10.3.2", "safe-publish-latest": "^2.0.0", "tap": "^8.0.1", - "tap-parser": "^5.4.0" + "tap-parser": "^5.4.0", + "typescript": "next" }, "scripts": { "prepack": "npmignore --auto --commentLines=autogenerated", @@ -76,6 +101,7 @@ "eclint:windows": "eclint check *.js", "prelint": "npm-run-posix-or-windows eclint", "lint": "eslint --ext .js,.cjs,.mjs . bin/*", + "postlint": "tsc -P . && attw -P", "pretest": "npm run lint", "test": "npm-run-posix-or-windows tests-only", "posttest": "aud --production", diff --git a/tape.d.ts b/tape.d.ts new file mode 100644 index 00000000..8ec4ea9a --- /dev/null +++ b/tape.d.ts @@ -0,0 +1,92 @@ +export type Fn = (...args: unknown[]) => unknown; + +export type Options = { + skip?: boolean; + todo?: boolean; + timeout?: number; + objectPrintDepth?: number; +}; + +import { EventEmitter } from 'events'; + +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 WrappedCall = ReturnCall | ThrowCall; + +import mockProperty = require('mock-property'); + +export type RestoreFunction = Exclude, undefined>; + +export type WrapResults = { + (): WrappedCall[]; + restore?: RestoreFunction; +}; + +export type WrappedFn = { (): T; calls?: WrappedCall[] }; + +export type WrapObject = { + __proto__: null; + wrapped: WrappedFn; + calls: WrappedCall[]; + results: WrapResults; +}; + +export type WrapFunction = (original: undefined | T) => WrapObject; + +export type InterceptResults = { + (): Call[]; + restore: RestoreFunction; +} + +export type Operator = string; + +export type Extra = { + exiting?: boolean; + skip?: unknown; + todo?: unknown; + message?: string; + operator?: Operator; + actual?: unknown; + expected?: unknown; + error?: unknown; +}; + +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 type StreamOptions = { + objectMode?: boolean; +}; \ No newline at end of file diff --git a/test/common.js b/test/common.js index 92784335..408f6ab9 100644 --- a/test/common.js +++ b/test/common.js @@ -5,6 +5,7 @@ var spawn = require('child_process').spawn; var concat = require('concat-stream'); var yaml = require('js-yaml'); +/** @type {(body: string, includeStack: boolean) => import('../tape').Result} */ module.exports.getDiag = function (body, includeStack) { var yamlStart = body.indexOf(' ---'); var yamlEnd = body.indexOf(' ...\n'); @@ -38,6 +39,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 +64,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) { @@ -97,6 +100,7 @@ module.exports.stripFullStack = function (output) { ).split(/\r?\n/g); }; +/** @type {(folderName: string, fileName: string, cb: import('../tape').Fn) => void} */ module.exports.runProgram = function (folderName, fileName, cb) { var result = { stdout: null, diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..9dac7d05 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,106 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "resolveJsonModule": true, /* Enable importing .json files */ + // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ + "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + "strictBindCallApply": false, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "exclude": [ + "coverage/**", + "example/**", + "test/**/*", + ], +}