diff --git a/.all-contributorsrc b/.all-contributorsrc index 86223eafbf..d9fba3b00f 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1246,7 +1246,7 @@ }, { "login": "kaizen3031593", - "name": "kaizen3031593", + "name": "Kaizen Conroy", "avatar_url": "https://avatars.githubusercontent.com/u/36202692?v=4", "profile": "https://github.com/kaizen3031593", "contributions": [ diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 688c4518b5..2eea33c9f1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -41,7 +41,7 @@ jobs: java-version: '8' distribution: 'zulu' - name: Set up Node 12 - uses: actions/setup-node@v2.4.1 + uses: actions/setup-node@v2.5.0 with: cache: yarn node-version: '12' @@ -135,7 +135,7 @@ jobs: java-version: '8' distribution: 'zulu' - name: Set up Node 12 - uses: actions/setup-node@v2.4.1 + uses: actions/setup-node@v2.5.0 with: cache: yarn node-version: '12' @@ -304,7 +304,7 @@ jobs: java-version: ${{ matrix.java }} distribution: 'zulu' - name: Set up Node ${{ matrix.node }} - uses: actions/setup-node@v2.4.1 + uses: actions/setup-node@v2.5.0 with: cache: yarn node-version: ${{ matrix.node }} diff --git a/.github/workflows/yarn-upgrade.yml b/.github/workflows/yarn-upgrade.yml index 95fda35aac..c94b43b0be 100644 --- a/.github/workflows/yarn-upgrade.yml +++ b/.github/workflows/yarn-upgrade.yml @@ -18,7 +18,7 @@ jobs: uses: actions/checkout@v2.4.0 - name: Set up Node - uses: actions/setup-node@v2.4.1 + uses: actions/setup-node@v2.5.0 with: cache: yarn node-version: 12 @@ -140,7 +140,7 @@ jobs: title: 'chore: npm-check-updates && yarn upgrade' body: |- Ran npm-check-updates and yarn upgrade to keep the `yarn.lock` file up-to-date. - labels: contribution/core,dependencies + labels: contribution/core,dependencies,pr/auto-approve team-reviewers: aws-cdk-team # Privileged token so automated PR validation happens token: ${{ secrets.AUTO_APPROVE_GITHUB_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 45a4f4f9d4..ac9e0cd1cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.47.0](https://github.com/aws/jsii/compare/v1.45.0...v1.47.0) (2021-12-06) + + +### Features + +* **reflect:** add `allTypes` accessor ([#3194](https://github.com/aws/jsii/issues/3194)) ([41f301a](https://github.com/aws/jsii/commit/41f301a8304bd1ed6ed7ec4e31bd23ffd1a2ed8b)) +* **rosetta:** metadata tag for fixtures in docs ([#3200](https://github.com/aws/jsii/issues/3200)) ([8cefa8b](https://github.com/aws/jsii/commit/8cefa8bc8c9554913960b48226531aff874ee247)) +* **rosetta:** generate rosetta tablets next to each assembly ([#3223](https://github.com/aws/jsii/issues/3223)) ([1e7b604](https://github.com/aws/jsii/commit/1e7b604c15a0083f27ceafd8ca32ff9b6cf61759)) +* **rosetta:** reuse output file as additional cache and introduce `--infuse` option for `extract` ([#3210](https://github.com/aws/jsii/issues/3210)) ([ccb3c57](https://github.com/aws/jsii/commit/ccb3c57b834225f16ec619f55a7976e59b7a53c3)) + + +### Bug Fixes + +* **jsii:** constants can't mix letters and digits ([#3209](https://github.com/aws/jsii/issues/3209)) ([a444e29](https://github.com/aws/jsii/commit/a444e2993b73dc002586e2c4a3446121c279eb65)), closes [#3208](https://github.com/aws/jsii/issues/3208) +* **jsii:** deprecation message is not displayed for deprecated classes ([#3206](https://github.com/aws/jsii/issues/3206)) ([3841538](https://github.com/aws/jsii/commit/3841538179226b67c756ca8689ac1a3de4bec521)) +* **pacmak:** don't automatically translate examples without asking ([#3219](https://github.com/aws/jsii/issues/3219)) ([937f8c3](https://github.com/aws/jsii/commit/937f8c3753bec27269ce9213a6bc55fea79647b0)) +* **rosetta:** `extract` ignores `--compile` option ([#3193](https://github.com/aws/jsii/issues/3193)) ([639c510](https://github.com/aws/jsii/commit/639c510ba6d07b26bf35d0c8d3c9cdcced6d916a)) +* **rosetta:** enum resolution breaks for properties ([#3190](https://github.com/aws/jsii/issues/3190)) ([3b49066](https://github.com/aws/jsii/commit/3b49066e4dd960385709dbdce1d9c1fbfb2f20cf)) +* **rosetta:** use `--compile` flag by default ([#3218](https://github.com/aws/jsii/issues/3218)) ([9df7950](https://github.com/aws/jsii/commit/9df7950f263aae045877accb45007e0f9a5b03bd)) + ## [1.46.0](https://github.com/aws/jsii/compare/v1.45.0...v1.46.0) (2021-11-21) diff --git a/README.md b/README.md index 7a1d76ddd4..68acb33c44 100644 --- a/README.md +++ b/README.md @@ -139,81 +139,81 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Joseph Martin

🐛
Junix

🐛
Justin Taylor

🐛 +
Kaizen Conroy

💻 🐛
Kyle Thomson

💻 👀
Leandro Padua

🐛
Liang Zhou

🐛 💻 -
Madeline Kusters

💻 🐛 +
Madeline Kusters

💻 🐛
Maja S Bratseth

🐛
Marcos Diez

🐛
Mark Nielsen

💻
Matthew Bonig

🐛 📝
Matthew Pirocchi

💻 🤔 👀
Michael Neil

🚧 -
Mike Lane

🐛 +
Mike Lane

🐛
Mitch Garnaat

🐛 💻 🤔 👀
Mitchell Valine

🐛 💻 🤔 🚧 👀
Mohamad Soufan

📖
Neta Nir

💻 🤔 🚧 👀
Nick Lynch

🐛 💻 🚧 👀
Niranjan Jayakar

🐛 💻 🤔 🚧 👀 -
Noah Litov

💻 🚧 👀 +
Noah Litov

💻 🚧 👀
Otavio Macedo

💻 🐛
PIDZ - Bart

🤔
Peter Woodworth

🚧
Petr Kacer

🐛
Petra Barus

💻
Philip Cali

🤔 -
Quentin Loos

🤔 +
Quentin Loos

🤔
Raphael

🐛
Richard H Boyd

🐛
Rico Huijbers

🐛 💻 🤔 🚧 👀
Romain Marcadier

🐛 💻 🎨 🤔 🚧 👀 📝
SADIK KUZU

👀
SK

🤔 -
Sam Fink

💻 👀 +
Sam Fink

💻 👀
Sam Goodwin

👀
Sebastian Korfmann

🐛 💻 🤔
Shane Witbeck

🤔
Shiv Lakshminarayan

💻 🚧 👀
Somaya

💻 🤔 🚧 👀
The Gitter Badger

💻 🚧 -
Thomas Poignant

🐛 +
Thomas Poignant

🐛
Thomas Steinbach

🐛
Thorsten Hoeger

💻
Tim Wagner

🐛 🤔
Tobias Lidskog

💻
Ty Coghlan

🐛
Tyler van Hensbergen

🤔 -
Vlad Hrybok

🐛 +
Vlad Hrybok

🐛
Vladimir Shchur

🐛
Yan Zhulanow

💻
Yigong Liu

🐛 🤔
Zach Bienenfeld

🐛
ajnarang

🤔
aniljava

💻 -
deccy-mcc

🐛 +
deccy-mcc

🐛
dependabot-preview[bot]

🐛 🚧
dependabot[bot]

🚧
dheffx

🐛
gregswdl

🐛
guyroberts21

📖 -
kaizen3031593

💻 🐛
mattBrzezinski

📖 diff --git a/eslint-config.yaml b/eslint-config.yaml index 83ee363cec..8c6c72d389 100644 --- a/eslint-config.yaml +++ b/eslint-config.yaml @@ -238,6 +238,10 @@ rules: 'no-case-declarations': off 'require-atomic-updates': off + # This is not a bad rule but it got sprung on us and our code loudly fails it + # Disable to get the eslint upgrade to pass. + '@typescript-eslint/no-unsafe-argument': off + # 'consistent-return' actually decreases safety. Its use will enforce useless `throws` # statements, forcing a runtime error that occlude cases where the TypeScript type # checker would actually have caught something like a non-exhaustive `switch` statement diff --git a/lerna.json b/lerna.json index cd5f11c975..4b6def9308 100644 --- a/lerna.json +++ b/lerna.json @@ -10,5 +10,5 @@ "rejectCycles": true } }, - "version": "1.46.0" + "version": "1.47.0" } diff --git a/package.json b/package.json index 90690b0f36..dde647af0e 100644 --- a/package.json +++ b/package.json @@ -15,23 +15,23 @@ "compliance": "(cd tools/jsii-compliance && yarn report)" }, "devDependencies": { - "@jest/types": "^27.2.4", - "@typescript-eslint/eslint-plugin": "^4.33.0", - "@typescript-eslint/parser": "^4.33.0", + "@jest/types": "^27.2.5", + "@typescript-eslint/eslint-plugin": "^5.4.0", + "@typescript-eslint/parser": "^5.4.0", "all-contributors-cli": "^6.20.0", - "eslint": "^7.32.0", + "eslint": "^8.3.0", "eslint-config-prettier": "^8.3.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^2.5.0", - "eslint-plugin-import": "^2.24.2", + "eslint-plugin-import": "^2.25.3", "eslint-plugin-prettier": "^4.0.0", - "jest-circus": "^27.2.4", - "jest-config": "^27.2.4", + "jest-circus": "^27.3.1", + "jest-config": "^27.3.1", "lerna": "^4.0.0", "prettier": "^2.4.1", - "standard-version": "^9.3.1", - "ts-jest": "^27.0.5", - "ts-node": "^10.2.1", + "standard-version": "^9.3.2", + "ts-jest": "^27.0.7", + "ts-node": "^10.4.0", "typescript": "~3.9.10" }, "repository": { diff --git a/packages/@jsii/Directory.Build.targets b/packages/@jsii/Directory.Build.targets index 5df07af12a..b2b6ca0bf2 100644 --- a/packages/@jsii/Directory.Build.targets +++ b/packages/@jsii/Directory.Build.targets @@ -6,7 +6,7 @@ - + diff --git a/packages/@jsii/check-node/package.json b/packages/@jsii/check-node/package.json index 1ec84558d8..e553956449 100644 --- a/packages/@jsii/check-node/package.json +++ b/packages/@jsii/check-node/package.json @@ -41,8 +41,8 @@ }, "devDependencies": { "@types/chalk": "^2.2.0", - "@types/jest": "^27.0.2", - "@types/node": "^12.20.28", - "jest": "^27.2.4" + "@types/jest": "^27.0.3", + "@types/node": "^12.20.37", + "jest": "^27.3.1" } } diff --git a/packages/@jsii/dotnet-runtime-test/package.json b/packages/@jsii/dotnet-runtime-test/package.json index 8680f8820e..ccf4e8654f 100644 --- a/packages/@jsii/dotnet-runtime-test/package.json +++ b/packages/@jsii/dotnet-runtime-test/package.json @@ -31,7 +31,7 @@ }, "devDependencies": { "@jsii/dotnet-runtime": "^0.0.0", - "@types/node": "^12.20.28", + "@types/node": "^12.20.37", "jsii-calc": "^3.20.120", "jsii-pacmak": "^0.0.0", "typescript": "~3.9.10" diff --git a/packages/@jsii/dotnet-runtime/package.json b/packages/@jsii/dotnet-runtime/package.json index 3419d0d8e1..33aca99fb0 100644 --- a/packages/@jsii/dotnet-runtime/package.json +++ b/packages/@jsii/dotnet-runtime/package.json @@ -39,8 +39,8 @@ }, "devDependencies": { "@jsii/runtime": "^0.0.0", - "@types/node": "^12.20.28", - "@types/semver": "^7.3.8", + "@types/node": "^12.20.37", + "@types/semver": "^7.3.9", "jsii-build-tools": "^0.0.0", "semver": "^7.3.5", "typescript": "~3.9.10" diff --git a/packages/@jsii/go-runtime/package.json b/packages/@jsii/go-runtime/package.json index 96a5f2216b..92c125dfd0 100644 --- a/packages/@jsii/go-runtime/package.json +++ b/packages/@jsii/go-runtime/package.json @@ -24,14 +24,14 @@ }, "devDependencies": { "@types/fs-extra": "^9.0.13", - "@types/node": "^12.20.28", + "@types/node": "^12.20.37", "codemaker": "^0.0.0", - "eslint": "^7.32.0", + "eslint": "^8.3.0", "fs-extra": "^9.1.0", "jsii-build-tools": "^0.0.0", "jsii-calc": "^3.20.120", "prettier": "^2.4.1", - "ts-node": "^10.2.1", + "ts-node": "^10.4.0", "typescript": "~3.9.10" } } diff --git a/packages/@jsii/integ-test/package.json b/packages/@jsii/integ-test/package.json index a60a591de5..55e57cf026 100644 --- a/packages/@jsii/integ-test/package.json +++ b/packages/@jsii/integ-test/package.json @@ -17,10 +17,10 @@ }, "license": "Apache-2.0", "dependencies": { - "@octokit/rest": "^18.11.4", + "@octokit/rest": "^18.12.0", "dotenv": "^8.6.0", "fs-extra": "^9.1.0", - "jest": "^27.2.4", + "jest": "^27.3.1", "jsii": "^0.0.0", "jsii-pacmak": "^0.0.0", "jsii-rosetta": "^0.0.0", @@ -29,10 +29,10 @@ "devDependencies": { "@types/dotenv": "^8.2.0", "@types/fs-extra": "^9.0.13", - "@types/jest": "^27.0.2", - "@types/node": "^12.20.28", - "@types/tar": "^4.0.5", - "eslint": "^7.32.0", + "@types/jest": "^27.0.3", + "@types/node": "^12.20.37", + "@types/tar": "^6.1.1", + "eslint": "^8.3.0", "prettier": "^2.4.1", "typescript": "~3.9.10" } diff --git a/packages/@jsii/java-runtime/package.json b/packages/@jsii/java-runtime/package.json index 45446ce3c8..cf74f3fbfa 100644 --- a/packages/@jsii/java-runtime/package.json +++ b/packages/@jsii/java-runtime/package.json @@ -33,7 +33,7 @@ }, "devDependencies": { "@jsii/runtime": "^0.0.0", - "@types/node": "^12.20.28", + "@types/node": "^12.20.37", "jsii-build-tools": "^0.0.0", "typescript": "~3.9.10" } diff --git a/packages/@jsii/kernel/package.json b/packages/@jsii/kernel/package.json index 3a7c862a95..eecf968f32 100644 --- a/packages/@jsii/kernel/package.json +++ b/packages/@jsii/kernel/package.json @@ -39,16 +39,16 @@ "@scope/jsii-calc-base": "^0.0.0", "@scope/jsii-calc-lib": "^0.0.0", "@types/fs-extra": "^9.0.13", - "@types/jest": "^27.0.2", - "@types/node": "^12.20.28", - "@types/tar": "^4.0.5", - "eslint": "^7.32.0", - "jest": "^27.2.4", + "@types/jest": "^27.0.3", + "@types/node": "^12.20.37", + "@types/tar": "^6.1.1", + "eslint": "^8.3.0", + "jest": "^27.3.1", "jest-expect-message": "^1.0.2", "jsii-build-tools": "^0.0.0", "jsii-calc": "^3.20.120", "prettier": "^2.4.1", - "ts-jest": "^27.0.5", + "ts-jest": "^27.0.7", "typescript": "~3.9.10" } } diff --git a/packages/@jsii/python-runtime/package.json b/packages/@jsii/python-runtime/package.json index f3f9a36e97..62a2669f42 100644 --- a/packages/@jsii/python-runtime/package.json +++ b/packages/@jsii/python-runtime/package.json @@ -41,7 +41,7 @@ "jsii-build-tools": "^0.0.0", "jsii-calc": "^3.20.120", "jsii-pacmak": "^0.0.0", - "ts-node": "^10.2.1", + "ts-node": "^10.4.0", "typescript": "~3.9.10" } } diff --git a/packages/@jsii/python-runtime/requirements.txt b/packages/@jsii/python-runtime/requirements.txt index 825f29a79d..8325b38ea2 100644 --- a/packages/@jsii/python-runtime/requirements.txt +++ b/packages/@jsii/python-runtime/requirements.txt @@ -1,9 +1,9 @@ -black~=21.10b0 +black~=21.11b1 mypy==0.812 pip~=21.3 pytest~=6.2 pytest-mypy~=0.8 -setuptools~=58.5 +setuptools~=59.2 wheel~=0.37 -e . diff --git a/packages/@jsii/python-runtime/setup.py b/packages/@jsii/python-runtime/setup.py index 52768e51c6..8a569b8e82 100644 --- a/packages/@jsii/python-runtime/setup.py +++ b/packages/@jsii/python-runtime/setup.py @@ -35,7 +35,7 @@ "cattrs~=1.8.0 ; python_version >= '3.7'", "importlib_resources ; python_version < '3.7'", "python-dateutil", - "typing_extensions~=3.7", + "typing_extensions>=3.7,<5.0", ], python_requires="~=3.6", classifiers=[ diff --git a/packages/@jsii/runtime/package.json b/packages/@jsii/runtime/package.json index 3db822ffc7..8d337546d7 100644 --- a/packages/@jsii/runtime/package.json +++ b/packages/@jsii/runtime/package.json @@ -41,17 +41,17 @@ "devDependencies": { "@scope/jsii-calc-base": "^0.0.0", "@scope/jsii-calc-lib": "^0.0.0", - "@types/jest": "^27.0.2", - "@types/node": "^12.20.28", - "eslint": "^7.32.0", - "jest": "^27.2.4", + "@types/jest": "^27.0.3", + "@types/node": "^12.20.37", + "eslint": "^8.3.0", + "jest": "^27.3.1", "jsii-build-tools": "^0.0.0", "jsii-calc": "^3.20.120", "prettier": "^2.4.1", "source-map-loader": "^3.0.0", - "ts-jest": "^27.0.5", + "ts-jest": "^27.0.7", "typescript": "~3.9.10", - "webpack": "^5.57.1", - "webpack-cli": "^4.8.0" + "webpack": "^5.64.3", + "webpack-cli": "^4.9.1" } } diff --git a/packages/@jsii/spec/lib/validate-assembly.ts b/packages/@jsii/spec/lib/validate-assembly.ts index 250c6ae478..4f798e2f97 100644 --- a/packages/@jsii/spec/lib/validate-assembly.ts +++ b/packages/@jsii/spec/lib/validate-assembly.ts @@ -8,7 +8,7 @@ export const schema: Schema = require('../schema/jsii-spec.schema.json'); export function validateAssembly(obj: any): Assembly { const validator = new Validator(); validator.addSchema(schema); // For definitions - const result = validator.validate(obj, schema, { nestedErrors: true } as any); // nestedErrors does exist but is not in the TypeScript definitions + const result = validator.validate(obj, schema, { nestedErrors: true }); if (result.valid) { return obj; } diff --git a/packages/@jsii/spec/package.json b/packages/@jsii/spec/package.json index bbb2eaa02a..3e7a5ef946 100644 --- a/packages/@jsii/spec/package.json +++ b/packages/@jsii/spec/package.json @@ -34,13 +34,13 @@ "jsonschema": "^1.4.0" }, "devDependencies": { - "@types/jest": "^27.0.2", - "@types/node": "^12.20.28", - "eslint": "^7.32.0", - "jest": "^27.2.4", + "@types/jest": "^27.0.3", + "@types/node": "^12.20.37", + "eslint": "^8.3.0", + "jest": "^27.3.1", "jsii-build-tools": "^0.0.0", "prettier": "^2.4.1", "typescript": "~3.9.10", - "typescript-json-schema": "^0.51.0" + "typescript-json-schema": "^0.52.0" } } diff --git a/packages/@scope/jsii-calc-base-of-base/package.json b/packages/@scope/jsii-calc-base-of-base/package.json index 3c33eed5ef..b4ed0b7c0d 100644 --- a/packages/@scope/jsii-calc-base-of-base/package.json +++ b/packages/@scope/jsii-calc-base-of-base/package.json @@ -30,7 +30,7 @@ "test:update": "npm run build && UPDATE_DIFF=1 npm run test" }, "devDependencies": { - "@types/node": "^12.20.28", + "@types/node": "^12.20.37", "jsii": "^0.0.0", "jsii-build-tools": "^0.0.0", "jsii-rosetta": "^0.0.0", diff --git a/packages/@scope/jsii-calc-base/package.json b/packages/@scope/jsii-calc-base/package.json index 13407f9701..0398e3d2c0 100644 --- a/packages/@scope/jsii-calc-base/package.json +++ b/packages/@scope/jsii-calc-base/package.json @@ -35,7 +35,7 @@ "@scope/jsii-calc-base-of-base": "^2.1.1" }, "devDependencies": { - "@types/node": "^12.20.28", + "@types/node": "^12.20.37", "jsii": "^0.0.0", "jsii-build-tools": "^0.0.0", "jsii-rosetta": "^0.0.0", diff --git a/packages/@scope/jsii-calc-lib/package.json b/packages/@scope/jsii-calc-lib/package.json index 6a9659d57b..e068cebef4 100644 --- a/packages/@scope/jsii-calc-lib/package.json +++ b/packages/@scope/jsii-calc-lib/package.json @@ -39,7 +39,7 @@ "@scope/jsii-calc-base-of-base": "^2.1.1" }, "devDependencies": { - "@types/node": "^12.20.28", + "@types/node": "^12.20.37", "jsii": "^0.0.0", "jsii-build-tools": "^0.0.0", "jsii-rosetta": "^0.0.0", diff --git a/packages/codemaker/package.json b/packages/codemaker/package.json index fcf27a2e04..960ad07123 100644 --- a/packages/codemaker/package.json +++ b/packages/codemaker/package.json @@ -31,16 +31,16 @@ "package": "rm -fr dist/js && mkdir -p dist/js && mv $(npm pack) dist/js" }, "dependencies": { - "camelcase": "^6.2.0", + "camelcase": "^6.2.1", "decamelize": "^5.0.1", "fs-extra": "^9.1.0" }, "devDependencies": { "@types/fs-extra": "^9.0.13", - "@types/jest": "^27.0.2", - "@types/node": "^12.20.28", - "eslint": "^7.32.0", - "jest": "^27.2.4", + "@types/jest": "^27.0.3", + "@types/node": "^12.20.37", + "eslint": "^8.3.0", + "jest": "^27.3.1", "prettier": "^2.4.1", "typescript": "~3.9.10" } diff --git a/packages/jsii-calc/lib/calculator.ts b/packages/jsii-calc/lib/calculator.ts index 23cea54d00..f6cbdccab5 100644 --- a/packages/jsii-calc/lib/calculator.ts +++ b/packages/jsii-calc/lib/calculator.ts @@ -168,7 +168,7 @@ export namespace composition { * The expression that this operation consists of. * Must be implemented by derived classes. */ - abstract readonly expression: NumericValue; + public abstract readonly expression: NumericValue; public toString() { switch (this.stringStyle) { diff --git a/packages/jsii-calc/package.json b/packages/jsii-calc/package.json index d1c77df2a1..16fea36110 100644 --- a/packages/jsii-calc/package.json +++ b/packages/jsii-calc/package.json @@ -51,8 +51,8 @@ "@scope/jsii-calc-lib": "^0.0.0" }, "devDependencies": { - "@types/node": "^12.20.28", - "eslint": "^7.32.0", + "@types/node": "^12.20.37", + "eslint": "^8.3.0", "jsii": "^0.0.0", "jsii-build-tools": "^0.0.0", "jsii-rosetta": "^0.0.0", diff --git a/packages/jsii-config/package.json b/packages/jsii-config/package.json index 58f7d71ad7..c3ca077367 100644 --- a/packages/jsii-config/package.json +++ b/packages/jsii-config/package.json @@ -20,11 +20,11 @@ }, "devDependencies": { "@types/inquirer": "^8.1.3", - "@types/jest": "^27.0.2", - "@types/node": "^12.20.28", - "@types/yargs": "^17.0.3", - "eslint": "^7.32.0", - "jest": "^27.2.4", + "@types/jest": "^27.0.3", + "@types/node": "^12.20.37", + "@types/yargs": "^17.0.7", + "eslint": "^8.3.0", + "jest": "^27.3.1", "jest-expect-message": "^1.0.2", "prettier": "^2.4.1", "typescript": "~3.9.10" diff --git a/packages/jsii-diff/package.json b/packages/jsii-diff/package.json index fe82688389..d28555edd9 100644 --- a/packages/jsii-diff/package.json +++ b/packages/jsii-diff/package.json @@ -43,11 +43,11 @@ }, "devDependencies": { "@types/fs-extra": "^9.0.13", - "@types/jest": "^27.0.2", - "@types/node": "^12.20.28", + "@types/jest": "^27.0.3", + "@types/node": "^12.20.37", "@types/tar-fs": "^2.0.1", - "eslint": "^7.32.0", - "jest": "^27.2.4", + "eslint": "^8.3.0", + "jest": "^27.3.1", "jest-expect-message": "^1.0.2", "jsii": "^0.0.0", "jsii-build-tools": "^0.0.0", diff --git a/packages/jsii-pacmak/bin/jsii-pacmak.ts b/packages/jsii-pacmak/bin/jsii-pacmak.ts index 35f39df32d..189ea7a9e2 100644 --- a/packages/jsii-pacmak/bin/jsii-pacmak.ts +++ b/packages/jsii-pacmak/bin/jsii-pacmak.ts @@ -95,7 +95,7 @@ import { VERSION_DESC } from '../lib/version'; .option('rosetta-translate-live', { type: 'boolean', desc: "Translate code samples on-the-fly if they can't be found in the samples tablet (deprecated)", - default: true, + default: undefined, }) .option('rosetta-unknown-snippets', { type: 'string', @@ -135,6 +135,21 @@ import { VERSION_DESC } from '../lib/version'; // Default to 4 threads in case of concurrency, good enough for most situations debug('command line arguments:', argv); + if ( + argv['rosetta-translate-live'] !== undefined && + argv['rosetta-unknown-snippets'] !== undefined + ) { + throw new Error( + 'Prefer using --rosetta-unknown-snippets over --rosetta-translate-live', + ); + } + + const rosettaUnknownSnippets = + (argv['rosetta-unknown-snippets'] as UnknownSnippetMode | undefined) ?? + (argv['rosetta-translate-live'] + ? UnknownSnippetMode.TRANSLATE + : UnknownSnippetMode.VERBATIM); + return pacmak({ argv, clean: argv.clean, @@ -147,10 +162,7 @@ import { VERSION_DESC } from '../lib/version'; outputDirectory: argv.outdir, parallel: argv.parallel, recurse: argv.recurse, - rosettaLiveConversion: argv['rosetta-translate-live'], - rosettaUnknownSnippets: argv['rosetta-unknown-snippets'] as - | UnknownSnippetMode - | undefined, + rosettaUnknownSnippets, rosettaTablet: argv['rosetta-tablet'], targets: argv.targets?.map((target) => target as TargetName), updateNpmIgnoreFiles: argv.npmignore, diff --git a/packages/jsii-pacmak/lib/index.ts b/packages/jsii-pacmak/lib/index.ts index 5d3c358306..1e8d6d7e9f 100644 --- a/packages/jsii-pacmak/lib/index.ts +++ b/packages/jsii-pacmak/lib/index.ts @@ -29,7 +29,6 @@ export async function pacmak({ outputDirectory, parallel = true, recurse = false, - rosettaLiveConversion, rosettaTablet, targets = Object.values(TargetName), timers = new Timers(), @@ -37,13 +36,7 @@ export async function pacmak({ updateNpmIgnoreFiles = false, validateAssemblies = false, }: PacmakOptions): Promise { - const unknownSnippets = - rosettaUnknownSnippets ?? - (rosettaLiveConversion - ? UnknownSnippetMode.TRANSLATE - : UnknownSnippetMode.VERBATIM); - - const rosetta = new Rosetta({ unknownSnippets }); + const rosetta = new Rosetta({ unknownSnippets: rosettaUnknownSnippets }); if (rosettaTablet) { await rosetta.loadTabletFromFile(rosettaTablet); } @@ -236,19 +229,10 @@ export interface PacmakOptions { */ readonly recurse?: boolean; - /** - * Whether `jsii-rosetta` conversion should be performed in-band for examples found in documentation which are not - * already translated in the `rosettaTablet` file. - * - * @default false - * @deprecated Use `rosettaUnknownSnippets` instead. - */ - readonly rosettaLiveConversion?: boolean; - /** * How rosetta should treat snippets that cannot be loaded from a translation tablet. * - * @default - falls back to the default of `rosettaLiveConversion`. + * @default UnknownSnippetMode.VERBATIM */ readonly rosettaUnknownSnippets?: UnknownSnippetMode; diff --git a/packages/jsii-pacmak/lib/targets/python/requirements-dev.txt b/packages/jsii-pacmak/lib/targets/python/requirements-dev.txt index ff95e1d96e..b2dc5a9e05 100644 --- a/packages/jsii-pacmak/lib/targets/python/requirements-dev.txt +++ b/packages/jsii-pacmak/lib/targets/python/requirements-dev.txt @@ -3,7 +3,7 @@ # be installed in the virtual environment used for building the distribution # package (wheel, sdist), but not declared as build-system dependencies. -setuptools~=58.5.3 # build-system +setuptools~=59.1.1 # build-system wheel~=0.37.0 # build-system -twine~=3.5.0 +twine~=3.6.0 diff --git a/packages/jsii-pacmak/package.json b/packages/jsii-pacmak/package.json index 5163ba831c..493492d406 100644 --- a/packages/jsii-pacmak/package.json +++ b/packages/jsii-pacmak/package.json @@ -59,16 +59,16 @@ "@types/clone": "^2.1.1", "@types/commonmark": "^0.27.5", "@types/fs-extra": "^9.0.13", - "@types/jest": "^27.0.2", - "@types/node": "^12.20.28", - "@types/semver": "^7.3.8", - "eslint": "^7.32.0", - "jest": "^27.2.4", + "@types/jest": "^27.0.3", + "@types/node": "^12.20.37", + "@types/semver": "^7.3.9", + "eslint": "^8.3.0", + "jest": "^27.3.1", "jsii": "^0.0.0", "jsii-build-tools": "^0.0.0", "jsii-calc": "^3.20.120", "prettier": "^2.4.1", - "ts-jest": "^27.0.5", + "ts-jest": "^27.0.7", "typescript": "~3.9.10" }, "keywords": [ diff --git a/packages/jsii-pacmak/test/generated-code/__snapshots__/examples.test.ts.snap b/packages/jsii-pacmak/test/generated-code/__snapshots__/examples.test.ts.snap index c4f1ca905f..bca0c45aaf 100644 --- a/packages/jsii-pacmak/test/generated-code/__snapshots__/examples.test.ts.snap +++ b/packages/jsii-pacmak/test/generated-code/__snapshots__/examples.test.ts.snap @@ -1147,7 +1147,7 @@ testpkg.FooBar=example.test.demo.FooBar exports[`diamond-struct-parameter.ts: /python/pyproject.toml 1`] = ` [build-system] -requires = ["setuptools~=58.5.3", "wheel~=0.37.0"] +requires = ["setuptools~=59.1.1", "wheel~=0.37.0"] build-backend = "setuptools.build_meta" `; @@ -2447,7 +2447,7 @@ testpkg.Namespace2.Foo.Final=example.test.demo.Namespace2$Foo.Final exports[`nested-types.ts: /python/pyproject.toml 1`] = ` [build-system] -requires = ["setuptools~=58.5.3", "wheel~=0.37.0"] +requires = ["setuptools~=59.1.1", "wheel~=0.37.0"] build-backend = "setuptools.build_meta" `; diff --git a/packages/jsii-pacmak/test/generated-code/__snapshots__/prerelease-identifiers.test.ts.snap b/packages/jsii-pacmak/test/generated-code/__snapshots__/prerelease-identifiers.test.ts.snap index cf54ff1ed1..e4cda418d9 100644 --- a/packages/jsii-pacmak/test/generated-code/__snapshots__/prerelease-identifiers.test.ts.snap +++ b/packages/jsii-pacmak/test/generated-code/__snapshots__/prerelease-identifiers.test.ts.snap @@ -416,7 +416,7 @@ foo exports[`foo@1.2.3 depends on bar@^2.0.0-rc.42: /python/pyproject.toml 1`] = ` [build-system] -requires = ["setuptools~=58.5.3", "wheel~=0.37.0"] +requires = ["setuptools~=59.1.1", "wheel~=0.37.0"] build-backend = "setuptools.build_meta" `; @@ -925,7 +925,7 @@ foo exports[`foo@1.2.3 depends on bar@^4.5.6-pre.1337: /python/pyproject.toml 1`] = ` [build-system] -requires = ["setuptools~=58.5.3", "wheel~=0.37.0"] +requires = ["setuptools~=59.1.1", "wheel~=0.37.0"] build-backend = "setuptools.build_meta" `; @@ -1414,7 +1414,7 @@ foo exports[`foo@2.0.0-rc.42: /python/pyproject.toml 1`] = ` [build-system] -requires = ["setuptools~=58.5.3", "wheel~=0.37.0"] +requires = ["setuptools~=59.1.1", "wheel~=0.37.0"] build-backend = "setuptools.build_meta" `; @@ -1900,7 +1900,7 @@ foo exports[`foo@4.5.6-pre.1337: /python/pyproject.toml 1`] = ` [build-system] -requires = ["setuptools~=58.5.3", "wheel~=0.37.0"] +requires = ["setuptools~=59.1.1", "wheel~=0.37.0"] build-backend = "setuptools.build_meta" `; diff --git a/packages/jsii-pacmak/test/generated-code/__snapshots__/target-python.test.ts.snap b/packages/jsii-pacmak/test/generated-code/__snapshots__/target-python.test.ts.snap index c75ddb6185..ed2a5b917b 100644 --- a/packages/jsii-pacmak/test/generated-code/__snapshots__/target-python.test.ts.snap +++ b/packages/jsii-pacmak/test/generated-code/__snapshots__/target-python.test.ts.snap @@ -243,7 +243,7 @@ scope.jsii-calc-base exports[`Generated code for "@scope/jsii-calc-base": /python/pyproject.toml 1`] = ` [build-system] -requires = ["setuptools~=58.5.3", "wheel~=0.37.0"] +requires = ["setuptools~=59.1.1", "wheel~=0.37.0"] build-backend = "setuptools.build_meta" `; @@ -723,7 +723,7 @@ scope.jsii-calc-base-of-base exports[`Generated code for "@scope/jsii-calc-base-of-base": /python/pyproject.toml 1`] = ` [build-system] -requires = ["setuptools~=58.5.3", "wheel~=0.37.0"] +requires = ["setuptools~=59.1.1", "wheel~=0.37.0"] build-backend = "setuptools.build_meta" `; @@ -1176,7 +1176,7 @@ scope.jsii-calc-lib exports[`Generated code for "@scope/jsii-calc-lib": /python/pyproject.toml 1`] = ` [build-system] -requires = ["setuptools~=58.5.3", "wheel~=0.37.0"] +requires = ["setuptools~=59.1.1", "wheel~=0.37.0"] build-backend = "setuptools.build_meta" `; @@ -2408,7 +2408,7 @@ foo = "bar" exports[`Generated code for "jsii-calc": /python/pyproject.toml 1`] = ` [build-system] -requires = ["setuptools~=58.5.3", "wheel~=0.37.0"] +requires = ["setuptools~=59.1.1", "wheel~=0.37.0"] build-backend = "setuptools.build_meta" `; diff --git a/packages/jsii-reflect/lib/assembly.ts b/packages/jsii-reflect/lib/assembly.ts index 08f3361258..f732a3a92f 100644 --- a/packages/jsii-reflect/lib/assembly.ts +++ b/packages/jsii-reflect/lib/assembly.ts @@ -169,6 +169,20 @@ export class Assembly extends ModuleLike { return Object.values(submodules); } + /** + * All types, even those in submodules and nested submodules. + */ + public get types(): readonly Type[] { + return Object.values(this.typeMap); + } + + /** + * Return all types in the current assembly/submodule and all submodules underneath + */ + public get allTypes(): readonly Type[] { + return [...this.types, ...this.allSubmodules.flatMap((s) => s.types)]; + } + public findType(fqn: string) { const type = this.tryFindType(fqn); if (!type) { diff --git a/packages/jsii-reflect/package.json b/packages/jsii-reflect/package.json index 1f465d95eb..577c9f8d51 100644 --- a/packages/jsii-reflect/package.json +++ b/packages/jsii-reflect/package.json @@ -44,10 +44,10 @@ "devDependencies": { "@scope/jsii-calc-lib": "^0.0.0", "@types/fs-extra": "^9.0.13", - "@types/jest": "^27.0.2", - "@types/node": "^12.20.28", - "eslint": "^7.32.0", - "jest": "^27.2.4", + "@types/jest": "^27.0.3", + "@types/node": "^12.20.37", + "eslint": "^8.3.0", + "jest": "^27.3.1", "jsii": "^0.0.0", "jsii-build-tools": "^0.0.0", "jsii-calc": "^3.20.120", diff --git a/packages/jsii-reflect/test/independent.test.ts b/packages/jsii-reflect/test/independent.test.ts index 14f5822f36..dd0f9d546e 100644 --- a/packages/jsii-reflect/test/independent.test.ts +++ b/packages/jsii-reflect/test/independent.test.ts @@ -1,10 +1,9 @@ -import { PackageInfo, sourceToAssemblyHelper } from 'jsii'; - import * as reflect from '../lib'; +import { assemblyFromSource } from './util'; test('get full github source location for a class or method', async () => { // WHEN - const assembly = await loadSource( + const assembly = await assemblyFromSource( ` export class Foo { public bar() { @@ -27,12 +26,3 @@ test('get full github source location for a class or method', async () => { 'https://github.com/aws/jsii/blob/master/some/sub/dir/index.ts#L1', ); }); - -async function loadSource( - source: string, - cb: (obj: PackageInfo) => void, -): Promise { - const ass = await sourceToAssemblyHelper(source, cb); - const ts = new reflect.TypeSystem(); - return ts.addAssembly(new reflect.Assembly(ts, ass)); -} diff --git a/packages/jsii-reflect/test/type-system.test.ts b/packages/jsii-reflect/test/type-system.test.ts index 57d361a3d2..ef799095ab 100644 --- a/packages/jsii-reflect/test/type-system.test.ts +++ b/packages/jsii-reflect/test/type-system.test.ts @@ -3,7 +3,7 @@ import { Stability } from '@jsii/spec'; import * as path from 'path'; import { TypeSystem } from '../lib'; -import { typeSystemFromSource } from './util'; +import { typeSystemFromSource, assemblyFromSource } from './util'; let typesys: TypeSystem; @@ -411,6 +411,15 @@ test('TypeSystem.methods', async () => { expect(ts.methods).toHaveLength(2); }); +test('Assembly allTypes includes submodule types', async () => { + const asm = await assemblyFromSource({ + 'index.ts': 'export * as submod from "./submod";', + 'submod.ts': `export class Foo {}`, + }); + + expect(asm.allTypes.map((t) => t.fqn)).toEqual(['testpkg.submod.Foo']); +}); + function resolveModuleDir(name: string) { return path.dirname(require.resolve(`${name}/package.json`)); } diff --git a/packages/jsii-reflect/test/util.ts b/packages/jsii-reflect/test/util.ts index 0ceda68ff4..575213a6a6 100644 --- a/packages/jsii-reflect/test/util.ts +++ b/packages/jsii-reflect/test/util.ts @@ -1,10 +1,20 @@ -import { sourceToAssemblyHelper } from 'jsii/lib/helpers'; +import { sourceToAssemblyHelper, MultipleSourceFiles, PackageInfo } from 'jsii'; import { Assembly, TypeSystem } from '../lib'; -export async function typeSystemFromSource(source: string) { - const assembly = await sourceToAssemblyHelper(source); +export async function typeSystemFromSource( + source: string | MultipleSourceFiles, + cb?: (obj: PackageInfo) => void, +) { + const asm = await assemblyFromSource(source, cb); + return asm.system; +} + +export async function assemblyFromSource( + source: string | MultipleSourceFiles, + cb?: (obj: PackageInfo) => void, +): Promise { + const ass = await sourceToAssemblyHelper(source, cb); const ts = new TypeSystem(); - ts.addAssembly(new Assembly(ts, assembly)); - return ts; + return ts.addAssembly(new Assembly(ts, ass)); } diff --git a/packages/jsii-rosetta/README.md b/packages/jsii-rosetta/README.md index d05d0973c2..7b1e32c7a8 100644 --- a/packages/jsii-rosetta/README.md +++ b/packages/jsii-rosetta/README.md @@ -5,36 +5,52 @@ Utility to transcribe example code snippets from TypeScript to other jsii langua Has knowledge about jsii language translation conventions to do the translations. Only supports a limited set of TypeScript language features. -## Compilability +## Rosetta for example authors + +This section describes what to pay attention to when writing examples that will be converted +by Rosetta. + +### Making examples compile The translator can translate both code that completely compiles and typechecks, as well as code that doesn't. In case of non-compiling samples the translations will be based off of grammatical parsing only. This has the downside -that we do not have the type information available to the exact right thing in all instances. +that we do not have the type information available to the exact thing in all instances. Specifically +struct types will not be able to be inferred from object literals. Have a look at the following piece of code: + +```ts +someObject.someMethod('foo', { + bar: 3, +}); +``` -If the samples don't compile or don't have full type information: +In non-TypeScript languages, it is important to know the type of the second +argument to the method here. However, without access to the definition of +`someMethod()`, it's impossible for Rosetta to know the type, and hence +it cannot translate the example. It is therefore important to include necessary +imports, variable declarations, etc, to give Rosetta enough information to figure +out what's going on in this code, and the example should read like this: -- No way to declare typed variables for Java and C#. -- Can only "see" the fields of structs as far as they are declared in the same snippet. Inherited fields or structs - declared not in the same snippet are invisible. -- When we explode a struct parameter into keyword parameters and we pass it on to another callable, we can't know which - keyword arguments the called function actually takes so we just pass all of them (might be too many). -- When structs contain nested structs, Python and other languages need to know the types of these fields to generate the - right calls. -- Object literals are used both to represent structs as well as to represent dictionaries, and without type information - it's impossible to determine which is which. +```ts +import * as myLib from 'some-library'; + +declare const someObject: myLib.SomeClass; -### Enforcing Compilability +someObject.someMethod('foo', { + bar: 3, +}); +``` -Package owners can enable enforcement of compiling code sample by setting the `jsii.rosetta.strict` assembly metadata -entry to `true`: +### Enforcing correct examples + +By default, Rosetta will accept non-compiling examples. If you set +`jsii.metadata.jsii.rosetta.strict` to `true` in your `package.json`, +the Rosetta command will fail if any example contains an error: ```js /// package.json { - // ... "jsii": { - // ... "metadata": { "jsii": { "rosetta": { @@ -42,175 +58,193 @@ entry to `true`: } } } - // ... - }, - // ... + } } ``` -This effectively enables the `--strict` option (which is equivalent to setting `--compile` and `--fail`, causing -`jsii-rosetta` to exit in error if any code sample fails to compile) when translating code samples from the assembly. +### Fixtures -## Hiding code from samples +To avoid having to repeat common setup every time, code samples can use +"fixtures": a source template where the example is inserted. A fixture must +contain the text `/// here` and typically looks like this: -In order to make examples compile, boilerplate code may need to be added that detracts from the example at hand (such as -variable declarations and imports). +```ts +const * as module from '@some/dependency'; -This package supports hiding parts of the original source after translation. +class MyClass { + constructor() { + const obj = new MyObject(); -To mark special locations in the source tree, we can use one of three mechanisms: + /// here + } +} +``` -- Use a `void` expression statement to mark statement locations in the AST. -- Use the `comma` operator combined with a `void` expression to mark expression locations in the AST. -- Use special directive comments (`/// !hide`, `/// !show`) to mark locations that span AST nodes. This is less reliable - (because the source location of translated syntax sometimes will have to be estimated) but the only option if you want - to mark non-contiguous nodes (such as hide part of a class declaration but show statements inside the constructor). +The example will be inserted at the location marked as `/// here` and will have +access to `module`, `obj` and `this`. Any `import` statements found in the +example will automatically be hoisted at the top of the fixture, where they are +guaranteed to be syntactically valid. -The `void` expression keyword and or the `comma` operator feature are little-used JavaScript features that are reliably -parsed by TypeScript and do not affect the semantics of the application in which they appear (so the program executes -the same with or without them). +The default file loaded as a fixture is called `rosetta/default.ts-fixture` in +the package directory (if it exists). -A handy mnemonic for this feature is that you can use it to "send your code into the void". +Examples can request an alternative fixture by specifying a `fixture` parameter +as part of the code block fence: -### Hiding statements +````text +```ts fixture=some-fixture +```` -Statement hiding looks like this: +Or opt out of using the default fixture by specifying `nofixture`: -```ts -before(); // will be shown +````text +```ts nofixture +```` -void 0; // start hiding (the argument to 'void' doesn't matter) -middle(); // will not be shown -void 'show'; // stop hiding +To specify fixtures in an `@example` block, use an accompanying `@exampleMetadata` tag: -after(); // will be shown again -``` +````text +/** + * My cool class + * + * @exampleMetadata fixture=with-setup + * @example + * + * new MyCoolClass(); + */ +```` -### Hiding expressions +## Rosetta for package publishers -For hiding expressions, we use `comma` expressions to attach a `void` statement to an expression value without changing -the meaning of the code. +This section describes how Rosetta integrates into your build process. -Example: - -```ts -foo(1, 2, (void 1, 3)); -``` +### Extract -Will render as +Rosetta has a number of subcommands. The most important one is `jsii-rosetta extract`. -``` -foo(1, 2) -``` +The `jsii-rosetta extract` command will take one or more jsii assemblies, +extract the snippets from them, will try to compile them with respect to a given +home directory, and finally store all translations in something called a +"tablet". -Also supports a visible ellipsis: +A couple of things to note here: -```ts -const x = (void '...', 3); -``` - -Renders to: - -``` -x = ... -``` +* Snippets are always read from the jsii assembly. That means if you make + changes to examples in source files, you must first re-run `jsii` to + regenerate the assembly, before re-running `jsii-rosetta extract`. +* The compilation directory will be used to resolve `import`s. Currently, you + are responsible for building a directory with the correct `node_modules` + directories in there so that a TypeScript compilation step will find all + libraries referenced in the examples. This is especially revelant if your + examples include libraries that depend on the *current* library: it is not + uncommon to write examples in library `A` showing how to use it in combination + with library `B`, where `B` depends on `A`. However, since by definition `B` + *cannot* be in the set of dependencies of `A`, you must build a directory with + both `B` and `A` in it somewhere in your filesystem and run Rosetta in that + directory. +* "Extract" will compile samples in parallel. The more assemblies you give it + at the same time, the more efficient of a job it will be able to do. -### Hiding across AST nodes +The extract command will write a file named `.jsii.tabl.json` next to every +assembly, containing translations for all samples found in the assembly. You +should include this file in your NPM package when you publish, so that +downstream consumers of the package have access to the translations. -Use special comment directives: +An example invocation of `jsii-rosetta extract` looks like this: -```ts -before(); -/// !hide -notShown(); -/// !show -after(); +```sh +jsii-rosetta extract --directory some/dir $(find . -name .jsii) ``` -(May also start with `/// !show` and `/// !hide`). +#### Running in parallel -## Fixtures +Since TypeScript compilation takes a lot of time, much time can be gained by +using the CPUs in your system effectively. `jsii-rosetta extract` will run the +compilations in parallel. -To avoid having to repeat common setup every time, code samples can use "fixtures": a source template where the example -is inserted. A fixture must contain the text `/// here` and may look like this: +`jsii-rosetta` will use a number of workers equal to half the number of CPU +cores, up to a maximum of 16 workers. This default maximum can be overridden by +setting the `JSII_ROSETTA_MAX_WORKER_COUNT` environment variable. -```ts -const module = require('@some/dependency'); +If you get out of memory errors running too many workers, run a command like +this to raise the memory allowed for your workers: -class MyClass { - constructor() { - const obj = new MyObject(); - - /// here - } -} +```sh +/sbin/sysctl -w vm.max_map_count=2251954 ``` -The example will be inserted at the location marked as `/// here` and will have access to `module`, `obj` and `this`. -Any `import` statements found in the example will automatically be hoisted at the top of the fixture, where they are -guaranteed to be syntactically valid. +#### Caching -The default file loaded as a fixture is called `rosetta/default.ts-fixture` in the package directory (if it exists). +Rosetta extract will translate all examples found in `.jsii` and write the +translations to `.jsii.tabl.json`. From compilation to compilation, many of these +examples won't have changed. Since TypeScript compilation is a fairly expensive +process, we would like to avoid doing unnecessary work as much as possible. -Examples can request an alternative fixture by specifying a `fixture` parameter as part of the code block fence: +To that end, rosetta can reuse translations from a cache, and write +new translations into the same cache: - ` ` `ts fixture=some-fixture - ... +```sh +jsii-rosetta extract \ + --directory some/dir \ + --cache cache.json \ + [--trim-cache] \ + $(find . -name .jsii) +``` -Or opt out of using the default fixture by specifying `nofixture`. +The `--trim-cache` flag will remove any old translations from the cache that +don't exist anymore in any of the given assemblies. This prevents the cache from +growing endlessly over time (an equivalent `jsii-rosetta trim-cache` command is +available if your workflow involves running `extract` in multiple distinct +invocations and want to retain the cache between them). -## Build integration +### Infuse -Because we want to control the compilation environment when compiling examples, extracting and compiling all samples can -be run as an external build step in a monorepo. This allows you to set up an environment with all desired packages and -compile all samples in one go. +The `jsii-rosetta infuse` command increases the coverage of examples for classes +in the assembly. -The `jsii-rosetta extract` command will take one or more jsii assemblies, extract the snippets from them, will try to -compile them with respect to a given home directory, and finally store all translations in something called a "tablet" -(which is a lookup map from the original snippet to all of its translations). +It finds classes in the assembly that don't have an example associated with them +yet (as specified via the `@example` tag in the doc comments), but that are used +in another example found elsewhere—in either a `README` or an example of another +class—it will copy the example to all classes involved. This will make sure +your handwritten examples go as far as possible. -A translation tablet will automatically be used by `jsii-pacmak` if present, so it can subsitute the translated examples -into the converted source code when writing out the converted code. When not given a tablet, `jsii-pacmak` can still -live-convert samples, but you will not have the fine control over the compilation environment that you would have if you -were to use the `extract` command. +Note that in order to do this, `infuse` will *modify* the assemblies it is +given. -Works like this: +`rosetta infuse` depends on the analysis perfomed by `rosetta extract`, and must +therefore be run after `extract`. It can also be run as part of `extract`, by +passing the `--infuse` flag: -``` -$ jsii-rosetta extract --compile $(find . -name .jsii) --directory some/dir -$ jsii-pacmak --samples-tablet .jsii-samples.tbl +```sh +jsii-rosetta extract \ + --directory some/dir \ + --infuse \ + $(find . -name .jsii) ``` -(The `extract` command is the default and may be omitted, but if you're passing assembliess as arguments you should -terminate the option list by passing `--`). +### Translations and pacmak -### Running in parallel +`jsii-pacmak` will read translation from tablets to substitute translated examples +into the generated source bindings. `pacmak` will automatically read individual +`.jsii.tabl.json` files if present, and can additionally also read from a global +tablet file. -Since TypeScript compilation takes a lot of time, much time can be gained by using the CPUs in your system effectively. -`jsii-rosetta extract` will run the compilations in parallel if support for NodeJS Worker Threads is detected. +When a translation for a code sample cannot be found, `pacmak` can be configured +to do one of the following: -`jsii-rosetta` will use a number of workers equal to half the number of CPU cores, -up to a maximum of 16 workers. This default maximum can be overridden by setting the `JSII_ROSETTA_MAX_WORKER_COUNT` -environment variable. +* Leave the sample untranslated (default) +* Translate the sample in-place (this will slow down generation a lot, and you + will not have the fine control over the compilation environment that you would + have if you were to use the `extract` command) +* Fail -If you get out of memory errors running too many workers, run a command like this to up the memory allowed for your workers: +Example: +```sh +jsii-pacmak \ + [--rosetta-tablet=global.json] \ + [--rosetta-unknown-snippets=verbatim|translate|fail] ``` -$ /sbin/sysctl -w vm.max_map_count=2251954 -``` - -## Infuse - -The `rosetta infuse` feature will copy code samples (for example, as found in -the README), to the classes that are referenced in the code sample. This will make -sure the same examples are rendered in multiple places, and increases their visibility -without additional work on the author's part. - -The infuse command will modify the relevant assemblies and tablet files: - -- Assembly: it will add new `example` sections to types in the assembly. -- Tablet: every newly added `example` will require a new copy of the example (with - a unique identifier) added to the tablet. ### Data flow @@ -253,3 +287,82 @@ The diagram below shows how data flows through the jsii tools when used together live-translates) ``` +## Advanced topics + +### Hiding code from samples + +In order to make examples compile, boilerplate code may need to be added that detracts from the example at hand (such as +variable declarations and imports). + +This package supports hiding parts of the original source after translation. + +To mark special locations in the source tree, we can use one of three mechanisms: + +* Use a `void` expression statement to mark statement locations in the AST. +* Use the `comma` operator combined with a `void` expression to mark expression locations in the AST. +* Use special directive comments (`/// !hide`, `/// !show`) to mark locations that span AST nodes. This is less reliable + (because the source location of translated syntax sometimes will have to be estimated) but the only option if you want + to mark non-contiguous nodes (such as hide part of a class declaration but show statements inside the constructor). + +The `void` expression keyword and or the `comma` operator feature are little-used JavaScript features that are reliably +parsed by TypeScript and do not affect the semantics of the application in which they appear (so the program executes +the same with or without them). + +A handy mnemonic for this feature is that you can use it to "send your code into the void". + +#### Hiding statements + +Statement hiding looks like this: + +```ts +before(); // will be shown + +void 0; // start hiding (the argument to 'void' doesn't matter) +middle(); // will not be shown +void 'show'; // stop hiding + +after(); // will be shown again +``` + +#### Hiding expressions + +For hiding expressions, we use `comma` expressions to attach a `void` statement to an expression value without changing +the meaning of the code. + +Example: + +```ts +foo(1, 2, (void 1, 3)); +``` + +Will render as + +```ts +foo(1, 2) +``` + +Also supports a visible ellipsis: + +```ts +const x = (void '...', 3); +``` + +Renders to: + +```ts +x = ... +``` + +#### Hiding across AST nodes + +Use special comment directives: + +```ts +before(); +/// !hide +notShown(); +/// !show +after(); +``` + +(May also start with `/// !show` and `/// !hide`). diff --git a/packages/jsii-rosetta/bin/jsii-rosetta.ts b/packages/jsii-rosetta/bin/jsii-rosetta.ts index 83ffa41a78..8fbae7a2a2 100644 --- a/packages/jsii-rosetta/bin/jsii-rosetta.ts +++ b/packages/jsii-rosetta/bin/jsii-rosetta.ts @@ -4,12 +4,13 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import * as yargs from 'yargs'; -import { TranslateResult, DEFAULT_TABLET_NAME, translateTypeScript, RosettaDiagnostic } from '../lib'; +import { TranslateResult, translateTypeScript, RosettaDiagnostic } from '../lib'; import { translateMarkdown } from '../lib/commands/convert'; -import { extractSnippets } from '../lib/commands/extract'; +import { extractAndInfuse, extractSnippets, ExtractOptions } from '../lib/commands/extract'; import { infuse, DEFAULT_INFUSION_RESULTS_NAME } from '../lib/commands/infuse'; import { readTablet } from '../lib/commands/read'; import { transliterateAssembly } from '../lib/commands/transliterate'; +import { trimCache } from '../lib/commands/trim-cache'; import { TargetLanguage } from '../lib/languages'; import { PythonVisitor } from '../lib/languages/python'; import { VisualizeAstVisitor } from '../lib/languages/visualize'; @@ -69,37 +70,31 @@ function main() { '(EXPERIMENTAL) mutates one or more assemblies by adding documentation examples to top-level types', (command) => command - .positional('TABLET', { - type: 'string', - required: true, - describe: 'Language tablet to read', - }) .positional('ASSEMBLY', { type: 'string', string: true, default: new Array(), describe: 'Assembly or directory to mutate', }) - .option('log', { + .option('log-file', { alias: 'l', - type: 'boolean', - describe: 'Test all algorithms and log results to an html file', - default: false, - }) - .option('output', { - alias: 'o', type: 'string', describe: 'Output file to store logging results. Ignored if -log is not true', default: DEFAULT_INFUSION_RESULTS_NAME, }) - .demandOption('TABLET'), + .option('cache-to', { + alias: 'o', + type: 'string', + describe: 'Append all translated snippets to the given tablet file', + requiresArg: true, + default: undefined, + }), wrapHandler(async (args) => { const absAssemblies = (args.ASSEMBLY.length > 0 ? args.ASSEMBLY : ['.']).map((x) => path.resolve(x)); - const absOutput = path.resolve(args.output); - const result = await infuse(absAssemblies, args.TABLET, { - outputFile: absOutput, - log: args.log, - tabletOutputFile: args.TABLET, + const cacheToFile = fmap(args['cache-to'], path.resolve); + const result = await infuse(absAssemblies, { + logFile: args['log-file'], + cacheToFile: cacheToFile, }); let totalTypes = 0; @@ -131,16 +126,16 @@ function main() { describe: 'Assembly or directory to extract from', }) .option('output', { - alias: 'o', type: 'string', - describe: 'Output file where to store the sample tablets', - default: DEFAULT_TABLET_NAME, + describe: 'Additional output file where to store translated samples (deprecated, alias for --cache-to)', + requiresArg: true, + default: undefined, }) .option('compile', { alias: 'c', type: 'boolean', - describe: 'Try compiling', - default: false, + describe: 'Try compiling (on by default, use --no-compile to switch off)', + default: true, }) .option('directory', { alias: 'd', @@ -153,6 +148,11 @@ function main() { describe: 'Extract only snippets with given ids', default: new Array(), }) + .option('infuse', { + type: 'boolean', + describe: 'bundle this command with the infuse command', + default: false, + }) .option('fail', { alias: 'f', type: 'boolean', @@ -168,10 +168,33 @@ function main() { alias: 'C', type: 'string', // eslint-disable-next-line prettier/prettier - describe: 'Reuse translations from the given tablet file if the snippet and type definitions did not change', + describe: + 'Reuse translations from the given tablet file if the snippet and type definitions did not change', + requiresArg: true, + default: undefined, + }) + .option('cache-to', { + alias: 'o', + type: 'string', + describe: 'Append all translated snippets to the given tablet file', requiresArg: true, default: undefined, }) + .conflicts('cache-to', 'output') + .option('cache', { + alias: 'k', + type: 'string', + describe: 'Alias for --cache-from and --cache-to together', + requiresArg: true, + default: undefined, + }) + .conflicts('cache', 'cache-from') + .conflicts('cache', 'cache-to') + .option('trim-cache', { + alias: 'T', + type: 'boolean', + describe: 'Remove translations that are not referenced by any of the assemblies anymore from the cache', + }) .option('strict', { alias: 'S', type: 'boolean', @@ -192,19 +215,26 @@ function main() { // compilerhost. Have to make all file references absolute before we chdir // though. const absAssemblies = (args.ASSEMBLY.length > 0 ? args.ASSEMBLY : ['.']).map((x) => path.resolve(x)); - const absOutput = path.resolve(args.output); - const absCache = fmap(args['cache-from'], path.resolve); + + const absCacheFrom = fmap(args.cache ?? args['cache-from'], path.resolve); + const absCacheTo = fmap(args.cache ?? args['cache-to'] ?? args.output, path.resolve); + if (args.directory) { process.chdir(args.directory); } - const result = await extractSnippets(absAssemblies, { - outputFile: absOutput, + const extractOptions: ExtractOptions = { includeCompilerDiagnostics: !!args.compile, validateAssemblies: args['validate-assemblies'], only: args.include, - cacheTabletFile: absCache, - }); + cacheFromFile: absCacheFrom, + cacheToFile: absCacheTo, + trimCache: args['trim-cache'], + }; + + const result = args.infuse + ? await extractAndInfuse(absAssemblies, extractOptions) + : await extractSnippets(absAssemblies, extractOptions); handleDiagnostics(result.diagnostics, args.fail, result.tablet.count); }), @@ -266,6 +296,30 @@ function main() { return transliterateAssembly(assemblies, languages, args); }), ) + .command( + 'trim-cache [ASSEMBLY..]', + 'Retain only those snippets in the cache which occur in one of the given assemblies', + (command) => + command + .positional('TABLET', { + type: 'string', + required: true, + describe: 'Language tablet to trim', + }) + .positional('ASSEMBLY', { + type: 'string', + string: true, + default: new Array(), + describe: 'Assembly or directory to search', + }) + .demandOption('TABLET'), + wrapHandler(async (args) => { + await trimCache({ + cacheFile: args.TABLET, + assemblyLocations: args.ASSEMBLY, + }); + }), + ) .command( 'read [KEY] [LANGUAGE]', 'Display snippets in a language tablet file', diff --git a/packages/jsii-rosetta/lib/commands/extract.ts b/packages/jsii-rosetta/lib/commands/extract.ts index e2a4f90b1a..b43ac224b8 100644 --- a/packages/jsii-rosetta/lib/commands/extract.ts +++ b/packages/jsii-rosetta/lib/commands/extract.ts @@ -1,10 +1,14 @@ -import { loadAssemblies, allTypeScriptSnippets } from '../jsii/assemblies'; +import * as path from 'path'; + +import { loadAssemblies, allTypeScriptSnippets, loadAllDefaultTablets } from '../jsii/assemblies'; import * as logging from '../logging'; import { RosettaTranslator, RosettaTranslatorOptions } from '../rosetta-translator'; -import { TypeScriptSnippet } from '../snippet'; +import { TypeScriptSnippet, SnippetParameters } from '../snippet'; import { snippetKey } from '../tablets/key'; -import { LanguageTablet } from '../tablets/tablets'; +import { LanguageTablet, DEFAULT_TABLET_NAME } from '../tablets/tablets'; import { RosettaDiagnostic } from '../translate'; +import { groupBy, isDefined } from '../util'; +import { infuse } from './infuse'; export interface ExtractResult { diagnostics: RosettaDiagnostic[]; @@ -12,15 +16,26 @@ export interface ExtractResult { } export interface ExtractOptions { - readonly outputFile: string; - readonly includeCompilerDiagnostics: boolean; - readonly validateAssemblies: boolean; + readonly includeCompilerDiagnostics?: boolean; + readonly validateAssemblies?: boolean; readonly only?: string[]; /** * A tablet file to be loaded and used as a source for caching */ - readonly cacheTabletFile?: string; + readonly cacheFromFile?: string; + + /** + * A tablet file to append translated snippets to + */ + readonly cacheToFile?: string; + + /** + * Trim cache to only contain translations found in the current assemblies + * + * @default false + */ + readonly trimCache?: boolean; /** * Make a translator (just for testing) @@ -28,26 +43,46 @@ export interface ExtractOptions { readonly translatorFactory?: (opts: RosettaTranslatorOptions) => RosettaTranslator; } +export async function extractAndInfuse( + assemblyLocations: string[], + options: ExtractOptions, + loose = false, +): Promise { + const result = await extractSnippets(assemblyLocations, options, loose); + await infuse(assemblyLocations, { + cacheFromFile: options.cacheFromFile, + cacheToFile: options.cacheToFile, + }); + return result; +} + /** * Extract all samples from the given assemblies into a tablet */ export async function extractSnippets( assemblyLocations: string[], - options: ExtractOptions, + options: ExtractOptions = {}, loose = false, ): Promise { const only = options.only ?? []; logging.info(`Loading ${assemblyLocations.length} assemblies`); - const assemblies = await loadAssemblies(assemblyLocations, options.validateAssemblies); + const assemblies = await loadAssemblies(assemblyLocations, options.validateAssemblies ?? false); let snippets = Array.from(allTypeScriptSnippets(assemblies, loose)); if (only.length > 0) { snippets = filterSnippets(snippets, only); } + // Map every assembly to a list of snippets, so that we know what implicit + // tablet to write the translations to later on. + const snippetsPerAssembly = groupBy( + snippets.map((s) => ({ key: snippetKey(s), location: projectDirectory(s) })), + (x) => x.location, + ); + const translatorOptions: RosettaTranslatorOptions = { - includeCompilerDiagnostics: options.includeCompilerDiagnostics, + includeCompilerDiagnostics: options.includeCompilerDiagnostics ?? false, assemblies: assemblies.map((a) => a.assembly), }; @@ -55,10 +90,17 @@ export async function extractSnippets( ? options.translatorFactory(translatorOptions) : new RosettaTranslator(translatorOptions); - if (options.cacheTabletFile) { - await translator.loadCache(options.cacheTabletFile); + // Prime the snippet cache with: + // - Cache source file + // - Default tablets found next to each assembly + if (options.cacheFromFile) { + await translator.addToCache(options.cacheFromFile); + } + translator.addTabletsToCache(...Object.values(await loadAllDefaultTablets(assemblies))); + + if (translator.hasCache()) { const { translations, remaining } = translator.readFromCache(snippets); - logging.info(`Reused ${translations.length} translations from cache ${options.cacheTabletFile}`); + logging.info(`Reused ${translations.length} translations from cache`); snippets = remaining; } @@ -80,8 +122,27 @@ export async function extractSnippets( logging.info('Nothing left to translate'); } - logging.info(`Saving language tablet to ${options.outputFile}`); - await translator.tablet.save(options.outputFile); + // Save to individual tablet files, and optionally append to the output file + await Promise.all( + Object.entries(snippetsPerAssembly).map(async ([location, snips]) => { + const asmTabletFile = path.join(location, DEFAULT_TABLET_NAME); + logging.debug(`Writing ${snips.length} translations to ${asmTabletFile}`); + const translations = snips.map(({ key }) => translator.tablet.tryGetSnippet(key)).filter(isDefined); + + const asmTablet = new LanguageTablet(); + asmTablet.addSnippets(...translations); + await asmTablet.save(asmTabletFile); + }), + ); + + if (options.cacheToFile) { + logging.info(`Adding translations to ${options.cacheToFile}`); + const output = options.trimCache + ? new LanguageTablet() + : await LanguageTablet.fromOptionalFile(options.cacheToFile); + output.addTablet(translator.tablet); + await output.save(options.cacheToFile); + } return { diagnostics, tablet: translator.tablet }; } @@ -92,3 +153,11 @@ export async function extractSnippets( function filterSnippets(ts: TypeScriptSnippet[], includeIds: string[]) { return ts.filter((t) => includeIds.includes(snippetKey(t))); } + +function projectDirectory(ts: TypeScriptSnippet) { + const dir = ts.parameters?.[SnippetParameters.$PROJECT_DIRECTORY]; + if (!dir) { + throw new Error(`Snippet does not have associated project directory: ${JSON.stringify(ts.location)}`); + } + return dir; +} diff --git a/packages/jsii-rosetta/lib/commands/infuse.ts b/packages/jsii-rosetta/lib/commands/infuse.ts index 7962c79fc1..6d20cf5e95 100644 --- a/packages/jsii-rosetta/lib/commands/infuse.ts +++ b/packages/jsii-rosetta/lib/commands/infuse.ts @@ -1,9 +1,19 @@ import * as spec from '@jsii/spec'; import * as fs from 'fs-extra'; +import * as path from 'path'; -import { loadAssemblies, replaceAssembly } from '../jsii/assemblies'; +import { + loadAssemblies, + replaceAssembly, + loadAllDefaultTablets, + LoadedAssembly, + allTypeScriptSnippets, +} from '../jsii/assemblies'; +import { renderMetadataline, TypeScriptSnippet } from '../snippet'; import { SnippetSelector, mean, meanLength, shortest, longest } from '../snippet-selectors'; -import { LanguageTablet, TranslatedSnippet } from '../tablets/tablets'; +import { snippetKey } from '../tablets/key'; +import { LanguageTablet, TranslatedSnippet, DEFAULT_TABLET_NAME } from '../tablets/tablets'; +import { isDefined, mkDict, fmap, indexBy } from '../util'; export interface InfuseResult { readonly coverageResults: Record; @@ -15,14 +25,17 @@ export interface InfuseTypes { } export interface InfuseOptions { - readonly outputFile?: string; + readonly logFile?: string; - readonly log?: boolean; + /** + * Where to read additional translations + */ + readonly cacheFromFile?: string; /** - * Where to write the updated tablet back + * In addition to the implicit tablets, also write all added examples to this additional output tablet */ - readonly tabletOutputFile?: string; + readonly cacheToFile?: string; } export const DEFAULT_INFUSION_RESULTS_NAME = 'infusion-results.html'; @@ -40,68 +53,84 @@ class DefaultRecord { } } -export async function infuse( - assemblyLocations: string[], - tabletFile: string, - options?: InfuseOptions, -): Promise { +/** + * Infuse will analyze the snippets in a set of tablets, and update the assembly to add + * examples to types that don't have any yet, based on snippets that use the given type. + */ +export async function infuse(assemblyLocations: string[], options?: InfuseOptions): Promise { let stream: fs.WriteStream | undefined = undefined; - if (options?.log) { - if (!options.outputFile) { - throw new Error("If 'log' is set, 'outputFile' must be set as well."); - } - + if (options?.logFile) { // Create stream for html file and insert some styling - stream = fs.createWriteStream(options.outputFile, { - encoding: 'utf-8', - }); + stream = fs.createWriteStream(options.logFile, { encoding: 'utf-8' }); startFile(stream); } // Load tablet file and assemblies - const tab = new LanguageTablet(); - await tab.load(tabletFile); - const assemblies = await loadAssemblies(assemblyLocations, true); - - const snippetsFromFqn = mapFqns(tab); - const coverageResults: Record = {}; - for (const { assembly, directory } of assemblies) { - stream?.write(`

@aws-cdk/${directory.split('/').pop()}

\n`); - - let typesWithInsertedExamples = 0; - const filteredTypes = filterForTypesWithoutExamples(assembly.types ?? {}); - for (const [typeFqn, type] of Object.entries(filteredTypes)) { - if (snippetsFromFqn[typeFqn] !== undefined) { - const meanResult = mean(snippetsFromFqn[typeFqn]); - if (options?.log) { - const selected = Object.entries(ADDITIONAL_SELECTORS).map( - ([name, fn]) => [name, fn(snippetsFromFqn[typeFqn])] as const, - ); - const selectedFromSelector = { - ...makeDict(selected), - mean: meanResult, - }; - logOutput(stream, typeFqn, createHtmlEntry(selectedFromSelector)); - } - insertExample(meanResult, type, tab); - typesWithInsertedExamples++; - } - } + const assemblies = await loadAssemblies(assemblyLocations, false); + const defaultTablets = await loadAllDefaultTablets(assemblies); - // eslint-disable-next-line no-await-in-loop - await replaceAssembly(assembly, directory); - coverageResults[directory] = { - types: Object.keys(filteredTypes).length, - typesWithInsertedExamples, - }; + const availableTranslations = new LanguageTablet(); + if (options?.cacheFromFile) { + availableTranslations.addTablet(await LanguageTablet.fromOptionalFile(options.cacheFromFile)); } + availableTranslations.addTablets(...Object.values(defaultTablets)); + + const { translationsByFqn, originalsByKey } = availableSnippetsPerFqn(assemblies, availableTranslations); + + const additionalOutputTablet = options?.cacheToFile + ? await LanguageTablet.fromOptionalFile(options?.cacheToFile) + : new LanguageTablet(); + + const coverageResults = mkDict( + await Promise.all( + assemblies.map(async ({ assembly, directory }) => { + stream?.write(`

${assembly.name}

\n`); + + const implicitTablet = defaultTablets[directory]; + if (!implicitTablet) { + throw new Error(`No tablet found for ${directory}`); + } + + let insertedExamples = 0; + const filteredTypes = filterForTypesWithoutExamples(assembly.types ?? {}); + for (const [typeFqn, type] of Object.entries(filteredTypes)) { + const available = translationsByFqn[typeFqn]; + if (!available) { + continue; + } + + const example = pickBestExample(typeFqn, available, stream); + const original = originalsByKey[example.key]; + insertExample(example, original, type, [implicitTablet, additionalOutputTablet]); + insertedExamples++; + } + + if (insertedExamples > 0) { + // Save the updated assembly and implicit tablets + // eslint-disable-next-line no-await-in-loop + await Promise.all([ + replaceAssembly(assembly, directory), + implicitTablet.save(path.join(directory, DEFAULT_TABLET_NAME)), + ]); + } + + return [ + directory, + { + types: Object.keys(filteredTypes).length, + typesWithInsertedExamples: insertedExamples, + } as InfuseTypes, + ] as const; + }), + ), + ); stream?.close(); // If we copied examples onto different types, we'll also have inserted new snippets // with different keys into the tablet. We must now write the updated tablet somewhere. - if (options?.tabletOutputFile) { - await tab.save(options.tabletOutputFile); + if (options?.cacheToFile) { + await additionalOutputTablet.save(options.cacheToFile); } return { @@ -109,6 +138,19 @@ export async function infuse( }; } +function pickBestExample(typeFqn: string, choices: TranslatedSnippet[], logStream?: fs.WriteStream) { + const meanResult = mean(choices); + if (logStream) { + const selected = Object.entries(ADDITIONAL_SELECTORS).map(([name, fn]) => [name, fn(choices)] as const); + const selectedFromSelector = { + ...makeDict(selected), + mean: meanResult, + }; + logOutput(logStream, typeFqn, createHtmlEntry(selectedFromSelector)); + } + return meanResult; +} + function startFile(stream: fs.WriteStream) { stream.write('