From 6936c4a76f74f10fa174a450ddb264f0d3249c33 Mon Sep 17 00:00:00 2001 From: Ryuya Date: Tue, 21 Jul 2026 21:49:54 -0700 Subject: [PATCH] fix: preserve type-only baseline declarations safely --- .gitattributes | 1 + README.md | 9 +- deploy/package-registry.mjs | 2 +- deploy/readmes/baseline.md | 7 +- derived/current/classification.json | 247 ++++++++--- derived/current/generation.json | 88 +++- derived/current/inventory.json | 7 +- .../reference/libBaseline.errors.txt | 6 +- .../tests/baselines/reference/libBaseline.js | 2 + .../baselines/reference/libBaseline.symbols | 4 + .../baselines/reference/libBaseline.types | 16 + .../tests/cases/compiler/libBaseline.ts | 1 + generated/current/baseline.d.ts | 408 ++++++++++++++++++ lib/classifier.mjs | 3 +- lib/generator.mjs | 125 +++++- lib/negative-probes.mjs | 18 + lib/surface-inventory.mjs | 58 ++- package.json | 2 +- test/classifier.test.mjs | 17 + test/consumer-smoke.test.mjs | 2 - test/excluded-units.test.mjs | 47 ++ test/generate.test.mjs | 46 +- test/packed-consumer-smoke.test.mjs | 2 - test/surface-inventory.test.mjs | 85 ++++ test/type-only-consumer.test.mjs | 90 ++++ 25 files changed, 1202 insertions(+), 91 deletions(-) create mode 100644 .gitattributes create mode 100644 test/type-only-consumer.test.mjs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d38f0d9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +fixtures/typescript/tests/baselines/reference/* -text whitespace=cr-at-eol diff --git a/README.md b/README.md index 5cc9d42..dd93d8f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # TypeScript Baseline Lib Generator -Generates `baseline.d.ts`, a TypeScript lib that contains only the JavaScript built-ins that are [Baseline widely available](https://web.dev/baseline). It classifies the `javascript.builtins.*` compat rows from `web-features` and emits the declarations whose `baselineStatus` is `"high"`. +Generates `baseline.d.ts`, a TypeScript lib for TypeScript-declarable JavaScript features that are [Baseline widely available](https://web.dev/baseline). It currently classifies `javascript.builtins.*` and the `arguments` object from `web-features`. ## Using the lib @@ -14,18 +14,17 @@ npm install --save-dev typescript-baseline-lib { "compilerOptions": { "noLib": true, - "skipLibCheck": true, "types": ["typescript-baseline-lib"] } } ``` -Now only Baseline widely available built-ins type-check. APIs that haven't reached Baseline yet (`Promise.withResolvers`, `Array.fromAsync` until it promotes, and so on) are reported as errors. The end goal is first-class `--lib baseline` support upstream in TypeScript. +Now only the supported Baseline widely available JavaScript surfaces type-check. APIs that haven't reached Baseline yet (`Promise.withResolvers`, `Array.fromAsync` until it promotes, and so on) are reported as errors. The end goal is first-class `--lib baseline` support upstream in TypeScript. ## Current contract - Target is `baseline` only. -- Scope is `javascript.builtins.*` only. +- Scope is TypeScript-declarable JavaScript surfaces: `javascript.builtins.*` plus the `arguments` object. - DOM, Web Worker, syntax, grammar, statements, and operators are out of scope. - Special compat rows are managed in `registry/compat-management.json` with a source URL for each. -- The checked-in dataset, derived, and generated artifacts are overwritten with the rolling latest; history lives in Git. \ No newline at end of file +- The checked-in dataset, derived, and generated artifacts are overwritten with the rolling latest; history lives in Git. diff --git a/deploy/package-registry.mjs b/deploy/package-registry.mjs index cd0a5d0..583d254 100644 --- a/deploy/package-registry.mjs +++ b/deploy/package-registry.mjs @@ -11,7 +11,7 @@ export const deployGeneratedDirectory = path.join(deployDirectory, "generated"); export const baselinePackage = { id: "baseline", name: "typescript-baseline-lib", - description: "Baseline widely available JavaScript built-in declarations for TypeScript.", + description: "Baseline widely available JavaScript declarations for TypeScript.", initialVersion: "0.0.1", license: "Apache-2.0", keywords: [ diff --git a/deploy/readmes/baseline.md b/deploy/readmes/baseline.md index 25ce5d9..d05c9b7 100644 --- a/deploy/readmes/baseline.md +++ b/deploy/readmes/baseline.md @@ -2,7 +2,7 @@ Generated `baseline` declarations for TypeScript. -This package is produced by the [`TypeScript-Baseline-lib-generator`](https://github.com/3ru/TypeScript-Baseline-lib-generator) repo. It packages the JavaScript built-ins that are [Baseline widely available](https://web.dev/baseline) (`baselineStatus === "high"`) as a single global declaration bundle. +This package is produced by the [`TypeScript-Baseline-lib-generator`](https://github.com/3ru/TypeScript-Baseline-lib-generator) repo. It packages TypeScript-declarable JavaScript features that are [Baseline widely available](https://web.dev/baseline) as a single global declaration bundle. Current snapshot: @@ -26,16 +26,15 @@ npm install --save-dev {{PACKAGE_NAME}} { "compilerOptions": { "noLib": true, - "skipLibCheck": true, "types": ["{{PACKAGE_NAME}}"] } } ``` -Now only Baseline widely available built-ins type-check; APIs that haven't reached Baseline yet are reported as errors. +Now only the supported Baseline widely available JavaScript surfaces type-check; APIs that haven't reached Baseline yet are reported as errors. ## Notes - The public surface is a single `baseline` lib. -- The current scope is `javascript.builtins.*` only. +- The current scope is `javascript.builtins.*` plus the `arguments` object. - The generated declarations are derived from the npm `typescript` package and preserve the upstream Microsoft license notice inside `baseline.d.ts`. diff --git a/derived/current/classification.json b/derived/current/classification.json index 1a514f4..2dc6a7d 100644 --- a/derived/current/classification.json +++ b/derived/current/classification.json @@ -7,12 +7,12 @@ "summary": { "featureCount": 121, "compatCount": 1356, - "libCompatCount": 1146, - "highCompatCount": 747, + "libCompatCount": 1150, + "highCompatCount": 750, "lowCompatCount": 77, - "falseCompatCount": 322, - "includedCompatCount": 747, - "emitCompatCount": 995, + "falseCompatCount": 323, + "includedCompatCount": 750, + "emitCompatCount": 998, "notModeledUpstreamCount": 64, "alreadyExcludedUpstreamCount": 1, "managedCompatCount": 68, @@ -33,7 +33,7 @@ "settled": 16, "tracked": 46 }, - "selectedUnitCount": 1342, + "selectedUnitCount": 1407, "transformedUnitCount": 2 }, "compatManagementRegistryPath": "registry/compat-management.json", @@ -153,7 +153,8 @@ "resolvedUnitIds": [ "lib.es5.d.ts::Array::401", "lib.es5.d.ts::Array::443", - "lib.es5.d.ts::ArrayConstructor::434" + "lib.es5.d.ts::ArrayConstructor::434", + "lib.es5.d.ts::ReadonlyArray::370" ], "transforms": [], "notes": "compat root Array selects standalone declaration units" @@ -175,7 +176,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2015.iterable.d.ts::Array.@@iterator::32" + "lib.es2015.iterable.d.ts::Array.@@iterator::32", + "lib.es2015.iterable.d.ts::ReadonlyArray.@@iterator::42" ], "transforms": [], "notes": "member surface Array.@@iterator" @@ -215,7 +217,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2015.symbol.wellknown.d.ts::Array.@@unscopables::18" + "lib.es2015.symbol.wellknown.d.ts::Array.@@unscopables::18", + "lib.es2015.symbol.wellknown.d.ts::ReadonlyArray.@@unscopables::21" ], "transforms": [], "notes": "member surface Array.@@unscopables" @@ -264,7 +267,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2022.array.d.ts::Array.at::2" + "lib.es2022.array.d.ts::Array.at::2", + "lib.es2022.array.d.ts::ReadonlyArray.at::5" ], "transforms": [], "notes": "member surface Array.at" @@ -287,7 +291,9 @@ "resolutionKind": "member", "resolvedUnitIds": [ "lib.es5.d.ts::Array.concat::407", - "lib.es5.d.ts::Array.concat::408" + "lib.es5.d.ts::Array.concat::408", + "lib.es5.d.ts::ReadonlyArray.concat::374", + "lib.es5.d.ts::ReadonlyArray.concat::375" ], "transforms": [], "notes": "member surface Array.concat" @@ -331,7 +337,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2015.iterable.d.ts::Array.entries::33" + "lib.es2015.iterable.d.ts::Array.entries::33", + "lib.es2015.iterable.d.ts::ReadonlyArray.entries::43" ], "transforms": [], "notes": "member surface Array.entries" @@ -354,7 +361,9 @@ "resolutionKind": "member", "resolvedUnitIds": [ "lib.es5.d.ts::Array.every::419", - "lib.es5.d.ts::Array.every::420" + "lib.es5.d.ts::Array.every::420", + "lib.es5.d.ts::ReadonlyArray.every::380", + "lib.es5.d.ts::ReadonlyArray.every::381" ], "transforms": [], "notes": "member surface Array.every" @@ -399,7 +408,9 @@ "resolutionKind": "member", "resolvedUnitIds": [ "lib.es5.d.ts::Array.filter::424", - "lib.es5.d.ts::Array.filter::425" + "lib.es5.d.ts::Array.filter::425", + "lib.es5.d.ts::ReadonlyArray.filter::385", + "lib.es5.d.ts::ReadonlyArray.filter::386" ], "transforms": [], "notes": "member surface Array.filter" @@ -422,7 +433,9 @@ "resolutionKind": "member", "resolvedUnitIds": [ "lib.es2015.core.d.ts::Array.find::2", - "lib.es2015.core.d.ts::Array.find::3" + "lib.es2015.core.d.ts::Array.find::3", + "lib.es2015.core.d.ts::ReadonlyArray.find::61", + "lib.es2015.core.d.ts::ReadonlyArray.find::62" ], "transforms": [], "notes": "member surface Array.find" @@ -444,7 +457,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2015.core.d.ts::Array.findIndex::4" + "lib.es2015.core.d.ts::Array.findIndex::4", + "lib.es2015.core.d.ts::ReadonlyArray.findIndex::63" ], "transforms": [], "notes": "member surface Array.findIndex" @@ -467,7 +481,9 @@ "resolutionKind": "member", "resolvedUnitIds": [ "lib.es2023.array.d.ts::Array.findLast::2", - "lib.es2023.array.d.ts::Array.findLast::3" + "lib.es2023.array.d.ts::Array.findLast::3", + "lib.es2023.array.d.ts::ReadonlyArray.findLast::12", + "lib.es2023.array.d.ts::ReadonlyArray.findLast::13" ], "transforms": [], "notes": "member surface Array.findLast" @@ -489,7 +505,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2023.array.d.ts::Array.findLastIndex::4" + "lib.es2023.array.d.ts::Array.findLastIndex::4", + "lib.es2023.array.d.ts::ReadonlyArray.findLastIndex::14" ], "transforms": [], "notes": "member surface Array.findLastIndex" @@ -511,7 +528,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2019.array.d.ts::Array.flat::8" + "lib.es2019.array.d.ts::Array.flat::8", + "lib.es2019.array.d.ts::ReadonlyArray.flat::4" ], "transforms": [], "notes": "member surface Array.flat" @@ -533,7 +551,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2019.array.d.ts::Array.flatMap::7" + "lib.es2019.array.d.ts::Array.flatMap::7", + "lib.es2019.array.d.ts::ReadonlyArray.flatMap::3" ], "transforms": [], "notes": "member surface Array.flatMap" @@ -555,7 +574,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es5.d.ts::Array.forEach::422" + "lib.es5.d.ts::Array.forEach::422", + "lib.es5.d.ts::ReadonlyArray.forEach::383" ], "transforms": [], "notes": "member surface Array.forEach" @@ -622,7 +642,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2016.array.include.d.ts::Array.includes::2" + "lib.es2016.array.include.d.ts::Array.includes::2", + "lib.es2016.array.include.d.ts::ReadonlyArray.includes::5" ], "transforms": [], "notes": "member surface Array.includes" @@ -644,7 +665,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es5.d.ts::Array.indexOf::417" + "lib.es5.d.ts::Array.indexOf::417", + "lib.es5.d.ts::ReadonlyArray.indexOf::378" ], "transforms": [], "notes": "member surface Array.indexOf" @@ -688,7 +710,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es5.d.ts::Array.join::409" + "lib.es5.d.ts::Array.join::409", + "lib.es5.d.ts::ReadonlyArray.join::376" ], "transforms": [], "notes": "member surface Array.join" @@ -710,7 +733,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2015.iterable.d.ts::Array.keys::34" + "lib.es2015.iterable.d.ts::Array.keys::34", + "lib.es2015.iterable.d.ts::ReadonlyArray.keys::44" ], "transforms": [], "notes": "member surface Array.keys" @@ -732,7 +756,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es5.d.ts::Array.lastIndexOf::418" + "lib.es5.d.ts::Array.lastIndexOf::418", + "lib.es5.d.ts::ReadonlyArray.lastIndexOf::379" ], "transforms": [], "notes": "member surface Array.lastIndexOf" @@ -754,7 +779,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es5.d.ts::Array.length::402" + "lib.es5.d.ts::Array.length::402", + "lib.es5.d.ts::ReadonlyArray.length::371" ], "transforms": [], "notes": "member surface Array.length" @@ -776,7 +802,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es5.d.ts::Array.map::423" + "lib.es5.d.ts::Array.map::423", + "lib.es5.d.ts::ReadonlyArray.map::384" ], "transforms": [], "notes": "member surface Array.map" @@ -866,7 +893,10 @@ "resolvedUnitIds": [ "lib.es5.d.ts::Array.reduce::426", "lib.es5.d.ts::Array.reduce::427", - "lib.es5.d.ts::Array.reduce::428" + "lib.es5.d.ts::Array.reduce::428", + "lib.es5.d.ts::ReadonlyArray.reduce::387", + "lib.es5.d.ts::ReadonlyArray.reduce::388", + "lib.es5.d.ts::ReadonlyArray.reduce::389" ], "transforms": [], "notes": "member surface Array.reduce" @@ -890,7 +920,10 @@ "resolvedUnitIds": [ "lib.es5.d.ts::Array.reduceRight::429", "lib.es5.d.ts::Array.reduceRight::430", - "lib.es5.d.ts::Array.reduceRight::431" + "lib.es5.d.ts::Array.reduceRight::431", + "lib.es5.d.ts::ReadonlyArray.reduceRight::390", + "lib.es5.d.ts::ReadonlyArray.reduceRight::391", + "lib.es5.d.ts::ReadonlyArray.reduceRight::392" ], "transforms": [], "notes": "member surface Array.reduceRight" @@ -956,7 +989,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es5.d.ts::Array.slice::412" + "lib.es5.d.ts::Array.slice::412", + "lib.es5.d.ts::ReadonlyArray.slice::377" ], "transforms": [], "notes": "member surface Array.slice" @@ -978,7 +1012,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es5.d.ts::Array.some::421" + "lib.es5.d.ts::Array.some::421", + "lib.es5.d.ts::ReadonlyArray.some::382" ], "transforms": [], "notes": "member surface Array.some" @@ -1063,7 +1098,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2015.core.d.ts::Array.toLocaleString::7" + "lib.es2015.core.d.ts::Array.toLocaleString::7", + "lib.es2015.core.d.ts::ReadonlyArray.toLocaleString::64" ], "transforms": [], "notes": "member surface Array.toLocaleString" @@ -1083,7 +1119,8 @@ "includeInTarget": true, "resolutionKind": "signature-compat", "resolvedUnitIds": [ - "lib.es2015.core.d.ts::Array.toLocaleString::7" + "lib.es2015.core.d.ts::Array.toLocaleString::7", + "lib.es2015.core.d.ts::ReadonlyArray.toLocaleString::64" ], "transforms": [], "notes": "locales_parameter is tracked against Array.toLocaleString" @@ -1103,7 +1140,8 @@ "includeInTarget": true, "resolutionKind": "signature-compat", "resolvedUnitIds": [ - "lib.es2015.core.d.ts::Array.toLocaleString::7" + "lib.es2015.core.d.ts::Array.toLocaleString::7", + "lib.es2015.core.d.ts::ReadonlyArray.toLocaleString::64" ], "transforms": [], "notes": "options_parameter is tracked against Array.toLocaleString" @@ -1125,7 +1163,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2023.array.d.ts::Array.toReversed::5" + "lib.es2023.array.d.ts::Array.toReversed::5", + "lib.es2023.array.d.ts::ReadonlyArray.toReversed::15" ], "transforms": [], "notes": "member surface Array.toReversed" @@ -1147,7 +1186,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2023.array.d.ts::Array.toSorted::6" + "lib.es2023.array.d.ts::Array.toSorted::6", + "lib.es2023.array.d.ts::ReadonlyArray.toSorted::16" ], "transforms": [], "notes": "member surface Array.toSorted" @@ -1170,7 +1210,9 @@ "resolutionKind": "member", "resolvedUnitIds": [ "lib.es2023.array.d.ts::Array.toSpliced::7", - "lib.es2023.array.d.ts::Array.toSpliced::8" + "lib.es2023.array.d.ts::Array.toSpliced::8", + "lib.es2023.array.d.ts::ReadonlyArray.toSpliced::17", + "lib.es2023.array.d.ts::ReadonlyArray.toSpliced::18" ], "transforms": [], "notes": "member surface Array.toSpliced" @@ -1192,7 +1234,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es5.d.ts::Array.toString::403" + "lib.es5.d.ts::Array.toString::403", + "lib.es5.d.ts::ReadonlyArray.toString::372" ], "transforms": [], "notes": "member surface Array.toString" @@ -1236,7 +1279,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2015.iterable.d.ts::Array.values::35" + "lib.es2015.iterable.d.ts::Array.values::35", + "lib.es2015.iterable.d.ts::ReadonlyArray.values::45" ], "transforms": [], "notes": "member surface Array.values" @@ -1258,7 +1302,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2023.array.d.ts::Array.with::9" + "lib.es2023.array.d.ts::Array.with::9", + "lib.es2023.array.d.ts::ReadonlyArray.with::19" ], "transforms": [], "notes": "member surface Array.with" @@ -10166,8 +10211,10 @@ "lib.es2015.collection.d.ts::Map::1", "lib.es2015.collection.d.ts::Map::14", "lib.es2015.collection.d.ts::MapConstructor::10", + "lib.es2015.collection.d.ts::ReadonlyMap::16", "lib.es2015.iterable.d.ts::Map::53", "lib.es2015.iterable.d.ts::MapConstructor::65", + "lib.es2015.iterable.d.ts::ReadonlyMap::59", "lib.es2015.symbol.wellknown.d.ts::Map::29", "lib.es2015.symbol.wellknown.d.ts::MapConstructor::112" ], @@ -10191,7 +10238,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2015.iterable.d.ts::Map.@@iterator::54" + "lib.es2015.iterable.d.ts::Map.@@iterator::54", + "lib.es2015.iterable.d.ts::ReadonlyMap.@@iterator::60" ], "transforms": [], "notes": "member surface Map.@@iterator" @@ -10354,7 +10402,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2015.iterable.d.ts::Map.entries::55" + "lib.es2015.iterable.d.ts::Map.entries::55", + "lib.es2015.iterable.d.ts::ReadonlyMap.entries::61" ], "transforms": [], "notes": "member surface Map.entries" @@ -10376,7 +10425,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2015.collection.d.ts::Map.forEach::4" + "lib.es2015.collection.d.ts::Map.forEach::4", + "lib.es2015.collection.d.ts::ReadonlyMap.forEach::17" ], "transforms": [], "notes": "member surface Map.forEach" @@ -10398,7 +10448,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2015.collection.d.ts::Map.get::5" + "lib.es2015.collection.d.ts::Map.get::5", + "lib.es2015.collection.d.ts::ReadonlyMap.get::18" ], "transforms": [], "notes": "member surface Map.get" @@ -10477,7 +10528,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2015.collection.d.ts::Map.has::6" + "lib.es2015.collection.d.ts::Map.has::6", + "lib.es2015.collection.d.ts::ReadonlyMap.has::19" ], "transforms": [], "notes": "member surface Map.has" @@ -10519,7 +10571,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2015.iterable.d.ts::Map.keys::56" + "lib.es2015.iterable.d.ts::Map.keys::56", + "lib.es2015.iterable.d.ts::ReadonlyMap.keys::62" ], "transforms": [], "notes": "member surface Map.keys" @@ -10563,7 +10616,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2015.collection.d.ts::Map.size::8" + "lib.es2015.collection.d.ts::Map.size::8", + "lib.es2015.collection.d.ts::ReadonlyMap.size::20" ], "transforms": [], "notes": "member surface Map.size" @@ -10585,7 +10639,8 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ - "lib.es2015.iterable.d.ts::Map.values::57" + "lib.es2015.iterable.d.ts::Map.values::57", + "lib.es2015.iterable.d.ts::ReadonlyMap.values::63" ], "transforms": [], "notes": "member surface Map.values" @@ -14760,9 +14815,11 @@ "includeInTarget": true, "resolutionKind": "root-availability", "resolvedUnitIds": [ + "lib.es2015.collection.d.ts::ReadonlySet::46", "lib.es2015.collection.d.ts::Set::33", "lib.es2015.collection.d.ts::Set::44", "lib.es2015.collection.d.ts::SetConstructor::41", + "lib.es2015.iterable.d.ts::ReadonlySet::83", "lib.es2015.iterable.d.ts::Set::77", "lib.es2015.iterable.d.ts::SetConstructor::89", "lib.es2015.symbol.wellknown.d.ts::Set::35", @@ -14788,6 +14845,7 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ + "lib.es2015.iterable.d.ts::ReadonlySet.@@iterator::84", "lib.es2015.iterable.d.ts::Set.@@iterator::78" ], "transforms": [], @@ -14964,6 +15022,7 @@ "includeInTarget": false, "resolutionKind": "member", "resolvedUnitIds": [ + "lib.es2025.collection.d.ts::ReadonlySet.difference::18", "lib.es2025.collection.d.ts::Set.difference::9" ], "transforms": [], @@ -14986,6 +15045,7 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ + "lib.es2015.iterable.d.ts::ReadonlySet.entries::85", "lib.es2015.iterable.d.ts::Set.entries::79" ], "transforms": [], @@ -15008,6 +15068,7 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ + "lib.es2015.collection.d.ts::ReadonlySet.forEach::47", "lib.es2015.collection.d.ts::Set.forEach::37" ], "transforms": [], @@ -15030,6 +15091,7 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ + "lib.es2015.collection.d.ts::ReadonlySet.has::48", "lib.es2015.collection.d.ts::Set.has::38" ], "transforms": [], @@ -15049,6 +15111,7 @@ "includeInTarget": false, "resolutionKind": "member", "resolvedUnitIds": [ + "lib.es2025.collection.d.ts::ReadonlySet.intersection::17", "lib.es2025.collection.d.ts::Set.intersection::8" ], "transforms": [], @@ -15068,6 +15131,7 @@ "includeInTarget": false, "resolutionKind": "member", "resolvedUnitIds": [ + "lib.es2025.collection.d.ts::ReadonlySet.isDisjointFrom::22", "lib.es2025.collection.d.ts::Set.isDisjointFrom::13" ], "transforms": [], @@ -15087,6 +15151,7 @@ "includeInTarget": false, "resolutionKind": "member", "resolvedUnitIds": [ + "lib.es2025.collection.d.ts::ReadonlySet.isSubsetOf::20", "lib.es2025.collection.d.ts::Set.isSubsetOf::11" ], "transforms": [], @@ -15106,6 +15171,7 @@ "includeInTarget": false, "resolutionKind": "member", "resolvedUnitIds": [ + "lib.es2025.collection.d.ts::ReadonlySet.isSupersetOf::21", "lib.es2025.collection.d.ts::Set.isSupersetOf::12" ], "transforms": [], @@ -15148,6 +15214,7 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ + "lib.es2015.iterable.d.ts::ReadonlySet.keys::86", "lib.es2015.iterable.d.ts::Set.keys::80" ], "transforms": [], @@ -15170,6 +15237,7 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ + "lib.es2015.collection.d.ts::ReadonlySet.size::49", "lib.es2015.collection.d.ts::Set.size::39" ], "transforms": [], @@ -15189,6 +15257,7 @@ "includeInTarget": false, "resolutionKind": "member", "resolvedUnitIds": [ + "lib.es2025.collection.d.ts::ReadonlySet.symmetricDifference::19", "lib.es2025.collection.d.ts::Set.symmetricDifference::10" ], "transforms": [], @@ -15208,6 +15277,7 @@ "includeInTarget": false, "resolutionKind": "member", "resolvedUnitIds": [ + "lib.es2025.collection.d.ts::ReadonlySet.union::16", "lib.es2025.collection.d.ts::Set.union::7" ], "transforms": [], @@ -15230,6 +15300,7 @@ "includeInTarget": true, "resolutionKind": "member", "resolvedUnitIds": [ + "lib.es2015.iterable.d.ts::ReadonlySet.values::87", "lib.es2015.iterable.d.ts::Set.values::81" ], "transforms": [], @@ -24622,6 +24693,84 @@ ], "transforms": [], "notes": "compat root unescape selects standalone declaration units" + }, + { + "compatKey": "javascript.functions.arguments", + "featureId": "functions", + "featureName": "Functions", + "baselineStatus": "high", + "baselineLowDate": "2015-07-29", + "baselineHighDate": "2018-01-29", + "sourceRefs": [ + "javascript.functions.arguments" + ], + "snapshot": [], + "compatRoot": "IArguments", + "includeInTarget": true, + "resolutionKind": "root-availability", + "resolvedUnitIds": [ + "lib.es5.d.ts::IArguments::94" + ], + "transforms": [], + "notes": "compat root IArguments selects standalone declaration units" + }, + { + "compatKey": "javascript.functions.arguments.@@iterator", + "featureId": "functions", + "featureName": "Functions", + "baselineStatus": "high", + "baselineLowDate": "2016-07-27", + "baselineHighDate": "2019-01-27", + "sourceRefs": [ + "javascript.functions.arguments.@@iterator" + ], + "snapshot": [], + "compatRoot": "IArguments", + "includeInTarget": true, + "resolutionKind": "member", + "resolvedUnitIds": [ + "lib.es2015.iterable.d.ts::IArguments.@@iterator::48" + ], + "transforms": [], + "notes": "member surface IArguments.@@iterator" + }, + { + "compatKey": "javascript.functions.arguments.callee", + "featureId": "arguments-callee", + "featureName": "arguments.callee", + "baselineStatus": false, + "sourceRefs": [ + "javascript.functions.arguments.callee" + ], + "snapshot": [], + "compatRoot": "IArguments", + "includeInTarget": false, + "resolutionKind": "member", + "resolvedUnitIds": [ + "lib.es5.d.ts::IArguments.callee::97" + ], + "transforms": [], + "notes": "member surface IArguments.callee" + }, + { + "compatKey": "javascript.functions.arguments.length", + "featureId": "functions", + "featureName": "Functions", + "baselineStatus": "high", + "baselineLowDate": "2015-07-29", + "baselineHighDate": "2018-01-29", + "sourceRefs": [ + "javascript.functions.arguments.length" + ], + "snapshot": [], + "compatRoot": "IArguments", + "includeInTarget": true, + "resolutionKind": "member", + "resolvedUnitIds": [ + "lib.es5.d.ts::IArguments.length::96" + ], + "transforms": [], + "notes": "member surface IArguments.length" } ] } diff --git a/derived/current/generation.json b/derived/current/generation.json index ccedce3..abce9db 100644 --- a/derived/current/generation.json +++ b/derived/current/generation.json @@ -16,10 +16,11 @@ "inventoryOutputPath": "derived/current/inventory.json", "summary": { "sourceLibCount": 84, - "classifiedCompatCount": 1146, - "selectedUnitCount": 1837, - "completeContainerCount": 171, - "excludedUnitCount": 373, + "classifiedCompatCount": 1150, + "selectedUnitCount": 1906, + "completeContainerCount": 178, + "excludedUnitCount": 381, + "preservedTypeOnlyUnitCount": 29, "transformedUnitCount": 2 }, "sourceLibs": [ @@ -534,6 +535,37 @@ "outputPath": "generated/current/baseline.d.ts" } ], + "preservedTypeOnlyUnits": [ + "lib.es2015.iterable.d.ts::BuiltinIteratorReturn::26", + "lib.es2015.iterable.d.ts::IteratorResult::11", + "lib.es2019.array.d.ts::FlatArray::0", + "lib.es2020.promise.d.ts::PromiseSettledResult::8", + "lib.es5.d.ts::ArrayBufferLike::496", + "lib.es5.d.ts::Awaited::460", + "lib.es5.d.ts::Capitalize::480", + "lib.es5.d.ts::ConstructorParameters::475", + "lib.es5.d.ts::Exclude::470", + "lib.es5.d.ts::Extract::471", + "lib.es5.d.ts::InstanceType::477", + "lib.es5.d.ts::Lowercase::479", + "lib.es5.d.ts::NoInfer::482", + "lib.es5.d.ts::NonNullable::473", + "lib.es5.d.ts::Omit::472", + "lib.es5.d.ts::OmitThisParameter::78", + "lib.es5.d.ts::Parameters::474", + "lib.es5.d.ts::Partial::465", + "lib.es5.d.ts::Pick::468", + "lib.es5.d.ts::PromiseConstructorLike::452", + "lib.es5.d.ts::PropertyKey::17", + "lib.es5.d.ts::Readonly::467", + "lib.es5.d.ts::Record::469", + "lib.es5.d.ts::Required::466", + "lib.es5.d.ts::ReturnType::476", + "lib.es5.d.ts::ThisParameterType::77", + "lib.es5.d.ts::Uncapitalize::481", + "lib.es5.d.ts::Uppercase::478", + "lib.es5.d.ts::WeakKey::488" + ], "transformedUnits": [ "lib.es2024.arraybuffer.d.ts::ArrayBufferConstructor.::10", "lib.es2024.sharedmemory.d.ts::SharedArrayBufferConstructor.::11" @@ -771,6 +803,48 @@ "javascript.builtins.SharedArrayBuffer.maxByteLength" ] }, + { + "unitId": "lib.es2025.collection.d.ts::ReadonlySet.difference::18", + "compatKeys": [ + "javascript.builtins.Set.difference" + ] + }, + { + "unitId": "lib.es2025.collection.d.ts::ReadonlySet.intersection::17", + "compatKeys": [ + "javascript.builtins.Set.intersection" + ] + }, + { + "unitId": "lib.es2025.collection.d.ts::ReadonlySet.isDisjointFrom::22", + "compatKeys": [ + "javascript.builtins.Set.isDisjointFrom" + ] + }, + { + "unitId": "lib.es2025.collection.d.ts::ReadonlySet.isSubsetOf::20", + "compatKeys": [ + "javascript.builtins.Set.isSubsetOf" + ] + }, + { + "unitId": "lib.es2025.collection.d.ts::ReadonlySet.isSupersetOf::21", + "compatKeys": [ + "javascript.builtins.Set.isSupersetOf" + ] + }, + { + "unitId": "lib.es2025.collection.d.ts::ReadonlySet.symmetricDifference::19", + "compatKeys": [ + "javascript.builtins.Set.symmetricDifference" + ] + }, + { + "unitId": "lib.es2025.collection.d.ts::ReadonlySet.union::16", + "compatKeys": [ + "javascript.builtins.Set.union" + ] + }, { "unitId": "lib.es2025.collection.d.ts::Set.difference::9", "compatKeys": [ @@ -945,6 +1019,12 @@ "javascript.builtins.Function.caller" ] }, + { + "unitId": "lib.es5.d.ts::IArguments.callee::97", + "compatKeys": [ + "javascript.functions.arguments.callee" + ] + }, { "unitId": "lib.es5.d.ts::RegExp.compile::276", "compatKeys": [ diff --git a/derived/current/inventory.json b/derived/current/inventory.json index 17798b8..37159b3 100644 --- a/derived/current/inventory.json +++ b/derived/current/inventory.json @@ -953,7 +953,8 @@ "lib.es5.d.ts::Array::443" ], "instanceContainerSymbols": [ - "Array" + "Array", + "ReadonlyArray" ], "staticContainerSymbols": [ "ArrayConstructor" @@ -2516,7 +2517,8 @@ "lib.es2015.collection.d.ts::Map::14" ], "instanceContainerSymbols": [ - "Map" + "Map", + "ReadonlyMap" ], "staticContainerSymbols": [ "MapConstructor" @@ -3115,6 +3117,7 @@ "lib.es2015.collection.d.ts::Set::44" ], "instanceContainerSymbols": [ + "ReadonlySet", "Set" ], "staticContainerSymbols": [ diff --git a/fixtures/typescript/tests/baselines/reference/libBaseline.errors.txt b/fixtures/typescript/tests/baselines/reference/libBaseline.errors.txt index a84422b..6728072 100644 --- a/fixtures/typescript/tests/baselines/reference/libBaseline.errors.txt +++ b/fixtures/typescript/tests/baselines/reference/libBaseline.errors.txt @@ -1,9 +1,10 @@ libBaseline.ts(12,29): error TS2339: Property 'caller' does not exist on type '() => void'. libBaseline.ts(13,12): error TS2339: Property 'substr' does not exist on type '"baseline"'. libBaseline.ts(14,24): error TS2339: Property 'compile' does not exist on type 'RegExp'. +libBaseline.ts(15,47): error TS2339: Property 'callee' does not exist on type 'IArguments'. -==== libBaseline.ts (3 errors) ==== +==== libBaseline.ts (4 errors) ==== Intl.supportedValuesOf("currency"); new Intl.NumberFormat().formatRange(1, 2); @@ -24,4 +25,7 @@ libBaseline.ts(14,24): error TS2339: Property 'compile' does not exist on type ' new RegExp("baseline").compile("baseline"); // Error ~~~~~~~ !!! error TS2339: Property 'compile' does not exist on type 'RegExp'. + (function probeArguments() { return arguments.callee; })(); // Error + ~~~~~~ +!!! error TS2339: Property 'callee' does not exist on type 'IArguments'. \ No newline at end of file diff --git a/fixtures/typescript/tests/baselines/reference/libBaseline.js b/fixtures/typescript/tests/baselines/reference/libBaseline.js index 97b40b4..0ab4394 100644 --- a/fixtures/typescript/tests/baselines/reference/libBaseline.js +++ b/fixtures/typescript/tests/baselines/reference/libBaseline.js @@ -15,6 +15,7 @@ new Error("problem", { cause: new Error("root") }); (function probeCaller() {}).caller; // Error "baseline".substr(1); // Error new RegExp("baseline").compile("baseline"); // Error +(function probeArguments() { return arguments.callee; })(); // Error //// [libBaseline.js] @@ -30,3 +31,4 @@ new Error("problem", { cause: new Error("root") }); (function probeCaller() { }).caller; // Error "baseline".substr(1); // Error new RegExp("baseline").compile("baseline"); // Error +(function probeArguments() { return arguments.callee; })(); // Error diff --git a/fixtures/typescript/tests/baselines/reference/libBaseline.symbols b/fixtures/typescript/tests/baselines/reference/libBaseline.symbols index c082585..d2e1d61 100644 --- a/fixtures/typescript/tests/baselines/reference/libBaseline.symbols +++ b/fixtures/typescript/tests/baselines/reference/libBaseline.symbols @@ -47,3 +47,7 @@ new Error("problem", { cause: new Error("root") }); new RegExp("baseline").compile("baseline"); // Error >RegExp : Symbol(RegExp, Decl(lib.baseline.d.ts, --, --), Decl(lib.baseline.d.ts, --, --), Decl(lib.baseline.d.ts, --, --), Decl(lib.baseline.d.ts, --, --), Decl(lib.baseline.d.ts, --, --) ... and 3 more) +(function probeArguments() { return arguments.callee; })(); // Error +>probeArguments : Symbol(probeArguments, Decl(libBaseline.ts, 14, 1)) +>arguments : Symbol(arguments) + diff --git a/fixtures/typescript/tests/baselines/reference/libBaseline.types b/fixtures/typescript/tests/baselines/reference/libBaseline.types index 8e99736..46d3a06 100644 --- a/fixtures/typescript/tests/baselines/reference/libBaseline.types +++ b/fixtures/typescript/tests/baselines/reference/libBaseline.types @@ -163,3 +163,19 @@ new RegExp("baseline").compile("baseline"); // Error >"baseline" : "baseline" > : ^^^^^^^^^^ +(function probeArguments() { return arguments.callee; })(); // Error +>(function probeArguments() { return arguments.callee; })() : any +> : ^^^ +>(function probeArguments() { return arguments.callee; }) : () => any +> : ^^^^^^^^^ +>function probeArguments() { return arguments.callee; } : () => any +> : ^^^^^^^^^ +>probeArguments : () => any +> : ^^^^^^^^^ +>arguments.callee : any +> : ^^^ +>arguments : IArguments +> : ^^^^^^^^^^ +>callee : any +> : ^^^ + diff --git a/fixtures/typescript/tests/cases/compiler/libBaseline.ts b/fixtures/typescript/tests/cases/compiler/libBaseline.ts index 41e4f7f..1c7affd 100644 --- a/fixtures/typescript/tests/cases/compiler/libBaseline.ts +++ b/fixtures/typescript/tests/cases/compiler/libBaseline.ts @@ -15,3 +15,4 @@ new Error("problem", { cause: new Error("root") }); (function probeCaller() {}).caller; // Error "baseline".substr(1); // Error new RegExp("baseline").compile("baseline"); // Error +(function probeArguments() { return arguments.callee; })(); // Error diff --git a/generated/current/baseline.d.ts b/generated/current/baseline.d.ts index 0742736..d06951b 100644 --- a/generated/current/baseline.d.ts +++ b/generated/current/baseline.d.ts @@ -70,6 +70,16 @@ interface MapConstructor { declare var Map: MapConstructor; +interface ReadonlyMap { + forEach(callbackfn: (value: V, key: K, map: ReadonlyMap) => void, thisArg?: any): void; + + get(key: K): V | undefined; + + has(key: K): boolean; + + readonly size: number; +} + interface WeakMap { /** * Removes the specified element from the WeakMap. @@ -143,6 +153,23 @@ interface SetConstructor { declare var Set: SetConstructor; +interface ReadonlySet { + /** + * Executes a provided function once per each value in the ReadonlySet object, in insertion order. + */ + forEach(callbackfn: (value: T, value2: T, set: ReadonlySet) => void, thisArg?: any): void; + + /** + * @returns a boolean indicating whether an element with the specified value exists in the Set or not. + */ + has(value: T): boolean; + + /** + * @returns the number of (unique) elements in the Set. + */ + readonly size: number; +} + interface WeakSet { /** * Appends a new value to the end of the WeakSet. @@ -496,6 +523,34 @@ interface ObjectConstructor { setPrototypeOf(o: any, proto: object | null): any; } +interface ReadonlyArray { + /** + * Returns the value of the first element in the array where predicate is true, and undefined + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, find + * immediately returns that element value. Otherwise, find returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + find(predicate: (value: T, index: number, obj: readonly T[]) => value is S, thisArg?: any): S | undefined; + + find(predicate: (value: T, index: number, obj: readonly T[]) => unknown, thisArg?: any): T | undefined; + + /** + * Returns the index of the first element in the array where predicate is true, and -1 + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, + * findIndex immediately returns that element index. Otherwise, findIndex returns -1. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + findIndex(predicate: (value: T, index: number, obj: readonly T[]) => unknown, thisArg?: any): number; + + toLocaleString(locales: string | string[], options?: Intl.NumberFormatOptions & Intl.DateTimeFormatOptions): string; +} + interface RegExp { /** * Returns a string indicating the flags of the regular expression in question. This field is read-only. @@ -776,6 +831,31 @@ interface ArrayConstructor { from(iterable: Iterable | ArrayLike, mapfn: (v: T, k: number) => U, thisArg?: any): U[]; } +interface ReadonlyArray { + /** Iterator of values in the array. */ + [Symbol.iterator](): ArrayIterator; + + /** + * Returns an iterable of key, value pairs for every entry in the array + */ + entries(): ArrayIterator<[number, T]>; + + /** + * Returns an iterable of keys in the array + */ + keys(): ArrayIterator; + + /** + * Returns an iterable of values in the array + */ + values(): ArrayIterator; +} + +interface IArguments { + /** Iterator */ + [Symbol.iterator](): ArrayIterator; +} + interface MapIterator extends IteratorObject { [Symbol.iterator](): MapIterator; } @@ -800,6 +880,26 @@ interface Map { values(): MapIterator; } +interface ReadonlyMap { + /** Returns an iterable of entries in the map. */ + [Symbol.iterator](): MapIterator<[K, V]>; + + /** + * Returns an iterable of key, value pairs for every entry in the map. + */ + entries(): MapIterator<[K, V]>; + + /** + * Returns an iterable of keys in the map + */ + keys(): MapIterator; + + /** + * Returns an iterable of values in the map + */ + values(): MapIterator; +} + interface MapConstructor { new (): Map; @@ -837,6 +937,26 @@ interface Set { values(): SetIterator; } +interface ReadonlySet { + /** Iterates over values in the set. */ + [Symbol.iterator](): SetIterator; + + /** + * Returns an iterable of [v,v] pairs for every value `v` in the set. + */ + entries(): SetIterator<[T, T]>; + + /** + * Despite its name, returns an iterable of the values in the set. + */ + keys(): SetIterator; + + /** + * Returns an iterable of values in the set. + */ + values(): SetIterator; +} + interface SetConstructor { new (iterable?: Iterable | null): Set; } @@ -1621,6 +1741,16 @@ interface Array { }; } +interface ReadonlyArray { + /** + * Is an object whose properties have the value 'true' + * when they will be absent when used in a 'with' statement. + */ + readonly [Symbol.unscopables]: { + [K in keyof readonly any[]]?: boolean; + }; +} + interface Date { /** * Converts a Date object to a string. @@ -1799,6 +1929,15 @@ interface Array { includes(searchElement: T, fromIndex?: number): boolean; } +interface ReadonlyArray { + /** + * Determines whether an array includes a certain element, returning true or false as appropriate. + * @param searchElement The element to search for. + * @param fromIndex The position in this array at which to begin searching for searchElement. + */ + includes(searchElement: T, fromIndex?: number): boolean; +} + interface Int8Array { /** * Determines whether an array includes a certain element, returning true or false as appropriate. @@ -2382,6 +2521,34 @@ type FlatArray = { : Arr; }[Depth extends -1 ? "done" : "recur"]; +interface ReadonlyArray { + /** + * Calls a defined callback function on each element of an array. Then, flattens the result into + * a new array. + * This is identical to a map followed by flat with depth 1. + * + * @param callback A function that accepts up to three arguments. The flatMap method calls the + * callback function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callback function. If + * thisArg is omitted, undefined is used as the this value. + */ + flatMap( + callback: (this: This, value: T, index: number, array: T[]) => U | ReadonlyArray, + thisArg?: This, + ): U[]; + + /** + * Returns a new array with all sub-array elements concatenated into it recursively up to the + * specified depth. + * + * @param depth The maximum recursion depth + */ + flat( + this: A, + depth?: D, + ): FlatArray[]; +} + interface Array { /** * Calls a defined callback function on each element of an array. Then, flattens the result into @@ -4177,6 +4344,14 @@ interface Array { at(index: number): T | undefined; } +interface ReadonlyArray { + /** + * Returns the item located at the specified index. + * @param index The zero-based index of the desired code unit. A negative index will count back from the last item. + */ + at(index: number): T | undefined; +} + interface Int8Array { /** * Returns the item located at the specified index. @@ -4453,6 +4628,85 @@ interface Array { with(index: number, value: T): T[]; } +interface ReadonlyArray { + /** + * Returns the value of the last element in the array where predicate is true, and undefined + * otherwise. + * @param predicate findLast calls predicate once for each element of the array, in descending + * order, until it finds one where predicate returns true. If such an element is found, findLast + * immediately returns that element value. Otherwise, findLast returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + findLast( + predicate: (value: T, index: number, array: readonly T[]) => value is S, + thisArg?: any, + ): S | undefined; + + findLast( + predicate: (value: T, index: number, array: readonly T[]) => unknown, + thisArg?: any, + ): T | undefined; + + /** + * Returns the index of the last element in the array where predicate is true, and -1 + * otherwise. + * @param predicate findLastIndex calls predicate once for each element of the array, in descending + * order, until it finds one where predicate returns true. If such an element is found, + * findLastIndex immediately returns that element index. Otherwise, findLastIndex returns -1. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + findLastIndex( + predicate: (value: T, index: number, array: readonly T[]) => unknown, + thisArg?: any, + ): number; + + /** + * Copies the array and returns the copied array with all of its elements reversed. + */ + toReversed(): T[]; + + /** + * Copies and sorts the array. + * @param compareFn Function used to determine the order of the elements. It is expected to return + * a negative value if the first argument is less than the second argument, zero if they're equal, and a positive + * value otherwise. If omitted, the elements are sorted in ascending, UTF-16 code unit order. + * ```ts + * [11, 2, 22, 1].toSorted((a, b) => a - b) // [1, 2, 11, 22] + * ``` + */ + toSorted(compareFn?: (a: T, b: T) => number): T[]; + + /** + * Copies an array and removes elements while, if necessary, inserting new elements in their place, returning the remaining elements. + * @param start The zero-based location in the array from which to start removing elements. + * @param deleteCount The number of elements to remove. + * @param items Elements to insert into the copied array in place of the deleted elements. + * @returns A copy of the original array with the remaining elements. + */ + toSpliced(start: number, deleteCount: number, ...items: T[]): T[]; + + /** + * Copies an array and removes elements while returning the remaining elements. + * @param start The zero-based location in the array from which to start removing elements. + * @param deleteCount The number of elements to remove. + * @returns A copy of the original array with the remaining elements. + */ + toSpliced(start: number, deleteCount?: number): T[]; + + /** + * Copies an array, then overwrites the value at the provided index with the + * given value. If the index is negative, then it replaces from the end + * of the array + * @param index The index of the value to overwrite. If the index is + * negative, then it replaces from the end of the array. + * @param value The value to insert into the copied array. + * @returns A copy of the original array with the inserted value. + */ + with(index: number, value: T): T[]; +} + interface Int8Array { /** * Returns the value of the last element in the array where predicate is true, and undefined @@ -5621,12 +5875,83 @@ type ThisParameterType = T extends (this: infer U, ...args: never) => any ? U type OmitThisParameter = unknown extends ThisParameterType ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T; interface CallableFunction extends Function { + /** + * Calls the function with the specified object as the this value and the elements of specified array as the arguments. + * @param thisArg The object to be used as the this object. + */ + apply(this: (this: T) => R, thisArg: T): R; + + /** + * Calls the function with the specified object as the this value and the elements of specified array as the arguments. + * @param thisArg The object to be used as the this object. + * @param args An array of argument values to be passed to the function. + */ + apply(this: (this: T, ...args: A) => R, thisArg: T, args: A): R; + + /** + * Calls the function with the specified object as the this value and the specified rest arguments as the arguments. + * @param thisArg The object to be used as the this object. + * @param args Argument values to be passed to the function. + */ + call(this: (this: T, ...args: A) => R, thisArg: T, ...args: A): R; + + /** + * For a given function, creates a bound function that has the same body as the original function. + * The this object of the bound function is associated with the specified object, and has the specified initial parameters. + * @param thisArg The object to be used as the this object. + */ + bind(this: T, thisArg: ThisParameterType): OmitThisParameter; + + /** + * For a given function, creates a bound function that has the same body as the original function. + * The this object of the bound function is associated with the specified object, and has the specified initial parameters. + * @param thisArg The object to be used as the this object. + * @param args Arguments to bind to the parameters of the function. + */ + bind(this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } interface NewableFunction extends Function { + /** + * Calls the function with the specified object as the this value and the elements of specified array as the arguments. + * @param thisArg The object to be used as the this object. + */ + apply(this: new () => T, thisArg: T): void; + + /** + * Calls the function with the specified object as the this value and the elements of specified array as the arguments. + * @param thisArg The object to be used as the this object. + * @param args An array of argument values to be passed to the function. + */ + apply(this: new (...args: A) => T, thisArg: T, args: A): void; + + /** + * Calls the function with the specified object as the this value and the specified rest arguments as the arguments. + * @param thisArg The object to be used as the this object. + * @param args Argument values to be passed to the function. + */ + call(this: new (...args: A) => T, thisArg: T, ...args: A): void; + + /** + * For a given function, creates a bound function that has the same body as the original function. + * The this object of the bound function is associated with the specified object, and has the specified initial parameters. + * @param thisArg The object to be used as the this object. + */ + bind(this: T, thisArg: any): T; + + /** + * For a given function, creates a bound function that has the same body as the original function. + * The this object of the bound function is associated with the specified object, and has the specified initial parameters. + * @param thisArg The object to be used as the this object. + * @param args Arguments to bind to the parameters of the function. + */ + bind(this: new (...args: [...A, ...B]) => R, thisArg: any, ...args: A): new (...args: B) => R; } interface IArguments { + [index: number]: any; + + length: number; } interface String { @@ -6849,6 +7174,8 @@ interface TypedPropertyDescriptor { set?: (value: T) => void; } +declare type PromiseConstructorLike = new (executor: (resolve: (value: T | PromiseLike) => void, reject: (reason?: any) => void) => void) => PromiseLike; + interface PromiseLike { /** * Attaches callbacks for the resolution and/or rejection of the Promise. @@ -6897,6 +7224,20 @@ interface ArrayLike { readonly [n: number]: T; } +/** + * Make all properties in T optional + */ +type Partial = { + [P in keyof T]?: T[P]; +}; + +/** + * Make all properties in T required + */ +type Required = { + [P in keyof T]-?: T[P]; +}; + /** * Make all properties in T readonly */ @@ -6911,11 +7252,78 @@ type Pick = { [P in K]: T[P]; }; +/** + * Construct a type with a set of properties K of type T + */ +type Record = { + [P in K]: T; +}; + /** * Exclude from T those types that are assignable to U */ type Exclude = T extends U ? never : T; +/** + * Extract from T those types that are assignable to U + */ +type Extract = T extends U ? T : never; + +/** + * Construct a type with the properties of T except for those in type K. + */ +type Omit = Pick>; + +/** + * Exclude null and undefined from T + */ +type NonNullable = T & {}; + +/** + * Obtain the parameters of a function type in a tuple + */ +type Parameters any> = T extends (...args: infer P) => any ? P : never; + +/** + * Obtain the parameters of a constructor function type in a tuple + */ +type ConstructorParameters any> = T extends abstract new (...args: infer P) => any ? P : never; + +/** + * Obtain the return type of a function type + */ +type ReturnType any> = T extends (...args: any) => infer R ? R : any; + +/** + * Obtain the return type of a constructor function type + */ +type InstanceType any> = T extends abstract new (...args: any) => infer R ? R : any; + +/** + * Convert string literal type to uppercase + */ +type Uppercase = intrinsic; + +/** + * Convert string literal type to lowercase + */ +type Lowercase = intrinsic; + +/** + * Convert first character of string literal type to uppercase + */ +type Capitalize = intrinsic; + +/** + * Convert first character of string literal type to lowercase + */ +type Uncapitalize = intrinsic; + +/** + * Marker for non-inference type position + */ +type NoInfer = intrinsic; + /** * Marker for contextual 'this' type */ diff --git a/lib/classifier.mjs b/lib/classifier.mjs index a82fd3c..bc3671c 100644 --- a/lib/classifier.mjs +++ b/lib/classifier.mjs @@ -20,6 +20,7 @@ import { getPreferredBaseDeclarationUnits, getPreferredMemberUnits, getRootSurface, + isLibCompatKey, } from "./surface-inventory.mjs"; const BEHAVIOR_QUALIFIERS = new Set([ @@ -170,7 +171,7 @@ export async function classifyManifest(options) { const libCompatRows = dataset.compatRows .filter( /** @param {any} compatRow */ - compatRow => compatRow.compatKey.startsWith("javascript.builtins."), + compatRow => isLibCompatKey(compatRow.compatKey), ) .sort( /** @param {any} left @param {any} right */ diff --git a/lib/generator.mjs b/lib/generator.mjs index 5ecb2e6..64f1dda 100644 --- a/lib/generator.mjs +++ b/lib/generator.mjs @@ -30,6 +30,8 @@ import { getRootSurface, } from "./surface-inventory.mjs"; +const RUNTIME_DECLARATION_KINDS = new Set(["var", "function", "class", "enum", "namespace"]); + // Resolution kinds where resolvedUnitIds points at the declaration surface of // the feature itself. Units from excluded rows (includeInTarget: false) with // these kinds must not exist in the generated artifact unless some included @@ -98,6 +100,85 @@ export function resolveExcludedUnits(classifiedCompatRows) { }; } +/** + * Preserve globally visible erased aliases that compatibility data cannot + * classify. Interfaces and their members remain compat- or dependency-driven. + * + * @param {{ + * inventory: import("./surface-inventory.mjs").SurfaceInventory; + * classifiedCompatRows: Array<{ resolvedUnitIds: string[]; }>; + * excludedUnitIds: Set; + * }} options + */ +export function resolveUnclaimedTypeOnlyUnitIds(options) { + const { + inventory, + classifiedCompatRows, + excludedUnitIds, + } = options; + const claimedUnitIds = new Set(classifiedCompatRows.flatMap(row => row.resolvedUnitIds)); + + return inventory.units + .filter(unit => { + if ( + claimedUnitIds.has(unit.id) + || excludedUnitIds.has(unit.id) + ) { + return false; + } + if (inventory.fileByLibFileName.get(unit.libFileName)?.preserveWholeFile) { + return false; + } + return unit.unitKind === "declaration" + && unit.declarationKind === "type-alias" + && !unit.parentContainerId; + }) + .map(unit => unit.id) + .sort(compareStringsCaseSensitive); +} + +/** + * @param {{ + * inventory: import("./surface-inventory.mjs").SurfaceInventory; + * compatSelectedUnitIds: Set; + * typeOnlyUnitIds: string[]; + * completeContainerUnitIds: Set; + * excludedUnitIds: Set; + * }} options + */ +export function resolveTypeOnlyDependencyClosure(options) { + const compatClosure = new Set(resolveDependencyClosure({ + inventory: options.inventory, + initiallySelectedUnitIds: options.compatSelectedUnitIds, + completeContainerUnitIds: new Set(options.completeContainerUnitIds), + excludedUnitIds: options.excludedUnitIds, + })); + const selectedUnitIds = resolveDependencyClosure({ + inventory: options.inventory, + initiallySelectedUnitIds: new Set([ + ...options.compatSelectedUnitIds, + ...options.typeOnlyUnitIds, + ]), + completeContainerUnitIds: options.completeContainerUnitIds, + excludedUnitIds: options.excludedUnitIds, + }); + const introducedRuntimeUnits = selectedUnitIds.filter(unitId => { + if (compatClosure.has(unitId)) { + return false; + } + const unit = options.inventory.unitById.get(unitId); + return unit?.unitKind === "declaration" + && RUNTIME_DECLARATION_KINDS.has(unit.declarationKind ?? ""); + }); + if (introducedRuntimeUnits.length) { + throw new Error( + "Type-only aliases introduce runtime declarations not selected by compat data:\n" + + introducedRuntimeUnits.map(unitId => `- ${unitId}`).join("\n"), + ); + } + return selectedUnitIds; +} + /** * Enforce, before emit, that units judged excluded are absent from the * generated artifact. No matter which path selected them (complete-container @@ -209,15 +290,22 @@ async function createGenerationPlan({ manifestPath, repoRoot }) { inventory, }); - const initiallySelectedUnitIds = new Set( - classification.classifiedCompatRows - .filter(row => row.includeInTarget) - .flatMap(row => row.resolvedUnitIds), - ); + const compatSelectedUnitIds = classification.classifiedCompatRows + .filter(row => row.includeInTarget) + .flatMap(row => row.resolvedUnitIds); const { excludedUnitIds, excludedRowsByUnitId, } = resolveExcludedUnits(classification.classifiedCompatRows); + const typeOnlyUnitIds = resolveUnclaimedTypeOnlyUnitIds({ + inventory, + classifiedCompatRows: classification.classifiedCompatRows, + excludedUnitIds, + }); + const initiallySelectedUnitIds = new Set([ + ...compatSelectedUnitIds, + ...typeOnlyUnitIds, + ]); const completeContainerUnitIds = new Set( classification.classifiedCompatRows .filter(row => row.includeInTarget && row.resolutionKind === "root-availability") @@ -237,9 +325,10 @@ async function createGenerationPlan({ manifestPath, repoRoot }) { const selectedUnitIds = stabilizeCompilerGlobalSupport({ inventory, unitTextOverrides, - selectedUnitIds: resolveDependencyClosure({ + selectedUnitIds: resolveTypeOnlyDependencyClosure({ inventory, - initiallySelectedUnitIds, + compatSelectedUnitIds: new Set(compatSelectedUnitIds), + typeOnlyUnitIds, completeContainerUnitIds, excludedUnitIds, }), @@ -269,6 +358,7 @@ async function createGenerationPlan({ manifestPath, repoRoot }) { completeContainerUnitIds, excludedUnitIds, excludedRowsByUnitId, + typeOnlyUnitIds, unitTextOverrides, outputEntries: [ { @@ -459,18 +549,18 @@ function stabilizeCompilerGlobalSupport(options) { continue; } if (!selectedUnitIds.has(supportUnitId)) { + maybePromoteDependencyDeclarationToCompleteContainer({ + inventory, + selectedUnitIds, + pending: [], + completeContainerUnitIds, + declarationUnitId: supportUnitId, + initiallySelectedUnitIds: selectedUnitIds, + excludedUnitIds, + }); selectedUnitIds.add(supportUnitId); changed = true; } - maybePromoteDependencyDeclarationToCompleteContainer({ - inventory, - selectedUnitIds, - pending: [], - completeContainerUnitIds, - declarationUnitId: supportUnitId, - initiallySelectedUnitIds: selectedUnitIds, - excludedUnitIds, - }); } } @@ -742,6 +832,7 @@ function getGenerationReport(plan) { selectedUnitCount: plan.selectedUnitIds.length, completeContainerCount: plan.completeContainerUnitIds.size, excludedUnitCount: plan.excludedUnitIds.size, + preservedTypeOnlyUnitCount: plan.typeOnlyUnitIds.length, transformedUnitCount: plan.unitTextOverrides.size, }, // Record sourcePath in canonical form (/lib/). Don't @@ -758,6 +849,7 @@ function getGenerationReport(plan) { kind: entry.kind, outputPath: formatPathForReport(plan.repoRoot, entry.outputPath), })), + preservedTypeOnlyUnits: [...plan.typeOnlyUnitIds].sort(compareStringsCaseSensitive), transformedUnits: [...plan.unitTextOverrides.keys()].sort(compareStringsCaseSensitive), // For audit: which compat rows block which units from the artifact. excludedUnits: [...plan.excludedRowsByUnitId.entries()] @@ -819,6 +911,7 @@ async function readManifest(manifestPath) { * completeContainerUnitIds: Set; * excludedUnitIds: Set; * excludedRowsByUnitId: Map; + * typeOnlyUnitIds: string[]; * unitTextOverrides: Map; * outputEntries: Array<{ kind: string; outputPath: string; }>; * }} GenerationPlan diff --git a/lib/negative-probes.mjs b/lib/negative-probes.mjs index 3e647bb..b0de9d5 100644 --- a/lib/negative-probes.mjs +++ b/lib/negative-probes.mjs @@ -48,6 +48,12 @@ export const STABLE_NEGATIVE_PROBES = [ errorPattern: /compile/, absencePattern: /\bcompile\(pattern/, }, + { + compatKey: "javascript.functions.arguments.callee", + sourceText: "(function probeArguments() { return arguments.callee; })();", + errorPattern: /callee/, + absencePattern: /\bcallee: Function;/, + }, ]; /** @type {NegativeProbe[]} */ @@ -64,6 +70,12 @@ export const LOW_NEGATIVE_PROBE_CANDIDATES = [ errorPattern: /withResolvers/, absencePattern: /withResolvers/, }, + { + compatKey: "javascript.builtins.Array.fromAsync", + sourceText: "Array.fromAsync([1, 2, 3]);", + errorPattern: /fromAsync/, + absencePattern: /fromAsync/, + }, { compatKey: "javascript.builtins.Map.groupBy", sourceText: "Map.groupBy([1, 2, 3], (value: number) => value % 2);", @@ -87,6 +99,12 @@ export const LOW_NEGATIVE_PROBE_CANDIDATES = [ sourceText: "RegExp.escape(\"probe\");", errorPattern: /escape/, }, + { + compatKey: "javascript.builtins.Set.union", + sourceText: "declare const probeReadonlySet: ReadonlySet; probeReadonlySet.union(new Set());", + errorPattern: /union/, + absencePattern: /\bunion\(/, + }, ]; /** diff --git a/lib/surface-inventory.mjs b/lib/surface-inventory.mjs index 25f103e..107aa0a 100644 --- a/lib/surface-inventory.mjs +++ b/lib/surface-inventory.mjs @@ -17,6 +17,12 @@ import { const TYPESCRIPT_SOURCE_LICENSE_HEADER_PATTERN = /\/\*! \*+\r?\nCopyright \(c\) Microsoft Corporation\. All rights reserved\.[\s\S]*?and limitations under the License\.\r?\n\*+ \*\/\s*/g; +const READONLY_COMPANION_BY_RUNTIME_ROOT = new Map([ + ["Array", "ReadonlyArray"], + ["Map", "ReadonlyMap"], + ["Set", "ReadonlySet"], +]); + const GENERATED_LIB_HEADER = [ "/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */", "// ", @@ -261,6 +267,37 @@ export async function createSurfaceInventory(options) { registerRootSurface(rootSurfaceByCompatName, declarationUnit); } } + for (const rootSurface of rootSurfaceByCompatName.values()) { + const readonlySymbol = READONLY_COMPANION_BY_RUNTIME_ROOT.get(rootSurface.compatName); + if (!readonlySymbol) { + continue; + } + const hasRuntimeRoot = rootSurface.rootDeclarationUnitIds.some(unitId => { + const declarationKind = strippedUnitById.get(unitId)?.declarationKind; + return declarationKind === "var" || declarationKind === "class"; + }); + if (!hasRuntimeRoot || !declarationUnitsBySymbol.has(readonlySymbol)) { + throw new Error(`Missing readonly companion ${readonlySymbol} for ${rootSurface.compatName}`); + } + const rootMemberNames = new Set( + (memberUnitsByOwnerSymbol.get(rootSurface.compatName) ?? []) + .flatMap(unit => unit.memberName ? [unit.memberName] : []), + ); + const readonlyMemberNames = new Set( + (memberUnitsByOwnerSymbol.get(readonlySymbol) ?? []) + .flatMap(unit => unit.memberName ? [unit.memberName] : []), + ); + const unmatchedMembers = [...readonlyMemberNames] + .filter(memberName => !rootMemberNames.has(memberName)) + .sort(compareStringsCaseSensitive); + if (!readonlyMemberNames.size || unmatchedMembers.length) { + throw new Error( + `Readonly companion ${readonlySymbol} does not structurally match ${rootSurface.compatName}: ` + + (unmatchedMembers.length ? unmatchedMembers.join(", ") : "no members"), + ); + } + rootSurface.instanceContainerSymbols.add(readonlySymbol); + } const inventory = { snapshot: snapshotName, @@ -339,7 +376,26 @@ export function emitSelectedUnits(options) { * @param {string} compatKey */ export function getCompatSegments(compatKey) { - return compatKey.replace(/^javascript\.builtins\./, "").split("."); + if (compatKey.startsWith("javascript.builtins.")) { + return compatKey.slice("javascript.builtins.".length).split("."); + } + const argumentsPrefix = "javascript.functions.arguments"; + if (compatKey === argumentsPrefix) { + return ["IArguments"]; + } + if (compatKey.startsWith(`${argumentsPrefix}.`)) { + return ["IArguments", ...compatKey.slice(argumentsPrefix.length + 1).split(".")]; + } + throw new Error(`Unsupported JavaScript lib compat key ${compatKey}`); +} + +/** + * @param {string} compatKey + */ +export function isLibCompatKey(compatKey) { + return compatKey.startsWith("javascript.builtins.") + || compatKey === "javascript.functions.arguments" + || compatKey.startsWith("javascript.functions.arguments."); } /** diff --git a/package.json b/package.json index d938b62..7b8538f 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "private": true, "license": "Apache-2.0", - "description": "Generates Baseline widely available JavaScript built-in declarations for TypeScript.", + "description": "Generates Baseline widely available JavaScript declarations for TypeScript.", "keywords": [ "typescript", "baseline", diff --git a/test/classifier.test.mjs b/test/classifier.test.mjs index 0f941db..cc36e7e 100644 --- a/test/classifier.test.mjs +++ b/test/classifier.test.mjs @@ -144,6 +144,12 @@ const FIXTURE_LIB_SOURCE = [ "interface IteratorObject {", " map(callback: unknown): IteratorObject;", "}", + "interface IArguments {", + " [index: number]: any;", + " length: number;", + " callee: Function;", + " [Symbol.iterator](): Iterator;", + "}", "", ].join("\n"); @@ -265,6 +271,10 @@ test("classifier routes synthetic compat rows to the expected resolution kinds", row("javascript.builtins.Widget.toy.stable_sorting", "high"), row("javascript.builtins.TypedArray.at", "high"), row("javascript.builtins.Iterator.map", "low"), + row("javascript.functions.arguments", "high"), + row("javascript.functions.arguments.@@iterator", "high"), + row("javascript.functions.arguments.callee", false), + row("javascript.functions.arguments.length", "high"), ...typedArrayFamilyRows({ Float16Array: "low" }), ], }); @@ -314,6 +324,13 @@ test("classifier routes synthetic compat rows to the expected resolution kinds", const iteratorRow = findRow(classification, "javascript.builtins.Iterator.map"); assert.equal(iteratorRow.resolutionKind, "member"); assert.ok(iteratorRow.resolvedUnitIds.some(unitId => unitId.includes("IteratorObject.map"))); + + const argumentsIterator = findRow(classification, "javascript.functions.arguments.@@iterator"); + assert.equal(argumentsIterator.resolutionKind, "member"); + assert.ok(argumentsIterator.resolvedUnitIds.some(unitId => unitId.includes("IArguments.@@iterator"))); + const argumentsCallee = findRow(classification, "javascript.functions.arguments.callee"); + assert.equal(argumentsCallee.resolutionKind, "member"); + assert.equal(argumentsCallee.includeInTarget, false); }); test("classifier honors the low baseline target and rejects unknown targets", async () => { diff --git a/test/consumer-smoke.test.mjs b/test/consumer-smoke.test.mjs index 9f049be..0767f2f 100644 --- a/test/consumer-smoke.test.mjs +++ b/test/consumer-smoke.test.mjs @@ -38,7 +38,6 @@ test("staged consumer smoke: stock tsc accepts supported baseline APIs and rejec writeJsonFile(path.join(consumerDirectory, "tsconfig.json"), { compilerOptions: { noLib: true, - skipLibCheck: true, strict: true, types: [baselinePackageName], }, @@ -69,7 +68,6 @@ test("staged consumer smoke: stock tsc accepts supported baseline APIs and rejec writeJsonFile(path.join(consumerDirectory, "tsconfig.fail.json"), { compilerOptions: { noLib: true, - skipLibCheck: true, strict: true, types: [baselinePackageName], }, diff --git a/test/excluded-units.test.mjs b/test/excluded-units.test.mjs index 09692d2..1b02bf9 100644 --- a/test/excluded-units.test.mjs +++ b/test/excluded-units.test.mjs @@ -11,6 +11,7 @@ import { import { assertExclusionInvariants, resolveExcludedUnits, + resolveUnclaimedTypeOnlyUnitIds, } from "../lib/generator.mjs"; import { createSurfaceInventory, @@ -176,6 +177,52 @@ test("resolveExcludedUnits bans surface-defining exclusions but keeps shared and assert.ok(!excludedUnitIds.has("unit-shared")); }); +test("resolveUnclaimedTypeOnlyUnitIds preserves global aliases without selecting interface surface", async () => { + const tempDirectory = createTempDirectory(tempDirectories); + const inventory = await createFixtureInventory(tempDirectory, { + "lib.es5.d.ts": [ + "type Utility = { value: T };", + "type ClaimedAlias = string;", + "interface ExcludedSupport { detail: string; }", + "interface TypeOnly {", + " value: string;", + " excluded(input: Utility): void;", + "}", + "interface RuntimeThing {", + " visible(): void;", + "}", + "interface RuntimeThingConstructor {", + " new(): RuntimeThing;", + "}", + "declare var RuntimeThing: RuntimeThingConstructor;", + "", + ].join("\n"), + }); + const claimedAlias = (inventory.declarationUnitsBySymbol.get("ClaimedAlias") ?? [])[0]; + const excludedMember = requireMemberUnit(inventory, "TypeOnly::excluded"); + assert.ok(claimedAlias); + + const selected = resolveUnclaimedTypeOnlyUnitIds({ + inventory, + classifiedCompatRows: [ + { resolvedUnitIds: [claimedAlias.id] }, + { resolvedUnitIds: [excludedMember.id] }, + ], + excludedUnitIds: new Set([excludedMember.id]), + }); + const selectedUnits = selected.map(unitId => inventory.unitById.get(unitId)); + + assert.ok(selectedUnits.some(unit => unit?.symbolName === "Utility")); + assert.ok(!selectedUnits.some(unit => unit?.symbolName === "ClaimedAlias")); + assert.ok(!selectedUnits.some(unit => unit?.symbolName === "ExcludedSupport")); + assert.ok(!selectedUnits.some(unit => unit?.ownerSymbol === "ExcludedSupport")); + assert.ok(!selectedUnits.some(unit => unit?.symbolName === "TypeOnly")); + assert.ok(!selectedUnits.some(unit => unit?.ownerSymbol === "TypeOnly")); + assert.ok(!selectedUnits.some(unit => unit?.ownerSymbol === "TypeOnly" && unit.memberName === "excluded")); + assert.ok(!selectedUnits.some(unit => unit?.symbolName?.startsWith("RuntimeThing"))); + assert.ok(!selectedUnits.some(unit => unit?.ownerSymbol?.startsWith("RuntimeThing"))); +}); + test("assertExclusionInvariants rejects selections that intersect excluded units", async () => { const tempDirectory = createTempDirectory(tempDirectories); const inventory = await createFixtureInventory(tempDirectory, { diff --git a/test/generate.test.mjs b/test/generate.test.mjs index 7773bb9..0efe69b 100644 --- a/test/generate.test.mjs +++ b/test/generate.test.mjs @@ -4,6 +4,7 @@ import assert from "node:assert/strict"; import fs from "node:fs"; import test from "node:test"; import { selectActiveNegativeProbes } from "../lib/negative-probes.mjs"; +import { isLibCompatKey } from "../lib/surface-inventory.mjs"; import { cleanupTempDirectories, createManifest, @@ -23,12 +24,12 @@ test.afterEach(() => { cleanupTempDirectories(tempDirectories); }); -test("generate emits a direct baseline lib for the current JS builtins widely available surface", () => { +test("generate emits the current TypeScript-declarable Baseline JavaScript surface", () => { const tempDirectory = createTempDirectory(tempDirectories); const fixture = createManifest(tempDirectory); /** @type {{ featureRows: unknown[]; compatRows: Array<{ compatKey: string; baselineStatus: string | boolean | undefined; }>; }} */ const dataset = readJsonFile(repoDatasetPath); - const expectedLibCompatRows = dataset.compatRows.filter(row => row.compatKey.startsWith("javascript.builtins.")); + const expectedLibCompatRows = dataset.compatRows.filter(row => isLibCompatKey(row.compatKey)); const expectedHighCompatRows = expectedLibCompatRows.filter(row => row.baselineStatus === "high"); const output = runGenerate(fixture.manifestPath); @@ -46,6 +47,37 @@ test("generate emits a direct baseline lib for the current JS builtins widely av assert.match(topLevelOutput, /interface Iterator \{[\s\S]*next\(/); assert.match(topLevelOutput, /interface ArrayBufferTypes \{[\s\S]*ArrayBuffer: ArrayBuffer;/); assert.match(topLevelOutput, /interface ListFormatOptions \{[\s\S]*localeMatcher\?:/); + for (const typeName of [ + "Awaited", + "Partial", + "Required", + "Readonly", + "Pick", + "Record", + "Exclude", + "Extract", + "Omit", + "NonNullable", + "Parameters", + "ConstructorParameters", + "ReturnType", + "InstanceType", + "ThisParameterType", + "OmitThisParameter", + "ThisType", + "Uppercase", + "Lowercase", + "Capitalize", + "Uncapitalize", + "ReadonlyMap", + "ReadonlySet", + ]) { + assert.match( + topLevelOutput, + new RegExp(`(?:type|interface) ${typeName}(?:[<{\\s]|$)`), + `expected erased declaration ${typeName}`, + ); + } assert.doesNotMatch(topLevelOutput, /\/\/\/ { + const tempDirectory = createTempDirectory(tempDirectories); + const libDirectory = path.join(tempDirectory, "lib"); + fs.mkdirSync(libDirectory, { recursive: true }); + fs.writeFileSync( + path.join(libDirectory, "lib.es5.d.ts"), + [ + "interface Array {", + " read(): void;", + "}", + "interface ReadonlyArray {", + " unrelated(): void;", + "}", + "declare var Array: ArrayConstructor;", + "interface ArrayConstructor {", + " new(): Array;", + "}", + "", + ].join("\n"), + ); + + const sourceLibEntries = await discoverBuiltinSourceLibEntries({ + libDirectory, + reportPathPrefix: "typescript/lib", + }); + await assert.rejects( + createSurfaceInventory({ + snapshotName: "readonly-companion-test", + repoRoot: tempDirectory, + sourceLibEntries, + inventoryOutputPath: path.join(tempDirectory, "inventory.json"), + }), + /Readonly companion ReadonlyArray does not structurally match Array: unrelated/, + ); +}); + +test("type-only aliases cannot introduce unclassified runtime declarations", async () => { + const tempDirectory = createTempDirectory(tempDirectories); + const libDirectory = path.join(tempDirectory, "lib"); + fs.mkdirSync(libDirectory, { recursive: true }); + fs.writeFileSync( + path.join(libDirectory, "lib.es5.d.ts"), + [ + "type FutureAlias = typeof FutureThing;", + "declare var FutureThing: FutureThingConstructor;", + "interface FutureThingConstructor {", + " dangerous(): void;", + "}", + "", + ].join("\n"), + ); + const sourceLibEntries = await discoverBuiltinSourceLibEntries({ + libDirectory, + reportPathPrefix: "typescript/lib", + }); + const inventory = await createSurfaceInventory({ + snapshotName: "type-only-runtime-test", + repoRoot: tempDirectory, + sourceLibEntries, + inventoryOutputPath: path.join(tempDirectory, "inventory.json"), + }); + const aliasUnit = inventory.declarationUnitsBySymbol.get("FutureAlias")?.[0]; + assert.ok(aliasUnit); + + assert.throws( + () => resolveTypeOnlyDependencyClosure({ + inventory, + compatSelectedUnitIds: new Set(), + typeOnlyUnitIds: [aliasUnit.id], + completeContainerUnitIds: new Set(), + excludedUnitIds: new Set(), + }), + /Type-only aliases introduce runtime declarations.*FutureThing/s, + ); +}); diff --git a/test/type-only-consumer.test.mjs b/test/type-only-consumer.test.mjs new file mode 100644 index 0000000..e1a09f6 --- /dev/null +++ b/test/type-only-consumer.test.mjs @@ -0,0 +1,90 @@ +// @ts-check + +import fs from "node:fs"; +import path from "node:path"; +import test from "node:test"; +import { + cleanupTempDirectories, + createTempDirectory, + repoGeneratedLibPath, + runTsc, + runTscStrada, + writeJsonFile, + writeTextFile, +} from "./helpers.mjs"; + +/** @type {string[]} */ +const tempDirectories = []; + +test.afterEach(() => { + cleanupTempDirectories(tempDirectories); +}); + +test("generated lib supports erased utility types with strict library checking", () => { + const tempDirectory = createTempDirectory(tempDirectories); + writeTextFile( + path.join(tempDirectory, "baseline.d.ts"), + fs.readFileSync(repoGeneratedLibPath, "utf8"), + ); + writeTextFile(path.join(tempDirectory, "third-party.d.ts"), [ + "declare const thirdPartyLabels: Record;", + "", + ].join("\n")); + writeTextFile(path.join(tempDirectory, "consumer.ts"), [ + "interface Model { a: string; b?: number; readonly c: boolean; }", + "declare function callable(this: { prefix: string }, value: number, flag?: boolean): string;", + "declare abstract class Constructable { constructor(value: number, label?: string); value: number; }", + "type T01 = Awaited>;", + "type T02 = Partial;", + "type T03 = Required;", + "type T04 = Readonly;", + "type T05 = Pick;", + "type T06 = Record;", + "type T07 = Exclude<\"a\" | \"b\", \"b\">;", + "type T08 = Extract<\"a\" | \"b\", \"b\">;", + "type T09 = Omit;", + "type T10 = NonNullable;", + "type T11 = Parameters;", + "type T12 = ConstructorParameters;", + "type T13 = ReturnType;", + "type T14 = InstanceType;", + "type T15 = ThisParameterType;", + "type T16 = OmitThisParameter;", + "type T17 = Uppercase<\"baseline\">;", + "type T18 = Lowercase<\"BASELINE\">;", + "type T19 = Capitalize<\"baseline\">;", + "type T20 = Uncapitalize<\"Baseline\">;", + "const contextual: { method(): number } & ThisType<{ value: number }> = {", + " method() { return this.value; },", + "};", + "declare const readonlyMap: ReadonlyMap;", + "declare const readonlySet: ReadonlySet;", + "declare const readonlyValues: readonly number[];", + "declare const promiseLike: PromiseLike;", + "declare const callableValue: ((this: { prefix: string }, value: number) => string) & CallableFunction;", + "readonlyMap.get(thirdPartyLabels.baseline);", + "readonlySet.has(contextual.method());", + "readonlyValues.findLast(value => value > 0);", + "readonlyValues.toReversed();", + "promiseLike.then(value => value + 1);", + "callableValue.call({ prefix: \"\" }, 1);", + "callableValue.bind({ prefix: \"\" });", + "declare const arr: readonly number[];", + "for (const x of arr) { const value: number = x; value; }", + "function readArguments() { for (const value of arguments) { value; } }", + "", + ].join("\n")); + writeJsonFile(path.join(tempDirectory, "tsconfig.json"), { + compilerOptions: { + noLib: true, + skipLibCheck: false, + strict: true, + target: "esnext", + noEmit: true, + }, + files: ["baseline.d.ts", "third-party.d.ts", "consumer.ts"], + }); + + runTsc(["-p", path.join(tempDirectory, "tsconfig.json")], { cwd: tempDirectory }); + runTscStrada(["-p", path.join(tempDirectory, "tsconfig.json")], { cwd: tempDirectory }); +});