From 740eeed730177960fbc6c187cd80713d39bf3c67 Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Wed, 28 Jan 2026 17:50:14 -0800 Subject: [PATCH 1/4] convert mocha to node:test --- package-lock.json | 235 ---------------------------------- package.json | 5 +- test/baselines.js | 1 + test/biblio.js | 1 + test/build.js | 1 + test/clauseIds.js | 3 +- test/cli.js | 8 +- test/errors.js | 1 + test/expr-parser.js | 1 + test/formatter.js | 1 + test/lint-algorithms.js | 1 + test/lint-spelling.js | 1 + test/lint-tags.js | 1 + test/lint-variable-use-def.js | 1 + test/lint.js | 1 + test/type-parser.js | 1 + test/typecheck.js | 1 + 17 files changed, 20 insertions(+), 244 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4f6244da..c5a0e6c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,6 @@ "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", - "mocha": "^5.2.0", "prettier": "^3.2.3", "safe-publish-latest": "^1.1.4", "typescript": "^5.5.4" @@ -804,12 +803,6 @@ "node": ">=8" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -1120,15 +1113,6 @@ "node": ">=0.4.0" } }, - "node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -1734,15 +1718,6 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, - "engines": { - "node": ">=4.x" - } - }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -1775,15 +1750,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true, - "bin": { - "he": "bin/he" - } - }, "node_modules/highlight.js": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.0.1.tgz", @@ -2201,101 +2167,6 @@ "node": "*" } }, - "node_modules/mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", - "dev": true, - "dependencies": { - "minimist": "0.0.8" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mkdirp/node_modules/minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "node_modules/mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "dev": true, - "dependencies": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/mocha/node_modules/commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "node_modules/mocha/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -3937,12 +3808,6 @@ "fill-range": "^7.0.1" } }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -4177,12 +4042,6 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -4625,12 +4484,6 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -4651,12 +4504,6 @@ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", "dev": true }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, "highlight.js": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.0.1.tgz", @@ -4970,88 +4817,6 @@ "brace-expansion": "^1.1.7" } }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - } - } - }, - "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "dev": true, - "requires": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "dependencies": { - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", diff --git a/package.json b/package.json index 42c295f9..b75fc73c 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ "build-spec": "mkdir -p docs && node bin/ecmarkup.js spec/index.html docs/index.html --assets-dir=docs && cp ecma-logo.png docs/", "prepack": "safe-publish-latest && npm run build-release", "format-spec": "node bin/emu-format.js --write spec/index.html", - "test": "mocha", - "test-baselines": "mocha --timeout 10000 test/baselines.js", + "test": "node --test", + "test-baselines": "node --test --test-timeout 10000 test/baselines.js", "test-declarations": "tsc -p tsconfig.test.json", "update-baselines": "npm --update-baselines run test-baselines", "pretest-published-files": "rm -rf \"ecmarkup-$npm_package_version.tgz\" package", @@ -77,7 +77,6 @@ "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", - "mocha": "^5.2.0", "prettier": "^3.2.3", "safe-publish-latest": "^1.1.4", "typescript": "^5.5.4" diff --git a/test/baselines.js b/test/baselines.js index 19005856..a8c289a1 100644 --- a/test/baselines.js +++ b/test/baselines.js @@ -1,5 +1,6 @@ 'use strict'; const assert = require('assert'); +const { describe, it } = require('node:test'); const fs = require('fs'); const path = require('path'); diff --git a/test/biblio.js b/test/biblio.js index 68a8d2cb..2969299b 100644 --- a/test/biblio.js +++ b/test/biblio.js @@ -1,6 +1,7 @@ 'use strict'; const assert = require('assert'); +const { describe, it, beforeEach } = require('node:test'); const { JSDOM } = require('jsdom'); const BiblioModule = require('../lib/Biblio'); const build = require('../lib/ecmarkup').build; diff --git a/test/build.js b/test/build.js index 4023c967..cfa75ec9 100644 --- a/test/build.js +++ b/test/build.js @@ -1,5 +1,6 @@ 'use strict'; const assert = require('assert'); +const { describe, it } = require('node:test'); const build = require('../lib/ecmarkup').build; diff --git a/test/clauseIds.js b/test/clauseIds.js index d34a097a..5e7c1956 100644 --- a/test/clauseIds.js +++ b/test/clauseIds.js @@ -1,6 +1,7 @@ 'use strict'; const assert = require('assert'); +const { describe, it, beforeEach } = require('node:test'); const sectionNums = require('../lib/clauseNums').default; describe('clause id generation', () => { @@ -10,7 +11,7 @@ describe('clause id generation', () => { iter = sectionNums({ opts: {} }); }); - specify('generating clause ids', () => { + it('generating clause ids', () => { const CLAUSE = { nodeName: 'EMU-CLAUSE', hasAttribute: () => false }; const ANNEX = { nodeName: 'EMU-ANNEX', hasAttribute: () => false }; assert.strictEqual(iter.next([], CLAUSE), '1'); diff --git a/test/cli.js b/test/cli.js index 2a5d6cbf..3b16361d 100644 --- a/test/cli.js +++ b/test/cli.js @@ -1,12 +1,11 @@ 'use strict'; const assert = require('assert'); +const { describe, it } = require('node:test'); const execSync = require('child_process').execSync; const execPath = process.execPath.includes(' ') ? `"${process.execPath}"` : process.execPath; -describe('ecmarkup#cli', function () { - this.timeout(4000); // Increase timeout for CLI tests as startup time can be variable. - +describe('ecmarkup#cli', { timeout: 4000 }, () => { it('exits cleanly on success', () => { execSync(`${execPath} ./bin/ecmarkup.js test/baselines/sources/example.html`, { encoding: 'utf8', @@ -39,8 +38,7 @@ describe('ecmarkup#cli', function () { }); }); -describe('emu-format --check', function () { - this.timeout(4000); +describe('emu-format --check', { timeout: 4000 }, () => { it('exits cleanly if the file needs no formatting', () => { execSync(`${execPath} ./bin/emu-format.js --check test/format-good.html`, { diff --git a/test/errors.js b/test/errors.js index 9689c54c..4633a89a 100644 --- a/test/errors.js +++ b/test/errors.js @@ -1,6 +1,7 @@ 'use strict'; let assert = require('assert'); +let { describe, it } = require('node:test'); let emu = require('../lib/ecmarkup'); let { diff --git a/test/expr-parser.js b/test/expr-parser.js index 490e1067..a6ed0dcd 100644 --- a/test/expr-parser.js +++ b/test/expr-parser.js @@ -1,5 +1,6 @@ 'use strict'; +let { describe, it, before } = require('node:test'); let { assertLint, positioned, diff --git a/test/formatter.js b/test/formatter.js index f232b4b4..7db13554 100644 --- a/test/formatter.js +++ b/test/formatter.js @@ -1,6 +1,7 @@ 'use strict'; let assert = require('assert'); +let { describe, it } = require('node:test'); // the more common `dedent` has bug: https://github.com/dmnd/dedent/issues/24 let dedent = require('dedent-js'); diff --git a/test/lint-algorithms.js b/test/lint-algorithms.js index 353f4f4f..26bc42fd 100644 --- a/test/lint-algorithms.js +++ b/test/lint-algorithms.js @@ -1,5 +1,6 @@ 'use strict'; +let { describe, it } = require('node:test'); let { assertLint, assertLintFree, lintLocationMarker: M, positioned } = require('./utils.js'); const nodeType = 'emu-alg'; diff --git a/test/lint-spelling.js b/test/lint-spelling.js index 7e396b6b..a9d304ae 100644 --- a/test/lint-spelling.js +++ b/test/lint-spelling.js @@ -1,5 +1,6 @@ 'use strict'; +let { describe, it } = require('node:test'); let { assertLint, assertLintFree, positioned, lintLocationMarker: M } = require('./utils.js'); describe('spelling', () => { diff --git a/test/lint-tags.js b/test/lint-tags.js index 72a9849d..94dc936f 100644 --- a/test/lint-tags.js +++ b/test/lint-tags.js @@ -1,5 +1,6 @@ 'use strict'; +let { describe, it } = require('node:test'); let { assertLint, assertLintFree, positioned, lintLocationMarker: M } = require('./utils.js'); describe('tags', () => { diff --git a/test/lint-variable-use-def.js b/test/lint-variable-use-def.js index ee696fce..6dfa4795 100644 --- a/test/lint-variable-use-def.js +++ b/test/lint-variable-use-def.js @@ -1,5 +1,6 @@ 'use strict'; +let { describe, it } = require('node:test'); let { assertLint, assertLintFree, diff --git a/test/lint.js b/test/lint.js index 440f94f6..296bd440 100644 --- a/test/lint.js +++ b/test/lint.js @@ -1,5 +1,6 @@ 'use strict'; +let { describe, it } = require('node:test'); let { assertLint, assertLintFree, diff --git a/test/type-parser.js b/test/type-parser.js index a59ad324..61d46bc5 100644 --- a/test/type-parser.js +++ b/test/type-parser.js @@ -1,5 +1,6 @@ 'use strict'; +let { describe, it } = require('node:test'); let { assertError, positioned, lintLocationMarker: M } = require('./utils.js'); let { TypeParser } = require('../lib/type-parser.js'); let assert = require('assert'); diff --git a/test/typecheck.js b/test/typecheck.js index 384ae86e..d12e91fb 100644 --- a/test/typecheck.js +++ b/test/typecheck.js @@ -1,5 +1,6 @@ 'use strict'; +let { describe, it, before } = require('node:test'); let { assertLint, assertLintFree, From 58d0a60e933268dd6491a71ed579b03adf7cb067 Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Wed, 28 Jan 2026 18:45:49 -0800 Subject: [PATCH 2/4] convert tests to esm --- test/baselines.js | 15 +++++++-------- test/biblio.js | 12 +++++------- test/build.js | 7 +++---- test/clauseIds.js | 9 ++++----- test/cli.js | 7 +++---- test/errors.js | 14 ++++++-------- test/expr-parser.js | 16 +++++++--------- test/formatter.js | 10 ++++------ test/lint-algorithms.js | 6 ++---- test/lint-spelling.js | 6 ++---- test/lint-tags.js | 6 ++---- test/lint-variable-use-def.js | 10 ++++------ test/lint.js | 10 ++++------ test/package.json | 3 +++ test/type-parser.js | 10 ++++------ test/typecheck.js | 10 ++++------ test/utils.js | 8 +++----- 17 files changed, 67 insertions(+), 92 deletions(-) create mode 100644 test/package.json diff --git a/test/baselines.js b/test/baselines.js index a8c289a1..c45a185f 100644 --- a/test/baselines.js +++ b/test/baselines.js @@ -1,10 +1,11 @@ -'use strict'; -const assert = require('assert'); -const { describe, it } = require('node:test'); -const fs = require('fs'); -const path = require('path'); +import assert from 'assert'; +import { describe, it } from 'node:test'; +import fs from 'fs'; +import path from 'path'; -const emu = require('../lib/ecmarkup'); +import * as emu from '../lib/ecmarkup.js'; + +import ecma262biblio from './ecma262biblio.json' with { type: 'json' }; const SOURCES_DIR = 'test/baselines/sources/'; const LOCAL_DIR = 'test/baselines/generated-local/'; @@ -14,8 +15,6 @@ const files = fs .readdirSync(SOURCES_DIR) .filter(f => f.endsWith('.html') && !f.endsWith('.bad.html')); -const ecma262biblio = require('./ecma262biblio.json'); - function build(file, options) { return emu.build( file, diff --git a/test/biblio.js b/test/biblio.js index 2969299b..de1ed960 100644 --- a/test/biblio.js +++ b/test/biblio.js @@ -1,10 +1,8 @@ -'use strict'; - -const assert = require('assert'); -const { describe, it, beforeEach } = require('node:test'); -const { JSDOM } = require('jsdom'); -const BiblioModule = require('../lib/Biblio'); -const build = require('../lib/ecmarkup').build; +import assert from 'assert'; +import { describe, it, beforeEach } from 'node:test'; +import { JSDOM } from 'jsdom'; +import BiblioModule from '../lib/Biblio.js'; +import { build } from '../lib/ecmarkup.js'; const Biblio = BiblioModule.default; const location = 'https://tc39.github.io/ecma262/'; diff --git a/test/build.js b/test/build.js index cfa75ec9..f5a845e1 100644 --- a/test/build.js +++ b/test/build.js @@ -1,8 +1,7 @@ -'use strict'; -const assert = require('assert'); -const { describe, it } = require('node:test'); +import assert from 'assert'; +import { describe, it } from 'node:test'; -const build = require('../lib/ecmarkup').build; +import { build } from '../lib/ecmarkup.js'; const doc = '

hi

'; diff --git a/test/clauseIds.js b/test/clauseIds.js index 5e7c1956..4f1c724d 100644 --- a/test/clauseIds.js +++ b/test/clauseIds.js @@ -1,8 +1,7 @@ -'use strict'; - -const assert = require('assert'); -const { describe, it, beforeEach } = require('node:test'); -const sectionNums = require('../lib/clauseNums').default; +import assert from 'assert'; +import { describe, it, beforeEach } from 'node:test'; +import clauseNumsModule from '../lib/clauseNums.js'; +const sectionNums = clauseNumsModule.default; describe('clause id generation', () => { let iter; diff --git a/test/cli.js b/test/cli.js index 3b16361d..a0e8656c 100644 --- a/test/cli.js +++ b/test/cli.js @@ -1,8 +1,7 @@ -'use strict'; +import assert from 'assert'; +import { describe, it } from 'node:test'; +import { execSync } from 'child_process'; -const assert = require('assert'); -const { describe, it } = require('node:test'); -const execSync = require('child_process').execSync; const execPath = process.execPath.includes(' ') ? `"${process.execPath}"` : process.execPath; describe('ecmarkup#cli', { timeout: 4000 }, () => { diff --git a/test/errors.js b/test/errors.js index 4633a89a..20266469 100644 --- a/test/errors.js +++ b/test/errors.js @@ -1,17 +1,15 @@ -'use strict'; +import assert from 'assert'; +import { describe, it } from 'node:test'; +import * as emu from '../lib/ecmarkup.js'; -let assert = require('assert'); -let { describe, it } = require('node:test'); -let emu = require('../lib/ecmarkup'); - -let { - lintLocationMarker: M, +import { + lintLocationMarker as M, positioned, multipositioned, assertError, assertErrorFree, getBiblio, -} = require('./utils.js'); +} from './utils.js'; describe('errors', () => { it('no contributors', async () => { diff --git a/test/expr-parser.js b/test/expr-parser.js index a6ed0dcd..2cda68c2 100644 --- a/test/expr-parser.js +++ b/test/expr-parser.js @@ -1,16 +1,14 @@ -'use strict'; - -let { describe, it, before } = require('node:test'); -let { +import { describe, it, before } from 'node:test'; +import { assertLint, positioned, - lintLocationMarker: M, + lintLocationMarker as M, assertLintFree, getBiblio, -} = require('./utils.js'); -let { parse: parseExpr } = require('../lib/expr-parser.js'); -let assert = require('assert'); -let { parseFragment } = require('ecmarkdown'); +} from './utils.js'; +import { parse as parseExpr } from '../lib/expr-parser.js'; +import assert from 'assert'; +import { parseFragment } from 'ecmarkdown'; describe('expression parsing', () => { describe('valid', () => { diff --git a/test/formatter.js b/test/formatter.js index 7db13554..ef1704a3 100644 --- a/test/formatter.js +++ b/test/formatter.js @@ -1,11 +1,9 @@ -'use strict'; - -let assert = require('assert'); -let { describe, it } = require('node:test'); +import assert from 'assert'; +import { describe, it } from 'node:test'; // the more common `dedent` has bug: https://github.com/dmnd/dedent/issues/24 -let dedent = require('dedent-js'); +import dedent from 'dedent-js'; -let { printDocument } = require('../lib/formatter/ecmarkup'); +import { printDocument } from '../lib/formatter/ecmarkup.js'; describe('document formatting', () => { it('indentation', async () => { diff --git a/test/lint-algorithms.js b/test/lint-algorithms.js index 26bc42fd..8098d7e8 100644 --- a/test/lint-algorithms.js +++ b/test/lint-algorithms.js @@ -1,7 +1,5 @@ -'use strict'; - -let { describe, it } = require('node:test'); -let { assertLint, assertLintFree, lintLocationMarker: M, positioned } = require('./utils.js'); +import { describe, it } from 'node:test'; +import { assertLint, assertLintFree, lintLocationMarker as M, positioned } from './utils.js'; const nodeType = 'emu-alg'; diff --git a/test/lint-spelling.js b/test/lint-spelling.js index a9d304ae..c4c9643d 100644 --- a/test/lint-spelling.js +++ b/test/lint-spelling.js @@ -1,7 +1,5 @@ -'use strict'; - -let { describe, it } = require('node:test'); -let { assertLint, assertLintFree, positioned, lintLocationMarker: M } = require('./utils.js'); +import { describe, it } from 'node:test'; +import { assertLint, assertLintFree, positioned, lintLocationMarker as M } from './utils.js'; describe('spelling', () => { it('*this* object', async () => { diff --git a/test/lint-tags.js b/test/lint-tags.js index 94dc936f..1a01f09f 100644 --- a/test/lint-tags.js +++ b/test/lint-tags.js @@ -1,7 +1,5 @@ -'use strict'; - -let { describe, it } = require('node:test'); -let { assertLint, assertLintFree, positioned, lintLocationMarker: M } = require('./utils.js'); +import { describe, it } from 'node:test'; +import { assertLint, assertLintFree, positioned, lintLocationMarker as M } from './utils.js'; describe('tags', () => { it('unknown "emu-" tags', async () => { diff --git a/test/lint-variable-use-def.js b/test/lint-variable-use-def.js index 6dfa4795..3f2c74d6 100644 --- a/test/lint-variable-use-def.js +++ b/test/lint-variable-use-def.js @@ -1,13 +1,11 @@ -'use strict'; - -let { describe, it } = require('node:test'); -let { +import { describe, it } from 'node:test'; +import { assertLint, assertLintFree, positioned, - lintLocationMarker: M, + lintLocationMarker as M, getBiblio, -} = require('./utils.js'); +} from './utils.js'; describe('variables are declared and used appropriately', () => { describe('variables must be declared', () => { diff --git a/test/lint.js b/test/lint.js index 296bd440..b3c3ff56 100644 --- a/test/lint.js +++ b/test/lint.js @@ -1,13 +1,11 @@ -'use strict'; - -let { describe, it } = require('node:test'); -let { +import { describe, it } from 'node:test'; +import { assertLint, assertLintFree, positioned, - lintLocationMarker: M, + lintLocationMarker as M, getBiblio, -} = require('./utils.js'); +} from './utils.js'; describe('linting whole program', () => { describe('grammar validity', () => { diff --git a/test/package.json b/test/package.json new file mode 100644 index 00000000..3dbc1ca5 --- /dev/null +++ b/test/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/test/type-parser.js b/test/type-parser.js index 61d46bc5..2ca42903 100644 --- a/test/type-parser.js +++ b/test/type-parser.js @@ -1,9 +1,7 @@ -'use strict'; - -let { describe, it } = require('node:test'); -let { assertError, positioned, lintLocationMarker: M } = require('./utils.js'); -let { TypeParser } = require('../lib/type-parser.js'); -let assert = require('assert'); +import { describe, it } from 'node:test'; +import { assertError, positioned, lintLocationMarker as M } from './utils.js'; +import { TypeParser } from '../lib/type-parser.js'; +import assert from 'assert'; describe('type parsing', () => { describe('errors', () => { diff --git a/test/typecheck.js b/test/typecheck.js index d12e91fb..df9be02e 100644 --- a/test/typecheck.js +++ b/test/typecheck.js @@ -1,13 +1,11 @@ -'use strict'; - -let { describe, it, before } = require('node:test'); -let { +import { describe, it, before } from 'node:test'; +import { assertLint, assertLintFree, positioned, - lintLocationMarker: M, + lintLocationMarker as M, getBiblio, -} = require('./utils.js'); +} from './utils.js'; describe('typechecking completions', () => { let biblio; diff --git a/test/utils.js b/test/utils.js index 54c6c6af..9b95edf5 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,7 +1,5 @@ -'use strict'; - -let assert = require('assert'); -let emu = require('../lib/ecmarkup'); +import assert from 'assert'; +import * as emu from '../lib/ecmarkup.js'; let lintLocationMarker = {}; @@ -192,7 +190,7 @@ async function getBiblio(html) { return upstream.exportBiblio(); } -module.exports = { +export { lintLocationMarker, positioned, multipositioned, From 268c4f82343ab5803f59ed5f3d4e5caffadd57c8 Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Wed, 28 Jan 2026 19:21:37 -0800 Subject: [PATCH 3/4] convert tests to typescript --- .eslintrc.json | 36 ++++++- .github/workflows/check.yml | 3 + package-lock.json | 38 ++++++-- package.json | 3 +- src/.eslintrc.json | 28 ------ src/Biblio.ts | 2 +- src/Eqn.ts | 4 +- src/Spec.ts | 18 ++-- src/arg-parser.ts | 6 +- src/cli.ts | 6 +- src/external.d.ts | 5 +- src/formatter/cli.ts | 4 +- src/lint/collect-algorithm-diagnostics.ts | 4 +- src/utils.ts | 4 +- test/{baselines.js => baselines.ts} | 52 +++++----- test/{biblio.js => biblio.ts} | 67 ++++++++----- test/{build.js => build.ts} | 6 +- test/clauseIds.js | 27 ------ test/clauseIds.ts | 33 +++++++ test/{cli.js => cli.ts} | 1 - test/{errors.js => errors.ts} | 18 ++-- test/{expr-parser.js => expr-parser.ts} | 11 ++- test/{formatter.js => formatter.ts} | 8 +- ...{lint-algorithms.js => lint-algorithms.ts} | 2 +- test/{lint-spelling.js => lint-spelling.ts} | 2 +- test/{lint-tags.js => lint-tags.ts} | 2 +- ...le-use-def.js => lint-variable-use-def.ts} | 4 +- test/{lint.js => lint.ts} | 6 +- test/tsconfig.json | 14 +++ test/{type-parser.js => type-parser.ts} | 2 +- test/{typecheck.js => typecheck.ts} | 39 ++++---- test/{utils.js => utils.ts} | 96 ++++++++++++------- tsconfig.json | 1 + 33 files changed, 327 insertions(+), 225 deletions(-) delete mode 100644 src/.eslintrc.json rename test/{baselines.js => baselines.ts} (62%) rename test/{biblio.js => biblio.ts} (73%) rename test/{build.js => build.ts} (83%) delete mode 100644 test/clauseIds.js create mode 100644 test/clauseIds.ts rename test/{cli.js => cli.ts} (99%) rename test/{errors.js => errors.ts} (98%) rename test/{expr-parser.js => expr-parser.ts} (96%) rename test/{formatter.js => formatter.ts} (98%) rename test/{lint-algorithms.js => lint-algorithms.ts} (99%) rename test/{lint-spelling.js => lint-spelling.ts} (99%) rename test/{lint-tags.js => lint-tags.ts} (97%) rename test/{lint-variable-use-def.js => lint-variable-use-def.ts} (99%) rename test/{lint.js => lint.ts} (99%) create mode 100644 test/tsconfig.json rename test/{type-parser.js => type-parser.ts} (99%) rename test/{typecheck.js => typecheck.ts} (98%) rename test/{utils.js => utils.ts} (61%) diff --git a/.eslintrc.json b/.eslintrc.json index e095356b..0e0899c3 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -12,14 +12,41 @@ "node": true, "es6": true }, - "parser": "@typescript-eslint/parser", "parserOptions": { - "sourceType": "script", + "sourceType": "module", "ecmaVersion": 2020 }, "overrides": [ + { + "files": ["*.ts"], + "parser": "@typescript-eslint/parser", + "extends": [ + "plugin:@typescript-eslint/eslint-recommended" + ], + "parserOptions": { + "//": "NB project is relative to the directory you run eslint from, see https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/parser#parseroptionsproject", + "project": "./tsconfig.json" + }, + "rules": { + "@typescript-eslint/consistent-type-imports": "error", + "@typescript-eslint/no-use-before-define": [ + "error", + { + "functions": false, + "typedefs": false + } + ], + "@typescript-eslint/no-unnecessary-type-assertion": "error", + "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/no-explicit-any": "error", + "no-unused-vars": "off" + } + }, { "files": ["js/**/*"], + "parserOptions": { + "sourceType": "script" + }, "env": { "node": false, "browser": true, @@ -45,13 +72,12 @@ }, { "files": ["test/**/*"], - "env": { - "mocha": true + "parserOptions": { + "project": "./test/tsconfig.json" } } ], "rules": { - "@typescript-eslint/consistent-type-imports": "error", "prettier/prettier": "error", "arrow-body-style": "error", "prefer-arrow-callback": "error", diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 2c1d20d9..2c757b72 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -25,6 +25,9 @@ jobs: - name: Build run: npm run build + - name: Typecheck + run: npm run typecheck + - name: Test run: npm test diff --git a/package-lock.json b/package-lock.json index c5a0e6c6..f738563e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "@types/command-line-usage": "^5.0.2", "@types/js-yaml": "^3.12.1", "@types/jsdom": "^16.2.13", - "@types/node": "^13.1.8", + "@types/node": "^24.10.13", "@types/parse5": "^6.0.2", "@typescript-eslint/eslint-plugin": "^8.6.0", "@typescript-eslint/parser": "^8.6.0", @@ -427,10 +427,14 @@ } }, "node_modules/@types/node": { - "version": "13.13.52", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", - "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==", - "dev": true + "version": "24.10.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", + "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } }, "node_modules/@types/parse5": { "version": "6.0.2", @@ -2983,6 +2987,13 @@ "node": ">=8" } }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -3564,10 +3575,13 @@ } }, "@types/node": { - "version": "13.13.52", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", - "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==", - "dev": true + "version": "24.10.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", + "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", + "dev": true, + "requires": { + "undici-types": "~7.16.0" + } }, "@types/parse5": { "version": "6.0.2", @@ -5396,6 +5410,12 @@ "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==" }, + "undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index b75fc73c..7e9d57c1 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "test-published-files": "npm pack && tar zxvf \"ecmarkup-$npm_package_version.tgz\" && cp -r test package/test && cd package && npm test && cd ..", "posttest-published-files": "rm -rf \"ecmarkup-$npm_package_version.tgz\" package", "lint": "eslint --ext .js,.ts js src test", + "typecheck": "tsc --noEmit && tsc -p test", "format": "prettier --write ." }, "bin": { @@ -70,7 +71,7 @@ "@types/command-line-usage": "^5.0.2", "@types/js-yaml": "^3.12.1", "@types/jsdom": "^16.2.13", - "@types/node": "^13.1.8", + "@types/node": "^24.10.13", "@types/parse5": "^6.0.2", "@typescript-eslint/eslint-plugin": "^8.6.0", "@typescript-eslint/parser": "^8.6.0", diff --git a/src/.eslintrc.json b/src/.eslintrc.json deleted file mode 100644 index 26be9821..00000000 --- a/src/.eslintrc.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "parser": "@typescript-eslint/parser", - "parserOptions": { - "sourceType": "module", - "ecmaVersion": 2020, - "//": "NB project is relative to the directory you run eslint from, see https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/parser#parseroptionsproject", - "project": "./tsconfig.json" - }, - "extends": [ - "../.eslintrc.json", - "plugin:@typescript-eslint/eslint-recommended" - ], - "plugins": [ - "@typescript-eslint" - ], - "rules": { - "@typescript-eslint/no-use-before-define": [ - "error", - { - "functions": false, - "typedefs": false - } - ], - "@typescript-eslint/no-unnecessary-type-assertion": "error", - "@typescript-eslint/no-unused-vars": "error", - "no-unused-vars": "off" - } -} diff --git a/src/Biblio.ts b/src/Biblio.ts index 0f63df55..de7c082e 100644 --- a/src/Biblio.ts +++ b/src/Biblio.ts @@ -414,7 +414,7 @@ export type BiblioEntry = | StepBiblioEntry; // see https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types -type Unkey = T extends any ? Omit : never; +type Unkey = T extends unknown ? Omit : never; type NonExportedKeys = 'location' | 'referencingIds' | 'namespace'; export type PartialBiblioEntry = Unkey; diff --git a/src/Eqn.ts b/src/Eqn.ts index a7cb259d..16784315 100644 --- a/src/Eqn.ts +++ b/src/Eqn.ts @@ -51,8 +51,8 @@ export default class Eqn extends Builder { let contents; try { contents = emd.fragment(node.innerHTML); - } catch (e: any) { - utils.warnEmdFailure(spec.warn, node, e); + } catch (e) { + utils.warnEmdFailure(spec.warn, node, e as SyntaxError & { line?: number; column?: number }); node.innerHTML = utils.wrapEmdFailure(node.innerHTML); return; } diff --git a/src/Spec.ts b/src/Spec.ts index d2fe45db..5211bf76 100644 --- a/src/Spec.ts +++ b/src/Spec.ts @@ -1276,10 +1276,10 @@ ${await utils.readFile(path.join(__dirname, '../js/multipage.js'))} const solarizedStyle = this.doc.createElement('style'); solarizedStyle.textContent = ` @import url("https://cdnjs.cloudflare.com/ajax/libs/highlight.js/${ - (hljs as any).versionString + (hljs as unknown as { versionString: string }).versionString }/styles/base16/solarized-light.min.css"); @import url("https://cdnjs.cloudflare.com/ajax/libs/highlight.js/${ - (hljs as any).versionString + (hljs as unknown as { versionString: string }).versionString }/styles/a11y-dark.min.css") (prefers-color-scheme: dark); `; this.doc.head.appendChild(solarizedStyle); @@ -1340,18 +1340,20 @@ ${this.opts.multipage ? `
  • Navigate to/from multipagem( try { // @ts-ignore the types are wrong about mutability args = commandLineArgs(options, { argv }); - } catch (e: any) { - if (e?.name === 'UNKNOWN_OPTION') { - fail(`Unknown option ${e.optionName}`); + } catch (e) { + if (e != null && typeof e === 'object' && 'name' in e && e.name === 'UNKNOWN_OPTION') { + fail(`Unknown option ${(e as unknown as { optionName: string }).optionName}`); } throw e; } diff --git a/src/cli.ts b/src/cli.ts index fd038dbc..0cf037da 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -186,7 +186,7 @@ const build = debounce(async function build() { utils.logVerbose(warned ? 'Completed with errors.' : 'Done.'); } - const pending: Promise[] = []; + const pending: Promise[] = []; if (args['write-biblio']) { if (args.verbose) { utils.logVerbose('Writing biblio file to ' + args['write-biblio']); @@ -281,9 +281,9 @@ const build = debounce(async function build() { } } } - } catch (e: any) { + } catch (e) { if (args.watch) { - process.stderr.write(e.stack); + process.stderr.write(e instanceof Error ? e.stack ?? e.message : String(e)); } else { throw e; } diff --git a/src/external.d.ts b/src/external.d.ts index 1fb04239..decc0b73 100644 --- a/src/external.d.ts +++ b/src/external.d.ts @@ -4,6 +4,9 @@ declare module 'html-escape' { } declare module 'promise-debounce' { - function debounce Promise>(fn: TFunc, ctx?: any): TFunc; + function debounce Promise>( + fn: TFunc, + ctx?: unknown, + ): TFunc; export = debounce; } diff --git a/src/formatter/cli.ts b/src/formatter/cli.ts index 1e9b2af0..42e6f66c 100644 --- a/src/formatter/cli.ts +++ b/src/formatter/cli.ts @@ -158,8 +158,8 @@ async function expand(pattern: string) { async function stat(path: string) { try { return await fs.stat(path); - } catch (error: any) { - if (error.code !== 'ENOENT') { + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { throw error; } return null; diff --git a/src/lint/collect-algorithm-diagnostics.ts b/src/lint/collect-algorithm-diagnostics.ts index d9f7cbc6..581cf14d 100644 --- a/src/lint/collect-algorithm-diagnostics.ts +++ b/src/lint/collect-algorithm-diagnostics.ts @@ -73,8 +73,8 @@ export function collectAlgorithmDiagnostics( let tree; try { tree = parseAlgorithm(algorithmSource); - } catch (e: any) { - warnEmdFailure(report, element, e); + } catch (e) { + warnEmdFailure(report, element, e as SyntaxError & { line?: number; column?: number }); } const parsedSteps: Map = new Map(); let allNodesParsedSuccessfully = true; diff --git a/src/utils.ts b/src/utils.ts index 839169b9..bd0a4bb5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -62,8 +62,8 @@ export function emdTextNode(spec: Spec, node: Text, namespace: string) { spec._ntStringRefs = spec._ntStringRefs.concat(nonterminals); } processed = emd.emit(parts); - } catch (e: any) { - warnEmdFailure(spec.warn, node, e); + } catch (e) { + warnEmdFailure(spec.warn, node, e as SyntaxError & { line?: number; column?: number }); processed = wrapEmdFailure(c); } diff --git a/test/baselines.js b/test/baselines.ts similarity index 62% rename from test/baselines.js rename to test/baselines.ts index c45a185f..47b286e7 100644 --- a/test/baselines.js +++ b/test/baselines.ts @@ -3,6 +3,8 @@ import { describe, it } from 'node:test'; import fs from 'fs'; import path from 'path'; +import type { EcmarkupError, Options } from '../lib/ecmarkup.js'; +import type { ExportedBiblio } from '../lib/Biblio.js'; import * as emu from '../lib/ecmarkup.js'; import ecma262biblio from './ecma262biblio.json' with { type: 'json' }; @@ -15,7 +17,7 @@ const files = fs .readdirSync(SOURCES_DIR) .filter(f => f.endsWith('.html') && !f.endsWith('.bad.html')); -function build(file, options) { +function build(file: string, options: Options) { return emu.build( file, file => @@ -23,28 +25,28 @@ function build(file, options) { fs.readFile(file, 'utf-8', (err, data) => (err ? reject(err) : resolve(data))), ), { - extraBiblios: [ecma262biblio], + extraBiblios: [ecma262biblio as ExportedBiblio], ...options, }, ); } describe('baselines', () => { - let rebaseline = !!process.env.npm_config_update_baselines; - let dirToWriteOnFailure = rebaseline ? REFERENCE_DIR : LOCAL_DIR; - let optionsSets = [{ lintSpec: false }]; - for (let file of files) { + const rebaseline = !!process.env.npm_config_update_baselines; + const dirToWriteOnFailure = rebaseline ? REFERENCE_DIR : LOCAL_DIR; + const optionsSets = [{ lintSpec: false }]; + for (const file of files) { const reference = REFERENCE_DIR + file; const sourcePath = SOURCES_DIR + file; it(sourcePath, async () => { - let expectedFiles = new Map(); + const expectedFiles = new Map(); (function walk(f) { if (!fs.existsSync(f)) { return; } if (fs.lstatSync(f).isDirectory()) { - for (let file of fs.readdirSync(f)) { + for (const file of fs.readdirSync(f)) { walk(path.join(f, file)); } } else { @@ -52,9 +54,9 @@ describe('baselines', () => { } })(reference); - let spec = await build(sourcePath, {}); + const spec = await build(sourcePath, {}); - let actualFiles = handleSingleFileOutput(spec.generatedFiles); + const actualFiles = handleSingleFileOutput(spec.generatedFiles); let threw = true; try { @@ -65,8 +67,8 @@ describe('baselines', () => { threw = false; } finally { if (threw) { - for (let [fileToWrite, contents] of actualFiles) { - let toWrite = path.resolve(dirToWriteOnFailure, path.join(file, fileToWrite)); + for (const [fileToWrite, contents] of actualFiles) { + const toWrite = path.resolve(dirToWriteOnFailure, path.join(file, fileToWrite ?? '')); fs.mkdirSync(path.dirname(toWrite), { recursive: true }); fs.writeFileSync(toWrite, contents, 'utf8'); } @@ -83,28 +85,28 @@ describe('baselines', () => { return; } - let contents = fs.readFileSync(sourcePath, 'utf8'); - let expectedWarnings = [...contents.matchAll(//g)].map(m => + const contents = fs.readFileSync(sourcePath, 'utf8'); + const expectedWarnings = [...contents.matchAll(//g)].map(m => JSON.parse(m[1]), ); - let warningProps = new Set(expectedWarnings.flatMap(obj => Object.keys(obj))); - function pickFromWarning(warning) { + const warningProps = new Set(expectedWarnings.flatMap(obj => Object.keys(obj))); + function pickFromWarning(warning: EcmarkupError) { if (warningProps.size === 0) { // No warnings are expected, so "pick" the entire object. return warning; } - let picks = {}; - for (let [key, value] of Object.entries(warning)) { + const picks: Record = {}; + for (const [key, value] of Object.entries(warning)) { if (warningProps.has(key)) picks[key] = value; } return picks; } - for (let options of optionsSets) { - let warnings = []; - let warn = warning => warnings.push(warning); - let spec = await build(sourcePath, { ...options, warn }); - let actualFiles = handleSingleFileOutput(spec.generatedFiles); + for (const options of optionsSets) { + const warnings: EcmarkupError[] = []; + const warn = (warning: EcmarkupError) => warnings.push(warning); + const spec = await build(sourcePath, { ...options, warn }); + const actualFiles = handleSingleFileOutput(spec.generatedFiles); assert.deepStrictEqual( actualFiles, expectedFiles, @@ -120,10 +122,10 @@ describe('baselines', () => { } }); -function handleSingleFileOutput(files) { +function handleSingleFileOutput(files: Map) { if (files.size === 1 && files.has(null)) { // i.e. single-file output - files.set('', files.get(null)); + files.set('', files.get(null)!); files.delete(null); } return files; diff --git a/test/biblio.js b/test/biblio.ts similarity index 73% rename from test/biblio.js rename to test/biblio.ts index de1ed960..9ce58b21 100644 --- a/test/biblio.js +++ b/test/biblio.ts @@ -2,21 +2,36 @@ import assert from 'assert'; import { describe, it, beforeEach } from 'node:test'; import { JSDOM } from 'jsdom'; import BiblioModule from '../lib/Biblio.js'; +import type { AlgorithmBiblioEntry, TermBiblioEntry } from '../lib/Biblio.js'; import { build } from '../lib/ecmarkup.js'; const Biblio = BiblioModule.default; const location = 'https://tc39.github.io/ecma262/'; +// Interface for testing Biblio internals +interface TestOpEntry { + type: 'op'; + aoid: string; + refId: string; +} + +interface TestBiblio { + add(entry: TestOpEntry, ns?: string | null): void; + byAoid(aoid: string, ns?: string): AlgorithmBiblioEntry | undefined; + localEntries(): unknown[]; + getDefinedWords(ns: string): Record; +} + describe('Biblio', () => { - let biblio; + let biblio: TestBiblio; beforeEach(() => { - biblio = new Biblio(location); + biblio = new Biblio(location) as unknown as TestBiblio; }); it('is created with a root namespace named "external"', () => { const opEntry = { - type: 'op', + type: 'op' as const, aoid: 'aoid', refId: 'clauseId', }; @@ -27,7 +42,7 @@ describe('Biblio', () => { it("is created with a nested namespace for the doc's location", () => { const opEntry = { - type: 'op', + type: 'op' as const, aoid: 'aoid', refId: 'clauseId', }; @@ -39,13 +54,13 @@ describe('Biblio', () => { it('localEntries includes only the local scope', () => { const opEntry1 = { - type: 'op', + type: 'op' as const, aoid: 'aoid1', refId: 'clauseId', }; const opEntry2 = { - type: 'op', + type: 'op' as const, aoid: 'aoid2', refId: 'clauseId', }; @@ -61,13 +76,13 @@ describe('Biblio', () => { it('getDefinedWords de-dupes by key', () => { const opEntry1 = { - type: 'op', + type: 'op' as const, aoid: 'aoid', refId: 'clauseId', }; const opEntry2 = { - type: 'op', + type: 'op' as const, aoid: 'aoid', refId: 'clauseId', }; @@ -75,14 +90,14 @@ describe('Biblio', () => { biblio.add(opEntry1, location); biblio.add(opEntry2, 'external'); - let results = biblio.getDefinedWords(location); + const results = biblio.getDefinedWords(location); assert.equal(results.aoid, opEntry1); }); it('can import an exported biblio', async () => { - let spec1 = await build( + const spec1 = await build( 'root.html', - () => ` + async () => `

    Clause C

    A definition of example.

    @@ -98,11 +113,11 @@ describe('Biblio', () => { }, }, ); - let spec1Biblio = spec1.exportBiblio(); + const spec1Biblio = spec1.exportBiblio(); - let spec2 = await build( + const spec2 = await build( 'root.html', - () => ` + async () => `

    Clause D

    Terms like example or examples are crosslinked.

    @@ -112,7 +127,7 @@ describe('Biblio', () => { copyright: false, assets: 'none', toc: false, - extraBiblios: [spec1Biblio], + extraBiblios: [spec1Biblio!], location: 'https://example.com/spec2/', warn: e => { console.error('Error:', e); @@ -120,17 +135,17 @@ describe('Biblio', () => { }, }, ); - let renderedSpec2 = new JSDOM(spec2.toHTML()).window; + const renderedSpec2 = new JSDOM((spec2 as unknown as { toHTML(): string }).toHTML()).window; assert.equal( - renderedSpec2.document.querySelector('p').innerHTML, + renderedSpec2.document.querySelector('p')!.innerHTML, 'Terms like example or examples are crosslinked.', ); }); it('propagates effects from exported biblios', async () => { - let spec1 = await build( + const spec1 = await build( 'root.html', - () => ` + async () => `

    UserCode ( )

    @@ -150,11 +165,11 @@ describe('Biblio', () => { }, }, ); - let spec1Biblio = spec1.exportBiblio(); + const spec1Biblio = spec1.exportBiblio(); - let spec2 = await build( + const spec2 = await build( 'root.html', - () => ` + async () => `

    Clause D

    @@ -174,7 +189,7 @@ describe('Biblio', () => { copyright: false, assets: 'none', toc: false, - extraBiblios: [spec1Biblio], + extraBiblios: [spec1Biblio!], markEffects: true, location: 'https://example.com/spec2/', warn: e => { @@ -183,13 +198,13 @@ describe('Biblio', () => { }, }, ); - let renderedSpec2 = new JSDOM(spec2.toHTML()).window; + const renderedSpec2 = new JSDOM((spec2 as unknown as { toHTML(): string }).toHTML()).window; assert.equal( - renderedSpec2.document.querySelector('emu-alg').innerHTML, + renderedSpec2.document.querySelector('emu-alg')!.innerHTML, '
    1. UserCode().
    ', ); assert.equal( - renderedSpec2.document.querySelector('#calls-foo').innerHTML, + renderedSpec2.document.querySelector('#calls-foo')!.innerHTML, '
    1. Foo().
    ', ); }); diff --git a/test/build.js b/test/build.ts similarity index 83% rename from test/build.js rename to test/build.ts index f5a845e1..3fd21889 100644 --- a/test/build.js +++ b/test/build.ts @@ -6,7 +6,7 @@ import { build } from '../lib/ecmarkup.js'; const doc = '

    hi

    '; -function fetch(file) { +function fetch(file: string) { if (file.match(/\.json$/)) { return '{}'; } else { @@ -16,14 +16,14 @@ function fetch(file) { describe('ecmarkup#build', () => { it('takes a fetch callback that returns a promise', async () => { - let spec = await build( + const spec = await build( 'root.html', file => new Promise(res => { process.nextTick(() => res(fetch(file))); }), ); - let result = spec.toHTML(); + const result = (spec as unknown as { toHTML(): string }).toHTML(); assert.equal(typeof result, 'string'); assert(result.includes(`
    `)); }); diff --git a/test/clauseIds.js b/test/clauseIds.js deleted file mode 100644 index 4f1c724d..00000000 --- a/test/clauseIds.js +++ /dev/null @@ -1,27 +0,0 @@ -import assert from 'assert'; -import { describe, it, beforeEach } from 'node:test'; -import clauseNumsModule from '../lib/clauseNums.js'; -const sectionNums = clauseNumsModule.default; - -describe('clause id generation', () => { - let iter; - - beforeEach(() => { - iter = sectionNums({ opts: {} }); - }); - - it('generating clause ids', () => { - const CLAUSE = { nodeName: 'EMU-CLAUSE', hasAttribute: () => false }; - const ANNEX = { nodeName: 'EMU-ANNEX', hasAttribute: () => false }; - assert.strictEqual(iter.next([], CLAUSE), '1'); - assert.strictEqual(iter.next([{}], CLAUSE), '1.1'); - assert.strictEqual(iter.next([{}], CLAUSE), '1.2'); - assert.strictEqual(iter.next([{}, {}], CLAUSE), '1.2.1'); - assert.strictEqual(iter.next([], CLAUSE), '2'); - assert.strictEqual(iter.next([], ANNEX), 'A'); - assert.strictEqual(iter.next([{}], ANNEX), 'A.1'); - assert.strictEqual(iter.next([{}], ANNEX), 'A.2'); - assert.strictEqual(iter.next([{}, {}], ANNEX), 'A.2.1'); - assert.strictEqual(iter.next([], ANNEX), 'B'); - }); -}); diff --git a/test/clauseIds.ts b/test/clauseIds.ts new file mode 100644 index 00000000..dedd9223 --- /dev/null +++ b/test/clauseIds.ts @@ -0,0 +1,33 @@ +import assert from 'assert'; +import { describe, it, beforeEach } from 'node:test'; +import clauseNumsModule from '../lib/clauseNums.js'; +import type { ClauseNumberIterator } from '../lib/clauseNums.js'; +import type ClauseModule from '../lib/Clause.js'; +import type { Spec } from '../lib/ecmarkup.js'; + +type Clause = InstanceType; +const sectionNums = clauseNumsModule.default; + +describe('clause id generation', () => { + let iter: ClauseNumberIterator; + + beforeEach(() => { + iter = sectionNums({ opts: {} } as Spec); + }); + + it('generating clause ids', () => { + const CLAUSE = { nodeName: 'EMU-CLAUSE', hasAttribute: () => false } as unknown as HTMLElement; + const ANNEX = { nodeName: 'EMU-ANNEX', hasAttribute: () => false } as unknown as HTMLElement; + const mockClause = {} as Clause; + assert.strictEqual(iter.next([], CLAUSE), '1'); + assert.strictEqual(iter.next([mockClause], CLAUSE), '1.1'); + assert.strictEqual(iter.next([mockClause], CLAUSE), '1.2'); + assert.strictEqual(iter.next([mockClause, mockClause], CLAUSE), '1.2.1'); + assert.strictEqual(iter.next([], CLAUSE), '2'); + assert.strictEqual(iter.next([], ANNEX), 'A'); + assert.strictEqual(iter.next([mockClause], ANNEX), 'A.1'); + assert.strictEqual(iter.next([mockClause], ANNEX), 'A.2'); + assert.strictEqual(iter.next([mockClause, mockClause], ANNEX), 'A.2.1'); + assert.strictEqual(iter.next([], ANNEX), 'B'); + }); +}); diff --git a/test/cli.js b/test/cli.ts similarity index 99% rename from test/cli.js rename to test/cli.ts index a0e8656c..6d7c918f 100644 --- a/test/cli.js +++ b/test/cli.ts @@ -38,7 +38,6 @@ describe('ecmarkup#cli', { timeout: 4000 }, () => { }); describe('emu-format --check', { timeout: 4000 }, () => { - it('exits cleanly if the file needs no formatting', () => { execSync(`${execPath} ./bin/emu-format.js --check test/format-good.html`, { encoding: 'utf8', diff --git a/test/errors.js b/test/errors.ts similarity index 98% rename from test/errors.js rename to test/errors.ts index 20266469..4cd8ce0f 100644 --- a/test/errors.js +++ b/test/errors.ts @@ -1,5 +1,7 @@ import assert from 'assert'; import { describe, it } from 'node:test'; +import type { EcmarkupError } from '../lib/ecmarkup.js'; +import type { ExportedBiblio } from '../lib/Biblio.js'; import * as emu from '../lib/ecmarkup.js'; import { @@ -9,7 +11,7 @@ import { assertError, assertErrorFree, getBiblio, -} from './utils.js'; +} from './utils.ts'; describe('errors', () => { it('no contributors', async () => { @@ -439,15 +441,15 @@ ${M} }); it('error in nested import', async () => { - let warnings = []; + const warnings: EcmarkupError[] = []; - let { line, column, html } = positioned` + const { line, column, html } = positioned`

    Title

    `; - let fetch = name => { + const fetch = async (name: string) => { switch (name.replace(/\\/g, '/')) { case 'foo/index.html': { return ``; @@ -473,7 +475,7 @@ ${M} line: e.line, column: e.column, message: e.message, - file: e.file.replace(/\\/g, '/'), + file: e.file!.replace(/\\/g, '/'), source: e.source, }), }); @@ -1060,11 +1062,11 @@ ${M} it('old-style biblio', async () => { await assert.rejects(async () => { - const fetch = () => ''; + const fetch = async () => ''; await emu.build('index.html', fetch, { copyright: false, assets: 'none', - extraBiblios: [{ 'https://tc39.es/ecma262/': [] }], + extraBiblios: [{ 'https://tc39.es/ecma262/': [] } as unknown as ExportedBiblio], }); }, /old-style biblio/); }); @@ -1180,7 +1182,7 @@ ${M} }); it('negative: external biblio', async () => { - let upstream = await getBiblio(` + const upstream = await getBiblio(` Foo : \`a\` diff --git a/test/expr-parser.js b/test/expr-parser.ts similarity index 96% rename from test/expr-parser.js rename to test/expr-parser.ts index 2cda68c2..f21638cc 100644 --- a/test/expr-parser.js +++ b/test/expr-parser.ts @@ -5,10 +5,11 @@ import { lintLocationMarker as M, assertLintFree, getBiblio, -} from './utils.js'; +} from './utils.ts'; import { parse as parseExpr } from '../lib/expr-parser.js'; import assert from 'assert'; import { parseFragment } from 'ecmarkdown'; +import type { ExportedBiblio } from '../lib/Biblio.js'; describe('expression parsing', () => { describe('valid', () => { @@ -296,7 +297,7 @@ describe('expression parsing', () => { }); describe('handling of ', () => { - let biblio; + let biblio: ExportedBiblio; before(async () => { biblio = await getBiblio(` @@ -353,12 +354,12 @@ describe('expression parsing', () => { }); describe('parse trees', () => { - function parse(src, sdoNames = new Set()) { - let tree = parseExpr(parseFragment(src), sdoNames); + function parse(src: string, sdoNames: Set = new Set()) { + const tree = parseExpr(parseFragment(src), sdoNames); return JSON.parse(JSON.stringify(tree, (k, v) => (k === 'location' ? undefined : v))); } it('is able to handle `a.[[b]` nodes in SDO calls', async () => { - let tree = parse('Perform SDO of _A_.[[B]] with argument _c_.[[D]].', new Set(['SDO'])); + const tree = parse('Perform SDO of _A_.[[B]] with argument _c_.[[D]].', new Set(['SDO'])); assert.deepStrictEqual(tree, { name: 'seq', items: [ diff --git a/test/formatter.js b/test/formatter.ts similarity index 98% rename from test/formatter.js rename to test/formatter.ts index ef1704a3..9bc083f1 100644 --- a/test/formatter.js +++ b/test/formatter.ts @@ -733,11 +733,11 @@ describe('entities', () => { }); }); -async function assertRoundTrips(src) { +async function assertRoundTrips(src: string) { await assertDocFormatsAs(src, src); } -async function assertDocFormatsAs(src, result) { +async function assertDocFormatsAs(src: string, result: string) { const withDoctype = await printDocument(src); assert.strictEqual(withDoctype.replace(/^\n+/, ''), result); @@ -745,8 +745,8 @@ async function assertDocFormatsAs(src, result) { assert.strictEqual(doubleFormatted, withDoctype, 'result changed after formatting again'); } -function dedentKeepingTrailingNewline(strings, ...exprs) { - let lastIsWhitespace = strings[strings.length - 1].match(/\n\s*$/); +function dedentKeepingTrailingNewline(strings: TemplateStringsArray, ...exprs: unknown[]) { + const lastIsWhitespace = strings[strings.length - 1].match(/\n\s*$/); let dedented = dedent(strings, ...exprs); if (lastIsWhitespace) { dedented += '\n'; diff --git a/test/lint-algorithms.js b/test/lint-algorithms.ts similarity index 99% rename from test/lint-algorithms.js rename to test/lint-algorithms.ts index 8098d7e8..1bd6838f 100644 --- a/test/lint-algorithms.js +++ b/test/lint-algorithms.ts @@ -1,5 +1,5 @@ import { describe, it } from 'node:test'; -import { assertLint, assertLintFree, lintLocationMarker as M, positioned } from './utils.js'; +import { assertLint, assertLintFree, lintLocationMarker as M, positioned } from './utils.ts'; const nodeType = 'emu-alg'; diff --git a/test/lint-spelling.js b/test/lint-spelling.ts similarity index 99% rename from test/lint-spelling.js rename to test/lint-spelling.ts index c4c9643d..599131c7 100644 --- a/test/lint-spelling.js +++ b/test/lint-spelling.ts @@ -1,5 +1,5 @@ import { describe, it } from 'node:test'; -import { assertLint, assertLintFree, positioned, lintLocationMarker as M } from './utils.js'; +import { assertLint, assertLintFree, positioned, lintLocationMarker as M } from './utils.ts'; describe('spelling', () => { it('*this* object', async () => { diff --git a/test/lint-tags.js b/test/lint-tags.ts similarity index 97% rename from test/lint-tags.js rename to test/lint-tags.ts index 1a01f09f..942c8c2d 100644 --- a/test/lint-tags.js +++ b/test/lint-tags.ts @@ -1,5 +1,5 @@ import { describe, it } from 'node:test'; -import { assertLint, assertLintFree, positioned, lintLocationMarker as M } from './utils.js'; +import { assertLint, assertLintFree, positioned, lintLocationMarker as M } from './utils.ts'; describe('tags', () => { it('unknown "emu-" tags', async () => { diff --git a/test/lint-variable-use-def.js b/test/lint-variable-use-def.ts similarity index 99% rename from test/lint-variable-use-def.js rename to test/lint-variable-use-def.ts index 3f2c74d6..f13a5eac 100644 --- a/test/lint-variable-use-def.js +++ b/test/lint-variable-use-def.ts @@ -5,7 +5,7 @@ import { positioned, lintLocationMarker as M, getBiblio, -} from './utils.js'; +} from './utils.ts'; describe('variables are declared and used appropriately', () => { describe('variables must be declared', () => { @@ -580,7 +580,7 @@ describe('variables are declared and used appropriately', () => { }); it('loop variables are visible within the loop', async () => { - let biblio = await getBiblio(` + const biblio = await getBiblio(` CaseClause : \`a\` diff --git a/test/lint.js b/test/lint.ts similarity index 99% rename from test/lint.js rename to test/lint.ts index b3c3ff56..e3055a5f 100644 --- a/test/lint.js +++ b/test/lint.ts @@ -5,7 +5,7 @@ import { positioned, lintLocationMarker as M, getBiblio, -} from './utils.js'; +} from './utils.ts'; describe('linting whole program', () => { describe('grammar validity', () => { @@ -89,7 +89,7 @@ describe('linting whole program', () => { }); it('negative: nonterminal defined upstream', async () => { - let upstream = await getBiblio(` + const upstream = await getBiblio(` Bar: \`a\` @@ -251,7 +251,7 @@ describe('linting whole program', () => { }); it('negative: productions defined in biblio', async () => { - let upstream = await getBiblio(` + const upstream = await getBiblio(` Statement: EmptyStatement diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 00000000..d0f08078 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../tsconfig.json", + "include": ["./**/*"], + "compilerOptions": { + "noEmit": true, + "declaration": false, + "module": "nodenext", + "moduleResolution": "nodenext", + "esModuleInterop": true, + "resolveJsonModule": true, + "strict": true, + "allowImportingTsExtensions": true + } +} \ No newline at end of file diff --git a/test/type-parser.js b/test/type-parser.ts similarity index 99% rename from test/type-parser.js rename to test/type-parser.ts index 2ca42903..8ae09968 100644 --- a/test/type-parser.js +++ b/test/type-parser.ts @@ -1,5 +1,5 @@ import { describe, it } from 'node:test'; -import { assertError, positioned, lintLocationMarker as M } from './utils.js'; +import { assertError, positioned, lintLocationMarker as M } from './utils.ts'; import { TypeParser } from '../lib/type-parser.js'; import assert from 'assert'; diff --git a/test/typecheck.js b/test/typecheck.ts similarity index 98% rename from test/typecheck.js rename to test/typecheck.ts index df9be02e..a29b3b88 100644 --- a/test/typecheck.js +++ b/test/typecheck.ts @@ -5,10 +5,11 @@ import { positioned, lintLocationMarker as M, getBiblio, -} from './utils.js'; +} from './utils.ts'; +import type { ExportedBiblio } from '../lib/Biblio.js'; describe('typechecking completions', () => { - let biblio; + let biblio: ExportedBiblio; before(async () => { biblio = await getBiblio(` @@ -558,7 +559,7 @@ describe('typechecking completions', () => { }); it('negative', async () => { - async function assertStepIsConsideredAbrupt(step) { + async function assertStepIsConsideredAbrupt(step: string) { await assertLintFree( ` @@ -919,7 +920,7 @@ describe('typechecking completions', () => { }); describe('signature agreement', async () => { - let biblio; + let biblio: ExportedBiblio; before(async () => { biblio = await getBiblio(` @@ -1201,7 +1202,7 @@ describe('signature agreement', async () => { }); it("'d params don't contribute to signature", async () => { - let biblio = await getBiblio(` + const biblio = await getBiblio(`

    DelExample ( @@ -1268,7 +1269,7 @@ describe('signature agreement', async () => { }); describe('invocation kind', async () => { - let biblio; + let biblio: ExportedBiblio; before(async () => { biblio = await getBiblio(` @@ -1432,7 +1433,7 @@ describe('invocation kind', async () => { }); describe('negation', async () => { - let biblio; + let biblio: ExportedBiblio; before(async () => { biblio = await getBiblio(` @@ -1507,11 +1508,11 @@ describe('negation', async () => { describe('type system', () => { async function assertTypeError( - paramType, - arg, - messageForParam, - messageForReturn, - extraBiblios = [], + paramType: string, + arg: string, + messageForParam: string, + messageForReturn: string | null, + extraBiblios: ExportedBiblio[] = [], ) { await assertLint( positioned` @@ -1577,7 +1578,11 @@ describe('type system', () => { } } - async function assertNoTypeError(paramType, arg, extraBiblios = []) { + async function assertNoTypeError( + paramType: string, + arg: string, + extraBiblios: ExportedBiblio[] = [], + ) { await assertLintFree( ` @@ -1596,7 +1601,7 @@ describe('type system', () => { ); } - let completionBiblio; + let completionBiblio: ExportedBiblio; before(async () => { completionBiblio = await getBiblio(` @@ -1898,7 +1903,7 @@ describe('type system', () => { }); it('call', async () => { - let biblio = await getBiblio(` + const biblio = await getBiblio(`

    ReturnsNumber (): a Number @@ -1942,7 +1947,7 @@ describe('type system', () => { }); it('non-strict type overlap', async () => { - let biblio = await getBiblio(` + const biblio = await getBiblio(`

    ReturnsListOfNumberOrString (): a List of either Numbers or Strings @@ -2211,7 +2216,7 @@ describe('special cases', () => { }); describe('concrete method vs abstract method agreement', () => { - let biblio; + let biblio: ExportedBiblio; before(async () => { biblio = await getBiblio(` diff --git a/test/utils.js b/test/utils.ts similarity index 61% rename from test/utils.js rename to test/utils.ts index 9b95edf5..09a475dd 100644 --- a/test/utils.js +++ b/test/utils.ts @@ -1,36 +1,56 @@ import assert from 'assert'; +import type { EcmarkupError, Options } from '../lib/ecmarkup.js'; import * as emu from '../lib/ecmarkup.js'; +import type { ExportedBiblio } from '../lib/Biblio.js'; + +type Position = { offset: number; line: number; column: number }; +type PositionedResult = Position & { html: string }; +type MultiPositionedResult = { positions: Position[]; html: string }; +type ErrorDescriptor = { + ruleId: string; + nodeType: string; + message: string; + line?: number; + column?: number; +}; +type PositionedInput = string | PositionedResult | MultiPositionedResult; -let lintLocationMarker = {}; +const lintLocationMarker = {}; -function positioned(literalParts, ...interpolatedParts) { - let markerIndex = interpolatedParts.indexOf(lintLocationMarker); +function positioned( + literalParts: TemplateStringsArray, + ...interpolatedParts: unknown[] +): PositionedResult { + const markerIndex = interpolatedParts.indexOf(lintLocationMarker); if (markerIndex < 0 || markerIndex !== interpolatedParts.lastIndexOf(lintLocationMarker)) { throw new Error('positioned template tag must interpolate the location marker exactly once'); } - let multi = multipositioned(literalParts, ...interpolatedParts); + const multi = multipositioned(literalParts, ...interpolatedParts); return { ...multi.positions[0], html: multi.html }; } -function multipositioned(literalParts, ...interpolatedParts) { - let markerIndexes = interpolatedParts +function multipositioned( + literalParts: TemplateStringsArray, + ...interpolatedParts: unknown[] +): MultiPositionedResult { + const markerIndexes = interpolatedParts .map((value, index) => ({ value, index })) .filter(({ value }) => value === lintLocationMarker) .map(({ index }) => index); - if (markerIndexes === 0) { + if (markerIndexes.length === 0) { throw new Error( 'multipositioned template tag must interpolate the location marker at least once', ); } - let positions = []; + const positions = []; let str = literalParts[0]; for (let i = 0; i < literalParts.length - 1; ++i) { if (markerIndexes.includes(i)) { - let offset = str.length; - let lines = str.split('\n'); - let line = lines.length; - let column = lines[lines.length - 1].length + 1; + const offset = str.length; + const lines = str.split('\n'); + const line = lines.length; + const column = lines[lines.length - 1].length + 1; positions.push({ offset, line, column }); } else { str += String(interpolatedParts[i]); @@ -40,13 +60,19 @@ function multipositioned(literalParts, ...interpolatedParts) { return { positions, html: str }; } -async function assertError(obj, errorOrErrors, opts) { - let html; +async function assertError( + obj: PositionedInput, + errorOrErrors: ErrorDescriptor | ErrorDescriptor[], + opts: Options & { asImport?: boolean | 'only' } = {}, +) { + let html: string; + let errors: ErrorDescriptor[]; if (Array.isArray(errorOrErrors)) { if (typeof obj === 'string') { html = obj; + errors = errorOrErrors; // no line and column for any error - } else if (!obj.positions) { + } else if (!('positions' in obj)) { if (errorOrErrors.length !== 1) { throw new Error( `your source has positions for 1 error but your assertion passes ${errorOrErrors.length}`, @@ -54,7 +80,7 @@ async function assertError(obj, errorOrErrors, opts) { } // kind of weird to pass an array of length 1 here but it might come up when copying tests, no reason to disallow ({ html } = obj); - errorOrErrors = [{ ...errorOrErrors[0], line: obj.line, column: obj.column }]; + errors = [{ ...errorOrErrors[0], line: obj.line, column: obj.column }]; } else { if (errorOrErrors.length !== obj.positions.length) { throw new Error( @@ -62,13 +88,13 @@ async function assertError(obj, errorOrErrors, opts) { ); } ({ html } = obj); - errorOrErrors = errorOrErrors.map((e, i) => ({ ...e, ...obj.positions[i] })); + errors = errorOrErrors.map((e, i) => ({ ...e, ...obj.positions[i] })); } } else { - let line, column; + let line: number | undefined, column: number | undefined; if (typeof obj === 'string') { html = obj; - } else if (obj.positions) { + } else if ('positions' in obj) { if (obj.positions.length !== 1) { throw new Error( `your source has positions for ${obj.positions.length} errors but your assertion only passes 1`, @@ -80,11 +106,11 @@ async function assertError(obj, errorOrErrors, opts) { } else { ({ line, column, html } = obj); } - errorOrErrors = [{ ...errorOrErrors, line, column }]; + errors = [{ ...errorOrErrors, line, column }]; } - let warnings = []; + let warnings: EcmarkupError[] = []; - let rootFile = 'test-example.emu'; + const rootFile = 'test-example.emu'; if (opts?.asImport !== 'only') { await emu.build(rootFile, async () => html, { copyright: false, @@ -104,7 +130,7 @@ async function assertError(obj, errorOrErrors, opts) { assert.deepStrictEqual( warnings, - errorOrErrors.map(({ ruleId, nodeType, line, column, message }) => ({ + errors.map(({ ruleId, nodeType, line, column, message }) => ({ ruleId, nodeType, line, @@ -123,8 +149,8 @@ async function assertError(obj, errorOrErrors, opts) { // because location information is complicated in imports, do it again in an emu-import warnings = []; - let importWrapper = ``; - let fetch = name => (name === rootFile ? importWrapper : html); + const importWrapper = ``; + const fetch = async (name: string) => (name === rootFile ? importWrapper : html); await emu.build(rootFile, fetch, { copyright: false, assets: 'none', @@ -143,7 +169,7 @@ async function assertError(obj, errorOrErrors, opts) { assert.deepStrictEqual( warnings, - errorOrErrors.map(({ ruleId, nodeType, line, column, message }) => ({ + errors.map(({ ruleId, nodeType, line, column, message }) => ({ ruleId, nodeType, line, @@ -156,13 +182,13 @@ async function assertError(obj, errorOrErrors, opts) { ); } -async function assertErrorFree(html, opts) { +async function assertErrorFree(html: string, opts: Options = {}) { if (typeof html !== 'string') { throw new Error( "assertErrorFree expects a string; did you forget to remove the 'positioned' tag?", ); } - let warnings = []; + const warnings: EcmarkupError[] = []; await emu.build('test-example.emu', async () => html, { copyright: false, @@ -174,20 +200,24 @@ async function assertErrorFree(html, opts) { assert.deepStrictEqual(warnings, []); } -async function assertLint(a, b, opts = {}) { +async function assertLint( + a: PositionedInput, + b: ErrorDescriptor | ErrorDescriptor[], + opts: Options = {}, +) { await assertError(a, b, { lintSpec: true, ...opts }); } -async function assertLintFree(html, opts = {}) { +async function assertLintFree(html: string, opts: Options = {}) { await assertErrorFree(html, { lintSpec: true, ...opts }); } -async function getBiblio(html) { - let upstream = await emu.build('root.html', () => html, { +async function getBiblio(html: string): Promise { + const upstream = await emu.build('root.html', async () => html, { assets: 'none', location: 'https://example.com/spec/', }); - return upstream.exportBiblio(); + return upstream.exportBiblio()!; } export { diff --git a/tsconfig.json b/tsconfig.json index d079648b..e00507b4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "module": "commonjs", "moduleResolution": "node", "strict": true, + "noImplicitAny": true, "declaration": true, "stripInternal": true, "outDir": "lib", From 75e5d8d1442729ea935e02fabb4a4fdc34eed8da Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Wed, 28 Jan 2026 20:42:23 -0800 Subject: [PATCH 4/4] require node 24 --- .github/workflows/check.yml | 6 +++--- .github/workflows/enforce-format.yml | 6 +++--- .github/workflows/update-docs.yml | 6 +++--- package.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 2c757b72..f2806113 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -12,12 +12,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v6 - name: Setup node - uses: actions/setup-node@v2 + uses: actions/setup-node@v6 with: - node-version: '18' + node-version: '24' - name: Install dependencies run: npm ci diff --git a/.github/workflows/enforce-format.yml b/.github/workflows/enforce-format.yml index 71ecec4e..1d721faf 100644 --- a/.github/workflows/enforce-format.yml +++ b/.github/workflows/enforce-format.yml @@ -12,12 +12,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v6 - name: Setup node - uses: actions/setup-node@v2 + uses: actions/setup-node@v6 with: - node-version: '18' + node-version: '24' - name: Install dependencies run: npm ci diff --git a/.github/workflows/update-docs.yml b/.github/workflows/update-docs.yml index a3dca9a0..dc2c77b0 100644 --- a/.github/workflows/update-docs.yml +++ b/.github/workflows/update-docs.yml @@ -11,12 +11,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v6 - name: Setup node - uses: actions/setup-node@v2 + uses: actions/setup-node@v6 with: - node-version: '18' + node-version: '24' - name: Install dependencies run: npm ci diff --git a/package.json b/package.json index 7e9d57c1..9f79b624 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,6 @@ "printWidth": 100 }, "engines": { - "node": ">= 18" + "node": ">= 24" } }