From 0660785b8ce92336926e598230c417b6e806424e Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Sun, 25 Oct 2026 00:54:19 +0000 Subject: [PATCH] test: update test426-fixtures to fae1fa49815813088c433e8486e330e8d953a44 --- .../fixtures/test426/.github/workflows/ci.yml | 70 +++ test/fixtures/test426/.gitignore | 6 + test/fixtures/test426/README.md | 39 ++ .../debug-id/debug-id-index-section.map | 4 +- .../debug-id-index-section.map.golden | 2 +- .../decoding/debug-id/debug-id-index.map | 4 +- .../test426/decoding/debug-id/debug-id.d.ts | 2 +- test/fixtures/test426/npm-package/README.md | 5 + .../test426/npm-package/package.template.json | 26 ++ .../test426/npm-package/types/index.d.ts | 2 + .../test426/npm-package/types/modules.d.ts | 14 + .../test426/npm-package/types/types.d.ts | 90 ++++ test/fixtures/test426/package-lock.json | 405 ++++++++++++++++++ test/fixtures/test426/package.json | 11 + .../test426/scripts/build-npm-package.js | 240 +++++++++++ test/fixtures/test426/scripts/check-types.js | 150 +++++++ test/test426/README.md | 2 +- 17 files changed, 1065 insertions(+), 7 deletions(-) create mode 100644 test/fixtures/test426/.github/workflows/ci.yml create mode 100644 test/fixtures/test426/.gitignore create mode 100644 test/fixtures/test426/npm-package/README.md create mode 100644 test/fixtures/test426/npm-package/package.template.json create mode 100644 test/fixtures/test426/npm-package/types/index.d.ts create mode 100644 test/fixtures/test426/npm-package/types/modules.d.ts create mode 100644 test/fixtures/test426/npm-package/types/types.d.ts create mode 100644 test/fixtures/test426/package-lock.json create mode 100644 test/fixtures/test426/package.json create mode 100644 test/fixtures/test426/scripts/build-npm-package.js create mode 100644 test/fixtures/test426/scripts/check-types.js diff --git a/test/fixtures/test426/.github/workflows/ci.yml b/test/fixtures/test426/.github/workflows/ci.yml new file mode 100644 index 000000000000..72d33dddb54c --- /dev/null +++ b/test/fixtures/test426/.github/workflows/ci.yml @@ -0,0 +1,70 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: Validate tests and build npm package + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + # Fetch all to count number of commits and compute pkg version + fetch-depth: 0 + - uses: actions/setup-node@v6 + with: + node-version: lts/* + cache: npm + - name: Install dependencies + run: npm ci + - name: Build npm package + run: node scripts/build-npm-package.js + - name: Check that JSON files match their TypeScript types + run: node scripts/check-types.js + - name: Pack npm package + run: mkdir -p dist && npm pack ./npm-package --pack-destination dist + - name: Upload package tarball + uses: actions/upload-artifact@v7 + with: + name: npm-package + path: dist/*.tgz + if-no-files-found: error + retention-days: 7 + + publish: + name: Publish to npm + needs: build + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && github.repository == 'tc39/source-map-tests' + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write # for npm trusted publishing + concurrency: + group: npm-publish + cancel-in-progress: false + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - uses: actions/setup-node@v6 + with: + node-version: lts/* + registry-url: https://registry.npmjs.org + - name: Build npm package + run: node scripts/build-npm-package.js + - name: Publish + run: | + name=$(node -p "require('./npm-package/package.json').name") + version=$(node -p "require('./npm-package/package.json').version") + if [ -n "$(npm view "$name@$version" version 2>/dev/null)" ]; then + echo "$name@$version is already published, skipping." + else + npm publish ./npm-package --access public + fi diff --git a/test/fixtures/test426/.gitignore b/test/fixtures/test426/.gitignore new file mode 100644 index 000000000000..a311c506246a --- /dev/null +++ b/test/fixtures/test426/.gitignore @@ -0,0 +1,6 @@ +npm-package/* +!npm-package/README.md +!npm-package/package.template.json +!npm-package/types/ +node_modules/ +.tmp/ diff --git a/test/fixtures/test426/README.md b/test/fixtures/test426/README.md index 1eb2818fcfa9..98dcb05da245 100644 --- a/test/fixtures/test426/README.md +++ b/test/fixtures/test426/README.md @@ -58,3 +58,42 @@ More information about running Chrome Devtools without building Chromium can be Follow the [running Node.js tests guide](https://github.com/nodejs/node/blob/main/BUILDING.md#running-tests) to build Node.js and run tests locally. Read more about running the source map tests at [here](https://github.com/nodejs/node/tree/main/test/test426). + +## npm package + +The test cases are published to npm as [`@tc39/source-map-tests`](https://www.npmjs.com/package/@tc39/source-map-tests), so that source map implementations can run them in their own test suites. A new version is published for every commit to `main`. + +```sh +npm install --save-dev --save-exact @tc39/source-map-tests +``` + +### Usage + +`@tc39/source-map-tests/suites.json` lists the test suites: + +```json +{ + "suites": [ + { "kind": "interpreting", "file": "interpreting/source-map-spec.json" }, + { "kind": "decoding", "file": "decoding/scopes.json" } + ] +} +``` + +There are two types of test suites: `interpreting` and `decoding`: +- **interpreting** provided a source map to apply to a generated file, together with some actions to perform through it to check that it has been applied correctly. +- **decoding** suites check that a source map is correctly decoded into a [Decoded Source Map Record](https://tc39.es/ecma426/#decoded-source-map-record). + +### TypeScript + +The package's root exports the types of the JSON files: + +```ts +import type { InterpretingSuite, DecodingSuite, TestAction } from "@tc39/source-map-tests"; +``` + +With TypeScript 7.1 or later, you can load type definitions for the various suites by adding `"types": ["@tc39/source-map-tests"]` to your `tsconfig.json`. Imported JSON modules then will be typed appropriately: +```ts +import spec from "@tc39/source-map-tests/interpreting/source-map-spec.json" with { type: "json" }; +spec.tests[0].testActions; // TestAction[] +``` diff --git a/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map b/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map index 9c6f3fbde1ed..7a6d25afc73f 100644 --- a/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map +++ b/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map @@ -4,7 +4,7 @@ "sections": [{ "offset": { "line": 0, - "column": 0, + "column": 0 }, "map": { "version": 3, @@ -16,7 +16,7 @@ }, { "offset": { "line": 1, - "column": 0, + "column": 0 }, "map": { "version": 3, diff --git a/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map.golden b/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map.golden index 7c3a8456e6b0..69f6ebf9dcb9 100644 --- a/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map.golden +++ b/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map.golden @@ -1,5 +1,5 @@ { "file": "app.js", "sources": [], - "mappings": [], + "mappings": [] } \ No newline at end of file diff --git a/test/fixtures/test426/decoding/debug-id/debug-id-index.map b/test/fixtures/test426/decoding/debug-id/debug-id-index.map index 11de486e1a9a..0b7231929e4b 100644 --- a/test/fixtures/test426/decoding/debug-id/debug-id-index.map +++ b/test/fixtures/test426/decoding/debug-id/debug-id-index.map @@ -5,7 +5,7 @@ "sections": [{ "offset": { "line": 0, - "column": 0, + "column": 0 }, "map": { "version": 3, @@ -17,7 +17,7 @@ }, { "offset": { "line": 1, - "column": 0, + "column": 0 }, "map": { "version": 3, diff --git a/test/fixtures/test426/decoding/debug-id/debug-id.d.ts b/test/fixtures/test426/decoding/debug-id/debug-id.d.ts index 1f9862f012ec..526b584ca199 100644 --- a/test/fixtures/test426/decoding/debug-id/debug-id.d.ts +++ b/test/fixtures/test426/decoding/debug-id/debug-id.d.ts @@ -2,5 +2,5 @@ /// interface SourceMapRecord { - debugId: string|null; + debugId?: string|null; } diff --git a/test/fixtures/test426/npm-package/README.md b/test/fixtures/test426/npm-package/README.md new file mode 100644 index 000000000000..11012332c544 --- /dev/null +++ b/test/fixtures/test426/npm-package/README.md @@ -0,0 +1,5 @@ +# @tc39/source-map-tests + +Cross-implementation test cases for the [ECMA-426 source map specification](https://tc39.es/ecma426/). + +See the [tc39/source-map-tests README](https://github.com/tc39/source-map-tests#npm-package) for the package contents and usage. diff --git a/test/fixtures/test426/npm-package/package.template.json b/test/fixtures/test426/npm-package/package.template.json new file mode 100644 index 000000000000..68c0ae4ac298 --- /dev/null +++ b/test/fixtures/test426/npm-package/package.template.json @@ -0,0 +1,26 @@ +{ + "name": "@tc39/source-map-tests", + "version": "X.X.X", + "description": "Cross-implementation test cases for the ECMA-426 source map specification", + "license": "BSD-3-Clause", + "repository": { + "type": "git", + "url": "git+https://github.com/tc39/source-map-tests.git" + }, + "homepage": "https://github.com/tc39/source-map-tests#readme", + "bugs": "https://github.com/tc39/source-map-tests/issues", + "keywords": [ + "source-map", + "sourcemap", + "ecma-426", + "test-suite", + "tests" + ], + "files": [ + "suites.json", + "interpreting/", + "decoding/", + "types/", + "LICENSE.md" + ] +} diff --git a/test/fixtures/test426/npm-package/types/index.d.ts b/test/fixtures/test426/npm-package/types/index.d.ts new file mode 100644 index 000000000000..658794c6f1d0 --- /dev/null +++ b/test/fixtures/test426/npm-package/types/index.d.ts @@ -0,0 +1,2 @@ +/// +export * from "./types.js"; diff --git a/test/fixtures/test426/npm-package/types/modules.d.ts b/test/fixtures/test426/npm-package/types/modules.d.ts new file mode 100644 index 000000000000..08300745affe --- /dev/null +++ b/test/fixtures/test426/npm-package/types/modules.d.ts @@ -0,0 +1,14 @@ +declare module "@tc39/source-map-tests/suites*.json" with { type: "json" } { + const index: import("./types.js").SuitesIndex; + export default index; +} + +declare module "@tc39/source-map-tests/interpreting/*.json" with { type: "json" } { + const suite: import("./types.js").InterpretingSuite; + export default suite; +} + +declare module "@tc39/source-map-tests/decoding/*.json" with { type: "json" } { + const suite: import("./types.js").DecodingSuite; + export default suite; +} diff --git a/test/fixtures/test426/npm-package/types/types.d.ts b/test/fixtures/test426/npm-package/types/types.d.ts new file mode 100644 index 000000000000..7cbe9d48892c --- /dev/null +++ b/test/fixtures/test426/npm-package/types/types.d.ts @@ -0,0 +1,90 @@ +/** + * Types for the JSON files in this package. + * + * All paths are relative to the package root. + */ + +/** The format of `suites.json`. */ +export interface SuitesIndex { + suites: SuiteInfo[]; +} + +export type SuiteInfo = + | { kind: "interpreting"; file: string } + | { kind: "decoding"; file: string }; + +/** A file from the test suite, with its contents inlined. */ +export interface TestFile { + path: string; + content: string; +} + +/** The format of interpreting suite files (`interpreting/.json`). */ +export interface InterpretingSuite { + tests: InterpretingTest[]; +} + +export interface InterpretingTest { + name: string; + description: string; + sourceMapIsValid: boolean; + testActions: TestAction[]; + /** + * The transpiled source that the source map refers to. + */ + baseFile: TestFile; + sourceMapFile: TestFile; +} + +export type TestAction = + | CheckMappingAction + | CheckMappingTransitiveAction + | CheckIgnoreListAction; + +export interface CheckMappingAction { + actionType: "checkMapping"; + /** 0-based. */ + generatedLine: number; + /** 0-based. */ + generatedColumn: number; + originalSource: string | null; + /** 0-based. */ + originalLine: number | null; + /** 0-based. */ + originalColumn: number | null; + mappedName: string | null; +} + +export interface CheckMappingTransitiveAction + extends Omit { + actionType: "checkMappingTransitive"; + /** Source maps to apply after `sourceMapFile`, in order. */ + intermediateMaps: TestFile[]; +} + +export interface CheckIgnoreListAction { + actionType: "checkIgnoreList"; + /** Sources that must be in the ignore list. */ + present: string[]; +} + +/** The format of decoding suite files (`decoding/.json`). */ +export interface DecodingSuite { + tests: DecodingTest[]; +} + +export interface DecodingTest { + name: string; + sourceMapFile: TestFile; + /** + * The expected Decoded Source Map Record, as JSON in the format described by + * `decoding/source-map-record.d.ts` (and extended by the proposal-specific + * `decoding//.d.ts` files). + */ + goldenFile: TestFile; + /** + * Whether decoding this source map should optionally report an error + * (https://tc39.es/ecma426/#sec-optional-errors). + */ + optionalErrors: boolean; +} diff --git a/test/fixtures/test426/package-lock.json b/test/fixtures/test426/package-lock.json new file mode 100644 index 000000000000..daf25a482c9f --- /dev/null +++ b/test/fixtures/test426/package-lock.json @@ -0,0 +1,405 @@ +{ + "name": "source-map-tests", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "typescript": "7.1.0-beta" + } + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.1.0-beta.tgz", + "integrity": "sha512-DXDG+jIWxBHGdoO70tcHON33rCnSzsEZj9P13oyf/r/KEGIo6awsdSNVNjAw0uMwEpqRu75IrgCcW58YcVkZHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-android-arm64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-android-arm64/-/typescript-android-arm64-7.1.0-beta.tgz", + "integrity": "sha512-UZx6PxoQavuvaVTrSf3aw0Oy/J/uJpRylipA0kzc038jMvZXh0UtE8vnQp20FqtdgD5bhXZ0aJrRJyoTSUO58Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.1.0-beta.tgz", + "integrity": "sha512-q7mxblcyX0E7hXsY4k+Q6KWKK5mGmfVq+BDesdTgRVgjodk27aIxER77Zyj9+TgSTlCje8F7+JM6f6hl2BuNdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.1.0-beta.tgz", + "integrity": "sha512-D5EYqG5Wm/guw9+0j8+Mn1Jbr/gfS/VtFTuoH8PJtFbcqeDaQGMulc7SW8q51QG6Y3wGOGX3nbsTOw6fIKWWAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.1.0-beta.tgz", + "integrity": "sha512-9A0XX0tcnH9qPdolSKKzSjTb93dlB0UEcAJbHYEsrFX2fkFNSum9+dvkQQJ9vLc+7vxHetDPujq9Qe6wx+Xhow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.1.0-beta.tgz", + "integrity": "sha512-EDf/dBHfY0TxX3NmUTek0hbffeyZlh3+ls6WQW3ICB8vYxwYLFnzg7wkgEUvl9eRlpiJ/nmc2S8arPMzINJ7rg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.1.0-beta.tgz", + "integrity": "sha512-AvN9HmzOQh46G4312ooSgMHhbB7DtnLwg8D9mo324X49B7weMwtJrBG1MxT9IyI8kzDd7YRtW+1hWGun7WG7Qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.1.0-beta.tgz", + "integrity": "sha512-hNqQBgQ5b5oGqN76QqbAU6CwYhJZ6iOcWc2kPTUs2OentgpxZktETziH6KIyRFJPaiBncognwn2ZLu3DNhHiWw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.1.0-beta.tgz", + "integrity": "sha512-xbUustlBrFflt2pNEyG5ye+d4WGPc/rfr2xK29MXeobPSw297JqwuKRrvbKM5o3fE/eUM/c+urVm6jQ3IaMVTw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.1.0-beta.tgz", + "integrity": "sha512-fS4/G7/0bXZ3NSq3nBMAXyg65I3tO4S7j08VNQ2g1aNC/qq79gE7Qne1BNjVsjMFfHBVBFjQYoTQCjmVU2iFlg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.1.0-beta.tgz", + "integrity": "sha512-0upCCxYPX8X6gOwkvfv+Ee7/ufyy7k97mTrGE0e5BxLlnUEa+w+rtgnv2dsKcxFdwdeZYVlZKCqgN/xCBV08+A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.1.0-beta.tgz", + "integrity": "sha512-0jKU9YRgOPRgiy5V/UWv37kB53PaTpIbwGCbbOMUPPAkrjVRNOIAJaz7Wa7s3H7mdDebX1LopUEPEJR+7Xa7zg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.1.0-beta.tgz", + "integrity": "sha512-j+d04s2kxaPQh6DLPb01sknAQfzsrdSZy15jBRHyLEZoaaQ19dVjbFRlkNVvYwUG7ZdP8+y5b/7RmPxdtHUKdQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.1.0-beta.tgz", + "integrity": "sha512-kcD51ihf+pnI5KT4uQQYG04OpwznaZYBlIQhDhl8fflFd27NktxXsUeguxZyvUMyWfum7IF2bBS6Jv7ox0AgFQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.1.0-beta.tgz", + "integrity": "sha512-8W85YyoGK+rgGoSU9jJQUpELbrKcZcbdJk2jPNQ6UKDKI7ryPlPFTrV2jCE5RQuv4Tt96hMDTc33s4YfRrLFBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.1.0-beta.tgz", + "integrity": "sha512-iGsTE0ve2PG0AcyZinwAR0LnGEZhu/y4dceS0hZsYCaKdYETvQtvtlNWlmYKempsBr6C7Il7dMXHVk+VFm9u0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.1.0-beta.tgz", + "integrity": "sha512-Z2FERdHB9fAKI+KGqdWn04O7uNlnB+VYiEm6INE1aZuzzLNd3KBqQ6IpKUJF2ny4GigGU6nVu75XD9tAUODPng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.1.0-beta.tgz", + "integrity": "sha512-NVG3qeyCq/rJEOhaRjR3LsWgjrjo5vJW25omVLeHAVnpKIG4pE4cenwwKGR06DiJ1s0QTOymcJeKXMEpsiNBVg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.1.0-beta.tgz", + "integrity": "sha512-ovAdoWXEn2B5/fyWG69pLU3FWmQwPWOjQV5TFbcxtpnOv+R3g8ySGHl1NYRq7Lb/GYdFDvaGcZ+SiU7iKuiMsA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.1.0-beta.tgz", + "integrity": "sha512-lJbo71IEOfL7ZZIZnArRaOLqjn9BAfoSzyUQn0aSjfSpDZL4ru0afUsdBfo6PQEjD6ssdmi0n/RwaPADX6bsvQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.1.0-beta.tgz", + "integrity": "sha512-UKcHvbybY2tDTvFW2b1vwiAvDlIRooo69lh8CXWVhOQejcvpM5+IgbiFW9EIwR5Wi5X/v+BdN7gdWbigBuJ3rw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/typescript": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.1.0-beta.tgz", + "integrity": "sha512-cjWjZAJDsmZzsNC5GwHNlW0YPM65jZa5oR0KyQcYX3KSVIfTvtBq//vewISyatKk4OLi5grZjHhIVE9fdrw1UQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.1.0-beta", + "@typescript/typescript-android-arm64": "7.1.0-beta", + "@typescript/typescript-darwin-arm64": "7.1.0-beta", + "@typescript/typescript-darwin-x64": "7.1.0-beta", + "@typescript/typescript-freebsd-arm64": "7.1.0-beta", + "@typescript/typescript-freebsd-x64": "7.1.0-beta", + "@typescript/typescript-linux-arm": "7.1.0-beta", + "@typescript/typescript-linux-arm64": "7.1.0-beta", + "@typescript/typescript-linux-loong64": "7.1.0-beta", + "@typescript/typescript-linux-mips64el": "7.1.0-beta", + "@typescript/typescript-linux-ppc64": "7.1.0-beta", + "@typescript/typescript-linux-riscv64": "7.1.0-beta", + "@typescript/typescript-linux-s390x": "7.1.0-beta", + "@typescript/typescript-linux-x64": "7.1.0-beta", + "@typescript/typescript-netbsd-arm64": "7.1.0-beta", + "@typescript/typescript-netbsd-x64": "7.1.0-beta", + "@typescript/typescript-openbsd-arm64": "7.1.0-beta", + "@typescript/typescript-openbsd-x64": "7.1.0-beta", + "@typescript/typescript-sunos-x64": "7.1.0-beta", + "@typescript/typescript-win32-arm64": "7.1.0-beta", + "@typescript/typescript-win32-x64": "7.1.0-beta" + } + } + } +} diff --git a/test/fixtures/test426/package.json b/test/fixtures/test426/package.json new file mode 100644 index 000000000000..f760360f8fe1 --- /dev/null +++ b/test/fixtures/test426/package.json @@ -0,0 +1,11 @@ +{ + "private": true, + "type": "module", + "scripts": { + "build": "node scripts/build-npm-package.js", + "check-types": "node scripts/build-npm-package.js && node scripts/check-types.js" + }, + "devDependencies": { + "typescript": "7.1.0-beta" + } +} diff --git a/test/fixtures/test426/scripts/build-npm-package.js b/test/fixtures/test426/scripts/build-npm-package.js new file mode 100644 index 000000000000..7570fefce1d6 --- /dev/null +++ b/test/fixtures/test426/scripts/build-npm-package.js @@ -0,0 +1,240 @@ +// Builds the contents for the @tc39/source-map-tests npm package, +// in the `npm-package` folder. + +import { execFileSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; + +const root = path.resolve(import.meta.dirname, ".."); +const outDir = path.join(root, "npm-package"); +const committedFiles = new Set([ + "README.md", + "package.template.json", + "types", +]); + +const errors = []; +const error = msg => errors.push(msg); + +const rel = abs => path.relative(root, abs).split(path.sep).join("/"); +const abs = relPath => path.join(root, relPath); +const out = relPath => path.join(outDir, relPath); + +function walk(dir) { + return fs + .readdirSync(dir, { withFileTypes: true }) + .flatMap(e => + e.isDirectory() ? walk(path.join(dir, e.name)) : [path.join(dir, e.name)] + ); +} + +/** + * Returns `{ path, content }` for a repo-relative path, or null if missing. + * `packagePath` is where the file ends up in the npm package. + */ +function fileEntry(relPath, context, packagePath = relPath) { + if (!fs.existsSync(abs(relPath))) { + error(`${context}: referenced file ${relPath} does not exist`); + return null; + } + return { path: packagePath, content: fs.readFileSync(abs(relPath), "utf8") }; +} + +function checkUniqueNames(suiteName, tests) { + const seen = new Set(); + for (const { name } of tests) { + if (seen.has(name)) error(`${suiteName}: duplicate test name ${name}`); + seen.add(name); + } +} + +// Interpreting suites: [-proposal]-tests.json manifests in the repo root, +// whose files live in resources/. In the package, resources/ +// is copied to interpreting/resources/. + +const referencedResources = new Set(); + +function buildInterpretingSuite(manifestFile) { + const name = manifestFile.replace(/-tests\.json$/, "").replace(/-proposal$/, ""); + const manifest = JSON.parse(fs.readFileSync(abs(manifestFile), "utf8")); + const base = path.posix.join("resources", manifest.resourceBasePath ?? ""); + const resource = (file, context) => { + const relPath = path.posix.join(base, file); + referencedResources.add(relPath); + return fileEntry(relPath, context, `interpreting/${relPath}`); + }; + + const tests = manifest.tests.map(test => { + const context = `${manifestFile} (${test.name})`; + return { + name: test.name, + description: test.description, + sourceMapIsValid: test.sourceMapIsValid, + baseFile: resource(test.baseFile, context), + sourceMapFile: resource(test.sourceMapFile, context), + testActions: (test.testActions ?? []).map(action => + action.intermediateMaps + ? { + ...action, + intermediateMaps: action.intermediateMaps.map(m => + resource(m, context) + ), + } + : action + ), + }; + }); + checkUniqueNames(name, tests); + return { name, kind: "interpreting", tests }; +} + +// Decoding suites: each directory under decoding/ containing .map files, +// each with a .map.golden and an optional .error/.map.error. + +function buildDecodingSuite(dir) { + const name = path.basename(dir); + const files = fs.readdirSync(dir).sort(); + const used = new Set(); + const tests = []; + + for (const file of files.filter(f => f.endsWith(".map"))) { + const testName = file.slice(0, -".map".length); + const context = `${rel(dir)} (${testName})`; + const sourceMapFile = fileEntry(rel(path.join(dir, file)), context); + const goldenFile = fileEntry(rel(path.join(dir, `${file}.golden`)), context); + const errorFile = [`${testName}.error`, `${file}.error`].find(f => + files.includes(f) + ); + used.add(file).add(`${file}.golden`).add(errorFile); + + if (goldenFile) { + try { + JSON.parse(goldenFile.content); + } catch (e) { + error(`${goldenFile.path}: invalid JSON (${e.message})`); + } + } + tests.push({ + name: testName, + sourceMapFile, + goldenFile, + optionalErrors: errorFile !== undefined, + }); + } + + for (const file of files) { + if (/\.(golden|error)$/.test(file) && !used.has(file)) { + error(`${rel(path.join(dir, file))}: no matching .map file`); + } + } + + checkUniqueNames(name, tests); + return { name, kind: "decoding", tests }; +} + +// Stable spec tests come first, followed by proposals. +const isProposal = f => f.endsWith("-proposal-tests.json"); +const interpretingSuites = fs + .readdirSync(root) + .filter(f => f.endsWith("-tests.json")) + .sort((a, b) => isProposal(a) - isProposal(b) || a.localeCompare(b)) + .map(buildInterpretingSuite); + +const decodingDirs = [ + path.join(root, "decoding"), + ...fs + .readdirSync(path.join(root, "decoding"), { withFileTypes: true }) + .filter(e => e.isDirectory()) + .map(e => path.join(root, "decoding", e.name)) + .sort(), +]; +const decodingSuites = decodingDirs + .filter(dir => fs.readdirSync(dir).some(f => f.endsWith(".map"))) + .map(buildDecodingSuite); + +const suites = [...interpretingSuites, ...decodingSuites]; + +// Suites go in /.json, next to the directory with their files. +const suiteFile = ({ name, kind }) => `${kind}/${name}.json`; + +const suiteFiles = new Set(); +for (const suite of suites) { + const file = suiteFile(suite); + if (suiteFiles.has(file)) error(`two suites would be written to ${file}`); + suiteFiles.add(file); +} + +for (const file of walk(path.join(root, "resources"))) { + if (file.endsWith(".map") && !referencedResources.has(rel(file))) { + error(`${rel(file)}: source map not referenced by any test`); + } +} + +if (errors.length > 0) { + console.error(`Found ${errors.length} problem(s) in the test data:`); + for (const msg of errors) console.error(` - ${msg}`); + process.exit(1); +} + +// Write the package. + +const writeJSON = (relPath, data) => { + fs.mkdirSync(path.dirname(out(relPath)), { recursive: true }); + fs.writeFileSync(out(relPath), JSON.stringify(data, null, 2) + "\n"); +}; + +const git = (...gitArgs) => + execFileSync("git", gitArgs, { cwd: root, encoding: "utf8" }).trim(); +const version = `0.0.${git("rev-list", "--count", "HEAD")}`; +const gitHead = git("rev-parse", "HEAD"); + +for (const entry of fs.readdirSync(outDir)) { + if (!committedFiles.has(entry)) { + fs.rmSync(out(entry), { recursive: true, force: true }); + } +} + +fs.cpSync(abs("resources"), out("interpreting/resources"), { recursive: true }); +fs.cpSync(abs("decoding"), out("decoding"), { recursive: true }); +fs.copyFileSync(abs("LICENSE.md"), out("LICENSE.md")); + +for (const suite of suites) { + writeJSON(suiteFile(suite), { tests: suite.tests }); +} +const packageJson = JSON.parse( + fs.readFileSync(out("package.template.json"), "utf8") +); + +const decodingTypes = walk(abs("decoding")) + .filter(f => f.endsWith(".d.ts")) + .map(rel) + .sort(); +const exportedFiles = [ + "package.json", + "suites.json", + ...suites.map(suiteFile), + ...decodingTypes, +]; +writeJSON("package.json", { + ...packageJson, + version, + gitHead, + exports: { + ".": { + "types@>=7.1.0-0": "./types/index.d.ts", + types: "./types/types.d.ts", + }, + ...Object.fromEntries(exportedFiles.map(f => [`./${f}`, `./${f}`])), + }, +}); +writeJSON("suites.json", { + suites: suites.map(suite => ({ + kind: suite.kind, + file: suiteFile(suite), + })), +}); + +console.log(`Built ${packageJson.name}@${version} in ${rel(outDir)}/`); +for (const { name, kind, tests } of suites) { + console.log(` ${name} (${kind}): ${tests.length} tests`); +} diff --git a/test/fixtures/test426/scripts/check-types.js b/test/fixtures/test426/scripts/check-types.js new file mode 100644 index 000000000000..7c5cbf0d67e6 --- /dev/null +++ b/test/fixtures/test426/scripts/check-types.js @@ -0,0 +1,150 @@ +// Checks that the JSON files in the generated npm package match +// the TS definitions. Run build-npm-package.js first. + +import { spawnSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; + +const root = path.resolve(import.meta.dirname, ".."); +const outDir = path.join(root, ".tmp", "check-types"); +const tsc = path.join(root, "node_modules", ".bin", "tsc"); + +const compilerOptions = { + strict: true, + noEmit: true, + skipLibCheck: false, + module: "nodenext", + moduleResolution: "nodenext", + target: "esnext", + types: [], +}; + +const relFrom = (from, to) => { + const rel = path.relative(path.dirname(from), to).split(path.sep).join("/"); + return rel.startsWith(".") ? rel : `./${rel}`; +}; + +function writeCheck(tsFile, header, type, jsonFile) { + const json = fs.readFileSync(jsonFile, "utf8"); + fs.mkdirSync(path.dirname(tsFile), { recursive: true }); + fs.writeFileSync( + tsFile, + `${header}\nexport default ${json.trim()} satisfies ${type};\n` + ); +} + +function runTsc(name, projectDir, files) { + const tsconfig = path.join(projectDir, "tsconfig.json"); + fs.writeFileSync(tsconfig, JSON.stringify({ compilerOptions, files }, null, 2)); + const { status } = spawnSync(tsc, ["-p", tsconfig, "--pretty"], { + stdio: "inherit", + }); + console.log(`${status === 0 ? "ok" : "FAIL"} ${name}`); + return status === 0; +} + +fs.rmSync(outDir, { recursive: true, force: true }); +let ok = true; + +// npm package files. + +const pkgDir = path.join(root, "npm-package"); +if (!fs.existsSync(path.join(pkgDir, "suites.json"))) { + console.error("npm-package/suites.json not found, run scripts/build-npm-package.js first."); + process.exit(1); +} +{ + const projectDir = path.join(outDir, "npm-package"); + const typesFile = path.join(pkgDir, "types", "types.d.ts"); + const files = []; + const check = (jsonRel, type) => { + const tsFile = path.join(projectDir, `${jsonRel}.ts`); + const header = `import type { ${type} } from "${relFrom(tsFile, typesFile)}";`; + writeCheck(tsFile, header, type, path.join(pkgDir, jsonRel)); + files.push(path.relative(projectDir, tsFile)); + }; + + const index = JSON.parse(fs.readFileSync(path.join(pkgDir, "suites.json"), "utf8")); + const expectedTypes = new Map([["suites.json", "SuitesIndex"]]); + for (const { kind, file } of index.suites) { + expectedTypes.set(file, kind === "interpreting" ? "InterpretingSuite" : "DecodingSuite"); + } + for (const [file, type] of expectedTypes) check(file, type); + + // Check that importing each exported JSON file `with { type: "json" }` gets + // its declared type from modules.d.ts. + const { name: pkgName, exports } = JSON.parse( + fs.readFileSync(path.join(pkgDir, "package.json"), "utf8") + ); + const exportedJSON = Object.keys(exports) + .filter(key => key.endsWith(".json") && key !== "./package.json") + .map(key => key.slice(2)); + const importsFile = path.join(projectDir, "imports.ts"); + const typeNames = [...new Set(expectedTypes.values())]; + fs.writeFileSync( + importsFile, + [ + `import type { ${typeNames.join(", ")} } from "${relFrom(importsFile, path.join(pkgDir, "types", "index.js"))}";`, + "type Equals = (() => T extends A ? 1 : 2) extends (() => T extends B ? 1 : 2) ? true : false;", + ...exportedJSON.flatMap((file, i) => { + const type = expectedTypes.get(file); + if (!type) throw new Error(`Exported ${file} is not a known suite file`); + return [ + `import json${i} from "${pkgName}/${file}" with { type: "json" };`, + `export const check${i}: Equals = true; // ${file}`, + ]; + }), + ].join("\n") + "\n" + ); + files.push(path.relative(projectDir, importsFile)); + + ok = runTsc("npm-package", projectDir, files) && ok; + + // Check that each exported decoding/**/*.d.ts file can be loaded through the + // package's exports. Each one is checked separately, because they extend + // the same global interfaces. + for (const key of Object.keys(exports).filter(k => k.endsWith(".d.ts"))) { + const name = `npm-package ${key.slice(2)}`; + const dtsDir = path.join(outDir, "exports", key.slice(2)); + const linkDir = path.join(dtsDir, "node_modules", ...pkgName.split("/")); + fs.mkdirSync(path.dirname(linkDir), { recursive: true }); + fs.symlinkSync(pkgDir, linkDir, "dir"); + fs.writeFileSync( + path.join(dtsDir, "index.ts"), + `/// \nexport type T = SourceMapRecord;\n` + ); + ok = runTsc(name, dtsDir, ["index.ts"]) && ok; + } +} + +// Check golden files + +const decodingDir = path.join(root, "decoding"); +const decodingSuiteDirs = [ + decodingDir, + ...fs + .readdirSync(decodingDir, { withFileTypes: true }) + .filter(e => e.isDirectory()) + .map(e => path.join(decodingDir, e.name)), +]; +for (const dir of decodingSuiteDirs) { + const goldens = fs.readdirSync(dir).filter(f => f.endsWith(".golden")).sort(); + if (goldens.length === 0) continue; + + const name = path.relative(root, dir).split(path.sep).join("/"); + const projectDir = path.join(outDir, name); + const suiteTypes = path.join(dir, `${path.basename(dir)}.d.ts`); + const typesFile = fs.existsSync(suiteTypes) + ? suiteTypes + : path.join(decodingDir, "source-map-record.d.ts"); + + const files = goldens.map(golden => { + const tsFile = path.join(projectDir, `${golden}.ts`); + const header = `/// `; + writeCheck(tsFile, header, "SourceMapRecord", path.join(dir, golden)); + return path.relative(projectDir, tsFile); + }); + ok = runTsc(name, projectDir, files) && ok; +} + +process.exit(ok ? 0 : 1); diff --git a/test/test426/README.md b/test/test426/README.md index 6935fc43c3d2..85db408434bc 100644 --- a/test/test426/README.md +++ b/test/test426/README.md @@ -7,7 +7,7 @@ suite ensures that the Node.js source map implementation conforms to the The [`test/fixtures/test426`](../fixtures/test426/) contains a copy of the set of [Source Map Tests][] suite. The last updated hash is: -* +* See the json files in [the `status` folder](./status) for prerequisites, expected failures, and support status for specific tests.