Skip to content

Commit e3d569e

Browse files
committed
Implement TypeScript types via JSDoc comments.
1 parent 3e09996 commit e3d569e

28 files changed

+351
-340
lines changed

.eslintrc.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
{
2-
"extends": ["env"]
2+
"extends": ["env"],
3+
"settings": {
4+
"jsdoc": {
5+
"mode": "typescript"
6+
}
7+
}
38
}

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"typescript.disableAutomaticTypeAcquisition": true,
3+
"typescript.enablePromptUseWorkspaceTsdk": true,
4+
"typescript.tsdk": "node_modules/typescript/lib"
5+
}

CliError.mjs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
// @ts-check
2+
13
/**
24
* A CLI error. Useful for anticipated CLI errors (such as invalid CLI
35
* arguments) that don’t need to be displayed with a stack trace, vs unexpected
46
* internal errors.
5-
* @kind class
6-
* @name CliError
7-
* @param {string} message Error message.
8-
* @ignore
97
*/
108
export default class CliError extends Error {
11-
constructor(...args) {
12-
super(...args);
9+
/** @param {string} message Error message. */
10+
constructor(message) {
11+
if (typeof message !== "string")
12+
throw new TypeError("Argument 1 `message` must be a string.");
13+
14+
super(message);
15+
1316
this.name = this.constructor.name;
1417
}
1518
}

CliError.test.mjs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// @ts-check
2+
3+
import { strictEqual, throws } from "assert";
4+
import CliError from "./CliError.mjs";
5+
6+
/**
7+
* Adds `CliError` tests.
8+
* @param {import("test-director").default} tests Test director.
9+
*/
10+
export default (tests) => {
11+
tests.add("`CliError` with argument 1 `message` not a string.", () => {
12+
throws(() => {
13+
new CliError(
14+
// @ts-expect-error Testing invalid.
15+
true
16+
);
17+
}, new TypeError("Argument 1 `message` must be a string."));
18+
});
19+
20+
tests.add("`CliError` with arguments valid.", () => {
21+
const message = "Message.";
22+
const error = new CliError(message);
23+
24+
strictEqual(error instanceof Error, true);
25+
strictEqual(error.name, "CliError");
26+
strictEqual(error.message, message);
27+
});
28+
};

analyseCoverage.mjs

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
1+
// @ts-check
2+
13
import fs from "fs";
24
import { join } from "path";
35
import { fileURLToPath } from "url";
46
import v8Coverage from "@bcoe/v8-coverage";
57
import sourceRange from "./sourceRange.mjs";
68

79
/**
8-
* Analyzes [Node.js generated V8 JavaScript code coverage data](https://nodejs.org/api/cli.html#cli_node_v8_coverage_dir)
10+
* Analyzes
11+
* [Node.js generated V8 JavaScript code coverage data](https://nodejs.org/api/cli.html#cli_node_v8_coverage_dir)
912
* in a directory; useful for reporting.
10-
* @kind function
11-
* @name analyseCoverage
1213
* @param {string} coverageDirPath Code coverage data directory path.
1314
* @returns {Promise<CoverageAnalysis>} Resolves the coverage analysis.
14-
* @example <caption>How to import.</caption>
15-
* ```js
16-
* import analyseCoverage from "coverage-node/analyseCoverage.mjs";
17-
* ```
1815
*/
1916
export default async function analyseCoverage(coverageDirPath) {
2017
if (typeof coverageDirPath !== "string")
@@ -29,6 +26,7 @@ export default async function analyseCoverage(coverageDirPath) {
2926
fs.promises
3027
.readFile(join(coverageDirPath, fileName), "utf8")
3128
.then((coverageFileJson) => {
29+
/** @type {import("@bcoe/v8-coverage").ProcessCov} */
3230
const { result } = JSON.parse(coverageFileJson);
3331
return {
3432
// For performance, filtering happens as early as possible.
@@ -53,18 +51,11 @@ export default async function analyseCoverage(coverageDirPath) {
5351
await Promise.all(filteredProcessCoverages)
5452
);
5553

56-
// The analysis will only include info useful for reporting.
54+
/** @type {CoverageAnalysis} */
5755
const analysis = {
58-
// Total number of files.
5956
filesCount: 0,
60-
61-
// Fully covered file paths.
6257
covered: [],
63-
64-
// File paths and ignored ranges.
6558
ignored: [],
66-
67-
// File paths and uncovered ranges.
6859
uncovered: [],
6960
};
7061

@@ -83,17 +74,17 @@ export default async function analyseCoverage(coverageDirPath) {
8374
const uncovered = [];
8475

8576
for (const range of uncoveredRanges) {
86-
const { ignore, ...rangeDetails } = sourceRange(
77+
const sourceCodeRange = sourceRange(
8778
source,
8879
range.startOffset,
8980
// The coverage data end offset is the first character after the
90-
// range. For reporting to a user, it’s better to show the range
91-
// as only the included characters.
81+
// range. For reporting to a user, it’s better to show the range as
82+
// only the included characters.
9283
range.endOffset - 1
9384
);
9485

95-
if (ignore) ignored.push(rangeDetails);
96-
else uncovered.push(rangeDetails);
86+
if (sourceCodeRange.ignore) ignored.push(sourceCodeRange);
87+
else uncovered.push(sourceCodeRange);
9788
}
9889

9990
if (ignored.length) analysis.ignored.push({ path, ranges: ignored });
@@ -104,3 +95,21 @@ export default async function analyseCoverage(coverageDirPath) {
10495

10596
return analysis;
10697
}
98+
99+
/**
100+
* [Node.js generated V8 JavaScript code coverage data](https://nodejs.org/api/cli.html#cli_node_v8_coverage_dir)
101+
* analysis; useful for reporting.
102+
* @typedef {object} CoverageAnalysis
103+
* @prop {number} filesCount Number of files analyzed.
104+
* @prop {Array<string>} covered Covered file absolute paths.
105+
* @prop {Array<SourceCodeRanges>} ignored Ignored source code ranges.
106+
* @prop {Array<SourceCodeRanges>} uncovered Uncovered source code ranges.
107+
*/
108+
109+
/**
110+
* A source code file with ranges of interest.
111+
* @typedef {object} SourceCodeRanges
112+
* @prop {string} path File absolute path.
113+
* @prop {Array<import("./sourceRange.mjs").SourceCodeRange>} ranges Ranges of
114+
* interest.
115+
*/

analyseCoverage.test.mjs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// @ts-check
2+
13
import { deepStrictEqual, rejects } from "assert";
24
import { spawn } from "child_process";
35
import fs from "fs";
@@ -6,12 +8,19 @@ import disposableDirectory from "disposable-directory";
68
import analyseCoverage from "./analyseCoverage.mjs";
79
import childProcessPromise from "./childProcessPromise.mjs";
810

11+
/**
12+
* Adds `reportCliError` tests.
13+
* @param {import("test-director").default} tests Test director.
14+
*/
915
export default (tests) => {
1016
tests.add(
1117
"`reportCliError` with first argument `coverageDirPath` not a string.",
1218
async () => {
1319
await rejects(
14-
analyseCoverage(true),
20+
analyseCoverage(
21+
// @ts-expect-error Testing invalid.
22+
true
23+
),
1524
new TypeError("First argument `coverageDirPath` must be a string.")
1625
);
1726
}
@@ -177,6 +186,7 @@ export default (tests) => {
177186
path: filePath,
178187
ranges: [
179188
{
189+
ignore: false,
180190
start: {
181191
offset: 0,
182192
line: 1,
@@ -189,6 +199,7 @@ export default (tests) => {
189199
},
190200
},
191201
{
202+
ignore: false,
192203
start: {
193204
offset: 17,
194205
line: 1,

changelog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,19 @@
1010
- Removed `./package` from the package `exports` field; the full `package.json` filename must be used in a `require` path.
1111
- Removed the package main index module; deep imports must be used.
1212
- Shortened public module deep import paths, removing the `/public/`.
13+
- Implemented TypeScript types via JSDoc comments.
1314

1415
### Patch
1516

1617
- Simplified package scripts.
18+
- Check TypeScript types via a new package `types` script.
19+
- Fixed various type related issues.
1720
- Also run GitHub Actions CI with Node.js v17, and drop v15.
1821
- Configured Prettier option `singleQuote` to the default, `false`.
1922
- Reorganized the test file structure.
2023
- Renamed imports in the test index module.
24+
- Added `CliError` class tests.
25+
- Runtime type check `CLiError` constructor arguments.
2126
- Readme tweaks.
2227

2328
## 5.0.1

childProcessPromise.mjs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
// @ts-check
2+
13
import { ChildProcess } from "child_process";
24

35
/**
46
* Promisifies a Node.js child process.
5-
* @kind function
6-
* @name childProcessPromise
77
* @param {ChildProcess} childProcess Node.js child process.
8-
* @returns {Promise<{exitCode: number, signal: string}>} Resolves the exit code if the child exited on its own, or the signal by which the child process was terminated.
9-
* @ignore
8+
* @returns {Promise<{
9+
* exitCode: number | null,
10+
* signal: NodeJS.Signals | null
11+
* }>} Resolves the exit code if the child exited on its own, or the signal by
12+
* which the child process was terminated.
1013
*/
1114
export default async function childProcessPromise(childProcess) {
1215
if (!(childProcess instanceof ChildProcess))

childProcessPromise.test.mjs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
1+
// @ts-check
2+
13
import { rejects } from "assert";
24
import childProcessPromise from "./childProcessPromise.mjs";
35

6+
/**
7+
* Adds `childProcessPromise` tests.
8+
* @param {import("test-director").default} tests Test director.
9+
*/
410
export default (tests) => {
511
tests.add(
612
"`childProcessPromise` with first argument `childProcess` not a `ChildProcess` instance.",
713
async () => {
814
await rejects(
9-
childProcessPromise(true),
15+
childProcessPromise(
16+
// @ts-expect-error Testing invalid.
17+
true
18+
),
1019
new TypeError(
1120
"First argument `childProcess` must be a `ChildProcess` instance."
1221
)

coverage-node.mjs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/usr/bin/env node
2+
// @ts-check
23

34
import { spawn } from "child_process";
45
import disposableDirectory from "disposable-directory";
@@ -15,10 +16,7 @@ import reportCoverage from "./reportCoverage.mjs";
1516
* Powers the `coverage-node` CLI. Runs Node.js with the given arguments and
1617
* coverage enabled. An analysis of the coverage is reported to the console, and
1718
* if coverage isn’t complete the process exits with code `1`.
18-
* @kind function
19-
* @name coverageNode
2019
* @returns {Promise<void>} Resolves when all work is complete.
21-
* @ignore
2220
*/
2321
async function coverageNode() {
2422
try {
@@ -43,7 +41,7 @@ async function coverageNode() {
4341
const analysis = await analyseCoverage(tempDirPath);
4442
reportCoverage(analysis);
4543
if (analysis.uncovered.length) process.exitCode = 1;
46-
} else process.exitCode = exitCode;
44+
} else if (exitCode !== null) process.exitCode = exitCode;
4745
});
4846
// coverage ignore next line
4947
} else {
@@ -57,7 +55,7 @@ async function coverageNode() {
5755
`Skipped code coverage as Node.js is ${process.version}, v${minNodeVersion.major}.${minNodeVersion.minor}.${minNodeVersion.patch}+ is supported.`
5856
)}\n`
5957
);
60-
else process.exitCode = exitCode;
58+
else if (exitCode !== null) process.exitCode = exitCode;
6159
}
6260
} catch (error) {
6361
reportCliError(

0 commit comments

Comments
 (0)