Repository: exercism/typescript Branch: main Commit: 3c3b7d4973aa Files: 1803 Total size: 4.0 MB Directory structure: gitextract_nny146az/ ├── .appends/ │ └── .github/ │ └── labels.yml ├── .dependabot/ │ └── config.yml ├── .gitattributes ├── .github/ │ ├── CODEOWNERS │ ├── dependabot.yml │ ├── labels.yml │ └── workflows/ │ ├── action-format.yml │ ├── action-sync.yml │ ├── ci.js.yml │ ├── codeql.yml │ ├── configlet.yml │ ├── no-important-files-changed.yml │ ├── pause-community-contributions.yml │ ├── ping-cross-track-maintainers-team.yml │ ├── pr.ci.js.yml │ ├── run-configlet-sync.yml │ ├── sync-labels.yml │ └── verify-code-formatting.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode/ │ ├── extensions.json │ └── settings.json ├── .yarn/ │ └── sdks/ │ ├── eslint/ │ │ ├── bin/ │ │ │ └── eslint.js │ │ ├── lib/ │ │ │ ├── api.js │ │ │ └── unsupported-api.js │ │ └── package.json │ ├── integrations.yml │ ├── prettier/ │ │ ├── bin/ │ │ │ └── prettier.cjs │ │ ├── index.cjs │ │ └── package.json │ └── typescript/ │ ├── bin/ │ │ ├── tsc │ │ └── tsserver │ ├── lib/ │ │ ├── tsc.js │ │ ├── tsserver.js │ │ ├── tsserverlibrary.js │ │ └── typescript.js │ └── package.json ├── .yarnrc.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── TAGS.md ├── babel.config.cjs ├── bin/ │ ├── check-formatting.sh │ ├── fetch-configlet │ ├── fetch-configlet.ps1 │ ├── format.sh │ ├── generate-config-tree │ ├── md5-hash │ └── print-config-tree ├── common/ │ ├── .vscode/ │ │ ├── extensions.json │ │ └── settings.json │ ├── .yarnrc.yml │ ├── babel.config.cjs │ ├── eslint.config.mjs │ ├── jest.config.cjs │ ├── keep.ts │ ├── package.json │ ├── test-runner.mjs │ └── tsconfig.json ├── concepts/ │ ├── .keep │ └── basics/ │ ├── .meta/ │ │ └── config.json │ ├── about.md │ ├── introduction.md │ └── links.json ├── config.json ├── docs/ │ ├── ABOUT.md │ ├── INSTALLATION.md │ ├── LEARNING.md │ ├── RESOURCES.md │ ├── SNIPPET.txt │ ├── TESTS.md │ └── config.json ├── eslint.config.mjs ├── exercises/ │ ├── concept/ │ │ ├── .keep │ │ └── lasagna/ │ │ ├── .docs/ │ │ │ ├── hints.md │ │ │ ├── instructions.md │ │ │ └── introduction.md │ │ ├── .meta/ │ │ │ ├── config.json │ │ │ ├── design.md │ │ │ └── exemplar.ts │ │ ├── .vscode/ │ │ │ ├── extensions.json │ │ │ └── settings.json │ │ ├── .yarnrc.yml │ │ ├── __typetests__/ │ │ │ └── lasagna.tst.ts │ │ ├── babel.config.cjs │ │ ├── eslint.config.mjs │ │ ├── jest.config.cjs │ │ ├── lasagna.test.ts │ │ ├── lasagna.ts │ │ ├── package.json │ │ ├── test-runner.mjs │ │ └── tsconfig.json │ ├── practice/ │ │ ├── accumulate/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ └── proof.ci.ts │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── accumulate.test.ts │ │ │ ├── accumulate.ts │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── acronym/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── acronym.test.ts │ │ │ ├── acronym.ts │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── all-your-base/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── all-your-base.test.ts │ │ │ ├── all-your-base.ts │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── allergies/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── allergies.test.ts │ │ │ ├── allergies.ts │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── alphametics/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── alphametics.test.ts │ │ │ ├── alphametics.ts │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── anagram/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.append.md │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── anagram.test.ts │ │ │ ├── anagram.ts │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── armstrong-numbers/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── armstrong-numbers.test.ts │ │ │ ├── armstrong-numbers.ts │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── atbash-cipher/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── atbash-cipher.test.ts │ │ │ ├── atbash-cipher.ts │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── bank-account/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ ├── solutions/ │ │ │ │ │ └── bank-account.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── bank-account.test.ts │ │ │ ├── bank-account.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── beer-song/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── beer-song.test.ts │ │ │ ├── beer-song.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── binary-search/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── binary-search.test.ts │ │ │ ├── binary-search.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── binary-search-tree/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── binary-search-tree.test.ts │ │ │ ├── binary-search-tree.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── bob/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── description.md │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── bob.test.ts │ │ │ ├── bob.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── bottle-song/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── bottle-song.test.ts │ │ │ ├── bottle-song.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── bowling/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── bowling.test.ts │ │ │ ├── bowling.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── camicia/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── camicia.test.ts │ │ │ ├── camicia.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── circular-buffer/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── circular-buffer.test.ts │ │ │ ├── circular-buffer.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── clock/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── clock.test.ts │ │ │ ├── clock.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── collatz-conjecture/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── collatz-conjecture.test.ts │ │ │ ├── collatz-conjecture.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── complex-numbers/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── complex-numbers.test.ts │ │ │ ├── complex-numbers.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── connect/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── connect.test.ts │ │ │ ├── connect.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── crypto-square/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── crypto-square.test.ts │ │ │ ├── crypto-square.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── custom-set/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── custom-set.test.ts │ │ │ ├── custom-set.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── darts/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── darts.test.ts │ │ │ ├── darts.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── diamond/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── diamond.test.ts │ │ │ ├── diamond.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── difference-of-squares/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── difference-of-squares.test.ts │ │ │ ├── difference-of-squares.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── diffie-hellman/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── diffie-hellman.test.ts │ │ │ ├── diffie-hellman.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── dnd-character/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── dnd-character.test.ts │ │ │ ├── dnd-character.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── eliuds-eggs/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eliuds-eggs.test.ts │ │ │ ├── eliuds-eggs.ts │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── etl/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── etl.test.ts │ │ │ ├── etl.ts │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── flatten-array/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── flatten-array.test.ts │ │ │ ├── flatten-array.ts │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── flower-field/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── flower-field.test.ts │ │ │ ├── flower-field.ts │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── food-chain/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── food-chain.test.ts │ │ │ ├── food-chain.ts │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── game-of-life/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── game-of-life.test.ts │ │ │ ├── game-of-life.ts │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── gigasecond/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── gigasecond.test.ts │ │ │ ├── gigasecond.ts │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── grade-school/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── grade-school.test.ts │ │ │ ├── grade-school.ts │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── grains/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── grains.test.ts │ │ │ ├── grains.ts │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── hamming/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── hamming.test.ts │ │ │ ├── hamming.ts │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── hello-world/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.append.md │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── hello-world.test.ts │ │ │ ├── hello-world.ts │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── house/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── house.test.ts │ │ │ ├── house.ts │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── isbn-verifier/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── isbn-verifier.test.ts │ │ │ ├── isbn-verifier.ts │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── isogram/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── isogram.test.ts │ │ │ ├── isogram.ts │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── kindergarten-garden/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── kindergarten-garden.test.ts │ │ │ ├── kindergarten-garden.ts │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── knapsack/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── knapsack.test.ts │ │ │ ├── knapsack.ts │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── largest-series-product/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── largest-series-product.test.ts │ │ │ ├── largest-series-product.ts │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── leap/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── leap.test.ts │ │ │ ├── leap.ts │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── line-up/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── line-up.test.ts │ │ │ ├── line-up.ts │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── linked-list/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ └── proof.ci.ts │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── linked-list.test.ts │ │ │ ├── linked-list.ts │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── list-ops/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.append.md │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── list-ops.test.ts │ │ │ ├── list-ops.ts │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── luhn/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── luhn.test.ts │ │ │ ├── luhn.ts │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── matching-brackets/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── matching-brackets.test.ts │ │ │ ├── matching-brackets.ts │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── matrix/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── matrix.test.ts │ │ │ ├── matrix.ts │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── minesweeper/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── minesweeper.test.ts │ │ │ ├── minesweeper.ts │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── nth-prime/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── nth-prime.test.ts │ │ │ ├── nth-prime.ts │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── nucleotide-count/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── nucleotide-count.test.ts │ │ │ ├── nucleotide-count.ts │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── ocr-numbers/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── ocr-numbers.test.ts │ │ │ ├── ocr-numbers.ts │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── palindrome-products/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── palindrome-products.test.ts │ │ │ ├── palindrome-products.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── pangram/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── pangram.test.ts │ │ │ ├── pangram.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── pascals-triangle/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── pascals-triangle.test.ts │ │ │ ├── pascals-triangle.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── perfect-numbers/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── perfect-numbers.test.ts │ │ │ ├── perfect-numbers.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── phone-number/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── phone-number.test.ts │ │ │ ├── phone-number.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── pig-latin/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── pig-latin.test.ts │ │ │ ├── pig-latin.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── prime-factors/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── prime-factors.test.ts │ │ │ ├── prime-factors.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── prism/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── prism.test.ts │ │ │ ├── prism.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── protein-translation/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── protein-translation.test.ts │ │ │ ├── protein-translation.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── proverb/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── proverb.test.ts │ │ │ ├── proverb.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── pythagorean-triplet/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── pythagorean-triplet.test.ts │ │ │ ├── pythagorean-triplet.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── queen-attack/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.append.md │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── queen-attack.test.ts │ │ │ ├── queen-attack.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── raindrops/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── raindrops.test.ts │ │ │ ├── raindrops.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── rational-numbers/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── rational-numbers.test.ts │ │ │ ├── rational-numbers.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── react/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── react.test.ts │ │ │ ├── react.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── rectangles/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── rectangles.test.ts │ │ │ ├── rectangles.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── relative-distance/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── relative-distance.test.ts │ │ │ ├── relative-distance.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── resistor-color/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── resistor-color.test.ts │ │ │ ├── resistor-color.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── resistor-color-duo/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── resistor-color-duo.test.ts │ │ │ ├── resistor-color-duo.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── resistor-color-trio/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── resistor-color-trio.test.ts │ │ │ ├── resistor-color-trio.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── reverse-string/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── reverse-string.test.ts │ │ │ ├── reverse-string.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── rna-transcription/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── rna-transcription.test.ts │ │ │ ├── rna-transcription.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── robot-name/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ └── proof.ci.ts │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── robot-name.test.ts │ │ │ ├── robot-name.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── robot-simulator/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── robot-simulator.test.ts │ │ │ ├── robot-simulator.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── roman-numerals/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── roman-numerals.test.ts │ │ │ ├── roman-numerals.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── rotational-cipher/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── rotational-cipher.test.ts │ │ │ ├── rotational-cipher.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── run-length-encoding/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── run-length-encoding.test.ts │ │ │ ├── run-length-encoding.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── saddle-points/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── saddle-points.test.ts │ │ │ ├── saddle-points.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── say/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── say.test.ts │ │ │ ├── say.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── scrabble-score/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── scrabble-score.test.ts │ │ │ ├── scrabble-score.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── secret-handshake/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── secret-handshake.test.ts │ │ │ ├── secret-handshake.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── series/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── series.test.ts │ │ │ ├── series.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── sieve/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── sieve.test.ts │ │ │ ├── sieve.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── simple-cipher/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── simple-cipher.test.ts │ │ │ ├── simple-cipher.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── space-age/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── space-age.test.ts │ │ │ ├── space-age.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── spiral-matrix/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── spiral-matrix.test.ts │ │ │ ├── spiral-matrix.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── split-second-stopwatch/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── split-second-stopwatch.test.ts │ │ │ ├── split-second-stopwatch.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── square-root/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── square-root.test.ts │ │ │ ├── square-root.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── state-of-tic-tac-toe/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── state-of-tic-tac-toe.test.ts │ │ │ ├── state-of-tic-tac-toe.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── strain/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── strain.test.ts │ │ │ ├── strain.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── sublist/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── sublist.test.ts │ │ │ ├── sublist.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── sum-of-multiples/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── sum-of-multiples.test.ts │ │ │ ├── sum-of-multiples.ts │ │ │ ├── test-runner.mjs │ │ │ └── tsconfig.json │ │ ├── tournament/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ ├── tournament.test.ts │ │ │ ├── tournament.ts │ │ │ └── tsconfig.json │ │ ├── transpose/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ ├── transpose.test.ts │ │ │ ├── transpose.ts │ │ │ └── tsconfig.json │ │ ├── triangle/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ ├── triangle.test.ts │ │ │ ├── triangle.ts │ │ │ └── tsconfig.json │ │ ├── twelve-days/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ ├── tsconfig.json │ │ │ ├── twelve-days.test.ts │ │ │ └── twelve-days.ts │ │ ├── two-bucket/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ ├── tsconfig.json │ │ │ ├── two-bucket.test.ts │ │ │ └── two-bucket.ts │ │ ├── two-fer/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ ├── tsconfig.json │ │ │ ├── two-fer.test.ts │ │ │ └── two-fer.ts │ │ ├── variable-length-quantity/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ ├── tsconfig.json │ │ │ ├── variable-length-quantity.test.ts │ │ │ └── variable-length-quantity.ts │ │ ├── word-count/ │ │ │ ├── .docs/ │ │ │ │ ├── instructions.md │ │ │ │ └── introduction.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ ├── tsconfig.json │ │ │ ├── word-count.test.ts │ │ │ └── word-count.ts │ │ ├── word-search/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ ├── tsconfig.json │ │ │ ├── word-search.test.ts │ │ │ └── word-search.ts │ │ ├── wordy/ │ │ │ ├── .docs/ │ │ │ │ └── instructions.md │ │ │ ├── .meta/ │ │ │ │ ├── config.json │ │ │ │ ├── proof.ci.ts │ │ │ │ └── tests.toml │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── settings.json │ │ │ ├── .yarnrc.yml │ │ │ ├── babel.config.cjs │ │ │ ├── eslint.config.mjs │ │ │ ├── jest.config.cjs │ │ │ ├── package.json │ │ │ ├── test-runner.mjs │ │ │ ├── tsconfig.json │ │ │ ├── wordy.test.ts │ │ │ └── wordy.ts │ │ └── yacht/ │ │ ├── .docs/ │ │ │ ├── instructions.md │ │ │ └── introduction.md │ │ ├── .meta/ │ │ │ ├── config.json │ │ │ ├── proof.ci.ts │ │ │ └── tests.toml │ │ ├── .vscode/ │ │ │ ├── extensions.json │ │ │ └── settings.json │ │ ├── .yarn/ │ │ │ └── releases/ │ │ │ └── yarn-3.6.4.cjs │ │ ├── .yarnrc.yml │ │ ├── babel.config.cjs │ │ ├── eslint.config.mjs │ │ ├── jest.config.cjs │ │ ├── package.json │ │ ├── test-runner.mjs │ │ ├── tsconfig.json │ │ ├── yacht.test.ts │ │ └── yacht.ts │ └── shared/ │ └── .docs/ │ ├── help.md │ └── tests.md ├── package.json ├── reference/ │ └── implementing-a-concept-exercise.md ├── scripts/ │ ├── checksum.mjs │ ├── ci-check.mjs │ ├── ci.mjs │ ├── format.mjs │ ├── helpers.mjs │ ├── lint.mjs │ ├── name-check.mjs │ ├── name-uniq.mjs │ ├── pr-check.mjs │ ├── pr.mjs │ ├── stub-check.mjs │ ├── sync.mjs │ └── test.mjs ├── tsconfig.json └── yarn-error.log ================================================ FILE CONTENTS ================================================ ================================================ FILE: .appends/.github/labels.yml ================================================ # ----------------------------------------------------------------------------------------- # # These are the repository-specific labels that augment the Exercise-wide labels defined in # # https://github.com/exercism/org-wide-files/blob/main/global-files/.github/labels.yml. # # ----------------------------------------------------------------------------------------- # - name: 'breaking :boom:' description: '' color: 'fca708' - name: 'bug :bug:' description: "Something isn't working" color: 'b60205' - name: 'chore :wrench:' description: 'Meta related task such as build, test, linting, maintainers.json etc.' color: '000000' - name: 'ci :gear:' description: 'Continuous integration related' color: '000000' - name: 'dependencies' description: 'Pull requests that update a dependency file' color: '0366d6' - name: 'discussion :speech_balloon:' description: '' color: 'fbca04' - name: 'do not merge :construction:' description: '' color: 'fbca04' - name: 'documentation :book:' description: 'Documentation changes' color: '1d76db' - name: 'duplicate :repeat:' description: 'This issue or pull request already exists' color: 'ffffff' - name: 'enhancement :unicorn:' description: "Changing current behaviour, enhancing what's already there" color: 'a2eeef' - name: 'github_actions' description: 'Pull requests that update Github_actions code' color: '000000' - name: 'good first issue' description: 'First-time contributors preferred' color: 'b1db51' - name: 'hacktoberfest' description: 'Hacktoberfest issues! Everyone allowed <3' color: '000000' - name: 'help wanted' description: '' color: 'b1db51' - name: 'invalid' description: '' color: 'ffffff' - name: 'javascript' description: 'Pull requests that update Javascript code' color: '168700' - name: 'new exercise :sparkles:' description: 'Adding an exercise from the canonical data' color: '1105b7' - name: 'question :thinking:' description: '' color: 'ffffff' - name: 'reputation/contributed_code/major' description: '' color: '34FBF8' - name: 'reputation/contributed_code/minor' description: '' color: 'EF6513' - name: 'rfc :speech_balloon:' description: 'Request for Comments' color: 'fbca04' - name: 'security :rotating_light:' description: 'Security related issue or change' color: 'b60205' - name: 'sync :arrows_counterclockwise:' description: 'Sync an exercise with the canonical data' color: '1105b7' - name: 'track-anatomy' description: 'config.json, new exercises' color: '000000' - name: 'upstream ⬆' description: '' color: 'ffffff' - name: 'v3-migration 🤖' description: 'Preparing for Exercism v3' color: 'E99695' - name: 'wontfix :no_good_woman:' description: '' color: 'ffffff' ================================================ FILE: .dependabot/config.yml ================================================ version: 1 update_configs: - package_manager: 'javascript' directory: '/' update_schedule: 'live' allowed_updates: - match: update_type: 'security' ================================================ FILE: .gitattributes ================================================ * text=auto eol=lf ================================================ FILE: .github/CODEOWNERS ================================================ # Code owners .github/CODEOWNERS @exercism/maintainers-admin # Changes to `fetch-configlet` should be made in the `exercism/configlet` repo bin/fetch-configlet @exercism/maintainers-admin bin/fetch-configlet.ps1 @exercism/maintainers-admin ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: # Keep dependencies for GitHub Actions up-to-date - package-ecosystem: 'github-actions' directory: '/' schedule: interval: 'monthly' # Enable version updates for npm (yarn) - package-ecosystem: 'npm' # Look for `package.json` and `lock` files in the `root` directory directory: '/' # Check the npm registry for updates every day (weekdays) schedule: interval: 'monthly' # Enable version updates for npm (yarn) - package-ecosystem: 'npm' # Look for `package.json` and `lock` files in the common directory directory: '/common/' # Check the npm registry for updates every day (weekdays) schedule: interval: 'monthly' ================================================ FILE: .github/labels.yml ================================================ # --------------------------------------------------------------- # # This is an auto-generated file - Do not manually edit this file # # --------------------------------------------------------------- # # This file is automatically generated by concatenating two files: # # 1. The Exercism-wide labels: defined in https://github.com/exercism/org-wide-files/blob/main/global-files/.github/labels.yml # 2. The repository-specific labels: defined in the `.appends/.github/labels.yml` file within this repository. # # If any of these two files change, a pull request is automatically created containing a re-generated version of this file. # Consequently, to change repository-specific labels you should update the `.appends/.github/labels.yml` file and _not_ this file. # # When the pull request has been merged, the GitHub labels will be automatically updated by the "Sync labels" workflow. # This typically takes 5-10 minutes. # --------------------------------------------------------------------- # # These are the Exercism-wide labels which are shared across all repos. # # --------------------------------------------------------------------- # # The following Exercism-wide labels are used to show "tasks" on the website, which will point users to things they can contribute to. # The `x:action/` labels describe what sort of work the contributor will be engaged in when working on the issue - name: "x:action/create" description: "Work on something from scratch" color: "ffffff" - name: "x:action/fix" description: "Fix an issue" color: "ffffff" - name: "x:action/improve" description: "Improve existing functionality/content" color: "ffffff" - name: "x:action/proofread" description: "Proofread text" color: "ffffff" - name: "x:action/sync" description: "Sync content with its latest version" color: "ffffff" # The `x:knowledge/` labels describe how much Exercism knowledge is required by the contributor - name: "x:knowledge/none" description: "No existing Exercism knowledge required" color: "ffffff" - name: "x:knowledge/elementary" description: "Little Exercism knowledge required" color: "ffffff" - name: "x:knowledge/intermediate" description: "Quite a bit of Exercism knowledge required" color: "ffffff" - name: "x:knowledge/advanced" description: "Comprehensive Exercism knowledge required" color: "ffffff" # The `x:module/` labels indicate what part of Exercism the contributor will be working on - name: "x:module/analyzer" description: "Work on Analyzers" color: "ffffff" - name: "x:module/concept" description: "Work on Concepts" color: "ffffff" - name: "x:module/concept-exercise" description: "Work on Concept Exercises" color: "ffffff" - name: "x:module/generator" description: "Work on Exercise generators" color: "ffffff" - name: "x:module/practice-exercise" description: "Work on Practice Exercises" color: "ffffff" - name: "x:module/representer" description: "Work on Representers" color: "ffffff" - name: "x:module/test-runner" description: "Work on Test Runners" color: "ffffff" # The `x:rep/` labels describe the amount of reputation to award # # For more information on reputation and how these labels should be used, # check out https://exercism.org/docs/using/product/reputation - name: "x:rep/tiny" description: "Tiny amount of reputation" color: "ffffff" - name: "x:rep/small" description: "Small amount of reputation" color: "ffffff" - name: "x:rep/medium" description: "Medium amount of reputation" color: "ffffff" - name: "x:rep/large" description: "Large amount of reputation" color: "ffffff" - name: "x:rep/massive" description: "Massive amount of reputation" color: "ffffff" # The `x:size/` labels describe the expected amount of work for a contributor - name: "x:size/tiny" description: "Tiny amount of work" color: "ffffff" - name: "x:size/small" description: "Small amount of work" color: "ffffff" - name: "x:size/medium" description: "Medium amount of work" color: "ffffff" - name: "x:size/large" description: "Large amount of work" color: "ffffff" - name: "x:size/massive" description: "Massive amount of work" color: "ffffff" # The `x:status/` label indicates if there is already someone working on the issue - name: "x:status/claimed" description: "Someone is working on this issue" color: "ffffff" # The `x:type/` labels describe what type of work the contributor will be engaged in - name: "x:type/ci" description: "Work on Continuous Integration (e.g. GitHub Actions workflows)" color: "ffffff" - name: "x:type/coding" description: "Write code that is not student-facing content (e.g. test-runners, generators, but not exercises)" color: "ffffff" - name: "x:type/content" description: "Work on content (e.g. exercises, concepts)" color: "ffffff" - name: "x:type/docker" description: "Work on Dockerfiles" color: "ffffff" - name: "x:type/docs" description: "Work on Documentation" color: "ffffff" # This Exercism-wide label is added to all automatically created pull requests that help migrate/prepare a track for Exercism v3 - name: "v3-migration 🤖" description: "Preparing for Exercism v3" color: "e99695" # This Exercism-wide label can be used to bulk-close issues in preparation for pausing community contributions - name: "paused" description: "Work paused until further notice" color: "e4e669" # ----------------------------------------------------------------------------------------- # # These are the repository-specific labels that augment the Exercise-wide labels defined in # # https://github.com/exercism/org-wide-files/blob/main/global-files/.github/labels.yml. # # ----------------------------------------------------------------------------------------- # - name: 'breaking :boom:' description: '' color: 'fca708' - name: 'bug :bug:' description: "Something isn't working" color: 'b60205' - name: 'chore :wrench:' description: 'Meta related task such as build, test, linting, maintainers.json etc.' color: '000000' - name: 'ci :gear:' description: 'Continuous integration related' color: '000000' - name: 'dependencies' description: 'Pull requests that update a dependency file' color: '0366d6' - name: 'discussion :speech_balloon:' description: '' color: 'fbca04' - name: 'do not merge :construction:' description: '' color: 'fbca04' - name: 'documentation :book:' description: 'Documentation changes' color: '1d76db' - name: 'duplicate :repeat:' description: 'This issue or pull request already exists' color: 'ffffff' - name: 'enhancement :unicorn:' description: "Changing current behaviour, enhancing what's already there" color: 'a2eeef' - name: 'github_actions' description: 'Pull requests that update Github_actions code' color: '000000' - name: 'good first issue' description: 'First-time contributors preferred' color: 'b1db51' - name: 'hacktoberfest' description: 'Hacktoberfest issues! Everyone allowed <3' color: '000000' - name: 'help wanted' description: '' color: 'b1db51' - name: 'invalid' description: '' color: 'ffffff' - name: 'javascript' description: 'Pull requests that update Javascript code' color: '168700' - name: 'new exercise :sparkles:' description: 'Adding an exercise from the canonical data' color: '1105b7' - name: 'question :thinking:' description: '' color: 'ffffff' - name: 'reputation/contributed_code/major' description: '' color: '34FBF8' - name: 'reputation/contributed_code/minor' description: '' color: 'EF6513' - name: 'rfc :speech_balloon:' description: 'Request for Comments' color: 'fbca04' - name: 'security :rotating_light:' description: 'Security related issue or change' color: 'b60205' - name: 'sync :arrows_counterclockwise:' description: 'Sync an exercise with the canonical data' color: '1105b7' - name: 'track-anatomy' description: 'config.json, new exercises' color: '000000' - name: 'upstream ⬆' description: '' color: 'ffffff' - name: 'v3-migration 🤖' description: 'Preparing for Exercism v3' color: 'E99695' - name: 'wontfix :no_good_woman:' description: '' color: 'ffffff' ================================================ FILE: .github/workflows/action-format.yml ================================================ name: 'typescript / format' on: issue_comment: types: [created] jobs: format: name: 'Format code' runs-on: ubuntu-24.04 if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/format') steps: - name: 'Post acknowledgement that it will format code' continue-on-error: true # Never fail the build if this fails uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: 'The "Format code" action has started running.' }) - name: 'Download PR data' run: | PR_DATA="/tmp/pr.json" jq -r ".issue.pull_request.url" "$GITHUB_EVENT_PATH" | \ xargs curl --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' -o "$PR_DATA" --url - name: 'Check fork status' id: fork_status run: | IS_FORK="$(jq '.head.repo.fork' "/tmp/pr.json")" echo "fork=$IS_FORK" >> "$GITHUB_OUTPUT" - name: 'Setup SSH deploy key' if: steps.fork_status.outputs.fork == 'false' run: | mkdir ~/.ssh echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 - name: 'Checkout code' run: | PR_DATA="/tmp/pr.json" HEAD_REF=$(jq -r ".head.ref" "$PR_DATA") if [ ${{ steps.fork_status.outputs.fork }} == "false" ]; then echo "::debug::Setting up repo using SSH" HEAD_REPO=$(jq -r '.head.repo.ssh_url' "$PR_DATA") else echo "::debug::Setting up repo using HTTPS" HEAD_REPO=$(jq -r '.head.repo.clone_url | sub("https://"; "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@")' "$PR_DATA") fi git clone $HEAD_REPO . git checkout -b "$HEAD_REF" "origin/$HEAD_REF" - name: Enable corepack to fix https://github.com/actions/setup-node/pull/901 run: corepack enable yarn - name: Use Node.js LTS (20.x) uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 with: node-version: 20.x cache: 'yarn' - name: Install project development dependencies run: corepack yarn install --immutable - name: 'Format code' run: ./bin/format.sh - name: 'Commit formatted code' run: | # Check if there is nothing to commit (i.e. no formatting changes made) if [ -z "$(git status --porcelain)" ]; then echo "Code is already formatted correctly" exit 0 fi # Setup the git user (required to commit anything) git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" # Commit the changes made by prettier git add . git commit -m "[CI] Format code" git push - name: 'Post acknowledgement that it has formatted the code' continue-on-error: true # Never fail the build if this fails uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: 'The "Format code" action has finished running.' }) - name: 'Post reminder to trigger build manually' continue-on-error: true # Never fail the build if this fails if: steps.fork_status.outputs.fork == 'true' uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: 'For security reasons, `/format` does not trigger CI builds when the PR has been submitted from a fork. If checks were not passing due to code format, trigger a build to make the required checks pass, through one of the following ways:\n\n- Push an empty commit to this branch: `git commit --allow-empty -m "Trigger builds"`.\n- Close and reopen the PR.\n- Push a regular commit to this branch.' }) ================================================ FILE: .github/workflows/action-sync.yml ================================================ name: 'typescript / sync' on: issue_comment: types: [created] jobs: format: name: 'Sync all exercises' runs-on: ubuntu-24.04 if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/sync') steps: - name: 'Post acknowledgement that it will sync exercises' continue-on-error: true # Never fail the build if this fails uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: 'The "Sync all exercises" action has started running.' }) - name: 'Download PR data' run: | PR_DATA="/tmp/pr.json" jq -r ".issue.pull_request.url" "$GITHUB_EVENT_PATH" | \ xargs curl --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' -o "$PR_DATA" --url - name: 'Check fork status' id: fork_status run: | IS_FORK="$(jq '.head.repo.fork' "/tmp/pr.json")" echo "fork=$IS_FORK" >> "$GITHUB_OUTPUT" - name: 'Setup SSH deploy key' if: steps.fork_status.outputs.fork == 'false' run: | mkdir ~/.ssh echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 - name: 'Checkout code' run: | PR_DATA="/tmp/pr.json" HEAD_REF=$(jq -r ".head.ref" "$PR_DATA") if [ ${{ steps.fork_status.outputs.fork }} == "false" ]; then echo "::debug::Setting up repo using SSH" HEAD_REPO=$(jq -r '.head.repo.ssh_url' "$PR_DATA") else echo "::debug::Setting up repo using HTTPS" HEAD_REPO=$(jq -r '.head.repo.clone_url | sub("https://"; "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@")' "$PR_DATA") fi git clone $HEAD_REPO . git checkout -b "$HEAD_REF" "origin/$HEAD_REF" - name: 'Install dependencies' run: | corepack enable yarn corepack yarn install - name: 'Sync exercises' run: corepack yarn sync - name: 'Commit changes' run: | # Check if there is nothing to commit (i.e. no syncing changes made) if [ -z "$(git status --porcelain)" ]; then echo "Exercises are already synced correctly" exit 0 fi # Setup the git user (required to commit anything) git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" # Commit the changes made by prettier git add . git commit -m "[CI] Sync exercises" git push - name: 'Post acknowledgement that it has synced the code' continue-on-error: true # Never fail the build if this fails uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: 'The "Sync all exercises" action has finished running.' }) - name: 'Post reminder to trigger build manually' continue-on-error: true # Never fail the build if this fails if: steps.fork_status.outputs.fork == 'true' uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: 'For security reasons, `/sync` does not trigger CI builds when the PR has been submitted from a fork. If checks were not passing due to code format, trigger a build to make the required checks pass, through one of the following ways:\n\n- Push an empty commit to this branch: `git commit --allow-empty -m "Trigger builds"`.\n- Close and reopen the PR.\n- Push a regular commit to this branch.' }) ================================================ FILE: .github/workflows/ci.js.yml ================================================ # This workflow will do a clean install of node dependencies and run tests across different versions of node # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions name: typescript / main on: push: branches: [main] jobs: precheck: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 - name: Enable corepack to fix https://github.com/actions/setup-node/pull/901 run: corepack enable yarn - name: Use Node.js LTS (20.x) uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 with: node-version: 20.x cache: 'yarn' - name: Install project dependencies run: corepack yarn install --immutable - name: Run exercism/typescript ci precheck (checks config, lint code, and runs tests) for all exercises run: corepack yarn ci:check ci: runs-on: ubuntu-24.04 strategy: matrix: node-version: [20.x, 22.x] steps: - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 - name: Enable corepack to fix https://github.com/actions/setup-node/pull/901 run: corepack enable yarn - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 with: node-version: ${{ matrix.node-version }} cache: 'yarn' - name: Install project dependencies run: corepack yarn install --immutable - name: Run exercism/typescript ci (checks config, lint code, and runs tests) for all exercises run: corepack yarn ci impersonate: # This job tries to run tests for the 'hello-world' exercise, but # simulating what would happen if a student runs the tests standalone. # We do this by removing all project files in the root. runs-on: ubuntu-24.04 steps: - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 - name: Enable corepack to fix https://github.com/actions/setup-node/pull/901 run: corepack enable yarn - name: Use Node.js 20.x uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 with: node-version: 20.x cache: 'yarn' - name: Remove root project files run: rm -f package.json yarn.lock .yarnrc.yml - name: Move solution file so that tests pass working-directory: exercises/practice/hello-world run: | rm -f hello-world.ts cp ./.meta/proof.ci.ts hello-world.ts - name: Install project dependencies working-directory: exercises/practice/hello-world run: corepack yarn install --no-immutable - name: Run tests working-directory: exercises/practice/hello-world run: corepack yarn test ================================================ FILE: .github/workflows/codeql.yml ================================================ # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. name: 'codeql' on: push: branches: [main] pull_request: # The branches below must be a subset of the branches above branches: [main] schedule: - cron: '0 14 * * 5' jobs: analyze: runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: # Override automatic language detection by changing the below list # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] language: ['javascript'] # Learn more... # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection steps: - name: Checkout repository uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v2.3.4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 ================================================ FILE: .github/workflows/configlet.yml ================================================ name: Configlet on: pull_request: push: branches: - main workflow_dispatch: permissions: contents: read jobs: configlet: uses: exercism/github-actions/.github/workflows/configlet.yml@main ================================================ FILE: .github/workflows/no-important-files-changed.yml ================================================ name: No important files changed on: pull_request_target: types: [opened] branches: [main] paths: - "exercises/concept/**" - "exercises/practice/**" - "!exercises/*/*/.approaches/**" - "!exercises/*/*/.articles/**" - "!exercises/*/*/.docs/**" - "!exercises/*/*/.meta/**" permissions: pull-requests: write jobs: check: uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@main with: repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }} ref: ${{ github.head_ref }} ================================================ FILE: .github/workflows/pause-community-contributions.yml ================================================ name: Pause Community Contributions on: issues: types: - opened pull_request_target: types: - opened paths-ignore: - 'exercises/*/*/.approaches/**' - 'exercises/*/*/.articles/**' permissions: issues: write pull-requests: write jobs: pause: if: github.repository_owner == 'exercism' # Stops this job from running on forks uses: exercism/github-actions/.github/workflows/community-contributions.yml@main with: forum_category: typescript secrets: github_membership_token: ${{ secrets.COMMUNITY_CONTRIBUTIONS_WORKFLOW_TOKEN }} ================================================ FILE: .github/workflows/ping-cross-track-maintainers-team.yml ================================================ name: Ping cross-track maintainers team on: pull_request_target: types: - opened permissions: pull-requests: write jobs: ping: if: github.repository_owner == 'exercism' # Stops this job from running on forks uses: exercism/github-actions/.github/workflows/ping-cross-track-maintainers-team.yml@main secrets: github_membership_token: ${{ secrets.COMMUNITY_CONTRIBUTIONS_WORKFLOW_TOKEN }} ================================================ FILE: .github/workflows/pr.ci.js.yml ================================================ # This workflow will do a clean install of node dependencies and run tests across different versions of node # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions name: typescript / pr on: pull_request jobs: precheck: runs-on: ubuntu-24.04 steps: - name: Checkout PR uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 with: fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }} - name: Get changed files id: changed-files run: | if ${{ github.event_name == 'pull_request' }}; then echo "changed_files=$(git diff --name-only --diff-filter=ACM -r HEAD^1 HEAD | xargs)" >> $GITHUB_OUTPUT else echo "changed_files=$(git diff --name-only --diff-filter=ACM ${{ github.event.before }} ${{ github.event.after }} | xargs)" >> $GITHUB_OUTPUT fi - name: Enable corepack to fix https://github.com/actions/setup-node/pull/901 run: corepack enable yarn - name: Use Node.js LTS (20.x) uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 with: node-version: 20.x cache: 'yarn' - name: Install project dependencies run: corepack yarn install --immutable - name: Run exercism/typescript ci precheck (stub files, config integrity) for changed exercises run: corepack yarn node scripts/pr-check.mjs ${{ steps.changed-files.outputs.changed_files }} ci: runs-on: ubuntu-24.04 strategy: matrix: node-version: [20.x, 22.x] steps: - name: Checkout PR uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 with: fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }} - name: Get changed files id: changed-files run: | if ${{ github.event_name == 'pull_request' }}; then echo "changed_files=$(git diff --name-only --diff-filter=ACM -r HEAD^1 HEAD | xargs)" >> $GITHUB_OUTPUT else echo "changed_files=$(git diff --name-only --diff-filter=ACM ${{ github.event.before }} ${{ github.event.after }} | xargs)" >> $GITHUB_OUTPUT fi - name: Enable corepack to fix https://github.com/actions/setup-node/pull/901 run: corepack enable yarn - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 with: node-version: ${{ matrix.node-version }} cache: 'yarn' - name: Install project dependencies run: corepack yarn install --immutable - name: Run exercism/typescript ci (runs tests) for changed/added exercises run: corepack yarn node scripts/pr.mjs ${{ steps.changed-files.outputs.changed_files }} impersonate: # This job tries to run tests for the 'hello-world' exercise, but # simulating what would happen if a student runs the tests standalone. # We do this by removing all project files in the root. runs-on: ubuntu-24.04 steps: - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 - name: Enable corepack to fix https://github.com/actions/setup-node/pull/901 run: corepack enable yarn - name: Use Node.js 20.x uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 with: node-version: 20.x cache: 'yarn' - name: Remove root project files run: rm -f package.json yarn.lock .yarnrc.yml - name: Move solution file so that tests pass working-directory: exercises/practice/hello-world run: | rm -f hello-world.ts cp ./.meta/proof.ci.ts hello-world.ts - name: Install project dependencies working-directory: exercises/practice/hello-world run: corepack yarn install --no-immutable - name: Run tests working-directory: exercises/practice/hello-world run: corepack yarn test ================================================ FILE: .github/workflows/run-configlet-sync.yml ================================================ name: Run Configlet Sync on: workflow_dispatch: schedule: - cron: '0 0 15 * *' jobs: call-gha-workflow: uses: exercism/github-actions/.github/workflows/configlet-sync.yml@main ================================================ FILE: .github/workflows/sync-labels.yml ================================================ name: Tools on: push: branches: - main paths: - .github/labels.yml - .github/workflows/sync-labels.yml workflow_dispatch: schedule: - cron: 0 0 1 * * # First day of each month permissions: issues: write jobs: sync-labels: uses: exercism/github-actions/.github/workflows/labels.yml@main ================================================ FILE: .github/workflows/verify-code-formatting.yml ================================================ name: typescript / format on: push: pull_request: workflow_dispatch: jobs: verify: runs-on: ubuntu-24.04 steps: - name: 'Checkout code' uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 - name: 'Verify formatting of all files' run: ./bin/check-formatting.sh ================================================ FILE: .gitignore ================================================ *.swp .DS_Store .idea npm-debug.log build babel-output lintreport.json debug.log # Binaries downloaded by binary download scripts bin/configlet bin/configlet.exe # Node modules /exercises/**/node_modules /common/node_modules /node_modules # Yarn files /exercises/**/.pnp.* /exercises/**/.yarn/* !/exercises/**/.yarn/patches !/exercises/**/.yarn/plugins !/exercises/**/.yarn/releases !/exercises/**/.yarn/sdks !/exercises/**/.yarn/versions /common/.pnp.* /common/.yarn/* !/common/.yarn/patches !/common/.yarn/plugins !/common/.yarn/releases !/common/.yarn/sdks !/common/.yarn/versions /.pnp.* /.yarn/* !/.yarn/patches !/.yarn/plugins !/.yarn/releases !/.yarn/sdks !/.yarn/versions # Maintainer specific generated files /tmp_exercises /tmp exercise-package.json ================================================ FILE: .prettierignore ================================================ /.github/labels.yml /.github/workflows/sync-labels.yml /.github/workflows/no-important-files-changed.yml exercises/**/README.md !/README.md .vscode/**/* .yarn/**/* # Originates from https://github.com/exercism/org-wide-files CODE_OF_CONDUCT.md LICENSE # These are formatted via configlet and will not match prettier exercises/**/.meta/config.json exercises/**/.approaches/config.json config.json # Originates from https://github.com/exercism/problem-specifications exercises/practice/**/.docs/instructions.md exercises/practice/**/.docs/introduction.md ================================================ FILE: .prettierrc ================================================ { "trailingComma": "es5", "tabWidth": 2, "semi": false, "singleQuote": true, "arrowParens": "always", "printWidth": 80, "endOfLine": "lf" } ================================================ FILE: .vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: .vscode/settings.json ================================================ { "cSpell.words": [ "exercism" ], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true }, "eslint.nodePath": ".yarn/sdks", "prettier.prettierPath": ".yarn/sdks/prettier/index.cjs", "typescript.tsdk": ".yarn/sdks/typescript/lib", "typescript.enablePromptUseWorkspaceTsdk": true } ================================================ FILE: .yarn/sdks/eslint/bin/eslint.js ================================================ #!/usr/bin/env node const {existsSync} = require(`fs`); const {createRequire, register} = require(`module`); const {resolve} = require(`path`); const {pathToFileURL} = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = createRequire(absPnpApiPath); const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); if (existsSync(absPnpApiPath)) { if (!process.versions.pnp) { // Setup the environment to be able to require eslint/bin/eslint.js require(absPnpApiPath).setup(); if (isPnpLoaderEnabled && register) { register(pathToFileURL(absPnpLoaderPath)); } } } // Defer to the real eslint/bin/eslint.js your application uses module.exports = absRequire(`eslint/bin/eslint.js`); ================================================ FILE: .yarn/sdks/eslint/lib/api.js ================================================ #!/usr/bin/env node const {existsSync} = require(`fs`); const {createRequire, register} = require(`module`); const {resolve} = require(`path`); const {pathToFileURL} = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = createRequire(absPnpApiPath); const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); if (existsSync(absPnpApiPath)) { if (!process.versions.pnp) { // Setup the environment to be able to require eslint require(absPnpApiPath).setup(); if (isPnpLoaderEnabled && register) { register(pathToFileURL(absPnpLoaderPath)); } } } // Defer to the real eslint your application uses module.exports = absRequire(`eslint`); ================================================ FILE: .yarn/sdks/eslint/lib/unsupported-api.js ================================================ #!/usr/bin/env node const {existsSync} = require(`fs`); const {createRequire, register} = require(`module`); const {resolve} = require(`path`); const {pathToFileURL} = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = createRequire(absPnpApiPath); const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); if (existsSync(absPnpApiPath)) { if (!process.versions.pnp) { // Setup the environment to be able to require eslint/use-at-your-own-risk require(absPnpApiPath).setup(); if (isPnpLoaderEnabled && register) { register(pathToFileURL(absPnpLoaderPath)); } } } // Defer to the real eslint/use-at-your-own-risk your application uses module.exports = absRequire(`eslint/use-at-your-own-risk`); ================================================ FILE: .yarn/sdks/eslint/package.json ================================================ { "name": "eslint", "version": "9.8.0-sdk", "main": "./lib/api.js", "type": "commonjs", "bin": { "eslint": "./bin/eslint.js" }, "exports": { "./package.json": "./package.json", ".": "./lib/api.js", "./use-at-your-own-risk": "./lib/unsupported-api.js" } } ================================================ FILE: .yarn/sdks/integrations.yml ================================================ # This file is automatically generated by @yarnpkg/sdks. # Manual changes might be lost! integrations: - vscode ================================================ FILE: .yarn/sdks/prettier/bin/prettier.cjs ================================================ #!/usr/bin/env node const {existsSync} = require(`fs`); const {createRequire, register} = require(`module`); const {resolve} = require(`path`); const {pathToFileURL} = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = createRequire(absPnpApiPath); const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); if (existsSync(absPnpApiPath)) { if (!process.versions.pnp) { // Setup the environment to be able to require prettier/bin/prettier.cjs require(absPnpApiPath).setup(); if (isPnpLoaderEnabled && register) { register(pathToFileURL(absPnpLoaderPath)); } } } // Defer to the real prettier/bin/prettier.cjs your application uses module.exports = absRequire(`prettier/bin/prettier.cjs`); ================================================ FILE: .yarn/sdks/prettier/index.cjs ================================================ #!/usr/bin/env node const {existsSync} = require(`fs`); const {createRequire, register} = require(`module`); const {resolve} = require(`path`); const {pathToFileURL} = require(`url`); const relPnpApiPath = "../../../.pnp.cjs"; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = createRequire(absPnpApiPath); const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); if (existsSync(absPnpApiPath)) { if (!process.versions.pnp) { // Setup the environment to be able to require prettier require(absPnpApiPath).setup(); if (isPnpLoaderEnabled && register) { register(pathToFileURL(absPnpLoaderPath)); } } } // Defer to the real prettier your application uses module.exports = absRequire(`prettier`); ================================================ FILE: .yarn/sdks/prettier/package.json ================================================ { "name": "prettier", "version": "3.3.3-sdk", "main": "./index.cjs", "type": "commonjs", "bin": "./bin/prettier.cjs" } ================================================ FILE: .yarn/sdks/typescript/bin/tsc ================================================ #!/usr/bin/env node const {existsSync} = require(`fs`); const {createRequire, register} = require(`module`); const {resolve} = require(`path`); const {pathToFileURL} = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = createRequire(absPnpApiPath); const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); if (existsSync(absPnpApiPath)) { if (!process.versions.pnp) { // Setup the environment to be able to require typescript/bin/tsc require(absPnpApiPath).setup(); if (isPnpLoaderEnabled && register) { register(pathToFileURL(absPnpLoaderPath)); } } } // Defer to the real typescript/bin/tsc your application uses module.exports = absRequire(`typescript/bin/tsc`); ================================================ FILE: .yarn/sdks/typescript/bin/tsserver ================================================ #!/usr/bin/env node const {existsSync} = require(`fs`); const {createRequire, register} = require(`module`); const {resolve} = require(`path`); const {pathToFileURL} = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = createRequire(absPnpApiPath); const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); if (existsSync(absPnpApiPath)) { if (!process.versions.pnp) { // Setup the environment to be able to require typescript/bin/tsserver require(absPnpApiPath).setup(); if (isPnpLoaderEnabled && register) { register(pathToFileURL(absPnpLoaderPath)); } } } // Defer to the real typescript/bin/tsserver your application uses module.exports = absRequire(`typescript/bin/tsserver`); ================================================ FILE: .yarn/sdks/typescript/lib/tsc.js ================================================ #!/usr/bin/env node const {existsSync} = require(`fs`); const {createRequire, register} = require(`module`); const {resolve} = require(`path`); const {pathToFileURL} = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = createRequire(absPnpApiPath); const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); if (existsSync(absPnpApiPath)) { if (!process.versions.pnp) { // Setup the environment to be able to require typescript/lib/tsc.js require(absPnpApiPath).setup(); if (isPnpLoaderEnabled && register) { register(pathToFileURL(absPnpLoaderPath)); } } } // Defer to the real typescript/lib/tsc.js your application uses module.exports = absRequire(`typescript/lib/tsc.js`); ================================================ FILE: .yarn/sdks/typescript/lib/tsserver.js ================================================ #!/usr/bin/env node const {existsSync} = require(`fs`); const {createRequire, register} = require(`module`); const {resolve} = require(`path`); const {pathToFileURL} = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = createRequire(absPnpApiPath); const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); if (existsSync(absPnpApiPath)) { if (!process.versions.pnp) { // Setup the environment to be able to require typescript/lib/tsserver.js require(absPnpApiPath).setup(); if (isPnpLoaderEnabled && register) { register(pathToFileURL(absPnpLoaderPath)); } } } const moduleWrapper = tsserver => { if (!process.versions.pnp) { return tsserver; } const {isAbsolute} = require(`path`); const pnpApi = require(`pnpapi`); const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); const isPortal = str => str.startsWith("portal:/"); const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { return `${locator.name}@${locator.reference}`; })); // VSCode sends the zip paths to TS using the "zip://" prefix, that TS // doesn't understand. This layer makes sure to remove the protocol // before forwarding it to TS, and to add it back on all returned paths. function toEditorPath(str) { // We add the `zip:` prefix to both `.zip/` paths and virtual paths if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { // We also take the opportunity to turn virtual paths into physical ones; // this makes it much easier to work with workspaces that list peer // dependencies, since otherwise Ctrl+Click would bring us to the virtual // file instances instead of the real ones. // // We only do this to modules owned by the the dependency tree roots. // This avoids breaking the resolution when jumping inside a vendor // with peer dep (otherwise jumping into react-dom would show resolution // errors on react). // const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; if (resolved) { const locator = pnpApi.findPackageLocator(resolved); if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { str = resolved; } } str = normalize(str); if (str.match(/\.zip\//)) { switch (hostInfo) { // Absolute VSCode `Uri.fsPath`s need to start with a slash. // VSCode only adds it automatically for supported schemes, // so we have to do it manually for the `zip` scheme. // The path needs to start with a caret otherwise VSCode doesn't handle the protocol // // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 // // 2021-10-08: VSCode changed the format in 1.61. // Before | ^zip:/c:/foo/bar.zip/package.json // After | ^/zip//c:/foo/bar.zip/package.json // // 2022-04-06: VSCode changed the format in 1.66. // Before | ^/zip//c:/foo/bar.zip/package.json // After | ^/zip/c:/foo/bar.zip/package.json // // 2022-05-06: VSCode changed the format in 1.68 // Before | ^/zip/c:/foo/bar.zip/package.json // After | ^/zip//c:/foo/bar.zip/package.json // case `vscode <1.61`: { str = `^zip:${str}`; } break; case `vscode <1.66`: { str = `^/zip/${str}`; } break; case `vscode <1.68`: { str = `^/zip${str}`; } break; case `vscode`: { str = `^/zip/${str}`; } break; // To make "go to definition" work, // We have to resolve the actual file system path from virtual path // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) case `coc-nvim`: { str = normalize(resolved).replace(/\.zip\//, `.zip::`); str = resolve(`zipfile:${str}`); } break; // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) // We have to resolve the actual file system path from virtual path, // everything else is up to neovim case `neovim`: { str = normalize(resolved).replace(/\.zip\//, `.zip::`); str = `zipfile://${str}`; } break; default: { str = `zip:${str}`; } break; } } else { str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); } } return str; } function fromEditorPath(str) { switch (hostInfo) { case `coc-nvim`: { str = str.replace(/\.zip::/, `.zip/`); // The path for coc-nvim is in format of //zipfile://.yarn/... // So in order to convert it back, we use .* to match all the thing // before `zipfile:` return process.platform === `win32` ? str.replace(/^.*zipfile:\//, ``) : str.replace(/^.*zipfile:/, ``); } break; case `neovim`: { str = str.replace(/\.zip::/, `.zip/`); // The path for neovim is in format of zipfile:////.yarn/... return str.replace(/^zipfile:\/\//, ``); } break; case `vscode`: default: { return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) } break; } } // Force enable 'allowLocalPluginLoads' // TypeScript tries to resolve plugins using a path relative to itself // which doesn't work when using the global cache // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but // TypeScript already does local loads and if this code is running the user trusts the workspace // https://github.com/microsoft/vscode/issues/45856 const ConfiguredProject = tsserver.server.ConfiguredProject; const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; ConfiguredProject.prototype.enablePluginsWithOptions = function() { this.projectService.allowLocalPluginLoads = true; return originalEnablePluginsWithOptions.apply(this, arguments); }; // And here is the point where we hijack the VSCode <-> TS communications // by adding ourselves in the middle. We locate everything that looks // like an absolute path of ours and normalize it. const Session = tsserver.server.Session; const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; let hostInfo = `unknown`; Object.assign(Session.prototype, { onMessage(/** @type {string | object} */ message) { const isStringMessage = typeof message === 'string'; const parsedMessage = isStringMessage ? JSON.parse(message) : message; if ( parsedMessage != null && typeof parsedMessage === `object` && parsedMessage.arguments && typeof parsedMessage.arguments.hostInfo === `string` ) { hostInfo = parsedMessage.arguments.hostInfo; if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( // The RegExp from https://semver.org/ but without the caret at the start /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ ) ?? []).map(Number) if (major === 1) { if (minor < 61) { hostInfo += ` <1.61`; } else if (minor < 66) { hostInfo += ` <1.66`; } else if (minor < 68) { hostInfo += ` <1.68`; } } } } const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { return typeof value === 'string' ? fromEditorPath(value) : value; }); return originalOnMessage.call( this, isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) ); }, send(/** @type {any} */ msg) { return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { return typeof value === `string` ? toEditorPath(value) : value; }))); } }); return tsserver; }; const [major, minor] = absRequire(`typescript/package.json`).version.split(`.`, 2).map(value => parseInt(value, 10)); // In TypeScript@>=5.5 the tsserver uses the public TypeScript API so that needs to be patched as well. // Ref https://github.com/microsoft/TypeScript/pull/55326 if (major > 5 || (major === 5 && minor >= 5)) { moduleWrapper(absRequire(`typescript`)); } // Defer to the real typescript/lib/tsserver.js your application uses module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`)); ================================================ FILE: .yarn/sdks/typescript/lib/tsserverlibrary.js ================================================ #!/usr/bin/env node const {existsSync} = require(`fs`); const {createRequire, register} = require(`module`); const {resolve} = require(`path`); const {pathToFileURL} = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = createRequire(absPnpApiPath); const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); if (existsSync(absPnpApiPath)) { if (!process.versions.pnp) { // Setup the environment to be able to require typescript/lib/tsserverlibrary.js require(absPnpApiPath).setup(); if (isPnpLoaderEnabled && register) { register(pathToFileURL(absPnpLoaderPath)); } } } const moduleWrapper = tsserver => { if (!process.versions.pnp) { return tsserver; } const {isAbsolute} = require(`path`); const pnpApi = require(`pnpapi`); const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); const isPortal = str => str.startsWith("portal:/"); const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { return `${locator.name}@${locator.reference}`; })); // VSCode sends the zip paths to TS using the "zip://" prefix, that TS // doesn't understand. This layer makes sure to remove the protocol // before forwarding it to TS, and to add it back on all returned paths. function toEditorPath(str) { // We add the `zip:` prefix to both `.zip/` paths and virtual paths if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { // We also take the opportunity to turn virtual paths into physical ones; // this makes it much easier to work with workspaces that list peer // dependencies, since otherwise Ctrl+Click would bring us to the virtual // file instances instead of the real ones. // // We only do this to modules owned by the the dependency tree roots. // This avoids breaking the resolution when jumping inside a vendor // with peer dep (otherwise jumping into react-dom would show resolution // errors on react). // const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; if (resolved) { const locator = pnpApi.findPackageLocator(resolved); if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { str = resolved; } } str = normalize(str); if (str.match(/\.zip\//)) { switch (hostInfo) { // Absolute VSCode `Uri.fsPath`s need to start with a slash. // VSCode only adds it automatically for supported schemes, // so we have to do it manually for the `zip` scheme. // The path needs to start with a caret otherwise VSCode doesn't handle the protocol // // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 // // 2021-10-08: VSCode changed the format in 1.61. // Before | ^zip:/c:/foo/bar.zip/package.json // After | ^/zip//c:/foo/bar.zip/package.json // // 2022-04-06: VSCode changed the format in 1.66. // Before | ^/zip//c:/foo/bar.zip/package.json // After | ^/zip/c:/foo/bar.zip/package.json // // 2022-05-06: VSCode changed the format in 1.68 // Before | ^/zip/c:/foo/bar.zip/package.json // After | ^/zip//c:/foo/bar.zip/package.json // case `vscode <1.61`: { str = `^zip:${str}`; } break; case `vscode <1.66`: { str = `^/zip/${str}`; } break; case `vscode <1.68`: { str = `^/zip${str}`; } break; case `vscode`: { str = `^/zip/${str}`; } break; // To make "go to definition" work, // We have to resolve the actual file system path from virtual path // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) case `coc-nvim`: { str = normalize(resolved).replace(/\.zip\//, `.zip::`); str = resolve(`zipfile:${str}`); } break; // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) // We have to resolve the actual file system path from virtual path, // everything else is up to neovim case `neovim`: { str = normalize(resolved).replace(/\.zip\//, `.zip::`); str = `zipfile://${str}`; } break; default: { str = `zip:${str}`; } break; } } else { str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); } } return str; } function fromEditorPath(str) { switch (hostInfo) { case `coc-nvim`: { str = str.replace(/\.zip::/, `.zip/`); // The path for coc-nvim is in format of //zipfile://.yarn/... // So in order to convert it back, we use .* to match all the thing // before `zipfile:` return process.platform === `win32` ? str.replace(/^.*zipfile:\//, ``) : str.replace(/^.*zipfile:/, ``); } break; case `neovim`: { str = str.replace(/\.zip::/, `.zip/`); // The path for neovim is in format of zipfile:////.yarn/... return str.replace(/^zipfile:\/\//, ``); } break; case `vscode`: default: { return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) } break; } } // Force enable 'allowLocalPluginLoads' // TypeScript tries to resolve plugins using a path relative to itself // which doesn't work when using the global cache // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but // TypeScript already does local loads and if this code is running the user trusts the workspace // https://github.com/microsoft/vscode/issues/45856 const ConfiguredProject = tsserver.server.ConfiguredProject; const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; ConfiguredProject.prototype.enablePluginsWithOptions = function() { this.projectService.allowLocalPluginLoads = true; return originalEnablePluginsWithOptions.apply(this, arguments); }; // And here is the point where we hijack the VSCode <-> TS communications // by adding ourselves in the middle. We locate everything that looks // like an absolute path of ours and normalize it. const Session = tsserver.server.Session; const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; let hostInfo = `unknown`; Object.assign(Session.prototype, { onMessage(/** @type {string | object} */ message) { const isStringMessage = typeof message === 'string'; const parsedMessage = isStringMessage ? JSON.parse(message) : message; if ( parsedMessage != null && typeof parsedMessage === `object` && parsedMessage.arguments && typeof parsedMessage.arguments.hostInfo === `string` ) { hostInfo = parsedMessage.arguments.hostInfo; if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( // The RegExp from https://semver.org/ but without the caret at the start /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ ) ?? []).map(Number) if (major === 1) { if (minor < 61) { hostInfo += ` <1.61`; } else if (minor < 66) { hostInfo += ` <1.66`; } else if (minor < 68) { hostInfo += ` <1.68`; } } } } const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { return typeof value === 'string' ? fromEditorPath(value) : value; }); return originalOnMessage.call( this, isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) ); }, send(/** @type {any} */ msg) { return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { return typeof value === `string` ? toEditorPath(value) : value; }))); } }); return tsserver; }; const [major, minor] = absRequire(`typescript/package.json`).version.split(`.`, 2).map(value => parseInt(value, 10)); // In TypeScript@>=5.5 the tsserver uses the public TypeScript API so that needs to be patched as well. // Ref https://github.com/microsoft/TypeScript/pull/55326 if (major > 5 || (major === 5 && minor >= 5)) { moduleWrapper(absRequire(`typescript`)); } // Defer to the real typescript/lib/tsserverlibrary.js your application uses module.exports = moduleWrapper(absRequire(`typescript/lib/tsserverlibrary.js`)); ================================================ FILE: .yarn/sdks/typescript/lib/typescript.js ================================================ #!/usr/bin/env node const {existsSync} = require(`fs`); const {createRequire, register} = require(`module`); const {resolve} = require(`path`); const {pathToFileURL} = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = createRequire(absPnpApiPath); const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); if (existsSync(absPnpApiPath)) { if (!process.versions.pnp) { // Setup the environment to be able to require typescript require(absPnpApiPath).setup(); if (isPnpLoaderEnabled && register) { register(pathToFileURL(absPnpLoaderPath)); } } } // Defer to the real typescript your application uses module.exports = absRequire(`typescript`); ================================================ FILE: .yarn/sdks/typescript/package.json ================================================ { "name": "typescript", "version": "5.5.4-sdk", "main": "./lib/typescript.js", "type": "commonjs", "bin": { "tsc": "./bin/tsc", "tsserver": "./bin/tsserver" } } ================================================ FILE: .yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of Conduct ## Introduction Exercism is a platform centered around empathetic conversation. We have a low tolerance for communication that makes anyone feel unwelcome, unsupported, insulted or discriminated against. ## Seen or experienced something uncomfortable? If you see or experience abuse, harassment, discrimination, or feel unsafe or upset, please email [abuse@exercism.org](mailto:abuse@exercism.org?subject=%5BCoC%5D) and include \[CoC\] in the subject line. We will follow up with you as a priority. ## Enforcement We actively monitor for Code of Conduct (CoC) violations and take any reports of violations extremely seriously. We have banned contributors, mentors and users due to violations. After we receive a report of a CoC violation, we view that person's conversation history on Exercism and related communication channels and attempt to understand whether someone has deliberately broken the CoC, or accidentally crossed a line. We generally reach out to the person who has been reported to discuss any concerns we have and warn them that repeated violations will result in a ban. Sometimes we decide that no violation has occurred and that no action is required and sometimes we will also ban people on a first offense. We strive to be fair, but will err on the side of protecting the culture of our community. Exercism's leadership reserve the right to take whatever action they feel appropriate with regards to CoC violations. ## The simple version - Be empathetic - Be welcoming - Be kind - Be honest - Be supportive - Be polite ## The details Exercism should be a safe place for everybody regardless of - Gender, gender identity or gender expression - Sexual orientation - Disability - Physical appearance (including but not limited to body size) - Race - Age - Religion - Anything else you can think of As someone who is part of this community, you agree that: - We are collectively and individually committed to safety and inclusivity - We have zero tolerance for abuse, harassment, or discrimination - We respect people’s boundaries and identities - We refrain from using language that can be considered offensive or oppressive (systemically or otherwise), eg. sexist, racist, homophobic, transphobic, ableist, classist, etc. - this includes (but is not limited to) various slurs. - We avoid using offensive topics as a form of humor We actively work towards: - Being a safe community - Cultivating a network of support & encouragement for each other - Encouraging responsible and varied forms of expression We condemn: - Stalking, doxxing, or publishing private information - Violence, threats of violence or violent language - Anything that compromises people’s safety - Conduct or speech which might be considered sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory or offensive in nature - The use of unwelcome, suggestive, derogatory or inappropriate nicknames or terms - Disrespect towards others (jokes, innuendo, dismissive attitudes) and towards differences of opinion - Intimidation or harassment (online or in-person). Please read the [Citizen Code of Conduct](https://github.com/stumpsyn/policies/blob/master/citizen_code_of_conduct.md) for how we interpret harassment - Inappropriate attention or contact - Not understanding the differences between constructive criticism and disparagement These things are NOT OK. Be aware of how your actions affect others. If it makes someone uncomfortable, stop. If you say something that is found offensive, and you are called out on it, try to: - Listen without interruption - Believe what the person is saying & do not attempt to disqualify what they have to say - Ask for tips / help with avoiding making the offense in the future - Apologize and ask forgiveness ## History This policy was initially adopted from the Front-end London Slack community and has been modified since. A version history can be seen on [GitHub](https://github.com/exercism/website-copy/edit/main/pages/code_of_conduct.md). _This policy is a "living" document, and subject to refinement and expansion in the future. This policy applies to the Exercism website, the Exercism GitHub organization, any other Exercism-related communication channels (e.g. Discord, Forum, Twitter, email) and any other Exercism entity or event._ ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing This is the TypeScript track, one of the many tracks on [exercism][web-exercism]. It holds all the _exercises_ that are currently implemented and available for students to complete. The track consists of various **core** exercises, the ones a student _must_ complete, and each **core** exercise may unlock various _side_ exercises. You can find this in the [`config.json`][file-config]. It's not uncommon that people discover incorrect implementations of certain tests, have a suggestion for a track specific hint to aid the student on the _JavaScript specifics_, see optimisations in terms of the configurations of `jest`, `eslint` or other dependencies, report missing edge cases, factual errors, logical errors, and, implement exercises or develop new exercises. > [!IMPORTANT] > When contributing, please open your PR from a branch so our automated tooling can run. We welcome contributions of all sorts and sizes, from reporting issues to submitting patches, as well as joining the current [discussions 💬][issue-discussion]. --- ## Table of Contents - [Contributing](#contributing) - [Table of Contents](#table-of-contents) - [Code of Conduct](#code-of-conduct) - [Exercises](#exercises) - [New exercise](#new-exercise) - [Implementing existing exercise](#implementing-existing-exercise) - [Creating a track-specific exercise](#creating-a-track-specific-exercise) - [Existing exercises](#existing-exercises) - [Improving the README.md](#improving-the-readmemd) - [Syncing the exercise](#syncing-the-exercise) - [Improving or adding mentor notes](#improving-or-adding-mentor-notes) - [Improving or adding automated test analyzers](#improving-or-adding-automated-test-analyzers) - [Documentation](#documentation) - [Tools](#tools) - [Fetch configlet](#fetch-configlet) - [Scripts](#scripts) - [`format`](#format) - [`lint`](#lint) - [`test`](#test) - [`sync`](#sync) - [`checksum`](#checksum) - [`ci-check`](#ci-check) - [`ci`](#ci) - [`name-check`](#name-check) - [`name-uniq`](#name-uniq) --- This guide covers several common scenarios pertaining to **improving the TypeScript track**. There are several other guides about contributing to other parts of the Exercism ecosystem, that are similar to this repository. - [Generic information about track-contributing][contributing-generic] - [The JavaScript track][contributing-javascript] - [The JavaScript Analyzer][contributing-javascript-analyzer] - [The JavaScript Test Runner][contributing-javascript-test-runner] - [The TypeScript Analyzer][contributing-typescript-analyzer] - [The TypeScript Test Runner][contributing-typescript-test-runner] ## Code of Conduct Help us keep Exercism welcoming. Please read and abide by the [Code of Conduct][coc]. ## Exercises Before contributing code to any existing exercise or any new exercise, please have a thorough look at the current exercises and dive into [open issues][issue-open]. ### New exercise There are two ways to implement new exercises (exercises that don't exist in this track). 1. Pick one from [the list of exercises][list-of-exercises] (implemented in other tracks). 2. Create a new, track-specific exercise from scratch. #### Implementing existing exercise Let's say you want to implement a new exercise, from the list of (practice) exercises, because you've noticed that this track could benefit from this exercise, really liked it in another track, or just because you find this interesting; the first step is to [check for an open issue][issue-new-exercise]. If it's there, make sure no one is working on it, and most of all that there is not an open Pull Request towards this exercise. If there is no such issue, you may open one. The baseline of work is as follows: 1. Open a new issue, we'll label it with `new exercise ✨` 1. We'll assign the issue to you, so you get to work on this exercise 1. Create a new folder in `/exercises` 1. You'll need to sync this folder with the matching config files. You can use `sync` to do this: `ASSIGNMENT=practice/slug corepack yarn sync`. 1. Create a `.ts` stub file. 1. Create a `.test.ts` test file. Here add the tests, per canonical data if possible (more on canonical data below). 1. Create a `.meta/proof.ci.ts` file. Place a working implementation, assuming it's renamed to `.ts` 1. Create `.meta/tests.toml`. If the exercise that is being implemented has test data in the [problem specifications repository][problem-specifications], the contents of this file **must** be a list of UUIDs of the tests that are implemented or not implemented. Scroll down to [tools](#tools) to find the configlet application which aids generating this file _interactively_. 1. Create a `.meta/config.json`. Copy the structure from any other `.meta/config.json`. Fill the `blurb`, `source` and `source_url` according to the `metadata.yml` in the [problem specifications repository][problem-specifications]. Add yourself as author. 1. Create a `.docs/instructions.md` file. Copy the instructions from the [problem specifications repository][problem-specifications] 1. Run the tests locally, using `scripts/test`: `ASSIGNMENT=practice/slug corepack yarn test`. 1. Run the linter locally, using `scripts/lint`: `ASSIGNMENT=practice/slug corepack yarn lint`. 1. Create an entry in `config.json`: a unique _new_ UUID (you can use the `configlet uuid` tool to generate one, scroll down to [tools](#tools) to see how you can get it), give it a difficulty (should be similar to similar exercises), and make sure the _order_ of the file is sane. Currently the file is ordered first on core - non core, then on difficulty low to high, and finally lexographically. 1. Format the files, using `scripts/format`: `corepack yarn format`. The final step is opening a Pull Request, with these items all checked off. Make sure the tests run and the linter is happy. It will run automatically on your PR. #### Creating a track-specific exercise The steps for a track-specific exercise are similar to those of implementing an established, existing exercise. The differences are: - You'll have to write a README.md and test-suite from scratch - You'll have to come up with a unique _slug_. - We need to require an icon for it. - Generate a UUID, for example using [configlet][configlet]. Open a new issue with your proposal, and we'll make sure all these steps are correctly taken. Don't worry! You're not alone in this. ### Existing exercises There are always improvements possible on existing exercises. #### Improving the README.md `README.md`: the description that shows up on the student's exercise page, when they are ready to start. It's also downloaded as part of the exercise's data. he `README.md`, together with the `.test.ts` file form the contract for the implementation of the exercise. No test should _force_ a specific implementation, no `README.md` explanation should _give away_ a certain implementation. The `README.md` files are [generated][doc-readme], which is explains [here][doc-readme]. - This file may need to be _regenerated_ in order to sync with the latest canonical data. - You may contribute track specific `hints.md`, as listed in that [document][doc-readme] - You may improve the track specific `exercise-readme-insert.md`, and regenerate all the READMEs. > **Note**: In v3, this will no longer be exactly the same. We'll update this section of the guide when V3 is live. #### Syncing the exercise Syncing an exercise with _canonical data_: There is a [problem-specifications][problem-specifications] repository that holds test data in a standardised format. These tests are occasionally fixed, improved, added, removed or otherwise changed. Syncing an exercise consists of: - updating `tests.toml`; - updating the `.test.ts` file; - updating the `.meta/tests.toml` file, if the exercise that is being updated has test data in the [problem specifications repository][problem-specifications]. The contents of this file can be updated using configlet, interactively; - match the `proof.ci.ts` file to still work with the new tests; and - regenerate the [`README.md`][doc-readme], should there be any changes. #### Improving or adding mentor notes [Mentor notes][mentor-notes] are the notes that are given to the mentors to guide them with mentoring. These notes _do not live in this repository_, but instead in the `website-copy` repository. Find their [contributing guidelines][contributing-website-copy] [here][contributing-website-copy]. #### Improving or adding automated test analyzers Some exercises already have automated mentoring support. These automations don't live in this repository, but instead in the `typescript-analyzer` repository. Find their [contributing guidelines][contributing-typescript-analyzer] [here][contributing-typescript-analyzer]. ## Documentation There is quite a bit of student-facing documentation, which can be found in the [`docs`][file-docs] folder. You may improve these files by making the required changes and opening a new Pull Request. ## Tools You'll need LTS or higher NodeJS in order to contribute to the _code_ in this respository. Run `yarn` in the root in order to be able to run the scripts as listed below. We use the following dependencies: - `shelljs` in order to provide shell interface to scripts - `eslint` for linting all code in the stub, test file and example file - `jest` to run all the test files on all example implementations - `babel` to transpile everything so it works _regardless of your version of NodeJS_. We also use `prettier` to format the files. When possible, use `corepack yarn format` to run prettier. If you want to auto-format using your editor, match the version in the GitHub Workflow `verify-code-formatting.yml`. ### Fetch configlet If you'd like to download [configlet][configlet], you can use the [`fetch-configlet`][bin-fetch-configlet] binary. It will run on Linux, Mac OSX and Windows, and download `configlet` to your local drive. Find more information about [configlet][configlet] [here][configlet]. > If a track implements an exercise for which test data exists, the exercise _must_ contain a `.meta/tests.toml` file. > The goal of the `tests.toml` file is to keep track of which tests are implemented by the exercise. > Tests in this file are identified by their UUID and each test has a boolean value that indicates if it is implemented by that exercise. A `tests.toml` file for a track's `two-fer` exercise looks like this: ```toml [canonical-tests] [19709124-b82e-4e86-a722-9e5c5ebf3952] description = "no name given" include = true [3451eebd-123f-4256-b667-7b109affce32] description = "a name given" include = true [653611c6-be9f-4935-ab42-978e25fe9a10] description = "another name given" include = false ``` To make it easy to keep the `tests.toml` files up to date, contributors can use the `configlet` application's `sync` command. This command will compare the tests specified in the `tests.toml` files against the tests that are defined in the exercise's canonical data. It then interactively gives the maintainer the option to include or exclude test cases that are currently missing, updating the `tests.toml` file accordingly. ### Scripts We have various `scripts` for you in order to aid with maintaining and contributing to this repository. #### `format` > This has been aliased as a top-level package.json `scripts` script: > > ```shell > corepack yarn format > ``` ```typescript /* * Run this script (from root directory): * * $ corepack yarn format * * This runs `prettier` on all applicable files, FORCES using the same version * as the CI uses to check if the files have been formatted. */ ``` Use this action to format all the files using the correct version of prettier. If you want your editor to do this automatically, make sure you install `corepack yarn install`. #### `lint` > This has been aliased as a top-level package.json `scripts` script: > > ```shell > corepack yarn lint > ``` ```typescript /* * Run this script (from root directory): * * $ corepack yarn lint * * This runs `eslint` on all sample solutions (and test) files */ ``` If the `ASSIGNMENT` environment variable is set, only _that_ exercise is tested. For example, if you only want to lint `two-fer`, you may, depending on your environment use: ```shell ASSIGNMENT=practice/two-fer corepack yarn lint ``` #### `test` > This has been aliased as a top-level package.json `scripts` script: > > ```shell > corepack yarn test > ``` ```typescript /** * Run this script (from root directory): * * $ corepack yarn test * * This runs `jest` tests for all sample solutions */ ``` If the `ASSIGNMENT` environment variable is set, only _that_ exercise is tested. For example, if you only want to test the `example.js` for `two-fer`, you may, depending on your environment, use: ```shell ASSIGNMENT=practice/two-fer corepack yarn test ``` #### `sync` > This has been aliased as a top-level package.json `scripts` script: > > ```shell > corepack yarn sync > ``` ```typescript /** * Run this script (from root directory): * * $ corepack yarn sync * * This script is used to propagate any change to root package.json to * all exercises and keep them in sync. * There is a CI step which checks that package.json in root & exercises match * (see checksum script for more info). */ ``` If the `ASSIGNMENT` environment variable is set, only _that_ exercise is tested. For example, if you only want to sync the files for `two-fer`, you may, depending on your environment, use: ```shell ASSIGNMENT=practice/two-fer corepack yarn sync ``` #### `checksum` ```typescript /* * Run this script (from root directory): * * $ corepack yarn node scripts/checksum.mjs * * This will check root `package.json` matches each exercise's `package.json`. * But the catch is there are some dependencies used for build but not served to end users * We skip those dependencies while performing checksum. * See `SKIP_PACKAGES_FOR_CHECKSUM` in helpers.js for list of skipped packages. */ ``` #### `ci-check` > This has been aliased as a top-level package.json `scripts` script: > > ```shell > corepack yarn ci:check > ``` ```typescript /** * Run this script (from root directory): * * $ corepack yarn ci:check * * This will run following checks: * * 1. Check config in all exercises matches * 2. Checks stubs exist * 3. Run eslint to check code-style */ ``` Run this script to check stubs, configuration integrity and lint the code. #### `ci` > This has been aliased as a top-level package.json `scripts` script: > > ```shell > corepack yarn ci > ``` This script is _almost_ the same as `test`. You may use them interchangeably at moment of writing ```typescript /** * Run this script (from root directory): * * $ corepack yarn ci * * This will run following checks: * * 1. Find the exercises * 2. Run tests against sample solutions */ ``` Run this script to test all exercises. #### `name-check` ```typescript /** * Run this script (from root directory): * * $ corepack yarn node scripts/name-check.mjs * * This will run following checks: * * 1. Package name is of the format "@exercism/typescript-" * * This script also allows fixing these names: * * $ corepack yarn node scripts/name-check.mjs --fix */ ``` Run this script to check if package name in package.json of exercises is in expected format. Run this script with the `--fix` flag to automatically fix the names. #### `name-uniq` ```typescript /** * Run this script (from root directory): * * $ corepack yarn node scripts/name-uniq.mjs * * This will run following checks: * * 1. All exercises have unique package names in their package.json files. */ ``` Run this script to check if there is any duplicate package name. [configlet]: https://github.com/exercism/docs/blob/master/language-tracks/configuration/configlet.md [bin-fetch-configlet]: https://github.com/exercism/javascript/blob/master/bin/fetch-configlet [web-exercism]: https://exercism.org [file-config]: https://github.com/exercism/javascript/blob/master/config.json [file-docs]: https://github.com/exercism/javascript/blob/master/docs [issue-open]: https://github.com/exercism/javascript/issues [issue-discussion]: https://github.com/exercism/javascript/labels/discussion%20%3Aspeech_balloon%3A [issue-new-exercise]: https://github.com/exercism/javascript/issues?q=is%3Aopen+is%3Aissue+label%3A%22%3Asparkles%3A+new+exercise%22 [list-of-exercises]: https://tracks.exercism.org/typescript/master/unimplemented [contributing-generic]: https://github.com/exercism/docs/tree/master/contributing-to-language-tracks [contributing-javascript]: https://github.com/exercism/javascript/blob/master/CONTRIBUTING.md [contributing-javascript-analyzer]: https://github.com/exercism/javascript-analyzer/blob/master/CONTRIBUTING.md [contributing-javascript-test-runner]: https://github.com/exercism/javascript-test-runner [contributing-typescript]: https://github.com/exercism/typescript/blob/master/CONTRIBUTING.md [contributing-typescript-analyzer]: https://github.com/exercism/typescript-analyzer/blob/master/CONTRIBUTING.md [contributing-typescript-test-runner]: https://github.com/exercism/typescript-test-runner [contributing-website-copy]: https://github.com/exercism/website-copy#contributing [doc-readme]: https://github.com/exercism/docs/blob/master/language-tracks/exercises/anatomy/readmes.md [problem-specifications]: https://github.com/exercism/problem-specifications [coc]: https://exercism.org/code-of-conduct [mentor-notes]: https://github.com/exercism/website-copy/tree/master/tracks/javascript/exercises ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Exercism Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Exercism TypeScript Track [![configlet](https://github.com/exercism/typescript/actions/workflows/configlet.yml/badge.svg)](https://github.com/exercism/typescript/actions/workflows/configlet.yml) [![typescript / ci](https://github.com/exercism/typescript/workflows/typescript%20/%20main/badge.svg)](https://github.com/exercism/typescript/actions?query=workflow%3A%22typescript+%2F+main%22) **Exercism exercises in TypeScript** This is the TypeScript track, one of the many tracks on [exercism][web-exercism]. It holds all the _exercises_ that are currently implemented and available for students to complete. The track consists of various **core** exercises - the ones a student _must_ complete - and each **core** exercise may unlock various _side_ exercises. You can find this in the [`config.json`][file-config]. > In version 3 of exercism, the above no longer holds. See the [`exercism/v3`][git-v3] repository for more information. ## Tools See [CONTRIBUTING.md][file-contributing] for a list of requirements to contribute to this track. It also has a list of tools you can use, of which the `test` tool is one of them. ## Running the test suite This runs `jest` tests for all sample solutions. This _does not_ use the regular way to run `jest`, because the example solution files must be renamed to be imported correctly into the test files. ```shell corepack yarn test ``` If the `ASSIGNMENT` environment variable is set, only _that_ exercise is tested. For example, if you only want to test the `two-fer.example.ts` for `two-fer`, you may, depending on your environment, use: ```shell ASSIGNMENT=practice/two-fer corepack yarn test ``` > Running on Windows? Depending on your shell, environment variables are set differently. > You can use `cross-env` to normalize this. The following should work across environments: > > ```bash > corepack yarn dlx cross-env ASSIGNMENT=practice/two-fer corepack yarn test > ``` ## Related repositories - [Website Copy][git-website-copy] (Mentor Notes) - [The TypeScript Analyzer][git-typescript-analyzer] - [The TypeScript Representer][git-typescript-representer] - [The TypeScript Test Runner][git-typescript-test-runner] ### Related JavaScript repositories A lot of the improvements made to this track and tooling, is also made to the JavaScript track and tooling and vice-versa. - [The JavaScript track][git-javascript] - [The JavaScript Analyzer][git-javascript-analyzer] (Automated Code Analysis) - [The JavaScript Representer][git-javascript-representer] - [The JavaScript Test Runner][git-javascript-test-runner] [web-exercism]: https://exercism.org [file-config]: https://github.com/exercism/typescript/blob/master/config.json [file-contributing]: https://github.com/exercism/typescript/blob/master/CONTRIBUTING.md [git-javascript]: https://github.com/exercism/javascript [git-javascript-analyzer]: https://github.com/exercism/javascript-analyzer [git-javascript-representer]: https://github.com/exercism/javascript-representer [git-javascript-test-runner]: https://github.com/exercism/javascript-test-runner [git-typescript]: https://github.com/exercism/typescript/ [git-typescript-analyzer]: https://github.com/exercism/typescript-analyzer [git-typescript-representer]: https://github.com/exercism/typescript-representer [git-typescript-test-runner]: https://github.com/exercism/typescript-test-runner [git-v3]: https://github.com/exercism/v3 [git-website-copy]: https://github.com/exercism/website-copy ================================================ FILE: TAGS.md ================================================ # Tags This document aims to provide reasoning why `config.json` contains the `"tags"` it contains. It uses as a basis the JavaScript's [TAGS.md](https://github.com/exercism/javascript/blob/main/TAGS.md) because anything that JavaScript does can be done with TypeScript, being a transpiled language, and adds/modifies with the perks TypeScript brings on top of it. ## Paradigms - [x] `paradigm/declarative`: mostly popularised by libraries and frameworks such as React, Vue, etc. - [x] `paradigm/functional`: there is a lot of support for functional programming, including various defactor libraries in the ecosystem providing functional programming patterns. - [x] `paradigm/imperative`: It retains JavaScript's script and DOM's manipulation imperative programming, but the language design doesn't encourage it. - [ ] `paradigm/logic`: whilst it is possible to write DSL or patterns that' allow for logic-based programming, JavaScript doesn't inherently support it. - [x] `paradigm/object_oriented`: becomes a main feature of the language compared to JavaScript, along with static type checking and a rich type system. ## Typing - [x] `typing/static`: Will check types at compile time for any part of the code which has its types stated. - [x] `typing/dynamic`: At runtime all TypeScript's type information is stripped. Runtime type checking can also be manually achieved, as well. - [x] `typing/strong`: TypeScript adds a rich type system, that is checked at compile time if used. - [x] `typing/weak`: TypeScript allows for explicit (strong) typing, inferred (weak) typing, deferred (computed / conditional) typing, opt-out (using `any`) of typing, and forced run-time (using `unknown`) typing. ## Execution mode - [x] `execution_mode/compiled`: TypeScript transpilation (transpilation via [`tsc`](https://www.typescriptlang.org/), or stripping types and then compiling using [`@babel/preset-typescript`](https://babeljs.io/docs/en/babel-preset-typescript)) compiles the code to a certain version of JavaScript. This includes in-memory compilation using tools such as [`ts-node`](https://github.com/TypeStrong/ts-node), however due to the in-line nature and REPL like support, it might be considered interpreted. - [x] `execution_mode/interpreted`: TypeScript _can_ be interpreted Just-In-Time by stripping types, for example using [`@babel/preset-typescript`](https://babeljs.io/docs/en/babel-preset-typescript). ## Platform - [x] `platform/windows`: popularised by Electron and Node - [x] `platform/mac`: popularised by Electron and Node - [x] `platform/linux`: popularised by Electron and Node - [x] `platform/ios`: popularised by PhoneGap/Cordova, Ionic, React-Native - [x] `platform/android`: popularised by PhoneGap/Cordova, Ionic, React-Native - [x] `platform/web`: used in frontend frameworks like Angular ## Runtime - [ ] `runtime/standalone_executable`: it doesn't. Any executable that exists packages Node, or requires Node or a Browser. - [x] `runtime/language_specific`: it runs on Node - [ ] `runtime/clr`: it doesn't - [ ] `runtime/jvm`: it doesn't - [ ] `runtime/beam`: it doesn't - [x] `runtime/wasmtime`: Just like JavaScript, it doesn't natively, but JavaScript can compile to WASM, and thus then run on wastime. It's a bit of a cheat, but probably what people will search for. ## Used for - [x] `used_for/artificial_intelligence`: popularised by TensorFlow - [x] `used_for/backends`: popularised by Express - [x] `used_for/cross_platform_development`: popularised by PhoneGap/Cordova, Ionic, React-Native, Electron and more - [ ] `used_for/embedded_systems`: It's possible to run the transpiled JavaScript on microcontrollers and IoT platforms, but the low-end nature makes it not a viable, professional, solution. Therefore this is not included. - [ ] `used_for/financial_systems`: Although it improves in the type safeness department, TypeScript being still JavaScript makes it too slow for fintech. - [x] `used_for/frontends`: Most TypeScript usage is with Frontend frameworks. - [x] `used_for/games`: probably one of the most popular replacements for browser-based games. - [x] `used_for/guis`: same reason as frontends, which is more and more interesting as libraries such as React can now also render to less common/expected displays, such as terminals (and thus be used to build GUIs), not requiring CSS or other ways to provide styling. - [x] `used_for/mobile`: yep, see platform - [ ] `used_for/robotics`: it's possible. Things like Johnny-Five help a lot. However, it's not a _go to_ language to provide robotics programming, so it's not included. - [ ] `used_for/scientific_calculations`: possible, and not uncommon especially for _visualisation_ (for example d3), but not a _go to_ language to do scientific calculations, so it's not included. - [ ] `used_for/scripts`: You could still transpile your TypeScript files, but is usually not worth the hassle, so is not used for scripting at all compared to JavaScript - [x] `used_for/web_development`: yes ================================================ FILE: babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: bin/check-formatting.sh ================================================ #!/usr/bin/env bash set -uo pipefail if [ -z "${EXERCISM_PRETTIER_VERSION:-}" ]; then echo "[format] pulling prettier version from yarn.lock using sed" EXERCISM_PRETTIER_VERSION="$(corepack yarn info prettier --json --name-only | sed -n -e 's/^"prettier@npm://' -e 's/"//p')" echo "[format] expected version is now ${EXERCISM_PRETTIER_VERSION:-}" fi if [ -z "${EXERCISM_PRETTIER_VERSION:-}" ]; then echo "Version could not be pulled using sed" >&2 echo "[format] pulling prettier version from yarn.lock using grep" EXERCISM_PRETTIER_VERSION="$(corepack yarn info prettier --json --name-only | grep -Po '"prettier@npm:\K[^"]+')" echo "[format] expected version is now ${EXERCISM_PRETTIER_VERSION:-}" fi if [ -z "${EXERCISM_PRETTIER_VERSION:-}" ]; then echo "Version could not be pulled using grep or sed" >&2 echo "" echo "---------------------------------------------------" echo "This script requires the EXERCISM_PRETTIER_VERSION variable to work." echo "Please see https://exercism.org/docs/building/markdown/style-guide for guidance." echo "---------------------------------------------------" echo "$(corepack yarn -v)" echo "" echo "This is what yarn why reports:" echo "$ corepack yarn why prettier" echo "$(corepack yarn why prettier)" echo "" echo "And corepack yarn info reports the following:" echo "$ corepack yarn info prettier --name-only" echo "$(corepack yarn info prettier --name-only)" echo "" echo "This is the version that can be extracted using grep:" echo "$ corepack yarn info prettier --json --name-only | grep -Po '"prettier@npm:\K[^"]+'" echo "└─ $(corepack yarn info prettier --json --name-only | grep -Po '"prettier@npm:\K[^"]+')" echo "" echo "This is the version that can be extracted using sed:" echo "$ corepack yarn info prettier --json --name-only | sed -n -e 's/^"prettier@npm://' -e 's/"//p'" echo "└─ $(corepack yarn info prettier --json --name-only | sed -n -e 's/^"prettier@npm://' -e 's/"//p')" echo "" echo "These files are found in the repo root:" echo "$(ls -p | grep -v /)" echo "---------------------------------------------------" exit 1 else echo "[format] running with prettier@$EXERCISM_PRETTIER_VERSION" fi corepack yarn dlx "prettier@$EXERCISM_PRETTIER_VERSION" --check "**/*.{js,jsx,ts,tsx,css,sass,scss,html,json,md,yml}" ================================================ FILE: bin/fetch-configlet ================================================ #!/usr/bin/env bash # This file is a copy of the # https://github.com/exercism/configlet/blob/main/scripts/fetch-configlet file. # Please submit bugfixes/improvements to the above file to ensure that all tracks benefit from the changes. set -eo pipefail curlopts=( --silent --show-error --fail --location --retry 3 ) if [[ -n "${GITHUB_TOKEN}" ]]; then curlopts+=(--header "authorization: Bearer ${GITHUB_TOKEN}") fi get_download_url() { local os="$1" local ext="$2" local latest='https://api.github.com/repos/exercism/configlet/releases/latest' local arch case "$(uname -m)" in aarch64|arm64) arch='arm64' ;; x86_64) arch='x86-64' ;; *686*) arch='i386' ;; *386*) arch='i386' ;; *) arch='x86-64' ;; esac local suffix="${os}_${arch}.${ext}" curl "${curlopts[@]}" --header 'Accept: application/vnd.github.v3+json' "${latest}" | grep "\"browser_download_url\": \".*/download/.*/configlet.*${suffix}\"$" | cut -d'"' -f4 } main() { local output_dir if [[ -d ./bin ]]; then output_dir="./bin" elif [[ $PWD == */bin ]]; then output_dir="$PWD" else echo "Error: no ./bin directory found. This script should be ran from a repo root." >&2 return 1 fi local os case "$(uname -s)" in Darwin*) os='macos' ;; Linux*) os='linux' ;; Windows*) os='windows' ;; MINGW*) os='windows' ;; MSYS_NT-*) os='windows' ;; *) os='linux' ;; esac local ext case "${os}" in windows) ext='zip' ;; *) ext='tar.gz' ;; esac echo "Fetching configlet..." >&2 local download_url download_url="$(get_download_url "${os}" "${ext}")" local output_path="${output_dir}/latest-configlet.${ext}" curl "${curlopts[@]}" --output "${output_path}" "${download_url}" case "${ext}" in zip) unzip "${output_path}" -d "${output_dir}" ;; *) tar xzf "${output_path}" -C "${output_dir}" ;; esac rm -f "${output_path}" local executable_ext case "${os}" in windows) executable_ext='.exe' ;; *) executable_ext='' ;; esac local configlet_path="${output_dir}/configlet${executable_ext}" local configlet_version configlet_version="$(${configlet_path} --version)" echo "Downloaded configlet ${configlet_version} to ${configlet_path}" } main ================================================ FILE: bin/fetch-configlet.ps1 ================================================ # This file is a copy of the # https://github.com/exercism/configlet/blob/main/scripts/fetch-configlet.ps1 file. # Please submit bugfixes/improvements to the above file to ensure that all tracks # benefit from the changes. $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" $requestOpts = @{ Headers = If ($env:GITHUB_TOKEN) { @{ Authorization = "Bearer ${env:GITHUB_TOKEN}" } } Else { @{ } } MaximumRetryCount = 3 RetryIntervalSec = 1 } Function Get-DownloadUrl { $arch = If ([Environment]::Is64BitOperatingSystem) { "x86-64" } Else { "i386" } $latestUrl = "https://api.github.com/repos/exercism/configlet/releases/latest" Invoke-RestMethod -Uri $latestUrl -PreserveAuthorizationOnRedirect @requestOpts ` | Select-Object -ExpandProperty assets ` | Where-Object { $_.name -match "^configlet_.+_windows_${arch}.zip$" } ` | Select-Object -ExpandProperty browser_download_url -First 1 } $outputDirectory = "bin" if (!(Test-Path -Path $outputDirectory)) { Write-Output "Error: no ./bin directory found. This script should be ran from a repo root." exit 1 } Write-Output "Fetching configlet..." $downloadUrl = Get-DownloadUrl $outputFileName = "configlet.zip" $outputPath = Join-Path -Path $outputDirectory -ChildPath $outputFileName Invoke-WebRequest -Uri $downloadUrl -OutFile $outputPath @requestOpts $configletPath = Join-Path -Path $outputDirectory -ChildPath "configlet.exe" if (Test-Path -Path $configletPath) { Remove-Item -Path $configletPath } [System.IO.Compression.ZipFile]::ExtractToDirectory($outputPath, $outputDirectory) Remove-Item -Path $outputPath $configletVersion = (Select-String -Pattern "/releases/download/(.+?)/" -InputObject $downloadUrl -AllMatches).Matches.Groups[1].Value Write-Output "Downloaded configlet ${configletVersion} to ${configletPath}" ================================================ FILE: bin/format.sh ================================================ #!/usr/bin/env bash set -uo pipefail if [ -z "${EXERCISM_PRETTIER_VERSION:-}" ]; then echo "[format] pulling prettier version from yarn.lock using sed" EXERCISM_PRETTIER_VERSION="$(corepack yarn info prettier --json --name-only | sed -n -e 's/^"prettier@npm://' -e 's/"//p')" echo "[format] expected version is now ${EXERCISM_PRETTIER_VERSION:-}" fi if [ -z "${EXERCISM_PRETTIER_VERSION:-}" ]; then echo "Version could not be pulled using sed" >&2 echo "[format] pulling prettier version from yarn.lock using grep" EXERCISM_PRETTIER_VERSION="$(corepack yarn info prettier --json --name-only | grep -Po '"prettier@npm:\K[^"]+')" echo "[format] expected version is now ${EXERCISM_PRETTIER_VERSION:-}" fi if [ -z "${EXERCISM_PRETTIER_VERSION:-}" ]; then echo "Version could not be pulled using grep or sed" >&2 echo "" echo "---------------------------------------------------" echo "This script requires the EXERCISM_PRETTIER_VERSION variable to work." echo "Please see https://exercism.org/docs/building/markdown/style-guide for guidance." echo "---------------------------------------------------" echo "$(corepack yarn -v)" echo "" echo "This is what corepack yarn why reports:" echo "$ corepack yarn why prettier" echo "$(corepack yarn why prettier)" echo "" echo "And corepack yarn info reports the following:" echo "$ corepack yarn info prettier --name-only" echo "$(corepack yarn info prettier --name-only)" echo "" echo "This is the version that can be extracted using grep:" echo "$ corepack yarn info prettier --json --name-only | grep -Po '"prettier@npm:\K[^"]+'" echo "└─ $(corepack yarn info prettier --json --name-only | grep -Po '"prettier@npm:\K[^"]+')" echo "" echo "This is the version that can be extracted using sed:" echo "$ corepack yarn info prettier --json --name-only | sed -n -e 's/^"prettier@npm://' -e 's/"//p'" echo "└─ $(corepack yarn info prettier --json --name-only | sed -n -e 's/^"prettier@npm://' -e 's/"//p')" echo "" echo "These files are found in the repo root:" echo "$(ls -p | grep -v /)" echo "---------------------------------------------------" exit 1 else echo "[format] running with prettier@$EXERCISM_PRETTIER_VERSION" fi corepack yarn dlx "prettier@$EXERCISM_PRETTIER_VERSION" --write "**/*.{js,jsx,ts,tsx,css,sass,scss,html,json,md,yml}" ================================================ FILE: bin/generate-config-tree ================================================ #!/usr/bin/env node const { exercises } = require('../config.json') const TAG_CORE = '__core' const TAG_BONUS = '__bonus' // node inter-opt exports exports.TAG_CORE = TAG_CORE exports.TAG_BONUS = TAG_BONUS exports.tree = exercises.reduce((result, exercise) => { const tag = exercise.slug const item = { slug: tag, difficulty: exercise.difficulty, } if (exercise.core) { const current = result[TAG_CORE] || [] if (result[tag]) { console.warn(`${tag} is not ordered correctly in config.json`) } return { ...result, __core: current.concat([item]), [tag]: result[tag] || [] } } const parent = exercise.unlocked_by || TAG_BONUS const current = result[parent] || [] return { ...result, [parent]: current.concat([item]) } }, {}) ================================================ FILE: bin/md5-hash ================================================ #!/bin/bash # # Calculates the MD5 hash of a given file. It uses hashing utilities powered by # operating systems, but wraps them into a consistent interface. OS=$( case $(uname) in (Darwin*) echo "mac";; (Linux*) echo "linux";; # TODO: implement MD5 hashing on Windows # (Windows*) echo "windows";; (*) echo "linux";; esac ) case $OS in mac ) md5 -q $@;; linux ) md5sum $@ | sed -E 's/([a-z0-9]{32}).+$/\1/';; esac ================================================ FILE: bin/print-config-tree ================================================ #!/usr/bin/env node const actions = require('./generate-config-tree') const { tree, TAG_BONUS, TAG_CORE } = actions const { [TAG_BONUS]: __bonus, [TAG_CORE]: __core, ...track } = tree function printLn(line) { process.stdout.write(`${line}\n`) } function printList(items) { items.forEach(item => { printLn(`- ${item.slug} (${item.difficulty})`) }) } printLn('Core (matches config.json) of this track:') printList(__core) printLn('\n') printLn('core') printLn('----') Object.keys(track).forEach(slug => { printLn(`├─ ${slug}`) track[slug].forEach((side, index, self) => { junction = index === self.length - 1 ? '└─' : '├─' printLn(`│ ${junction} ${side.slug} (${side.difficulty})`) }) printLn('│') }) printLn('bonus') printLn('----') printList(__bonus) ================================================ FILE: common/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: common/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: common/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: common/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: common/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: common/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: common/keep.ts ================================================ // This exists to keep tsconfig.json in this folder happy export {} ================================================ FILE: common/package.json ================================================ { "name": "@exercism/typescript", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "author": "Katrina Owen", "contributors": [ "Derk-Jan Karrenbeld (https://derk-jan.com)" ], "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: common/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: common/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: concepts/.keep ================================================ ================================================ FILE: concepts/basics/.meta/config.json ================================================ { "blurb": "Basics contains the minimal knowledge required to get started in TypeScript: function declarations, variable assignment, and exposing entities.", "authors": ["SleeplessByte"], "contributors": ["junedev"] } ================================================ FILE: concepts/basics/about.md ================================================ # About TypeScript is JavaScript with syntax for types, making it a a strongly typed programming language, supporting object-oriented, imperative, and declarative (e.g. functional programming) styles, giving you better tooling at any scale. It has a few [primitives][mdn-primitive], and everything else is considered an object. While JavaScript is the most well-known as the scripting language for Web pages, many non-browser environments also use it, such as Node.js. The language is actively being developed; and because of its multi-paradigm property, allows for many styles of programming. TypeScript builds on-top of this and is also actively developed. In some rankings from 2023 it is more popular than JavaScript in daily use. Because [you cannot learn TypeScript without learning JavaScript][handbook-js-or-ts], some of the content in this Track is focused on teaching JavaScript concepts and some of the concepts focus on TypeScript specific features only. ## (Re-)Assignment There are a few primary ways to assign values to names in TypeScript - using variables or constants. On Exercism, variables are always written in [camelCase][wiki-camel-case]; constants are written in [SCREAMING_SNAKE_CASE][wiki-snake-case]. There is no official guide to follow, and various companies and organizations have various style guides. _Feel free to write variables any way you like_. The upside from writing them the way the exercises are prepared is that they'll be highlighted differently in the web interface and most IDEs. Variables in TypeScript can be defined using the [`const`][mdn-const], [`let`][mdn-let] or [`var`][mdn-var] keyword. A variable can reference different values over its lifetime when using `let` or `var`. For example, `myFirstVariable` can be defined and redefined many times using the assignment operator `=`: ```typescript let myFirstVariable = 1 myFirstVariable = 'Some string' myFirstVariable = new SomeComplexClass() ``` In contrast to `let` and `var`, variables that are defined with `const` can only be assigned once. This is used to define constants in TypeScript. ```typescript const MY_FIRST_CONSTANT = 10 // Can not be re-assigned. MY_FIRST_CONSTANT = 20 // => TypeError: Assignment to constant variable. ``` Because TypeScript can detect this statically, the TypeScript compiler will also yield an error: ```typescript // ^? Cannot assign to 'MY_FIRST_CONSTANT' because it is a constant.(2588) ``` This means you don't need to run the code to detect the `TypeError`. ~~~~exercism/note 💡 In a later Learning Exercise, the difference between _constant_ assignment / binding and _constant_ value is explored and explained. ~~~~ ## Type inference Without diving too deep into the subject of [type inference][handbook-type-inference], you should be aware that an assigned variable usually as an inferred type, even without a type annotation. ```typescript const MY_FIRST_CONSTANT = 10 // ^? const MY_FIRST_CONSTANT: number ``` This type is then enforced throughout the code. This also means that where the following code is valid JavaScript: ```javascript let myFirstVariable = 1 myFirstVariable = 'Some string' ``` It complains when using TypeScript: ```typescript let myFirstVariable = 1 myFirstVariable = 'Some string' // ^? Type 'string' is not assignable to type 'number'.(2322) ``` This feature ensures type-safety even when type annotations are not used. ### Constant Assignment The `const` keyword is mentioned _both_ for variables and constants. Another concept often mentioned around constants is [(im)-mutability][wiki-mutability]. The `const` keyword only makes the _binding_ immutable, that is, you can only assign a value to a `const` variable once. In TypeScript, only [primitive][mdn-primitive] values are immutable. However, [non-primitive][mdn-primitive] values can still be mutated. ```typescript const MY_MUTABLE_VALUE_CONSTANT = { food: 'apple' } // This is possible MY_MUTABLE_VALUE_CONSTANT.food = 'pear' MY_MUTABLE_VALUE_CONSTANT // => { food: "pear" } ``` ### Constant Value (Immutability) As a rule, on Exercism, and many other organizations and project style guides, don't mutate values that look like `const SCREAMING_SNAKE_CASE`. Technically the values _can_ be changed, but for clarity and expectation management on Exercism this is discouraged. When this _must_ be enforced, use [`Object.freeze(value)`][mdn-object-freeze]. Where possible, the TypeScript keyword `readonly`, `as const`, or `Readonly` generic type may be used to enforce immutability statically. You will learn more about this subject later. ```typescript const MY_VALUE_CONSTANT = Object.freeze({ food: 'apple' }) MY_VALUE_CONSTANT.food = 'pear' // ^? Cannot assign to 'food' because it is a read-only property.(2540) MY_VALUE_CONSTANT // => { food: "apple" } ``` In the wild, it's unlikely to see `Object.freeze` all over a code base, but the rule to not mutate a `SCREAMING_SNAKE_CASE` value ever, is a good rule; often enforced using automated analysis such as a linter. ## Function Declarations In TypeScript, units of functionality are encapsulated in _functions_, usually grouping functions together in the same file if they belong together. These functions can take parameters (arguments), and can _return_ a value using the `return` keyword. Functions are invoked using `()` syntax. ```typescript function add(num1: number, num2: number): number { return num1 + num2 } add(1, 3) // => 4 ``` Function parameters should usually be annotated with a type using a colon (`:`) followed by the type. Function return values can be annotated after closing the parameter list using a colon (`:`) followed by the type. If a function does not have a type annotation for its return value, the type will be inferred. ```typescript function add(num1: number, num2: number) { return num1 + num2 } add(1, 3) // ^? function add(num1: number, num2: number): number ``` Here the return type was inferred because TypeScript knows that the result of `number + number` must always be `number`. ~~~~exercism/note 💡 In TypeScript there are _many_ different ways to declare a function. These other ways look different than using the `function` keyword. The track tries to gradually introduce them, but if you already know about them, feel free to use any of them. In most cases, using one or the other isn't better or worse. ~~~~ ## Type Annotations As shown in the function declaration for `add`, the parameters have an explicit type annotation `: number`. Variable declarations, class properties, function declarations and more all support type annotations. Both explicit type annotation as inferred types are enforced by the type-checker. ```typescript add('foo', 3) // ^? Argument of type 'string' is not assignable to parameter of type 'number'.(2345) ``` If TypeScript does not find an explicit type annotation and cannot infer the type, it will assign the `any` type, which [you should not use][handbook-dont-use-any]. Later you will learn about the `unknown` type as a good alternative. ## Export and Import The `export` and `import` keywords are powerful tools that turn a regular TypeScript file into a [TypeScript module][mdn-module]. Apart from allowing code to selectively expose components, such as functions, classes, variables and constants, it also enables a whole range of other features, such as: - [Renaming exports and imports][mdn-renaming-modules], which allows you to avoid naming conflicts, - [Dynamic Imports][mdn-dynamic-imports], which loads code on demand, - [Tree shaking][blog-tree-shaking], which reduces the size of the final code by eliminating side-effect free modules and even contents of modules _that are not used_, - Exporting [_live bindings_][blog-live-bindings], which allows you to export a value that mutates everywhere it's imported if the original value mutates. A concrete example is how the tests work on Exercism's TypeScript Track. Each exercise has at least one implementation file, for example `lasagna.ts`, and each exercise has at least one test file, for example `lasagna.test.ts`. The implementation file uses `export` to expose the public API and the test file uses `import` to access these, which is how it can test the implementation's outcomes. ```typescript // file.js export const MY_VALUE = 10 export function add(num1, num2) { return num1 + num2 } // file.spec.js import { MY_VALUE, add } from './file.js' add(MY_VALUE, 5) // => 15 ``` ~~~~exercism/advanced Because the TypeScript compiler does _not rewrite import paths_, the imports should be written using the `.js` extension (as that's what it will become after transpilation). However, the option `allowImportingTsExtensions` is on because we have a process that rewrites the paths. This allows importing from `.ts` (as well as `.js`). In older code you will find imports _without file extension_. ~~~~ [blog-live-bindings]: https://2ality.com/2015/07/es6-module-exports.html#es6-modules-export-immutable-bindings [blog-tree-shaking]: https://bitsofco.de/what-is-tree-shaking/ [mdn-const]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const [mdn-dynamic-imports]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Imports [mdn-let]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let [mdn-module]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules [mdn-object-freeze]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze [mdn-primitive]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive [mdn-renaming-modules]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#Renaming_imports_and_exports [mdn-var]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var [handbook-dont-use-any]: https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#any [handbook-js-or-ts]: https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html#learning-javascript-and-typescript [handbook-type-inference]: https://www.typescriptlang.org/docs/handbook/type-inference.html [wiki-mutability]: https://en.wikipedia.org/wiki/Immutable_object [wiki-camel-case]: https://en.wikipedia.org/wiki/Camel_case [wiki-snake-case]: https://en.wikipedia.org/wiki/Snake_case ================================================ FILE: concepts/basics/introduction.md ================================================ # Introduction TypeScript is JavaScript with syntax for types, making it a a strongly typed programming language, supporting object-oriented, imperative, and declarative (e.g. functional programming) styles, giving you better tooling at any scale. Because [you cannot learn TypeScript without learning JavaScript][handbook-js-or-ts], some of the content in this Track is focused on teaching JavaScript concepts and some of the concepts focus on TypeScript specific features only. ## (Re-)Assignment There are a few primary ways to assign values to names in TypeScript - using variables or constants. On Exercism, variables are always written in [camelCase][wiki-camel-case]; constants are written in [SCREAMING_SNAKE_CASE][wiki-snake-case]. There is no official guide to follow, and various companies and organizations have various style guides. _Feel free to write variables any way you like_. The upside from writing them the way the exercises are prepared is that they'll be highlighted differently in the web interface and most IDEs. Variables in TypeScript can be defined using the [`const`][mdn-const], [`let`][mdn-let] or [`var`][mdn-var] keyword. A variable can reference different values over its lifetime when using `let` or `var`. For example, `myFirstVariable` can be defined and redefined many times using the assignment operator `=`: ```typescript let myFirstVariable = 1 myFirstVariable = 0 myFirstVariable = 42 ``` In contrast to `let` and `var`, variables that are defined with `const` can only be assigned once. This is used to define constants in TypeScript. ```typescript const MY_FIRST_CONSTANT = 10 // Can not be re-assigned. MY_FIRST_CONSTANT = 20 // => TypeError: Assignment to constant variable. ``` Because TypeScript can detect this statically, the TypeScript compiler will also yield an error: ```typescript // ^? Cannot assign to 'MY_FIRST_CONSTANT' because it is a constant.(2588) ``` This means you don't need to run the code to detect the `TypeError`. ~~~~exercism/note 💡 In a later Learning Exercise, the difference between _constant_ assignment / binding and _constant_ value is explored and explained. ~~~~ ## Type inference Without diving too deep into the subject of [type inference][handbook-type-inference], you should be aware that an assigned variable usually as an inferred type, even without a type annotation. ```typescript const MY_FIRST_CONSTANT = 10 // ^? const MY_FIRST_CONSTANT: number ``` This type is then enforced throughout the code. This also means that where the following code is valid JavaScript: ```javascript let myFirstVariable = 1 myFirstVariable = 'Some string' ``` It complains when using TypeScript: ```typescript let myFirstVariable = 1 myFirstVariable = 'Some string' // ^? Type 'string' is not assignable to type 'number'.(2322) ``` This feature ensures type-safety even when type annotations are not used. ## Function Declarations In TypeScript, units of functionality are encapsulated in _functions_, usually grouping functions together in the same file if they belong together. These functions can take parameters (arguments), and can _return_ a value using the `return` keyword. Functions are invoked using `()` syntax. ```typescript function add(num1: number, num2: number): number { return num1 + num2 } add(1, 3) // => 4 ``` Function parameters should usually be annotated with a type using a colon (`:`) followed by the type. Function return values can be annotated after closing the parameter list using a colon (`:`) followed by the type. ~~~~exercism/note 💡 In TypeScript there are _many_ different ways to declare a function. These other ways look different than using the `function` keyword. The track tries to gradually introduce them, but if you already know about them, feel free to use any of them. In most cases, using one or the other isn't better or worse. ~~~~ ## Type Annotations As shown in the function declaration for `add`, the parameters have an explicit type annotation `: number`. Variable declarations, class properties, function declarations and more all support type annotations. Both explicit type annotation as inferred types are enforced by the type-checker. ```typescript add('foo', 3) // ^? Argument of type 'string' is not assignable to parameter of type 'number'.(2345) ``` ## Exposing to Other Files To make a `function`, a constant, or a variable available in _other files_, they need to be [exported][mdn-export] using the `export` keyword. Another file may then [import][mdn-import] these using the `import` keyword. This is also known as the module system. A great example is how all the tests work. Each exercise has at least one file, for example `lasagna.ts`, which contains the _implementation_. Additionally, there is at least one other file, for example `lasagna.test.ts`, that contains the _tests_. This file _imports_ the public (i.e. exported) entities to test the implementation: ```typescript // file.ts export const MY_VALUE = 10 export function add(num1, num2) { return num1 + num2 } // file.test.ts import { MY_VALUE, add } from './file.js' add(MY_VALUE, 5) // => 15 ``` [mdn-const]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const [mdn-export]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export [mdn-import]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import [mdn-let]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let [mdn-var]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var [handbook-js-or-ts]: https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html#learning-javascript-and-typescript [handbook-type-inference]: https://www.typescriptlang.org/docs/handbook/type-inference.html [wiki-camel-case]: https://en.wikipedia.org/wiki/Camel_case [wiki-snake-case]: https://en.wikipedia.org/wiki/Snake_case ================================================ FILE: concepts/basics/links.json ================================================ [ { "url": "https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html", "description": "TypeScript for JavaScript Programmers" }, { "url": "https://www.typescriptlang.org/docs/handbook/2/basic-types.html", "description": "TypeScript Handbook: The Basics" }, { "url": "https://www.typescriptlang.org/docs/handbook/type-inference.html", "description": "TypeScript Handbook: Type Inference" }, { "url": "https://developer.mozilla.org/en-US/docs/Glossary/Primitive", "description": "MDN Glossary: JavaScript Primitive" }, { "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze", "description": "MDN: JavaScript Object freezing" }, { "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules", "description": "MDN: JavaScript Module System" }, { "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#Renaming_imports_and_exports", "description": "MDN: Renaming modules when importing or exporting" }, { "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Imports", "description": "MDN: JavaScript Dynamic imports" }, { "url": "https://en.wikipedia.org/wiki/Immutable_object", "description": "About immutable objects" }, { "url": "https://bitsofco.de/what-is-tree-shaking/", "description": "About tree shaking" }, { "url": "https://2ality.com/2015/07/es6-module-exports.html#es6-modules-export-immutable-bindings", "description": "About live bindings (JavaScript exports)" } ] ================================================ FILE: config.json ================================================ { "language": "TypeScript", "slug": "typescript", "active": true, "status": { "concept_exercises": false, "test_runner": true, "representer": true, "analyzer": true }, "blurb": "TypeScript is a language for writing more maintainable JavaScript. TypeScript adds optional types, classes, and modules to JavaScript.", "version": 3, "online_editor": { "indent_style": "space", "indent_size": 2, "highlightjs_language": "typescript" }, "test_runner": { "average_run_time": 8 }, "files": { "solution": [ "%{kebab_slug}.ts" ], "test": [ "%{kebab_slug}.test.ts" ], "example": [ ".meta/proof.ci.ts" ], "exemplar": [ ".meta/exemplar.ts" ] }, "exercises": { "concept": [ { "slug": "lasagna", "name": "lasagna", "uuid": "a0bed44f-b179-4d09-8788-a23d2ace359e", "concepts": [ "basics" ], "prerequisites": [], "status": "wip" } ], "practice": [ { "slug": "hello-world", "name": "Hello World", "uuid": "755f6f85-9ab3-4af6-9e35-dbb9d64be9c5", "practices": [], "prerequisites": [], "difficulty": 1, "topics": [ "optional_values", "strings", "text_formatting" ] }, { "slug": "two-fer", "name": "Two Fer", "uuid": "f66508fe-4ddc-4bbd-b8e6-542d400e57e9", "practices": [], "prerequisites": [], "difficulty": 1, "topics": [ "optional_values", "strings", "text_formatting" ] }, { "slug": "resistor-color", "name": "Resistor Color", "uuid": "c6f41fd5-584c-46ca-b3c5-6a0825446bf4", "practices": [], "prerequisites": [], "difficulty": 1, "topics": [ "arrays", "strings" ] }, { "slug": "resistor-color-duo", "name": "Resistor Color Duo", "uuid": "fd677d24-72eb-46d9-bfa4-8013e96fa71f", "practices": [], "prerequisites": [], "difficulty": 2, "topics": [ "strings", "arrays" ] }, { "slug": "resistor-color-trio", "name": "Resistor Color Trio", "uuid": "9316fc6a-42d2-4b12-85e1-e8abd9ce3ae2", "practices": [], "prerequisites": [], "difficulty": 3, "topics": [ "strings", "arrays" ] }, { "slug": "leap", "name": "Leap", "uuid": "fb80f76c-42da-4f62-9f0f-8c85d984908b", "practices": [], "prerequisites": [], "difficulty": 1, "topics": [ "booleans", "integers", "logic" ] }, { "slug": "line-up", "name": "Line Up", "uuid": "614dbbe5-56dc-47ea-86e6-ee64497f2cac", "practices": [], "prerequisites": [], "difficulty": 1, "topics": [ "numbers", "strings" ] }, { "slug": "rna-transcription", "name": "RNA Transcription", "uuid": "b4db381f-1c99-44c6-948c-c8892d77823e", "practices": [], "prerequisites": [], "difficulty": 2, "topics": [ "strings", "transforming" ] }, { "slug": "space-age", "name": "Space Age", "uuid": "8fe1e0ef-068e-4a53-a576-35be59f8152f", "practices": [], "prerequisites": [], "difficulty": 2, "topics": [ "classes", "floating_point_numbers" ] }, { "slug": "dnd-character", "name": "D&D Character", "uuid": "6f34eb4d-4028-4f89-9965-c9a887f416a0", "practices": [], "prerequisites": [], "difficulty": 2, "topics": [ "classes" ] }, { "slug": "darts", "name": "Darts", "uuid": "5697c090-4ab3-4f76-b9d2-a82330a27a91", "practices": [], "prerequisites": [], "difficulty": 3 }, { "slug": "pangram", "name": "Pangram", "uuid": "721d9c74-cc16-4b32-8f0b-ffab75027539", "practices": [], "prerequisites": [], "difficulty": 3, "topics": [ "algorithms", "conditionals", "loops", "lists", "maps", "searching", "strings" ] }, { "slug": "bob", "name": "Bob", "uuid": "3977d4e5-82ca-4801-ae20-6682dda23506", "practices": [], "prerequisites": [], "difficulty": 3, "topics": [ "conditionals", "pattern_recognition", "polymorphism", "regular_expressions", "strings", "unicode" ] }, { "slug": "matrix", "name": "Matrix", "uuid": "167ec8f2-a206-4a7e-999d-7dbfc136a9fe", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "arrays", "conditionals", "loops", "data_structures", "matrices", "text_formatting" ] }, { "slug": "robot-name", "name": "Robot Name", "uuid": "b00dd1af-f89c-4382-ab43-514651de6b20", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "conditionals", "exception_handling", "randomness", "regular_expressions", "sets" ] }, { "slug": "grade-school", "name": "Grade School", "uuid": "32e79fd7-f002-4bdc-b6bd-7a91d8c1af61", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "arrays", "maps", "sorting" ] }, { "slug": "clock", "name": "Clock", "uuid": "c6960d69-7794-4711-8891-9c5a63727112", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "integers", "logic", "strings" ] }, { "slug": "split-second-stopwatch", "name": "Split-Second Stopwatch", "uuid": "bc1fc50f-36ed-4a16-92bd-3001c8cd53d3", "practices": [], "prerequisites": [], "difficulty": 4 }, { "slug": "secret-handshake", "name": "Secret Handshake", "uuid": "ac2ec51b-a2cd-4d8c-99e3-5bd6758b4e7b", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "algorithms", "arrays", "bitwise_operations", "conditionals", "loops", "games" ] }, { "slug": "binary-search", "name": "Binary Search", "uuid": "53584e8d-9b8d-4c0e-8ad8-4c228fcf6bcf", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "algorithms", "arrays", "conditionals", "loops" ] }, { "slug": "linked-list", "name": "Linked List", "uuid": "5b7ab927-6e4d-480f-b005-d23091e579d6", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "algorithms", "arrays", "conditionals", "loops", "data_structures", "lists", "optional_values" ] }, { "slug": "prism", "name": "Prism", "uuid": "ba7d5b31-50ab-482f-9669-718949ccb753", "practices": [], "prerequisites": [], "difficulty": 5 }, { "slug": "rational-numbers", "name": "Rational Numbers", "uuid": "4439ab9d-266b-483b-b5ca-3920c478fd62", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "algorithms", "floating_point_numbers", "math" ] }, { "slug": "atbash-cipher", "name": "Atbash Cipher", "uuid": "04de61fb-9fde-461c-b306-0b4ce52e2169", "practices": [], "prerequisites": [], "difficulty": 6, "topics": [ "algorithms", "arrays", "conditionals", "loops", "text_formatting" ] }, { "slug": "simple-cipher", "name": "Simple Cipher", "uuid": "3ea9d03e-6d06-48e5-a4d3-6e1bb9367c2f", "practices": [], "prerequisites": [], "difficulty": 7, "topics": [ "algorithms", "conditionals", "loops", "randomness", "strings", "text_formatting", "transforming" ] }, { "slug": "wordy", "name": "Wordy", "uuid": "269577e5-e782-4264-9ad9-9ad4b8bc0aab", "practices": [], "prerequisites": [], "difficulty": 7, "topics": [ "conditionals", "loops", "exception_handling", "parsing", "pattern_recognition", "regular_expressions", "strings" ] }, { "slug": "list-ops", "name": "List Ops", "uuid": "26787ded-0aa2-46f6-88dd-13a00cce9236", "practices": [], "prerequisites": [], "difficulty": 8, "topics": [ "data_structures", "lists", "recursion" ] }, { "slug": "word-count", "name": "Word Count", "uuid": "5e5c48fb-91bb-495a-97f6-ec8642739a0a", "practices": [], "prerequisites": [], "difficulty": 1, "topics": [ "loops", "lists", "regular_expressions", "strings", "unicode" ] }, { "slug": "difference-of-squares", "name": "Difference of Squares", "uuid": "3f649490-dc7d-4a77-a2a0-2ae71ae834a9", "practices": [], "prerequisites": [], "difficulty": 3, "topics": [ "algorithms", "loops", "integers", "math" ] }, { "slug": "gigasecond", "name": "Gigasecond", "uuid": "9dfabc5c-d2a5-4896-9fc2-2b25b9a5f62f", "practices": [], "prerequisites": [], "difficulty": 3, "topics": [ "time" ] }, { "slug": "square-root", "name": "Square Root", "uuid": "bf4a0802-334b-4ed7-9ade-a3ef5fa6878e", "practices": [], "prerequisites": [], "difficulty": 2 }, { "slug": "reverse-string", "name": "Reverse String", "uuid": "86d5a60a-fc44-443d-bc00-5c6265e736c4", "practices": [], "prerequisites": [], "difficulty": 2, "topics": [ "loops", "strings" ] }, { "slug": "triangle", "name": "Triangle", "uuid": "0eb37e93-d191-4451-bd77-cd3681c91d94", "practices": [], "prerequisites": [], "difficulty": 3, "topics": [ "conditionals", "loops", "exception_handling", "integers" ] }, { "slug": "collatz-conjecture", "name": "Collatz Conjecture", "uuid": "a701b4ca-55ea-4aa9-a607-2fe8a9828d9c", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "conditionals", "exception_handling", "integers", "math", "recursion" ] }, { "slug": "etl", "name": "ETL", "uuid": "ab027a47-d483-4011-aac4-564a6284e5b8", "practices": [], "prerequisites": [], "difficulty": 2, "topics": [ "loops", "integers", "maps", "transforming" ] }, { "slug": "protein-translation", "name": "Protein Translation", "uuid": "e18a436f-5d7b-4372-9f74-15e8e9a52d63", "practices": [], "prerequisites": [], "difficulty": 2, "topics": [ "arrays", "conditionals", "loops", "strings" ] }, { "slug": "raindrops", "name": "Raindrops", "uuid": "c8677318-ba6c-4e8c-83ec-513cc6530e7f", "practices": [], "prerequisites": [], "difficulty": 2, "topics": [ "conditionals", "integers", "strings", "transforming" ] }, { "slug": "hamming", "name": "Hamming", "uuid": "18204e23-fca8-44dc-8d5c-abe66b87c640", "practices": [], "prerequisites": [], "difficulty": 3, "topics": [ "conditionals", "loops", "equality", "strings" ] }, { "slug": "nucleotide-count", "name": "Nucleotide Count", "uuid": "ef16d487-524e-4c5a-8311-9162babba181", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "arrays", "conditionals", "loops", "strings", "text_formatting" ] }, { "slug": "camicia", "name": "Camicia", "uuid": "5ee5b5e4-bc56-4be5-b384-225b2b339444", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "conditionals", "loops", "strings" ] }, { "slug": "scrabble-score", "name": "Scrabble Score", "uuid": "7c60f7c5-2922-4ce4-acbe-51c5ab654b4d", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "conditionals", "loops", "maps", "strings" ] }, { "slug": "allergies", "name": "Allergies", "uuid": "9389fcb2-841a-42b6-8ece-f5a63f750273", "practices": [], "prerequisites": [], "difficulty": 6, "topics": [ "arrays", "bitwise_operations", "conditionals", "loops" ] }, { "slug": "perfect-numbers", "name": "Perfect Numbers", "uuid": "53cc064f-35d2-4a0e-a915-6a25130252c3", "practices": [], "prerequisites": [], "difficulty": 3, "topics": [ "arrays", "conditionals", "loops", "integers", "math" ] }, { "slug": "complex-numbers", "name": "Complex Numbers", "uuid": "59d842b4-a804-4cd5-a388-983a22a70c9e", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "arrays", "conditionals", "loops", "math" ] }, { "slug": "luhn", "name": "Luhn", "uuid": "249365fb-145c-40e3-90ed-11f3f74a9df1", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "conditionals", "loops", "integers", "strings" ] }, { "slug": "grains", "name": "Grains", "uuid": "5a04b3f8-99b6-4aec-8afd-0d54930e4aff", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "conditionals", "loops", "integers" ] }, { "slug": "pythagorean-triplet", "name": "Pythagorean Triplet", "uuid": "85d74be3-8a54-4355-9ef8-0f13ed2a7683", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "algorithms", "conditionals", "loops", "integers", "math" ] }, { "slug": "relative-distance", "name": "Relative Distance", "uuid": "bbdf7688-65cb-4291-9a4a-0ab89d122a31", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "algorithms", "data_structures", "maps" ] }, { "slug": "sum-of-multiples", "name": "Sum of Multiples", "uuid": "e431fb10-766a-4542-94ad-9c30224a3885", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "conditionals", "loops", "integers", "lists", "math" ] }, { "slug": "acronym", "name": "Acronym", "uuid": "f6631a0f-2da3-47a4-b46b-7a81fa0d4dfd", "practices": [], "prerequisites": [], "difficulty": 2, "topics": [ "loops", "regular_expressions", "strings", "transforming" ] }, { "slug": "anagram", "name": "Anagram", "uuid": "d3c93056-07b9-4d06-a681-1bb25db68c01", "practices": [], "prerequisites": [], "difficulty": 2, "topics": [ "filtering", "strings" ] }, { "slug": "isogram", "name": "Isogram", "uuid": "505eadfc-40df-476f-82d7-fe3b78b1f713", "practices": [], "prerequisites": [], "difficulty": 3, "topics": [ "conditionals", "loops", "filtering", "searching", "strings" ] }, { "slug": "roman-numerals", "name": "Roman Numerals", "uuid": "a4ea1a4d-d010-4039-a2b5-e3e748df7f8a", "practices": [], "prerequisites": [], "difficulty": 3, "topics": [ "conditionals", "loops", "pattern_recognition", "transforming" ] }, { "slug": "series", "name": "Series", "uuid": "dd332597-4924-48dc-abb3-1d3fde492777", "practices": [], "prerequisites": [], "difficulty": 3, "topics": [ "loops", "exception_handling", "strings", "text_formatting" ] }, { "slug": "state-of-tic-tac-toe", "name": "State of Tic-Tac-Toe", "uuid": "86316f23-cbea-4a4d-b5e0-577d24073f08", "practices": [], "prerequisites": [], "difficulty": 2 }, { "slug": "phone-number", "name": "Phone Number", "uuid": "bbd4665c-be4b-4d70-bc92-e91ce7a1d55f", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "parsing", "transforming" ] }, { "slug": "two-bucket", "name": "Two Bucket", "uuid": "6d187289-27b2-4630-b37b-58553271188a", "practices": [], "prerequisites": [], "difficulty": 6, "topics": [ "algorithms", "arrays", "conditionals", "loops", "exception_handling", "games", "parsing" ] }, { "slug": "variable-length-quantity", "name": "Variable Length Quantity", "uuid": "50050198-b5a3-4ab1-8b4b-0eadc8007ebb", "practices": [], "prerequisites": [], "difficulty": 6, "topics": [ "algorithms", "arrays", "bitwise_operations", "conditionals", "loops", "exception_handling", "lists", "transforming" ] }, { "slug": "largest-series-product", "name": "Largest Series Product", "uuid": "b646dc26-59c1-436e-883a-20290de7a526", "practices": [], "prerequisites": [], "difficulty": 7, "topics": [ "conditionals", "loops", "exception_handling", "integers", "math", "regular_expressions", "strings" ] }, { "slug": "food-chain", "name": "Food Chain", "uuid": "246129c9-83b5-43e0-beb6-8a2cea7e4e17", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "algorithms", "text_formatting" ] }, { "slug": "house", "name": "House", "uuid": "9850a74a-1842-4db0-b3ed-5a53b4f25050", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "arrays", "conditionals", "loops", "recursion", "strings" ] }, { "slug": "isbn-verifier", "name": "ISBN Verifier", "uuid": "50447e18-cfe8-43fb-a791-e8a82483bef6", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "conditionals", "loops", "pattern_recognition", "regular_expressions", "strings" ] }, { "slug": "pig-latin", "name": "Pig Latin", "uuid": "38951bf1-6e7d-4d7d-9b7d-219471f17112", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "conditionals", "loops", "games", "regular_expressions", "strings", "transforming" ] }, { "slug": "proverb", "name": "Proverb", "uuid": "b18876a7-f281-46e5-94b9-f805bb0d1fec", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "arrays", "conditionals", "loops", "optional_values", "strings", "text_formatting" ] }, { "slug": "twelve-days", "name": "Twelve Days", "uuid": "cb04ab17-9611-44a1-8406-ed385da0044d", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "conditionals", "loops", "pattern_recognition", "polymorphism", "strings" ] }, { "slug": "beer-song", "name": "Beer Song", "uuid": "7a4fefd2-6e71-42d6-82fd-a25d2ef9eae9", "practices": [], "prerequisites": [], "difficulty": 5, "status": "deprecated", "topics": [ "conditionals", "loops", "strings" ] }, { "slug": "say", "name": "Say", "uuid": "8e7b92f4-a508-4200-bd62-b36281dd9ed9", "practices": [], "prerequisites": [], "difficulty": 6, "topics": [ "conditionals", "loops", "exception_handling", "integers", "strings", "text_formatting" ] }, { "slug": "rectangles", "name": "Rectangles", "uuid": "6d81d98f-afde-4aea-85ff-c7baef98fb7e", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "arrays", "conditionals", "loops", "matrices", "strings" ] }, { "slug": "saddle-points", "name": "Saddle Points", "uuid": "d35da73a-5124-4911-a2c3-07c68d4b79c2", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "arrays", "conditionals", "loops", "integers", "lists", "matrices", "sets" ] }, { "slug": "spiral-matrix", "name": "Spiral Matrix", "uuid": "702b1e87-55db-479f-9ef9-4ccdfe8849b1", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "arrays", "conditionals", "loops", "data_structures", "matrices" ] }, { "slug": "transpose", "name": "Transpose", "uuid": "4e484e0f-0b24-4778-a949-224294735c23", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "arrays", "conditionals", "loops", "lists", "matrices", "strings", "text_formatting" ] }, { "slug": "ocr-numbers", "name": "OCR Numbers", "uuid": "61f0964f-0779-446d-bd6b-6bb988414302", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "conditionals", "loops", "equality", "exception_handling", "integers", "parsing", "text_formatting" ] }, { "slug": "nth-prime", "name": "Nth Prime", "uuid": "4c1392d7-8779-496f-b0bb-81605e777e3b", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "algorithms", "conditionals", "loops", "exception_handling", "integers", "math" ] }, { "slug": "alphametics", "name": "Alphametics", "uuid": "7219275b-935b-48ac-8bed-3436221bf3f1", "practices": [], "prerequisites": [], "difficulty": 7, "topics": [ "algorithms", "games" ] }, { "slug": "connect", "name": "Connect", "uuid": "69cbdc2a-50f4-4e14-b9f4-c1ddee021018", "practices": [], "prerequisites": [], "difficulty": 7, "topics": [ "arrays", "conditionals", "loops", "games", "maps", "parsing" ] }, { "slug": "bowling", "name": "Bowling", "uuid": "18376dda-8e8d-4cf7-a44a-db71da438e87", "practices": [], "prerequisites": [], "difficulty": 8, "topics": [ "arrays", "conditionals", "loops", "exception_handling", "games", "parsing", "text_formatting" ] }, { "slug": "prime-factors", "name": "Prime Factors", "uuid": "ef38ee9b-39b7-4bb8-9a34-2e19eba92260", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "algorithms", "conditionals", "loops", "integers", "math" ] }, { "slug": "diamond", "name": "Diamond", "uuid": "df7c1bff-2224-422b-9c34-64b28b09510e", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "arrays", "conditionals", "loops", "exception_handling", "games", "parsing", "text_formatting" ] }, { "slug": "pascals-triangle", "name": "Pascal's Triangle", "uuid": "10edb37d-cf5d-443a-bb4c-8831a3986b61", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "conditionals", "loops", "math" ] }, { "slug": "binary-search-tree", "name": "Binary Search Tree", "uuid": "b912fe4f-c505-4bac-8029-76412644375b", "practices": [], "prerequisites": [], "difficulty": 6, "topics": [ "algorithms", "conditionals", "loops", "recursion" ] }, { "slug": "sublist", "name": "Sublist", "uuid": "c5ed4703-59cd-4e31-b109-ba5bfc48a578", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "arrays", "conditionals", "loops", "data_structures", "lists" ] }, { "slug": "custom-set", "name": "Custom Set", "uuid": "e2cfc3b4-5c71-4344-a692-5d55609f097b", "practices": [], "prerequisites": [], "difficulty": 6, "topics": [ "arrays", "conditionals", "loops", "data_structures", "equality", "lists", "recursion", "sets" ] }, { "slug": "circular-buffer", "name": "Circular Buffer", "uuid": "759aa79d-975a-4b54-b306-618caba8f201", "practices": [], "prerequisites": [], "difficulty": 8, "topics": [ "arrays", "conditionals", "loops", "data_structures", "exception_handling", "lists" ] }, { "slug": "word-search", "name": "Word Search", "uuid": "ab16bfad-960d-405b-aba4-afd142330663", "practices": [], "prerequisites": [], "difficulty": 8, "topics": [ "arrays", "conditionals", "loops", "equality", "optional_values", "parsing", "text_formatting" ] }, { "slug": "sieve", "name": "Sieve", "uuid": "f3ad1525-c124-4b2f-b09f-4639eb4a6ad9", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "conditionals", "loops", "integers", "math", "recursion" ] }, { "slug": "palindrome-products", "name": "Palindrome Products", "uuid": "ffb0ad36-2cf5-46d4-9a5f-5bbdfb9691a7", "practices": [], "prerequisites": [], "difficulty": 7, "topics": [ "algorithms", "conditionals", "loops", "integers", "math" ] }, { "slug": "diffie-hellman", "name": "Diffie-Hellman", "uuid": "1d12bce3-bfdc-4785-9dc7-0a496d2e6d38", "practices": [], "prerequisites": [], "difficulty": 3, "topics": [ "algorithms", "arrays", "conditionals", "loops", "exception_handling", "math" ] }, { "slug": "robot-simulator", "name": "Robot Simulator", "uuid": "abe907d0-7ca0-4fe5-83fd-72a4d2acab66", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "conditionals", "loops", "exception_handling", "games", "parsing", "strings" ] }, { "slug": "armstrong-numbers", "name": "Armstrong Numbers", "uuid": "31e1b8ba-a5dd-4806-9a4c-94f7550e25d5", "practices": [], "prerequisites": [], "difficulty": 2, "topics": [ "algorithms", "integers", "math" ] }, { "slug": "run-length-encoding", "name": "Run-Length Encoding", "uuid": "16c5cf98-c381-46bb-a439-78dbc847c0d0", "practices": [], "prerequisites": [], "difficulty": 2, "topics": [ "conditionals", "exception_handling", "parsing", "pattern_recognition", "regular_expressions", "strings", "text_formatting" ] }, { "slug": "bank-account", "name": "Bank Account", "uuid": "3247f8b4-8b80-4c0b-bc3b-779f7a3eb291", "practices": [], "prerequisites": [], "difficulty": 3, "topics": [ "classes", "conditionals" ] }, { "slug": "eliuds-eggs", "name": "Eliud's Eggs", "uuid": "2cdf947c-34ed-4ecf-92dd-4c4e6c21f9f3", "practices": [], "prerequisites": [], "difficulty": 3 }, { "slug": "bottle-song", "name": "Bottle Song", "uuid": "6bcfe2b6-e555-4c90-8c80-168730dd5ad3", "practices": [], "prerequisites": [], "difficulty": 3, "topics": [ "conditionals", "loops", "strings" ] }, { "slug": "rotational-cipher", "name": "Rotational Cipher", "uuid": "dbe39983-5635-4369-89a3-fd549144259b", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "strings", "iterators" ] }, { "slug": "strain", "name": "Strain", "uuid": "160c31bd-814c-4de7-8af5-7cd223763da8", "practices": [], "prerequisites": [], "difficulty": 4, "topics": [ "algorithms", "arrays", "callbacks", "conditionals", "loops", "filtering", "lists" ] }, { "slug": "accumulate", "name": "Accumulate", "uuid": "32953258-d488-4761-9669-895a359a8b9a", "practices": [], "prerequisites": [], "difficulty": 5, "status": "deprecated", "topics": [ "algorithms", "callbacks", "conditionals", "lists" ] }, { "slug": "all-your-base", "name": "All Your Base", "uuid": "0f4f98fe-245a-4842-b3cf-6581b89afd44", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "conditionals", "loops", "exception_handling", "integers", "math", "parsing" ] }, { "slug": "flatten-array", "name": "Flatten Array", "uuid": "ebe8227e-f445-4387-bf9f-54d4d9730142", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "arrays", "lists", "loops", "recursion" ] }, { "slug": "matching-brackets", "name": "Matching Brackets", "uuid": "b455f96b-329d-4dec-ac31-599b02c0b2e5", "practices": [], "prerequisites": [], "difficulty": 5, "topics": [ "parsing", "stacks", "strings" ] }, { "slug": "tournament", "name": "Tournament", "uuid": "f4634d37-2357-4b54-8cd8-ad450234537a", "practices": [ "string-formatting" ], "prerequisites": [ "strings", "string-formatting" ], "difficulty": 6 }, { "slug": "flower-field", "name": "Flower Field", "uuid": "f1c96aaa-0944-4a11-83df-9f3a4f5417f0", "practices": [], "prerequisites": [], "difficulty": 7, "topics": [ "algorithms", "arrays", "games" ] }, { "slug": "minesweeper", "name": "Minesweeper", "uuid": "f5b00e76-3015-444d-8012-9862990db08c", "practices": [], "prerequisites": [], "difficulty": 7, "status": "deprecated", "topics": [ "algorithms", "arrays", "games" ] }, { "slug": "kindergarten-garden", "name": "Kindergarten Garden", "uuid": "d70d3579-999c-452c-9243-98908e24b47e", "practices": [], "prerequisites": [], "difficulty": 7, "topics": [ "arrays", "conditionals", "loops", "strings", "text_formatting" ] }, { "slug": "queen-attack", "name": "Queen Attack", "uuid": "837fbe57-a567-4fc7-a7a2-8b63059ac8b7", "practices": [], "prerequisites": [], "difficulty": 8, "topics": [ "conditionals", "loops", "equality", "exception_handling", "optional_values", "parsing", "text_formatting" ] }, { "slug": "react", "name": "React", "uuid": "6ad78102-09cc-4aeb-bfa7-58ba3dba57f3", "practices": [], "prerequisites": [], "difficulty": 8, "topics": [ "algorithms", "closures", "reactive_programming" ] }, { "slug": "crypto-square", "name": "Crypto Square", "uuid": "e92b7444-a5c3-4452-82fd-8c70cf14085a", "practices": [], "prerequisites": [], "difficulty": 9, "topics": [ "algorithms", "arrays", "conditionals", "loops", "regular_expressions", "sorting", "text_formatting", "transforming" ] }, { "slug": "knapsack", "name": "Knapsack", "uuid": "b80ebb8d-9416-4fc8-91ee-3db99411810c", "practices": [], "prerequisites": [], "difficulty": 9, "topics": [ "algorithms" ] }, { "slug": "game-of-life", "name": "Conway's Game of Life", "uuid": "c338cbce-fad4-4de0-bf0d-4a90803a0fe9", "practices": [], "prerequisites": [], "difficulty": 2 }, { "slug": "yacht", "name": "Yacht", "uuid": "06ac7eed-1417-4510-b526-eb068af21ad9", "practices": [], "prerequisites": [], "difficulty": 4, "topics": ["arrays", "conditionals", "filtering", "games"] } ] }, "concepts": [ { "uuid": "6b93ea93-3312-4aee-ad6f-3f8b034e92d9", "slug": "basics", "name": "Basics" } ], "key_features": [ { "title": "Evolving", "content": "Typescript is a modern and constantly evolving open-source language supported by Microsoft.", "icon": "evolving" }, { "title": "Widely used", "content": "TypeScript is used by many libraries and frameworks, and integrates with JavaScript code as well.", "icon": "widely-used" }, { "title": "Typed JavaScript", "content": "Typescript flexible typing system lets you describe what to expect, supporting gradual adoption.", "icon": "dynamically-typed" }, { "title": "Use any programming style", "content": "Use prototype-based, object-oriented, functional, or declarative programming styles, and more.", "icon": "multi-paradigm" }, { "title": "Consistently Good Tooling", "content": "TS's JavaScript integration is handled by the language itself, enabling its tools and features.", "icon": "tooling" }, { "title": "Large Community", "content": "The large number of users makes it easy to find answers, documentation, and libraries.", "icon": "community" } ], "tags": [ "execution_mode/compiled", "paradigm/declarative", "paradigm/functional", "paradigm/imperative", "paradigm/object_oriented", "platform/android", "platform/ios", "platform/linux", "platform/mac", "platform/web", "platform/windows", "runtime/language_specific", "runtime/wasmtime", "typing/dynamic", "typing/static", "typing/strong", "typing/weak", "used_for/artificial_intelligence", "used_for/backends", "used_for/cross_platform_development", "used_for/frontends", "used_for/games", "used_for/guis", "used_for/mobile", "used_for/web_development" ] } ================================================ FILE: docs/ABOUT.md ================================================ # About [TypeScript](https://www.typescriptlang.org/) (TS) is a superset of JavaScript (JS), created at Microsoft in response to frustration developing large-scale applications in JS. In a large JS project, knowing what properties your own objects have, what arguments your functions take (and what type they need to be) can become difficult. Similarly, since there is no ability to intelligently inspect JS code, when you include a package (like from `npm`), you have to keep the documentation up so you know what methods are available and what arguments they take. TS solves these issues. It is currently an open-source project hosted on Github. It supports tools for any browser as well as Node, for any host, on any OS. TS compiles to readable, standards-based JavaScript. TS adds a flexible type system to JS, in addition to interfaces (custom types) and modifying the syntax of some ECMAScript features such as classes. Types are optional and flexible (for example, you can specify an argument is a string OR a number). Types allow tooling available in most code editors that improve the development experience such as code completion and method detection, both in your own code and in packages you use. It supports many upcoming ECMAScript features (such as async/await). TS can be written in Object Oriented or Functional styles. It is compatible with all existing JS packages. TS transpiles to clean, readable JS. Try it out at the [playground](https://www.typescriptlang.org/play), and stay up to date via [the Typescript blog](https://devblogs.microsoft.com/typescript/) and [Twitter account](https://twitter.com/typescript). ================================================ FILE: docs/INSTALLATION.md ================================================ # Installation You need [NodeJS LTS](https://nodejs.org/en). > 💡 This track likely works with older stable versions, as well as the "current" version. > However, [by design](https://github.com/nodejs/release#release-schedule), odd versions are never stable. > Always make sure you install an even version of NodeJS. Install [Yarn 4+](https://yarnpkg.com/getting-started/install) using Node.JS `corepack`: ```shell corepack enable yarn ``` ## Checking your installation You generally need to open a new terminal in order to use binaries you've just installed. ```shell $ node -v v20.15.0 $ corepack -v 0.28.1 $ corepack yarn -v v4.3.1 ``` _Note: your versions will likely differ from this._ ## Assignment installation **Each assignment** needs some tools to run the tests: They can be installed running this command within each assignment directory: ```bash $ corepack yarn install ``` Depending on your editor, the [Editor SDK](https://yarnpkg.com/getting-started/editor-sdks) needs to be installed in each assignment, in order to work with Plug'n'Play installs. At moment of writing the following are supported: - [CoC nvim](https://yarnpkg.com/getting-started/editor-sdks#coc-nvim) - [Emacs](https://yarnpkg.com/getting-started/editor-sdks#emacs) - [Neovim Native LSP](https://yarnpkg.com/getting-started/editor-sdks#neovim-native-lsp) - [VSCode](https://yarnpkg.com/getting-started/editor-sdks#vscode) For example, if using VSCode, the commands you run are: ```shell $ corepack yarn install ➤ YN0000: · Yarn 4.3.1 ➤ YN0000: ┌ Resolution step ➤ YN0000: └ Completed ➤ YN0000: ┌ Post-resolution validation ➤ YN0060: │ # [...] ➤ YN0000: └ Completed ➤ YN0000: ┌ Fetch step ➤ YN0000: └ Completed in 0s 566ms ➤ YN0000: ┌ Link step ➤ YN0000: │ ESM support for PnP uses the experimental loader API and is therefore experimental ➤ YN0000: └ Completed ➤ YN0000: · Done with warnings in 0s 768ms $ corepack yarn dlx @yarnpkg/sdks vscode ➤ YN0000: · Yarn 4.3.1 ➤ YN0000: ┌ Resolution step ➤ YN0085: │ + @yarnpkg/sdks@npm:3.1.3, and 103 more. ➤ YN0000: └ Completed in 0s 509ms ➤ YN0000: ┌ Fetch step ➤ YN0013: │ 104 packages were added to the project (+ 2.87 MiB). ➤ YN0000: └ Completed ➤ YN0000: ┌ Link step ➤ YN0000: └ Completed in 0s 270ms ➤ YN0000: · Done in 0s 881ms ➤ YN0000: Cleaning up the existing SDK files... ➤ YN0000: ┌ Generating SDKs inside .yarn/sdks ➤ YN0000: │ ✓ Eslint ➤ YN0000: │ ✓ Prettier ➤ YN0000: │ ✓ Typescript ➤ YN0000: │ • 5 SDKs were skipped based on your root dependencies ➤ YN0000: └ Completed ➤ YN0000: ┌ Generating settings ➤ YN0000: │ ✓ Vscode (updated 🔼) ➤ YN0000: └ Completed ``` You must run install _inside the exercise directory_ before you are able to run the tests. [Yarn 4 PnP](https://yarnpkg.com/features/pnp/) is enabled, installing the dependencies in each exercise directory does NOT install a complete copy of all the dependencies. If your editor is showing errors for `eslint`, `prettier`, or `typescript` afterwards, reload the window or reopen the editor. ================================================ FILE: docs/LEARNING.md ================================================ # Learning If you want to learn Typescript, check out the following resources. - [TS Tutorial](https://www.typescriptlang.org/docs/handbook/release-notes/overview.html) - [TS Playground](https://www.typescriptlang.org/play) TypeScript is a superset of JS. TypeScript offers all of JavaScript’s features, and an additional layer on top of these: TypeScript’s type system. - [Exploring ES6](https://leanpub.com/exploring-es6) book, by Axel Rauschmayer - [ES6 Katas](https://jskatas.org/#bundle-es6-katas) website - [Babel compiler](https://github.com/babel/babel) ================================================ FILE: docs/RESOURCES.md ================================================ # Resources ## Recommended References - [TypeScript QuickStart](https://www.typescriptlang.org/docs/handbook/release-notes/overview.html) - [TypeScript Playground](https://www.typescriptlang.org/play) - [ECMAScript 2015 Language Specification](https://www.ecma-international.org/wp-content/uploads/ECMA-262_6th_edition_june_2015.pdf) (pdf) - [Mozilla JavaScript Reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference) ## Editors ### Free/Open-Source - [VS Code](https://code.visualstudio.com) VS Code is an open-source code editor. You can find information on how to set up TypeScript [here](https://code.visualstudio.com/docs/languages/typescript). VS Code is a popular editor for TypeScript because Microsoft maintains both VS Code and TypeScript, so it works well with the language; VS Code supports TS without any extra configuration. In fact, VS Code was written using TypeScript! A common issue with VS Code and TS is when your editor and project versions are different, which can cause VS Code to display a possibly confusing error message. Instructions on how to handle this error are [here](https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-newer-typescript-versions) ### Licensed - [Sublime Text](https://www.sublimetext.com) Sublime Text is a popular code editor with support for most languages through installable plugins. It is free to try, but you will be regularly prompted to buy a license, which currently costs \$70 USD. - [WebStorm](https://www.jetbrains.com/webstorm/) WebStorm is a fully-featured JavaScript/TypeScript IDE (Integrated Development Environment). It supports TypeScript with no extra configuration and has integrated testing, debugging and code analysis. It is free to try for 30 days, then an annual license costs \$129 USD for the first year. ================================================ FILE: docs/SNIPPET.txt ================================================ export class HelloWorld { static hello(name = 'World'): string { return `Hello, ${name}!` } } ================================================ FILE: docs/TESTS.md ================================================ # Tests Before trying to execute the tests, ensure the assignment folder is set-up correctly by following the installation steps, namely `corepack yarn install` and the Editor SDK setup. Execute the tests with: ```bash $ corepack yarn test ``` Be sure your code follows best practices, as other users do, with eslint, a tool to perform static analysis to your code. Tools like this save you some time detecting typos or silly mistakes in your code: ```bash $ corepack yarn lint ``` Or do both at the same time: ```bash $ corepack yarn test && corepack yarn lint ``` The TypeScript track on Exercism does not enforce code _style_, so you're free to choose between semicolons, tabs vs spaces, and everything in-between. ## Making Your First Module Usually, tests on this track will load your implementation importing it as a module: `import Bob from './bob';`. To make it work, you need to export your implementation from the file the tests are looking for your module, `bob.ts`: ```typescript export class Bob { public hey(message: string) { // Your solution here } } ``` We've provided stubs in each exercise and placed this `export` in there for you. ================================================ FILE: docs/config.json ================================================ { "docs": [ { "uuid": "c79ecacf-6b92-4033-9f25-1f86e7299760", "slug": "installation", "path": "docs/INSTALLATION.md", "title": "Installing TypeScript locally", "blurb": "Learn how to install TypeScript locally to solve Exercism's exercises on your own machine" }, { "uuid": "d5553e1b-8432-4b27-a855-d060e830636d", "slug": "learning", "path": "docs/LEARNING.md", "title": "How to learn TypeScript", "blurb": "An overview of how to get started from scratch with TypeScript" }, { "uuid": "bef8b724-0569-4ad9-8e00-5daff3c17057", "slug": "tests", "path": "docs/TESTS.md", "title": "Testing on the TypeScript track", "blurb": "Learn how to test your TypeScript exercises on Exercism" }, { "uuid": "4d00c4da-0f99-4d0c-9a43-aca47128e4fb", "slug": "resources", "path": "docs/RESOURCES.md", "title": "Useful TypeScript resources", "blurb": "A collection of useful resources to help you master TypeScript" } ] } ================================================ FILE: eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' import globals from 'globals' export default [ ...tsEslint.config( ...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }, { files: ['scripts/**/*.mjs'], languageOptions: { globals: { ...globals.node, }, }, } ), { ignores: [ // # Protected or generated '/.appends/**/*', '/.github/**/*', '/.vscode/**/*', '/.yarn/**/*', '/common/.vscode/**/*', '/common/.yarn/**/*', // # Binaries '/bin/*', // # Configuration '/config', '/babel.config.cjs', ], }, ] ================================================ FILE: exercises/concept/.keep ================================================ ================================================ FILE: exercises/concept/lasagna/.docs/hints.md ================================================ # Hints ## 1. Define the expected oven time in minutes - Define a [constant][constants] which should contain the [`number`][numbers] value specified in the recipe. - [`export`][export] the constant. ## 2. Calculate the remaining oven time in minutes - [Explicitly return a number][return] from the function. - Use the [mathematical operator for subtraction][operators] to subtract values. ## 3. Calculate the preparation time in minutes - [Explicitly return a number][return] from the function. - Use the [mathematical operator for multiplication][operators] to multiply values. - Use the extra constant for the time in minutes per layer. ## 4. Calculate the total working time in minutes - [Explicitly return a number][return] from the function. - [Invoke][invocation] one of the other methods implemented previously. - Use the [mathematical operator for addition][operators] to add values. [return]: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Return_values [export]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export [operators]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators [constants]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const [invocation]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#Calling_functions [numbers]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type ================================================ FILE: exercises/concept/lasagna/.docs/instructions.md ================================================ # Instructions Lucian's girlfriend is on her way home, and he hasn't cooked their anniversary dinner! In this exercise, you're going to write some code to help Lucian cook an exquisite lasagna from his favorite cookbook. You have four tasks related to the time spent cooking the lasagna. ## 1. Define the expected oven time in minutes Define the `EXPECTED_MINUTES_IN_OVEN` constant that represents how many minutes the lasagna should be in the oven. It must be exported. According to the cooking book, the expected oven time in minutes is `40`. ## 2. Calculate the remaining oven time in minutes Implement the `remainingMinutesInOven` function that takes the actual minutes the lasagna has been in the oven as a _parameter_ and _returns_ how many minutes the lasagna still has to remain in the oven, based on the **expected oven time in minutes** from the previous task. ```javascript remainingMinutesInOven(30) // => 10 ``` ## 3. Calculate the preparation time in minutes Implement the `preparationTimeInMinutes` function that takes the number of layers you added to the lasagna as a _parameter_ and _returns_ how many minutes you spent preparing the lasagna, assuming each layer takes you 2 minutes to prepare. ```javascript preparationTimeInMinutes(2) // => 4 ``` ## 4. Calculate the total working time in minutes Implement the `totalTimeInMinutes` function that takes _two parameters_: the `numberOfLayers` parameter is the number of layers you added to the lasagna, and the `actualMinutesInOven` parameter is the number of minutes the lasagna has been in the oven. The function should _return_ how many minutes in total you've worked on cooking the lasagna, which is the sum of the preparation time in minutes, and the time in minutes the lasagna has spent in the oven at the moment. ```javascript totalTimeInMinutes(3, 20) // => 26 ``` ================================================ FILE: exercises/concept/lasagna/.docs/introduction.md ================================================ # Introduction JavaScript is a dynamic language, supporting object-oriented, imperative, and declarative (e.g. functional programming) styles. ## (Re-)Assignment There are a few primary ways to assign values to names in JavaScript - using variables or constants. On Exercism, variables are always written in [camelCase][wiki-camel-case]; constants are written in [SCREAMING_SNAKE_CASE][wiki-snake-case]. There is no official guide to follow, and various companies and organizations have various style guides. _Feel free to write variables any way you like_. The upside from writing them the way the exercises are prepared is that they'll be highlighted differently in the web interface and most IDEs. Variables in JavaScript can be defined using the [`const`][mdn-const], [`let`][mdn-let] or [`var`][mdn-var] keyword. A variable can reference different values over its lifetime when using `let` or `var`. For example, `myFirstVariable` can be defined and redefined many times using the assignment operator `=`: ```javascript let myFirstVariable = 1 myFirstVariable = 'Some string' myFirstVariable = new SomeComplexClass() ``` In contrast to `let` and `var`, variables that are defined with `const` can only be assigned once. This is used to define constants in JavaScript. ```javascript const MY_FIRST_CONSTANT = 10 // Can not be re-assigned. MY_FIRST_CONSTANT = 20 // => TypeError: Assignment to constant variable. ``` > 💡 In a later Concept Exercise the difference between _constant_ assignment / binding and _constant_ value is explored and explained. ## Function Declarations In JavaScript, units of functionality are encapsulated in _functions_, usually grouping functions together in the same file if they belong together. These functions can take parameters (arguments), and can _return_ a value using the `return` keyword. Functions are invoked using `()` syntax. ```javascript function add(num1, num2) { return num1 + num2 } add(1, 3) // => 4 ``` > 💡 In JavaScript there are _many_ different ways to declare a function. These other ways look different than using the `function` keyword. The track tries to gradually introduce them, but if you already know about them, feel free to use any of them. In most cases, using one or the other isn't better or worse. ## Exposing to Other Files To make a `function`, a constant, or a variable available in _other files_, they need to be [exported][mdn-export] using the `export` keyword. Another file may then [import][mdn-import] these using the `import` keyword. This is also known as the module system. A great example is how all the tests work. Each exercise has at least one file, for example `lasagna.js`, which contains the _implementation_. Additionally there is at least one other file, for example `lasagna.spec.js`, that contains the _tests_. This file _imports_ the public (i.e. exported) entities in order to test the implementation: ```javascript // file.js export const MY_VALUE = 10 export function add(num1, num2) { return num1 + num2 } // file.spec.js import { MY_VALUE, add } from './file' add(MY_VALUE, 5) // => 15 ``` [mdn-const]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const [mdn-export]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export [mdn-import]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import [mdn-let]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let [mdn-var]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var [wiki-camel-case]: https://en.wikipedia.org/wiki/Camel_case [wiki-snake-case]: https://en.wikipedia.org/wiki/Snake_case ================================================ FILE: exercises/concept/lasagna/.meta/config.json ================================================ { "authors": ["SleeplessByte"], "files": { "solution": [ "lasagna.ts" ], "test": [ "__typetests__/lasagna.tst.ts", "lasagna.test.ts" ], "exemplar": [ ".meta/exemplar.ts" ] }, "forked_from": [ "javascript/lasagna" ], "blurb": "Learn the basics of TypeScript cooking a brilliant lasagna from your favorite cooking book.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": true, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": true } } ================================================ FILE: exercises/concept/lasagna/.meta/design.md ================================================ # Design ## Learning objectives - Know what a variable is. - Know what a constant variable is. - Know how to define a variable. - Know how to export a variable - Know how to return a value from a function (explicit return). - Know how to annotate a function parameter - Know how to annotate a function return type ## Out of scope This exercise is really just to introduce the bare minimum a student needs to know to solve a very basic exercise on Exercism. Details about the primitive data types, different ways to define functions etc. will all be properly introduced in the later concept exercises. We don't even explicitly teach the basics of numbers and arithmetic operators in the introduction. Given the general code examples that are provided and some "I will just try that", the student should be fine solving the exercise nevertheless. ## Concepts - `basics` ## Prerequisites There are no prerequisites. ## Analyzer This exercise could benefit from the following rules added to the the [analyzer][analyzer]: - Verify that the `remainingMinutesInOven` function uses the `EXPECTED_MINUTES_IN_OVEN` constant. - Verify that the `preparationTimeInMinutes` function uses the `PREPARATION_MINUTES_PER_LAYER` constant. - Verify that the `totalTimeInMinutes` function calls the `preparationTimeInMinutes` function. - Verify that no extra _bookkeeping_ or _intermediate_ variables are declared [analyzer]: https://github.com/exercism/typescript-analyzer ================================================ FILE: exercises/concept/lasagna/.meta/exemplar.ts ================================================ /** * The amount of minutes the lasagna should be in the oven. */ export const EXPECTED_MINUTES_IN_OVEN = 40 /** * The amount of minutes it takes to prepare a single layer. */ const PREPARATION_MINUTES_PER_LAYER = 2 /** * Determines the amount of minutes the lasagna still needs to remain in the * oven to be properly prepared. * * @param actualMinutesInOven * @returns the number of minutes remaining */ export function remainingMinutesInOven(actualMinutesInOven: number): number { return EXPECTED_MINUTES_IN_OVEN - actualMinutesInOven } /** * Given a number of layers, determines the total preparation time. * * @param numberOfLayers * @returns the total preparation time */ export function preparationTimeInMinutes(numberOfLayers: number): number { return numberOfLayers * PREPARATION_MINUTES_PER_LAYER } /** * Calculates the total working time. That is, the time to prepare all the layers * of lasagna, and the time already spent in the oven. * * @param numberOfLayers * @param actualMinutesInOven * @returns the total working time */ export function totalTimeInMinutes( numberOfLayers: number, actualMinutesInOven: number ): number { return preparationTimeInMinutes(numberOfLayers) + actualMinutesInOven } ================================================ FILE: exercises/concept/lasagna/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/concept/lasagna/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/concept/lasagna/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/concept/lasagna/__typetests__/lasagna.tst.ts ================================================ import { describe, expect, test } from 'tstyche' import { EXPECTED_MINUTES_IN_OVEN, remainingMinutesInOven, preparationTimeInMinutes, totalTimeInMinutes, } from '../lasagna.ts' describe('EXPECTED_MINUTES_IN_OVEN', () => { test('constant is defined as a number or a constant number', () => { expect(EXPECTED_MINUTES_IN_OVEN).type.toBeAssignableTo() }) }) describe('remainingMinutesInOven', () => { test('takes one number parameter', () => { expect>().type.toBe<[number]>() }) test('returns a number', () => { expect>().type.toBe() }) }) describe('preparationTimeInMinutes', () => { test('takes one number parameter', () => { expect>().type.toBe<[number]>() }) test('returns a number', () => { expect>().type.toBe() }) }) describe('totalTimeInMinutes', () => { test('takes two number parameters', () => { expect>().type.toBe< [number, number] >() }) test('returns a number', () => { expect>().type.toBe() }) }) ================================================ FILE: exercises/concept/lasagna/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/concept/lasagna/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/concept/lasagna/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/concept/lasagna/lasagna.test.ts ================================================ import { describe, test, expect } from '@jest/globals' import { EXPECTED_MINUTES_IN_OVEN, remainingMinutesInOven, preparationTimeInMinutes, totalTimeInMinutes, } from './lasagna.ts' describe('EXPECTED_MINUTES_IN_OVEN', () => { test('constant is defined correctly', () => { expect(EXPECTED_MINUTES_IN_OVEN).toBe(40) }) }) describe('remainingMinutesInOven', () => { test('calculates the remaining time', () => { expect(remainingMinutesInOven(25)).toBe(15) expect(remainingMinutesInOven(5)).toBe(35) expect(remainingMinutesInOven(39)).toBe(1) }) test('works correctly for the edge cases', () => { expect(remainingMinutesInOven(40)).toBe(0) expect(remainingMinutesInOven(0)).toBe(40) }) }) describe('preparationTimeInMinutes', () => { test('calculates the preparation time', () => { expect(preparationTimeInMinutes(1)).toBe(2) expect(preparationTimeInMinutes(2)).toBe(4) expect(preparationTimeInMinutes(8)).toBe(16) }) }) describe('totalTimeInMinutes', () => { test('calculates the total cooking time', () => { expect(totalTimeInMinutes(1, 5)).toBe(7) expect(totalTimeInMinutes(4, 15)).toBe(23) expect(totalTimeInMinutes(1, 30)).toBe(32) }) }) ================================================ FILE: exercises/concept/lasagna/lasagna.ts ================================================ // 👋🏽 Hi there! // // On the TypeScript track we provide you with stubs. These stubs provide a // starting point to solving the exercise. // // In general, each variable/constant and each declared function will have a // JSDoc comment block above it, explaining what the variable/constant holds or // the function is supposed to accomplish. // // 💡 Often, the JSDoc comment blocks have annotations, such as @param and // @returns which are usually highlighted with a different color if the IDE // you're in recognizes them. It's these annotations that are used when // referring to the constant, variable, or function from somewhere else that // IDEs display. // // You don't need to write these yourself; it is not expected in idiomatic // TypeScript, but some companies and style-guides do enforce them. // // 💡 You're allowed to completely clear a stub before you get started. Often // we recommend using the stub, because they are already set-up correctly to // work with the tests, which you can find in ./lasagna.spec.js // // Good luck preparing some lasagna! /** * The number of minutes it takes to prepare a single layer. */ const PREPARATION_MINUTES_PER_LAYER = 2 /** * Determines the number of minutes the lasagna still needs to remain in the * oven to be properly prepared. * * @param actualMinutesInOven * @returns the number of minutes remaining */ export function remainingMinutesInOven(actualMinutesInOven: number): number { throw new Error('Remove this line and implement the function') } /** * Given a number of layers, determines the total preparation time. * * @param numberOfLayers * @returns the total preparation time */ export function preparationTimeInMinutes(numberOfLayers: number): number { throw new Error('Remove this line and implement the function') } /** * Calculates the total working time. That is, the time to prepare all the * layers of lasagna, and the time already spent in the oven. * * @param numberOfLayers * @param actualMinutesInOven * @returns the total working time */ export function totalTimeInMinutes( numberOfLayers: number, actualMinutesInOven: number ): number { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/concept/lasagna/package.json ================================================ { "name": "@exercism/typescript-lasagna", "version": "1.0.0", "description": "Exercism concept exercise on lasagna", "author": "Derk-Jan Karrenbeld (https://derk-jan.com)", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/concept/lasagna/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/concept/lasagna/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/accumulate/.docs/instructions.md ================================================ # Instructions Implement the `accumulate` operation, which, given a collection and an operation to perform on each element of the collection, returns a new collection containing the result of applying that operation to each element of the input collection. Given the collection of numbers: - 1, 2, 3, 4, 5 And the operation: - square a number (`x => x * x`) Your code should be able to produce the collection of squares: - 1, 4, 9, 16, 25 Check out the test suite to see the expected function signature. ## Restrictions Keep your hands off that collect/map/fmap/whatchamacallit functionality provided by your standard library! Solve this one yourself using other basic tools instead. ================================================ FILE: exercises/practice/accumulate/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "emiller42", "lumosmind", "masters3d", "pkchv", "SleeplessByte" ], "files": { "solution": [ "accumulate.ts" ], "test": [ "accumulate.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Implement the `accumulate` operation, which, given a collection and an operation to perform on each element of the collection, returns a new collection containing the result of applying that operation to each element of the input collection.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Conversation with James Edward Gray II", "source_url": "https://twitter.com/jeg2" } ================================================ FILE: exercises/practice/accumulate/.meta/proof.ci.ts ================================================ export function accumulate(list: T[], accumulator: (arg: T) => O): O[] { const out = [] let idx = -1 const end = list.length while (++idx < end) { out.push(accumulator(list[idx])) } return out } ================================================ FILE: exercises/practice/accumulate/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/accumulate/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/accumulate/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/accumulate/accumulate.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { accumulate } from './accumulate.ts' describe('accumulate()', () => { it('accumulation empty', () => { const accumulator = (e: number): number => e * e expect(accumulate([], accumulator)).toEqual([]) }) xit('accumulate squares', () => { const accumulator = (n: number): number => n * n const result = accumulate([1, 2, 3], accumulator) expect(result).toEqual([1, 4, 9]) }) xit('accumulate upcases', () => { const accumulator = (word: string): string => word.toUpperCase() const result = accumulate('hello world'.split(/\s/), accumulator) expect(result).toEqual(['HELLO', 'WORLD']) }) xit('accumulate reversed strings', () => { const accumulator = (word: string): string => word.split('').reverse().join('') const result = accumulate( 'the quick brown fox etc'.split(/\s/), accumulator ) expect(result).toEqual(['eht', 'kciuq', 'nworb', 'xof', 'cte']) }) xit('accumulate recursively', () => { const result = accumulate('a b c'.split(/\s/), (char: string) => accumulate('1 2 3'.split(/\s/), (digit: string) => char + digit) ) expect(result).toEqual([ ['a1', 'a2', 'a3'], ['b1', 'b2', 'b3'], ['c1', 'c2', 'c3'], ]) }) }) ================================================ FILE: exercises/practice/accumulate/accumulate.ts ================================================ export function accumulate(list: unknown, accumulator: unknown): never { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/accumulate/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/accumulate/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/accumulate/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/accumulate/package.json ================================================ { "name": "@exercism/typescript-accumulate", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/accumulate/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/accumulate/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/acronym/.docs/instructions.md ================================================ # Instructions Convert a phrase to its acronym. Techies love their TLA (Three Letter Acronyms)! Help generate some jargon by writing a program that converts a long name like Portable Network Graphics to its acronym (PNG). Punctuation is handled as follows: hyphens are word separators (like whitespace); all other punctuation can be removed from the input. For example: | Input | Output | | ------------------------- | ------ | | As Soon As Possible | ASAP | | Liquid-crystal display | LCD | | Thank George It's Friday! | TGIF | ================================================ FILE: exercises/practice/acronym/.meta/config.json ================================================ { "authors": [], "contributors": [ "lilislilit", "masters3d", "SleeplessByte" ], "files": { "solution": [ "acronym.ts" ], "test": [ "acronym.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Convert a long phrase to its acronym.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Julien Vanier", "source_url": "https://github.com/monkbroc" } ================================================ FILE: exercises/practice/acronym/.meta/proof.ci.ts ================================================ export function parse(phrase: string): string { if (typeof phrase !== 'undefined' && phrase !== undefined) { const match = phrase.match(/[A-Z]+[a-z]*|[a-z]+/g) return !match ? '' : match.reduce( (acronym: string, word: string) => (acronym += word[0].toUpperCase()), '' ) } return '' } ================================================ FILE: exercises/practice/acronym/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [1e22cceb-c5e4-4562-9afe-aef07ad1eaf4] description = "basic" [79ae3889-a5c0-4b01-baf0-232d31180c08] description = "lowercase words" [ec7000a7-3931-4a17-890e-33ca2073a548] description = "punctuation" [32dd261c-0c92-469a-9c5c-b192e94a63b0] description = "all caps word" [ae2ac9fa-a606-4d05-8244-3bcc4659c1d4] description = "punctuation without whitespace" [0e4b1e7c-1a6d-48fb-81a7-bf65eb9e69f9] description = "very long abbreviation" [6a078f49-c68d-4b7b-89af-33a1a98c28cc] description = "consecutive delimiters" [5118b4b1-4572-434c-8d57-5b762e57973e] description = "apostrophes" [adc12eab-ec2d-414f-b48c-66a4fc06cdef] description = "underscore emphasis" ================================================ FILE: exercises/practice/acronym/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/acronym/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/acronym/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/acronym/acronym.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { parse } from './acronym.ts' describe('Acronym are produced from', () => { it('title cased phrases', () => { expect(parse('Portable Network Graphics')).toEqual('PNG') }) xit('other title cased phrases', () => { expect(parse('Ruby on Rails')).toEqual('ROR') }) xit('inconsistently cased phrases', () => { expect(parse('HyperText Markup Language')).toEqual('HTML') }) xit('phrases with punctuation', () => { expect(parse('First In, First Out')).toEqual('FIFO') }) xit('other phrases with punctuation', () => { expect(parse('PHP: Hypertext Preprocessor')).toEqual('PHP') }) xit('phrases with punctuation and sentence casing', () => { expect(parse('Complementary metal-oxide semiconductor')).toEqual('CMOS') }) }) ================================================ FILE: exercises/practice/acronym/acronym.ts ================================================ export function parse(phrase: unknown): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/acronym/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/acronym/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/acronym/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/acronym/package.json ================================================ { "name": "@exercism/typescript-acronym", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/acronym/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/acronym/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/all-your-base/.docs/instructions.md ================================================ # Instructions Convert a sequence of digits in one base, representing a number, into a sequence of digits in another base, representing the same number. ~~~~exercism/note Try to implement the conversion yourself. Do not use something else to perform the conversion for you. ~~~~ ## About [Positional Notation][positional-notation] In positional notation, a number in base **b** can be understood as a linear combination of powers of **b**. The number 42, _in base 10_, means: `(4 × 10¹) + (2 × 10⁰)` The number 101010, _in base 2_, means: `(1 × 2⁵) + (0 × 2⁴) + (1 × 2³) + (0 × 2²) + (1 × 2¹) + (0 × 2⁰)` The number 1120, _in base 3_, means: `(1 × 3³) + (1 × 3²) + (2 × 3¹) + (0 × 3⁰)` _Yes. Those three numbers above are exactly the same. Congratulations!_ [positional-notation]: https://en.wikipedia.org/wiki/Positional_notation ================================================ FILE: exercises/practice/all-your-base/.docs/introduction.md ================================================ # Introduction You've just been hired as professor of mathematics. Your first week went well, but something is off in your second week. The problem is that every answer given by your students is wrong! Luckily, your math skills have allowed you to identify the problem: the student answers _are_ correct, but they're all in base 2 (binary)! Amazingly, it turns out that each week, the students use a different base. To help you quickly verify the student answers, you'll be building a tool to translate between bases. ================================================ FILE: exercises/practice/all-your-base/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "all-your-base.ts" ], "test": [ "all-your-base.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Convert a number, represented as a sequence of digits in one base, to any other base.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false } } ================================================ FILE: exercises/practice/all-your-base/.meta/proof.ci.ts ================================================ const isValidBase = (base: number): boolean => { return !base || base < 2 || Math.floor(base) !== base } const isInputValid = (array: number[], base: number): boolean => { if (!array || !array.length) { return false } const val = base - 1 for (let i = 0, n = array.length; i < n; i++) { const tmp = array[i] if (tmp > val || tmp < 0) { return false } } return true } const convertFromDecimalToBase = ( num: number, outputBase: number ): number[] => { let tmp = num const result = [] while (tmp) { result.unshift(tmp % outputBase) tmp = Math.floor(tmp / outputBase) } return result } export function convert( array: number[], inputBase: number, outputBase: number ): number[] { if (isValidBase(inputBase)) { throw new Error('Wrong input base') } if (isValidBase(outputBase)) { throw new Error('Wrong output base') } const regexp = new RegExp('^0.', 'g') const str = array.join('') if (str.match(regexp) || !isInputValid(array, inputBase)) { throw new Error('Input has wrong format') } if (str === '0') { return [0] } if (str === '1') { return [1] } const decimalValue = array.reduce( (accumulator, value) => accumulator * inputBase + value, 0 ) return convertFromDecimalToBase(decimalValue, outputBase) } ================================================ FILE: exercises/practice/all-your-base/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [5ce422f9-7a4b-4f44-ad29-49c67cb32d2c] description = "single bit one to decimal" [0cc3fea8-bb79-46ac-a2ab-5a2c93051033] description = "binary to single decimal" [f12db0f9-0d3d-42c2-b3ba-e38cb375a2b8] description = "single decimal to binary" [2c45cf54-6da3-4748-9733-5a3c765d925b] description = "binary to multiple decimal" [65ddb8b4-8899-4fcc-8618-181b2cf0002d] description = "decimal to binary" [8d418419-02a7-4824-8b7a-352d33c6987e] description = "trinary to hexadecimal" [d3901c80-8190-41b9-bd86-38d988efa956] description = "hexadecimal to trinary" [5d42f85e-21ad-41bd-b9be-a3e8e4258bbf] description = "15-bit integer" [d68788f7-66dd-43f8-a543-f15b6d233f83] description = "empty list" [5e27e8da-5862-4c5f-b2a9-26c0382b6be7] description = "single zero" [2e1c2573-77e4-4b9c-8517-6c56c5bcfdf2] description = "multiple zeros" [3530cd9f-8d6d-43f5-bc6e-b30b1db9629b] description = "leading zeros" [a6b476a1-1901-4f2a-92c4-4d91917ae023] description = "input base is one" [e21a693a-7a69-450b-b393-27415c26a016] description = "input base is zero" [54a23be5-d99e-41cc-88e0-a650ffe5fcc2] description = "input base is negative" [9eccf60c-dcc9-407b-95d8-c37b8be56bb6] description = "negative digit" [232fa4a5-e761-4939-ba0c-ed046cd0676a] description = "invalid positive digit" [14238f95-45da-41dc-95ce-18f860b30ad3] description = "output base is one" [73dac367-da5c-4a37-95fe-c87fad0a4047] description = "output base is zero" [13f81f42-ff53-4e24-89d9-37603a48ebd9] description = "output base is negative" [0e6c895d-8a5d-4868-a345-309d094cfe8d] description = "both bases are negative" ================================================ FILE: exercises/practice/all-your-base/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/all-your-base/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/all-your-base/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/all-your-base/all-your-base.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { convert } from './all-your-base.ts' describe('Converter', () => { it('single bit one to decimal', () => { expect(convert([1], 2, 10)).toEqual([1]) }) xit('binary to single decimal', () => { expect(convert([1, 0, 1], 2, 10)).toEqual([5]) }) xit('single decimal to binary', () => { expect(convert([5], 10, 2)).toEqual([1, 0, 1]) }) xit('binary to multiple decimal', () => { expect(convert([1, 0, 1, 0, 1, 0], 2, 10)).toEqual([4, 2]) }) xit('decimal to binary', () => { expect(convert([4, 2], 10, 2)).toEqual([1, 0, 1, 0, 1, 0]) }) xit('trinary to hexadecimal', () => { expect(convert([1, 1, 2, 0], 3, 16)).toEqual([2, 10]) }) xit('hexadecimal to trinary', () => { expect(convert([2, 10], 16, 3)).toEqual([1, 1, 2, 0]) }) xit('15-bit integer', () => { expect(convert([3, 46, 60], 97, 73)).toEqual([6, 10, 45]) }) xit('empty list', () => { expect(() => { convert([], 2, 10) }).toThrow('Input has wrong format') }) xit('single zero', () => { expect(convert([0], 10, 2)).toEqual([0]) }) xit('multiple zeros', () => { expect(() => { convert([0, 0, 0], 10, 2) }).toThrow('Input has wrong format') }) xit('leading zeros', () => { expect(() => { convert([0, 6, 0], 7, 10) }).toThrow('Input has wrong format') }) xit('negative digit', () => { expect(() => { convert([1, -1, 1, 0, 1, 0], 2, 10) }).toThrow('Input has wrong format') }) xit('invalid positive digit', () => { expect(() => { convert([1, 2, 1, 0, 1, 0], 2, 10) }).toThrow('Input has wrong format') }) xit('first base is one', () => { expect(() => { convert([], 1, 10) }).toThrow('Wrong input base') }) xit('second base is one', () => { expect(() => { convert([1, 0, 1, 0, 1, 0], 2, 1) }).toThrow('Wrong output base') }) xit('first base is zero', () => { expect(() => { convert([], 0, 10) }).toThrow('Wrong input base') }) xit('second base is zero', () => { expect(() => { convert([7], 10, 0) }).toThrow('Wrong output base') }) xit('first base is negative', () => { expect(() => { convert([1], -2, 10) }).toThrow('Wrong input base') }) xit('second base is negative', () => { expect(() => { convert([1], 2, -7) }).toThrow('Wrong output base') }) xit('both bases are negative', () => { expect(() => { convert([1], -2, -7) }).toThrow('Wrong input base') }) xit('wrong output_base base not integer', () => { expect(() => { convert([0], 3, 2.5) }).toThrow('Wrong output base') }) }) ================================================ FILE: exercises/practice/all-your-base/all-your-base.ts ================================================ export function convert( digits: unknown, inputBase: unknown, outputBase: unknown ): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/all-your-base/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/all-your-base/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/all-your-base/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/all-your-base/package.json ================================================ { "name": "@exercism/typescript-all-your-base", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/all-your-base/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/all-your-base/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/allergies/.docs/instructions.md ================================================ # Instructions Given a person's allergy score, determine whether or not they're allergic to a given item, and their full list of allergies. An allergy test produces a single numeric score which contains the information about all the allergies the person has (that they were tested for). The list of items (and their value) that were tested are: - eggs (1) - peanuts (2) - shellfish (4) - strawberries (8) - tomatoes (16) - chocolate (32) - pollen (64) - cats (128) So if Tom is allergic to peanuts and chocolate, he gets a score of 34. Now, given just that score of 34, your program should be able to say: - Whether Tom is allergic to any one of those allergens listed above. - All the allergens Tom is allergic to. Note: a given score may include allergens **not** listed above (i.e. allergens that score 256, 512, 1024, etc.). Your program should ignore those components of the score. For example, if the allergy score is 257, your program should only report the eggs (1) allergy. ================================================ FILE: exercises/practice/allergies/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "allergies.ts" ], "test": [ "allergies.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a person's allergy score, determine whether or not they're allergic to a given item, and their full list of allergies.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", "source_url": "https://www.turing.edu/" } ================================================ FILE: exercises/practice/allergies/.meta/proof.ci.ts ================================================ export class Allergies { private allergenIndex: number private possibleAllergies = [ 'eggs', 'peanuts', 'shellfish', 'strawberries', 'tomatoes', 'chocolate', 'pollen', 'cats', ] as const constructor(allergenIndex: number) { this.allergenIndex = allergenIndex } public allergicTo(food: string): boolean { let isAllergic = false const allergyList = this.list() for (const allergy of allergyList) { if (allergy === food) { isAllergic = true break } } return isAllergic } public list(): string[] { const possibleAllergies = this.possibleAllergies const allergicTo = [] for (let i = 0; i < possibleAllergies.length; i++) { const allergy = possibleAllergies[i] if (this.allergenIndex & Math.pow(2, i)) { allergicTo.push(allergy) } } return allergicTo } } ================================================ FILE: exercises/practice/allergies/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [17fc7296-2440-4ac4-ad7b-d07c321bc5a0] description = "testing for eggs allergy -> not allergic to anything" [07ced27b-1da5-4c2e-8ae2-cb2791437546] description = "testing for eggs allergy -> allergic only to eggs" [5035b954-b6fa-4b9b-a487-dae69d8c5f96] description = "testing for eggs allergy -> allergic to eggs and something else" [64a6a83a-5723-4b5b-a896-663307403310] description = "testing for eggs allergy -> allergic to something, but not eggs" [90c8f484-456b-41c4-82ba-2d08d93231c6] description = "testing for eggs allergy -> allergic to everything" [d266a59a-fccc-413b-ac53-d57cb1f0db9d] description = "testing for peanuts allergy -> not allergic to anything" [ea210a98-860d-46b2-a5bf-50d8995b3f2a] description = "testing for peanuts allergy -> allergic only to peanuts" [eac69ae9-8d14-4291-ac4b-7fd2c73d3a5b] description = "testing for peanuts allergy -> allergic to peanuts and something else" [9152058c-ce39-4b16-9b1d-283ec6d25085] description = "testing for peanuts allergy -> allergic to something, but not peanuts" [d2d71fd8-63d5-40f9-a627-fbdaf88caeab] description = "testing for peanuts allergy -> allergic to everything" [b948b0a1-cbf7-4b28-a244-73ff56687c80] description = "testing for shellfish allergy -> not allergic to anything" [9ce9a6f3-53e9-4923-85e0-73019047c567] description = "testing for shellfish allergy -> allergic only to shellfish" [b272fca5-57ba-4b00-bd0c-43a737ab2131] description = "testing for shellfish allergy -> allergic to shellfish and something else" [21ef8e17-c227-494e-8e78-470a1c59c3d8] description = "testing for shellfish allergy -> allergic to something, but not shellfish" [cc789c19-2b5e-4c67-b146-625dc8cfa34e] description = "testing for shellfish allergy -> allergic to everything" [651bde0a-2a74-46c4-ab55-02a0906ca2f5] description = "testing for strawberries allergy -> not allergic to anything" [b649a750-9703-4f5f-b7f7-91da2c160ece] description = "testing for strawberries allergy -> allergic only to strawberries" [50f5f8f3-3bac-47e6-8dba-2d94470a4bc6] description = "testing for strawberries allergy -> allergic to strawberries and something else" [23dd6952-88c9-48d7-a7d5-5d0343deb18d] description = "testing for strawberries allergy -> allergic to something, but not strawberries" [74afaae2-13b6-43a2-837a-286cd42e7d7e] description = "testing for strawberries allergy -> allergic to everything" [c49a91ef-6252-415e-907e-a9d26ef61723] description = "testing for tomatoes allergy -> not allergic to anything" [b69c5131-b7d0-41ad-a32c-e1b2cc632df8] description = "testing for tomatoes allergy -> allergic only to tomatoes" [1ca50eb1-f042-4ccf-9050-341521b929ec] description = "testing for tomatoes allergy -> allergic to tomatoes and something else" [e9846baa-456b-4eff-8025-034b9f77bd8e] description = "testing for tomatoes allergy -> allergic to something, but not tomatoes" [b2414f01-f3ad-4965-8391-e65f54dad35f] description = "testing for tomatoes allergy -> allergic to everything" [978467ab-bda4-49f7-b004-1d011ead947c] description = "testing for chocolate allergy -> not allergic to anything" [59cf4e49-06ea-4139-a2c1-d7aad28f8cbc] description = "testing for chocolate allergy -> allergic only to chocolate" [b0a7c07b-2db7-4f73-a180-565e07040ef1] description = "testing for chocolate allergy -> allergic to chocolate and something else" [f5506893-f1ae-482a-b516-7532ba5ca9d2] description = "testing for chocolate allergy -> allergic to something, but not chocolate" [02debb3d-d7e2-4376-a26b-3c974b6595c6] description = "testing for chocolate allergy -> allergic to everything" [17f4a42b-c91e-41b8-8a76-4797886c2d96] description = "testing for pollen allergy -> not allergic to anything" [7696eba7-1837-4488-882a-14b7b4e3e399] description = "testing for pollen allergy -> allergic only to pollen" [9a49aec5-fa1f-405d-889e-4dfc420db2b6] description = "testing for pollen allergy -> allergic to pollen and something else" [3cb8e79f-d108-4712-b620-aa146b1954a9] description = "testing for pollen allergy -> allergic to something, but not pollen" [1dc3fe57-7c68-4043-9d51-5457128744b2] description = "testing for pollen allergy -> allergic to everything" [d3f523d6-3d50-419b-a222-d4dfd62ce314] description = "testing for cats allergy -> not allergic to anything" [eba541c3-c886-42d3-baef-c048cb7fcd8f] description = "testing for cats allergy -> allergic only to cats" [ba718376-26e0-40b7-bbbe-060287637ea5] description = "testing for cats allergy -> allergic to cats and something else" [3c6dbf4a-5277-436f-8b88-15a206f2d6c4] description = "testing for cats allergy -> allergic to something, but not cats" [1faabb05-2b98-4995-9046-d83e4a48a7c1] description = "testing for cats allergy -> allergic to everything" [f9c1b8e7-7dc5-4887-aa93-cebdcc29dd8f] description = "list when: -> no allergies" [9e1a4364-09a6-4d94-990f-541a94a4c1e8] description = "list when: -> just eggs" [8851c973-805e-4283-9e01-d0c0da0e4695] description = "list when: -> just peanuts" [2c8943cb-005e-435f-ae11-3e8fb558ea98] description = "list when: -> just strawberries" [6fa95d26-044c-48a9-8a7b-9ee46ec32c5c] description = "list when: -> eggs and peanuts" [19890e22-f63f-4c5c-a9fb-fb6eacddfe8e] description = "list when: -> more than eggs but not peanuts" [4b68f470-067c-44e4-889f-c9fe28917d2f] description = "list when: -> lots of stuff" [0881b7c5-9efa-4530-91bd-68370d054bc7] description = "list when: -> everything" [12ce86de-b347-42a0-ab7c-2e0570f0c65b] description = "list when: -> no allergen score parts" [93c2df3e-4f55-4fed-8116-7513092819cd] description = "list when: -> no allergen score parts without highest valid score" ================================================ FILE: exercises/practice/allergies/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/allergies/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/allergies/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/allergies/allergies.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { Allergies } from './allergies.ts' describe('allergicTo', () => { it('no allergies means not allergic', () => { const allergies = new Allergies(0) expect(allergies.allergicTo('peanuts')).toBeFalsy() expect(allergies.allergicTo('cats')).toBeFalsy() expect(allergies.allergicTo('strawberries')).toBeFalsy() }) xit('is allergic to eggs', () => { const allergies = new Allergies(1) expect(allergies.allergicTo('eggs')).toBeTruthy() }) xit('allergic to eggs in addition to other stuff', () => { const allergies = new Allergies(5) expect(allergies.allergicTo('eggs')).toBeTruthy() expect(allergies.allergicTo('shellfish')).toBeTruthy() expect(allergies.allergicTo('strawberries')).toBeFalsy() }) }) describe('list', () => { xit('no allergies at all', () => { const allergies = new Allergies(0) const expected: string[] = [] expect(allergies.list()).toEqual(expected) }) xit('allergic to just eggs', () => { const allergies = new Allergies(1) const expected = ['eggs'] expect(allergies.list()).toEqual(expected) }) xit('allergic to just peanuts', () => { const allergies = new Allergies(2) const expected = ['peanuts'] expect(allergies.list()).toEqual(expected) }) xit('allergic to just strawberries', () => { const allergies = new Allergies(8) const expected = ['strawberries'] expect(allergies.list()).toEqual(expected) }) xit('allergic to eggs and peanuts', () => { const allergies = new Allergies(3) const expected = ['eggs', 'peanuts'] expect(allergies.list()).toEqual(expected) }) xit('allergic to more than eggs but not peanuts', () => { const allergies = new Allergies(5) const expected = ['eggs', 'shellfish'] expect(allergies.list()).toEqual(expected) }) xit('allergic to lots of stuff', () => { const allergies = new Allergies(248) const expected = ['strawberries', 'tomatoes', 'chocolate', 'pollen', 'cats'] expect(allergies.list()).toEqual(expected) }) xit('allergic to everything', () => { const allergies = new Allergies(255) const expected = [ 'eggs', 'peanuts', 'shellfish', 'strawberries', 'tomatoes', 'chocolate', 'pollen', 'cats', ] expect(allergies.list()).toEqual(expected) }) xit('ignore non allergen score parts', () => { const allergies = new Allergies(509) const expected = [ 'eggs', 'shellfish', 'strawberries', 'tomatoes', 'chocolate', 'pollen', 'cats', ] expect(allergies.list()).toEqual(expected) }) xit('ignore non allergen score parts, without highest valid score', () => { const allergies = new Allergies(257) const expected = ['eggs'] expect(allergies.list()).toEqual(expected) }) }) ================================================ FILE: exercises/practice/allergies/allergies.ts ================================================ export class Allergies { constructor(allergenIndex: unknown) { throw new Error('Remove this line and implement the function') } public list(): unknown { throw new Error('Remove this line and implement the function') } public allergicTo(allergen: unknown): unknown { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/allergies/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/allergies/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/allergies/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/allergies/package.json ================================================ { "name": "@exercism/typescript-allergies", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/allergies/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/allergies/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/alphametics/.docs/instructions.md ================================================ # Instructions Given an alphametics puzzle, find the correct solution. [Alphametics][alphametics] is a puzzle where letters in words are replaced with numbers. For example `SEND + MORE = MONEY`: ```text S E N D M O R E + ----------- M O N E Y ``` Replacing these with valid numbers gives: ```text 9 5 6 7 1 0 8 5 + ----------- 1 0 6 5 2 ``` This is correct because every letter is replaced by a different number and the words, translated into numbers, then make a valid sum. Each letter must represent a different digit, and the leading digit of a multi-digit number must not be zero. [alphametics]: https://en.wikipedia.org/wiki/Alphametics ================================================ FILE: exercises/practice/alphametics/.meta/config.json ================================================ { "authors": [ "W0lfw00d" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "alphametics.ts" ], "test": [ "alphametics.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given an alphametics puzzle, find the correct solution.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false } } ================================================ FILE: exercises/practice/alphametics/.meta/proof.ci.ts ================================================ export function solve(puzzle: string): undefined | { [key: string]: number } { const parts: string[] = puzzle .split(/[+|==]/g) .map((o) => o.trim()) .filter((o) => o !== '') if (parts.length < 3) { return undefined } const uniqueLetters = new Set(parts.join('').split('')) const firstLetters = new Set(parts.map((p) => p[0])) const numberCombinations: number[][] = getNumberCombinations( [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], uniqueLetters.size ) while (numberCombinations.length) { const permutations = generate( Array(uniqueLetters.size) .fill(Array()) .map((_, i) => i) ) const numberCombination: number[] = numberCombinations.pop() || [] for (const permutation of permutations) { const newNumbers = assignNumbers( numberCombination, uniqueLetters, permutation ) if (testNumbers(newNumbers, parts, firstLetters)) { return newNumbers } } } return undefined } function assignNumbers( numberCombination: number[], uniqueLetters: Set, permutation: number[] ): { [key: string]: number } { const output: { [key: string]: number } = {} let i = 0 for (const letter of uniqueLetters.values()) { output[letter] = numberCombination[permutation[i++]] } return output } function testNumbers( numbers: { [key: string]: number }, puzzleParts: string[], firstLetters: Set ): boolean { const keys: string[] = Object.keys(numbers) for (const key of keys) { if (numbers[key] === 0 && firstLetters.has(key)) { return false } } const replaceRegex = new RegExp(`[${keys.join('')}]`, 'g') const puzzlePartsNumbers: number[] = puzzleParts .join(',') .replace(replaceRegex, (input) => numbers[input].toString()) .split(',') .map((t) => parseInt(t, 10)) const total = puzzlePartsNumbers.slice(puzzlePartsNumbers.length - 1)[0] return ( total === puzzlePartsNumbers .slice(0, puzzleParts.length - 1) .reduce((acc: number, val: number) => acc + val, 0) ) } function* generate(A: number[]): IterableIterator { const c = [] const n = A.length yield A for (let i = 0; i < n; i++) { c[i] = 0 } let i = 0 while (i < n) { if (c[i] < i) { if (i % 2 === 0) { swap(A, 0, i) } else { swap(A, c[i], i) } yield A c[i] += 1 i = 0 } else { c[i] = 0 i += 1 } } } function swap(list: number[], x: number, y: number): number[] { const tmp = list[x] list[x] = list[y] list[y] = tmp return list } function getNumberCombinations(arr: number[], size: number): number[][] { const len = arr.length if (size === len) { return [arr] } return arr.reduce((acc: number[][], val: number, i: number) => { const res: number[][] = getNumberCombinations( arr.slice(i + 1), size - 1 ).map((comb) => [val].concat(comb)) return acc.concat(res) }, []) } ================================================ FILE: exercises/practice/alphametics/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [e0c08b07-9028-4d5f-91e1-d178fead8e1a] description = "puzzle with three letters" [a504ee41-cb92-4ec2-9f11-c37e95ab3f25] description = "solution must have unique value for each letter" [4e3b81d2-be7b-4c5c-9a80-cd72bc6d465a] description = "leading zero solution is invalid" [8a3e3168-d1ee-4df7-94c7-b9c54845ac3a] description = "puzzle with two digits final carry" [a9630645-15bd-48b6-a61e-d85c4021cc09] description = "puzzle with four letters" [3d905a86-5a52-4e4e-bf80-8951535791bd] description = "puzzle with six letters" [4febca56-e7b7-4789-97b9-530d09ba95f0] description = "puzzle with seven letters" [12125a75-7284-4f9a-a5fa-191471e0d44f] description = "puzzle with eight letters" [fb05955f-38dc-477a-a0b6-5ef78969fffa] description = "puzzle with ten letters" [9a101e81-9216-472b-b458-b513a7adacf7] description = "puzzle with ten letters and 199 addends" ================================================ FILE: exercises/practice/alphametics/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/alphametics/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/alphametics/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/alphametics/alphametics.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { solve } from './alphametics.ts' describe('Solve the alphametics puzzle', () => { it('puzzle with three letters', () => { const puzzle = 'I + BB == ILL' const expected = { I: 1, B: 9, L: 0, } expect(solve(puzzle)).toEqual(expected) }) xit('solution must have unique value for each letter', () => { const puzzle = 'A == B' expect(solve(puzzle)).toBeUndefined() }) xit('leading zero solution is invalid', () => { const puzzle = 'ACA + DD == BD' expect(solve(puzzle)).toBeUndefined() }) xit('puzzle with four letters', () => { const puzzle = 'AS + A == MOM' const expected = { A: 9, S: 2, M: 1, O: 0, } expect(solve(puzzle)).toEqual(expected) }) xit('puzzle with six letters', () => { const puzzle = 'NO + NO + TOO == LATE' const expected = { N: 7, O: 4, T: 9, L: 1, A: 0, E: 2, } expect(solve(puzzle)).toEqual(expected) }) xit('puzzle with seven letters', () => { const puzzle = 'HE + SEES + THE == LIGHT' const expected = { E: 4, G: 2, H: 5, I: 0, L: 1, S: 9, T: 7, } expect(solve(puzzle)).toEqual(expected) }) xit('puzzle with eight letters', () => { const puzzle = 'SEND + MORE == MONEY' const expected = { S: 9, E: 5, N: 6, D: 7, M: 1, O: 0, R: 8, Y: 2, } expect(solve(puzzle)).toEqual(expected) }) xit('puzzle with ten letters', () => { const puzzle = 'AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE' const expected = { A: 5, D: 3, E: 4, F: 7, G: 8, N: 0, O: 2, R: 1, S: 6, T: 9, } expect(solve(puzzle)).toEqual(expected) }) }) ================================================ FILE: exercises/practice/alphametics/alphametics.ts ================================================ export function solve(puzzle: unknown): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/alphametics/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/alphametics/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/alphametics/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/alphametics/package.json ================================================ { "name": "@exercism/typescript-alphametics", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/alphametics/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/alphametics/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/anagram/.docs/instructions.append.md ================================================ # Instructions Append The anagrams can be returned in any order. ================================================ FILE: exercises/practice/anagram/.docs/instructions.md ================================================ # Instructions Given a target word and one or more candidate words, your task is to find the candidates that are anagrams of the target. An anagram is a rearrangement of letters to form a new word: for example `"owns"` is an anagram of `"snow"`. A word is _not_ its own anagram: for example, `"stop"` is not an anagram of `"stop"`. The target word and candidate words are made up of one or more ASCII alphabetic characters (`A`-`Z` and `a`-`z`). Lowercase and uppercase characters are equivalent: for example, `"PoTS"` is an anagram of `"sTOp"`, but `"StoP"` is not an anagram of `"sTOp"`. The words you need to find should be taken from the candidate words, using the same letter case. Given the target `"stone"` and the candidate words `"stone"`, `"tones"`, `"banana"`, `"tons"`, `"notes"`, and `"Seton"`, the anagram words you need to find are `"tones"`, `"notes"`, and `"Seton"`. ================================================ FILE: exercises/practice/anagram/.docs/introduction.md ================================================ # Introduction At a garage sale, you find a lovely vintage typewriter at a bargain price! Excitedly, you rush home, insert a sheet of paper, and start typing away. However, your excitement wanes when you examine the output: all words are garbled! For example, it prints "stop" instead of "post" and "least" instead of "stale." Carefully, you try again, but now it prints "spot" and "slate." After some experimentation, you find there is a random delay before each letter is printed, which messes up the order. You now understand why they sold it for so little money! You realize this quirk allows you to generate anagrams, which are words formed by rearranging the letters of another word. Pleased with your finding, you spend the rest of the day generating hundreds of anagrams. ================================================ FILE: exercises/practice/anagram/.meta/config.json ================================================ { "authors": [ "masters3d" ], "contributors": [ "CRivasGomez", "fredrb", "lukaszklis", "SleeplessByte" ], "files": { "solution": [ "anagram.ts" ], "test": [ "anagram.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a word and a list of possible anagrams, select the correct sublist.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Inspired by the Extreme Startup game", "source_url": "https://github.com/rchatley/extreme_startup" } ================================================ FILE: exercises/practice/anagram/.meta/proof.ci.ts ================================================ function sort(input: string): string { return input.toString().toLowerCase().split('').sort().join('') } export class Anagram { private value: string constructor(input: string) { this.value = input } public matches(...input: string[]): string[] { const result: string[] = [] for (const each of input) { if (sort(each) === sort(this.value)) { if (each.toLowerCase() === this.value.toLowerCase()) { continue } result.push(each) } } return result } } ================================================ FILE: exercises/practice/anagram/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [dd40c4d2-3c8b-44e5-992a-f42b393ec373] description = "no matches" [b3cca662-f50a-489e-ae10-ab8290a09bdc] description = "detects two anagrams" include = false [03eb9bbe-8906-4ea0-84fa-ffe711b52c8b] description = "detects two anagrams" reimplements = "b3cca662-f50a-489e-ae10-ab8290a09bdc" [a27558ee-9ba0-4552-96b1-ecf665b06556] description = "does not detect anagram subsets" [64cd4584-fc15-4781-b633-3d814c4941a4] description = "detects anagram" [99c91beb-838f-4ccd-b123-935139917283] description = "detects three anagrams" [78487770-e258-4e1f-a646-8ece10950d90] description = "detects multiple anagrams with different case" [1d0ab8aa-362f-49b7-9902-3d0c668d557b] description = "does not detect non-anagrams with identical checksum" [9e632c0b-c0b1-4804-8cc1-e295dea6d8a8] description = "detects anagrams case-insensitively" [b248e49f-0905-48d2-9c8d-bd02d8c3e392] description = "detects anagrams using case-insensitive subject" [f367325c-78ec-411c-be76-e79047f4bd54] description = "detects anagrams using case-insensitive possible matches" [7cc195ad-e3c7-44ee-9fd2-d3c344806a2c] description = "does not detect an anagram if the original word is repeated" include = false [630abb71-a94e-4715-8395-179ec1df9f91] description = "does not detect an anagram if the original word is repeated" reimplements = "7cc195ad-e3c7-44ee-9fd2-d3c344806a2c" [9878a1c9-d6ea-4235-ae51-3ea2befd6842] description = "anagrams must use all letters exactly once" [85757361-4535-45fd-ac0e-3810d40debc1] description = "words are not anagrams of themselves (case-insensitive)" include = false [68934ed0-010b-4ef9-857a-20c9012d1ebf] description = "words are not anagrams of themselves" reimplements = "85757361-4535-45fd-ac0e-3810d40debc1" [589384f3-4c8a-4e7d-9edc-51c3e5f0c90e] description = "words are not anagrams of themselves even if letter case is partially different" reimplements = "85757361-4535-45fd-ac0e-3810d40debc1" [ba53e423-7e02-41ee-9ae2-71f91e6d18e6] description = "words are not anagrams of themselves even if letter case is completely different" reimplements = "85757361-4535-45fd-ac0e-3810d40debc1" [a0705568-628c-4b55-9798-82e4acde51ca] description = "words other than themselves can be anagrams" include = false [33d3f67e-fbb9-49d3-a90e-0beb00861da7] description = "words other than themselves can be anagrams" reimplements = "a0705568-628c-4b55-9798-82e4acde51ca" [a6854f66-eec1-4afd-a137-62ef2870c051] description = "handles case of greek letters" [fd3509e5-e3ba-409d-ac3d-a9ac84d13296] description = "different characters may have the same bytes" ================================================ FILE: exercises/practice/anagram/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/anagram/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/anagram/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/anagram/anagram.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { Anagram } from './anagram.ts' const areSetsEqual = (setA: Set, setB: Set): boolean => setA.size === setB.size && [...setA].every((val) => setB.has(val)) describe('Anagram', () => { it('no matches', () => { const subject = new Anagram('diaper') const matches = subject.matches('hello', 'world', 'zombies', 'pants') const expected = [] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) xit('detects two anagrams', () => { const subject = new Anagram('solemn') const matches = subject.matches('lemons', 'cherry', 'melons') const expected = ['lemons', 'melons'] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) xit('does not detect anagram subsets', () => { const subject = new Anagram('good') const matches = subject.matches('dog', 'goody') const expected = [] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) xit('detects anagram', () => { const subject = new Anagram('listen') const matches = subject.matches('enlists', 'google', 'inlets', 'banana') const expected = ['inlets'] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) xit('detects three anagrams', () => { const subject = new Anagram('allergy') const matches = subject.matches( 'gallery', 'ballerina', 'regally', 'clergy', 'largely', 'leading' ) const expected = ['gallery', 'regally', 'largely'] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) xit('detects multiple anagrams with different case', () => { const subject = new Anagram('nose') const matches = subject.matches('Eons', 'ONES') const expected = ['Eons', 'ONES'] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) xit('does not detect non-anagrams with identical checksum', () => { const subject = new Anagram('mass') const matches = subject.matches('last') const expected = [] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) xit('detects anagrams case-insensitively', () => { const subject = new Anagram('Orchestra') const matches = subject.matches('cashregister', 'Carthorse', 'radishes') const expected = ['Carthorse'] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) xit('detects anagrams using case-insensitive subject', () => { const subject = new Anagram('Orchestra') const matches = subject.matches('cashregister', 'carthorse', 'radishes') const expected = ['carthorse'] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) xit('detects anagrams using case-insensitive possible matches', () => { const subject = new Anagram('orchestra') const matches = subject.matches('cashregister', 'Carthorse', 'radishes') const expected = ['Carthorse'] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) xit('does not detect an anagram if the original word is repeated', () => { const subject = new Anagram('go') const matches = subject.matches('goGoGO') const expected = [] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) xit('anagrams must use all letters exactly once', () => { const subject = new Anagram('tapper') const matches = subject.matches('patter') const expected = [] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) xit('words are not anagrams of themselves', () => { const subject = new Anagram('BANANA') const matches = subject.matches('BANANA') const expected = [] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) xit('words are not anagrams of themselves even if letter case is partially different', () => { const subject = new Anagram('BANANA') const matches = subject.matches('Banana') const expected = [] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) xit('words are not anagrams of themselves even if letter case is completely different', () => { const subject = new Anagram('BANANA') const matches = subject.matches('Banana') const expected = [] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) xit('words other than themselves can be anagrams', () => { const subject = new Anagram('LISTEN') const matches = subject.matches('LISTEN', 'Silent') const expected = ['Silent'] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) xit('handles case of greek letters', () => { const subject = new Anagram('ΑΒΓ') const matches = subject.matches('ΒΓΑ', 'ΒΓΔ', 'γβα', 'αβγ') const expected = ['ΒΓΑ', 'γβα'] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) xit('different characters may have the same bytes', () => { const subject = new Anagram('a⬂') const matches = subject.matches('€a') const expected = [] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) xit('matches() accepts string arguments', () => { const subject = new Anagram('ant') const matches = subject.matches('stand', 'tan', 'at') const expected = ['tan'] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) xit('matches() accepts single string argument', () => { const subject = new Anagram('ant') const matches = subject.matches('tan') const expected = ['tan'] expect(areSetsEqual(new Set(expected), new Set(matches))).toEqual(true) }) }) ================================================ FILE: exercises/practice/anagram/anagram.ts ================================================ export class Anagram { constructor(input: unknown) { throw new Error('Remove this line and implement the function') } public matches(...potentials: unknown[]): unknown { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/anagram/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/anagram/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/anagram/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/anagram/package.json ================================================ { "name": "@exercism/typescript-anagram", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/anagram/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/anagram/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/armstrong-numbers/.docs/instructions.md ================================================ # Instructions An [Armstrong number][armstrong-number] is a number that is the sum of its own digits each raised to the power of the number of digits. For example: - 9 is an Armstrong number, because `9 = 9^1 = 9` - 10 is _not_ an Armstrong number, because `10 != 1^2 + 0^2 = 1` - 153 is an Armstrong number, because: `153 = 1^3 + 5^3 + 3^3 = 1 + 125 + 27 = 153` - 154 is _not_ an Armstrong number, because: `154 != 1^3 + 5^3 + 4^3 = 1 + 125 + 64 = 190` Write some code to determine whether a number is an Armstrong number. [armstrong-number]: https://en.wikipedia.org/wiki/Narcissistic_number ================================================ FILE: exercises/practice/armstrong-numbers/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "armstrong-numbers.ts" ], "test": [ "armstrong-numbers.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Determine if a number is an Armstrong number.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Narcissistic_number" } ================================================ FILE: exercises/practice/armstrong-numbers/.meta/proof.ci.ts ================================================ export function isArmstrongNumber(input: number | bigint): boolean { const digits = String(input).split('') const sum = digits.reduce((total, current) => { return total + BigInt(parseInt(current, 10)) ** BigInt(digits.length) }, BigInt(0)) // eslint-disable-next-line eqeqeq return sum == input } ================================================ FILE: exercises/practice/armstrong-numbers/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [c1ed103c-258d-45b2-be73-d8c6d9580c7b] description = "Zero is an Armstrong number" [579e8f03-9659-4b85-a1a2-d64350f6b17a] description = "Single-digit numbers are Armstrong numbers" [2d6db9dc-5bf8-4976-a90b-b2c2b9feba60] description = "There are no two-digit Armstrong numbers" [509c087f-e327-4113-a7d2-26a4e9d18283] description = "Three-digit number that is an Armstrong number" [7154547d-c2ce-468d-b214-4cb953b870cf] description = "Three-digit number that is not an Armstrong number" [6bac5b7b-42e9-4ecb-a8b0-4832229aa103] description = "Four-digit number that is an Armstrong number" [eed4b331-af80-45b5-a80b-19c9ea444b2e] description = "Four-digit number that is not an Armstrong number" [f971ced7-8d68-4758-aea1-d4194900b864] description = "Seven-digit number that is an Armstrong number" [7ee45d52-5d35-4fbd-b6f1-5c8cd8a67f18] description = "Seven-digit number that is not an Armstrong number" [5ee2fdf8-334e-4a46-bb8d-e5c19c02c148] description = "Armstrong number containing seven zeroes" [12ffbf10-307a-434e-b4ad-c925680e1dd4] description = "The largest and last Armstrong number" ================================================ FILE: exercises/practice/armstrong-numbers/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/armstrong-numbers/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/armstrong-numbers/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/armstrong-numbers/armstrong-numbers.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { isArmstrongNumber } from './armstrong-numbers.ts' describe('Armstrong Numbers', () => { it('Single-digit numbers are Armstrong numbers', () => { expect(isArmstrongNumber(5)).toBeTruthy() }) xit('There are no two-digit Armstrong numbers', () => { expect(isArmstrongNumber(10)).toBeFalsy() }) xit('Three-digit number that is an Armstrong number', () => { expect(isArmstrongNumber(153)).toBeTruthy() }) xit('Three-digit number that is not an Armstrong number', () => { expect(isArmstrongNumber(100)).toBeFalsy() }) xit('Four-digit number that is an Armstrong number', () => { expect(isArmstrongNumber(9474)).toBeTruthy() }) xit('Four-digit number that is not an Armstrong number', () => { expect(isArmstrongNumber(9475)).toBeFalsy() }) xit('Seven-digit number that is an Armstrong number', () => { expect(isArmstrongNumber(9926315)).toBeTruthy() }) xit('Seven-digit number that is not an Armstrong number', () => { expect(isArmstrongNumber(9926314)).toBeFalsy() }) xit('Armstrong number containing seven zeroes', () => { expect( isArmstrongNumber(BigInt('186709961001538790100634132976990')) ).toBeTruthy() }) xit('The largest and last Armstrong number', () => { expect( isArmstrongNumber(BigInt('115132219018763992565095597973971522401')) ).toBeTruthy() }) }) ================================================ FILE: exercises/practice/armstrong-numbers/armstrong-numbers.ts ================================================ export function isArmstrongNumber(number: unknown): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/armstrong-numbers/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/armstrong-numbers/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/armstrong-numbers/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/armstrong-numbers/package.json ================================================ { "name": "@exercism/typescript-armstrong-numbers", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/armstrong-numbers/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/armstrong-numbers/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/atbash-cipher/.docs/instructions.md ================================================ # Instructions Create an implementation of the Atbash cipher, an ancient encryption system created in the Middle East. The Atbash cipher is a simple substitution cipher that relies on transposing all the letters in the alphabet such that the resulting alphabet is backwards. The first letter is replaced with the last letter, the second with the second-last, and so on. An Atbash cipher for the Latin alphabet would be as follows: ```text Plain: abcdefghijklmnopqrstuvwxyz Cipher: zyxwvutsrqponmlkjihgfedcba ``` It is a very weak cipher because it only has one possible key, and it is a simple mono-alphabetic substitution cipher. However, this may not have been an issue in the cipher's time. Ciphertext is written out in groups of fixed length, the traditional group size being 5 letters, leaving numbers unchanged, and punctuation is excluded. This is to make it harder to guess things based on word boundaries. All text will be encoded as lowercase letters. ## Examples - Encoding `test` gives `gvhg` - Encoding `x123 yes` gives `c123b vh` - Decoding `gvhg` gives `test` - Decoding `gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt` gives `thequickbrownfoxjumpsoverthelazydog` ================================================ FILE: exercises/practice/atbash-cipher/.meta/config.json ================================================ { "authors": [ "bward" ], "contributors": [ "masters3d", "Roshanjossey", "SleeplessByte" ], "files": { "solution": [ "atbash-cipher.ts" ], "test": [ "atbash-cipher.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Create an implementation of the Atbash cipher, an ancient encryption system created in the Middle East.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Atbash" } ================================================ FILE: exercises/practice/atbash-cipher/.meta/proof.ci.ts ================================================ const alphabet = 'abcdefghijklmnopqrstuvwxyz' const numbers = '0123456789' export function encode(plainText: string): string { const lowerCaseLettersOnly = plainText .toLowerCase() .split('') .filter((char) => alphabet.includes(char) || numbers.includes(char)) .join('') return decode(lowerCaseLettersOnly) .split('') .reduce((accumulator: string[], _, index, array) => { if (index % 5 === 0) { accumulator.push(array.slice(index, index + 5).join('')) } return accumulator }, []) .join(' ') } export function decode(cipherText: string): string { return cipherText .split(' ') .join('') .split('') .map((char) => alphabet.includes(char) ? alphabet[25 - alphabet.indexOf(char)] : char ) .join('') } ================================================ FILE: exercises/practice/atbash-cipher/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [2f47ebe1-eab9-4d6b-b3c6-627562a31c77] description = "encode yes" [b4ffe781-ea81-4b74-b268-cc58ba21c739] description = "encode no" [10e48927-24ab-4c4d-9d3f-3067724ace00] description = "encode OMG" [d59b8bc3-509a-4a9a-834c-6f501b98750b] description = "encode spaces" [31d44b11-81b7-4a94-8b43-4af6a2449429] description = "encode mindblowingly" [d503361a-1433-48c0-aae0-d41b5baa33ff] description = "encode numbers" [79c8a2d5-0772-42d4-b41b-531d0b5da926] description = "encode deep thought" [9ca13d23-d32a-4967-a1fd-6100b8742bab] description = "encode all the letters" [bb50e087-7fdf-48e7-9223-284fe7e69851] description = "decode exercism" [ac021097-cd5d-4717-8907-b0814b9e292c] description = "decode a sentence" [18729de3-de74-49b8-b68c-025eaf77f851] description = "decode numbers" [0f30325f-f53b-415d-ad3e-a7a4f63de034] description = "decode all the letters" [39640287-30c6-4c8c-9bac-9d613d1a5674] description = "decode with too many spaces" [b34edf13-34c0-49b5-aa21-0768928000d5] description = "decode with no spaces" ================================================ FILE: exercises/practice/atbash-cipher/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/atbash-cipher/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/atbash-cipher/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/atbash-cipher/atbash-cipher.test.ts ================================================ import { describe, xdescribe, it, expect, xit } from '@jest/globals' import { encode, decode } from './atbash-cipher.ts' describe('AtbashCipher', () => { describe('encoding', () => { it('encode yes', () => { const cipherText = encode('yes') expect(cipherText).toEqual('bvh') }) xit('encode no', () => { const cipherText = encode('no') expect(cipherText).toEqual('ml') }) xit('encode OMG', () => { const cipherText = encode('OMG') expect(cipherText).toEqual('lnt') }) xit('encode spaces', () => { const cipherText = encode('O M G') expect(cipherText).toEqual('lnt') }) xit('encode mindblowingly', () => { const cipherText = encode('mindblowingly') expect(cipherText).toEqual('nrmwy oldrm tob') }) xit('encode numbers', () => { const cipherText = encode('Testing,1 2 3, testing.') expect(cipherText).toEqual('gvhgr mt123 gvhgr mt') }) xit('encode deep thought', () => { const cipherText = encode('Truth is fiction.') expect(cipherText).toEqual('gifgs rhurx grlm') }) xit('encode all the letters', () => { const cipherText = encode('thequickbrownfoxjumpsoverthelazydog') expect(cipherText).toEqual('gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt') }) }) xdescribe('decode', () => { xit('decode exercism', () => { const plainText = decode('vcvix rhn') expect(plainText).toEqual('exercism') }) xit('decode a sentence', () => { const cipherText = decode('zmlyh gzxov rhlug vmzhg vkkrm thglm v') expect(cipherText).toEqual('anobstacleisoftenasteppingstone') }) xit('decode numbers', () => { const plainText = decode('gvhgr mt123 gvhgr mt') expect(plainText).toEqual('testing123testing') }) xit('decode all the letters', () => { const cipherText = decode('gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt') expect(cipherText).toEqual('thequickbrownfoxjumpsoverthelazydog') }) }) }) ================================================ FILE: exercises/practice/atbash-cipher/atbash-cipher.ts ================================================ export function encode(plainText: unknown): unknown { throw new Error('Remove this line and implement the function') } export function decode(cipherText: unknown): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/atbash-cipher/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/atbash-cipher/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/atbash-cipher/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/atbash-cipher/package.json ================================================ { "name": "@exercism/typescript-atbash-cipher", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/atbash-cipher/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/atbash-cipher/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/bank-account/.docs/instructions.md ================================================ # Instructions Your task is to implement bank accounts supporting opening/closing, withdrawals, and deposits of money. As bank accounts can be accessed in many different ways (internet, mobile phones, automatic charges), your bank software must allow accounts to be safely accessed from multiple threads/processes (terminology depends on your programming language) in parallel. For example, there may be many deposits and withdrawals occurring in parallel; you need to ensure there are no [race conditions][wikipedia] between when you read the account balance and set the new balance. It should be possible to close an account; operations against a closed account must fail. [wikipedia]: https://en.wikipedia.org/wiki/Race_condition#In_software ================================================ FILE: exercises/practice/bank-account/.docs/introduction.md ================================================ # Introduction After years of filling out forms and waiting, you've finally acquired your banking license. This means you are now officially eligible to open your own bank, hurray! Your first priority is to get the IT systems up and running. After a day of hard work, you can already open and close accounts, as well as handle withdrawals and deposits. Since you couldn't be bothered writing tests, you invite some friends to help test the system. However, after just five minutes, one of your friends claims they've lost money! While you're confident your code is bug-free, you start looking through the logs to investigate. Ah yes, just as you suspected, your friend is at fault! They shared their test credentials with another friend, and together they conspired to make deposits and withdrawals from the same account _in parallel_. Who would do such a thing? While you argue that it's physically _impossible_ for someone to access their account in parallel, your friend smugly notifies you that the banking rules _require_ you to support this. Thus, no parallel banking support, no go-live signal. Sighing, you create a mental note to work on this tomorrow. This will set your launch date back at _least_ one more day, but well... ================================================ FILE: exercises/practice/bank-account/.meta/config.json ================================================ { "authors": [ "therealowenrees" ], "files": { "solution": [ "bank-account.ts" ], "test": [ "bank-account.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Simulate a bank account supporting opening/closing, withdraws, and deposits of money. Watch out for concurrent transactions!", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false } } ================================================ FILE: exercises/practice/bank-account/.meta/proof.ci.ts ================================================ export class ValueError extends Error { constructor() { super('Bank account error') } } export class BankAccount { private _isOpen: boolean private _balance: number constructor() { this._isOpen = false this._balance = 0 } open(): void { if (this._isOpen) throw new ValueError() this._isOpen = true } close(): void { if (!this._isOpen) throw new ValueError() this._isOpen = false this._balance = 0 } deposit(amount: number): void { if (!this._isOpen || amount < 0) throw new ValueError() this._balance += amount } withdraw(amount: number): void { if (!this._isOpen || amount < 0 || amount > this._balance) throw new ValueError() this._balance -= amount } get balance(): number { if (!this._isOpen) throw new ValueError() return this._balance } } ================================================ FILE: exercises/practice/bank-account/.meta/solutions/bank-account.ts ================================================ export class ValueError extends Error { constructor() { super('Bank account error') } } export class BankAccount { private _isOpen: boolean private _balance: number constructor() { this._isOpen = false this._balance = 0 } open(): void { if (this._isOpen) throw new ValueError() this._isOpen = true } close(): void { if (!this._isOpen) throw new ValueError() this._isOpen = false this._balance = 0 } deposit(amount: number): void { if (!this._isOpen || amount < 0) throw new ValueError() this._balance += amount } withdraw(amount: number): void { if (!this._isOpen || amount < 0 || amount > this._balance) throw new ValueError() this._balance -= amount } get balance(): number { if (!this._isOpen) throw new ValueError() return this._balance } } ================================================ FILE: exercises/practice/bank-account/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [983a1528-4ceb-45e5-8257-8ce01aceb5ed] description = "Newly opened account has zero balance" [e88d4ec3-c6bf-4752-8e59-5046c44e3ba7] description = "Single deposit" [3d9147d4-63f4-4844-8d2b-1fee2e9a2a0d] description = "Multiple deposits" [08f1af07-27ae-4b38-aa19-770bde558064] description = "Withdraw once" [6f6d242f-8c31-4ac6-8995-a90d42cad59f] description = "Withdraw twice" [45161c94-a094-4c77-9cec-998b70429bda] description = "Can do multiple operations sequentially" [f9facfaa-d824-486e-8381-48832c4bbffd] description = "Cannot check balance of closed account" [7a65ba52-e35c-4fd2-8159-bda2bde6e59c] description = "Cannot deposit into closed account" [a0a1835d-faae-4ad4-a6f3-1fcc2121380b] description = "Cannot deposit into unopened account" [570dfaa5-0532-4c1f-a7d3-0f65c3265608] description = "Cannot withdraw from closed account" [c396d233-1c49-4272-98dc-7f502dbb9470] description = "Cannot close an account that was not opened" [c06f534f-bdc2-4a02-a388-1063400684de] description = "Cannot open an already opened account" [0722d404-6116-4f92-ba3b-da7f88f1669c] description = "Reopened account does not retain balance" [ec42245f-9361-4341-8231-a22e8d19c52f] description = "Cannot withdraw more than deposited" [4f381ef8-10ef-4507-8e1d-0631ecc8ee72] description = "Cannot withdraw negative" [d45df9ea-1db0-47f3-b18c-d365db49d938] description = "Cannot deposit negative" [ba0c1e0b-0f00-416f-8097-a7dfc97871ff] description = "Can handle concurrent transactions" ================================================ FILE: exercises/practice/bank-account/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/bank-account/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/bank-account/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/bank-account/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/bank-account/bank-account.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { BankAccount, ValueError } from './bank-account.ts' describe('Bank Account', () => { it('newly opened account has zero balance', () => { const account = new BankAccount() account.open() expect(account.balance).toEqual(0) }) xit('can deposit money', () => { const account = new BankAccount() account.open() account.deposit(100) expect(account.balance).toEqual(100) }) xit('can deposit money sequentially', () => { const account = new BankAccount() account.open() account.deposit(100) account.deposit(50) expect(account.balance).toEqual(150) }) xit('can withdraw money', () => { const account = new BankAccount() account.open() account.deposit(100) account.withdraw(50) expect(account.balance).toEqual(50) }) xit('can withdraw money sequentially', () => { const account = new BankAccount() account.open() account.deposit(100) account.withdraw(20) account.withdraw(80) expect(account.balance).toEqual(0) }) xit('checking balance of closed account throws error', () => { const account = new BankAccount() account.open() account.close() expect(() => account.balance).toThrow(ValueError) }) xit('deposit into closed account throws error', () => { const account = new BankAccount() account.open() account.close() expect(() => { account.deposit(50) }).toThrow(ValueError) }) xit('withdraw from closed account throws error', () => { const account = new BankAccount() account.open() account.close() expect(() => { account.withdraw(50) }).toThrow(ValueError) }) xit('close already closed account throws error', () => { const account = new BankAccount() expect(() => { account.close() }).toThrow(ValueError) }) xit('open already opened account throws error', () => { const account = new BankAccount() account.open() expect(() => { account.open() }).toThrow(ValueError) }) xit('reopened account does not retain balance', () => { const account = new BankAccount() account.open() account.deposit(50) account.close() account.open() expect(account.balance).toEqual(0) }) xit('cannot withdraw more than deposited', () => { const account = new BankAccount() account.open() account.deposit(25) expect(() => { account.withdraw(50) }).toThrow(ValueError) }) xit('cannot withdraw negative amount', () => { const account = new BankAccount() account.open() account.deposit(100) expect(() => { account.withdraw(-50) }).toThrow(ValueError) }) xit('cannot deposit negative amount', () => { const account = new BankAccount() account.open() expect(() => { account.deposit(-50) }).toThrow(ValueError) }) xit('changing balance directly throws error', () => { const account = new BankAccount() account.open() expect(() => { // @ts-expect-error This is supposed to be a read-only property account.balance = 100 }).toThrow(Error) }) }) ================================================ FILE: exercises/practice/bank-account/bank-account.ts ================================================ // // This is only a SKELETON file for the 'Bank Account' exercise. It's been provided as a // convenience to get you started writing code faster. // export class ValueError extends Error { constructor() { super('Bank account error') } } export class BankAccount { constructor() { throw new Error('Remove this line and implement the function') } open(): unknown { throw new Error('Remove this line and implement the function') } close(): unknown { throw new Error('Remove this line and implement the function') } deposit(_argument: unknown): unknown { throw new Error('Remove this line and implement the function') } withdraw(_argument: unknown): unknown { throw new Error('Remove this line and implement the function') } get balance(): unknown { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/bank-account/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/bank-account/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/bank-account/package.json ================================================ { "name": "@exercism/typescript-bank-account", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/bank-account/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/bank-account/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/beer-song/.docs/instructions.md ================================================ # Instructions Recite the lyrics to that beloved classic, that field-trip favorite: 99 Bottles of Beer on the Wall. Note that not all verses are identical. ```text 99 bottles of beer on the wall, 99 bottles of beer. Take one down and pass it around, 98 bottles of beer on the wall. 98 bottles of beer on the wall, 98 bottles of beer. Take one down and pass it around, 97 bottles of beer on the wall. 97 bottles of beer on the wall, 97 bottles of beer. Take one down and pass it around, 96 bottles of beer on the wall. 96 bottles of beer on the wall, 96 bottles of beer. Take one down and pass it around, 95 bottles of beer on the wall. 95 bottles of beer on the wall, 95 bottles of beer. Take one down and pass it around, 94 bottles of beer on the wall. 94 bottles of beer on the wall, 94 bottles of beer. Take one down and pass it around, 93 bottles of beer on the wall. 93 bottles of beer on the wall, 93 bottles of beer. Take one down and pass it around, 92 bottles of beer on the wall. 92 bottles of beer on the wall, 92 bottles of beer. Take one down and pass it around, 91 bottles of beer on the wall. 91 bottles of beer on the wall, 91 bottles of beer. Take one down and pass it around, 90 bottles of beer on the wall. 90 bottles of beer on the wall, 90 bottles of beer. Take one down and pass it around, 89 bottles of beer on the wall. 89 bottles of beer on the wall, 89 bottles of beer. Take one down and pass it around, 88 bottles of beer on the wall. 88 bottles of beer on the wall, 88 bottles of beer. Take one down and pass it around, 87 bottles of beer on the wall. 87 bottles of beer on the wall, 87 bottles of beer. Take one down and pass it around, 86 bottles of beer on the wall. 86 bottles of beer on the wall, 86 bottles of beer. Take one down and pass it around, 85 bottles of beer on the wall. 85 bottles of beer on the wall, 85 bottles of beer. Take one down and pass it around, 84 bottles of beer on the wall. 84 bottles of beer on the wall, 84 bottles of beer. Take one down and pass it around, 83 bottles of beer on the wall. 83 bottles of beer on the wall, 83 bottles of beer. Take one down and pass it around, 82 bottles of beer on the wall. 82 bottles of beer on the wall, 82 bottles of beer. Take one down and pass it around, 81 bottles of beer on the wall. 81 bottles of beer on the wall, 81 bottles of beer. Take one down and pass it around, 80 bottles of beer on the wall. 80 bottles of beer on the wall, 80 bottles of beer. Take one down and pass it around, 79 bottles of beer on the wall. 79 bottles of beer on the wall, 79 bottles of beer. Take one down and pass it around, 78 bottles of beer on the wall. 78 bottles of beer on the wall, 78 bottles of beer. Take one down and pass it around, 77 bottles of beer on the wall. 77 bottles of beer on the wall, 77 bottles of beer. Take one down and pass it around, 76 bottles of beer on the wall. 76 bottles of beer on the wall, 76 bottles of beer. Take one down and pass it around, 75 bottles of beer on the wall. 75 bottles of beer on the wall, 75 bottles of beer. Take one down and pass it around, 74 bottles of beer on the wall. 74 bottles of beer on the wall, 74 bottles of beer. Take one down and pass it around, 73 bottles of beer on the wall. 73 bottles of beer on the wall, 73 bottles of beer. Take one down and pass it around, 72 bottles of beer on the wall. 72 bottles of beer on the wall, 72 bottles of beer. Take one down and pass it around, 71 bottles of beer on the wall. 71 bottles of beer on the wall, 71 bottles of beer. Take one down and pass it around, 70 bottles of beer on the wall. 70 bottles of beer on the wall, 70 bottles of beer. Take one down and pass it around, 69 bottles of beer on the wall. 69 bottles of beer on the wall, 69 bottles of beer. Take one down and pass it around, 68 bottles of beer on the wall. 68 bottles of beer on the wall, 68 bottles of beer. Take one down and pass it around, 67 bottles of beer on the wall. 67 bottles of beer on the wall, 67 bottles of beer. Take one down and pass it around, 66 bottles of beer on the wall. 66 bottles of beer on the wall, 66 bottles of beer. Take one down and pass it around, 65 bottles of beer on the wall. 65 bottles of beer on the wall, 65 bottles of beer. Take one down and pass it around, 64 bottles of beer on the wall. 64 bottles of beer on the wall, 64 bottles of beer. Take one down and pass it around, 63 bottles of beer on the wall. 63 bottles of beer on the wall, 63 bottles of beer. Take one down and pass it around, 62 bottles of beer on the wall. 62 bottles of beer on the wall, 62 bottles of beer. Take one down and pass it around, 61 bottles of beer on the wall. 61 bottles of beer on the wall, 61 bottles of beer. Take one down and pass it around, 60 bottles of beer on the wall. 60 bottles of beer on the wall, 60 bottles of beer. Take one down and pass it around, 59 bottles of beer on the wall. 59 bottles of beer on the wall, 59 bottles of beer. Take one down and pass it around, 58 bottles of beer on the wall. 58 bottles of beer on the wall, 58 bottles of beer. Take one down and pass it around, 57 bottles of beer on the wall. 57 bottles of beer on the wall, 57 bottles of beer. Take one down and pass it around, 56 bottles of beer on the wall. 56 bottles of beer on the wall, 56 bottles of beer. Take one down and pass it around, 55 bottles of beer on the wall. 55 bottles of beer on the wall, 55 bottles of beer. Take one down and pass it around, 54 bottles of beer on the wall. 54 bottles of beer on the wall, 54 bottles of beer. Take one down and pass it around, 53 bottles of beer on the wall. 53 bottles of beer on the wall, 53 bottles of beer. Take one down and pass it around, 52 bottles of beer on the wall. 52 bottles of beer on the wall, 52 bottles of beer. Take one down and pass it around, 51 bottles of beer on the wall. 51 bottles of beer on the wall, 51 bottles of beer. Take one down and pass it around, 50 bottles of beer on the wall. 50 bottles of beer on the wall, 50 bottles of beer. Take one down and pass it around, 49 bottles of beer on the wall. 49 bottles of beer on the wall, 49 bottles of beer. Take one down and pass it around, 48 bottles of beer on the wall. 48 bottles of beer on the wall, 48 bottles of beer. Take one down and pass it around, 47 bottles of beer on the wall. 47 bottles of beer on the wall, 47 bottles of beer. Take one down and pass it around, 46 bottles of beer on the wall. 46 bottles of beer on the wall, 46 bottles of beer. Take one down and pass it around, 45 bottles of beer on the wall. 45 bottles of beer on the wall, 45 bottles of beer. Take one down and pass it around, 44 bottles of beer on the wall. 44 bottles of beer on the wall, 44 bottles of beer. Take one down and pass it around, 43 bottles of beer on the wall. 43 bottles of beer on the wall, 43 bottles of beer. Take one down and pass it around, 42 bottles of beer on the wall. 42 bottles of beer on the wall, 42 bottles of beer. Take one down and pass it around, 41 bottles of beer on the wall. 41 bottles of beer on the wall, 41 bottles of beer. Take one down and pass it around, 40 bottles of beer on the wall. 40 bottles of beer on the wall, 40 bottles of beer. Take one down and pass it around, 39 bottles of beer on the wall. 39 bottles of beer on the wall, 39 bottles of beer. Take one down and pass it around, 38 bottles of beer on the wall. 38 bottles of beer on the wall, 38 bottles of beer. Take one down and pass it around, 37 bottles of beer on the wall. 37 bottles of beer on the wall, 37 bottles of beer. Take one down and pass it around, 36 bottles of beer on the wall. 36 bottles of beer on the wall, 36 bottles of beer. Take one down and pass it around, 35 bottles of beer on the wall. 35 bottles of beer on the wall, 35 bottles of beer. Take one down and pass it around, 34 bottles of beer on the wall. 34 bottles of beer on the wall, 34 bottles of beer. Take one down and pass it around, 33 bottles of beer on the wall. 33 bottles of beer on the wall, 33 bottles of beer. Take one down and pass it around, 32 bottles of beer on the wall. 32 bottles of beer on the wall, 32 bottles of beer. Take one down and pass it around, 31 bottles of beer on the wall. 31 bottles of beer on the wall, 31 bottles of beer. Take one down and pass it around, 30 bottles of beer on the wall. 30 bottles of beer on the wall, 30 bottles of beer. Take one down and pass it around, 29 bottles of beer on the wall. 29 bottles of beer on the wall, 29 bottles of beer. Take one down and pass it around, 28 bottles of beer on the wall. 28 bottles of beer on the wall, 28 bottles of beer. Take one down and pass it around, 27 bottles of beer on the wall. 27 bottles of beer on the wall, 27 bottles of beer. Take one down and pass it around, 26 bottles of beer on the wall. 26 bottles of beer on the wall, 26 bottles of beer. Take one down and pass it around, 25 bottles of beer on the wall. 25 bottles of beer on the wall, 25 bottles of beer. Take one down and pass it around, 24 bottles of beer on the wall. 24 bottles of beer on the wall, 24 bottles of beer. Take one down and pass it around, 23 bottles of beer on the wall. 23 bottles of beer on the wall, 23 bottles of beer. Take one down and pass it around, 22 bottles of beer on the wall. 22 bottles of beer on the wall, 22 bottles of beer. Take one down and pass it around, 21 bottles of beer on the wall. 21 bottles of beer on the wall, 21 bottles of beer. Take one down and pass it around, 20 bottles of beer on the wall. 20 bottles of beer on the wall, 20 bottles of beer. Take one down and pass it around, 19 bottles of beer on the wall. 19 bottles of beer on the wall, 19 bottles of beer. Take one down and pass it around, 18 bottles of beer on the wall. 18 bottles of beer on the wall, 18 bottles of beer. Take one down and pass it around, 17 bottles of beer on the wall. 17 bottles of beer on the wall, 17 bottles of beer. Take one down and pass it around, 16 bottles of beer on the wall. 16 bottles of beer on the wall, 16 bottles of beer. Take one down and pass it around, 15 bottles of beer on the wall. 15 bottles of beer on the wall, 15 bottles of beer. Take one down and pass it around, 14 bottles of beer on the wall. 14 bottles of beer on the wall, 14 bottles of beer. Take one down and pass it around, 13 bottles of beer on the wall. 13 bottles of beer on the wall, 13 bottles of beer. Take one down and pass it around, 12 bottles of beer on the wall. 12 bottles of beer on the wall, 12 bottles of beer. Take one down and pass it around, 11 bottles of beer on the wall. 11 bottles of beer on the wall, 11 bottles of beer. Take one down and pass it around, 10 bottles of beer on the wall. 10 bottles of beer on the wall, 10 bottles of beer. Take one down and pass it around, 9 bottles of beer on the wall. 9 bottles of beer on the wall, 9 bottles of beer. Take one down and pass it around, 8 bottles of beer on the wall. 8 bottles of beer on the wall, 8 bottles of beer. Take one down and pass it around, 7 bottles of beer on the wall. 7 bottles of beer on the wall, 7 bottles of beer. Take one down and pass it around, 6 bottles of beer on the wall. 6 bottles of beer on the wall, 6 bottles of beer. Take one down and pass it around, 5 bottles of beer on the wall. 5 bottles of beer on the wall, 5 bottles of beer. Take one down and pass it around, 4 bottles of beer on the wall. 4 bottles of beer on the wall, 4 bottles of beer. Take one down and pass it around, 3 bottles of beer on the wall. 3 bottles of beer on the wall, 3 bottles of beer. Take one down and pass it around, 2 bottles of beer on the wall. 2 bottles of beer on the wall, 2 bottles of beer. Take one down and pass it around, 1 bottle of beer on the wall. 1 bottle of beer on the wall, 1 bottle of beer. Take it down and pass it around, no more bottles of beer on the wall. No more bottles of beer on the wall, no more bottles of beer. Go to the store and buy some more, 99 bottles of beer on the wall. ``` ================================================ FILE: exercises/practice/beer-song/.meta/config.json ================================================ { "authors": [ "masters3d" ], "contributors": [ "lukaszklis", "SleeplessByte" ], "files": { "solution": [ "beer-song.ts" ], "test": [ "beer-song.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Produce the lyrics to that beloved classic, that field-trip favorite: 99 Bottles of Beer on the Wall.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Learn to Program by Chris Pine", "source_url": "https://pine.fm/LearnToProgram/?Chapter=06" } ================================================ FILE: exercises/practice/beer-song/.meta/proof.ci.ts ================================================ function pluralize(input: number): string { if (input === 0) { return 'o more bottles ' } return input === 1 ? '1 bottle ' : `${input} bottles ` } export function verse(input: number): string { const wall = 'of beer on the wall' if (input === 0) { return `N${pluralize(0)}${wall}, n${pluralize(0)}of beer. Go to the store and buy some more, ${pluralize(99)}${wall}. ` } if (input === 1) { return `${pluralize(1)}${wall}, ${pluralize(1)}of beer. Take it down and pass it around, n${pluralize(0)}${wall}. ` } return `${pluralize(input)}${wall}, ${pluralize(input)}of beer. Take one down and pass it around, ${pluralize(input - 1)}${wall}. ` } export function sing(end: number = 99, start: number = 0): string { let temp = `` for (let i: number = end; i >= start; i -= 1) { temp += verse(i) if (i !== start) { temp += '\n' } } return temp } ================================================ FILE: exercises/practice/beer-song/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [5a02fd08-d336-4607-8006-246fe6fa9fb0] description = "first generic verse" [77299ca6-545e-4217-a9cc-606b342e0187] description = "last generic verse" [102cbca0-b197-40fd-b548-e99609b06428] description = "verse with 2 bottles" [b8ef9fce-960e-4d85-a0c9-980a04ec1972] description = "verse with 1 bottle" [c59d4076-f671-4ee3-baaa-d4966801f90d] description = "verse with 0 bottles" [7e17c794-402d-4ca6-8f96-4d8f6ee1ec7e] description = "first two verses" [949868e7-67e8-43d3-9bb4-69277fe020fb] description = "last three verses" [bc220626-126c-4e72-8df4-fddfc0c3e458] description = "all verses" ================================================ FILE: exercises/practice/beer-song/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/beer-song/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/beer-song/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/beer-song/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/beer-song/beer-song.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { verse, sing } from './beer-song.ts' describe('Beer', () => { it('prints an arbitrary verse', () => { const expected = `8 bottles of beer on the wall, 8 bottles of beer. Take one down and pass it around, 7 bottles of beer on the wall. ` expect(verse(8)).toEqual(expected) }) xit('handles 1 bottle', () => { const expected = `1 bottle of beer on the wall, 1 bottle of beer. Take it down and pass it around, no more bottles of beer on the wall. ` expect(verse(1)).toEqual(expected) }) xit('handles 0 bottles', () => { const expected = `No more bottles of beer on the wall, no more bottles of beer. Go to the store and buy some more, 99 bottles of beer on the wall. ` expect(verse(0)).toEqual(expected) }) xit('sings several verses', () => { const expected = `8 bottles of beer on the wall, 8 bottles of beer. Take one down and pass it around, 7 bottles of beer on the wall. 7 bottles of beer on the wall, 7 bottles of beer. Take one down and pass it around, 6 bottles of beer on the wall. 6 bottles of beer on the wall, 6 bottles of beer. Take one down and pass it around, 5 bottles of beer on the wall. ` expect(sing(8, 6)).toEqual(expected) }) xit('sings the rest of the verses', () => { const expected = `3 bottles of beer on the wall, 3 bottles of beer. Take one down and pass it around, 2 bottles of beer on the wall. 2 bottles of beer on the wall, 2 bottles of beer. Take one down and pass it around, 1 bottle of beer on the wall. 1 bottle of beer on the wall, 1 bottle of beer. Take it down and pass it around, no more bottles of beer on the wall. No more bottles of beer on the wall, no more bottles of beer. Go to the store and buy some more, 99 bottles of beer on the wall. ` expect(sing(3)).toEqual(expected) }) xit('sings all the verses', () => { const song = sing() expect(song).toEqual(`99 bottles of beer on the wall, 99 bottles of beer. Take one down and pass it around, 98 bottles of beer on the wall. 98 bottles of beer on the wall, 98 bottles of beer. Take one down and pass it around, 97 bottles of beer on the wall. 97 bottles of beer on the wall, 97 bottles of beer. Take one down and pass it around, 96 bottles of beer on the wall. 96 bottles of beer on the wall, 96 bottles of beer. Take one down and pass it around, 95 bottles of beer on the wall. 95 bottles of beer on the wall, 95 bottles of beer. Take one down and pass it around, 94 bottles of beer on the wall. 94 bottles of beer on the wall, 94 bottles of beer. Take one down and pass it around, 93 bottles of beer on the wall. 93 bottles of beer on the wall, 93 bottles of beer. Take one down and pass it around, 92 bottles of beer on the wall. 92 bottles of beer on the wall, 92 bottles of beer. Take one down and pass it around, 91 bottles of beer on the wall. 91 bottles of beer on the wall, 91 bottles of beer. Take one down and pass it around, 90 bottles of beer on the wall. 90 bottles of beer on the wall, 90 bottles of beer. Take one down and pass it around, 89 bottles of beer on the wall. 89 bottles of beer on the wall, 89 bottles of beer. Take one down and pass it around, 88 bottles of beer on the wall. 88 bottles of beer on the wall, 88 bottles of beer. Take one down and pass it around, 87 bottles of beer on the wall. 87 bottles of beer on the wall, 87 bottles of beer. Take one down and pass it around, 86 bottles of beer on the wall. 86 bottles of beer on the wall, 86 bottles of beer. Take one down and pass it around, 85 bottles of beer on the wall. 85 bottles of beer on the wall, 85 bottles of beer. Take one down and pass it around, 84 bottles of beer on the wall. 84 bottles of beer on the wall, 84 bottles of beer. Take one down and pass it around, 83 bottles of beer on the wall. 83 bottles of beer on the wall, 83 bottles of beer. Take one down and pass it around, 82 bottles of beer on the wall. 82 bottles of beer on the wall, 82 bottles of beer. Take one down and pass it around, 81 bottles of beer on the wall. 81 bottles of beer on the wall, 81 bottles of beer. Take one down and pass it around, 80 bottles of beer on the wall. 80 bottles of beer on the wall, 80 bottles of beer. Take one down and pass it around, 79 bottles of beer on the wall. 79 bottles of beer on the wall, 79 bottles of beer. Take one down and pass it around, 78 bottles of beer on the wall. 78 bottles of beer on the wall, 78 bottles of beer. Take one down and pass it around, 77 bottles of beer on the wall. 77 bottles of beer on the wall, 77 bottles of beer. Take one down and pass it around, 76 bottles of beer on the wall. 76 bottles of beer on the wall, 76 bottles of beer. Take one down and pass it around, 75 bottles of beer on the wall. 75 bottles of beer on the wall, 75 bottles of beer. Take one down and pass it around, 74 bottles of beer on the wall. 74 bottles of beer on the wall, 74 bottles of beer. Take one down and pass it around, 73 bottles of beer on the wall. 73 bottles of beer on the wall, 73 bottles of beer. Take one down and pass it around, 72 bottles of beer on the wall. 72 bottles of beer on the wall, 72 bottles of beer. Take one down and pass it around, 71 bottles of beer on the wall. 71 bottles of beer on the wall, 71 bottles of beer. Take one down and pass it around, 70 bottles of beer on the wall. 70 bottles of beer on the wall, 70 bottles of beer. Take one down and pass it around, 69 bottles of beer on the wall. 69 bottles of beer on the wall, 69 bottles of beer. Take one down and pass it around, 68 bottles of beer on the wall. 68 bottles of beer on the wall, 68 bottles of beer. Take one down and pass it around, 67 bottles of beer on the wall. 67 bottles of beer on the wall, 67 bottles of beer. Take one down and pass it around, 66 bottles of beer on the wall. 66 bottles of beer on the wall, 66 bottles of beer. Take one down and pass it around, 65 bottles of beer on the wall. 65 bottles of beer on the wall, 65 bottles of beer. Take one down and pass it around, 64 bottles of beer on the wall. 64 bottles of beer on the wall, 64 bottles of beer. Take one down and pass it around, 63 bottles of beer on the wall. 63 bottles of beer on the wall, 63 bottles of beer. Take one down and pass it around, 62 bottles of beer on the wall. 62 bottles of beer on the wall, 62 bottles of beer. Take one down and pass it around, 61 bottles of beer on the wall. 61 bottles of beer on the wall, 61 bottles of beer. Take one down and pass it around, 60 bottles of beer on the wall. 60 bottles of beer on the wall, 60 bottles of beer. Take one down and pass it around, 59 bottles of beer on the wall. 59 bottles of beer on the wall, 59 bottles of beer. Take one down and pass it around, 58 bottles of beer on the wall. 58 bottles of beer on the wall, 58 bottles of beer. Take one down and pass it around, 57 bottles of beer on the wall. 57 bottles of beer on the wall, 57 bottles of beer. Take one down and pass it around, 56 bottles of beer on the wall. 56 bottles of beer on the wall, 56 bottles of beer. Take one down and pass it around, 55 bottles of beer on the wall. 55 bottles of beer on the wall, 55 bottles of beer. Take one down and pass it around, 54 bottles of beer on the wall. 54 bottles of beer on the wall, 54 bottles of beer. Take one down and pass it around, 53 bottles of beer on the wall. 53 bottles of beer on the wall, 53 bottles of beer. Take one down and pass it around, 52 bottles of beer on the wall. 52 bottles of beer on the wall, 52 bottles of beer. Take one down and pass it around, 51 bottles of beer on the wall. 51 bottles of beer on the wall, 51 bottles of beer. Take one down and pass it around, 50 bottles of beer on the wall. 50 bottles of beer on the wall, 50 bottles of beer. Take one down and pass it around, 49 bottles of beer on the wall. 49 bottles of beer on the wall, 49 bottles of beer. Take one down and pass it around, 48 bottles of beer on the wall. 48 bottles of beer on the wall, 48 bottles of beer. Take one down and pass it around, 47 bottles of beer on the wall. 47 bottles of beer on the wall, 47 bottles of beer. Take one down and pass it around, 46 bottles of beer on the wall. 46 bottles of beer on the wall, 46 bottles of beer. Take one down and pass it around, 45 bottles of beer on the wall. 45 bottles of beer on the wall, 45 bottles of beer. Take one down and pass it around, 44 bottles of beer on the wall. 44 bottles of beer on the wall, 44 bottles of beer. Take one down and pass it around, 43 bottles of beer on the wall. 43 bottles of beer on the wall, 43 bottles of beer. Take one down and pass it around, 42 bottles of beer on the wall. 42 bottles of beer on the wall, 42 bottles of beer. Take one down and pass it around, 41 bottles of beer on the wall. 41 bottles of beer on the wall, 41 bottles of beer. Take one down and pass it around, 40 bottles of beer on the wall. 40 bottles of beer on the wall, 40 bottles of beer. Take one down and pass it around, 39 bottles of beer on the wall. 39 bottles of beer on the wall, 39 bottles of beer. Take one down and pass it around, 38 bottles of beer on the wall. 38 bottles of beer on the wall, 38 bottles of beer. Take one down and pass it around, 37 bottles of beer on the wall. 37 bottles of beer on the wall, 37 bottles of beer. Take one down and pass it around, 36 bottles of beer on the wall. 36 bottles of beer on the wall, 36 bottles of beer. Take one down and pass it around, 35 bottles of beer on the wall. 35 bottles of beer on the wall, 35 bottles of beer. Take one down and pass it around, 34 bottles of beer on the wall. 34 bottles of beer on the wall, 34 bottles of beer. Take one down and pass it around, 33 bottles of beer on the wall. 33 bottles of beer on the wall, 33 bottles of beer. Take one down and pass it around, 32 bottles of beer on the wall. 32 bottles of beer on the wall, 32 bottles of beer. Take one down and pass it around, 31 bottles of beer on the wall. 31 bottles of beer on the wall, 31 bottles of beer. Take one down and pass it around, 30 bottles of beer on the wall. 30 bottles of beer on the wall, 30 bottles of beer. Take one down and pass it around, 29 bottles of beer on the wall. 29 bottles of beer on the wall, 29 bottles of beer. Take one down and pass it around, 28 bottles of beer on the wall. 28 bottles of beer on the wall, 28 bottles of beer. Take one down and pass it around, 27 bottles of beer on the wall. 27 bottles of beer on the wall, 27 bottles of beer. Take one down and pass it around, 26 bottles of beer on the wall. 26 bottles of beer on the wall, 26 bottles of beer. Take one down and pass it around, 25 bottles of beer on the wall. 25 bottles of beer on the wall, 25 bottles of beer. Take one down and pass it around, 24 bottles of beer on the wall. 24 bottles of beer on the wall, 24 bottles of beer. Take one down and pass it around, 23 bottles of beer on the wall. 23 bottles of beer on the wall, 23 bottles of beer. Take one down and pass it around, 22 bottles of beer on the wall. 22 bottles of beer on the wall, 22 bottles of beer. Take one down and pass it around, 21 bottles of beer on the wall. 21 bottles of beer on the wall, 21 bottles of beer. Take one down and pass it around, 20 bottles of beer on the wall. 20 bottles of beer on the wall, 20 bottles of beer. Take one down and pass it around, 19 bottles of beer on the wall. 19 bottles of beer on the wall, 19 bottles of beer. Take one down and pass it around, 18 bottles of beer on the wall. 18 bottles of beer on the wall, 18 bottles of beer. Take one down and pass it around, 17 bottles of beer on the wall. 17 bottles of beer on the wall, 17 bottles of beer. Take one down and pass it around, 16 bottles of beer on the wall. 16 bottles of beer on the wall, 16 bottles of beer. Take one down and pass it around, 15 bottles of beer on the wall. 15 bottles of beer on the wall, 15 bottles of beer. Take one down and pass it around, 14 bottles of beer on the wall. 14 bottles of beer on the wall, 14 bottles of beer. Take one down and pass it around, 13 bottles of beer on the wall. 13 bottles of beer on the wall, 13 bottles of beer. Take one down and pass it around, 12 bottles of beer on the wall. 12 bottles of beer on the wall, 12 bottles of beer. Take one down and pass it around, 11 bottles of beer on the wall. 11 bottles of beer on the wall, 11 bottles of beer. Take one down and pass it around, 10 bottles of beer on the wall. 10 bottles of beer on the wall, 10 bottles of beer. Take one down and pass it around, 9 bottles of beer on the wall. 9 bottles of beer on the wall, 9 bottles of beer. Take one down and pass it around, 8 bottles of beer on the wall. 8 bottles of beer on the wall, 8 bottles of beer. Take one down and pass it around, 7 bottles of beer on the wall. 7 bottles of beer on the wall, 7 bottles of beer. Take one down and pass it around, 6 bottles of beer on the wall. 6 bottles of beer on the wall, 6 bottles of beer. Take one down and pass it around, 5 bottles of beer on the wall. 5 bottles of beer on the wall, 5 bottles of beer. Take one down and pass it around, 4 bottles of beer on the wall. 4 bottles of beer on the wall, 4 bottles of beer. Take one down and pass it around, 3 bottles of beer on the wall. 3 bottles of beer on the wall, 3 bottles of beer. Take one down and pass it around, 2 bottles of beer on the wall. 2 bottles of beer on the wall, 2 bottles of beer. Take one down and pass it around, 1 bottle of beer on the wall. 1 bottle of beer on the wall, 1 bottle of beer. Take it down and pass it around, no more bottles of beer on the wall. No more bottles of beer on the wall, no more bottles of beer. Go to the store and buy some more, 99 bottles of beer on the wall. `) }) }) ================================================ FILE: exercises/practice/beer-song/beer-song.ts ================================================ export function verse(index: unknown): unknown { throw new Error('Remove this line and implement the function') } export function sing( initialBottlesCount?: unknown, takeDownCount?: unknown ): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/beer-song/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/beer-song/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/beer-song/package.json ================================================ { "name": "@exercism/typescript-beer-song", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/beer-song/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/beer-song/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/binary-search/.docs/instructions.md ================================================ # Instructions Your task is to implement a binary search algorithm. A binary search algorithm finds an item in a list by repeatedly splitting it in half, only keeping the half which contains the item we're looking for. It allows us to quickly narrow down the possible locations of our item until we find it, or until we've eliminated all possible locations. ~~~~exercism/caution Binary search only works when a list has been sorted. ~~~~ The algorithm looks like this: - Find the middle element of a _sorted_ list and compare it with the item we're looking for. - If the middle element is our item, then we're done! - If the middle element is greater than our item, we can eliminate that element and all the elements **after** it. - If the middle element is less than our item, we can eliminate that element and all the elements **before** it. - If every element of the list has been eliminated then the item is not in the list. - Otherwise, repeat the process on the part of the list that has not been eliminated. Here's an example: Let's say we're looking for the number 23 in the following sorted list: `[4, 8, 12, 16, 23, 28, 32]`. - We start by comparing 23 with the middle element, 16. - Since 23 is greater than 16, we can eliminate the left half of the list, leaving us with `[23, 28, 32]`. - We then compare 23 with the new middle element, 28. - Since 23 is less than 28, we can eliminate the right half of the list: `[23]`. - We've found our item. ================================================ FILE: exercises/practice/binary-search/.docs/introduction.md ================================================ # Introduction You have stumbled upon a group of mathematicians who are also singer-songwriters. They have written a song for each of their favorite numbers, and, as you can imagine, they have a lot of favorite numbers (like [0][zero] or [73][seventy-three] or [6174][kaprekars-constant]). You are curious to hear the song for your favorite number, but with so many songs to wade through, finding the right song could take a while. Fortunately, they have organized their songs in a playlist sorted by the title — which is simply the number that the song is about. You realize that you can use a binary search algorithm to quickly find a song given the title. [zero]: https://en.wikipedia.org/wiki/0 [seventy-three]: https://en.wikipedia.org/wiki/73_(number) [kaprekars-constant]: https://en.wikipedia.org/wiki/6174_(number) ================================================ FILE: exercises/practice/binary-search/.meta/config.json ================================================ { "authors": [ "anuragsoni" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "binary-search.ts" ], "test": [ "binary-search.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Implement a binary search algorithm.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Binary_search_algorithm" } ================================================ FILE: exercises/practice/binary-search/.meta/proof.ci.ts ================================================ export function find(array: number[], element: number): number | never { let start = 0 let end = array.length - 1 let middle: number while (start <= end) { middle = Math.floor((start + end) / 2) if (element === array[middle]) { return middle } else if (element < array[middle]) { end = middle - 1 } else if (element > array[middle]) { start = middle + 1 } } throw new Error('Value not in array') } ================================================ FILE: exercises/practice/binary-search/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [b55c24a9-a98d-4379-a08c-2adcf8ebeee8] description = "finds a value in an array with one element" [73469346-b0a0-4011-89bf-989e443d503d] description = "finds a value in the middle of an array" [327bc482-ab85-424e-a724-fb4658e66ddb] description = "finds a value at the beginning of an array" [f9f94b16-fe5e-472c-85ea-c513804c7d59] description = "finds a value at the end of an array" [f0068905-26e3-4342-856d-ad153cadb338] description = "finds a value in an array of odd length" [fc316b12-c8b3-4f5e-9e89-532b3389de8c] description = "finds a value in an array of even length" [da7db20a-354f-49f7-a6a1-650a54998aa6] description = "identifies that a value is not included in the array" [95d869ff-3daf-4c79-b622-6e805c675f97] description = "a value smaller than the array's smallest value is not found" [8b24ef45-6e51-4a94-9eac-c2bf38fdb0ba] description = "a value larger than the array's largest value is not found" [f439a0fa-cf42-4262-8ad1-64bf41ce566a] description = "nothing is found in an empty array" [2c353967-b56d-40b8-acff-ce43115eed64] description = "nothing is found when the left and right bounds cross" ================================================ FILE: exercises/practice/binary-search/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/binary-search/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/binary-search/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/binary-search/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/binary-search/binary-search.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { find } from './binary-search.ts' describe('Binary Search', () => { it('finds a value in an array with one element', () => { expect(find([6], 6)).toEqual(0) }) xit('finds a value in the middle of an array', () => { const array = [1, 3, 4, 6, 8, 9, 11] expect(find(array, 6)).toEqual(3) }) xit('finds a value at the beginning of an array', () => { const array = [1, 3, 4, 6, 8, 9, 11] expect(find(array, 1)).toEqual(0) }) xit('finds a value at the end of an array', () => { const array = [1, 3, 4, 6, 8, 9, 11] expect(find(array, 11)).toEqual(6) }) xit('finds a value in an array of odd length', () => { const array = [1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 634] expect(find(array, 144)).toEqual(9) }) xit('finds a value in an array of even length', () => { const array = [1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377] expect(find(array, 21)).toEqual(5) }) xit('identifies that a value is not included in the array', () => { const array = [1, 3, 4, 6, 8, 9, 11] expect(() => find(array, 7)).toThrow(new Error('Value not in array')) }) xit("a value smaller than the array's smallest value is not found", () => { const array = [1, 3, 4, 6, 8, 9, 11] expect(() => find(array, 0)).toThrow(new Error('Value not in array')) }) xit("a value larger than the array's largest value is not found", () => { const array = [1, 3, 4, 6, 8, 9, 11] expect(() => find(array, 13)).toThrow(new Error('Value not in array')) }) xit('nothing is found in an empty array', () => { expect(() => find([], 1)).toThrow(new Error('Value not in array')) }) xit('nothing is found when the left and right bounds cross', () => { expect(() => find([1, 2], 0)).toThrow(new Error('Value not in array')) }) }) ================================================ FILE: exercises/practice/binary-search/binary-search.ts ================================================ export function find(haystack: unknown, needle: unknown): number | never { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/binary-search/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/binary-search/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/binary-search/package.json ================================================ { "name": "@exercism/typescript-binary-search", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/binary-search/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/binary-search/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/binary-search-tree/.docs/instructions.md ================================================ # Instructions Insert and search for numbers in a binary tree. When we need to represent sorted data, an array does not make a good data structure. Say we have the array `[1, 3, 4, 5]`, and we add 2 to it so it becomes `[1, 3, 4, 5, 2]`. Now we must sort the entire array again! We can improve on this by realizing that we only need to make space for the new item `[1, nil, 3, 4, 5]`, and then adding the item in the space we added. But this still requires us to shift many elements down by one. Binary Search Trees, however, can operate on sorted data much more efficiently. A binary search tree consists of a series of connected nodes. Each node contains a piece of data (e.g. the number 3), a variable named `left`, and a variable named `right`. The `left` and `right` variables point at `nil`, or other nodes. Since these other nodes in turn have other nodes beneath them, we say that the left and right variables are pointing at subtrees. All data in the left subtree is less than or equal to the current node's data, and all data in the right subtree is greater than the current node's data. For example, if we had a node containing the data 4, and we added the data 2, our tree would look like this: ![A graph with root node 4 and a single child node 2.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2.svg) ```text 4 / 2 ``` If we then added 6, it would look like this: ![A graph with root node 4 and two child nodes 2 and 6.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2-6.svg) ```text 4 / \ 2 6 ``` If we then added 3, it would look like this ![A graph with root node 4, two child nodes 2 and 6, and a grandchild node 3.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2-6-3.svg) ```text 4 / \ 2 6 \ 3 ``` And if we then added 1, 5, and 7, it would look like this ![A graph with root node 4, two child nodes 2 and 6, and four grandchild nodes 1, 3, 5 and 7.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2-6-1-3-5-7.svg) ```text 4 / \ / \ 2 6 / \ / \ 1 3 5 7 ``` ## Credit The images were created by [habere-et-dispertire][habere-et-dispertire] using [PGF/TikZ][pgf-tikz] by Till Tantau. [habere-et-dispertire]: https://exercism.org/profiles/habere-et-dispertire [pgf-tikz]: https://en.wikipedia.org/wiki/PGF/TikZ ================================================ FILE: exercises/practice/binary-search-tree/.meta/config.json ================================================ { "authors": [ "Sn0wFox" ], "contributors": [ "masters3d", "peerreynders", "SleeplessByte" ], "files": { "solution": [ "binary-search-tree.ts" ], "test": [ "binary-search-tree.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Insert and search for numbers in a binary tree.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Josh Cheek" } ================================================ FILE: exercises/practice/binary-search-tree/.meta/proof.ci.ts ================================================ type BinarySearchTreeMaybe = BinarySearchTree | undefined type EachFn = (data: number) => void export class BinarySearchTree { private readonly _data: number private _left?: BinarySearchTree private _right?: BinarySearchTree constructor(data: number) { this._data = data } public insert(value: number): this { if (value <= this._data) { if (this._left) this._left.insert(value) else this._left = new BinarySearchTree(value) } else { if (this._right) this._right.insert(value) else this._right = new BinarySearchTree(value) } return this } public each(fn: EachFn): void { if (this._left) this._left.each(fn) fn(this._data) if (this._right) this._right.each(fn) } public get data(): number { return this._data } public get left(): BinarySearchTreeMaybe { return this._left } public get right(): BinarySearchTreeMaybe { return this._right } } ================================================ FILE: exercises/practice/binary-search-tree/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [e9c93a78-c536-4750-a336-94583d23fafa] description = "data is retained" [7a95c9e8-69f6-476a-b0c4-4170cb3f7c91] description = "smaller number at left node" [22b89499-9805-4703-a159-1a6e434c1585] description = "same number at left node" [2e85fdde-77b1-41ed-b6ac-26ce6b663e34] description = "greater number at right node" [dd898658-40ab-41d0-965e-7f145bf66e0b] description = "can create complex tree" [9e0c06ef-aeca-4202-b8e4-97f1ed057d56] description = "can sort single number" [425e6d07-fceb-4681-a4f4-e46920e380bb] description = "can sort if second number is smaller than first" [bd7532cc-6988-4259-bac8-1d50140079ab] description = "can sort if second number is same as first" [b6d1b3a5-9d79-44fd-9013-c83ca92ddd36] description = "can sort if second number is greater than first" [d00ec9bd-1288-4171-b968-d44d0808c1c8] description = "can sort complex tree" ================================================ FILE: exercises/practice/binary-search-tree/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/binary-search-tree/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/binary-search-tree/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/binary-search-tree/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/binary-search-tree/binary-search-tree.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { BinarySearchTree } from './binary-search-tree.ts' function recordAllData(bst: BinarySearchTree): unknown[] { const out: unknown[] = [] bst.each((data) => { out.push(data) }) return out } describe('BinarySearchTree', () => { it('should retain data', () => { expect(new BinarySearchTree(4).data).toEqual(4) }) xit('should insert a lesser number to the left', () => { const four = new BinarySearchTree(4) four.insert(2) expect(four.data).toEqual(4) expect(four.left!.data).toEqual(2) }) xit('should insert the same number to the left', () => { const four = new BinarySearchTree(4) four.insert(4) expect(four.data).toEqual(4) expect(four.left!.data).toEqual(4) }) xit('should insert a greater number to the right', () => { const four = new BinarySearchTree(4) four.insert(5) expect(four.data).toEqual(4) expect(four.right!.data).toEqual(5) }) xit('should deal with a complex tree', () => { const four = new BinarySearchTree(4) four.insert(2) four.insert(6) four.insert(1) four.insert(3) four.insert(7) four.insert(5) expect(four.data).toEqual(4) expect(four.left!.data).toEqual(2) expect(four.left!.left!.data).toEqual(1) expect(four.left!.right!.data).toEqual(3) expect(four.right!.data).toEqual(6) expect(four.right!.left!.data).toEqual(5) expect(four.right!.right!.data).toEqual(7) }) xit('should iterate over one element', () => { expect(recordAllData(new BinarySearchTree(4))).toEqual([4]) }) xit('should iterate over smaller element', () => { const four = new BinarySearchTree(4) four.insert(2) expect(recordAllData(four)).toEqual([2, 4]) }) xit('should iterate over larger element', () => { const four = new BinarySearchTree(4) four.insert(5) expect(recordAllData(four)).toEqual([4, 5]) }) xit('should iterate over complex tree', () => { const four = new BinarySearchTree(4) four.insert(2) four.insert(1) four.insert(3) four.insert(6) four.insert(7) four.insert(5) expect(recordAllData(four)).toEqual([1, 2, 3, 4, 5, 6, 7]) }) }) ================================================ FILE: exercises/practice/binary-search-tree/binary-search-tree.ts ================================================ export class BinarySearchTree { constructor(data: unknown) { throw new Error('Remove this line and implement the function') } public get data(): unknown { throw new Error('Remove this line and implement the function') } public get right(): BinarySearchTree | undefined { throw new Error('Remove this line and implement the function') } public get left(): BinarySearchTree | undefined { throw new Error('Remove this line and implement the function') } public insert(item: unknown): unknown { throw new Error('Remove this line and implement the function') } public each(callback: (data: unknown) => unknown): unknown { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/binary-search-tree/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/binary-search-tree/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/binary-search-tree/package.json ================================================ { "name": "@exercism/typescript-binary-search-tree", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/binary-search-tree/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/binary-search-tree/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/bob/.docs/instructions.md ================================================ # Instructions Your task is to determine what Bob will reply to someone when they say something to him or ask him a question. Bob only ever answers one of five things: - **"Sure."** This is his response if you ask him a question, such as "How are you?" The convention used for questions is that it ends with a question mark. - **"Whoa, chill out!"** This is his answer if you YELL AT HIM. The convention used for yelling is ALL CAPITAL LETTERS. - **"Calm down, I know what I'm doing!"** This is what he says if you yell a question at him. - **"Fine. Be that way!"** This is how he responds to silence. The convention used for silence is nothing, or various combinations of whitespace characters. - **"Whatever."** This is what he answers to anything else. ================================================ FILE: exercises/practice/bob/.docs/introduction.md ================================================ # Introduction Bob is a [lackadaisical][] teenager. He likes to think that he's very cool. And he definitely doesn't get excited about things. That wouldn't be cool. When people talk to him, his responses are pretty limited. [lackadaisical]: https://www.collinsdictionary.com/dictionary/english/lackadaisical ================================================ FILE: exercises/practice/bob/.meta/config.json ================================================ { "authors": [ "masters3d" ], "contributors": [ "amscotti", "DFXLuna", "kytrinyx", "lukaszklis", "SleeplessByte" ], "files": { "solution": [ "bob.ts" ], "test": [ "bob.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Bob is a lackadaisical teenager. In conversation, his responses are very limited.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Inspired by the 'Deaf Grandma' exercise in Chris Pine's Learn to Program tutorial.", "source_url": "https://pine.fm/LearnToProgram/chap_06.html" } ================================================ FILE: exercises/practice/bob/.meta/description.md ================================================ # Description Bob is a lackadaisical teenager. In conversation, his responses are very limited. Bob answers 'Sure.' if you ask him a question. He answers 'Whoa, chill out!' if you yell at him. He says 'Fine. Be that way!' if you address him without actually saying anything. He answers 'Whatever.' to anything else. ================================================ FILE: exercises/practice/bob/.meta/proof.ci.ts ================================================ function isYelling(message: string): boolean { return message.toUpperCase() === message && message.toLowerCase() !== message } function isQuestion(message: string): boolean { return message.trim().slice(-1) === '?' } function isSilence(message: string): boolean { return message.trim().length === 0 } export function hey(message: string): string { if (isYelling(message) && isQuestion(message)) { return "Calm down, I know what I'm doing!" } if (isYelling(message)) { return 'Whoa, chill out!' } if (isQuestion(message)) { return 'Sure.' } if (isSilence(message)) { return 'Fine. Be that way!' } return 'Whatever.' } ================================================ FILE: exercises/practice/bob/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [e162fead-606f-437a-a166-d051915cea8e] description = "stating something" [73a966dc-8017-47d6-bb32-cf07d1a5fcd9] description = "shouting" [d6c98afd-df35-4806-b55e-2c457c3ab748] description = "shouting gibberish" [8a2e771d-d6f1-4e3f-b6c6-b41495556e37] description = "asking a question" [81080c62-4e4d-4066-b30a-48d8d76920d9] description = "asking a numeric question" [2a02716d-685b-4e2e-a804-2adaf281c01e] description = "asking gibberish" [c02f9179-ab16-4aa7-a8dc-940145c385f7] description = "talking forcefully" [153c0e25-9bb5-4ec5-966e-598463658bcd] description = "using acronyms in regular speech" [a5193c61-4a92-4f68-93e2-f554eb385ec6] description = "forceful question" [a20e0c54-2224-4dde-8b10-bd2cdd4f61bc] description = "shouting numbers" [f7bc4b92-bdff-421e-a238-ae97f230ccac] description = "no letters" [bb0011c5-cd52-4a5b-8bfb-a87b6283b0e2] description = "question with no letters" [496143c8-1c31-4c01-8a08-88427af85c66] description = "shouting with special characters" [e6793c1c-43bd-4b8d-bc11-499aea73925f] description = "shouting with no exclamation mark" [aa8097cc-c548-4951-8856-14a404dd236a] description = "statement containing question mark" [9bfc677d-ea3a-45f2-be44-35bc8fa3753e] description = "non-letters with question" [8608c508-f7de-4b17-985b-811878b3cf45] description = "prattling on" [bc39f7c6-f543-41be-9a43-fd1c2f753fc0] description = "silence" [d6c47565-372b-4b09-b1dd-c40552b8378b] description = "prolonged silence" [4428f28d-4100-4d85-a902-e5a78cb0ecd3] description = "alternate silence" [66953780-165b-4e7e-8ce3-4bcb80b6385a] description = "multiple line question" [5371ef75-d9ea-4103-bcfa-2da973ddec1b] description = "starting with whitespace" [05b304d6-f83b-46e7-81e0-4cd3ca647900] description = "ending with whitespace" [72bd5ad3-9b2f-4931-a988-dce1f5771de2] description = "other whitespace" [12983553-8601-46a8-92fa-fcaa3bc4a2a0] description = "non-question ending with whitespace" ================================================ FILE: exercises/practice/bob/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/bob/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/bob/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/bob/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/bob/bob.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { hey } from './bob.ts' describe('Bob', () => { it('stating something', () => { const result = hey('Tom-ay-to, tom-aaaah-to.') expect(result).toEqual('Whatever.') }) xit('shouting', () => { const result = hey('WATCH OUT!') expect(result).toEqual('Whoa, chill out!') }) xit('shouting gibberish', () => { const result = hey('FCECDFCAAB') expect(result).toEqual('Whoa, chill out!') }) xit('asking a question', () => { const result = hey('Does this cryogenic chamber make me look fat?') expect(result).toEqual('Sure.') }) xit('asking a numeric question', () => { const result = hey('You are, what, like 15?') expect(result).toEqual('Sure.') }) xit('asking gibberish', () => { const result = hey('fffbbcbeab?') expect(result).toEqual('Sure.') }) xit('talking forcefully', () => { const result = hey("Let's go make out behind the gym!") expect(result).toEqual('Whatever.') }) xit('using acronyms in regular speech', () => { const result = hey("It's OK if you don't want to go to the DMV.") expect(result).toEqual('Whatever.') }) xit('forceful question', () => { const result = hey('WHAT THE HELL WERE YOU THINKING?') expect(result).toEqual("Calm down, I know what I'm doing!") }) xit('shouting numbers', () => { const result = hey('1, 2, 3 GO!') expect(result).toEqual('Whoa, chill out!') }) xit('no letters', () => { const result = hey('1, 2, 3') expect(result).toEqual('Whatever.') }) xit('question with no letters', () => { const result = hey('4?') expect(result).toEqual('Sure.') }) xit('shouting with special characters', () => { const result = hey('ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!') expect(result).toEqual('Whoa, chill out!') }) xit('shouting with no exclamation mark', () => { const result = hey('I HATE THE DMV') expect(result).toEqual('Whoa, chill out!') }) xit('statement containing question mark', () => { const result = hey('Ending with ? means a question.') expect(result).toEqual('Whatever.') }) xit('prattling on', () => { const result = hey('Wait! Hang on. Are you going to be OK?') expect(result).toEqual('Sure.') }) xit('silence', () => { const result = hey('') expect(result).toEqual('Fine. Be that way!') }) xit('prolonged silence', () => { const result = hey(' ') expect(result).toEqual('Fine. Be that way!') }) xit('alternate silence', () => { const result = hey('\t\t\t\t\t\t\t\t\t\t') expect(result).toEqual('Fine. Be that way!') }) xit('multiple line question', () => { const result = hey('\nDoes this cryogenic chamber make me look fat?\nNo.') expect(result).toEqual('Whatever.') }) xit('starting with whitespace', () => { const result = hey(' hmmmmmmm...') expect(result).toEqual('Whatever.') }) xit('ending with whitespace', () => { const result = hey('Okay if like my spacebar quite a bit? ') expect(result).toEqual('Sure.') }) xit('other whitespace', () => { const result = hey('\n\r \t') expect(result).toEqual('Fine. Be that way!') }) xit('non-question ending with whitespace', () => { const result = hey('This is a statement ending with whitespace ') expect(result).toEqual('Whatever.') }) }) ================================================ FILE: exercises/practice/bob/bob.ts ================================================ export function hey(message: unknown): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/bob/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/bob/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/bob/package.json ================================================ { "name": "@exercism/typescript-bob", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/bob/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/bob/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/bottle-song/.docs/instructions.md ================================================ # Instructions Recite the lyrics to that popular children's repetitive song: Ten Green Bottles. Note that not all verses are identical. ```text Ten green bottles hanging on the wall, Ten green bottles hanging on the wall, And if one green bottle should accidentally fall, There'll be nine green bottles hanging on the wall. Nine green bottles hanging on the wall, Nine green bottles hanging on the wall, And if one green bottle should accidentally fall, There'll be eight green bottles hanging on the wall. Eight green bottles hanging on the wall, Eight green bottles hanging on the wall, And if one green bottle should accidentally fall, There'll be seven green bottles hanging on the wall. Seven green bottles hanging on the wall, Seven green bottles hanging on the wall, And if one green bottle should accidentally fall, There'll be six green bottles hanging on the wall. Six green bottles hanging on the wall, Six green bottles hanging on the wall, And if one green bottle should accidentally fall, There'll be five green bottles hanging on the wall. Five green bottles hanging on the wall, Five green bottles hanging on the wall, And if one green bottle should accidentally fall, There'll be four green bottles hanging on the wall. Four green bottles hanging on the wall, Four green bottles hanging on the wall, And if one green bottle should accidentally fall, There'll be three green bottles hanging on the wall. Three green bottles hanging on the wall, Three green bottles hanging on the wall, And if one green bottle should accidentally fall, There'll be two green bottles hanging on the wall. Two green bottles hanging on the wall, Two green bottles hanging on the wall, And if one green bottle should accidentally fall, There'll be one green bottle hanging on the wall. One green bottle hanging on the wall, One green bottle hanging on the wall, And if one green bottle should accidentally fall, There'll be no green bottles hanging on the wall. ``` ================================================ FILE: exercises/practice/bottle-song/.meta/config.json ================================================ { "authors": [ "Budmin" ], "files": { "solution": [ "bottle-song.ts" ], "test": [ "bottle-song.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Produce the lyrics to the popular children's repetitive song: Ten Green Bottles.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Ten_Green_Bottles" } ================================================ FILE: exercises/practice/bottle-song/.meta/proof.ci.ts ================================================ const NUMBERS = [ 'no', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten', ] export const recite = ( initialBottleCount: number, takeDownCount = 1 ): string[] => { const out: string[] = [] for ( let i = initialBottleCount; i > initialBottleCount - takeDownCount; i-- ) { const currentBottles = i === 1 ? 'bottle' : 'bottles' const nextBottles = i - 1 === 1 ? 'bottle' : 'bottles' const line = `${NUMBERS[i]} green ${currentBottles} hanging on the wall,` out.push( line, line, `And if one green bottle should accidentally fall,`, `There'll be ${NUMBERS[i - 1].toLowerCase()} green ${nextBottles} hanging on the wall.` ) if (initialBottleCount - takeDownCount !== i - 1) { out.push('') } } return out } ================================================ FILE: exercises/practice/bottle-song/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [d4ccf8fc-01dc-48c0-a201-4fbeb30f2d03] description = "verse -> single verse -> first generic verse" [0f0aded3-472a-4c64-b842-18d4f1f5f030] description = "verse -> single verse -> last generic verse" [f61f3c97-131f-459e-b40a-7428f3ed99d9] description = "verse -> single verse -> verse with 2 bottles" [05eadba9-5dbd-401e-a7e8-d17cc9baa8e0] description = "verse -> single verse -> verse with 1 bottle" [a4a28170-83d6-4dc1-bd8b-319b6abb6a80] description = "lyrics -> multiple verses -> first two verses" [3185d438-c5ac-4ce6-bcd3-02c9ff1ed8db] description = "lyrics -> multiple verses -> last three verses" [28c1584a-0e51-4b65-9ae2-fbc0bf4bbb28] description = "lyrics -> multiple verses -> all verses" ================================================ FILE: exercises/practice/bottle-song/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/bottle-song/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/bottle-song/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/bottle-song/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/bottle-song/bottle-song.test.ts ================================================ import { describe, expect, it, xit } from '@jest/globals' import { recite } from './bottle-song.ts' describe('Bottle Song', () => { describe('verse', () => { describe('single verse', () => { it('first generic verse', () => { const expected = [ `Ten green bottles hanging on the wall,`, `Ten green bottles hanging on the wall,`, `And if one green bottle should accidentally fall,`, `There'll be nine green bottles hanging on the wall.`, ] expect(recite(10, 1)).toEqual(expected) }) xit('last generic verse', () => { const expected = [ `Three green bottles hanging on the wall,`, `Three green bottles hanging on the wall,`, `And if one green bottle should accidentally fall,`, `There'll be two green bottles hanging on the wall.`, ] expect(recite(3, 1)).toEqual(expected) }) xit('verse with 2 bottles', () => { const expected = [ `Two green bottles hanging on the wall,`, `Two green bottles hanging on the wall,`, `And if one green bottle should accidentally fall,`, `There'll be one green bottle hanging on the wall.`, ] expect(recite(2, 1)).toEqual(expected) }) xit('verse with 1 bottle', () => { const expected = [ `One green bottle hanging on the wall,`, `One green bottle hanging on the wall,`, `And if one green bottle should accidentally fall,`, `There'll be no green bottles hanging on the wall.`, ] expect(recite(1, 1)).toEqual(expected) }) }) }) describe('lyrics', () => { describe('multiple verses', () => { xit('first two verses', () => { const expected = [ `Ten green bottles hanging on the wall,`, `Ten green bottles hanging on the wall,`, `And if one green bottle should accidentally fall,`, `There'll be nine green bottles hanging on the wall.`, ``, `Nine green bottles hanging on the wall,`, `Nine green bottles hanging on the wall,`, `And if one green bottle should accidentally fall,`, `There'll be eight green bottles hanging on the wall.`, ] expect(recite(10, 2)).toEqual(expected) }) xit('last three verses', () => { const expected = [ `Three green bottles hanging on the wall,`, `Three green bottles hanging on the wall,`, `And if one green bottle should accidentally fall,`, `There'll be two green bottles hanging on the wall.`, ``, `Two green bottles hanging on the wall,`, `Two green bottles hanging on the wall,`, `And if one green bottle should accidentally fall,`, `There'll be one green bottle hanging on the wall.`, ``, `One green bottle hanging on the wall,`, `One green bottle hanging on the wall,`, `And if one green bottle should accidentally fall,`, `There'll be no green bottles hanging on the wall.`, ] expect(recite(3, 3)).toEqual(expected) }) xit('all verses', () => { const expected = [ `Ten green bottles hanging on the wall,`, `Ten green bottles hanging on the wall,`, `And if one green bottle should accidentally fall,`, `There'll be nine green bottles hanging on the wall.`, ``, `Nine green bottles hanging on the wall,`, `Nine green bottles hanging on the wall,`, `And if one green bottle should accidentally fall,`, `There'll be eight green bottles hanging on the wall.`, ``, `Eight green bottles hanging on the wall,`, `Eight green bottles hanging on the wall,`, `And if one green bottle should accidentally fall,`, `There'll be seven green bottles hanging on the wall.`, ``, `Seven green bottles hanging on the wall,`, `Seven green bottles hanging on the wall,`, `And if one green bottle should accidentally fall,`, `There'll be six green bottles hanging on the wall.`, ``, `Six green bottles hanging on the wall,`, `Six green bottles hanging on the wall,`, `And if one green bottle should accidentally fall,`, `There'll be five green bottles hanging on the wall.`, ``, `Five green bottles hanging on the wall,`, `Five green bottles hanging on the wall,`, `And if one green bottle should accidentally fall,`, `There'll be four green bottles hanging on the wall.`, ``, `Four green bottles hanging on the wall,`, `Four green bottles hanging on the wall,`, `And if one green bottle should accidentally fall,`, `There'll be three green bottles hanging on the wall.`, ``, `Three green bottles hanging on the wall,`, `Three green bottles hanging on the wall,`, `And if one green bottle should accidentally fall,`, `There'll be two green bottles hanging on the wall.`, ``, `Two green bottles hanging on the wall,`, `Two green bottles hanging on the wall,`, `And if one green bottle should accidentally fall,`, `There'll be one green bottle hanging on the wall.`, ``, `One green bottle hanging on the wall,`, `One green bottle hanging on the wall,`, `And if one green bottle should accidentally fall,`, `There'll be no green bottles hanging on the wall.`, ] expect(recite(10, 10)).toEqual(expected) }) }) }) }) ================================================ FILE: exercises/practice/bottle-song/bottle-song.ts ================================================ export const recite = ( initialBottleCount: unknown, takeDownCount: unknown ): unknown => { throw new Error('Remove this statement and implement this function') } ================================================ FILE: exercises/practice/bottle-song/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/bottle-song/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/bottle-song/package.json ================================================ { "name": "@exercism/typescript-bottle-song", "version": "1.0.0", "description": "Exercism practice exercise on bottle-song", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/bottle-song/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/bottle-song/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/bowling/.docs/instructions.md ================================================ # Instructions Score a bowling game. Bowling is a game where players roll a heavy ball to knock down pins arranged in a triangle. Write code to keep track of the score of a game of bowling. ## Scoring Bowling The game consists of 10 frames. A frame is composed of one or two ball throws with 10 pins standing at frame initialization. There are three cases for the tabulation of a frame. - An open frame is where a score of less than 10 is recorded for the frame. In this case the score for the frame is the number of pins knocked down. - A spare is where all ten pins are knocked down by the second throw. The total value of a spare is 10 plus the number of pins knocked down in their next throw. - A strike is where all ten pins are knocked down by the first throw. The total value of a strike is 10 plus the number of pins knocked down in the next two throws. If a strike is immediately followed by a second strike, then the value of the first strike cannot be determined until the ball is thrown one more time. Here is a three frame example: | Frame 1 | Frame 2 | Frame 3 | | :--------: | :--------: | :--------------: | | X (strike) | 5/ (spare) | 9 0 (open frame) | Frame 1 is (10 + 5 + 5) = 20 Frame 2 is (5 + 5 + 9) = 19 Frame 3 is (9 + 0) = 9 This means the current running total is 48. The tenth frame in the game is a special case. If someone throws a spare or a strike then they get one or two fill balls respectively. Fill balls exist to calculate the total of the 10th frame. Scoring a strike or spare on the fill ball does not give the player more fill balls. The total value of the 10th frame is the total number of pins knocked down. For a tenth frame of X1/ (strike and a spare), the total value is 20. For a tenth frame of XXX (three strikes), the total value is 30. ## Requirements Write code to keep track of the score of a game of bowling. It should support two operations: - `roll(pins : int)` is called each time the player rolls a ball. The argument is the number of pins knocked down. - `score() : int` is called only at the very end of the game. It returns the total score for that game. ================================================ FILE: exercises/practice/bowling/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "bowling.ts" ], "test": [ "bowling.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Score a bowling game.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "The Bowling Game Kata from UncleBob", "source_url": "https://web.archive.org/web/20221001111000/http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata" } ================================================ FILE: exercises/practice/bowling/.meta/proof.ci.ts ================================================ type Frame = 'X' | 'S' | number | undefined export class Bowling { private readonly maxPins: number private readonly maxFrames: number private currentFrame: number private frames: Frame[] private frameScores: number[] private frameRoll: number private remainingPins: number constructor() { this.maxPins = 10 this.maxFrames = 10 this.currentFrame = 0 this.frames = [] this.frameScores = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] this.initializeFrame() } private initializeFrame(): void { this.frameRoll = 1 this.remainingPins = this.maxPins this.currentFrame = this.currentFrame + 1 } private incrementFrame(): void { this.frameRoll = this.frameRoll + 1 } private incrementScore(pins: number): void { if (this.currentFrame > this.maxFrames) return this.frameScores[this.currentFrame - 1] += pins } private scoreStrike(): void { this.frames[this.currentFrame - 1] = 'X' this.applyStrikeBonus(this.maxPins) this.applySpareBonus(this.maxPins) this.incrementFrame() } private scoreFirstRoll(pins: number): void { this.remainingPins = this.remainingPins - pins this.applySpareBonus(pins) this.applyStrikeBonus(pins) this.incrementFrame() } private scoreSpare(pins: number): void { this.frames[this.currentFrame - 1] = 'S' this.applyStrikeBonus(pins) this.incrementFrame() } private scoreOpenFrame(pins: number): void { this.frames[this.currentFrame - 1] = this.maxPins - this.remainingPins + pins this.applyStrikeBonus(pins) this.incrementFrame() } private applySpareBonus(pins: number): void { // pins on the first roll after a spare are counted twice (on the frame of spare) if (this.frames[this.currentFrame - 2] === 'S') { this.frameScores[this.currentFrame - 2] += pins } } private applyStrikeBonus(pins: number): void { // on the two rolls after a strike are counted twice (on the frame of the strike) if ( this.frames[this.currentFrame - 3] === 'X' && this.frames[this.currentFrame - 2] === 'X' && this.frameRoll === 1 && this.currentFrame <= this.maxFrames + 2 ) { this.frameScores[this.currentFrame - 3] += pins } if ( this.frames[this.currentFrame - 2] === 'X' && this.currentFrame <= this.maxFrames + 1 ) { this.frameScores[this.currentFrame - 2] += pins } } private isGameOver(): boolean { if (this.currentFrame <= this.maxFrames) return false if ( this.frames[this.maxFrames - 1] !== 'X' && this.frames[this.maxFrames - 1] !== 'S' ) return true // spare in the last frame gets no more than bonus roll if (this.frames[this.maxFrames - 1] === 'S' && this.frameRoll > 1) return true // bonus roll after the spare in the last frame may get a strike but then the games ends // without another roll if ( this.frames[this.maxFrames - 1] === 'S' && this.frames[this.maxFrames] === 'X' ) return true if (this.frames[this.maxFrames - 1] === 'X') { // if the first bonus roll is not a strike then finish the bonus frame if ( this.frames[this.maxFrames] !== 'X' && this.currentFrame > this.maxFrames + 1 ) return true if (this.frames[this.maxFrames] === 'X') { // if the second bonus roll is a strike, but was still used, the game is over if (this.frames[this.maxFrames + 1] !== 'X' && this.frameRoll > 1) return true // if the second bonus roll is a strike the game is over if (this.frames[this.maxFrames + 1] === 'X') return true } } return false } public roll(pins: number): void { if (pins < 0) { throw new Error('Negative roll is invalid') } if (pins > this.remainingPins) { throw new Error('Pin count exceeds pins on the lane') } if (this.isGameOver()) { throw new Error('Cannot roll after game is over') } this.incrementScore(pins) if (this.frameRoll === 1) { if (pins === this.maxPins) { this.scoreStrike() this.initializeFrame() } else { this.scoreFirstRoll(pins) } } else { if (pins === this.remainingPins) { this.scoreSpare(pins) } else { this.scoreOpenFrame(pins) } this.initializeFrame() } } public score(): number { if (!this.isGameOver()) { throw new Error('Score cannot be taken until the end of the game') } return this.frameScores.reduce((total, num) => total + num) } } ================================================ FILE: exercises/practice/bowling/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [656ae006-25c2-438c-a549-f338e7ec7441] description = "should be able to score a game with all zeros" [f85dcc56-cd6b-4875-81b3-e50921e3597b] description = "should be able to score a game with no strikes or spares" [d1f56305-3ac2-4fe0-8645-0b37e3073e20] description = "a spare followed by zeros is worth ten points" [0b8c8bb7-764a-4287-801a-f9e9012f8be4] description = "points scored in the roll after a spare are counted twice" [4d54d502-1565-4691-84cd-f29a09c65bea] description = "consecutive spares each get a one roll bonus" [e5c9cf3d-abbe-4b74-ad48-34051b2b08c0] description = "a spare in the last frame gets a one roll bonus that is counted once" [75269642-2b34-4b72-95a4-9be28ab16902] description = "a strike earns ten points in a frame with a single roll" [037f978c-5d01-4e49-bdeb-9e20a2e6f9a6] description = "points scored in the two rolls after a strike are counted twice as a bonus" [1635e82b-14ec-4cd1-bce4-4ea14bd13a49] description = "consecutive strikes each get the two roll bonus" [e483e8b6-cb4b-4959-b310-e3982030d766] description = "a strike in the last frame gets a two roll bonus that is counted once" [9d5c87db-84bc-4e01-8e95-53350c8af1f8] description = "rolling a spare with the two roll bonus does not get a bonus roll" [576faac1-7cff-4029-ad72-c16bcada79b5] description = "strikes with the two roll bonus do not get bonus rolls" [efb426ec-7e15-42e6-9b96-b4fca3ec2359] description = "last two strikes followed by only last bonus with non strike points" [72e24404-b6c6-46af-b188-875514c0377b] description = "a strike with the one roll bonus after a spare in the last frame does not get a bonus" [62ee4c72-8ee8-4250-b794-234f1fec17b1] description = "all strikes is a perfect game" [1245216b-19c6-422c-b34b-6e4012d7459f] description = "rolls cannot score negative points" [5fcbd206-782c-4faa-8f3a-be5c538ba841] description = "a roll cannot score more than 10 points" [fb023c31-d842-422d-ad7e-79ce1db23c21] description = "two rolls in a frame cannot score more than 10 points" [6082d689-d677-4214-80d7-99940189381b] description = "bonus roll after a strike in the last frame cannot score more than 10 points" [e9565fe6-510a-4675-ba6b-733a56767a45] description = "two bonus rolls after a strike in the last frame cannot score more than 10 points" [2f6acf99-448e-4282-8103-0b9c7df99c3d] description = "two bonus rolls after a strike in the last frame can score more than 10 points if one is a strike" [6380495a-8bc4-4cdb-a59f-5f0212dbed01] description = "the second bonus rolls after a strike in the last frame cannot be a strike if the first one is not a strike" [2b2976ea-446c-47a3-9817-42777f09fe7e] description = "second bonus roll after a strike in the last frame cannot score more than 10 points" [29220245-ac8d-463d-bc19-98a94cfada8a] description = "an unstarted game cannot be scored" [4473dc5d-1f86-486f-bf79-426a52ddc955] description = "an incomplete game cannot be scored" [2ccb8980-1b37-4988-b7d1-e5701c317df3] description = "cannot roll if game already has ten frames" [4864f09b-9df3-4b65-9924-c595ed236f1b] description = "bonus rolls for a strike in the last frame must be rolled before score can be calculated" [537f4e37-4b51-4d1c-97e2-986eb37b2ac1] description = "both bonus rolls for a strike in the last frame must be rolled before score can be calculated" [8134e8c1-4201-4197-bf9f-1431afcde4b9] description = "bonus roll for a spare in the last frame must be rolled before score can be calculated" [9d4a9a55-134a-4bad-bae8-3babf84bd570] description = "cannot roll after bonus roll for spare" [d3e02652-a799-4ae3-b53b-68582cc604be] description = "cannot roll after bonus rolls for strike" ================================================ FILE: exercises/practice/bowling/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/bowling/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/bowling/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/bowling/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/bowling/bowling.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { Bowling } from './bowling.ts' describe('Bowling', () => { describe('Check game can be scored correctly.', () => { it('should be able to score a game with all zeros', () => { const rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(bowling.score()).toEqual(0) }) xit('should be able to score a game with no strikes or spares', () => { const rolls = [3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(bowling.score()).toEqual(90) }) xit('a spare followed by zeros is worth ten points', () => { const rolls = [6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(bowling.score()).toEqual(10) }) xit('points scored in the roll after a spare are counted twice', () => { const rolls = [6, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(bowling.score()).toEqual(16) }) xit('consecutive spares each get a one roll bonus', () => { const rolls = [5, 5, 3, 7, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(bowling.score()).toEqual(31) }) xit('a spare in the last frame gets a one roll bonus that is counted once', () => { const rolls = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 7, ] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(bowling.score()).toEqual(17) }) xit('a strike earns ten points in a frame with a single roll', () => { const rolls = [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(bowling.score()).toEqual(10) }) xit('points scored in the two rolls after a strike are counted twice as a bonus', () => { const rolls = [10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(bowling.score()).toEqual(26) }) xit('consecutive strikes each get the two roll bonus', () => { const rolls = [10, 10, 10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(bowling.score()).toEqual(81) }) xit('a strike in the last frame gets a two roll bonues that is counted once', () => { const rolls = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 1, ] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(bowling.score()).toEqual(18) }) xit('rolling a spare with the two roll bonus does not get a bonus roll', () => { const rolls = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 3, ] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(bowling.score()).toEqual(20) }) xit('strikes with the two roll bonus do not get bonus rolls', () => { const rolls = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10, ] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(bowling.score()).toEqual(30) }) xit('last two strikes followed by only last bonus with non strike points', () => { const rolls = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 0, 1, ] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(bowling.score()).toEqual(31) }) xit('a strike with the one roll bonus after a spare in the last frame does not get a bonus', () => { const rolls = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 10, ] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(bowling.score()).toEqual(20) }) xit('all strikes is a perfect game', () => { const rolls = [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(bowling.score()).toEqual(300) }) }) describe('Check game rules.', () => { xit('rolls cannot score negative points', () => { const bowling = new Bowling() expect(() => { bowling.roll(-1) }).toThrow(new Error('Negative roll is invalid')) }) xit('a roll cannot score more than 10 points', () => { const bowling = new Bowling() expect(() => { bowling.roll(11) }).toThrow(new Error('Pin count exceeds pins on the lane')) }) xit('two rolls in a frame cannot score more than 10 points', () => { const bowling = new Bowling() bowling.roll(5) expect(() => { bowling.roll(6) }).toThrow(new Error('Pin count exceeds pins on the lane')) }) xit('bonus roll after a strike in the last frame cannot score more than 10 points', () => { const rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(() => { bowling.roll(11) }).toThrow(new Error('Pin count exceeds pins on the lane')) }) xit('two bonus rolls after a strike in the last frame cannot score more than 10 points', () => { const rolls = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 5, ] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(() => { bowling.roll(6) }).toThrow(new Error('Pin count exceeds pins on the lane')) }) xit('two bonus rolls after a strike in the last frame can score more than 10 points if one is a strike', () => { const rolls = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 6, ] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(bowling.score()).toEqual(26) }) xit('the second bonus rolls after a strike in the last frame cannot be a strike if the first one is not a strike', () => { const rolls = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 6, ] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(() => { bowling.roll(10) }).toThrow(new Error('Pin count exceeds pins on the lane')) }) xit('second bonus roll after a strike in the last frame cannot score more than 10 points', () => { const rolls = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, ] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(() => { bowling.roll(11) }).toThrow(new Error('Pin count exceeds pins on the lane')) }) xit('an unstarted game cannot be scored', () => { const bowling = new Bowling() expect(() => { bowling.score() }).toThrow(new Error('Score cannot be taken until the end of the game')) }) xit('an incomplete game cannot be scored', () => { const rolls = [0, 0] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(() => { bowling.score() }).toThrow(new Error('Score cannot be taken until the end of the game')) }) xit('cannot roll if game already has ten frames', () => { const rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(() => { bowling.roll(0) }).toThrow(new Error('Cannot roll after game is over')) }) xit('bonus rolls for a strike in the last frame must be rolled before score can be calculated', () => { const rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(() => { bowling.score() }).toThrow(new Error('Score cannot be taken until the end of the game')) }) xit('both bonus rolls for a strike in the last frame must be rolled before score can be calculated', () => { const rolls = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, ] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(() => { bowling.score() }).toThrow(new Error('Score cannot be taken until the end of the game')) }) xit('bonus roll for a spare in the last frame must be rolled before score can be calculated', () => { const rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(() => { bowling.score() }).toThrow(new Error('Score cannot be taken until the end of the game')) }) xit('cannot roll after bonus roll for spare', () => { const rolls = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 2, ] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(() => { bowling.roll(2) }).toThrow(new Error('Cannot roll after game is over')) }) xit('cannot roll after bonus rolls for strike', () => { const rolls = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 3, 2, ] const bowling = new Bowling() rolls.forEach((roll) => { bowling.roll(roll) }) expect(() => { bowling.roll(2) }).toThrow(new Error('Cannot roll after game is over')) }) }) }) ================================================ FILE: exercises/practice/bowling/bowling.ts ================================================ export class Bowling { public roll(pins: unknown): unknown { throw new Error('Remove this line and implement the function') } public score(): unknown { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/bowling/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/bowling/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/bowling/package.json ================================================ { "name": "@exercism/typescript-bowling", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/bowling/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/bowling/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/camicia/.docs/instructions.md ================================================ # Instructions In this exercise, you will simulate a game very similar to the classic card game **Camicia**. Your program will receive the initial configuration of two players' decks and must simulate the game until it ends (or detect that it will never end). ## Rules - The deck is split between **two players**. The player's cards are read from left to right, where the leftmost card is the top of the deck. - A round consists of both players playing at least one card. - Players take turns placing the **top card** of their deck onto a central pile. - If the card is a **number card** (2-10), play simply passes to the other player. - If the card is a **payment card**, a penalty must be paid: - **J** → opponent must pay 1 card - **Q** → opponent must pay 2 cards - **K** → opponent must pay 3 cards - **A** → opponent must pay 4 cards - If the player paying a penalty reveals another payment card, that player stops paying the penalty. The other player must then pay a penalty based on the new payment card. - If the penalty is fully paid without interruption, the player who placed the **last payment card** collects the central pile and places it at the bottom of their deck. That player then starts the next round. - If a player runs out of cards and is unable to play a card (either while paying a penalty or when it is their turn), the other player collects the central pile. - The moment when a player collects cards from the central pile is called a **trick**. - If a player has all the cards in their possession after a trick, the game **ends**. - The game **enters a loop** as soon as the decks are identical to what they were earlier during the game, **not** counting number cards! ## Examples A small example of a match that ends. | Round | Player A | Player B | Pile | Penalty Due | | :---- | :----------- | :------------------------- | :------------------------- | :---------- | | 1 | 2 A 7 8 Q 10 | 3 4 5 6 K 9 J | | - | | 1 | A 7 8 Q 10 | 3 4 5 6 K 9 J | 2 | - | | 1 | A 7 8 Q 10 | 4 5 6 K 9 J | 2 3 | - | | 1 | 7 8 Q 10 | 4 5 6 K 9 J | 2 3 A | Player B: 4 | | 1 | 7 8 Q 10 | 5 6 K 9 J | 2 3 A 4 | Player B: 3 | | 1 | 7 8 Q 10 | 6 K 9 J | 2 3 A 4 5 | Player B: 2 | | 1 | 7 8 Q 10 | K 9 J | 2 3 A 4 5 6 | Player B: 1 | | 1 | 7 8 Q 10 | 9 J | 2 3 A 4 5 6 K | Player A: 3 | | 1 | 8 Q 10 | 9 J | 2 3 A 4 5 6 K 7 | Player A: 2 | | 1 | Q 10 | 9 J | 2 3 A 4 5 6 K 7 8 | Player A: 1 | | 1 | 10 | 9 J | 2 3 A 4 5 6 K 7 8 Q | Player B: 2 | | 1 | 10 | J | 2 3 A 4 5 6 K 7 8 Q 9 | Player B: 1 | | 1 | 10 | - | 2 3 A 4 5 6 K 7 8 Q 9 J | Player A: 1 | | 1 | - | - | 2 3 A 4 5 6 K 7 8 Q 9 J 10 | - | | 2 | - | 2 3 A 4 5 6 K 7 8 Q 9 J 10 | - | - | status: `"finished"`, cards: 13, tricks: 1 This is a small example of a match that loops. | Round | Player A | Player B | Pile | Penalty Due | | :---- | :------- | :------- | :---- | :---------- | | 1 | J 2 3 | 4 J 5 | - | - | | 1 | 2 3 | 4 J 5 | J | Player B: 1 | | 1 | 2 3 | J 5 | J 4 | - | | 2 | 2 3 J 4 | J 5 | - | - | | 2 | 3 J 4 | J 5 | 2 | - | | 2 | 3 J 4 | 5 | 2 J | Player A: 1 | | 2 | J 4 | 5 | 2 J 3 | - | | 3 | J 4 | 5 2 J 3 | - | - | | 3 | J 4 | 2 J 3 | 5 | - | | 3 | 4 | 2 J 3 | 5 J | Player B: 1 | | 3 | 4 | J 3 | 5 J 2 | - | | 4 | 4 5 J 2 | J 3 | - | - | The start of round 4 matches the start of round 2. Recall, the value of the number cards does not matter. status: `"loop"`, cards: 8, tricks: 3 ## Your Task - Using the input, simulate the game following the rules above. - Determine the following information regarding the game: - **Status**: `"finished"` or `"loop"` - **Cards**: total number of cards played throughout the game - **Tricks**: number of times the central pile was collected ~~~~exercism/advanced For those who want to take on a more exciting challenge, the hunt for other records for the longest game with an end is still open. There are 653,534,134,886,878,245,000 (approximately 654 quintillion) possibilities, and we haven't calculated them all yet! ~~~~ ================================================ FILE: exercises/practice/camicia/.docs/introduction.md ================================================ # Introduction One rainy afternoon, you sit at the kitchen table playing cards with your grandmother. The game is her take on [Camicia][bmn]. At first it feels like just another friendly match: cards slapped down, laughter across the table, the occasional victorious grin from Nonna. But as the game stretches on, something strange happens. The same cards keep cycling back. You play card after card, yet the end never seems to come. You start to wonder. _Will this game ever finish? Or could we keep playing forever?_ Later, driven by curiosity, you search online and to your surprise you discover that what happened wasn't just bad luck. You and your grandmother may have stumbled upon one of the longest possible sequences! Suddenly, you're hooked. What began as a casual game has turned into a quest: _how long can such a game really last?_ _Can you find a sequence even longer than the one you played at the kitchen table?_ _Perhaps even long enough to set a new world record?_ And so, armed with nothing but a deck of cards and some algorithmic ingenuity, you decide to investigate... [bmn]: https://en.wikipedia.org/wiki/Beggar-my-neighbour ================================================ FILE: exercises/practice/camicia/.meta/config.json ================================================ { "authors": [ "BNAndras" ], "files": { "solution": [ "camicia.ts" ], "test": [ "camicia.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Simulate the card game and determine whether the match ends or enters an infinite loop.", "source": "Beggar-My-Neighbour", "source_url": "https://www.richardpmann.com/beggar-my-neighbour-records.html" } ================================================ FILE: exercises/practice/camicia/.meta/proof.ci.ts ================================================ export const simulateGame = ( playerA: string[], playerB: string[] ): { status: string; tricks: number; cards: number } => { const getCardValue = (card: string): number => { if (card === 'J') return 1 if (card === 'Q') return 2 if (card === 'K') return 3 if (card === 'A') return 4 return 0 } const handA = playerA.map(getCardValue) const handB = playerB.map(getCardValue) let turn = 'A' let pile = [] const seen = new Set() let totalTricks = 0 let cardsPlayed = 0 let currentDebt = 0 while (true) { if (pile.length === 0) { const round = JSON.stringify([handA, handB, turn]) if (seen.has(round)) { return { status: 'loop', tricks: totalTricks, cards: cardsPlayed } } seen.add(round) } const activeHand = turn === 'A' ? handA : handB const otherHand = turn === 'A' ? handB : handA if (activeHand.length === 0) { const extraTrick = pile.length === 0 ? 0 : 1 return { status: 'finished', tricks: totalTricks + extraTrick, cards: cardsPlayed, } } const cardVal = activeHand.shift()! pile.push(cardVal) cardsPlayed += 1 // payment card so debt is either forgiven for player or assigned to opponent if (cardVal > 0) { currentDebt = cardVal turn = turn === 'A' ? 'B' : 'A' } else { // time to pay up! if (currentDebt > 0) { currentDebt -= 1 if (currentDebt === 0) { // penalty paid off otherHand.push(...pile) pile = [] totalTricks += 1 currentDebt = 0 if (handA.length === 0 || handB.length === 0) { return { status: 'finished', tricks: totalTricks, cards: cardsPlayed, } } turn = turn === 'A' ? 'B' : 'A' } } else { turn = turn === 'A' ? 'B' : 'A' } } } } ================================================ FILE: exercises/practice/camicia/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [0b7f737c-3ecd-4a55-b34d-e65c62a85c28] description = "two cards, one trick" [27c19d75-53a5-48e5-b33b-232c3884d4f3] description = "three cards, one trick" [9b02dd49-efaf-4b71-adca-a05c18a7c5b0] description = "four cards, one trick" [fa3f4479-466a-4734-a001-ab79bfe27260] description = "the ace reigns supreme" [07629689-f589-4f54-a6d1-8ce22776ce72] description = "the king beats ace" [54d4a1c5-76fb-4d1e-8358-0e0296ac0601] description = "the queen seduces the king" [c875500c-ff3d-47a4-bd1e-b60b90da80aa] description = "the jack betrays the queen" [436875da-96ca-4149-be22-0b78173b8125] description = "the 10 just wants to put on a show" [5be39bb6-1b34-4ce6-a1cd-0fcc142bb272] description = "simple loop with decks of 3 cards" [2795dc21-0a2a-4c38-87c2-5a42e1ff15eb] description = "the story is starting to get a bit complicated" [6999dfac-3fdc-41e2-b64b-38f4be228712] description = "two tricks" [83dcd4f3-e089-4d54-855a-73f5346543a3] description = "more tricks" [3107985a-f43e-486a-9ce8-db51547a9941] description = "simple loop with decks of 4 cards" [dca32c31-11ed-49f6-b078-79ab912c1f7b] description = "easy card combination" [1f8488d0-48d3-45ae-b819-59cedad0a5f4] description = "easy card combination, inverted decks" [98878d35-623a-4d05-b81a-7bdc569eb88d] description = "mirrored decks" [3e0ba597-ca10-484b-87a3-31a7df7d6da3] description = "opposite decks" [92334ddb-aaa7-47fa-ab36-e928a8a6a67c] description = "random decks #1" [30477523-9651-4860-84a3-e1ac461bb7fa] description = "random decks #2" [20967de8-9e94-4e0e-9010-14bc1c157432] description = "Kleber 1999" [9f2fdfe8-27f3-4323-816d-6bce98a9c6f7] description = "Collins 2006" [c90b6f8d-7013-49f3-b5cb-14ea006cca1d] description = "Mann and Wu 2007" [a3f1fbc5-1d0b-499a-92a5-22932dfc6bc8] description = "Nessler 2012" [9cefb1ba-e6d1-4ab7-9d8f-76d8e0976d5f] description = "Anderson 2013" [d37c0318-5be6-48d0-ab72-a7aaaff86179] description = "Rucklidge 2014" [4305e479-ba87-432f-8a29-cd2bd75d2f05] description = "Nessler 2021" [252f5cc3-b86d-4251-87ce-f920b7a6a559] description = "Nessler 2022" [b9efcfa4-842f-4542-8112-8389c714d958] description = "Casella 2024, first infinite game found" ================================================ FILE: exercises/practice/camicia/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/camicia/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/camicia/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/camicia/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/camicia/camicia.test.ts ================================================ import { describe, expect, it, xit } from '@jest/globals' import { simulateGame } from './camicia.ts' describe('Camicia', () => { it('two cards, one trick', () => { const playerA = ['2'] const playerB = ['3'] const expected = { status: 'finished', cards: 2, tricks: 1 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) xit('three cards, one trick', () => { const playerA = ['2', '4'] const playerB = ['3'] const expected = { status: 'finished', cards: 3, tricks: 1 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) xit('four cards, one trick', () => { const playerA = ['2', '4'] const playerB = ['3', '5', '6'] const expected = { status: 'finished', cards: 4, tricks: 1 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) xit('the ace reigns supreme', () => { const playerA = ['2', 'A'] const playerB = ['3', '4', '5', '6', '7'] const expected = { status: 'finished', cards: 7, tricks: 1 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) xit('the king beats ace', () => { const playerA = ['2', 'A'] const playerB = ['3', '4', '5', '6', 'K'] const expected = { status: 'finished', cards: 7, tricks: 1 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) xit('the queen seduces the king', () => { const playerA = ['2', 'A', '7', '8', 'Q'] const playerB = ['3', '4', '5', '6', 'K'] const expected = { status: 'finished', cards: 10, tricks: 1 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) xit('the jack betrays the queen', () => { const playerA = ['2', 'A', '7', '8', 'Q'] const playerB = ['3', '4', '5', '6', 'K', '9', 'J'] const expected = { status: 'finished', cards: 12, tricks: 1 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) xit('the 10 just wants to put on a show', () => { const playerA = ['2', 'A', '7', '8', 'Q', '10'] const playerB = ['3', '4', '5', '6', 'K', '9', 'J'] const expected = { status: 'finished', cards: 13, tricks: 1 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) xit('simple loop with decks of 3 cards', () => { const playerA = ['J', '2', '3'] const playerB = ['4', 'J', '5'] const expected = { status: 'loop', cards: 8, tricks: 3 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) // prettier-ignore xit('the story is starting to get a bit complicated', () => { const playerA = [ '2', '6', '6', 'J', '4', 'K', 'Q', '10', 'K', 'J', 'Q', '2', '3', 'K', '5', '6', 'Q', 'Q', 'A', 'A', '6', '9', 'K', 'A', '8', 'K', '2', 'A', '9', 'A', 'Q', '4', 'K', 'K', 'K', '3', '5', 'K', '8', 'Q', '3', 'Q', '7', 'J', 'K', 'J', '9', 'J', '3', '3', 'K', 'K', 'Q', 'A', 'K', '7', '10', 'A', 'Q', '7', '10', 'J', '4', '5', 'J', '9', '10', 'Q', 'J', 'J', 'K', '6', '10', 'J', '6', 'Q', 'J', '5', 'J', 'Q', 'Q', '8', '3', '8', 'A', '2', '6', '9', 'K', '7', 'J', 'K', 'K', '8', 'K', 'Q', '6', '10', 'J', '10', 'J', 'Q', 'J', '10', '3', '8', 'K', 'A', '6', '9', 'K', '2', 'A', 'A', '10', 'J', '6', 'A', '4', 'J', 'A', 'J', 'J', '6', '2', 'J', '3', 'K', '2', '5', '9', 'J', '9', '6', 'K', 'A', '5', 'Q', 'J', '2', 'Q', 'K', 'A', '3', 'K', 'J', 'K', '2', '5', '6', 'Q', 'J', 'Q', 'Q', 'J', '2', 'J', '9', 'Q', '7', '7', 'A', 'Q', '7', 'Q', 'J', 'K', 'J', 'A', '7', '7', '8', 'Q', '10', 'J', '10', 'J', 'J', '9', '2', 'A', '2', ] const playerB = [ '7', '2', '10', 'K', '8', '2', 'J', '9', 'A', '5', '6', 'J', 'Q', '6', 'K', '6', '5', 'A', '4', 'Q', '7', 'J', '7', '10', '2', 'Q', '8', '2', '2', 'K', 'J', 'A', '5', '5', 'A', '4', 'Q', '6', 'Q', 'K', '10', '8', 'Q', '2', '10', 'J', 'A', 'Q', '8', 'Q', 'Q', 'J', 'J', 'A', 'A', '9', '10', 'J', 'K', '4', 'Q', '10', '10', 'J', 'K', '10', '2', 'J', '7', 'A', 'K', 'K', 'J', 'A', 'J', '10', '8', 'K', 'A', '7', 'Q', 'Q', 'J', '3', 'Q', '4', 'A', '3', 'A', 'Q', 'Q', 'Q', '5', '4', 'K', 'J', '10', 'A', 'Q', 'J', '6', 'J', 'A', '10', 'A', '5', '8', '3', 'K', '5', '9', 'Q', '8', '7', '7', 'J', '7', 'Q', 'Q', 'Q', 'A', '7', '8', '9', 'A', 'Q', 'A', 'K', '8', 'A', 'A', 'J', '8', '4', '8', 'K', 'J', 'A', '10', 'Q', '8', 'J', '8', '6', '10', 'Q', 'J', 'J', 'A', 'A', 'J', '5', 'Q', '6', 'J', 'K', 'Q', '8', 'K', '4', 'Q', 'Q', '6', 'J', 'K', '4', '7', 'J', 'J', '9', '9', 'A', 'Q', 'Q', 'K', 'A', '6', '5', 'K', ] const expected = { status: 'finished', cards: 361, tricks: 1 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) xit('two tricks', () => { const playerA = ['J'] const playerB = ['3', 'J'] const expected = { status: 'finished', cards: 5, tricks: 2 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) xit('more tricks', () => { const playerA = ['J', '2', '4'] const playerB = ['3', 'J', 'A'] const expected = { status: 'finished', cards: 12, tricks: 4 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) xit('simple loop with decks of 4 cards', () => { const playerA = ['2', '3', 'J', '6'] const playerB = ['K', '5', 'J', '7'] const expected = { status: 'loop', cards: 16, tricks: 4 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) // prettier-ignore xit('easy card combination', () => { const playerA = [ '4', '8', '7', '5', '4', '10', '3', '9', '7', '3', '10', '10', '6', '8', '2', '8', '5', '4', '5', '9', '6', '5', '2', '8', '10', '9', ] const playerB = [ '6', '9', '4', '7', '2', '2', '3', '6', '7', '3', 'A', 'A', 'A', 'A', 'K', 'K', 'K', 'K', 'Q', 'Q', 'Q', 'Q', 'J', 'J', 'J', 'J', ] const expected = { status: 'finished', cards: 40, tricks: 4 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) // prettier-ignore xit('easy card combination, inverted decks', () => { const playerA = [ '3', '3', '5', '7', '3', '2', '10', '7', '6', '7', 'A', 'A', 'A', 'A', 'K', 'K', 'K', 'K', 'Q', 'Q', 'Q', 'Q', 'J', 'J', 'J', 'J', ] const playerB = [ '5', '10', '8', '2', '6', '7', '2', '4', '9', '2', '6', '10', '10', '5', '4', '8', '4', '8', '6', '9', '8', '5', '9', '3', '4', '9', ] const expected = { status: 'finished', cards: 40, tricks: 4 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) // prettier-ignore xit('mirrored decks', () => { const playerA = [ '2', 'A', '3', 'A', '3', 'K', '4', 'K', '2', 'Q', '2', 'Q', '10', 'J', '5', 'J', '6', '10', '2', '9', '10', '7', '3', '9', '6', '9', ] const playerB = [ '6', 'A', '4', 'A', '7', 'K', '4', 'K', '7', 'Q', '7', 'Q', '5', 'J', '8', 'J', '4', '5', '8', '9', '10', '6', '8', '3', '8', '5', ] const expected = { status: 'finished', cards: 59, tricks: 4 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) // prettier-ignore xit('opposite decks', () => { const playerA = [ '4', 'A', '9', 'A', '4', 'K', '9', 'K', '6', 'Q', '8', 'Q', '8', 'J', '10', 'J', '9', '8', '4', '6', '3', '6', '5', '2', '4', '3', ] const playerB = [ '10', '7', '3', '2', '9', '2', '7', '8', '7', '5', 'J', '7', 'J', '10', 'Q', '10', 'Q', '3', 'K', '5', 'K', '6', 'A', '2', 'A', '5', ] const expected = { status: 'finished', cards: 151, tricks: 21 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) // prettier-ignore xit('random decks #1', () => { const playerA = [ 'K', '10', '9', '8', 'J', '8', '6', '9', '7', 'A', 'K', '5', '4', '4', 'J', '5', 'J', '4', '3', '5', '8', '6', '7', '7', '4', '9', ] const playerB = [ '6', '3', 'K', 'A', 'Q', '10', 'A', '2', 'Q', '8', '2', '10', '10', '2', 'Q', '3', 'K', '9', '7', 'A', '3', 'Q', '5', 'J', '2', '6', ] const expected = { status: 'finished', cards: 542, tricks: 76 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) // prettier-ignore xit('random decks #2', () => { const playerA = [ '8', 'A', '4', '8', '5', 'Q', 'J', '2', '6', '2', '9', '7', 'K', 'A', '8', '10', 'K', '8', '10', '9', 'K', '6', '7', '3', 'K', '9', ] const playerB = [ '10', '5', '2', '6', 'Q', 'J', 'A', '9', '5', '5', '3', '7', '3', 'J', 'A', '2', 'Q', '3', 'J', 'Q', '4', '10', '4', '7', '4', '6', ] const expected = { status: 'finished', cards: 327, tricks: 42 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) // prettier-ignore xit('Kleber 1999', () => { const playerA = [ '4', '8', '9', 'J', 'Q', '8', '5', '5', 'K', '2', 'A', '9', '8', '5', '10', 'A', '4', 'J', '3', 'K', '6', '9', '2', 'Q', 'K', '7', ] const playerB = [ '10', 'J', '3', '2', '4', '10', '4', '7', '5', '3', '6', '6', '7', 'A', 'J', 'Q', 'A', '7', '2', '10', '3', 'K', '9', '6', '8', 'Q', ] const expected = { status: 'finished', cards: 5790, tricks: 805 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) // prettier-ignore xit('Collins 2006', () => { const playerA = [ 'A', '8', 'Q', 'K', '9', '10', '3', '7', '4', '2', 'Q', '3', '2', '10', '9', 'K', 'A', '8', '7', '7', '4', '5', 'J', '9', '2', '10', ] const playerB = [ '4', 'J', 'A', 'K', '8', '5', '6', '6', 'A', '6', '5', 'Q', '4', '6', '10', '8', 'J', '2', '5', '7', 'Q', 'J', '3', '3', 'K', '9', ] const expected = { status: 'finished', cards: 6913, tricks: 960 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) // prettier-ignore xit('Mann and Wu 2007', () => { const playerA = [ 'K', '2', 'K', 'K', '3', '3', '6', '10', 'K', '6', 'A', '2', '5', '5', '7', '9', 'J', 'A', 'A', '3', '4', 'Q', '4', '8', 'J', '6', ] const playerB = [ '4', '5', '2', 'Q', '7', '9', '9', 'Q', '7', 'J', '9', '8', '10', '3', '10', 'J', '4', '10', '8', '6', '8', '7', 'A', 'Q', '5', '2', ] const expected = { status: 'finished', cards: 7157, tricks: 1007 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) // prettier-ignore xit('Nessler 2012', () => { const playerA = [ '10', '3', '6', '7', 'Q', '2', '9', '8', '2', '8', '4', 'A', '10', '6', 'K', '2', '10', 'A', '5', 'A', '2', '4', 'Q', 'J', 'K', '4', ] const playerB = [ '10', 'Q', '4', '6', 'J', '9', '3', 'J', '9', '3', '3', 'Q', 'K', '5', '9', '5', 'K', '6', '5', '7', '8', 'J', 'A', '7', '8', '7', ] const expected = { status: 'finished', cards: 7207, tricks: 1015 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) // prettier-ignore xit('Anderson 2013', () => { const playerA = [ '6', '7', 'A', '3', 'Q', '3', '5', 'J', '3', '2', 'J', '7', '4', '5', 'Q', '10', '5', 'A', 'J', '2', 'K', '8', '9', '9', 'K', '3', ] const playerB = [ '4', 'J', '6', '9', '8', '5', '10', '7', '9', 'Q', '2', '7', '10', '8', '4', '10', 'A', '6', '4', 'A', '6', '8', 'Q', 'K', 'K', '2', ] const expected = { status: 'finished', cards: 7225, tricks: 1016 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) // prettier-ignore xit('Rucklidge 2014', () => { const playerA = [ '8', 'J', '2', '9', '4', '4', '5', '8', 'Q', '3', '9', '3', '6', '2', '8', 'A', 'A', 'A', '9', '4', '7', '2', '5', 'Q', 'Q', '3', ] const playerB = [ 'K', '7', '10', '6', '3', 'J', 'A', '7', '6', '5', '5', '8', '10', '9', '10', '4', '2', '7', 'K', 'Q', '10', 'K', '6', 'J', 'J', 'K', ] const expected = { status: 'finished', cards: 7959, tricks: 1122 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) // prettier-ignore xit('Nessler 2021', () => { const playerA = [ '7', '2', '3', '4', 'K', '9', '6', '10', 'A', '8', '9', 'Q', '7', 'A', '4', '8', 'J', 'J', 'A', '4', '3', '2', '5', '6', '6', 'J', ] const playerB = [ '3', '10', '8', '9', '8', 'K', 'K', '2', '5', '5', '7', '6', '4', '3', '5', '7', 'A', '9', 'J', 'K', '2', 'Q', '10', 'Q', '10', 'Q', ] const expected = { status: 'finished', cards: 7972, tricks: 1106 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) // prettier-ignore xit('Nessler 2022', () => { const playerA = [ '2', '10', '10', 'A', 'J', '3', '8', 'Q', '2', '5', '5', '5', '9', '2', '4', '3', '10', 'Q', 'A', 'K', 'Q', 'J', 'J', '9', 'Q', 'K', ] const playerB = [ '10', '7', '6', '3', '6', 'A', '8', '9', '4', '3', 'K', 'J', '6', 'K', '4', '9', '7', '8', '5', '7', '8', '2', 'A', '7', '4', '6', ] const expected = { status: 'finished', cards: 8344, tricks: 1164 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) // prettier-ignore xit('Casella 2024, first infinite game found', () => { const playerA = [ '2', '8', '4', 'K', '5', '2', '3', 'Q', '6', 'K', 'Q', 'A', 'J', '3', '5', '9', '8', '3', 'A', 'A', 'J', '4', '4', 'J', '7', '5', ] const playerB = [ '7', '7', '8', '6', '10', '10', '6', '10', '7', '2', 'Q', '6', '3', '2', '4', 'K', 'Q', '10', 'J', '5', '9', '8', '9', '9', 'K', 'A', ] const expected = { status: 'loop', cards: 474, tricks: 66 } expect(simulateGame(playerA, playerB)).toEqual(expected) }) }) ================================================ FILE: exercises/practice/camicia/camicia.ts ================================================ export const simulateGame = (playerA: unknown, playerB: unknown): unknown => { throw new Error('Remove this statement and implement this function') } ================================================ FILE: exercises/practice/camicia/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/camicia/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/camicia/package.json ================================================ { "name": "@exercism/typescript-camicia", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/camicia/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/camicia/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/circular-buffer/.docs/instructions.md ================================================ # Instructions A circular buffer, cyclic buffer or ring buffer is a data structure that uses a single, fixed-size buffer as if it were connected end-to-end. A circular buffer first starts empty and of some predefined length. For example, this is a 7-element buffer: ```text [ ][ ][ ][ ][ ][ ][ ] ``` Assume that a 1 is written into the middle of the buffer (exact starting location does not matter in a circular buffer): ```text [ ][ ][ ][1][ ][ ][ ] ``` Then assume that two more elements are added — 2 & 3 — which get appended after the 1: ```text [ ][ ][ ][1][2][3][ ] ``` If two elements are then removed from the buffer, the oldest values inside the buffer are removed. The two elements removed, in this case, are 1 & 2, leaving the buffer with just a 3: ```text [ ][ ][ ][ ][ ][3][ ] ``` If the buffer has 7 elements then it is completely full: ```text [5][6][7][8][9][3][4] ``` When the buffer is full an error will be raised, alerting the client that further writes are blocked until a slot becomes free. When the buffer is full, the client can opt to overwrite the oldest data with a forced write. In this case, two more elements — A & B — are added and they overwrite the 3 & 4: ```text [5][6][7][8][9][A][B] ``` 3 & 4 have been replaced by A & B making 5 now the oldest data in the buffer. Finally, if two elements are removed then what would be returned is 5 & 6 yielding the buffer: ```text [ ][ ][7][8][9][A][B] ``` Because there is space available, if the client again uses overwrite to store C & D then the space where 5 & 6 were stored previously will be used not the location of 7 & 8. 7 is still the oldest element and the buffer is once again full. ```text [C][D][7][8][9][A][B] ``` ================================================ FILE: exercises/practice/circular-buffer/.meta/config.json ================================================ { "authors": [ "jspengeman" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "circular-buffer.ts" ], "test": [ "circular-buffer.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "A data structure that uses a single, fixed-size buffer as if it were connected end-to-end.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Circular_buffer" } ================================================ FILE: exercises/practice/circular-buffer/.meta/proof.ci.ts ================================================ interface Buffer { read(): T | undefined write(value: T): void clear(): void } export class BufferFullError extends Error { constructor() { super('Buffer is full.') } } export class BufferEmptyError extends Error { constructor() { super('Buffer is empty.') } } export default class CircularBuffer implements Buffer { private capacity: number private buffer: T[] = [] constructor(capacity: number) { this.capacity = capacity } public read(): T | undefined { if (this.buffer.length === 0) { throw new BufferEmptyError() } return this.buffer.shift() } public write(value: T): void { if (this.buffer.length + 1 > this.capacity) { throw new BufferFullError() } this.buffer.push(value) } public forceWrite(value: T): void { if (this.buffer.length === this.capacity) { this.read() } this.buffer.push(value) } public clear(): void { this.buffer = [] } } ================================================ FILE: exercises/practice/circular-buffer/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [28268ed4-4ff3-45f3-820e-895b44d53dfa] description = "reading empty buffer should fail" [2e6db04a-58a1-425d-ade8-ac30b5f318f3] description = "can read an item just written" [90741fe8-a448-45ce-be2b-de009a24c144] description = "each item may only be read once" [be0e62d5-da9c-47a8-b037-5db21827baa7] description = "items are read in the order they are written" [2af22046-3e44-4235-bfe6-05ba60439d38] description = "full buffer can't be written to" [547d192c-bbf0-4369-b8fa-fc37e71f2393] description = "a read frees up capacity for another write" [04a56659-3a81-4113-816b-6ecb659b4471] description = "read position is maintained even across multiple writes" [60c3a19a-81a7-43d7-bb0a-f07242b1111f] description = "items cleared out of buffer can't be read" [45f3ae89-3470-49f3-b50e-362e4b330a59] description = "clear frees up capacity for another write" [e1ac5170-a026-4725-bfbe-0cf332eddecd] description = "clear does nothing on empty buffer" [9c2d4f26-3ec7-453f-a895-7e7ff8ae7b5b] description = "overwrite acts like write on non-full buffer" [880f916b-5039-475c-bd5c-83463c36a147] description = "overwrite replaces the oldest item on full buffer" [bfecab5b-aca1-4fab-a2b0-cd4af2b053c3] description = "overwrite replaces the oldest item remaining in buffer following a read" [9cebe63a-c405-437b-8b62-e3fdc1ecec5a] description = "initial clear does not affect wrapping around" ================================================ FILE: exercises/practice/circular-buffer/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/circular-buffer/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/circular-buffer/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/circular-buffer/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/circular-buffer/circular-buffer.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import CircularBuffer, { BufferFullError, BufferEmptyError, } from './circular-buffer.ts' describe('CircularBuffer', () => { it('reading an empty buffer throws a BufferEmptyError', () => { const buffer = new CircularBuffer(1) expect(() => buffer.read()).toThrow(BufferEmptyError) }) xit('write and read back one item', () => { const buffer = new CircularBuffer(1) buffer.write('1') expect(buffer.read()).toBe('1') expect(() => buffer.read()).toThrow(BufferEmptyError) }) xit('write and read back multiple items', () => { const buffer = new CircularBuffer(2) buffer.write('1') buffer.write('2') expect(buffer.read()).toBe('1') expect(buffer.read()).toBe('2') expect(() => buffer.read()).toThrow(BufferEmptyError) }) xit('clearing a buffer', () => { const buffer = new CircularBuffer(2) buffer.write('1') buffer.write('2') buffer.clear() expect(() => buffer.read()).toThrow(BufferEmptyError) buffer.write('3') buffer.write('4') expect(buffer.read()).toBe('3') expect(buffer.read()).toBe('4') }) xit('alternate write and read', () => { const buffer = new CircularBuffer(2) buffer.write('1') expect(buffer.read()).toBe('1') buffer.write('2') expect(buffer.read()).toBe('2') }) xit('reads back oldest item', () => { const buffer = new CircularBuffer(3) buffer.write('1') buffer.write('2') buffer.read() buffer.write('3') expect(buffer.read()).toBe('2') expect(buffer.read()).toBe('3') }) xit('writing to a full buffer throws a BufferFullError', () => { const buffer = new CircularBuffer(2) buffer.write('1') buffer.write('2') expect(() => buffer.write('A')).toThrow(BufferFullError) }) xit('forced writes over write oldest item in a full buffer', () => { const buffer = new CircularBuffer(2) buffer.write('1') buffer.write('2') buffer.forceWrite('A') expect(buffer.read()).toBe('2') expect(buffer.read()).toBe('A') expect(() => buffer.read()).toThrow(BufferEmptyError) }) xit('forced writes act like write in a non-full buffer', () => { const buffer = new CircularBuffer(2) buffer.write('1') buffer.forceWrite('2') expect(buffer.read()).toBe('1') expect(buffer.read()).toBe('2') expect(() => buffer.read()).toThrow(BufferEmptyError) }) xit('alternate force write and read into full buffer', () => { const buffer = new CircularBuffer(5) buffer.write('1') buffer.write('2') buffer.write('3') buffer.read() buffer.read() buffer.write('4') buffer.read() buffer.write('5') buffer.write('6') buffer.write('7') buffer.write('8') buffer.forceWrite('A') buffer.forceWrite('B') expect(buffer.read()).toBe('6') expect(buffer.read()).toBe('7') expect(buffer.read()).toBe('8') expect(buffer.read()).toBe('A') expect(buffer.read()).toBe('B') expect(() => buffer.read()).toThrow(BufferEmptyError) }) }) ================================================ FILE: exercises/practice/circular-buffer/circular-buffer.ts ================================================ export default class CircularBuffer { constructor(initial: unknown) { throw new Error('Remove this line and implement the function') } write(value: unknown): unknown { throw new Error('Remove this line and implement the function') } read(): unknown { throw new Error('Remove this line and implement the function') } forceWrite(value: unknown): unknown { throw new Error('Remove this line and implement the function') } clear(): unknown { throw new Error('Remove this line and implement the function') } } export class BufferFullError extends Error { constructor() { super() throw new Error('Remove this line and implement the function') } } export class BufferEmptyError extends Error { constructor() { super() throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/circular-buffer/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/circular-buffer/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/circular-buffer/package.json ================================================ { "name": "@exercism/typescript-circular-buffer", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/circular-buffer/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/circular-buffer/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/clock/.docs/instructions.md ================================================ # Instructions Implement a clock that handles times without dates. You should be able to add and subtract minutes to it. Two clocks that represent the same time should be equal to each other. ================================================ FILE: exercises/practice/clock/.meta/config.json ================================================ { "authors": [ "rwaskiewicz" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "clock.ts" ], "test": [ "clock.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Implement a clock that handles times without dates.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Pairing session with Erin Drummond" } ================================================ FILE: exercises/practice/clock/.meta/proof.ci.ts ================================================ export class Clock { private hour!: number private minute!: number constructor(hour: number, minute: number = 0) { this.reset() const totalMinutes = hour * 60 + minute this.adjustTime(totalMinutes) } private reset(): void { this.hour = 0 this.minute = 0 } public getHour(): number { return this.hour } public getMinute(): number { return this.minute } public toString(): string { return `${this.formatNumber(this.hour)}:${this.formatNumber(this.minute)}` } private formatNumber(numberToFormat: number): string { const numberString = numberToFormat.toString() return numberString.length === 1 ? `0${numberString}` : numberString } public plus(minutes: number): Clock { this.adjustTime(minutes) return this } public minus(minutes: number): Clock { this.adjustTime(-1 * minutes) return this } public equals(clock: Clock): boolean { return this.hour === clock.getHour() && this.minute === clock.getMinute() } private adjustTime(delta: number): void { const minutesPerDay = 1440 const minutesPerHour = 60 const hoursPerDay = 24 delta = Math.abs(delta) >= minutesPerDay ? delta % minutesPerDay : delta const currentMinutes = this.hour * minutesPerHour + this.minute let newMinutes = (currentMinutes + delta) % minutesPerDay newMinutes = newMinutes < 0 ? (newMinutes += minutesPerDay) : newMinutes this.hour = Math.floor(newMinutes / minutesPerHour) % hoursPerDay this.minute = newMinutes - this.hour * minutesPerHour } } ================================================ FILE: exercises/practice/clock/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [a577bacc-106b-496e-9792-b3083ea8705e] description = "on the hour" [b5d0c360-3b88-489b-8e84-68a1c7a4fa23] description = "past the hour" [473223f4-65f3-46ff-a9f7-7663c7e59440] description = "midnight is zero hours" [ca95d24a-5924-447d-9a96-b91c8334725c] description = "hour rolls over" [f3826de0-0925-4d69-8ac8-89aea7e52b78] description = "hour rolls over continuously" [a02f7edf-dfd4-4b11-b21a-86de3cc6a95c] description = "sixty minutes is next hour" [8f520df6-b816-444d-b90f-8a477789beb5] description = "minutes roll over" [c75c091b-47ac-4655-8d40-643767fc4eed] description = "minutes roll over continuously" [06343ecb-cf39-419d-a3f5-dcbae0cc4c57] description = "hour and minutes roll over" [be60810e-f5d9-4b58-9351-a9d1e90e660c] description = "hour and minutes roll over continuously" [1689107b-0b5c-4bea-aad3-65ec9859368a] description = "hour and minutes roll over to exactly midnight" [d3088ee8-91b7-4446-9e9d-5e2ad6219d91] description = "negative hour" [77ef6921-f120-4d29-bade-80d54aa43b54] description = "negative hour rolls over" [359294b5-972f-4546-bb9a-a85559065234] description = "negative hour rolls over continuously" [509db8b7-ac19-47cc-bd3a-a9d2f30b03c0] description = "negative minutes" [5d6bb225-130f-4084-84fd-9e0df8996f2a] description = "negative minutes roll over" [d483ceef-b520-4f0c-b94a-8d2d58cf0484] description = "negative minutes roll over continuously" [1cd19447-19c6-44bf-9d04-9f8305ccb9ea] description = "negative sixty minutes is previous hour" [9d3053aa-4f47-4afc-bd45-d67a72cef4dc] description = "negative hour and minutes both roll over" [51d41fcf-491e-4ca0-9cae-2aa4f0163ad4] description = "negative hour and minutes both roll over continuously" [d098e723-ad29-4ef9-997a-2693c4c9d89a] description = "add minutes" [b6ec8f38-e53e-4b22-92a7-60dab1f485f4] description = "add no minutes" [efd349dd-0785-453e-9ff8-d7452a8e7269] description = "add to next hour" [749890f7-aba9-4702-acce-87becf4ef9fe] description = "add more than one hour" [da63e4c1-1584-46e3-8d18-c9dc802c1713] description = "add more than two hours with carry" [be167a32-3d33-4cec-a8bc-accd47ddbb71] description = "add across midnight" [6672541e-cdae-46e4-8be7-a820cc3be2a8] description = "add more than one day (1500 min = 25 hrs)" [1918050d-c79b-4cb7-b707-b607e2745c7e] description = "add more than two days" [37336cac-5ede-43a5-9026-d426cbe40354] description = "subtract minutes" [0aafa4d0-3b5f-4b12-b3af-e3a9e09c047b] description = "subtract to previous hour" [9b4e809c-612f-4b15-aae0-1df0acb801b9] description = "subtract more than an hour" [8b04bb6a-3d33-4e6c-8de9-f5de6d2c70d6] description = "subtract across midnight" [07c3bbf7-ce4d-4658-86e8-4a77b7a5ccd9] description = "subtract more than two hours" [90ac8a1b-761c-4342-9c9c-cdc3ed5db097] description = "subtract more than two hours with borrow" [2149f985-7136-44ad-9b29-ec023a97a2b7] description = "subtract more than one day (1500 min = 25 hrs)" [ba11dbf0-ac27-4acb-ada9-3b853ec08c97] description = "subtract more than two days" [f2fdad51-499f-4c9b-a791-b28c9282e311] description = "clocks with same time" [5d409d4b-f862-4960-901e-ec430160b768] description = "clocks a minute apart" [a6045fcf-2b52-4a47-8bb2-ef10a064cba5] description = "clocks an hour apart" [66b12758-0be5-448b-a13c-6a44bce83527] description = "clocks with hour overflow" [2b19960c-212e-4a71-9aac-c581592f8111] description = "clocks with hour overflow by several days" [6f8c6541-afac-4a92-b0c2-b10d4e50269f] description = "clocks with negative hour" [bb9d5a68-e324-4bf5-a75e-0e9b1f97a90d] description = "clocks with negative hour that wraps" [56c0326d-565b-4d19-a26f-63b3205778b7] description = "clocks with negative hour that wraps multiple times" [c90b9de8-ddff-4ffe-9858-da44a40fdbc2] description = "clocks with minute overflow" [533a3dc5-59a7-491b-b728-a7a34fe325de] description = "clocks with minute overflow by several days" [fff49e15-f7b7-4692-a204-0f6052d62636] description = "clocks with negative minute" [605c65bb-21bd-43eb-8f04-878edf508366] description = "clocks with negative minute that wraps" [b87e64ed-212a-4335-91fd-56da8421d077] description = "clocks with negative minute that wraps multiple times" [822fbf26-1f3b-4b13-b9bf-c914816b53dd] description = "clocks with negative hours and minutes" [e787bccd-cf58-4a1d-841c-ff80eaaccfaa] description = "clocks with negative hours and minutes that wrap" [96969ca8-875a-48a1-86ae-257a528c44f5] description = "full clock and zeroed clock" ================================================ FILE: exercises/practice/clock/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/clock/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/clock/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/clock/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/clock/clock.test.ts ================================================ import { describe, xdescribe, it, expect, xit } from '@jest/globals' import { Clock } from './clock.ts' describe('Clock', () => { describe('Creating a new clock with an initial time', () => { it('on the hour', () => { expect(new Clock(8).toString()).toEqual('08:00') }) xit('past the hour', () => { expect(new Clock(11, 9).toString()).toEqual('11:09') }) xit('midnight is zero hours', () => { expect(new Clock(24, 0).toString()).toEqual('00:00') }) xit('hour rolls over', () => { expect(new Clock(25, 0).toString()).toEqual('01:00') }) xit('hour rolls over continuously', () => { expect(new Clock(100, 0).toString()).toEqual('04:00') }) xit('sixty minutes is next hour', () => { expect(new Clock(1, 60).toString()).toEqual('02:00') }) xit('minutes roll over', () => { expect(new Clock(0, 160).toString()).toEqual('02:40') }) xit('minutes roll over continuously', () => { expect(new Clock(0, 1723).toString()).toEqual('04:43') }) xit('hour and minutes roll over', () => { expect(new Clock(25, 160).toString()).toEqual('03:40') }) xit('hour and minutes roll over continuously', () => { expect(new Clock(201, 3001).toString()).toEqual('11:01') }) xit('hour and minutes roll over to exactly midnight', () => { expect(new Clock(72, 8640).toString()).toEqual('00:00') }) xit('negative hour', () => { expect(new Clock(-1, 15).toString()).toEqual('23:15') }) xit('negative hour rolls over', () => { expect(new Clock(-25, 0).toString()).toEqual('23:00') }) xit('negative hour rolls over continuously', () => { expect(new Clock(-91, 0).toString()).toEqual('05:00') }) xit('negative minutes', () => { expect(new Clock(1, -40).toString()).toEqual('00:20') }) xit('negative minutes rolls over', () => { expect(new Clock(1, -160).toString()).toEqual('22:20') }) xit('negative minutes rolls over continuously', () => { expect(new Clock(1, -4820).toString()).toEqual('16:40') }) xit('negative hour and minutes both roll over', () => { expect(new Clock(-25, -160).toString()).toEqual('20:20') }) xit('negative hour and minutes both roll over continuously', () => { expect(new Clock(-121, -5810).toString()).toEqual('22:10') }) xdescribe('Adding and subtracting minutes', () => { xit('add minutes', () => { expect(new Clock(10, 0).plus(3).toString()).toEqual('10:03') }) xit('add no minutes', () => { expect(new Clock(6, 41).plus(0).toString()).toEqual('06:41') }) xit('add to next hour', () => { expect(new Clock(0, 45).plus(40).toString()).toEqual('01:25') }) xit('add more than one hour', () => { expect(new Clock(10, 0).plus(61).toString()).toEqual('11:01') }) xit('add more than two hours with carry', () => { expect(new Clock(0, 45).plus(160).toString()).toEqual('03:25') }) xit('add across midnight', () => { expect(new Clock(23, 59).plus(2).toString()).toEqual('00:01') }) xit('add more than one day (1500 min = 25 hrs)', () => { expect(new Clock(5, 32).plus(1500).toString()).toEqual('06:32') }) xit('add more than two days', () => { expect(new Clock(1, 1).plus(3500).toString()).toEqual('11:21') }) xit('subtract minutes', () => { expect(new Clock(10, 3).minus(3).toString()).toEqual('10:00') }) xit('subtract to previous hour', () => { expect(new Clock(10, 3).minus(30).toString()).toEqual('09:33') }) xit('subtract more than an hour', () => { expect(new Clock(10, 3).minus(70).toString()).toEqual('08:53') }) xit('subtract across midnight', () => { expect(new Clock(0, 3).minus(4).toString()).toEqual('23:59') }) xit('subtract more than two hours', () => { expect(new Clock(0, 0).minus(160).toString()).toEqual('21:20') }) xit('subtract more than two hours with borrow', () => { expect(new Clock(6, 15).minus(160).toString()).toEqual('03:35') }) xit('subtract more than one day (1500 min = 25 hrs)', () => { expect(new Clock(5, 32).minus(1500).toString()).toEqual('04:32') }) xit('subtract more than two days', () => { expect(new Clock(2, 20).minus(3000).toString()).toEqual('00:20') }) }) xdescribe('Construct two separate clocks, set times, test if they are equal', () => { xit('clocks with same time', () => { expect(new Clock(15, 37).equals(new Clock(15, 37))).toBeTruthy() }) xit('clocks a minute apart', () => { expect(new Clock(15, 36).equals(new Clock(15, 37))).toBeFalsy() }) xit('clocks an hour apart', () => { expect(new Clock(14, 37).equals(new Clock(15, 37))).toBeFalsy() }) xit('clocks with hour overflow', () => { expect(new Clock(10, 37).equals(new Clock(34, 37))).toBeTruthy() }) xit('clocks with hour overflow by several days', () => { expect(new Clock(3, 11).equals(new Clock(99, 11))).toBeTruthy() }) xit('clocks with negative hour', () => { expect(new Clock(22, 40).equals(new Clock(-2, 40))).toBeTruthy() }) xit('clocks with negative hour that wraps', () => { expect(new Clock(17, 3).equals(new Clock(-31, 3))).toBeTruthy() }) xit('clocks with negative hour that wraps multiple times', () => { expect(new Clock(13, 49).equals(new Clock(-83, 49))).toBeTruthy() }) xit('clocks with minute overflow', () => { expect(new Clock(0, 1).equals(new Clock(0, 1441))).toBeTruthy() }) xit('clocks with minute overflow by several days', () => { expect(new Clock(2, 2).equals(new Clock(2, 4322))).toBeTruthy() }) xit('clocks with negative minute', () => { expect(new Clock(2, 40).equals(new Clock(3, -20))).toBeTruthy() }) xit('clocks with negative minute that wraps', () => { expect(new Clock(4, 10).equals(new Clock(5, -1490))).toBeTruthy() }) xit('clocks with negative minute that wraps multiple times', () => { expect(new Clock(6, 15).equals(new Clock(6, -4305))).toBeTruthy() }) xit('clocks with negative hours and minutes', () => { expect(new Clock(7, 32).equals(new Clock(-12, -268))).toBeTruthy() }) xit('clocks with negative hours and minutes that wrap', () => { expect(new Clock(18, 7).equals(new Clock(-54, -11513))).toBeTruthy() }) }) }) }) ================================================ FILE: exercises/practice/clock/clock.ts ================================================ export class Clock { constructor(hour: unknown, minute?: unknown) { throw new Error('Remove this line and implement the function') } public toString(): unknown { throw new Error('Remove this line and implement the function') } public plus(minutes: unknown): Clock { throw new Error('Remove this line and implement the function') } public minus(minutes: unknown): Clock { throw new Error('Remove this line and implement the function') } public equals(other: unknown): unknown { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/clock/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/clock/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/clock/package.json ================================================ { "name": "@exercism/typescript-clock", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/clock/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/clock/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/collatz-conjecture/.docs/instructions.md ================================================ # Instructions Given a positive integer, return the number of steps it takes to reach 1 according to the rules of the Collatz Conjecture. ================================================ FILE: exercises/practice/collatz-conjecture/.docs/introduction.md ================================================ # Introduction One evening, you stumbled upon an old notebook filled with cryptic scribbles, as though someone had been obsessively chasing an idea. On one page, a single question stood out: **Can every number find its way to 1?** It was tied to something called the **Collatz Conjecture**, a puzzle that has baffled thinkers for decades. The rules were deceptively simple. Pick any positive integer. - If it's even, divide it by 2. - If it's odd, multiply it by 3 and add 1. Then, repeat these steps with the result, continuing indefinitely. Curious, you picked number 12 to test and began the journey: 12 ➜ 6 ➜ 3 ➜ 10 ➜ 5 ➜ 16 ➜ 8 ➜ 4 ➜ 2 ➜ 1 Counting from the second number (6), it took 9 steps to reach 1, and each time the rules repeated, the number kept changing. At first, the sequence seemed unpredictable — jumping up, down, and all over. Yet, the conjecture claims that no matter the starting number, we'll always end at 1. It was fascinating, but also puzzling. Why does this always seem to work? Could there be a number where the process breaks down, looping forever or escaping into infinity? The notebook suggested solving this could reveal something profound — and with it, fame, [fortune][collatz-prize], and a place in history awaits whoever could unlock its secrets. [collatz-prize]: https://mathprize.net/posts/collatz-conjecture/ ================================================ FILE: exercises/practice/collatz-conjecture/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "collatz-conjecture.ts" ], "test": [ "collatz-conjecture.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Calculate the number of steps to reach 1 using the Collatz conjecture.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Collatz_conjecture" } ================================================ FILE: exercises/practice/collatz-conjecture/.meta/proof.ci.ts ================================================ export function steps(n: number): number { if (n <= 0 || !Number.isInteger(n)) { throw new Error('Only positive integers are allowed') } return calculateStepsRecursively(n, 0) } function calculateStepsRecursively(n: number, count: number): number { if (n === 1) { return count } else if (n % 2 === 0) { return calculateStepsRecursively(n / 2, ++count) } else { return calculateStepsRecursively(n * 3 + 1, ++count) } } ================================================ FILE: exercises/practice/collatz-conjecture/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [540a3d51-e7a6-47a5-92a3-4ad1838f0bfd] description = "zero steps for one" [3d76a0a6-ea84-444a-821a-f7857c2c1859] description = "divide if even" [754dea81-123c-429e-b8bc-db20b05a87b9] description = "even and odd steps" [ecfd0210-6f85-44f6-8280-f65534892ff6] description = "large number of even and odd steps" [7d4750e6-def9-4b86-aec7-9f7eb44f95a3] description = "zero is an error" include = false [2187673d-77d6-4543-975e-66df6c50e2da] description = "zero is an error" reimplements = "7d4750e6-def9-4b86-aec7-9f7eb44f95a3" [c6c795bf-a288-45e9-86a1-841359ad426d] description = "negative value is an error" include = false [ec11f479-56bc-47fd-a434-bcd7a31a7a2e] description = "negative value is an error" reimplements = "c6c795bf-a288-45e9-86a1-841359ad426d" ================================================ FILE: exercises/practice/collatz-conjecture/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/collatz-conjecture/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/collatz-conjecture/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/collatz-conjecture/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/collatz-conjecture/collatz-conjecture.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { steps } from './collatz-conjecture.ts' describe('CollatzConjecture', () => { it('zero steps for one', () => { const expected = 0 expect(steps(1)).toBe(expected) }) xit('divide if even', () => { const expected = 4 expect(steps(16)).toBe(expected) }) xit('even and odd steps', () => { const expected = 9 expect(steps(12)).toBe(expected) }) xit('Large number of even and odd steps', () => { const expected = 152 expect(steps(1000000)).toBe(expected) }) xit('zero is an error', () => { const expected = 'Only positive integers are allowed' expect(() => { steps(0) }).toThrow(expected) }) xit('negative value is an error', () => { const expected = 'Only positive integers are allowed' expect(() => { steps(-15) }).toThrow(expected) }) xit('non-integer value is an error', () => { const expected = 'Only positive integers are allowed' expect(() => { steps(3.1415) }).toThrow(expected) }) }) ================================================ FILE: exercises/practice/collatz-conjecture/collatz-conjecture.ts ================================================ export function steps(count: unknown): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/collatz-conjecture/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/collatz-conjecture/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/collatz-conjecture/package.json ================================================ { "name": "@exercism/typescript-collatz-conjecture", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/collatz-conjecture/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/collatz-conjecture/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/complex-numbers/.docs/instructions.md ================================================ # Instructions A **complex number** is expressed in the form `z = a + b * i`, where: - `a` is the **real part** (a real number), - `b` is the **imaginary part** (also a real number), and - `i` is the **imaginary unit** satisfying `i^2 = -1`. ## Operations on Complex Numbers ### Conjugate The conjugate of the complex number `z = a + b * i` is given by: ```text zc = a - b * i ``` ### Absolute Value The absolute value (or modulus) of `z` is defined as: ```text |z| = sqrt(a^2 + b^2) ``` The square of the absolute value is computed as the product of `z` and its conjugate `zc`: ```text |z|^2 = z * zc = a^2 + b^2 ``` ### Addition The sum of two complex numbers `z1 = a + b * i` and `z2 = c + d * i` is computed by adding their real and imaginary parts separately: ```text z1 + z2 = (a + b * i) + (c + d * i) = (a + c) + (b + d) * i ``` ### Subtraction The difference of two complex numbers is obtained by subtracting their respective parts: ```text z1 - z2 = (a + b * i) - (c + d * i) = (a - c) + (b - d) * i ``` ### Multiplication The product of two complex numbers is defined as: ```text z1 * z2 = (a + b * i) * (c + d * i) = (a * c - b * d) + (b * c + a * d) * i ``` ### Reciprocal The reciprocal of a non-zero complex number is given by: ```text 1 / z = 1 / (a + b * i) = a / (a^2 + b^2) - b / (a^2 + b^2) * i ``` ### Division The division of one complex number by another is given by: ```text z1 / z2 = z1 * (1 / z2) = (a + b * i) / (c + d * i) = (a * c + b * d) / (c^2 + d^2) + (b * c - a * d) / (c^2 + d^2) * i ``` ### Exponentiation Raising _e_ (the base of the natural logarithm) to a complex exponent can be expressed using Euler's formula: ```text e^(a + b * i) = e^a * e^(b * i) = e^a * (cos(b) + i * sin(b)) ``` ## Implementation Requirements Given that you should not use built-in support for complex numbers, implement the following operations: - **addition** of two complex numbers - **subtraction** of two complex numbers - **multiplication** of two complex numbers - **division** of two complex numbers - **conjugate** of a complex number - **absolute value** of a complex number - **exponentiation** of _e_ (the base of the natural logarithm) to a complex number ================================================ FILE: exercises/practice/complex-numbers/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "complex-numbers.ts" ], "test": [ "complex-numbers.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Implement complex numbers.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Complex_number" } ================================================ FILE: exercises/practice/complex-numbers/.meta/proof.ci.ts ================================================ export class ComplexNumber { public readonly real: number public readonly imag: number constructor(real: number, imag: number) { this.real = real this.imag = imag } public add(other: ComplexNumber): ComplexNumber { return new ComplexNumber(this.real + other.real, this.imag + other.imag) } public sub(other: ComplexNumber): ComplexNumber { return new ComplexNumber(this.real - other.real, this.imag - other.imag) } public mul(other: ComplexNumber): ComplexNumber { return new ComplexNumber( this.real * other.real - this.imag * other.imag, this.imag * other.real + this.real * other.imag ) } public div(other: ComplexNumber): ComplexNumber { return new ComplexNumber( (this.real * other.real + this.imag * other.imag) / (other.real * other.real + other.imag * other.imag), (this.imag * other.real - this.real * other.imag) / (other.real * other.real + other.imag * other.imag) ) } public get abs(): number { return Math.sqrt(this.real * this.real + this.imag * this.imag) } public get conj(): ComplexNumber { return new ComplexNumber(this.real, this.imag !== 0 ? -this.imag : 0) } public get exp(): ComplexNumber { return new ComplexNumber( Math.exp(this.real) * Math.cos(this.imag), Math.exp(this.real) * Math.sin(this.imag) ) } } ================================================ FILE: exercises/practice/complex-numbers/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [9f98e133-eb7f-45b0-9676-cce001cd6f7a] description = "Real part of a purely real number" [07988e20-f287-4bb7-90cf-b32c4bffe0f3] description = "Real part of a purely imaginary number" [4a370e86-939e-43de-a895-a00ca32da60a] description = "Real part of a number with real and imaginary part" [9b3fddef-4c12-4a99-b8f8-e3a42c7ccef6] description = "Imaginary part of a purely real number" [a8dafedd-535a-4ed3-8a39-fda103a2b01e] description = "Imaginary part of a purely imaginary number" [0f998f19-69ee-4c64-80ef-01b086feab80] description = "Imaginary part of a number with real and imaginary part" [a39b7fd6-6527-492f-8c34-609d2c913879] description = "Imaginary unit" [9a2c8de9-f068-4f6f-b41c-82232cc6c33e] description = "Add purely real numbers" [657c55e1-b14b-4ba7-bd5c-19db22b7d659] description = "Add purely imaginary numbers" [4e1395f5-572b-4ce8-bfa9-9a63056888da] description = "Add numbers with real and imaginary part" [1155dc45-e4f7-44b8-af34-a91aa431475d] description = "Subtract purely real numbers" [f95e9da8-acd5-4da4-ac7c-c861b02f774b] description = "Subtract purely imaginary numbers" [f876feb1-f9d1-4d34-b067-b599a8746400] description = "Subtract numbers with real and imaginary part" [8a0366c0-9e16-431f-9fd7-40ac46ff4ec4] description = "Multiply purely real numbers" [e560ed2b-0b80-4b4f-90f2-63cefc911aaf] description = "Multiply purely imaginary numbers" [4d1d10f0-f8d4-48a0-b1d0-f284ada567e6] description = "Multiply numbers with real and imaginary part" [b0571ddb-9045-412b-9c15-cd1d816d36c1] description = "Divide purely real numbers" [5bb4c7e4-9934-4237-93cc-5780764fdbdd] description = "Divide purely imaginary numbers" [c4e7fef5-64ac-4537-91c2-c6529707701f] description = "Divide numbers with real and imaginary part" [c56a7332-aad2-4437-83a0-b3580ecee843] description = "Absolute value of a positive purely real number" [cf88d7d3-ee74-4f4e-8a88-a1b0090ecb0c] description = "Absolute value of a negative purely real number" [bbe26568-86c1-4bb4-ba7a-da5697e2b994] description = "Absolute value of a purely imaginary number with positive imaginary part" [3b48233d-468e-4276-9f59-70f4ca1f26f3] description = "Absolute value of a purely imaginary number with negative imaginary part" [fe400a9f-aa22-4b49-af92-51e0f5a2a6d3] description = "Absolute value of a number with real and imaginary part" [fb2d0792-e55a-4484-9443-df1eddfc84a2] description = "Conjugate a purely real number" [e37fe7ac-a968-4694-a460-66cb605f8691] description = "Conjugate a purely imaginary number" [f7704498-d0be-4192-aaf5-a1f3a7f43e68] description = "Conjugate a number with real and imaginary part" [6d96d4c6-2edb-445b-94a2-7de6d4caaf60] description = "Euler's identity/formula" [2d2c05a0-4038-4427-a24d-72f6624aa45f] description = "Exponential of 0" [ed87f1bd-b187-45d6-8ece-7e331232c809] description = "Exponential of a purely real number" [08eedacc-5a95-44fc-8789-1547b27a8702] description = "Exponential of a number with real and imaginary part" ================================================ FILE: exercises/practice/complex-numbers/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/complex-numbers/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/complex-numbers/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/complex-numbers/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/complex-numbers/complex-numbers.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { ComplexNumber } from './complex-numbers.ts' describe('Complex numbers', () => { it('Real part of a purely real number', () => { const expected = 1 const actual = new ComplexNumber(1, 0).real expect(actual).toEqual(expected) }) xit('Real part of a purely imaginary number', () => { const expected = 0 const actual = new ComplexNumber(0, 1).real expect(actual).toEqual(expected) }) xit('Real part of a number with real and imaginary part', () => { const expected = 1 const actual = new ComplexNumber(1, 2).real expect(actual).toEqual(expected) }) xit('Imaginary part of a purely real number', () => { const expected = 0 const actual = new ComplexNumber(1, 0).imag expect(actual).toEqual(expected) }) xit('Imaginary part of a purely imaginary number', () => { const expected = 1 const actual = new ComplexNumber(0, 1).imag expect(actual).toEqual(expected) }) xit('Imaginary part of a number with real and imaginary part', () => { const expected = 2 const actual = new ComplexNumber(1, 2).imag expect(actual).toEqual(expected) }) xit('Add purely real numbers', () => { const expected = new ComplexNumber(3, 0) const actual = new ComplexNumber(1, 0).add(new ComplexNumber(2, 0)) expect(actual).toEqual(expected) }) xit('Add purely imaginary numbers', () => { const expected = new ComplexNumber(0, 3) const actual = new ComplexNumber(0, 1).add(new ComplexNumber(0, 2)) expect(actual).toEqual(expected) }) xit('Add numbers with real and imaginary part', () => { const expected = new ComplexNumber(4, 6) const actual = new ComplexNumber(1, 2).add(new ComplexNumber(3, 4)) expect(actual).toEqual(expected) }) xit('Subtract purely real numbers', () => { const expected = new ComplexNumber(-1, 0) const actual = new ComplexNumber(1, 0).sub(new ComplexNumber(2, 0)) expect(actual).toEqual(expected) }) xit('Subtract purely imaginary numbers', () => { const expected = new ComplexNumber(0, -1) const actual = new ComplexNumber(0, 1).sub(new ComplexNumber(0, 2)) expect(actual).toEqual(expected) }) xit('Subtract numbers with real and imaginary part', () => { const expected = new ComplexNumber(-2, -2) const actual = new ComplexNumber(1, 2).sub(new ComplexNumber(3, 4)) expect(actual).toEqual(expected) }) xit('Multiply purely real numbers', () => { const expected = new ComplexNumber(2, 0) const actual = new ComplexNumber(1, 0).mul(new ComplexNumber(2, 0)) expect(actual).toEqual(expected) }) xit('Multiply imaginary unit', () => { const expected = new ComplexNumber(-1, 0) const actual = new ComplexNumber(0, 1).mul(new ComplexNumber(0, 1)) expect(actual).toEqual(expected) }) xit('Multiply purely imaginary numbers', () => { const expected = new ComplexNumber(-2, 0) const actual = new ComplexNumber(0, 1).mul(new ComplexNumber(0, 2)) expect(actual).toEqual(expected) }) xit('Multiply numbers with real and imaginary part', () => { const expected = new ComplexNumber(-5, 10) const actual = new ComplexNumber(1, 2).mul(new ComplexNumber(3, 4)) expect(actual).toEqual(expected) }) xit('Divide purely real numbers', () => { const expected = new ComplexNumber(0.5, 0) const actual = new ComplexNumber(1, 0).div(new ComplexNumber(2, 0)) expect(actual).toEqual(expected) }) xit('Divide purely imaginary numbers', () => { const expected = new ComplexNumber(0.5, 0) const actual = new ComplexNumber(0, 1).div(new ComplexNumber(0, 2)) expect(actual).toEqual(expected) }) xit('Divide numbers with real and imaginary part', () => { const expected = new ComplexNumber(0.44, 0.08) const actual = new ComplexNumber(1, 2).div(new ComplexNumber(3, 4)) expect(actual).toEqual(expected) }) xit('Absolute value of a positive purely real number', () => { const expected = 5 const actual = new ComplexNumber(5, 0).abs expect(actual).toEqual(expected) }) xit('Absolute value of a negative purely real number', () => { const expected = 5 const actual = new ComplexNumber(-5, 0).abs expect(actual).toEqual(expected) }) xit('Absolute value of a purely imaginary number with positive imaginary part', () => { const expected = 5 const actual = new ComplexNumber(0, 5).abs expect(actual).toEqual(expected) }) xit('Absolute value of a purely imaginary number with negative imaginary part', () => { const expected = 5 const actual = new ComplexNumber(0, -5).abs expect(actual).toEqual(expected) }) xit('Absolute value of a number with real and imaginary part', () => { const expected = 5 const actual = new ComplexNumber(3, 4).abs expect(actual).toEqual(expected) }) xit('Conjugate a purely real number', () => { const expected = new ComplexNumber(5, 0) const actual = new ComplexNumber(5, 0).conj expect(actual).toEqual(expected) }) xit('Conjugate a purely imaginary number', () => { const expected = new ComplexNumber(0, -5) const actual = new ComplexNumber(0, 5).conj expect(actual).toEqual(expected) }) xit('Conjugate a number with real and imaginary part', () => { const expected = new ComplexNumber(1, -1) const actual = new ComplexNumber(1, 1).conj expect(actual).toEqual(expected) }) xit("Euler's identity/formula", () => { const expected = new ComplexNumber(-1, 0) const actual = new ComplexNumber(0, Math.PI).exp expect(actual.real).toBeCloseTo(expected.real) expect(actual.imag).toBeCloseTo(expected.imag) }) xit('Exponential of 0', () => { const expected = new ComplexNumber(1, 0) const actual = new ComplexNumber(0, 0).exp expect(actual.real).toBeCloseTo(expected.real) expect(actual.imag).toBeCloseTo(expected.imag) }) xit('Exponential of a purely real number', () => { const expected = new ComplexNumber(Math.E, 0) const actual = new ComplexNumber(1, 0).exp expect(actual.real).toBeCloseTo(expected.real) expect(actual.imag).toBeCloseTo(expected.imag) }) }) ================================================ FILE: exercises/practice/complex-numbers/complex-numbers.ts ================================================ export class ComplexNumber { constructor(real: unknown, imaginary: unknown) { throw new Error('Remove this line and implement the function') } public get real(): number { throw new Error('Remove this line and implement the function') } public get imag(): number { throw new Error('Remove this line and implement the function') } public add(other: unknown): unknown { throw new Error('Remove this line and implement the function') } public sub(other: unknown): unknown { throw new Error('Remove this line and implement the function') } public div(other: unknown): unknown { throw new Error('Remove this line and implement the function') } public mul(other: unknown): unknown { throw new Error('Remove this line and implement the function') } public get abs(): unknown { throw new Error('Remove this line and implement the function') } public get conj(): unknown { throw new Error('Remove this line and implement the function') } public get exp(): ComplexNumber { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/complex-numbers/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/complex-numbers/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/complex-numbers/package.json ================================================ { "name": "@exercism/typescript-complex-numbers", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/complex-numbers/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/complex-numbers/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/connect/.docs/instructions.md ================================================ # Instructions Compute the result for a game of Hex / Polygon. The abstract boardgame known as [Hex][hex] / Polygon / CON-TAC-TIX is quite simple in rules, though complex in practice. Two players place stones on a parallelogram with hexagonal fields. The player to connect his/her stones to the opposite side first wins. The four sides of the parallelogram are divided between the two players (i.e. one player gets assigned a side and the side directly opposite it and the other player gets assigned the two other sides). Your goal is to build a program that given a simple representation of a board computes the winner (or lack thereof). Note that all games need not be "fair". (For example, players may have mismatched piece counts or the game's board might have a different width and height.) The boards look like this: ```text . O . X . . X X O . O O O X . . X O X O X O O O X ``` "Player `O`" plays from top to bottom, "Player `X`" plays from left to right. In the above example `O` has made a connection from left to right but nobody has won since `O` didn't connect top and bottom. [hex]: https://en.wikipedia.org/wiki/Hex_%28board_game%29 ================================================ FILE: exercises/practice/connect/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "connect.ts" ], "test": [ "connect.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Compute the result for a game of Hex / Polygon.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false } } ================================================ FILE: exercises/practice/connect/.meta/proof.ci.ts ================================================ /** * "Player O" plays from top to bottom, "Player X" plays from left to right. * @param board */ export class Board { private board: string[][] constructor(board: string[]) { this.board = board.map((b) => [...b]) } public winner(): '' | 'X' | 'O' { const players = ['X', 'O'] as const for (const player of players) { if (this.checkWin(player)) { return player } } return '' } private checkWin(player: 'X' | 'O'): boolean { const positions = this.startPositions(player) for (const position of positions) { if (this.search(position, player, [])) { return true } } return false } private search( pos: { x: number; y: number }, XorO: 'X' | 'O', checked: Array<{ x: number; y: number }> ): boolean { if (!this.matches(pos, XorO)) { return false } if (this.winningSpot(pos, XorO)) { return true } checked = checked.slice(0) checked.push(pos) const matches = this.neighbors(pos).filter( ({ x, y }) => this.matches({ x, y }, XorO) && checked.filter((spot) => spot.x === x && spot.y === y).length === 0 ) if (matches.length === 0) { return false } return matches.filter((spot) => this.search(spot, XorO, checked)).length > 0 } private neighbors(pos: { x: number y: number }): Array<{ x: number; y: number }> { return [ { x: pos.x, y: pos.y + 2 }, { x: pos.x, y: pos.y - 2 }, { x: pos.x + 1, y: pos.y + 1 }, { x: pos.x - 1, y: pos.y + 1 }, { x: pos.x + 1, y: pos.y - 1 }, { x: pos.x - 1, y: pos.y - 1 }, ] } private startPositions(XorO: 'X' | 'O'): Array<{ x: number; y: number }> { return XorO === 'X' ? this.board.map((_, i) => ({ x: i, y: i })) : this.board[0].map((_, i) => ({ x: 0, y: i })) } private winningSpot(pos: { x: number; y: number }, XorO: 'X' | 'O'): boolean { return XorO === 'X' ? pos.y === this.board[0].length - 1 + pos.x : pos.x === this.board.length - 1 } private matches(pos: { x: number; y: number }, XorO: 'X' | 'O'): boolean { return this.board[pos.x] !== undefined && this.board[pos.x][pos.y] === XorO } } ================================================ FILE: exercises/practice/connect/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [6eff0df4-3e92-478d-9b54-d3e8b354db56] description = "an empty board has no winner" [298b94c0-b46d-45d8-b34b-0fa2ea71f0a4] description = "X can win on a 1x1 board" [763bbae0-cb8f-4f28-bc21-5be16a5722dc] description = "O can win on a 1x1 board" [819fde60-9ae2-485e-a024-cbb8ea68751b] description = "only edges does not make a winner" [2c56a0d5-9528-41e5-b92b-499dfe08506c] description = "illegal diagonal does not make a winner" [41cce3ef-43ca-4963-970a-c05d39aa1cc1] description = "nobody wins crossing adjacent angles" [cd61c143-92f6-4a8d-84d9-cb2b359e226b] description = "X wins crossing from left to right" [73d1eda6-16ab-4460-9904-b5f5dd401d0b] description = "O wins crossing from top to bottom" [c3a2a550-944a-4637-8b3f-1e1bf1340a3d] description = "X wins using a convoluted path" [17e76fa8-f731-4db7-92ad-ed2a285d31f3] description = "X wins using a spiral path" ================================================ FILE: exercises/practice/connect/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/connect/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/connect/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/connect/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/connect/connect.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { Board } from './connect.ts' describe('Judging a game of connect', () => { it('an empty board has no winner', () => { const board = [ '. . . . .', ' . . . . .', ' . . . . .', ' . . . . .', ' . . . . .', ] expect(new Board(board).winner()).toEqual('') }) xit('X can win on a 1x1 board', () => { const board = ['X'] expect(new Board(board).winner()).toEqual('X') }) xit('O can win on a 1x1 board', () => { const board = ['O'] expect(new Board(board).winner()).toEqual('O') }) xit('only edges does not make a winner', () => { const board = ['O O O X', ' X . . X', ' X . . X', ' X O O O'] expect(new Board(board).winner()).toEqual('') }) xit('illegal diagonal does not make a winner', () => { const board = [ 'X O . .', ' O X X X', ' O X O .', ' . O X .', ' X X O O', ] expect(new Board(board).winner()).toEqual('') }) xit('nobody wins crossing adjacent angles', () => { const board = [ 'X . . .', ' . X O .', ' O . X O', ' . O . X', ' . . O .', ] expect(new Board(board).winner()).toEqual('') }) xit('X wins crossing from left to right', () => { const board = [ '. O . .', ' O X X X', ' O X O .', ' X X O X', ' . O X .', ] expect(new Board(board).winner()).toEqual('X') }) xit('O wins crossing from top to bottom', () => { const board = [ '. O . .', ' O X X X', ' O O O .', ' X X O X', ' . O X .', ] expect(new Board(board).winner()).toEqual('O') }) xit('X wins using a convoluted path', () => { const board = [ '. X X . .', ' X . X . X', ' . X . X .', ' . X X . .', ' O O O O O', ] expect(new Board(board).winner()).toEqual('X') }) xit('X wins using a spiral path', () => { const board = [ 'O X X X X X X X X', ' O X O O O O O O O', ' O X O X X X X X O', ' O X O X O O O X O', ' O X O X X X O X O', ' O X O O O X O X O', ' O X X X X X O X O', ' O O O O O O O X O', ' X X X X X X X X O', ] expect(new Board(board).winner()).toEqual('X') }) }) ================================================ FILE: exercises/practice/connect/connect.ts ================================================ export class Board { constructor(board: unknown) { throw new Error('Remove this line and implement the function') } public winner(): unknown { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/connect/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/connect/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/connect/package.json ================================================ { "name": "@exercism/typescript-connect", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/connect/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/connect/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/crypto-square/.docs/instructions.md ================================================ # Instructions Implement the classic method for composing secret messages called a square code. Given an English text, output the encoded version of that text. First, the input is normalized: the spaces and punctuation are removed from the English text and the message is down-cased. Then, the normalized characters are broken into rows. These rows can be regarded as forming a rectangle when printed with intervening newlines. For example, the sentence ```text "If man was meant to stay on the ground, god would have given us roots." ``` is normalized to: ```text "ifmanwasmeanttostayonthegroundgodwouldhavegivenusroots" ``` The plaintext should be organized into a rectangle as square as possible. The size of the rectangle should be decided by the length of the message. If `c` is the number of columns and `r` is the number of rows, then for the rectangle `r` x `c` find the smallest possible integer `c` such that: - `r * c >= length of message`, - and `c >= r`, - and `c - r <= 1`. Our normalized text is 54 characters long, dictating a rectangle with `c = 8` and `r = 7`: ```text "ifmanwas" "meanttos" "tayonthe" "groundgo" "dwouldha" "vegivenu" "sroots " ``` The coded message is obtained by reading down the columns going left to right. The message above is coded as: ```text "imtgdvsfearwermayoogoanouuiontnnlvtwttddesaohghnsseoau" ``` Output the encoded text in chunks that fill perfect rectangles `(r X c)`, with `c` chunks of `r` length, separated by spaces. For phrases that are `n` characters short of the perfect rectangle, pad each of the last `n` chunks with a single trailing space. ```text "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau " ``` Notice that were we to stack these, we could visually decode the ciphertext back in to the original message: ```text "imtgdvs" "fearwer" "mayoogo" "anouuio" "ntnnlvt" "wttddes" "aohghn " "sseoau " ``` ================================================ FILE: exercises/practice/crypto-square/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "crypto-square.ts" ], "test": [ "crypto-square.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Implement the classic method for composing secret messages called a square code.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": true, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "J Dalbey's Programming Practice problems", "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" } ================================================ FILE: exercises/practice/crypto-square/.meta/proof.ci.ts ================================================ export class Crypto { constructor(private readonly input: string) {} private get plaintext(): string { return this.input.toLowerCase().replace(/[^a-zA-Z0-9]/g, '') } public get ciphertext(): string { const chunkSize = this.size if (chunkSize === 0) { return '' } const splitRegex = new RegExp(`.{1,${chunkSize}}`, 'g') return this.ciphertextSegments() .join('') .match(splitRegex) .map((item) => item.padEnd(chunkSize, ' ')) .join(' ') } public get size(): number { const realLength = Math.sqrt(this.plaintext.length) return Math.ceil(realLength) } private ciphertextSegments(): string[] { const textSegments = this.plaintextSegments() const columns: string[][] = [] let i: number let j: number let currentSegment: RegExpMatchArray[number] let currentLetter: RegExpMatchArray[number][number] for (i = 0; i < this.size; i += 1) { columns.push([]) } for (i = 0; i < textSegments.length; i += 1) { currentSegment = textSegments[i] for (j = 0; j < currentSegment.length; j += 1) { currentLetter = currentSegment[j] columns[j].push(currentLetter) } } const result: string[] = [] for (i = 0; i < columns.length; i += 1) { result[i] = columns[i].join('') } return result } private plaintextSegments(): RegExpMatchArray { const plainText = this.plaintext const chunkSize = this.size const splitRegex = new RegExp(`.{1,${chunkSize}}`, 'g') return plainText.match(splitRegex) } } ================================================ FILE: exercises/practice/crypto-square/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [407c3837-9aa7-4111-ab63-ec54b58e8e9f] description = "empty plaintext results in an empty ciphertext" [aad04a25-b8bb-4304-888b-581bea8e0040] description = "normalization results in empty plaintext" [64131d65-6fd9-4f58-bdd8-4a2370fb481d] description = "Lowercase" [63a4b0ed-1e3c-41ea-a999-f6f26ba447d6] description = "Remove spaces" [1b5348a1-7893-44c1-8197-42d48d18756c] description = "Remove punctuation" [8574a1d3-4a08-4cec-a7c7-de93a164f41a] description = "9 character plaintext results in 3 chunks of 3 characters" [a65d3fa1-9e09-43f9-bcec-7a672aec3eae] description = "8 character plaintext results in 3 chunks, the last one with a trailing space" [fbcb0c6d-4c39-4a31-83f6-c473baa6af80] description = "54 character plaintext results in 7 chunks, the last two with trailing spaces" include = false [33fd914e-fa44-445b-8f38-ff8fbc9fe6e6] description = "54 character plaintext results in 8 chunks, the last two with trailing spaces" reimplements = "fbcb0c6d-4c39-4a31-83f6-c473baa6af80" ================================================ FILE: exercises/practice/crypto-square/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/crypto-square/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/crypto-square/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/crypto-square/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/crypto-square/crypto-square.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { Crypto } from './crypto-square.ts' describe('Crypto', () => { it('empty plaintext results in an empty ciphertext', () => { const crypto = new Crypto('') expect(crypto.ciphertext).toEqual('') }) xit('normalization results in empty plaintext', () => { const crypto = new Crypto('... --- ...') expect(crypto.ciphertext).toEqual('') }) xit('Lowercase', () => { const crypto = new Crypto('A') expect(crypto.ciphertext).toEqual('a') }) xit('Remove spaces', () => { const crypto = new Crypto(' b ') expect(crypto.ciphertext).toEqual('b') }) xit('Remove punctuation', () => { const crypto = new Crypto('@1,%!') expect(crypto.ciphertext).toEqual('1') }) xit('9 character plaintext results in 3 chunks of 3 characters', () => { const crypto = new Crypto('This is fun!') expect(crypto.ciphertext).toEqual('tsf hiu isn') }) xit('8 character plaintext results in 3 chunks, the last one with a trailing space', () => { const crypto = new Crypto('Chill out.') expect(crypto.ciphertext).toEqual('clu hlt io ') }) it.skip('54 character plaintext results in 8 chunks, the last two with trailing spaces', () => { const crypto = new Crypto( 'If man was meant to stay on the ground, god would have given us roots.' ) expect(crypto.ciphertext).toEqual( 'imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau ' ) }) }) ================================================ FILE: exercises/practice/crypto-square/crypto-square.ts ================================================ export class Crypto { constructor(plainText: unknown) { throw new Error('Remove this line and implement the function') } get ciphertext(): unknown { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/crypto-square/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/crypto-square/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/crypto-square/package.json ================================================ { "name": "@exercism/typescript-crypto-square", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/crypto-square/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/crypto-square/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/custom-set/.docs/instructions.md ================================================ # Instructions Create a custom set type. Sometimes it is necessary to define a custom data structure of some type, like a set. In this exercise you will define your own set. How it works internally doesn't matter, as long as it behaves like a set of unique elements. ================================================ FILE: exercises/practice/custom-set/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "custom-set.ts" ], "test": [ "custom-set.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Create a custom set type.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false } } ================================================ FILE: exercises/practice/custom-set/.meta/proof.ci.ts ================================================ export class CustomSet { private data: Set constructor(data: T[] = []) { this.data = new Set() data.forEach((el) => { this.add(el) }) } public add(el: T): this { this.data.add(el) return this } public delete(el: T): this { this.data.delete(el) return this } public size(): number { return this.data.size } public empty(): boolean { return this.data.size === 0 } public contains(el: T): boolean { return this.data.has(el) } public eql(other: CustomSet): boolean { if (this.data.size !== other.data.size) { return false } for (const item of this.data) { if (!other.data.has(item)) { return false } } return true } public difference(other: CustomSet): CustomSet { const result = new CustomSet() for (const item of this.data) { if (!other.data.has(item)) { result.add(item) } } return result } public disjoint(other: CustomSet): boolean { return this.size() === 0 || this.difference(other).size() === this.size() } public intersection(other: CustomSet): CustomSet { return this.difference(this.difference(other)) } public union(other: CustomSet): CustomSet { const result = new CustomSet() for (const item of this.data) { result.add(item) } for (const item of other.data) { result.add(item) } return result } public subset(other: CustomSet): boolean { return this.eql(this.intersection(other)) } public toList(): T[] { return Object.values(this.data) as T[] } } ================================================ FILE: exercises/practice/custom-set/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [20c5f855-f83a-44a7-abdd-fe75c6cf022b] description = "sets with no elements are empty" [d506485d-5706-40db-b7d8-5ceb5acf88d2] description = "sets with elements are not empty" [759b9740-3417-44c3-8ca3-262b3c281043] description = "nothing is contained in an empty set" [f83cd2d1-2a85-41bc-b6be-80adbff4be49] description = "when the element is in the set" [93423fc0-44d0-4bc0-a2ac-376de8d7af34] description = "when the element is not in the set" [c392923a-637b-4495-b28e-34742cd6157a] description = "empty set is a subset of another empty set" [5635b113-be8c-4c6f-b9a9-23c485193917] description = "empty set is a subset of non-empty set" [832eda58-6d6e-44e2-92c2-be8cf0173cee] description = "non-empty set is not a subset of empty set" [c830c578-8f97-4036-b082-89feda876131] description = "set is a subset of set with exact same elements" [476a4a1c-0fd1-430f-aa65-5b70cbc810c5] description = "set is a subset of larger set with same elements" [d2498999-3e46-48e4-9660-1e20c3329d3d] description = "set is not a subset of set that does not contain its elements" [7d38155e-f472-4a7e-9ad8-5c1f8f95e4cc] description = "the empty set is disjoint with itself" [7a2b3938-64b6-4b32-901a-fe16891998a6] description = "empty set is disjoint with non-empty set" [589574a0-8b48-48ea-88b0-b652c5fe476f] description = "non-empty set is disjoint with empty set" [febeaf4f-f180-4499-91fa-59165955a523] description = "sets are not disjoint if they share an element" [0de20d2f-c952-468a-88c8-5e056740f020] description = "sets are disjoint if they share no elements" [4bd24adb-45da-4320-9ff6-38c044e9dff8] description = "empty sets are equal" [f65c0a0e-6632-4b2d-b82c-b7c6da2ec224] description = "empty set is not equal to non-empty set" [81e53307-7683-4b1e-a30c-7e49155fe3ca] description = "non-empty set is not equal to empty set" [d57c5d7c-a7f3-48cc-a162-6b488c0fbbd0] description = "sets with the same elements are equal" [dd61bafc-6653-42cc-961a-ab071ee0ee85] description = "sets with different elements are not equal" [06059caf-9bf4-425e-aaff-88966cb3ea14] description = "set is not equal to larger set with same elements" [8a677c3c-a658-4d39-bb88-5b5b1a9659f4] description = "add to empty set" [0903dd45-904d-4cf2-bddd-0905e1a8d125] description = "add to non-empty set" [b0eb7bb7-5e5d-4733-b582-af771476cb99] description = "adding an existing element does not change the set" [893d5333-33b8-4151-a3d4-8f273358208a] description = "intersection of two empty sets is an empty set" [d739940e-def2-41ab-a7bb-aaf60f7d782c] description = "intersection of an empty set and non-empty set is an empty set" [3607d9d8-c895-4d6f-ac16-a14956e0a4b7] description = "intersection of a non-empty set and an empty set is an empty set" [b5120abf-5b5e-41ab-aede-4de2ad85c34e] description = "intersection of two sets with no shared elements is an empty set" [af21ca1b-fac9-499c-81c0-92a591653d49] description = "intersection of two sets with shared elements is a set of the shared elements" [c5e6e2e4-50e9-4bc2-b89f-c518f015b57e] description = "difference of two empty sets is an empty set" [2024cc92-5c26-44ed-aafd-e6ca27d6fcd2] description = "difference of empty set and non-empty set is an empty set" [e79edee7-08aa-4c19-9382-f6820974b43e] description = "difference of a non-empty set and an empty set is the non-empty set" [c5ac673e-d707-4db5-8d69-7082c3a5437e] description = "difference of two non-empty sets is a set of elements that are only in the first set" [c45aed16-5494-455a-9033-5d4c93589dc6] description = "union of empty sets is an empty set" [9d258545-33c2-4fcb-a340-9f8aa69e7a41] description = "union of an empty set and non-empty set is the non-empty set" [3aade50c-80c7-4db8-853d-75bac5818b83] description = "union of a non-empty set and empty set is the non-empty set" [a00bb91f-c4b4-4844-8f77-c73e2e9df77c] description = "union of non-empty sets contains all unique elements" ================================================ FILE: exercises/practice/custom-set/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/custom-set/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/custom-set/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/custom-set/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/custom-set/custom-set.test.ts ================================================ import { describe, xdescribe, it, expect, xit } from '@jest/globals' import { CustomSet } from './custom-set.ts' describe('CustomSet', () => { describe('empty: returns true if the set contains no elements', () => { it('sets with no elements are empty', () => { const actual = new CustomSet().empty() expect(actual).toBeTruthy() }) xit('sets with elements are not empty', () => { const actual = new CustomSet([1]).empty() expect(actual).toBeFalsy() }) }) xdescribe('contains: sets can report if they contain an element', () => { xit('nothing is contained in an empty set', () => { const actual = new CustomSet().contains(1) expect(actual).toBeFalsy() }) xit('when the element is in the set', () => { const actual = new CustomSet([1, 2, 3]).contains(1) expect(actual).toBeTruthy() }) xit('when the element is not in the set', () => { const actual = new CustomSet([1, 2, 3]).contains(4) expect(actual).toBeFalsy() }) }) xdescribe('subset: a set is a subset if all of its elements are contained in the other set', () => { xit('empty set is a subset of another empty set', () => { const actual = new CustomSet().subset(new CustomSet()) expect(actual).toBeTruthy() }) xit('empty set is a subset of non-empty set', () => { const actual = new CustomSet().subset(new CustomSet([1])) expect(actual).toBeTruthy() }) xit('non-empty set is not a subset of empty set', () => { const actual = new CustomSet([1]).subset(new CustomSet()) expect(actual).toBeFalsy() }) xit('set is a subset of set with exact same elements', () => { const actual = new CustomSet([1, 2, 3]).subset(new CustomSet([1, 2, 3])) expect(actual).toBeTruthy() }) xit('set is a subset of larger set with same elements', () => { const actual = new CustomSet([1, 2, 3]).subset( new CustomSet([4, 1, 2, 3]) ) expect(actual).toBeTruthy() }) xit('set is not a subset of set that does not contain its elements', () => { const actual = new CustomSet([1, 2, 3]).subset(new CustomSet([4, 1, 3])) expect(actual).toBeFalsy() }) }) xdescribe('disjoint: sets are disjoint if they share no elements', () => { xit('the empty set is disjoint with itself', () => { const actual = new CustomSet().disjoint(new CustomSet([])) expect(actual).toBeTruthy() }) xit('empty set is disjoint with non-empty set', () => { const actual = new CustomSet().disjoint(new CustomSet([1])) expect(actual).toBeTruthy() }) xit('non-empty set is disjoint with empty set', () => { const actual = new CustomSet([1]).disjoint(new CustomSet([])) expect(actual).toBeTruthy() }) xit('sets are not disjoint if they share an element', () => { const actual = new CustomSet([1, 2]).disjoint(new CustomSet([2, 3])) expect(actual).toBeFalsy() }) xit('sets are disjoint if they share no elements', () => { const actual = new CustomSet([1, 2]).disjoint(new CustomSet([3, 4])) expect(actual).toBeTruthy() }) }) xdescribe('eql: sets with the same elements are equal', () => { xit('empty sets are equal', () => { const actual = new CustomSet().eql(new CustomSet()) expect(actual).toBeTruthy() }) xit('empty set is not equal to non-empty set', () => { const actual = new CustomSet().eql(new CustomSet([1, 2, 3])) expect(actual).toBeFalsy() }) xit('non-empty set is not equal to empty set', () => { const actual = new CustomSet([1, 2, 3]).eql(new CustomSet()) expect(actual).toBeFalsy() }) xit('sets with the same elements are equal', () => { const actual = new CustomSet([1, 2]).eql(new CustomSet([2, 1])) expect(actual).toBeTruthy() }) xit('sets with different elements are not equal', () => { const actual = new CustomSet([1, 2, 3]).eql(new CustomSet([1, 2, 4])) expect(actual).toBeFalsy() }) }) xdescribe('add: unique elements can be added to a set', () => { xit('add to empty set', () => { const actual = new CustomSet().add(3) const expected = new CustomSet([3]) expect(actual.eql(expected)).toBeTruthy() }) xit('add to non-empty set', () => { const actual = new CustomSet([1, 2, 4]).add(3) const expected = new CustomSet([1, 2, 3, 4]) expect(actual.eql(expected)).toBeTruthy() }) xit('adding an existing element does not change the set', () => { const actual = new CustomSet([1, 2, 3]).add(3) const expected = new CustomSet([1, 2, 3]) expect(actual.eql(expected)).toBeTruthy() }) }) xdescribe('intersection: returns a set of all shared elements', () => { xit('intersection of two empty sets is an empty set', () => { const actual = new CustomSet().intersection(new CustomSet()) const expected = new CustomSet() expect(actual.eql(expected)).toBeTruthy() }) xit('intersection of an empty set and non-empty set is an empty set', () => { const actual = new CustomSet().intersection(new CustomSet([3, 2, 5])) const expected = new CustomSet([]) expect(actual.eql(expected)).toBeTruthy() }) xit('intersection of a non-empty set and an empty set is an empty set', () => { const actual = new CustomSet([1, 2, 3, 4]).intersection(new CustomSet([])) const expected = new CustomSet([]) expect(actual.eql(expected)).toBeTruthy() }) xit('intersection of two sets with no shared elements is an empty set', () => { const actual = new CustomSet([1, 2, 3]).intersection( new CustomSet([4, 5, 6]) ) const expected = new CustomSet([]) expect(actual.eql(expected)).toBeTruthy() }) xit('intersection of two sets with shared elements is a set of the shared elements', () => { const actual = new CustomSet([1, 2, 3, 4]).intersection( new CustomSet([3, 2, 5]) ) const expected = new CustomSet([2, 3]) expect(actual.eql(expected)).toBeTruthy() }) }) xdescribe('difference of a set is a set of all elements that are only in the first set', () => { xit('difference of two empty sets is an empty set', () => { const actual = new CustomSet().difference(new CustomSet()) const expected = new CustomSet() expect(actual.eql(expected)).toBeTruthy() }) xit('difference of empty set and non-empty set is an empty set', () => { const actual = new CustomSet().difference(new CustomSet([3, 2, 5])) const expected = new CustomSet() expect(actual.eql(expected)).toBeTruthy() }) xit('difference of a non-empty set and an empty set is the non-empty set', () => { const actual = new CustomSet([1, 2, 3, 4]).difference(new CustomSet()) const expected = new CustomSet([1, 2, 3, 4]) expect(actual.eql(expected)).toBeTruthy() }) xit('difference of two non-empty sets is a set of elements that are only in the first set', () => { const actual = new CustomSet([3, 2, 1]).difference(new CustomSet([2, 4])) const expected = new CustomSet([1, 3]) expect(actual.eql(expected)).toBeTruthy() }) }) xdescribe('union: returns a set of all elements in either set', () => { xit('union of empty sets is an empty set', () => { const actual = new CustomSet().union(new CustomSet()) const expected = new CustomSet() expect(actual.eql(expected)).toBeTruthy() }) xit('union of an empty set and non-empty set is the non-empty set', () => { const actual = new CustomSet().union(new CustomSet([2])) const expected = new CustomSet([2]) expect(actual.eql(expected)).toBeTruthy() }) xit('union of a non-empty set and empty set is the non-empty set', () => { const actual = new CustomSet([1, 3]).union(new CustomSet()) const expected = new CustomSet([1, 3]) expect(actual.eql(expected)).toBeTruthy() }) xit('union of non-empty sets contains all unique elements', () => { const actual = new CustomSet([1, 3]).union(new CustomSet([2, 3])) const expected = new CustomSet([1, 2, 3]) expect(actual.eql(expected)).toBeTruthy() }) }) }) ================================================ FILE: exercises/practice/custom-set/custom-set.ts ================================================ export class CustomSet { constructor(initial?: unknown) { throw new Error('Remove this line and implement the function') } empty(): unknown { throw new Error('Remove this line and implement the function') } contains(element: unknown): unknown { throw new Error('Remove this line and implement the function') } add(element: unknown): CustomSet { throw new Error('Remove this line and implement the function') } subset(other: unknown): CustomSet { throw new Error('Remove this line and implement the function') } disjoint(other: unknown): CustomSet { throw new Error('Remove this line and implement the function') } eql(other: unknown): unknown { throw new Error('Remove this line and implement the function') } union(other: unknown): CustomSet { throw new Error('Remove this line and implement the function') } intersection(other: unknown): CustomSet { throw new Error('Remove this line and implement the function') } difference(other: unknown): CustomSet { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/custom-set/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/custom-set/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/custom-set/package.json ================================================ { "name": "@exercism/typescript-custom-set", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/custom-set/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/custom-set/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/darts/.docs/instructions.md ================================================ # Instructions Calculate the points scored in a single toss of a Darts game. [Darts][darts] is a game where players throw darts at a [target][darts-target]. In our particular instance of the game, the target rewards 4 different amounts of points, depending on where the dart lands: ![Our dart scoreboard with values from a complete miss to a bullseye](https://assets.exercism.org/images/exercises/darts/darts-scoreboard.svg) - If the dart lands outside the target, player earns no points (0 points). - If the dart lands in the outer circle of the target, player earns 1 point. - If the dart lands in the middle circle of the target, player earns 5 points. - If the dart lands in the inner circle of the target, player earns 10 points. The outer circle has a radius of 10 units (this is equivalent to the total radius for the entire target), the middle circle a radius of 5 units, and the inner circle a radius of 1. Of course, they are all centered at the same point — that is, the circles are [concentric][] defined by the coordinates (0, 0). Given a point in the target (defined by its [Cartesian coordinates][cartesian-coordinates] `x` and `y`, where `x` and `y` are [real][real-numbers]), calculate the correct score earned by a dart landing at that point. ## Credit The scoreboard image was created by [habere-et-dispertire][habere-et-dispertire] using [Inkscape][inkscape]. [darts]: https://en.wikipedia.org/wiki/Darts [darts-target]: https://en.wikipedia.org/wiki/Darts#/media/File:Darts_in_a_dartboard.jpg [concentric]: https://mathworld.wolfram.com/ConcentricCircles.html [cartesian-coordinates]: https://www.mathsisfun.com/data/cartesian-coordinates.html [real-numbers]: https://www.mathsisfun.com/numbers/real-numbers.html [habere-et-dispertire]: https://exercism.org/profiles/habere-et-dispertire [inkscape]: https://en.wikipedia.org/wiki/Inkscape ================================================ FILE: exercises/practice/darts/.meta/config.json ================================================ { "authors": [ "angelikatyborska" ], "contributors": [ "SleeplessByte" ], "files": { "solution": [ "darts.ts" ], "test": [ "darts.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Calculate the points scored in a single toss of a Darts game.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Inspired by an exercise created by a professor Della Paolera in Argentina" } ================================================ FILE: exercises/practice/darts/.meta/proof.ci.ts ================================================ export function score(x: number, y: number): number { // Use euclidean distance const distanceToDart = Math.sqrt(x * x + y * y) // Define points for each section of the target if (distanceToDart > 10) { return 0 } if (distanceToDart > 5) { return 1 } if (distanceToDart > 1) { return 5 } return 10 } ================================================ FILE: exercises/practice/darts/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [9033f731-0a3a-4d9c-b1c0-34a1c8362afb] description = "Missed target" [4c9f6ff4-c489-45fd-be8a-1fcb08b4d0ba] description = "On the outer circle" [14378687-ee58-4c9b-a323-b089d5274be8] description = "On the middle circle" [849e2e63-85bd-4fed-bc3b-781ae962e2c9] description = "On the inner circle" [1c5ffd9f-ea66-462f-9f06-a1303de5a226] description = "Exactly on center" [b65abce3-a679-4550-8115-4b74bda06088] description = "Near the center" [66c29c1d-44f5-40cf-9927-e09a1305b399] description = "Just within the inner circle" [d1012f63-c97c-4394-b944-7beb3d0b141a] description = "Just outside the inner circle" [ab2b5666-b0b4-49c3-9b27-205e790ed945] description = "Just within the middle circle" [70f1424e-d690-4860-8caf-9740a52c0161] description = "Just outside the middle circle" [a7dbf8db-419c-4712-8a7f-67602b69b293] description = "Just within the outer circle" [e0f39315-9f9a-4546-96e4-a9475b885aa7] description = "Just outside the outer circle" [045d7d18-d863-4229-818e-b50828c75d19] description = "Asymmetric position between the inner and middle circles" ================================================ FILE: exercises/practice/darts/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/darts/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/darts/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/darts/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/darts/darts.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { score } from './darts.ts' describe('Darts', () => { it('Missed target', () => { expect(score(-9, 9)).toEqual(0) }) xit('On the outer circle', () => { expect(score(0, 10)).toEqual(1) }) xit('On the middle circle', () => { expect(score(-5, 0)).toEqual(5) }) xit('On the inner circle', () => { expect(score(0, -1)).toEqual(10) }) xit('Exactly on centre', () => { expect(score(0, 0)).toEqual(10) }) xit('Near the centre', () => { expect(score(-0.1, -0.1)).toEqual(10) }) xit('Just within the inner circle', () => { expect(score(0.7, 0.7)).toEqual(10) }) xit('Just outside the inner circle', () => { expect(score(0.8, -0.8)).toEqual(5) }) xit('Just within the middle circle', () => { expect(score(-3.5, 3.5)).toEqual(5) }) xit('Just outside the middle circle', () => { expect(score(-3.6, -3.6)).toEqual(1) }) xit('Just within the outer circle', () => { expect(score(-7.0, 7.0)).toEqual(1) }) xit('Just outside the outer circle', () => { expect(score(7.1, -7.1)).toEqual(0) }) xit('Asymmetric position between the inner and middle circles', () => { expect(score(0.5, -4)).toEqual(5) }) }) ================================================ FILE: exercises/practice/darts/darts.ts ================================================ export function score(x: unknown, y: unknown): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/darts/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/darts/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/darts/package.json ================================================ { "name": "@exercism/typescript-darts", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/darts/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/darts/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/diamond/.docs/instructions.md ================================================ # Instructions The diamond kata takes as its input a letter, and outputs it in a diamond shape. Given a letter, it prints a diamond starting with 'A', with the supplied letter at the widest point. ## Requirements - The first row contains one 'A'. - The last row contains one 'A'. - All rows, except the first and last, have exactly two identical letters. - All rows have as many trailing spaces as leading spaces. (This might be 0). - The diamond is horizontally symmetric. - The diamond is vertically symmetric. - The diamond has a square shape (width equals height). - The letters form a diamond shape. - The top half has the letters in ascending order. - The bottom half has the letters in descending order. - The four corners (containing the spaces) are triangles. ## Examples In the following examples, spaces are indicated by `·` characters. Diamond for letter 'A': ```text A ``` Diamond for letter 'C': ```text ··A·· ·B·B· C···C ·B·B· ··A·· ``` Diamond for letter 'E': ```text ····A···· ···B·B··· ··C···C·· ·D·····D· E·······E ·D·····D· ··C···C·· ···B·B··· ····A···· ``` ================================================ FILE: exercises/practice/diamond/.meta/config.json ================================================ { "authors": [ "njgingrich" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "diamond.ts" ], "test": [ "diamond.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a letter, print a diamond starting with 'A' with the supplied letter at the widest point.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Seb Rose", "source_url": "https://web.archive.org/web/20220807163751/http://claysnow.co.uk/recycling-tests-in-tdd/" } ================================================ FILE: exercises/practice/diamond/.meta/proof.ci.ts ================================================ export function makeDiamond(input: string): string { // A = 0, Z = 25 const inputIx = input.charCodeAt(0) - 65 const lines = [] for (let i = 0; i <= inputIx; i++) { lines.push(getLine(inputIx, i)) } for (let i = inputIx - 1; i >= 0; i--) { lines.push(lines[i]) } return lines.join('\n') + '\n' } function getAlphaString(index: number): string { if (index === 0) { return `A` } const char = String.fromCharCode(index + 65) const padding = ' '.repeat((index - 1) * 2 + 1) return `${char}${padding}${char}` } function getLine(inputIx: number, index: number): string { const difference = inputIx - index const alphaStr = getAlphaString(index) return `${padString(alphaStr, difference)}` } function padString(str: string, times: number): string { const spaces = ' '.repeat(times) return `${spaces}${str}${spaces}` } /* ex: D -> loop: A: D=3, A=0 -> 3 spaces, A, 3 spaces (special case) B: D=3, B=1 -> 2 spaces, B, 1 space, B, 2 spaces C: D=3, C=2 -> 1 space, C, 3 spaces, C, 1 space D: D=3, D=3 -> 0 spaces, D, 5 spaces, D, 0 spaces A B B C C D D C C B B A */ ================================================ FILE: exercises/practice/diamond/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [202fb4cc-6a38-4883-9193-a29d5cb92076] description = "Degenerate case with a single 'A' row" [bd6a6d78-9302-42e9-8f60-ac1461e9abae] description = "Degenerate case with no row containing 3 distinct groups of spaces" [af8efb49-14ed-447f-8944-4cc59ce3fd76] description = "Smallest non-degenerate case with odd diamond side length" [e0c19a95-9888-4d05-86a0-fa81b9e70d1d] description = "Smallest non-degenerate case with even diamond side length" [82ea9aa9-4c0e-442a-b07e-40204e925944] description = "Largest possible diamond" ================================================ FILE: exercises/practice/diamond/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/diamond/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/diamond/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/diamond/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/diamond/diamond.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { makeDiamond } from './diamond.ts' function diamondify(parts: TemplateStringsArray): string { // prettier-ignore return ( parts[0] .trim() // Remove leading and trailing whitespace .split('\n') // Consider each row .map((line) => { return line .trim() // Remove whitespace at start (and end) .replace(/·/g, ' ') // Use spaces instead of · }) .filter(Boolean) // Remove empty rows (if any) .join('\n') + // Turn back into a single string '\n' // Should have a final newline ) } describe('Make diamond function', () => { it('test letter A', () => { const result = 'A\n' expect(makeDiamond('A')).toEqual(result) }) xit('test letter C', () => { const result = diamondify` ··A·· ·B·B· C···C ·B·B· ··A·· ` expect(makeDiamond('C')).toEqual(result) }) xit('test letter E', () => { const result = diamondify` ····A···· ···B·B··· ··C···C·· ·D·····D· E·······E ·D·····D· ··C···C·· ···B·B··· ····A···· ` expect(makeDiamond('E')).toEqual(result) }) }) ================================================ FILE: exercises/practice/diamond/diamond.ts ================================================ export function makeDiamond(character: unknown): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/diamond/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/diamond/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/diamond/package.json ================================================ { "name": "@exercism/typescript-diamond", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/diamond/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/diamond/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/difference-of-squares/.docs/instructions.md ================================================ # Instructions Find the difference between the square of the sum and the sum of the squares of the first N natural numbers. The square of the sum of the first ten natural numbers is (1 + 2 + ... + 10)² = 55² = 3025. The sum of the squares of the first ten natural numbers is 1² + 2² + ... + 10² = 385. Hence the difference between the square of the sum of the first ten natural numbers and the sum of the squares of the first ten natural numbers is 3025 - 385 = 2640. You are not expected to discover an efficient solution to this yourself from first principles; research is allowed, indeed, encouraged. Finding the best algorithm for the problem is a key skill in software engineering. ================================================ FILE: exercises/practice/difference-of-squares/.meta/config.json ================================================ { "authors": [], "contributors": [ "masters3d", "Scientifica96", "SleeplessByte" ], "files": { "solution": [ "difference-of-squares.ts" ], "test": [ "difference-of-squares.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Find the difference between the square of the sum and the sum of the squares of the first N natural numbers.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Problem 6 at Project Euler", "source_url": "https://projecteuler.net/problem=6" } ================================================ FILE: exercises/practice/difference-of-squares/.meta/proof.ci.ts ================================================ export class Squares { public readonly squareOfSum: number public readonly sumOfSquares: number public readonly difference: number constructor(int: number) { this.squareOfSum = this._squareOfSum(int) this.sumOfSquares = this._sumOfSquares(int) this.difference = this._difference() } private _squareOfSum(int: number): number { let sum = 0 let i = 1 while (i <= int) { sum += i i++ } return sum * sum } private _sumOfSquares(int: number): number { let sum = 0 let i = 1 while (i <= int) { sum += i * i i++ } return sum } private _difference(): number { return this.squareOfSum - this.sumOfSquares } } ================================================ FILE: exercises/practice/difference-of-squares/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [e46c542b-31fc-4506-bcae-6b62b3268537] description = "square of sum 1" [9b3f96cb-638d-41ee-99b7-b4f9c0622948] description = "square of sum 5" [54ba043f-3c35-4d43-86ff-3a41625d5e86] description = "square of sum 100" [01d84507-b03e-4238-9395-dd61d03074b5] description = "sum of squares 1" [c93900cd-8cc2-4ca4-917b-dd3027023499] description = "sum of squares 5" [94807386-73e4-4d9e-8dec-69eb135b19e4] description = "sum of squares 100" [44f72ae6-31a7-437f-858d-2c0837adabb6] description = "difference of squares 1" [005cb2bf-a0c8-46f3-ae25-924029f8b00b] description = "difference of squares 5" [b1bf19de-9a16-41c0-a62b-1f02ecc0b036] description = "difference of squares 100" ================================================ FILE: exercises/practice/difference-of-squares/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/difference-of-squares/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/difference-of-squares/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/difference-of-squares/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/difference-of-squares/difference-of-squares.test.ts ================================================ import { describe, xdescribe, it, expect, xit } from '@jest/globals' import { Squares } from './difference-of-squares.ts' describe('Squares', () => { describe('up to 5', () => { const squares = new Squares(5) it('gets the square of sum', () => { expect(squares.squareOfSum).toBe(225) }) xit('gets the sum of squares', () => { expect(squares.sumOfSquares).toBe(55) }) xit('gets the difference', () => { expect(squares.difference).toBe(170) }) }) xdescribe('up to 10', () => { const squares = new Squares(10) xit('gets the square of sum', () => { expect(squares.squareOfSum).toBe(3025) }) xit('gets the sum of squares', () => { expect(squares.sumOfSquares).toBe(385) }) xit('gets the difference', () => { expect(squares.difference).toBe(2640) }) }) xdescribe('up to 100', () => { const squares = new Squares(100) xit('gets the square of sum', () => { expect(squares.squareOfSum).toBe(25502500) }) xit('gets the sum of squares', () => { expect(squares.sumOfSquares).toBe(338350) }) xit('gets the difference', () => { expect(squares.difference).toBe(25164150) }) }) }) ================================================ FILE: exercises/practice/difference-of-squares/difference-of-squares.ts ================================================ export class Squares { constructor(count: unknown) { throw new Error('Remove this line and implement the function') } get sumOfSquares(): unknown { throw new Error('Remove this line and implement the function') } get squareOfSum(): unknown { throw new Error('Remove this line and implement the function') } get difference(): unknown { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/difference-of-squares/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/difference-of-squares/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/difference-of-squares/package.json ================================================ { "name": "@exercism/typescript-difference-of-squares", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/difference-of-squares/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/difference-of-squares/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/diffie-hellman/.docs/instructions.md ================================================ # Instructions Diffie-Hellman key exchange. Alice and Bob use Diffie-Hellman key exchange to share secrets. They start with prime numbers, pick private keys, generate and share public keys, and then generate a shared secret key. ## Step 0 The test program supplies prime numbers p and g. ## Step 1 Alice picks a private key, a, greater than 1 and less than p. Bob does the same to pick a private key b. ## Step 2 Alice calculates a public key A. A = gᵃ mod p Using the same p and g, Bob similarly calculates a public key B from his private key b. ## Step 3 Alice and Bob exchange public keys. Alice calculates secret key s. s = Bᵃ mod p Bob calculates s = Aᵇ mod p The calculations produce the same result! Alice and Bob now share secret s. ================================================ FILE: exercises/practice/diffie-hellman/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "diffie-hellman.ts" ], "test": [ "diffie-hellman.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Diffie-Hellman key exchange.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Wikipedia, 1024 bit key from www.cryptopp.com/wiki.", "source_url": "https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange" } ================================================ FILE: exercises/practice/diffie-hellman/.meta/proof.ci.ts ================================================ // array of first 1000 primes. // prettier-ignore const PRIMES = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079, 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, 3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907, 3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, 3967, 3989, 4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, 4057, 4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139, 4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231, 4241, 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, 4297, 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409, 4421, 4423, 4441, 4447, 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, 4523, 4547, 4549, 4561, 4567, 4583, 4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657, 4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751, 4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, 4831, 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937, 4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003, 5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, 5081, 5087, 5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179, 5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279, 5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387, 5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443, 5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521, 5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, 5639, 5641, 5647, 5651, 5653, 5657, 5659, 5669, 5683, 5689, 5693, 5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791, 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857, 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939, 5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053, 6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, 6133, 6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221, 6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301, 6311, 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, 6367, 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473, 6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571, 6577, 6581, 6599, 6607, 6619, 6637, 6653, 6659, 6661, 6673, 6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761, 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833, 6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, 6917, 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997, 7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103, 7109, 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207, 7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297, 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411, 7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499, 7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561, 7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643, 7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, 7723, 7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829, 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919, ] export class DiffieHellman { constructor( private readonly p: number, private readonly g: number ) { if (!DiffieHellman.validateInitialArguments(p, g)) { throw Error('Constructor arguments are out of range or non-prime!') } } public getPublicKey(privateKey: number): number { if (privateKey <= 1 || privateKey > this.p - 1) { throw Error( 'Private key a must be greater than one but less than modulus parameter p!' ) } return this.g ** privateKey % this.p } public getSecret(theirPublicKey: number, ourPrivateKey: number): number { return theirPublicKey ** ourPrivateKey % this.p } private static validateInitialArguments(p: number, g: number): boolean { const BIGGEST_PRIME = PRIMES[PRIMES.length - 1] return ( p >= 2 && g >= 2 && p <= BIGGEST_PRIME && g <= BIGGEST_PRIME && PRIMES.includes(p) && PRIMES.includes(g) ) } } ================================================ FILE: exercises/practice/diffie-hellman/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [1b97bf38-4307-418e-bfd2-446ffc77588d] description = "private key is greater than 1 and less than p" [68b2a5f7-7755-44c3-97b2-d28d21f014a9] description = "private key is random" [b4161d8e-53a1-4241-ae8f-48cc86527f22] description = "can calculate public key using private key" [0d25f8d7-4897-4338-a033-2d3d7a9af688] description = "can calculate public key when given a different private key" [cd02ad45-3f52-4510-99cc-5161dad948a8] description = "can calculate secret using other party's public key" [17f13c61-a111-4075-9a1f-c2d4636dfa60] description = "key exchange" ================================================ FILE: exercises/practice/diffie-hellman/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/diffie-hellman/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/diffie-hellman/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/diffie-hellman/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/diffie-hellman/diffie-hellman.test.ts ================================================ import { describe, xdescribe, it, expect, xit } from '@jest/globals' import { DiffieHellman } from './diffie-hellman.ts' describe('diffie-hellman', () => { it('throws an error if the constructor arguments are out of range', () => { expect(() => { new DiffieHellman(0, 9999) }).toThrow() }) xit('throws an error if the constructor arguments are not prime', () => { expect(() => { new DiffieHellman(10, 13) }).toThrow() }) xdescribe('input validation', () => { const p = 23 const g = 5 const diffieHellman = new DiffieHellman(p, g) xit('throws an error if private key is negative', () => { expect(() => { diffieHellman.getPublicKey(-1) }).toThrow() }) xit('throws an error if private key is zero', () => { expect(() => { diffieHellman.getPublicKey(0) }).toThrow() }) xit('throws an error if private key is one', () => { expect(() => { diffieHellman.getPublicKey(1) }).toThrow() }) xit('throws an error if private key equals the modulus parameter p', () => { expect(() => { diffieHellman.getPublicKey(p) }).toThrow() }) xit('throws an error if private key is greater than the modulus parameter p', () => { expect(() => { diffieHellman.getPublicKey(p + 1) }).toThrow() }) }) xdescribe('stateless calculation', () => { const diffieHellman = new DiffieHellman(23, 5) const alicePrivateKey = 6 const alicePublicKey = 8 const bobPrivateKey = 15 const bobPublicKey = 19 xit('can calculate public key using private key', () => { expect(diffieHellman.getPublicKey(alicePrivateKey)).toEqual( alicePublicKey ) }) xit('can calculate public key when given a different private key', () => { expect(diffieHellman.getPublicKey(bobPrivateKey)).toEqual(bobPublicKey) }) }) xit("can calculate secret using other party's public key", () => { expect(new DiffieHellman(23, 5).getSecret(19, 6)).toEqual(2) }) xit('key exchange', () => { const diffieHellman = new DiffieHellman(23, 5) const alicePrivateKey = 6 const bobPrivateKey = 15 const alicePublicKey = diffieHellman.getPublicKey(alicePrivateKey) const bobPublicKey = diffieHellman.getPublicKey(bobPrivateKey) const secretA = diffieHellman.getSecret(bobPublicKey, alicePrivateKey) const secretB = diffieHellman.getSecret(alicePublicKey, bobPrivateKey) expect(secretA).toEqual(secretB) }) }) ================================================ FILE: exercises/practice/diffie-hellman/diffie-hellman.ts ================================================ export class DiffieHellman { constructor(p: unknown, g: unknown) { throw new Error('Remove this line and implement the function') } public getPublicKey(privateKey: unknown): unknown { throw new Error('Remove this line and implement the function') } public getSecret(theirPublicKey: unknown, myPrivateKey: unknown): unknown { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/diffie-hellman/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/diffie-hellman/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/diffie-hellman/package.json ================================================ { "name": "@exercism/typescript-diffie-hellman", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/diffie-hellman/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/diffie-hellman/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/dnd-character/.docs/instructions.md ================================================ # Instructions For a game of [Dungeons & Dragons][dnd], each player starts by generating a character they can play with. This character has, among other things, six abilities; strength, dexterity, constitution, intelligence, wisdom and charisma. These six abilities have scores that are determined randomly. You do this by rolling four 6-sided dice and recording the sum of the largest three dice. You do this six times, once for each ability. Your character's initial hitpoints are 10 + your character's constitution modifier. You find your character's constitution modifier by subtracting 10 from your character's constitution, divide by 2 and round down. Write a random character generator that follows the above rules. For example, the six throws of four dice may look like: - 5, 3, 1, 6: You discard the 1 and sum 5 + 3 + 6 = 14, which you assign to strength. - 3, 2, 5, 3: You discard the 2 and sum 3 + 5 + 3 = 11, which you assign to dexterity. - 1, 1, 1, 1: You discard the 1 and sum 1 + 1 + 1 = 3, which you assign to constitution. - 2, 1, 6, 6: You discard the 1 and sum 2 + 6 + 6 = 14, which you assign to intelligence. - 3, 5, 3, 4: You discard the 3 and sum 5 + 3 + 4 = 12, which you assign to wisdom. - 6, 6, 6, 6: You discard the 6 and sum 6 + 6 + 6 = 18, which you assign to charisma. Because constitution is 3, the constitution modifier is -4 and the hitpoints are 6. ~~~~exercism/note Most programming languages feature (pseudo-)random generators, but few programming languages are designed to roll dice. One such language is [Troll][troll]. [troll]: https://di.ku.dk/Ansatte/?pure=da%2Fpublications%2Ftroll-a-language-for-specifying-dicerolls(84a45ff0-068b-11df-825d-000ea68e967b)%2Fexport.html ~~~~ [dnd]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons ================================================ FILE: exercises/practice/dnd-character/.docs/introduction.md ================================================ # Introduction After weeks of anticipation, you and your friends get together for your very first game of [Dungeons & Dragons][dnd] (D&D). Since this is the first session of the game, each player has to generate a character to play with. The character's abilities are determined by rolling 6-sided dice, but where _are_ the dice? With a shock, you realize that your friends are waiting for _you_ to produce the dice; after all it was your idea to play D&D! Panicking, you realize you forgot to bring the dice, which would mean no D&D game. As you have some basic coding skills, you quickly come up with a solution: you'll write a program to simulate dice rolls. [dnd]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons ================================================ FILE: exercises/practice/dnd-character/.meta/config.json ================================================ { "authors": [ "JoshiRaez", "SleeplessByte" ], "files": { "solution": [ "dnd-character.ts" ], "test": [ "dnd-character.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Randomly generate Dungeons & Dragons characters.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Simon Shine, Erik Schierboom", "source_url": "https://github.com/exercism/problem-specifications/issues/616#issuecomment-437358945" } ================================================ FILE: exercises/practice/dnd-character/.meta/proof.ci.ts ================================================ export class DnDCharacter { public readonly hitpoints: number public readonly strength: number public readonly dexterity: number public readonly constitution: number public readonly intelligence: number public readonly wisdom: number public readonly charisma: number constructor() { this.strength = DnDCharacter.generateAbilityScore() this.dexterity = DnDCharacter.generateAbilityScore() this.constitution = DnDCharacter.generateAbilityScore() this.intelligence = DnDCharacter.generateAbilityScore() this.wisdom = DnDCharacter.generateAbilityScore() this.charisma = DnDCharacter.generateAbilityScore() this.hitpoints = 10 + DnDCharacter.getModifierFor(this.constitution) } public static generateAbilityScore(): number { return this.rollDice(4) .sort() .slice(1, 4) .reduce((acu, act) => acu + act, 0) } public static getModifierFor(abilityValue: number): number { return Math.floor((abilityValue - 10) / 2) } private static rollDice(quantity: number): number[] { return new Array(quantity).fill(0).map(() => this.rollDie()) } private static rollDie(): number { return Math.floor(Math.random() * 6) + 1 } } ================================================ FILE: exercises/practice/dnd-character/.meta/tests.toml ================================================ [1e9ae1dc-35bd-43ba-aa08-e4b94c20fa37] description = "Ability modifier for score 3 is -4" include = true [cc9bb24e-56b8-4e9e-989d-a0d1a29ebb9c] description = "Ability modifier for score 4 is -3" include = true [5b519fcd-6946-41ee-91fe-34b4f9808326] description = "Ability modifier for score 5 is -3" include = true [dc2913bd-6d7a-402e-b1e2-6d568b1cbe21] description = "Ability modifier for score 6 is -2" include = true [099440f5-0d66-4b1a-8a10-8f3a03cc499f] description = "Ability modifier for score 7 is -2" include = true [cfda6e5c-3489-42f0-b22b-4acb47084df0] description = "Ability modifier for score 8 is -1" include = true [c70f0507-fa7e-4228-8463-858bfbba1754] description = "Ability modifier for score 9 is -1" include = true [6f4e6c88-1cd9-46a0-92b8-db4a99b372f7] description = "Ability modifier for score 10 is 0" include = true [e00d9e5c-63c8-413f-879d-cd9be9697097] description = "Ability modifier for score 11 is 0" include = true [eea06f3c-8de0-45e7-9d9d-b8cab4179715] description = "Ability modifier for score 12 is 1" include = true [9c51f6be-db72-4af7-92ac-b293a02c0dcd] description = "Ability modifier for score 13 is 1" include = true [94053a5d-53b6-4efc-b669-a8b5098f7762] description = "Ability modifier for score 14 is 2" include = true [8c33e7ca-3f9f-4820-8ab3-65f2c9e2f0e2] description = "Ability modifier for score 15 is 2" include = true [c3ec871e-1791-44d0-b3cc-77e5fb4cd33d] description = "Ability modifier for score 16 is 3" include = true [3d053cee-2888-4616-b9fd-602a3b1efff4] description = "Ability modifier for score 17 is 3" include = true [bafd997a-e852-4e56-9f65-14b60261faee] description = "Ability modifier for score 18 is 4" include = true [4f28f19c-2e47-4453-a46a-c0d365259c14] description = "Random ability is within range" include = true [385d7e72-864f-4e88-8279-81a7d75b04ad] description = "Random character is valid" include = true [2ca77b9b-c099-46c3-a02c-0d0f68ffa0fe] description = "Each ability is only calculated once" include = true ================================================ FILE: exercises/practice/dnd-character/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/dnd-character/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/dnd-character/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/dnd-character/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/dnd-character/dnd-character.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { DnDCharacter } from './dnd-character.ts' describe('Ability modifier', () => { it('Ability modifier for score 3 is -4', () => { expect(DnDCharacter.getModifierFor(3)).toEqual(-4) }) xit('Ability modifier for score 4 is -3', () => { expect(DnDCharacter.getModifierFor(4)).toEqual(-3) }) xit('Ability modifier for score 5 is -3', () => { expect(DnDCharacter.getModifierFor(5)).toEqual(-3) }) xit('Ability modifier for score 6 is -2', () => { expect(DnDCharacter.getModifierFor(6)).toEqual(-2) }) xit('Ability modifier for score 7 is -2', () => { expect(DnDCharacter.getModifierFor(7)).toEqual(-2) }) xit('Ability modifier for score 8 is -1', () => { expect(DnDCharacter.getModifierFor(8)).toEqual(-1) }) xit('Ability modifier for score 9 is -1', () => { expect(DnDCharacter.getModifierFor(9)).toEqual(-1) }) xit('Ability modifier for score 10 is 0', () => { expect(DnDCharacter.getModifierFor(10)).toEqual(0) }) xit('Ability modifier for score 11 is 0', () => { expect(DnDCharacter.getModifierFor(11)).toEqual(0) }) xit('Ability modifier for score 12 is 1', () => { expect(DnDCharacter.getModifierFor(12)).toEqual(1) }) xit('Ability modifier for score 13 is 1', () => { expect(DnDCharacter.getModifierFor(13)).toEqual(1) }) xit('Ability modifier for score 14 is 2', () => { expect(DnDCharacter.getModifierFor(14)).toEqual(2) }) xit('Ability modifier for score 15 is 2', () => { expect(DnDCharacter.getModifierFor(15)).toEqual(2) }) xit('Ability modifier for score 16 is 3', () => { expect(DnDCharacter.getModifierFor(16)).toEqual(3) }) xit('Ability modifier for score 17 is 3', () => { expect(DnDCharacter.getModifierFor(17)).toEqual(3) }) xit('Ability modifier for score 18 is 4', () => { expect(DnDCharacter.getModifierFor(18)).toEqual(4) }) }) describe('Ability generator', () => { xit('Random ability is within range', () => { const abilityScore = DnDCharacter.generateAbilityScore() expect(abilityScore).toBeGreaterThanOrEqual(3) expect(abilityScore).toBeLessThanOrEqual(18) }) }) describe('Character creation', () => { xit('Random character is valid - hitpoints', () => { const character = new DnDCharacter() expect(character.hitpoints).toEqual( 10 + DnDCharacter.getModifierFor(character.constitution) ) }) xit('Random character is valid - strength', () => { const character = new DnDCharacter() expect(character.strength).toBeGreaterThanOrEqual(3) expect(character.strength).toBeLessThanOrEqual(18) }) xit('Random character is valid - dexterity', () => { const character = new DnDCharacter() expect(character.dexterity).toBeGreaterThanOrEqual(3) expect(character.dexterity).toBeLessThanOrEqual(18) }) xit('Random character is valid - constitution', () => { const character = new DnDCharacter() expect(character.constitution).toBeGreaterThanOrEqual(3) expect(character.constitution).toBeLessThanOrEqual(18) }) xit('Random character is valid - intelligence', () => { const character = new DnDCharacter() expect(character.intelligence).toBeGreaterThanOrEqual(3) expect(character.intelligence).toBeLessThanOrEqual(18) }) xit('Random character is valid - wisdom', () => { const character = new DnDCharacter() expect(character.wisdom).toBeGreaterThanOrEqual(3) expect(character.wisdom).toBeLessThanOrEqual(18) }) xit('Random character is valid - charisma', () => { const character = new DnDCharacter() expect(character.charisma).toBeGreaterThanOrEqual(3) expect(character.charisma).toBeLessThanOrEqual(18) }) xit('Each ability is only calculated once', () => { const character = new DnDCharacter() expect(character.strength === character.strength).toBeTruthy() }) }) ================================================ FILE: exercises/practice/dnd-character/dnd-character.ts ================================================ export class DnDCharacter { public static generateAbilityScore(): number { throw new Error('Remove this line and implement the function') } public static getModifierFor(abilityValue: number): number { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/dnd-character/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/dnd-character/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/dnd-character/package.json ================================================ { "name": "@exercism/typescript-dnd-character", "version": "1.0.0", "description": "Exercism practice exercise on dnd-character", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/dnd-character/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/dnd-character/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/eliuds-eggs/.docs/instructions.md ================================================ # Instructions Your task is to count the number of 1 bits in the binary representation of a number. ## Restrictions Keep your hands off that bit-count functionality provided by your standard library! Solve this one yourself using other basic tools instead. ================================================ FILE: exercises/practice/eliuds-eggs/.docs/introduction.md ================================================ # Introduction Your friend Eliud inherited a farm from her grandma Tigist. Her granny was an inventor and had a tendency to build things in an overly complicated manner. The chicken coop has a digital display showing an encoded number representing the positions of all eggs that could be picked up. Eliud is asking you to write a program that shows the actual number of eggs in the coop. The position information encoding is calculated as follows: 1. Scan the potential egg-laying spots and mark down a `1` for an existing egg or a `0` for an empty spot. 2. Convert the number from binary to decimal. 3. Show the result on the display. ## Example 1 ![Seven individual nest boxes arranged in a row whose first, third, fourth and seventh nests each have a single egg.](https://assets.exercism.org/images/exercises/eliuds-eggs/example-1-coop.svg) ```text _ _ _ _ _ _ _ |E| |E|E| | |E| ``` ### Resulting Binary ![1011001](https://assets.exercism.org/images/exercises/eliuds-eggs/example-1-binary.svg) ```text _ _ _ _ _ _ _ |1|0|1|1|0|0|1| ``` ### Decimal number on the display 89 ### Actual eggs in the coop 4 ## Example 2 ![Seven individual nest boxes arranged in a row where only the fourth nest has an egg.](https://assets.exercism.org/images/exercises/eliuds-eggs/example-2-coop.svg) ```text _ _ _ _ _ _ _ | | | |E| | | | ``` ### Resulting Binary ![0001000](https://assets.exercism.org/images/exercises/eliuds-eggs/example-2-binary.svg) ```text _ _ _ _ _ _ _ |0|0|0|1|0|0|0| ``` ### Decimal number on the display 8 ### Actual eggs in the coop 1 ================================================ FILE: exercises/practice/eliuds-eggs/.meta/config.json ================================================ { "authors": [ "therealowenrees" ], "files": { "solution": [ "eliuds-eggs.ts" ], "test": [ "eliuds-eggs.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Help Eliud count the number of eggs in her chicken coop by counting the number of 1 bits in a binary representation.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Christian Willner, Eric Willigers", "source_url": "https://forum.exercism.org/t/new-exercise-suggestion-pop-count/7632/5" } ================================================ FILE: exercises/practice/eliuds-eggs/.meta/proof.ci.ts ================================================ export const eggCount = (displayValue: number): number => { let count = 0 while (displayValue !== 0) { count += displayValue & 1 displayValue >>= 1 } return count } ================================================ FILE: exercises/practice/eliuds-eggs/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [559e789d-07d1-4422-9004-3b699f83bca3] description = "0 eggs" [97223282-f71e-490c-92f0-b3ec9e275aba] description = "1 egg" [1f8fd18f-26e9-4144-9a0e-57cdfc4f4ff5] description = "4 eggs" [0c18be92-a498-4ef2-bcbb-28ac4b06cb81] description = "13 eggs" ================================================ FILE: exercises/practice/eliuds-eggs/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/eliuds-eggs/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/eliuds-eggs/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/eliuds-eggs/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/eliuds-eggs/eliuds-eggs.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { eggCount } from './eliuds-eggs.ts' describe('EliudsEggs', () => { it('0 eggs', () => { const expected = 0 const actual = eggCount(0) expect(actual).toEqual(expected) }) xit('1 egg', () => { const expected = 1 const actual = eggCount(16) expect(actual).toEqual(expected) }) xit('4 eggs', () => { const expected = 4 const actual = eggCount(89) expect(actual).toEqual(expected) }) xit('13 eggs', () => { const expected = 13 const actual = eggCount(2000000000) expect(actual).toEqual(expected) }) }) ================================================ FILE: exercises/practice/eliuds-eggs/eliuds-eggs.ts ================================================ // // This is only a SKELETON file for the 'Pop Count' exercise. It's been provided as a // convenience to get you started writing code faster. // export const eggCount = (displayValue: unknown): unknown => { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/eliuds-eggs/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/eliuds-eggs/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/eliuds-eggs/package.json ================================================ { "name": "@exercism/typescript-eliuds-eggs", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/eliuds-eggs/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/eliuds-eggs/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/etl/.docs/instructions.md ================================================ # Instructions Your task is to change the data format of letters and their point values in the game. Currently, letters are stored in groups based on their score, in a one-to-many mapping. - 1 point: "A", "E", "I", "O", "U", "L", "N", "R", "S", "T", - 2 points: "D", "G", - 3 points: "B", "C", "M", "P", - 4 points: "F", "H", "V", "W", "Y", - 5 points: "K", - 8 points: "J", "X", - 10 points: "Q", "Z", This needs to be changed to store each individual letter with its score in a one-to-one mapping. - "a" is worth 1 point. - "b" is worth 3 points. - "c" is worth 3 points. - "d" is worth 2 points. - etc. As part of this change, the team has also decided to change the letters to be lower-case rather than upper-case. ~~~~exercism/note If you want to look at how the data was previously structured and how it needs to change, take a look at the examples in the test suite. ~~~~ ================================================ FILE: exercises/practice/etl/.docs/introduction.md ================================================ # Introduction You work for a company that makes an online multiplayer game called Lexiconia. To play the game, each player is given 13 letters, which they must rearrange to create words. Different letters have different point values, since it's easier to create words with some letters than others. The game was originally launched in English, but it is very popular, and now the company wants to expand to other languages as well. Different languages need to support different point values for letters. The point values are determined by how often letters are used, compared to other letters in that language. For example, the letter 'C' is quite common in English, and is only worth 3 points. But in Norwegian it's a very rare letter, and is worth 10 points. To make it easier to add new languages, your team needs to change the way letters and their point values are stored in the game. ================================================ FILE: exercises/practice/etl/.meta/config.json ================================================ { "authors": [ "masters3d" ], "contributors": [ "lukaszklis", "pranasziaukas", "SleeplessByte" ], "files": { "solution": [ "etl.ts" ], "test": [ "etl.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Change the data format for scoring a game to more easily add other languages.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Based on an exercise by the JumpstartLab team for students at The Turing School of Software and Design.", "source_url": "https://www.turing.edu/" } ================================================ FILE: exercises/practice/etl/.meta/proof.ci.ts ================================================ export function transform(input: { [key: string]: string[] }): { [key: string]: number } { const phase1: { [key: string]: number } = {} for (const key in input) { if (Object.prototype.hasOwnProperty.call(input, key)) { const value = input[key] for (const each of value) { phase1[each.toLowerCase()] = Number.parseInt(key, 10) } } } return phase1 } ================================================ FILE: exercises/practice/etl/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [78a7a9f9-4490-4a47-8ee9-5a38bb47d28f] description = "single letter" [60dbd000-451d-44c7-bdbb-97c73ac1f497] description = "single score with multiple letters" [f5c5de0c-301f-4fdd-a0e5-df97d4214f54] description = "multiple scores with multiple letters" [5db8ea89-ecb4-4dcd-902f-2b418cc87b9d] description = "multiple scores with differing numbers of letters" ================================================ FILE: exercises/practice/etl/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/etl/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/etl/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/etl/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/etl/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/etl/etl.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { transform } from './etl.ts' describe('Transform', () => { it('transforms one value', () => { const old: { [key: string]: string[] } = { 1: ['A'] } const expected: { [key: string]: number } = { a: 1 } expect(transform(old)).toEqual(expected) }) xit('transforms more values', () => { const old = { 1: ['A', 'E', 'I', 'O', 'U'] } const expected = { a: 1, e: 1, i: 1, o: 1, u: 1 } expect(transform(old)).toEqual(expected) }) xit('transforms more keys', () => { const old = { 1: ['A', 'E'], 2: ['D', 'G'] } const expected = { a: 1, e: 1, d: 2, g: 2 } expect(transform(old)).toEqual(expected) }) xit('transforms a full dataset', () => { const old = { 1: ['A', 'E', 'I', 'O', 'U', 'L', 'N', 'R', 'S', 'T'], 2: ['D', 'G'], 3: ['B', 'C', 'M', 'P'], 4: ['F', 'H', 'V', 'W', 'Y'], 5: ['K'], 8: ['J', 'X'], 10: ['Q', 'Z'], } const expected = { a: 1, b: 3, c: 3, d: 2, e: 1, f: 4, g: 2, h: 4, i: 1, j: 8, k: 5, l: 1, m: 3, n: 1, o: 1, p: 3, q: 10, r: 1, s: 1, t: 1, u: 1, v: 4, w: 4, x: 8, y: 4, z: 10, } expect(transform(old)).toEqual(expected) }) }) ================================================ FILE: exercises/practice/etl/etl.ts ================================================ export function transform(/* Parameters go here */) { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/etl/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/etl/package.json ================================================ { "name": "@exercism/typescript-etl", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/etl/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/etl/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/flatten-array/.docs/instructions.md ================================================ # Instructions Take a nested array of any depth and return a fully flattened array. Note that some language tracks may include null-like values in the input array, and the way these values are represented varies by track. Such values should be excluded from the flattened array. Additionally, the input may be of a different data type and contain different types, depending on the track. Check the test suite for details. ## Example input: `[1, [2, 6, null], [[null, [4]], 5]]` output: `[1, 2, 6, 4, 5]` ================================================ FILE: exercises/practice/flatten-array/.docs/introduction.md ================================================ # Introduction A shipment of emergency supplies has arrived, but there's a problem. To protect from damage, the items — flashlights, first-aid kits, blankets — are packed inside boxes, and some of those boxes are nested several layers deep inside other boxes! To be prepared for an emergency, everything must be easily accessible in one box. Can you unpack all the supplies and place them into a single box, so they're ready when needed most? ================================================ FILE: exercises/practice/flatten-array/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "flatten-array.ts" ], "test": [ "flatten-array.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Take a nested list and return a single list with all values except nil/null.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Interview Question", "source_url": "https://reference.wolfram.com/language/ref/Flatten.html" } ================================================ FILE: exercises/practice/flatten-array/.meta/proof.ci.ts ================================================ export function flatten>(arr: T): A[] { return arr .reduce( (acc: A[], el) => Array.isArray(el) ? acc.concat(flatten(el)) : acc.concat(el), [] ) .filter((el: A) => el !== null && el !== undefined) } ================================================ FILE: exercises/practice/flatten-array/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [8c71dabd-da60-422d-a290-4a571471fb14] description = "empty" [d268b919-963c-442d-9f07-82b93f1b518c] description = "no nesting" [3f15bede-c856-479e-bb71-1684b20c6a30] description = "flattens a nested array" [c84440cc-bb3a-48a6-862c-94cf23f2815d] description = "flattens array with just integers present" [d3d99d39-6be5-44f5-a31d-6037d92ba34f] description = "5 level nesting" [d572bdba-c127-43ed-bdcd-6222ac83d9f7] description = "6 level nesting" [0705a8e5-dc86-4cec-8909-150c5e54fa9c] description = "null values are omitted from the final result" [c6cf26de-8ccd-4410-84bd-b9efd88fd2bc] description = "consecutive null values at the front of the list are omitted from the final result" include = false [bc72da10-5f55-4ada-baf3-50e4da02ec8e] description = "consecutive null values at the front of the array are omitted from the final result" reimplements = "c6cf26de-8ccd-4410-84bd-b9efd88fd2bc" [382c5242-587e-4577-b8ce-a5fb51e385a1] description = "consecutive null values in the middle of the list are omitted from the final result" include = false [6991836d-0d9b-4703-80a0-3f1f23eb5981] description = "consecutive null values in the middle of the array are omitted from the final result" reimplements = "382c5242-587e-4577-b8ce-a5fb51e385a1" [ef1d4790-1b1e-4939-a179-51ace0829dbd] description = "6 level nest list with null values" include = false [dc90a09c-5376-449c-a7b3-c2d20d540069] description = "6 level nested array with null values" reimplements = "ef1d4790-1b1e-4939-a179-51ace0829dbd" [85721643-705a-4150-93ab-7ae398e2942d] description = "all values in nested list are null" include = false [51f5d9af-8f7f-4fb5-a156-69e8282cb275] description = "all values in nested array are null" reimplements = "85721643-705a-4150-93ab-7ae398e2942d" ================================================ FILE: exercises/practice/flatten-array/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/flatten-array/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/flatten-array/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/flatten-array/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/flatten-array/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/flatten-array/flatten-array.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { flatten } from './flatten-array.ts' describe('Flatten Array', () => { it('empty', () => { const arr = [] const expected = [] expect(flatten(arr)).toEqual(expected) }) xit('no nesting', () => { const arr = [0, 1, 2] const expected = [0, 1, 2] expect(flatten(arr)).toEqual(expected) }) xit('flattens a nested array', () => { const arr = [[[]]] const expected = [] expect(flatten(arr)).toEqual(expected) }) xit('flattens array with just integers present', () => { const arr = [1, [2, 3, 4, 5, 6, 7], 8] const expected = [1, 2, 3, 4, 5, 6, 7, 8] expect(flatten(arr)).toEqual(expected) }) xit('5 level nesting', () => { const arr = [0, 2, [[2, 3], 8, 100, 4, [[[50]]]], -2] const expected = [0, 2, 2, 3, 8, 100, 4, 50, -2] expect(flatten(arr)).toEqual(expected) }) xit('6 level nesting', () => { const arr = [1, [2, [[3]], [4, [[5]]], 6, 7], 8] const expected = [1, 2, 3, 4, 5, 6, 7, 8] expect(flatten(arr)).toEqual(expected) }) xit('consecutive null values at the front of the array are omitted from the final result', () => { const arr = [undefined, undefined, 3] const expected = [3] expect(flatten(arr)).toEqual(expected) }) xit('consecutive null values in the middle of the array are omitted from the final result', () => { const arr = [1, undefined, undefined, 4] const expected = [1, 4] expect(flatten(arr)).toEqual(expected) }) xit('6 level nest array with null values', () => { const arr = [0, 2, [[2, 3], 8, [[100]], undefined, [[undefined]]], -2] const expected = [0, 2, 2, 3, 8, 100, -2] expect(flatten(arr)).toEqual(expected) }) xit('all values in nested array are null', () => { const expected: number[] = [] expect( flatten([ undefined, [[[undefined]]], undefined, undefined, [[undefined, undefined], undefined], undefined, ]) ).toEqual(expected) }) }) ================================================ FILE: exercises/practice/flatten-array/flatten-array.ts ================================================ export function flatten(/* Parameters go here */) { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/flatten-array/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/flatten-array/package.json ================================================ { "name": "@exercism/typescript-flatten-array", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/flatten-array/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/flatten-array/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/flower-field/.docs/instructions.md ================================================ # Instructions Your task is to add flower counts to empty squares in a completed Flower Field garden. The garden itself is a rectangle board composed of squares that are either empty (`' '`) or a flower (`'*'`). For each empty square, count the number of flowers adjacent to it (horizontally, vertically, diagonally). If the empty square has no adjacent flowers, leave it empty. Otherwise replace it with the count of adjacent flowers. For example, you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen): ```text ·*·*· ··*·· ··*·· ····· ``` Which your code should transform into this: ```text 1*3*1 13*31 ·2*2· ·111· ``` ================================================ FILE: exercises/practice/flower-field/.docs/introduction.md ================================================ # Introduction [Flower Field][history] is a compassionate reimagining of the popular game Minesweeper. The object of the game is to find all the flowers in the garden using numeric hints that indicate how many flowers are directly adjacent (horizontally, vertically, diagonally) to a square. "Flower Field" shipped in regional versions of Microsoft Windows in Italy, Germany, South Korea, Japan and Taiwan. [history]: https://web.archive.org/web/20020409051321fw_/http://rcm.usr.dsi.unimi.it/rcmweb/fnm/ ================================================ FILE: exercises/practice/flower-field/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "BNAndras", "iignatov", "masters3d", "SleeplessByte" ], "files": { "solution": [ "flower-field.ts" ], "test": [ "flower-field.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Mark all the flowers in a garden." } ================================================ FILE: exercises/practice/flower-field/.meta/proof.ci.ts ================================================ const FLOWER = '*' const DELTAS = [ [-1, -1], [-1, 0], [-1, 1], [1, 1], [1, 0], [1, -1], [0, 1], [0, -1], ] export function annotate(rows: string[]): string[] { if (noDataPresent(rows)) { return rows } const inputBoard = rows.map((row) => [...row]) const result = inputBoard.map((row, x) => [...row].map((cell, y) => cellToFlowerOrCount(cell, inputBoard, x, y)) ) return stringify(result as string[][]) } function cellToFlowerOrCount( cell: string, inputBoard: string[][], x: number, y: number ): number | '*' | ' ' { if (cell === FLOWER) { return FLOWER } return countAdjacentFlowers(inputBoard, x, y) || ' ' } function countAdjacentFlowers(board: string[][], x: number, y: number): number { return DELTAS.filter((d) => adjacentSquareIsOnBoard(board, x, d)).filter( (d) => adjacentSquareHasFlower(board, x, y, d) ).length } function stringify(board: string[][]): string[] { return board.map((row) => row.join('')) } function noDataPresent(rows: string[]): boolean { return rows.length === 0 || rows[0].length === 0 } function adjacentSquareIsOnBoard( board: string[][], x: number, d: number[] ): string[] { return board[x + d[0]] } function adjacentSquareHasFlower( board: string[][], x: number, y: number, d: number[] ): boolean { return board[x + d[0]][y + d[1]] === FLOWER } ================================================ FILE: exercises/practice/flower-field/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [237ff487-467a-47e1-9b01-8a891844f86c] description = "no rows" [4b4134ec-e20f-439c-a295-664c38950ba1] description = "no columns" [d774d054-bbad-4867-88ae-069cbd1c4f92] description = "no flowers" [225176a0-725e-43cd-aa13-9dced501f16e] description = "garden full of flowers" [3f345495-f1a5-4132-8411-74bd7ca08c49] description = "flower surrounded by spaces" [6cb04070-4199-4ef7-a6fa-92f68c660fca] description = "space surrounded by flowers" [272d2306-9f62-44fe-8ab5-6b0f43a26338] description = "horizontal line" [c6f0a4b2-58d0-4bf6-ad8d-ccf4144f1f8e] description = "horizontal line, flowers at edges" [a54e84b7-3b25-44a8-b8cf-1753c8bb4cf5] description = "vertical line" [b40f42f5-dec5-4abc-b167-3f08195189c1] description = "vertical line, flowers at edges" [58674965-7b42-4818-b930-0215062d543c] description = "cross" [dd9d4ca8-9e68-4f78-a677-a2a70fd7a7b8] description = "large garden" [6e4ac13a-3e43-4728-a2e3-3551d4b1a996] description = "multiple adjacent flowers" ================================================ FILE: exercises/practice/flower-field/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/flower-field/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/flower-field/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/flower-field/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/flower-field/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/flower-field/flower-field.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { annotate } from './flower-field.ts' describe('Flower Field annotate', () => { it('handles no rows', () => { expect(annotate([])).toEqual([]) }) xit('handles no columns', () => { expect(annotate([''])).toEqual(['']) }) xit('handles no flowers', () => { const input = [' ', ' ', ' '] const expected = [' ', ' ', ' '] expect(annotate(input)).toEqual(expected) }) xit('handles garden full of flowers', () => { const input = ['***', '***', '***'] const expected = ['***', '***', '***'] expect(annotate(input)).toEqual(expected) }) xit('handles flower surrounded by spaces', () => { const input = [' ', ' * ', ' '] const expected = ['111', '1*1', '111'] expect(annotate(input)).toEqual(expected) }) xit('handles space surrounded by flowers', () => { const input = ['***', '* *', '***'] const expected = ['***', '*8*', '***'] expect(annotate(input)).toEqual(expected) }) xit('handles horizontal line', () => { const input = [' * * '] const expected = ['1*2*1'] expect(annotate(input)).toEqual(expected) }) xit('handles horizontal line, flowers at edges', () => { const input = ['* *'] const expected = ['*1 1*'] expect(annotate(input)).toEqual(expected) }) xit('handles vertical line', () => { const input = [' ', '*', ' ', '*', ' '] const expected = ['1', '*', '2', '*', '1'] expect(annotate(input)).toEqual(expected) }) xit('handles vertical line, flowers at edges', () => { const input = ['*', ' ', ' ', ' ', '*'] const expected = ['*', '1', ' ', '1', '*'] expect(annotate(input)).toEqual(expected) }) xit('handles cross', () => { const input = [' * ', ' * ', '*****', ' * ', ' * '] const expected = [' 2*2 ', '25*52', '*****', '25*52', ' 2*2 '] expect(annotate(input)).toEqual(expected) }) xit('handles large gardem', () => { const input = [' * * ', ' * ', ' * ', ' * *', ' * * ', ' '] const expected = [ '1*22*1', '12*322', ' 123*2', '112*4*', '1*22*2', '111111', ] expect(annotate(input)).toEqual(expected) }) xit('multiple adjacent flowers', () => { expect(annotate([' ** '])).toEqual(['1**1']) }) }) ================================================ FILE: exercises/practice/flower-field/flower-field.ts ================================================ export function annotate(field: unknown): unknown { throw new Error('Remove this statement and implement this function') } ================================================ FILE: exercises/practice/flower-field/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/flower-field/package.json ================================================ { "name": "@exercism/typescript-flower-field", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/flower-field/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/flower-field/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/food-chain/.docs/instructions.md ================================================ # Instructions Generate the lyrics of the song 'I Know an Old Lady Who Swallowed a Fly'. While you could copy/paste the lyrics, or read them from a file, this problem is much more interesting if you approach it algorithmically. This is a [cumulative song][cumulative-song] of unknown origin. This is one of many common variants. ```text I know an old lady who swallowed a fly. I don't know why she swallowed the fly. Perhaps she'll die. I know an old lady who swallowed a spider. It wriggled and jiggled and tickled inside her. She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. I know an old lady who swallowed a bird. How absurd to swallow a bird! She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. I know an old lady who swallowed a cat. Imagine that, to swallow a cat! She swallowed the cat to catch the bird. She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. I know an old lady who swallowed a dog. What a hog, to swallow a dog! She swallowed the dog to catch the cat. She swallowed the cat to catch the bird. She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. I know an old lady who swallowed a goat. Just opened her throat and swallowed a goat! She swallowed the goat to catch the dog. She swallowed the dog to catch the cat. She swallowed the cat to catch the bird. She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. I know an old lady who swallowed a cow. I don't know how she swallowed a cow! She swallowed the cow to catch the goat. She swallowed the goat to catch the dog. She swallowed the dog to catch the cat. She swallowed the cat to catch the bird. She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. I know an old lady who swallowed a horse. She's dead, of course! ``` [cumulative-song]: https://en.wikipedia.org/wiki/Cumulative_song ================================================ FILE: exercises/practice/food-chain/.meta/config.json ================================================ { "authors": [ "masters3d" ], "contributors": [ "lukaszklis", "SleeplessByte" ], "files": { "solution": [ "food-chain.ts" ], "test": [ "food-chain.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Generate the lyrics of the song 'I Know an Old Lady Who Swallowed a Fly'.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/There_Was_an_Old_Lady_Who_Swallowed_a_Fly" } ================================================ FILE: exercises/practice/food-chain/.meta/proof.ci.ts ================================================ const animals = [ 'fly', 'spider', 'bird', 'cat', 'dog', 'goat', 'cow', 'horse', ] as const export function verses(start = 1, end = 8): string { if (end < start) { throw new Error('end should be smaller than the start') } let result = '' for (; start <= end; start += 1) { result += verse(start) if (start !== end) { result += '\n' } } return result } export function verse(num: number): string { let result = '' let index = num - 1 result += `I know an old lady who swallowed a ${animals[index]}.\n` switch (num) { case 2: result += 'It wriggled and jiggled and tickled inside her.\n' break case 3: result += 'How absurd to swallow a bird!\n' break case 4: result += 'Imagine that, to swallow a cat!\n' break case 5: result += 'What a hog, to swallow a dog!\n' break case 6: result += 'Just opened her throat and swallowed a goat!\n' break case 7: result += "I don't know how she swallowed a cow!\n" break case 8: result += "She's dead, of course!\n" return result default: break } while (index >= 1) { result += `She swallowed the ${animals[index]} to catch the ${ animals[index - 1] }` if (index === 2) { result += ' that wriggled and jiggled and tickled inside her' } result += '.\n' index -= 1 } result += "I don't know why she swallowed the fly. Perhaps she'll die." return result + '\n' } ================================================ FILE: exercises/practice/food-chain/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [751dce68-9412-496e-b6e8-855998c56166] description = "fly" [6c56f861-0c5e-4907-9a9d-b2efae389379] description = "spider" [3edf5f33-bef1-4e39-ae67-ca5eb79203fa] description = "bird" [e866a758-e1ff-400e-9f35-f27f28cc288f] description = "cat" [3f02c30e-496b-4b2a-8491-bc7e2953cafb] description = "dog" [4b3fd221-01ea-46e0-825b-5734634fbc59] description = "goat" [1b707da9-7001-4fac-941f-22ad9c7a65d4] description = "cow" [3cb10d46-ae4e-4d2c-9296-83c9ffc04cdc] description = "horse" [22b863d5-17e4-4d1e-93e4-617329a5c050] description = "multiple verses" [e626b32b-745c-4101-bcbd-3b13456893db] description = "full song" ================================================ FILE: exercises/practice/food-chain/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/food-chain/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/food-chain/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/food-chain/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/food-chain/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/food-chain/food-chain.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { verse, verses } from './food-chain.ts' describe('Food Chain', () => { it('fly', () => { const expected = `I know an old lady who swallowed a fly. I don't know why she swallowed the fly. Perhaps she'll die. ` expect(verse(1)).toEqual(expected) }) xit('spider', () => { const expected = `I know an old lady who swallowed a spider. It wriggled and jiggled and tickled inside her. She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. ` expect(verse(2)).toEqual(expected) }) xit('bird', () => { const expected = `I know an old lady who swallowed a bird. How absurd to swallow a bird! She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. ` expect(verse(3)).toEqual(expected) }) xit('cat', () => { const expected = `I know an old lady who swallowed a cat. Imagine that, to swallow a cat! She swallowed the cat to catch the bird. She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. ` expect(verse(4)).toEqual(expected) }) xit('dog', () => { const expected = `I know an old lady who swallowed a dog. What a hog, to swallow a dog! She swallowed the dog to catch the cat. She swallowed the cat to catch the bird. She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. ` expect(verse(5)).toEqual(expected) }) xit('goat', () => { const expected = `I know an old lady who swallowed a goat. Just opened her throat and swallowed a goat! She swallowed the goat to catch the dog. She swallowed the dog to catch the cat. She swallowed the cat to catch the bird. She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. ` expect(verse(6)).toEqual(expected) }) xit('cow', () => { const expected = `I know an old lady who swallowed a cow. I don't know how she swallowed a cow! She swallowed the cow to catch the goat. She swallowed the goat to catch the dog. She swallowed the dog to catch the cat. She swallowed the cat to catch the bird. She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. ` expect(verse(7)).toEqual(expected) }) xit('horse', () => { const expected = `I know an old lady who swallowed a horse. She's dead, of course! ` expect(verse(8)).toEqual(expected) }) xit('multiple verses', () => { const expected = `I know an old lady who swallowed a fly. I don't know why she swallowed the fly. Perhaps she'll die. I know an old lady who swallowed a spider. It wriggled and jiggled and tickled inside her. She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. ` expect(verses(1, 2)).toEqual(expected) }) xit('the whole song', () => { const expected = `I know an old lady who swallowed a fly. I don't know why she swallowed the fly. Perhaps she'll die. I know an old lady who swallowed a spider. It wriggled and jiggled and tickled inside her. She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. I know an old lady who swallowed a bird. How absurd to swallow a bird! She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. I know an old lady who swallowed a cat. Imagine that, to swallow a cat! She swallowed the cat to catch the bird. She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. I know an old lady who swallowed a dog. What a hog, to swallow a dog! She swallowed the dog to catch the cat. She swallowed the cat to catch the bird. She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. I know an old lady who swallowed a goat. Just opened her throat and swallowed a goat! She swallowed the goat to catch the dog. She swallowed the dog to catch the cat. She swallowed the cat to catch the bird. She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. I know an old lady who swallowed a cow. I don't know how she swallowed a cow! She swallowed the cow to catch the goat. She swallowed the goat to catch the dog. She swallowed the dog to catch the cat. She swallowed the cat to catch the bird. She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. She swallowed the spider to catch the fly. I don't know why she swallowed the fly. Perhaps she'll die. I know an old lady who swallowed a horse. She's dead, of course! ` expect(verses(1, 8)).toEqual(expected) }) }) ================================================ FILE: exercises/practice/food-chain/food-chain.ts ================================================ export function verse(/* Parameters go here */) { throw new Error('Remove this line and implement the function') } export function verses(/* Parameters go here */) { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/food-chain/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/food-chain/package.json ================================================ { "name": "@exercism/typescript-food-chain", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/food-chain/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/food-chain/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/game-of-life/.docs/instructions.md ================================================ # Instructions After each generation, the cells interact with their eight neighbors, which are cells adjacent horizontally, vertically, or diagonally. The following rules are applied to each cell: - Any live cell with two or three live neighbors lives on. - Any dead cell with exactly three live neighbors becomes a live cell. - All other cells die or stay dead. Given a matrix of 1s and 0s (corresponding to live and dead cells), apply the rules to each cell, and return the next generation. ================================================ FILE: exercises/practice/game-of-life/.docs/introduction.md ================================================ # Introduction [Conway's Game of Life][game-of-life] is a fascinating cellular automaton created by the British mathematician John Horton Conway in 1970. The game consists of a two-dimensional grid of cells that can either be "alive" or "dead." After each generation, the cells interact with their eight neighbors via a set of rules, which define the new generation. [game-of-life]: https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life ================================================ FILE: exercises/practice/game-of-life/.meta/config.json ================================================ { "authors": [ "BNAndras" ], "files": { "solution": [ "game-of-life.ts" ], "test": [ "game-of-life.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Implement Conway's Game of Life.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life" } ================================================ FILE: exercises/practice/game-of-life/.meta/proof.ci.ts ================================================ export class GameOfLife { constructor(private matrix: number[][]) {} public tick(): void { if (this.matrix.length === 0) { return } const rows = this.matrix.length const cols = this.matrix[0].length const newMatrix = JSON.parse(JSON.stringify(this.matrix)) as number[][] for (let row = 0; row < rows; row++) { for (let col = 0; col < cols; col++) { let liveNeighbors = 0 for (let newRow = row - 1; newRow <= row + 1; newRow++) { for (let newCol = col - 1; newCol <= col + 1; newCol++) { if (newRow === row && newCol === col) { continue } if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols) { liveNeighbors += this.matrix[newRow][newCol] } } } let cell = this.matrix[row][col] if (cell === 1) { if (liveNeighbors < 2 || liveNeighbors > 3) { cell = 0 } } else { if (liveNeighbors === 3) { cell = 1 } } newMatrix[row][col] = cell } } this.matrix = newMatrix } public state(): number[][] { return this.matrix } } ================================================ FILE: exercises/practice/game-of-life/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [ae86ea7d-bd07-4357-90b3-ac7d256bd5c5] description = "empty matrix" [4ea5ccb7-7b73-4281-954a-bed1b0f139a5] description = "live cells with zero live neighbors die" [df245adc-14ff-4f9c-b2ae-f465ef5321b2] description = "live cells with only one live neighbor die" [2a713b56-283c-48c8-adae-1d21306c80ae] description = "live cells with two live neighbors stay alive" [86d5c5a5-ab7b-41a1-8907-c9b3fc5e9dae] description = "live cells with three live neighbors stay alive" [015f60ac-39d8-4c6c-8328-57f334fc9f89] description = "dead cells with three live neighbors become alive" [2ee69c00-9d41-4b8b-89da-5832e735ccf1] description = "live cells with four or more neighbors die" [a79b42be-ed6c-4e27-9206-43da08697ef6] description = "bigger matrix" ================================================ FILE: exercises/practice/game-of-life/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/game-of-life/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/game-of-life/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/game-of-life/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/game-of-life/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/game-of-life/game-of-life.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { GameOfLife } from './game-of-life.ts' describe('Game of Life', () => { // Empty matrix it('empty matrix', () => { const matrix: number[][] = [] const game = new GameOfLife(matrix) game.tick() const expected: number[][] = [] expect(game.state()).toEqual(expected) }) // Live cells with zero live neighbors die xit('live cells with zero live neighbors die', () => { const matrix = [ [0, 0, 0], [0, 1, 0], [0, 0, 0], ] const game = new GameOfLife(matrix) game.tick() const expected = [ [0, 0, 0], [0, 0, 0], [0, 0, 0], ] expect(game.state()).toEqual(expected) }) // Live cells with only one live neighbor die xit('live cells with only one live neighbor die', () => { const matrix = [ [0, 0, 0], [0, 1, 0], [0, 1, 0], ] const game = new GameOfLife(matrix) game.tick() const expected = [ [0, 0, 0], [0, 0, 0], [0, 0, 0], ] expect(game.state()).toEqual(expected) }) // Live cells with two live neighbors stay alive xit('live cells with two live neighbors stay alive', () => { const matrix = [ [1, 0, 1], [1, 0, 1], [1, 0, 1], ] const game = new GameOfLife(matrix) game.tick() const expected = [ [0, 0, 0], [1, 0, 1], [0, 0, 0], ] expect(game.state()).toEqual(expected) }) // Live cells with three live neighbors stay alive xit('live cells with three live neighbors stay alive', () => { const matrix = [ [0, 1, 0], [1, 0, 0], [1, 1, 0], ] const game = new GameOfLife(matrix) game.tick() const expected = [ [0, 0, 0], [1, 0, 0], [1, 1, 0], ] expect(game.state()).toEqual(expected) }) // Dead cells with three live neighbors become alive xit('dead cells with three live neighbors become alive', () => { const matrix = [ [1, 1, 0], [0, 0, 0], [1, 0, 0], ] const game = new GameOfLife(matrix) game.tick() const expected = [ [0, 0, 0], [1, 1, 0], [0, 0, 0], ] expect(game.state()).toEqual(expected) }) // Live cells with four or more neighbors die xit('live cells with four or more neighbors die', () => { const matrix = [ [1, 1, 1], [1, 1, 1], [1, 1, 1], ] const game = new GameOfLife(matrix) game.tick() const expected = [ [1, 0, 1], [0, 0, 0], [1, 0, 1], ] expect(game.state()).toEqual(expected) }) // Bigger matrix xit('bigger matrix', () => { const matrix = [ [1, 1, 0, 1, 1, 0, 0, 0], [1, 0, 1, 1, 0, 0, 0, 0], [1, 1, 1, 0, 0, 1, 1, 1], [0, 0, 0, 0, 0, 1, 1, 0], [1, 0, 0, 0, 1, 1, 0, 0], [1, 1, 0, 0, 0, 1, 1, 1], [0, 0, 1, 0, 1, 0, 0, 1], [1, 0, 0, 0, 0, 0, 1, 1], ] const game = new GameOfLife(matrix) game.tick() const expected = [ [1, 1, 0, 1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 1, 0], [1, 0, 1, 1, 1, 1, 0, 1], [1, 0, 0, 0, 0, 0, 0, 1], [1, 1, 0, 0, 1, 0, 0, 1], [1, 1, 0, 1, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 1], ] expect(game.state()).toEqual(expected) }) }) ================================================ FILE: exercises/practice/game-of-life/game-of-life.ts ================================================ export class GameOfLife { constructor(matrix: unknown) { throw new Error('Remove this line and implement the function') } public tick(): unknown { throw new Error('Remove this line and implement the function') } public state(): unknown { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/game-of-life/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/game-of-life/package.json ================================================ { "name": "@exercism/typescript-game-of-life", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/game-of-life/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/game-of-life/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/gigasecond/.docs/instructions.md ================================================ # Instructions Your task is to determine the date and time one gigasecond after a certain date. A gigasecond is one thousand million seconds. That is a one with nine zeros after it. If you were born on _January 24th, 2015 at 22:00 (10:00:00pm)_, then you would be a gigasecond old on _October 2nd, 2046 at 23:46:40 (11:46:40pm)_. ================================================ FILE: exercises/practice/gigasecond/.docs/introduction.md ================================================ # Introduction The way we measure time is kind of messy. We have 60 seconds in a minute, and 60 minutes in an hour. This comes from ancient Babylon, where they used 60 as the basis for their number system. We have 24 hours in a day, 7 days in a week, and how many days in a month? Well, for days in a month it depends not only on which month it is, but also on what type of calendar is used in the country you live in. What if, instead, we only use seconds to express time intervals? Then we can use metric system prefixes for writing large numbers of seconds in more easily comprehensible quantities. - A food recipe might explain that you need to let the brownies cook in the oven for two kiloseconds (that's two thousand seconds). - Perhaps you and your family would travel to somewhere exotic for two megaseconds (that's two million seconds). - And if you and your spouse were married for _a thousand million_ seconds, you would celebrate your one gigasecond anniversary. ~~~~exercism/note If we ever colonize Mars or some other planet, measuring time is going to get even messier. If someone says "year" do they mean a year on Earth or a year on Mars? The idea for this exercise came from the science fiction novel ["A Deepness in the Sky"][vinge-novel] by author Vernor Vinge. In it the author uses the metric system as the basis for time measurements. [vinge-novel]: https://www.tor.com/2017/08/03/science-fiction-with-something-for-everyone-a-deepness-in-the-sky-by-vernor-vinge/ ~~~~ ================================================ FILE: exercises/practice/gigasecond/.meta/config.json ================================================ { "authors": [ "masters3d" ], "contributors": [ "lukaszklis", "pranasziaukas", "SleeplessByte" ], "files": { "solution": [ "gigasecond.ts" ], "test": [ "gigasecond.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a moment, determine the moment that would be after a gigasecond has passed.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Chapter 9 in Chris Pine's online Learn to Program tutorial.", "source_url": "https://pine.fm/LearnToProgram/chap_09.html" } ================================================ FILE: exercises/practice/gigasecond/.meta/proof.ci.ts ================================================ const gigamilisecond = 10 ** 12 export class Gigasecond { constructor(private readonly currentDate: Readonly) {} public date(): Date { return new Date(this.currentDate.valueOf() + gigamilisecond) } } ================================================ FILE: exercises/practice/gigasecond/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [92fbe71c-ea52-4fac-bd77-be38023cacf7] description = "date only specification of time" [6d86dd16-6f7a-47be-9e58-bb9fb2ae1433] description = "second test for date only specification of time" [77eb8502-2bca-4d92-89d9-7b39ace28dd5] description = "third test for date only specification of time" [c9d89a7d-06f8-4e28-a305-64f1b2abc693] description = "full time specified" [09d4e30e-728a-4b52-9005-be44a58d9eba] description = "full time with day roll-over" [fcec307c-7529-49ab-b0fe-20309197618a] description = "does not mutate the input" ================================================ FILE: exercises/practice/gigasecond/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/gigasecond/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/gigasecond/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/gigasecond/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/gigasecond/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/gigasecond/gigasecond.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { Gigasecond } from './gigasecond.ts' describe('Gigasecond', () => { it('date only specification of time', () => { const gs = new Gigasecond(new Date(Date.parse('2011-04-25'))) const expectedDate = new Date(Date.parse('2043-01-01T01:46:40Z')) expect(gs.date()).toEqual(expectedDate) }) xit('second test for date only specification of time', () => { const gs = new Gigasecond(new Date(Date.parse('1977-06-13'))) const expectedDate = new Date(Date.parse('2009-02-19T01:46:40Z')) expect(gs.date()).toEqual(expectedDate) }) xit('third test for date only specification of time', () => { const gs = new Gigasecond(new Date(Date.parse('1959-07-19'))) const expectedDate = new Date(Date.parse('1991-03-27T01:46:40Z')) expect(gs.date()).toEqual(expectedDate) }) xit('full time specified', () => { const gs = new Gigasecond(new Date(Date.parse('2015-01-24T22:00:00Z'))) const expectedDate = new Date(Date.parse('2046-10-02T23:46:40Z')) expect(gs.date()).toEqual(expectedDate) }) xit('full time with day roll-over', () => { const gs = new Gigasecond(new Date(Date.parse('2015-01-24T23:59:59Z'))) const expectedDate = new Date(Date.parse('2046-10-03T01:46:39Z')) expect(gs.date()).toEqual(expectedDate) }) xit('does not mutate the input', () => { const gs = new Gigasecond(new Date(Date.parse('2015-01-24T23:59:59Z'))) const expectedDate = new Date(Date.parse('2046-10-03T01:46:39Z')) gs.date() expect(gs.date()).toEqual(expectedDate) }) }) ================================================ FILE: exercises/practice/gigasecond/gigasecond.ts ================================================ export class Gigasecond { public date(/* Parameters go here */) { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/gigasecond/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/gigasecond/package.json ================================================ { "name": "@exercism/typescript-gigasecond", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/gigasecond/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/gigasecond/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/grade-school/.docs/instructions.md ================================================ # Instructions Given students' names along with the grade they are in, create a roster for the school. In the end, you should be able to: - Add a student's name to the roster for a grade: - "Add Jim to grade 2." - "OK." - Get a list of all students enrolled in a grade: - "Which students are in grade 2?" - "We've only got Jim right now." - Get a sorted list of all students in all grades. Grades should be sorted as 1, 2, 3, etc., and students within a grade should be sorted alphabetically by name. - "Who is enrolled in school right now?" - "Let me think. We have Anna, Barb, and Charlie in grade 1, Alex, Peter, and Zoe in grade 2, and Jim in grade 5. So the answer is: Anna, Barb, Charlie, Alex, Peter, Zoe, and Jim." Note that all our students only have one name (it's a small town, what do you want?), and each student cannot be added more than once to a grade or the roster. If a test attempts to add the same student more than once, your implementation should indicate that this is incorrect. ================================================ FILE: exercises/practice/grade-school/.meta/config.json ================================================ { "authors": [ "masters3d" ], "contributors": [ "lukaszklis", "pedrorolo", "SleeplessByte" ], "files": { "solution": [ "grade-school.ts" ], "test": [ "grade-school.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given students' names along with the grade that they are in, create a roster for the school.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "A pairing session with Phil Battos at gSchool" } ================================================ FILE: exercises/practice/grade-school/.meta/proof.ci.ts ================================================ type Student = string type Grade = number type StudentRooster = Record type StudentGrades = Map export class GradeSchool { private students: StudentGrades constructor() { this.students = new Map() } public add(student: Student, level: Grade): void { this.students.set(student, level) } public grade(level: Grade): Student[] { return Array.from(this.students.entries()) .filter(([, studentGrade]) => studentGrade === level) .map(([student]) => student) .sort() } public roster(): StudentRooster { const result: StudentRooster = {} Array.from(this.students.entries()).forEach(([, studentGrade]) => { if (!result[studentGrade]) { result[studentGrade] = this.grade(studentGrade) } }) return result } } ================================================ FILE: exercises/practice/grade-school/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [6d0a30e4-1b4e-472e-8e20-c41702125667] description = "Adding a student adds them to the sorted roster" [233be705-dd58-4968-889d-fb3c7954c9cc] description = "Adding more student adds them to the sorted roster" [75a51579-d1d7-407c-a2f8-2166e984e8ab] description = "Adding students to different grades adds them to the same sorted roster" [a3f0fb58-f240-4723-8ddc-e644666b85cc] description = "Roster returns an empty list if there are no students enrolled" [180a8ff9-5b94-43fc-9db1-d46b4a8c93b6] description = "Student names with grades are displayed in the same sorted roster" [1bfbcef1-e4a3-49e8-8d22-f6f9f386187e] description = "Grade returns the students in that grade in alphabetical order" [5e67aa3c-a3c6-4407-a183-d8fe59cd1630] description = "Grade returns an empty list if there are no students in that grade" ================================================ FILE: exercises/practice/grade-school/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/grade-school/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/grade-school/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/grade-school/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/grade-school/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/grade-school/grade-school.test.ts ================================================ import { beforeEach, describe, it, expect, xit } from '@jest/globals' import { GradeSchool } from './grade-school.ts' describe('School', () => { let school: GradeSchool beforeEach(() => { school = new GradeSchool() }) it('a new school has an empty roster', () => { expect(school.roster()).toEqual({}) }) xit('adding a student adds them to the roster for the given grade', () => { school.add('Aimee', 2) const expectedDb = { 2: ['Aimee'] } expect(school.roster()).toEqual(expectedDb) }) xit('adding more students to the same grade adds them to the roster', () => { school.add('Blair', 2) school.add('James', 2) school.add('Paul', 2) const expectedDb = { 2: ['Blair', 'James', 'Paul'] } expect(school.roster()).toEqual(expectedDb) }) xit('adding students to different grades adds them to the roster', () => { school.add('Chelsea', 3) school.add('Logan', 7) const expectedDb = { 3: ['Chelsea'], 7: ['Logan'] } expect(school.roster()).toEqual(expectedDb) }) xit('grade returns the students in that grade in alphabetical order', () => { school.add('Franklin', 5) school.add('Bradley', 5) school.add('Jeff', 1) const expectedStudents = ['Bradley', 'Franklin'] expect(school.grade(5)).toEqual(expectedStudents) }) xit('grade returns an empty array if there are no students in that grade', () => { expect(school.grade(1)).toEqual([]) }) xit('the students names in each grade in the roster are sorted', () => { school.add('Jennifer', 4) school.add('Kareem', 6) school.add('Christopher', 4) school.add('Kyle', 3) const expectedSortedStudents = { 3: ['Kyle'], 4: ['Christopher', 'Jennifer'], 6: ['Kareem'], } expect(school.roster()).toEqual(expectedSortedStudents) }) xit('roster cannot be modified outside of module', () => { school.add('Aimee', 2) const roster = school.roster() try { roster[2].push('Oops.') } catch { /* empty */ } const expectedDb = { 2: ['Aimee'] } expect(school.roster()).toEqual(expectedDb) }) xit('roster cannot be modified outside of module using grade()', () => { school.add('Aimee', 2) try { school.grade(2).push('Oops.') } catch { /* empty */ } const expectedDb = { 2: ['Aimee'] } expect(school.roster()).toEqual(expectedDb) }) xit("a student can't be in two different grades", () => { school.add('Aimee', 2) school.add('Aimee', 1) expect(school.grade(2)).toEqual([]) }) }) ================================================ FILE: exercises/practice/grade-school/grade-school.ts ================================================ export class GradeSchool { roster() { throw new Error('Remove this line and implement the function') } add() { throw new Error('Remove this line and implement the function') } grade() { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/grade-school/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/grade-school/package.json ================================================ { "name": "@exercism/typescript-grade-school", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/grade-school/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/grade-school/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/grains/.docs/instructions.md ================================================ # Instructions Calculate the number of grains of wheat on a chessboard. A chessboard has 64 squares. Square 1 has one grain, square 2 has two grains, square 3 has four grains, and so on, doubling each time. Write code that calculates: - the number of grains on a given square - the total number of grains on the chessboard ================================================ FILE: exercises/practice/grains/.docs/introduction.md ================================================ # Introduction There once was a wise servant who saved the life of a prince. The king promised to pay whatever the servant could dream up. Knowing that the king loved chess, the servant told the king he would like to have grains of wheat. One grain on the first square of a chessboard, with the number of grains doubling on each successive square. ================================================ FILE: exercises/practice/grains/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "G-Rath", "masters3d", "SleeplessByte", "JoshiRaez" ], "files": { "solution": [ "grains.ts" ], "test": [ "grains.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Calculate the number of grains of wheat on a chessboard given that the number on each square doubles.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "The CodeRanch Cattle Drive, Assignment 6", "source_url": "https://web.archive.org/web/20240908084142/https://coderanch.com/wiki/718824/Grains" } ================================================ FILE: exercises/practice/grains/.meta/proof.ci.ts ================================================ export function square(n: number): bigint { if (n <= 0 || n >= 65) { throw new Error() } return 2n ** BigInt(n - 1) } export function total(): bigint { let result = 0n for (let i = 1; i <= 64; i++) { result += square(i) } return result } ================================================ FILE: exercises/practice/grains/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [9fbde8de-36b2-49de-baf2-cd42d6f28405] description = "1" [ee1f30c2-01d8-4298-b25d-c677331b5e6d] description = "2" [10f45584-2fc3-4875-8ec6-666065d1163b] description = "3" [a7cbe01b-36f4-4601-b053-c5f6ae055170] description = "4" [c50acc89-8535-44e4-918f-b848ad2817d4] description = "16" [acd81b46-c2ad-4951-b848-80d15ed5a04f] description = "32" [c73b470a-5efb-4d53-9ac6-c5f6487f227b] description = "64" [1d47d832-3e85-4974-9466-5bd35af484e3] description = "square 0 raises an exception" [61974483-eeb2-465e-be54-ca5dde366453] description = "negative square raises an exception" [a95e4374-f32c-45a7-a10d-ffec475c012f] description = "square greater than 64 raises an exception" [6eb07385-3659-4b45-a6be-9dc474222750] description = "returns the total number of grains on the board" ================================================ FILE: exercises/practice/grains/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/grains/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/grains/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/grains/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/grains/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/grains/grains.test.ts ================================================ import { describe, xdescribe, it, expect, xit } from '@jest/globals' import { square, total } from './grains.ts' describe('returns the number of grains on the square', () => { it('1', () => { const expected = 1n expect(square(1)).toEqual(expected) }) xit('2', () => { const expected = 2n expect(square(2)).toEqual(expected) }) xit('3', () => { const expected = 4n expect(square(3)).toEqual(expected) }) xit('4', () => { const expected = 8n expect(square(4)).toEqual(expected) }) xit('16', () => { const expected = 32768n expect(square(16)).toEqual(expected) }) xit('32', () => { const expected = 2147483648n expect(square(32)).toEqual(expected) }) xit('64', () => { const expected = 9223372036854775808n expect(square(64)).toEqual(expected) }) xit('square 0 raises an exception', () => { expect(() => square(0)).toThrow() }) xit('negative square raises an exception', () => { expect(() => square(-1)).toThrow() }) xit('square greater than 64 raises an exception', () => { expect(() => square(65)).toThrow() }) }) xdescribe('returns the total number of grains on the board', () => { xit('total', () => { const expected = 18446744073709551615n expect(total()).toEqual(expected) }) }) ================================================ FILE: exercises/practice/grains/grains.ts ================================================ export const square = () => { throw new Error('Remove this line and implement the function') } export const total = () => { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/grains/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/grains/package.json ================================================ { "name": "@exercism/typescript-grains", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/grains/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/grains/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/hamming/.docs/instructions.md ================================================ # Instructions Calculate the Hamming distance between two DNA strands. We read DNA using the letters C, A, G and T. Two strands might look like this: GAGCCTACTAACGGGAT CATCGTAATGACGGCCT ^ ^ ^ ^ ^ ^^ They have 7 differences, and therefore the Hamming distance is 7. ## Implementation notes The Hamming distance is only defined for sequences of equal length, so an attempt to calculate it between sequences of different lengths should not work. ================================================ FILE: exercises/practice/hamming/.docs/introduction.md ================================================ # Introduction Your body is made up of cells that contain DNA. Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells. In fact, the average human body experiences about 10 quadrillion cell divisions in a lifetime! When cells divide, their DNA replicates too. Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information. If we compare two strands of DNA and count the differences between them, we can see how many mistakes occurred. This is known as the "Hamming distance". The Hamming distance is useful in many areas of science, not just biology, so it's a nice phrase to be familiar with :) ================================================ FILE: exercises/practice/hamming/.meta/config.json ================================================ { "authors": [ "masters3d" ], "contributors": [ "lukaszklis", "SleeplessByte" ], "files": { "solution": [ "hamming.ts" ], "test": [ "hamming.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Calculate the Hamming distance between two DNA strands.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "The Calculating Point Mutations problem at Rosalind", "source_url": "https://rosalind.info/problems/hamm/" } ================================================ FILE: exercises/practice/hamming/.meta/proof.ci.ts ================================================ export function compute(left: string, right: string): number { if (left.length !== right.length) { throw new Error('DNA strands must be of equal length.') } const size = left.length const leftArray = left.split('') const rightArray = right.split('') let count = 0 for (let i = 0; i < size; i += 1) { if (leftArray[i] !== rightArray[i]) { count += 1 } } return count } ================================================ FILE: exercises/practice/hamming/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [f6dcb64f-03b0-4b60-81b1-3c9dbf47e887] description = "empty strands" [54681314-eee2-439a-9db0-b0636c656156] description = "single letter identical strands" [294479a3-a4c8-478f-8d63-6209815a827b] description = "single letter different strands" [9aed5f34-5693-4344-9b31-40c692fb5592] description = "long identical strands" [cd2273a5-c576-46c8-a52b-dee251c3e6e5] description = "long different strands" [919f8ef0-b767-4d1b-8516-6379d07fcb28] description = "disallow first strand longer" include = false [b9228bb1-465f-4141-b40f-1f99812de5a8] description = "disallow first strand longer" reimplements = "919f8ef0-b767-4d1b-8516-6379d07fcb28" [8a2d4ed0-ead5-4fdd-924d-27c4cf56e60e] description = "disallow second strand longer" include = false [dab38838-26bb-4fff-acbe-3b0a9bfeba2d] description = "disallow second strand longer" reimplements = "8a2d4ed0-ead5-4fdd-924d-27c4cf56e60e" [5dce058b-28d4-4ca7-aa64-adfe4e17784c] description = "disallow left empty strand" include = false [db92e77e-7c72-499d-8fe6-9354d2bfd504] description = "disallow left empty strand" include = false reimplements = "5dce058b-28d4-4ca7-aa64-adfe4e17784c" [b764d47c-83ff-4de2-ab10-6cfe4b15c0f3] description = "disallow empty first strand" reimplements = "db92e77e-7c72-499d-8fe6-9354d2bfd504" [38826d4b-16fb-4639-ac3e-ba027dec8b5f] description = "disallow right empty strand" include = false [920cd6e3-18f4-4143-b6b8-74270bb8f8a3] description = "disallow right empty strand" include = false reimplements = "38826d4b-16fb-4639-ac3e-ba027dec8b5f" [9ab9262f-3521-4191-81f5-0ed184a5aa89] description = "disallow empty second strand" reimplements = "920cd6e3-18f4-4143-b6b8-74270bb8f8a3" ================================================ FILE: exercises/practice/hamming/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/hamming/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/hamming/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/hamming/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/hamming/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/hamming/hamming.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { compute } from './hamming.ts' describe('Hamming', () => { it('empty strands', () => { expect(compute('', '')).toEqual(0) }) xit('single letter identical strands', () => { expect(compute('A', 'A')).toEqual(0) }) xit('single letter different strands', () => { expect(compute('G', 'T')).toEqual(1) }) xit('long identical strands', () => { expect(compute('GGACTGAAATCTG', 'GGACTGAAATCTG')).toEqual(0) }) xit('long different strands', () => { expect(compute('GGACGGATTCTG', 'AGGACGGATTCT')).toEqual(9) }) xit('disallow first strand longer', () => { expect(() => { compute('AATG', 'AAA') }).toThrow('DNA strands must be of equal length.') }) xit('disallow second strand longer', () => { expect(() => { compute('ATA', 'AGTG') }).toThrow('DNA strands must be of equal length.') }) xit('disallow empty first strand', () => { expect(() => { compute('', 'G') }).toThrow('DNA strands must be of equal length.') }) xit('disallow empty second strand', () => { expect(() => { compute('G', '') }).toThrow('DNA strands must be of equal length.') }) }) ================================================ FILE: exercises/practice/hamming/hamming.ts ================================================ export function compute(left: unknown, right: unknown): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/hamming/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/hamming/package.json ================================================ { "name": "@exercism/typescript-hamming", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/hamming/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/hamming/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/hello-world/.docs/instructions.append.md ================================================ # Instructions append ## Setup Go through the setup instructions for TypeScript to install the necessary dependencies: [https://exercism.org/docs/tracks/typescript/installation](https://exercism.org/docs/tracks/typescript/installation) ## Requirements Install assignment dependencies: ```bash $ yarn install ``` ## Making the test suite pass Execute the tests with: ```bash $ yarn test ``` In many test suites all but the first test have been skipped. Once you get a test passing, you can unskip the next one by changing `xit` to `it`. ## Tutorial This section is a step-by-step guide to solving this exercise. This exercise has two files: - hello-world.ts - hello-world.test.ts The first file is where you will write your code. The second is where the tests are defined. The tests will check whether your code is doing the right thing. You don't need to be able to write a test suite from scratch, but it helps to understand what a test looks like, and what it is doing. Open up the test file, hello-world.test.ts. There is a single test inside: ```typescript it('says hello world', () => { expect(hello()).toEqual('Hello, World!') }) ``` Run the test now, with the following command on the command-line: ```bash $ yarn test ``` The test fails, which makes sense since you've not written any code yet. The failure looks like this: ``` × says hello world (5ms) ● Hello World › says hello world expect(received).toEqual(expected) // deep equality Expected: "Hello, World!" Received: "Goodbye, Mars!" 4 | 5 | it('says hello world', () => { > 6 | expect(hello()).toEqual('Hello, World!') | ^ 7 | }) 8 | 9 | }) at Object.it (hello-world.test.ts:6:32) ``` And these are those code lines with probable defects in the `hello-world.test.ts` file: the 6th line: ```typescript expect(hello()).toEqual('Hello, World!') ^ ``` Hence the problem is with the `hello()` function call. We can see that the test is expecting `'Hello, World!'` as output, but instead is getting `"Goodbye, Mars!"`. So let's check now this function in the `hello-worlds.ts` file: ```typescript export function hello(): string { return 'Goodbye, Mars!' } ``` Now we see that the function returns the incorrect string, which is the reason for our failure. Let's fix this by changing the returned value: ```typescript export function hello(): string { return 'Hello, World!' } ``` Run the test again: ```bash PASS ./hello-world.test.ts Hello World √ says hello world (4ms) ``` And it passes! ================================================ FILE: exercises/practice/hello-world/.docs/instructions.md ================================================ # Instructions The classical introductory exercise. Just say "Hello, World!". ["Hello, World!"][hello-world] is the traditional first program for beginning programming in a new language or environment. The objectives are simple: - Modify the provided code so that it produces the string "Hello, World!". - Run the test suite and make sure that it succeeds. - Submit your solution and check it at the website. If everything goes well, you will be ready to fetch your first real exercise. [hello-world]: https://en.wikipedia.org/wiki/%22Hello,_world!%22_program ================================================ FILE: exercises/practice/hello-world/.meta/config.json ================================================ { "authors": [ "masters3d" ], "contributors": [ "DFXLuna", "iHiD", "kytrinyx", "lukaszklis", "porkostomus", "SleeplessByte" ], "files": { "solution": [ "hello-world.ts" ], "test": [ "hello-world.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Exercism's classic introductory exercise. Just say \"Hello, World!\".", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "This is an exercise to introduce users to using Exercism", "source_url": "https://en.wikipedia.org/wiki/%22Hello,_world!%22_program" } ================================================ FILE: exercises/practice/hello-world/.meta/proof.ci.ts ================================================ export function hello(): string { return 'Hello, World!' } ================================================ FILE: exercises/practice/hello-world/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [af9ffe10-dc13-42d8-a742-e7bdafac449d] description = "Say Hi!" ================================================ FILE: exercises/practice/hello-world/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/hello-world/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/hello-world/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/hello-world/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/hello-world/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/hello-world/hello-world.test.ts ================================================ import { describe, it, expect } from '@jest/globals' import { hello } from './hello-world.ts' describe('Hello World', () => { it('says hello world', () => { expect(hello()).toEqual('Hello, World!') }) }) ================================================ FILE: exercises/practice/hello-world/hello-world.ts ================================================ export function hello(): string { return 'Goodbye, Mars!' } ================================================ FILE: exercises/practice/hello-world/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/hello-world/package.json ================================================ { "name": "@exercism/typescript-hello-world", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/hello-world/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/hello-world/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/house/.docs/instructions.md ================================================ # Instructions Recite the nursery rhyme 'This is the House that Jack Built'. > [The] process of placing a phrase of clause within another phrase of clause is called embedding. > It is through the processes of recursion and embedding that we are able to take a finite number of forms (words and phrases) and construct an infinite number of expressions. > Furthermore, embedding also allows us to construct an infinitely long structure, in theory anyway. - [papyr.com][papyr] The nursery rhyme reads as follows: ```text This is the house that Jack built. This is the malt that lay in the house that Jack built. This is the rat that ate the malt that lay in the house that Jack built. This is the cat that killed the rat that ate the malt that lay in the house that Jack built. This is the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built. This is the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built. This is the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built. This is the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built. This is the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built. This is the rooster that crowed in the morn that woke the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built. This is the farmer sowing his corn that kept the rooster that crowed in the morn that woke the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built. This is the horse and the hound and the horn that belonged to the farmer sowing his corn that kept the rooster that crowed in the morn that woke the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built. ``` [papyr]: https://papyr.com/hypertextbooks/grammar/ph_noun.htm ================================================ FILE: exercises/practice/house/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "house.ts" ], "test": [ "house.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Output the nursery rhyme 'This is the House that Jack Built'.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "British nursery rhyme", "source_url": "https://en.wikipedia.org/wiki/This_Is_The_House_That_Jack_Built" } ================================================ FILE: exercises/practice/house/.meta/proof.ci.ts ================================================ const OBJECTS = [ 'house', 'malt', 'rat', 'cat', 'dog', 'cow with the crumpled horn', 'maiden all forlorn', 'man all tattered and torn', 'priest all shaven and shorn', 'rooster that crowed in the morn', 'farmer sowing his corn', 'horse and the hound and the horn', ] as const const ACTIONS = [ 'Jack built', 'lay in', 'ate', 'killed', 'worried', 'tossed', 'milked', 'kissed', 'married', 'woke', 'kept', 'belonged to', ] as const export function verse(verseNumber: number): string[] { const lines = [] const totalLines = verseNumber let itemIndex = verseNumber - 1 for (let lineNumber = 1; lineNumber <= totalLines; lineNumber += 1) { let lineText = '' if (lineNumber === 1) { lineText += 'This is' } else { lineText += `that ${ACTIONS[itemIndex]}` itemIndex -= 1 } lineText += ` the ${OBJECTS[itemIndex]}` if (lineNumber === totalLines) { lineText += ` that ${ACTIONS[itemIndex]}.` } lines.push(lineText) } return lines } export function verses(start: number, end: number): string[] { let lines: string[] = [] for (let i = start; i <= end; i += 1) { const verseLines = verse(i) lines = lines.concat(verseLines) if (i < end) { lines.push('') } } return lines } ================================================ FILE: exercises/practice/house/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [28a540ff-f765-4348-9d57-ae33f25f41f2] description = "verse one - the house that jack built" [ebc825ac-6e2b-4a5e-9afd-95732191c8da] description = "verse two - the malt that lay" [1ed8bb0f-edb8-4bd1-b6d4-b64754fe4a60] description = "verse three - the rat that ate" [64b0954e-8b7d-4d14-aad0-d3f6ce297a30] description = "verse four - the cat that killed" [1e8d56bc-fe31-424d-9084-61e6111d2c82] description = "verse five - the dog that worried" [6312dc6f-ab0a-40c9-8a55-8d4e582beac4] description = "verse six - the cow with the crumpled horn" [68f76d18-6e19-4692-819c-5ff6a7f92feb] description = "verse seven - the maiden all forlorn" [73872564-2004-4071-b51d-2e4326096747] description = "verse eight - the man all tattered and torn" [0d53d743-66cb-4351-a173-82702f3338c9] description = "verse nine - the priest all shaven and shorn" [452f24dc-8fd7-4a82-be1a-3b4839cfeb41] description = "verse 10 - the rooster that crowed in the morn" [97176f20-2dd3-4646-ac72-cffced91ea26] description = "verse 11 - the farmer sowing his corn" [09824c29-6aad-4dcd-ac98-f61374a6a8b7] description = "verse 12 - the horse and the hound and the horn" [d2b980d3-7851-49e1-97ab-1524515ec200] description = "multiple verses" [0311d1d0-e085-4f23-8ae7-92406fb3e803] description = "full rhyme" ================================================ FILE: exercises/practice/house/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/house/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/house/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/house/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/house/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/house/house.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { verse, verses } from './house.ts' describe('House', () => { it('verse one - the house that jack built', () => { const lyrics = ['This is the house that Jack built.'] expect(verse(1)).toEqual(lyrics) }) xit('verse two - the malt that lay', () => { const lyrics = [ 'This is the malt', 'that lay in the house that Jack built.', ] expect(verse(2)).toEqual(lyrics) }) xit('verse three - the rat that ate', () => { const lyrics = [ 'This is the rat', 'that ate the malt', 'that lay in the house that Jack built.', ] expect(verse(3)).toEqual(lyrics) }) xit('verse four - the cat that killed', () => { const lyrics = [ 'This is the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', ] expect(verse(4)).toEqual(lyrics) }) xit('verse five - the dog that worried', () => { const lyrics = [ 'This is the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', ] expect(verse(5)).toEqual(lyrics) }) xit('verse six - the cow with the crumpled horn', () => { const lyrics = [ 'This is the cow with the crumpled horn', 'that tossed the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', ] expect(verse(6)).toEqual(lyrics) }) xit('verse seven - the maiden all forlorn', () => { const lyrics = [ 'This is the maiden all forlorn', 'that milked the cow with the crumpled horn', 'that tossed the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', ] expect(verse(7)).toEqual(lyrics) }) xit('verse eight - the man all tattered and torn', () => { const lyrics = [ 'This is the man all tattered and torn', 'that kissed the maiden all forlorn', 'that milked the cow with the crumpled horn', 'that tossed the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', ] expect(verse(8)).toEqual(lyrics) }) xit('verse nine - the priest all shaven and shorn', () => { const lyrics = [ 'This is the priest all shaven and shorn', 'that married the man all tattered and torn', 'that kissed the maiden all forlorn', 'that milked the cow with the crumpled horn', 'that tossed the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', ] expect(verse(9)).toEqual(lyrics) }) xit('verse ten - the rooster that crowed in the morn', () => { const lyrics = [ 'This is the rooster that crowed in the morn', 'that woke the priest all shaven and shorn', 'that married the man all tattered and torn', 'that kissed the maiden all forlorn', 'that milked the cow with the crumpled horn', 'that tossed the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', ] expect(verse(10)).toEqual(lyrics) }) xit('verse eleven - the farmer sowing his corn', () => { const lyrics = [ 'This is the farmer sowing his corn', 'that kept the rooster that crowed in the morn', 'that woke the priest all shaven and shorn', 'that married the man all tattered and torn', 'that kissed the maiden all forlorn', 'that milked the cow with the crumpled horn', 'that tossed the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', ] expect(verse(11)).toEqual(lyrics) }) xit('verse twelve - the horse and the hound and the horn', () => { const lyrics = [ 'This is the horse and the hound and the horn', 'that belonged to the farmer sowing his corn', 'that kept the rooster that crowed in the morn', 'that woke the priest all shaven and shorn', 'that married the man all tattered and torn', 'that kissed the maiden all forlorn', 'that milked the cow with the crumpled horn', 'that tossed the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', ] expect(verse(12)).toEqual(lyrics) }) xit('multiple verses', () => { const startVerse = 4 const endVerse = 8 const lyrics = [ 'This is the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', '', 'This is the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', '', 'This is the cow with the crumpled horn', 'that tossed the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', '', 'This is the maiden all forlorn', 'that milked the cow with the crumpled horn', 'that tossed the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', '', 'This is the man all tattered and torn', 'that kissed the maiden all forlorn', 'that milked the cow with the crumpled horn', 'that tossed the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', ] expect(verses(startVerse, endVerse)).toEqual(lyrics) }) xit('full rhyme', () => { const startVerse = 1 const endVerse = 12 const lyrics = [ 'This is the house that Jack built.', '', 'This is the malt', 'that lay in the house that Jack built.', '', 'This is the rat', 'that ate the malt', 'that lay in the house that Jack built.', '', 'This is the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', '', 'This is the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', '', 'This is the cow with the crumpled horn', 'that tossed the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', '', 'This is the maiden all forlorn', 'that milked the cow with the crumpled horn', 'that tossed the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', '', 'This is the man all tattered and torn', 'that kissed the maiden all forlorn', 'that milked the cow with the crumpled horn', 'that tossed the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', '', 'This is the priest all shaven and shorn', 'that married the man all tattered and torn', 'that kissed the maiden all forlorn', 'that milked the cow with the crumpled horn', 'that tossed the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', '', 'This is the rooster that crowed in the morn', 'that woke the priest all shaven and shorn', 'that married the man all tattered and torn', 'that kissed the maiden all forlorn', 'that milked the cow with the crumpled horn', 'that tossed the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', '', 'This is the farmer sowing his corn', 'that kept the rooster that crowed in the morn', 'that woke the priest all shaven and shorn', 'that married the man all tattered and torn', 'that kissed the maiden all forlorn', 'that milked the cow with the crumpled horn', 'that tossed the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', '', 'This is the horse and the hound and the horn', 'that belonged to the farmer sowing his corn', 'that kept the rooster that crowed in the morn', 'that woke the priest all shaven and shorn', 'that married the man all tattered and torn', 'that kissed the maiden all forlorn', 'that milked the cow with the crumpled horn', 'that tossed the dog', 'that worried the cat', 'that killed the rat', 'that ate the malt', 'that lay in the house that Jack built.', ] expect(verses(startVerse, endVerse)).toEqual(lyrics) }) }) ================================================ FILE: exercises/practice/house/house.ts ================================================ export function verse(/* parameters go here */): unknown { throw new Error('Remove this line and implement the function') } export function verses(/* parameters go here */): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/house/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/house/package.json ================================================ { "name": "@exercism/typescript-house", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/house/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/house/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/isbn-verifier/.docs/instructions.md ================================================ # Instructions The [ISBN-10 verification process][isbn-verification] is used to validate book identification numbers. These normally contain dashes and look like: `3-598-21508-8` ## ISBN The ISBN-10 format is 9 digits (0 to 9) plus one check character (either a digit or an X only). In the case the check character is an X, this represents the value '10'. These may be communicated with or without hyphens, and can be checked for their validity by the following formula: ```text (d₁ * 10 + d₂ * 9 + d₃ * 8 + d₄ * 7 + d₅ * 6 + d₆ * 5 + d₇ * 4 + d₈ * 3 + d₉ * 2 + d₁₀ * 1) mod 11 == 0 ``` If the result is 0, then it is a valid ISBN-10, otherwise it is invalid. ## Example Let's take the ISBN-10 `3-598-21508-8`. We plug it in to the formula, and get: ```text (3 * 10 + 5 * 9 + 9 * 8 + 8 * 7 + 2 * 6 + 1 * 5 + 5 * 4 + 0 * 3 + 8 * 2 + 8 * 1) mod 11 == 0 ``` Since the result is 0, this proves that our ISBN is valid. ## Task Given a string the program should check if the provided string is a valid ISBN-10. Putting this into place requires some thinking about preprocessing/parsing of the string prior to calculating the check digit for the ISBN. The program should be able to verify ISBN-10 both with and without separating dashes. ## Caveats Converting from strings to numbers can be tricky in certain languages. Now, it's even trickier since the check digit of an ISBN-10 may be 'X' (representing '10'). For instance `3-598-21507-X` is a valid ISBN-10. [isbn-verification]: https://en.wikipedia.org/wiki/International_Standard_Book_Number ================================================ FILE: exercises/practice/isbn-verifier/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "isbn-verifier.ts" ], "test": [ "isbn-verifier.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Check if a given string is a valid ISBN-10 number.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Converting a string into a number and some basic processing utilizing a relatable real world example.", "source_url": "https://en.wikipedia.org/wiki/International_Standard_Book_Number#ISBN-10_check_digit_calculation" } ================================================ FILE: exercises/practice/isbn-verifier/.meta/proof.ci.ts ================================================ export function isValid(input: string): boolean { const isbn = input.replace(/-/g, '') if (!isbn.match(/^(\d{9}[\dxX])$/)) { return false } const digits = [...isbn] if (digits[9].match(/[xX]/)) { digits[9] = '10' } const sum = digits.reduce( (acc, value, index) => acc + (10 - index) * parseInt(value, 10), 0 ) return sum % 11 === 0 } ================================================ FILE: exercises/practice/isbn-verifier/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [0caa3eac-d2e3-4c29-8df8-b188bc8c9292] description = "valid isbn" [19f76b53-7c24-45f8-87b8-4604d0ccd248] description = "invalid isbn check digit" [4164bfee-fb0a-4a1c-9f70-64c6a1903dcd] description = "valid isbn with a check digit of 10" [3ed50db1-8982-4423-a993-93174a20825c] description = "check digit is a character other than X" [9416f4a5-fe01-4b61-a07b-eb75892ef562] description = "invalid check digit in isbn is not treated as zero" [c19ba0c4-014f-4dc3-a63f-ff9aefc9b5ec] description = "invalid character in isbn is not treated as zero" [28025280-2c39-4092-9719-f3234b89c627] description = "X is only valid as a check digit" [8005b57f-f194-44ee-88d2-a77ac4142591] description = "only one check digit is allowed" [fdb14c99-4cf8-43c5-b06d-eb1638eff343] description = "X is not substituted by the value 10" [f6294e61-7e79-46b3-977b-f48789a4945b] description = "valid isbn without separating dashes" [185ab99b-3a1b-45f3-aeec-b80d80b07f0b] description = "isbn without separating dashes and X as check digit" [7725a837-ec8e-4528-a92a-d981dd8cf3e2] description = "isbn without check digit and dashes" [47e4dfba-9c20-46ed-9958-4d3190630bdf] description = "too long isbn and no dashes" [737f4e91-cbba-4175-95bf-ae630b41fb60] description = "too short isbn" [5458a128-a9b6-4ff8-8afb-674e74567cef] description = "isbn without check digit" [70b6ad83-d0a2-4ca7-a4d5-a9ab731800f7] description = "check digit of X should not be used for 0" [94610459-55ab-4c35-9b93-ff6ea1a8e562] description = "empty isbn" [7bff28d4-d770-48cc-80d6-b20b3a0fb46c] description = "input is 9 characters" [ed6e8d1b-382c-4081-8326-8b772c581fec] description = "invalid characters are not ignored after checking length" [daad3e58-ce00-4395-8a8e-e3eded1cdc86] description = "invalid characters are not ignored before checking length" [fb5e48d8-7c03-4bfb-a088-b101df16fdc3] description = "input is too long but contains a valid isbn" ================================================ FILE: exercises/practice/isbn-verifier/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/isbn-verifier/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/isbn-verifier/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/isbn-verifier/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/isbn-verifier/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/isbn-verifier/isbn-verifier.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { isValid } from './isbn-verifier.ts' describe('ISBN Verifier', () => { it('valid isbn', () => { expect(isValid('3-598-21508-8')).toBeTruthy() }) xit('invalid isbn check digit', () => { expect(isValid('3-598-21508-9')).toBeFalsy() }) xit('valid isbn with a check digit of 10', () => { expect(isValid('3-598-21507-X')).toBeTruthy() }) xit('check digit is a character other than X', () => { expect(isValid('3-598-21507-A')).toBeFalsy() }) xit('invalid check digit in isbn is not treated as zero', () => { expect(isValid('4-598-21507-B')).toBeFalsy() }) xit('invalid character in isbn is not treated as zero', () => { expect(isValid('3-598-2K507-0')).toBeFalsy() }) xit('X is only valid as a check digit', () => { expect(isValid('3-598-2X507-9')).toBeFalsy() }) xit('only one check digit is allowed', () => { expect(isValid('3-598-21508-96')).toBeFalsy() }) xit('X is not substituted by the value 10', () => { expect(isValid('3-598-2X507-5')).toBeFalsy() }) xit('valid isbn without separating dashes', () => { expect(isValid('3598215088')).toBeTruthy() }) xit('isbn without separating dashes and X as check digit', () => { expect(isValid('359821507X')).toBeTruthy() }) xit('isbn without check digit and dashes', () => { expect(isValid('359821507')).toBeFalsy() }) xit('too long isbn', () => { expect(isValid('3-598-21507-XX')).toBeFalsy() }) xit('too long isbn and no dashes', () => { expect(isValid('3598215078X')).toBeFalsy() }) xit('too short isbn', () => { expect(isValid('00')).toBeFalsy() }) xit('isbn without check digit', () => { expect(isValid('3-598-21507')).toBeFalsy() }) xit('check digit of X should not be used for 0', () => { expect(isValid('3-598-21515-X')).toBeFalsy() }) xit('empty isbn', () => { expect(isValid('')).toBeFalsy() }) xit('input is 9 characters', () => { expect(isValid('134456729')).toBeFalsy() }) xit('invalid characters are not ignored after checking length', () => { expect(isValid('3132P34035')).toBeFalsy() }) xit('invalid characters are not ignored before checking length', () => { expect(isValid('3598P215088')).toBeFalsy() }) xit('input is too long but contains a valid isbn', () => { expect(isValid('98245726788')).toBeFalsy() }) }) ================================================ FILE: exercises/practice/isbn-verifier/isbn-verifier.ts ================================================ export function isValid(isbn: unknown): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/isbn-verifier/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/isbn-verifier/package.json ================================================ { "name": "@exercism/typescript-isbn-verifier", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/isbn-verifier/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/isbn-verifier/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/isogram/.docs/instructions.md ================================================ # Instructions Determine if a word or phrase is an isogram. An isogram (also known as a "non-pattern word") is a word or phrase without a repeating letter, however spaces and hyphens are allowed to appear multiple times. Examples of isograms: - lumberjacks - background - downstream - six-year-old The word _isograms_, however, is not an isogram, because the s repeats. ================================================ FILE: exercises/practice/isogram/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "isogram.ts" ], "test": [ "isogram.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Determine if a word or phrase is an isogram.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Isogram" } ================================================ FILE: exercises/practice/isogram/.meta/proof.ci.ts ================================================ export function isIsogram(phrase: string): boolean { const appearances = new Set() for (const letter of phrase) { if (isLetter(letter)) { if (appearances.has(letter.toLowerCase())) { return false } else { appearances.add(letter.toLowerCase()) } } else { continue } } return true } function isLetter(letter: string): boolean { if ( (letter.charCodeAt(0) >= 97 && letter.charCodeAt(0) <= 122) || (letter.charCodeAt(0) >= 65 && letter.charCodeAt(0) <= 90) ) { return true } else { return false } } ================================================ FILE: exercises/practice/isogram/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [a0e97d2d-669e-47c7-8134-518a1e2c4555] description = "empty string" [9a001b50-f194-4143-bc29-2af5ec1ef652] description = "isogram with only lower case characters" [8ddb0ca3-276e-4f8b-89da-d95d5bae78a4] description = "word with one duplicated character" [6450b333-cbc2-4b24-a723-0b459b34fe18] description = "word with one duplicated character from the end of the alphabet" [a15ff557-dd04-4764-99e7-02cc1a385863] description = "longest reported english isogram" [f1a7f6c7-a42f-4915-91d7-35b2ea11c92e] description = "word with duplicated character in mixed case" [14a4f3c1-3b47-4695-b645-53d328298942] description = "word with duplicated character in mixed case, lowercase first" [423b850c-7090-4a8a-b057-97f1cadd7c42] description = "hypothetical isogrammic word with hyphen" [93dbeaa0-3c5a-45c2-8b25-428b8eacd4f2] description = "hypothetical word with duplicated character following hyphen" [36b30e5c-173f-49c6-a515-93a3e825553f] description = "isogram with duplicated hyphen" [cdabafa0-c9f4-4c1f-b142-689c6ee17d93] description = "made-up name that is an isogram" [5fc61048-d74e-48fd-bc34-abfc21552d4d] description = "duplicated character in the middle" [310ac53d-8932-47bc-bbb4-b2b94f25a83e] description = "same first and last characters" [0d0b8644-0a1e-4a31-a432-2b3ee270d847] description = "word with duplicated character and with two hyphens" ================================================ FILE: exercises/practice/isogram/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/isogram/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/isogram/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/isogram/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/isogram/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/isogram/isogram.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { isIsogram } from './isogram.ts' describe('Check if the given string is an isogram', () => { it('empty string', () => { const expected = true expect(isIsogram('')).toEqual(expected) }) xit('isogram with only lower case characters', () => { const expected = true expect(isIsogram('isogram')).toEqual(expected) }) xit('word with one duplicated character', () => { const expected = false expect(isIsogram('eleven')).toEqual(expected) }) xit('word with one duplicated character from the end of the alphabet', () => { const expected = false expect(isIsogram('zzyzx')).toEqual(expected) }) xit('longest reported english isogram', () => { const expected = true expect(isIsogram('subdermatoglyphic')).toEqual(expected) }) xit('word with duplicated character in mixed case', () => { const expected = false expect(isIsogram('Alphabet')).toEqual(expected) }) xit('word with duplicated character in mixed case, lowercase first', () => { const expected = false expect(isIsogram('alphAbet')).toEqual(expected) }) xit('hypothetical isogrammic word with hyphen', () => { const expected = true expect(isIsogram('thumbscrew-japingly')).toEqual(expected) }) xit('hypothetical word with duplicated character following hyphen', () => { const expected = false expect(isIsogram('thumbscrew-jappingly')).toEqual(expected) }) xit('isogram with duplicated hyphen', () => { const expected = true expect(isIsogram('six-year-old')).toEqual(expected) }) xit('made-up name that is an isogram', () => { const expected = true expect(isIsogram('Emily Jung Schwartzkopf')).toEqual(expected) }) xit('duplicated character in the middle', () => { const expected = false expect(isIsogram('accentor')).toEqual(expected) }) xit('same first and last characters', () => { const expected = false expect(isIsogram('angola')).toEqual(expected) }) xit('word with duplicated character and with two hyphens', () => { const expected = false expect(isIsogram('up-to-date')).toEqual(expected) }) }) ================================================ FILE: exercises/practice/isogram/isogram.ts ================================================ export function isIsogram(/* parameters go here */): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/isogram/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/isogram/package.json ================================================ { "name": "@exercism/typescript-isogram", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/isogram/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/isogram/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/kindergarten-garden/.docs/instructions.md ================================================ # Instructions Your task is to, given a diagram, determine which plants each child in the kindergarten class is responsible for. There are 12 children in the class: - Alice, Bob, Charlie, David, Eve, Fred, Ginny, Harriet, Ileana, Joseph, Kincaid, and Larry. Four different types of seeds are planted: | Plant | Diagram encoding | | ------ | ---------------- | | Grass | G | | Clover | C | | Radish | R | | Violet | V | Each child gets four cups, two on each row: ```text [window][window][window] ........................ # each dot represents a cup ........................ ``` Their teacher assigns cups to the children alphabetically by their names, which means that Alice comes first and Larry comes last. Here is an example diagram representing Alice's plants: ```text [window][window][window] VR...................... RG...................... ``` In the first row, nearest the windows, she has a violet and a radish. In the second row she has a radish and some grass. Your program will be given the plants from left-to-right starting with the row nearest the windows. From this, it should be able to determine which plants belong to each student. For example, if it's told that the garden looks like so: ```text [window][window][window] VRCGVVRVCGGCCGVRGCVCGCGV VRCCCGCRRGVCGCRVVCVGCGCV ``` Then if asked for Alice's plants, it should provide: - Violets, radishes, violets, radishes While asking for Bob's plants would yield: - Clover, grass, clover, clover ================================================ FILE: exercises/practice/kindergarten-garden/.docs/introduction.md ================================================ # Introduction The kindergarten class is learning about growing plants. The teacher thought it would be a good idea to give the class seeds to plant and grow in the dirt. To this end, the children have put little cups along the window sills and planted one type of plant in each cup. The children got to pick their favorites from four available types of seeds: grass, clover, radishes, and violets. ================================================ FILE: exercises/practice/kindergarten-garden/.meta/config.json ================================================ { "authors": [ "fredrb" ], "contributors": [ "CRivasGomez", "masters3d", "SleeplessByte" ], "files": { "solution": [ "kindergarten-garden.ts" ], "test": [ "kindergarten-garden.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a diagram, determine which plants each child in the kindergarten class is responsible for.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", "source_url": "https://www.turing.edu/" } ================================================ FILE: exercises/practice/kindergarten-garden/.meta/proof.ci.ts ================================================ const DEFAULT_STUDENTS: Student[] = [ 'Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Fred', 'Ginny', 'Harriet', 'Ileana', 'Joseph', 'Kincaid', 'Larry', ] const PLANT_CODES = { G: 'grass', V: 'violets', R: 'radishes', C: 'clover', } as const type Student = string type Plant = (typeof PLANT_CODES)[keyof typeof PLANT_CODES] type Plants = Plant[] type Pots = Plants[] function getPlants(pots: Pots, index: number): Plants { const plants: Plants = [] const position = 2 * index plants.push(pots[0][position]) plants.push(pots[0][position + 1]) plants.push(pots[1][position]) plants.push(pots[1][position + 1]) return plants } function parse(diagram: string): Pots { return diagram .split('\n') .map((row) => [...row].map( (sign) => PLANT_CODES[sign as keyof typeof PLANT_CODES] as Plant ) ) } export class Garden { private plots: Record constructor( diagram: string, private students = DEFAULT_STUDENTS ) { this.students.sort() this.plots = {} this.students.forEach((student, index) => { this.plots[student] = getPlants(parse(diagram), index) }) } public plants(student: Student): Plants { return this.plots[student] } } ================================================ FILE: exercises/practice/kindergarten-garden/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [1fc316ed-17ab-4fba-88ef-3ae78296b692] description = "partial garden -> garden with single student" [acd19dc1-2200-4317-bc2a-08f021276b40] description = "partial garden -> different garden with single student" [c376fcc8-349c-446c-94b0-903947315757] description = "partial garden -> garden with two students" [2d620f45-9617-4924-9d27-751c80d17db9] description = "partial garden -> multiple students for the same garden with three students -> second student's garden" [57712331-4896-4364-89f8-576421d69c44] description = "partial garden -> multiple students for the same garden with three students -> third student's garden" [149b4290-58e1-40f2-8ae4-8b87c46e765b] description = "full garden -> for Alice, first student's garden" [ba25dbbc-10bd-4a37-b18e-f89ecd098a5e] description = "full garden -> for Bob, second student's garden" [566b621b-f18e-4c5f-873e-be30544b838c] description = "full garden -> for Charlie" [3ad3df57-dd98-46fc-9269-1877abf612aa] description = "full garden -> for David" [0f0a55d1-9710-46ed-a0eb-399ba8c72db2] description = "full garden -> for Eve" [a7e80c90-b140-4ea1-aee3-f4625365c9a4] description = "full garden -> for Fred" [9d94b273-2933-471b-86e8-dba68694c615] description = "full garden -> for Ginny" [f55bc6c2-ade8-4844-87c4-87196f1b7258] description = "full garden -> for Harriet" [759070a3-1bb1-4dd4-be2c-7cce1d7679ae] description = "full garden -> for Ileana" [78578123-2755-4d4a-9c7d-e985b8dda1c6] description = "full garden -> for Joseph" [6bb66df7-f433-41ab-aec2-3ead6e99f65b] description = "full garden -> for Kincaid, second to last student's garden" [d7edec11-6488-418a-94e6-ed509e0fa7eb] description = "full garden -> for Larry, last student's garden" ================================================ FILE: exercises/practice/kindergarten-garden/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/kindergarten-garden/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/kindergarten-garden/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/kindergarten-garden/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/kindergarten-garden/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/kindergarten-garden/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/kindergarten-garden/kindergarten-garden.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { Garden } from './kindergarten-garden.ts' describe('partial Garden', () => { it('garden with single student', () => { expect(new Garden('RC\nGG').plants('Alice')).toEqual([ 'radishes', 'clover', 'grass', 'grass', ]) }) xit('different garden with single student', () => { expect(new Garden('VC\nRC').plants('Alice')).toEqual([ 'violets', 'clover', 'radishes', 'clover', ]) }) xit('garden with two students', () => { expect(new Garden('VVCG\nVVRC').plants('Bob')).toEqual([ 'clover', 'grass', 'radishes', 'clover', ]) }) describe('multiple students for the same garden with three students', () => { xit("second student's garden", () => { expect(new Garden('VVCCGG\nVVCCGG').plants('Bob')).toEqual([ 'clover', 'clover', 'clover', 'clover', ]) }) xit("third student's garden", () => { expect(new Garden('VVCCGG\nVVCCGG').plants('Charlie')).toEqual([ 'grass', 'grass', 'grass', 'grass', ]) }) }) }) describe('full garden', () => { const diagram = 'VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV' const garden = new Garden(diagram) xit("for Alice, first student's garden", () => { expect(garden.plants('Alice')).toEqual([ 'violets', 'radishes', 'violets', 'radishes', ]) }) xit("for Bob, second student's garden", () => { expect(garden.plants('Bob')).toEqual([ 'clover', 'grass', 'clover', 'clover', ]) }) xit('for Charlie', () => { expect(garden.plants('Charlie')).toEqual([ 'violets', 'violets', 'clover', 'grass', ]) }) xit('for David', () => { expect(garden.plants('David')).toEqual([ 'radishes', 'violets', 'clover', 'radishes', ]) }) xit('for Eve', () => { expect(garden.plants('Eve')).toEqual([ 'clover', 'grass', 'radishes', 'grass', ]) }) xit('for Fred', () => { expect(garden.plants('Fred')).toEqual([ 'grass', 'clover', 'violets', 'clover', ]) }) xit('for Ginny', () => { expect(garden.plants('Ginny')).toEqual([ 'clover', 'grass', 'grass', 'clover', ]) }) xit('for Harriet', () => { expect(garden.plants('Harriet')).toEqual([ 'violets', 'radishes', 'radishes', 'violets', ]) }) xit('for Ileana', () => { expect(garden.plants('Ileana')).toEqual([ 'grass', 'clover', 'violets', 'clover', ]) }) xit('for Joseph', () => { expect(garden.plants('Joseph')).toEqual([ 'violets', 'clover', 'violets', 'grass', ]) }) xit("for Kincaid, second to last student's garden", () => { expect(garden.plants('Kincaid')).toEqual([ 'grass', 'clover', 'clover', 'grass', ]) }) xit("for Larry, last student's garden", () => { expect(garden.plants('Larry')).toEqual([ 'grass', 'violets', 'clover', 'violets', ]) }) }) describe('disordered class', () => { const diagram = 'VCRRGVRG\nRVGCCGCV' const students = ['Samantha', 'Patricia', 'Xander', 'Roger'] const garden = new Garden(diagram, students) xit('for Patricia', () => { expect(garden.plants('Patricia')).toEqual([ 'violets', 'clover', 'radishes', 'violets', ]) }) xit('for Roger', () => { expect(garden.plants('Roger')).toEqual([ 'radishes', 'radishes', 'grass', 'clover', ]) }) xit('for Samantha', () => { expect(garden.plants('Samantha')).toEqual([ 'grass', 'violets', 'clover', 'grass', ]) }) xit('for Xander', () => { expect(garden.plants('Xander')).toEqual([ 'radishes', 'grass', 'clover', 'violets', ]) }) }) describe('Two gardens, different students', () => { const diagram = 'VCRRGVRG\nRVGCCGCV' const garden1 = new Garden(diagram, ['Alice', 'Bob', 'Charlie', 'Dan']) const garden2 = new Garden(diagram, ['Bob', 'Charlie', 'Dan', 'Erin']) xit('Bob and Charlie for each garden', () => { expect(garden1.plants('Bob')).toEqual([ 'radishes', 'radishes', 'grass', 'clover', ]) expect(garden2.plants('Bob')).toEqual([ 'violets', 'clover', 'radishes', 'violets', ]) expect(garden1.plants('Charlie')).toEqual([ 'grass', 'violets', 'clover', 'grass', ]) expect(garden2.plants('Charlie')).toEqual([ 'radishes', 'radishes', 'grass', 'clover', ]) }) }) ================================================ FILE: exercises/practice/kindergarten-garden/kindergarten-garden.ts ================================================ // // This is only a SKELETON file for the 'Kindergarten Garden' exercise. // It's been provided as a convenience to get you started writing code faster. // const DEFAULT_STUDENTS: Student[] = [ 'Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Fred', 'Ginny', 'Harriet', 'Ileana', 'Joseph', 'Kincaid', 'Larry', ] const PLANT_CODES = { G: 'grass', V: 'violets', R: 'radishes', C: 'clover', } as const type Student = string type Plant = (typeof PLANT_CODES)[keyof typeof PLANT_CODES] type Plants = Plant[] type Pots = Plants[] export class Garden { constructor(diagram: string, students = DEFAULT_STUDENTS) { throw new Error('Remove this line and implement the function') } public plants(student: Student): Plants { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/kindergarten-garden/package.json ================================================ { "name": "@exercism/typescript-kindergarten-garden", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/kindergarten-garden/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/kindergarten-garden/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/knapsack/.docs/instructions.md ================================================ # Instructions Your task is to determine which items to take so that the total value of her selection is maximized, taking into account the knapsack's carrying capacity. Items will be represented as a list of items. Each item will have a weight and value. All values given will be strictly positive. Lhakpa can take only one of each item. For example: ```text Items: [ { "weight": 5, "value": 10 }, { "weight": 4, "value": 40 }, { "weight": 6, "value": 30 }, { "weight": 4, "value": 50 } ] Knapsack Maximum Weight: 10 ``` For the above, the first item has weight 5 and value 10, the second item has weight 4 and value 40, and so on. In this example, Lhakpa should take the second and fourth item to maximize her value, which, in this case, is 90. She cannot get more than 90 as her knapsack has a weight limit of 10. ================================================ FILE: exercises/practice/knapsack/.docs/introduction.md ================================================ # Introduction Lhakpa is a [Sherpa][sherpa] mountain guide and porter. After months of careful planning, the expedition Lhakpa works for is about to leave. She will be paid the value she carried to the base camp. In front of her are many items, each with a value and weight. Lhakpa would gladly take all of the items, but her knapsack can only hold so much weight. [sherpa]: https://en.wikipedia.org/wiki/Sherpa_people#Mountaineering ================================================ FILE: exercises/practice/knapsack/.meta/config.json ================================================ { "authors": [ "JoshiRaez" ], "contributors": [ "SleeplessByte" ], "files": { "solution": [ "knapsack.ts" ], "test": [ "knapsack.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a knapsack that can only carry a certain weight, determine which items to put in the knapsack in order to maximize their combined value.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Knapsack_problem" } ================================================ FILE: exercises/practice/knapsack/.meta/proof.ci.ts ================================================ type Item = { weight: number value: number } export function maximumValue({ maximumWeight, items, }: { maximumWeight: number items: Item[] }): number { const maxValueForItemsSizeToWeight: number[][] = new Array(items.length + 1) .fill(undefined) .map(() => new Array(maximumWeight + 1)) return solve(items.length, maximumWeight, items, maxValueForItemsSizeToWeight) } function solve( setItemsSize: number, maxWeight: number, items: Item[], maxValueForItemsSizeToWeight: number[][] ): number { return memoizedMax( setItemsSize, maxWeight, items, maxValueForItemsSizeToWeight ) } function memoizedMax( setItemsSize: number, maxWeight: number, items: Item[], maxValueForItemsSizeToWeight: number[][] ): number { const alreadyKnownMaxValue = maxValueForItemsSizeToWeight[setItemsSize][maxWeight] if (alreadyKnownMaxValue || alreadyKnownMaxValue === 0) return alreadyKnownMaxValue const max = calculateMax( setItemsSize, maxWeight, items, maxValueForItemsSizeToWeight ) maxValueForItemsSizeToWeight[setItemsSize][maxWeight] = max return max } function calculateMax( setItemsSize: number, maxWeight: number, items: Item[], maxValueForItemsSizeToWeight: number[][] ): number { if (!setItemsSize) return 0 return Math.max( ...Array.from(Array(maxWeight + 1).keys()).map( (checkingWeight) => memoizedMax( setItemsSize - 1, checkingWeight, items, maxValueForItemsSizeToWeight ) + valueOfItemIfFits(maxWeight, checkingWeight, items[setItemsSize - 1]) ) ) } function valueOfItemIfFits( maxWeight: number, alreadyInBagWeight: number, itemToFit: Item ): number { if (alreadyInBagWeight + itemToFit.weight <= maxWeight) return itemToFit.value return 0 } ================================================ FILE: exercises/practice/knapsack/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [a4d7d2f0-ad8a-460c-86f3-88ba709d41a7] description = "no items" include = false [3993a824-c20e-493d-b3c9-ee8a7753ee59] description = "no items" reimplements = "a4d7d2f0-ad8a-460c-86f3-88ba709d41a7" [1d39e98c-6249-4a8b-912f-87cb12e506b0] description = "one item, too heavy" [833ea310-6323-44f2-9d27-a278740ffbd8] description = "five items (cannot be greedy by weight)" [277cdc52-f835-4c7d-872b-bff17bab2456] description = "five items (cannot be greedy by value)" [81d8e679-442b-4f7a-8a59-7278083916c9] description = "example knapsack" [f23a2449-d67c-4c26-bf3e-cde020f27ecc] description = "8 items" [7c682ae9-c385-4241-a197-d2fa02c81a11] description = "15 items" ================================================ FILE: exercises/practice/knapsack/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/knapsack/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/knapsack/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/knapsack/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/knapsack/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/knapsack/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/knapsack/knapsack.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { maximumValue } from './knapsack.ts' describe('Check if max carriable value is returned', () => { it('No items', () => { const input = { maximumWeight: 100, items: [], } const expected = 0 expect(maximumValue(input)).toEqual(expected) }) xit('One item, but too heavy', () => { const input = { maximumWeight: 10, items: [{ weight: 100, value: 1 }], } const expected = 0 expect(maximumValue(input)).toEqual(expected) }) xit("Five items. Can't be greedy by weight", () => { const input = { maximumWeight: 10, items: [ { weight: 2, value: 5 }, { weight: 2, value: 5 }, { weight: 2, value: 5 }, { weight: 2, value: 5 }, { weight: 10, value: 21 }, ], } const expected = 21 expect(maximumValue(input)).toEqual(expected) }) xit("Five items. Can't be greedy by value", () => { const input = { maximumWeight: 10, items: [ { weight: 2, value: 20 }, { weight: 2, value: 20 }, { weight: 2, value: 20 }, { weight: 2, value: 20 }, { weight: 10, value: 50 }, ], } const expected = 80 expect(maximumValue(input)).toEqual(expected) }) xit('Example knapsack', () => { const input = { maximumWeight: 10, items: [ { weight: 5, value: 10 }, { weight: 4, value: 40 }, { weight: 6, value: 30 }, { weight: 4, value: 50 }, ], } const expected = 90 expect(maximumValue(input)).toEqual(expected) }) xit('8 items', () => { const input = { maximumWeight: 104, items: [ { weight: 25, value: 350 }, { weight: 35, value: 400 }, { weight: 45, value: 450 }, { weight: 5, value: 20 }, { weight: 25, value: 70 }, { weight: 3, value: 8 }, { weight: 2, value: 5 }, { weight: 2, value: 5 }, ], } const expected = 900 expect(maximumValue(input)).toEqual(expected) }) xit('15 items', () => { const input = { maximumWeight: 750, items: [ { weight: 70, value: 135 }, { weight: 73, value: 139 }, { weight: 77, value: 149 }, { weight: 80, value: 150 }, { weight: 82, value: 156 }, { weight: 87, value: 163 }, { weight: 90, value: 173 }, { weight: 94, value: 184 }, { weight: 98, value: 192 }, { weight: 106, value: 201 }, { weight: 110, value: 210 }, { weight: 113, value: 214 }, { weight: 115, value: 221 }, { weight: 118, value: 229 }, { weight: 120, value: 240 }, ], } const expected = 1458 expect(maximumValue(input)).toEqual(expected) }) }) ================================================ FILE: exercises/practice/knapsack/knapsack.ts ================================================ type Item = { weight: number value: number } export function maximumValue({ maximumWeight, items, }: { maximumWeight: number items: Item[] }): number { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/knapsack/package.json ================================================ { "name": "@exercism/typescript-knapsack", "version": "1.0.0", "description": "Exercism practice exercise on knapsack", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/knapsack/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/knapsack/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/largest-series-product/.docs/instructions.md ================================================ # Instructions Your task is to look for patterns in the long sequence of digits in the encrypted signal. The technique you're going to use here is called the largest series product. Let's define a few terms, first. - **input**: the sequence of digits that you need to analyze - **series**: a sequence of adjacent digits (those that are next to each other) that is contained within the input - **span**: how many digits long each series is - **product**: what you get when you multiply numbers together Let's work through an example, with the input `"63915"`. - To form a series, take adjacent digits in the original input. - If you are working with a span of `3`, there will be three possible series: - `"639"` - `"391"` - `"915"` - Then we need to calculate the product of each series: - The product of the series `"639"` is 162 (`6 × 3 × 9 = 162`) - The product of the series `"391"` is 27 (`3 × 9 × 1 = 27`) - The product of the series `"915"` is 45 (`9 × 1 × 5 = 45`) - 162 is bigger than both 27 and 45, so the largest series product of `"63915"` is from the series `"639"`. So the answer is **162**. ================================================ FILE: exercises/practice/largest-series-product/.docs/introduction.md ================================================ # Introduction You work for a government agency that has intercepted a series of encrypted communication signals from a group of bank robbers. The signals contain a long sequence of digits. Your team needs to use various digital signal processing techniques to analyze the signals and identify any patterns that may indicate the planning of a heist. ================================================ FILE: exercises/practice/largest-series-product/.meta/config.json ================================================ { "authors": [ "deyshin" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "largest-series-product.ts" ], "test": [ "largest-series-product.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a string of digits, calculate the largest product for a contiguous substring of digits of length n.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "A variation on Problem 8 at Project Euler", "source_url": "https://projecteuler.net/problem=8" } ================================================ FILE: exercises/practice/largest-series-product/.meta/proof.ci.ts ================================================ export function largestProduct(digits: string, seriesLength: number): number { if (seriesLength === 0) { return 1 } if (seriesLength > digits.length) { throw new Error('Span must not exceed string length') } if (seriesLength < 0) { throw new Error('Span must not be negative') } if (!/^[0-9]+$/g.test(digits)) { throw new Error('Digits input must only contain digits') } let result = 0 for (let i = 0; i <= digits.length - seriesLength; i++) { const product = digits .substring(i, i + seriesLength) .split('') .map((digit) => Number(digit)) .reduce((a, b) => a * b) if (product > result) { result = product } } return result } ================================================ FILE: exercises/practice/largest-series-product/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [7c82f8b7-e347-48ee-8a22-f672323324d4] description = "finds the largest product if span equals length" [88523f65-21ba-4458-a76a-b4aaf6e4cb5e] description = "can find the largest product of 2 with numbers in order" [f1376b48-1157-419d-92c2-1d7e36a70b8a] description = "can find the largest product of 2" [46356a67-7e02-489e-8fea-321c2fa7b4a4] description = "can find the largest product of 3 with numbers in order" [a2dcb54b-2b8f-4993-92dd-5ce56dece64a] description = "can find the largest product of 3" [673210a3-33cd-4708-940b-c482d7a88f9d] description = "can find the largest product of 5 with numbers in order" [02acd5a6-3bbf-46df-8282-8b313a80a7c9] description = "can get the largest product of a big number" [76dcc407-21e9-424c-a98e-609f269622b5] description = "reports zero if the only digits are zero" [6ef0df9f-52d4-4a5d-b210-f6fae5f20e19] description = "reports zero if all spans include zero" [5d81aaf7-4f67-4125-bf33-11493cc7eab7] description = "rejects span longer than string length" include = false [0ae1ce53-d9ba-41bb-827f-2fceb64f058b] description = "rejects span longer than string length" reimplements = "5d81aaf7-4f67-4125-bf33-11493cc7eab7" [06bc8b90-0c51-4c54-ac22-3ec3893a079e] description = "reports 1 for empty string and empty product (0 span)" include = false [3ec0d92e-f2e2-4090-a380-70afee02f4c0] description = "reports 1 for nonempty string and empty product (0 span)" include = false [6d96c691-4374-4404-80ee-2ea8f3613dd4] description = "rejects empty string and nonzero span" include = false [6cf66098-a6af-4223-aab1-26aeeefc7402] description = "rejects empty string and nonzero span" reimplements = "6d96c691-4374-4404-80ee-2ea8f3613dd4" [7a38f2d6-3c35-45f6-8d6f-12e6e32d4d74] description = "rejects invalid character in digits" [5fe3c0e5-a945-49f2-b584-f0814b4dd1ef] description = "rejects negative span" include = false [c859f34a-9bfe-4897-9c2f-6d7f8598e7f0] description = "rejects negative span" reimplements = "5fe3c0e5-a945-49f2-b584-f0814b4dd1ef" ================================================ FILE: exercises/practice/largest-series-product/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/largest-series-product/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/largest-series-product/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/largest-series-product/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/largest-series-product/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/largest-series-product/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/largest-series-product/largest-series-product.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { largestProduct } from './largest-series-product.ts' describe('Largest Series Product', () => { it('finds the largest product if span equals length', () => { expect(largestProduct('29', 2)).toEqual(18) }) xit('can find the largest product of 2 with numbers in order', () => { expect(largestProduct('0123456789', 2)).toEqual(72) }) xit('can find the largest product of 2', () => { expect(largestProduct('576802143', 2)).toEqual(48) }) xit('can find the largest product of 3 with numbers in order', () => { expect(largestProduct('0123456789', 3)).toEqual(504) }) xit('can find the largest product of 3', () => { expect(largestProduct('1027839564', 3)).toEqual(270) }) xit('can find the largest product of 5 with numbers in order', () => { expect(largestProduct('0123456789', 5)).toEqual(15120) }) xit('can get the largest product of a big number', () => { expect( largestProduct('73167176531330624919225119674426574742355349194934', 6) ).toEqual(23520) }) xit('reports zero if the only digits are zero', () => { expect(largestProduct('0000', 2)).toEqual(0) }) xit('reports zero if all spans include zero', () => { expect(largestProduct('99099', 3)).toEqual(0) }) xit('rejects span longer than string length', () => { expect(() => largestProduct('123', 4)).toThrow( new Error('Span must not exceed string length') ) }) xit('rejects empty string and nonzero span', () => { expect(() => largestProduct('', 1)).toThrow( new Error('Span must not exceed string length') ) }) xit('rejects invalid character in digits', () => { expect(() => largestProduct('1234a5', 2)).toThrow( new Error('Digits input must only contain digits') ) }) xit('rejects negative span', () => { expect(() => largestProduct('12345', -1)).toThrow( new Error('Span must not be negative') ) }) }) ================================================ FILE: exercises/practice/largest-series-product/largest-series-product.ts ================================================ export const largestProduct = () => { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/largest-series-product/package.json ================================================ { "name": "@exercism/typescript-largest-series-product", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/largest-series-product/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/largest-series-product/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/leap/.docs/instructions.md ================================================ # Instructions Your task is to determine whether a given year is a leap year. ================================================ FILE: exercises/practice/leap/.docs/introduction.md ================================================ # Introduction A leap year (in the Gregorian calendar) occurs: - In every year that is evenly divisible by 4. - Unless the year is evenly divisible by 100, in which case it's only a leap year if the year is also evenly divisible by 400. Some examples: - 1997 was not a leap year as it's not divisible by 4. - 1900 was not a leap year as it's not divisible by 400. - 2000 was a leap year! ~~~~exercism/note For a delightful, four-minute explanation of the whole phenomenon of leap years, check out [this YouTube video](https://www.youtube.com/watch?v=xX96xng7sAE). ~~~~ ================================================ FILE: exercises/practice/leap/.meta/config.json ================================================ { "authors": [ "masters3d" ], "contributors": [ "DFXLuna", "lukaszklis", "SleeplessByte" ], "files": { "solution": [ "leap.ts" ], "test": [ "leap.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Determine whether a given year is a leap year.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "CodeRanch Cattle Drive, Assignment 3", "source_url": "https://web.archive.org/web/20240907033714/https://coderanch.com/t/718816/Leap" } ================================================ FILE: exercises/practice/leap/.meta/proof.ci.ts ================================================ export function isLeap(year: number): boolean { return (year % 100 !== 0 && year % 4 === 0) || year % 400 === 0 } ================================================ FILE: exercises/practice/leap/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [6466b30d-519c-438e-935d-388224ab5223] description = "year not divisible by 4 in common year" [ac227e82-ee82-4a09-9eb6-4f84331ffdb0] description = "year divisible by 2, not divisible by 4 in common year" [4fe9b84c-8e65-489e-970b-856d60b8b78e] description = "year divisible by 4, not divisible by 100 in leap year" [7fc6aed7-e63c-48f5-ae05-5fe182f60a5d] description = "year divisible by 4 and 5 is still a leap year" [78a7848f-9667-4192-ae53-87b30c9a02dd] description = "year divisible by 100, not divisible by 400 in common year" [9d70f938-537c-40a6-ba19-f50739ce8bac] description = "year divisible by 100 but not by 3 is still not a leap year" [42ee56ad-d3e6-48f1-8e3f-c84078d916fc] description = "year divisible by 400 in leap year" [57902c77-6fe9-40de-8302-587b5c27121e] description = "year divisible by 400 but not by 125 is still a leap year" [c30331f6-f9f6-4881-ad38-8ca8c12520c1] description = "year divisible by 200, not divisible by 400 in common year" ================================================ FILE: exercises/practice/leap/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/leap/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/leap/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/leap/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/leap/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/leap/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/leap/leap.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { isLeap } from './leap.ts' describe('A leap year', () => { it('year not divisible by 4 in common year', () => { expect(isLeap(2015)).toBe(false) }) xit('year divisible by 2, not divisible by 4 in common year', () => { expect(isLeap(1970)).toBe(false) }) xit('year divisible by 4, not divisible by 100 in leap year', () => { expect(isLeap(1996)).toBe(true) }) xit('year divisible by 4 and 5 is still a leap year', () => { expect(isLeap(1960)).toBe(true) }) xit('year divisible by 100, not divisible by 400 in common year', () => { expect(isLeap(2100)).toBe(false) }) xit('year divisible by 100 but not by 3 is still not a leap year', () => { expect(isLeap(1900)).toBe(false) }) xit('year divisible by 400 in leap year', () => { expect(isLeap(2000)).toBe(true) }) xit('year divisible by 400 but not by 125 is still a leap year', () => { expect(isLeap(2400)).toBe(true) }) xit('year divisible by 200, not divisible by 400 in common year', () => { expect(isLeap(1800)).toBe(false) }) }) ================================================ FILE: exercises/practice/leap/leap.ts ================================================ export function isLeap() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/leap/package.json ================================================ { "name": "@exercism/typescript-leap", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/leap/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/leap/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/line-up/.docs/instructions.md ================================================ # Instructions Given a name and a number, your task is to produce a sentence using that name and that number as an [ordinal numeral][ordinal-numeral]. Yaʻqūb expects to use numbers from 1 up to 999. Rules: - Numbers ending in 1 (unless ending in 11) → `"st"` - Numbers ending in 2 (unless ending in 12) → `"nd"` - Numbers ending in 3 (unless ending in 13) → `"rd"` - All other numbers → `"th"` Examples: - `"Mary", 1` → `"Mary, you are the 1st customer we serve today. Thank you!"` - `"John", 12` → `"John, you are the 12th customer we serve today. Thank you!"` - `"Dahir", 162` → `"Dahir, you are the 162nd customer we serve today. Thank you!"` [ordinal-numeral]: https://en.wikipedia.org/wiki/Ordinal_numeral ================================================ FILE: exercises/practice/line-up/.docs/introduction.md ================================================ # Introduction Your friend Yaʻqūb works the counter at a deli in town, slicing, weighing, and wrapping orders for a line of hungry customers that gets longer every day. Waiting customers are starting to lose track of who is next, so he wants numbered tickets they can use to track the order in which they arrive. To make the customers feel special, he does not want the ticket to have only a number on it. They shall get a proper English sentence with their name and number on it. ================================================ FILE: exercises/practice/line-up/.meta/config.json ================================================ { "authors": [ "BNAndras" ], "files": { "solution": [ "line-up.ts" ], "test": [ "line-up.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Help lining up customers at Yaʻqūb's Deli.", "source": "mk-mxp, based on previous work from Exercism contributors codedge and neenjaw", "source_url": "https://forum.exercism.org/t/new-exercise-ordinal-numbers/19147" } ================================================ FILE: exercises/practice/line-up/.meta/proof.ci.ts ================================================ const getSuffix = (number: number): string => { const lastTwoDigits = number % 100 const lastOneDigit = number % 10 if (lastTwoDigits >= 11 && lastTwoDigits <= 13) { return 'th' } if (lastOneDigit === 1) { return 'st' } if (lastOneDigit === 2) { return 'nd' } if (lastOneDigit === 3) { return 'rd' } return 'th' } export const format = (name: string, number: number): string => `${name}, you are the ${number}${getSuffix(number)} customer we serve today. Thank you!` ================================================ FILE: exercises/practice/line-up/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [7760d1b8-4864-4db4-953b-0fa7c047dbc0] description = "format smallest non-exceptional ordinal numeral 4" [e8b7c715-6baa-4f7b-8fb3-2fa48044ab7a] description = "format greatest single digit non-exceptional ordinal numeral 9" [f370aae9-7ae7-4247-90ce-e8ff8c6934df] description = "format non-exceptional ordinal numeral 5" [37f10dea-42a2-49de-bb92-0b690b677908] description = "format non-exceptional ordinal numeral 6" [d8dfb9a2-3a1f-4fee-9dae-01af3600054e] description = "format non-exceptional ordinal numeral 7" [505ec372-1803-42b1-9377-6934890fd055] description = "format non-exceptional ordinal numeral 8" [8267072d-be1f-4f70-b34a-76b7557a47b9] description = "format exceptional ordinal numeral 1" [4d8753cb-0364-4b29-84b8-4374a4fa2e3f] description = "format exceptional ordinal numeral 2" [8d44c223-3a7e-4f48-a0ca-78e67bf98aa7] description = "format exceptional ordinal numeral 3" [6c4f6c88-b306-4f40-bc78-97cdd583c21a] description = "format smallest two digit non-exceptional ordinal numeral 10" [e257a43f-d2b1-457a-97df-25f0923fc62a] description = "format non-exceptional ordinal numeral 11" [bb1db695-4d64-457f-81b8-4f5a2107e3f4] description = "format non-exceptional ordinal numeral 12" [60a3187c-9403-4835-97de-4f10ebfd63e2] description = "format non-exceptional ordinal numeral 13" [2bdcebc5-c029-4874-b6cc-e9bec80d603a] description = "format exceptional ordinal numeral 21" [74ee2317-0295-49d2-baf0-d56bcefa14e3] description = "format exceptional ordinal numeral 62" [b37c332d-7f68-40e3-8503-e43cbd67a0c4] description = "format exceptional ordinal numeral 100" [0375f250-ce92-4195-9555-00e28ccc4d99] description = "format exceptional ordinal numeral 101" [0d8a4974-9a8a-45a4-aca7-a9fb473c9836] description = "format non-exceptional ordinal numeral 112" [06b62efe-199e-4ce7-970d-4bf73945713f] description = "format exceptional ordinal numeral 123" ================================================ FILE: exercises/practice/line-up/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/line-up/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/line-up/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/line-up/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/line-up/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/line-up/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/line-up/line-up.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { format } from './line-up.ts' describe('Line Up', () => { it('format smallest non-exceptional ordinal numeral 4', () => { expect(format('Gianna', 4)).toBe( 'Gianna, you are the 4th customer we serve today. Thank you!' ) }) xit('format greatest single digit non-exceptional ordinal numeral 9', () => { expect(format('Maarten', 9)).toBe( 'Maarten, you are the 9th customer we serve today. Thank you!' ) }) xit('format non-exceptional ordinal numeral 5', () => { expect(format('Petronila', 5)).toBe( 'Petronila, you are the 5th customer we serve today. Thank you!' ) }) xit('format non-exceptional ordinal numeral 6', () => { expect(format('Attakullakulla', 6)).toBe( 'Attakullakulla, you are the 6th customer we serve today. Thank you!' ) }) xit('format non-exceptional ordinal numeral 7', () => { expect(format('Kate', 7)).toBe( 'Kate, you are the 7th customer we serve today. Thank you!' ) }) xit('format non-exceptional ordinal numeral 8', () => { expect(format('Maximiliano', 8)).toBe( 'Maximiliano, you are the 8th customer we serve today. Thank you!' ) }) xit('format exceptional ordinal numeral 1', () => { expect(format('Mary', 1)).toBe( 'Mary, you are the 1st customer we serve today. Thank you!' ) }) xit('format exceptional ordinal numeral 2', () => { expect(format('Haruto', 2)).toBe( 'Haruto, you are the 2nd customer we serve today. Thank you!' ) }) xit('format exceptional ordinal numeral 3', () => { expect(format('Henriette', 3)).toBe( 'Henriette, you are the 3rd customer we serve today. Thank you!' ) }) xit('format smallest two digit non-exceptional ordinal numeral 10', () => { expect(format('Alvarez', 10)).toBe( 'Alvarez, you are the 10th customer we serve today. Thank you!' ) }) xit('format non-exceptional ordinal numeral 11', () => { expect(format('Jacqueline', 11)).toBe( 'Jacqueline, you are the 11th customer we serve today. Thank you!' ) }) xit('format non-exceptional ordinal numeral 12', () => { expect(format('Juan', 12)).toBe( 'Juan, you are the 12th customer we serve today. Thank you!' ) }) xit('format non-exceptional ordinal numeral 13', () => { expect(format('Patricia', 13)).toBe( 'Patricia, you are the 13th customer we serve today. Thank you!' ) }) xit('format exceptional ordinal numeral 21', () => { expect(format('Washi', 21)).toBe( 'Washi, you are the 21st customer we serve today. Thank you!' ) }) xit('format exceptional ordinal numeral 62', () => { expect(format('Nayra', 62)).toBe( 'Nayra, you are the 62nd customer we serve today. Thank you!' ) }) xit('format exceptional ordinal numeral 100', () => { expect(format('John', 100)).toBe( 'John, you are the 100th customer we serve today. Thank you!' ) }) xit('format exceptional ordinal numeral 101', () => { expect(format('Zeinab', 101)).toBe( 'Zeinab, you are the 101st customer we serve today. Thank you!' ) }) xit('format exceptional ordinal numeral 112', () => { expect(format('Knud', 112)).toBe( 'Knud, you are the 112th customer we serve today. Thank you!' ) }) xit('format exceptional ordinal numeral 123', () => { expect(format('Yma', 123)).toBe( 'Yma, you are the 123rd customer we serve today. Thank you!' ) }) }) ================================================ FILE: exercises/practice/line-up/line-up.ts ================================================ export function format(name: unknown, number: unknown): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/line-up/package.json ================================================ { "name": "@exercism/typescript-line-up", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/line-up/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/line-up/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/linked-list/.docs/instructions.md ================================================ # Instructions Your team has decided to use a doubly linked list to represent each train route in the schedule. Each station along the train's route will be represented by a node in the linked list. You don't need to worry about arrival and departure times at the stations. Each station will simply be represented by a number. Routes can be extended, adding stations to the beginning or end of a route. They can also be shortened by removing stations from the beginning or the end of a route. Sometimes a station gets closed down, and in that case the station needs to be removed from the route, even if it is not at the beginning or end of the route. The size of a route is measured not by how far the train travels, but by how many stations it stops at. ~~~~exercism/note The linked list is a fundamental data structure in computer science, often used in the implementation of other data structures. As the name suggests, it is a list of nodes that are linked together. It is a list of "nodes", where each node links to its neighbor or neighbors. In a **singly linked list** each node links only to the node that follows it. In a **doubly linked list** each node links to both the node that comes before, as well as the node that comes after. If you want to dig deeper into linked lists, check out [this article][intro-linked-list] that explains it using nice drawings. [intro-linked-list]: https://medium.com/basecs/whats-a-linked-list-anyway-part-1-d8b7e6508b9d ~~~~ ================================================ FILE: exercises/practice/linked-list/.docs/introduction.md ================================================ # Introduction You are working on a project to develop a train scheduling system for a busy railway network. You've been asked to develop a prototype for the train routes in the scheduling system. Each route consists of a sequence of train stations that a given train stops at. ================================================ FILE: exercises/practice/linked-list/.meta/config.json ================================================ { "authors": [ "jspengeman" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "linked-list.ts" ], "test": [ "linked-list.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Implement a doubly linked list.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Classic computer science topic" } ================================================ FILE: exercises/practice/linked-list/.meta/proof.ci.ts ================================================ class Node { private value: T private next: Node | undefined private prev: Node | undefined constructor(value: T) { this.value = value } public getNext(): Node | undefined { return this.next } public setNext(newNext: Node | undefined): void { this.next = newNext } public getPrev(): Node | undefined { return this.prev } public setPrev(newPrev: Node | undefined): void { this.prev = newPrev } public getValue(): T { return this.value } } export class LinkedList { private head: Node | undefined private tail: Node | undefined public pop(): T | undefined { if (!this.head) { return undefined } const value = this.head.getValue() this.head = this.head.getNext() if (this.head) { this.head.setPrev(undefined) } else { this.head = this.tail = undefined } return value } public push(value: T): void { if (this.head) { const newHead = new Node(value) newHead.setNext(this.head) this.head.setPrev(newHead) this.head = newHead } else { this.head = new Node(value) this.tail = this.head } } public shift(): T | undefined { if (!this.tail) { return undefined } const value = this.tail.getValue() this.tail = this.tail.getPrev() if (this.tail) { this.tail.setNext(undefined) } else { this.head = this.tail = undefined } return value } public unshift(value: T): void { if (this.tail) { const newTail = new Node(value) newTail.setPrev(this.tail) this.tail.setNext(newTail) this.tail = newTail } else { this.tail = new Node(value) this.head = this.tail } } public count(): number { let count = 0 let element: Node | undefined = this.head while (this.head && element) { count++ element = element.getNext() } return count } public delete(value: T): void { let element = this.head while (element) { if (element.getValue() === value) { const nextNode = element.getNext() const prevNode = element.getPrev() if (nextNode) { nextNode.setPrev(prevNode) } else if (this.tail) { this.tail = this.tail.getPrev() } if (prevNode) { prevNode.setNext(nextNode) } else if (this.head) { this.head = this.head.getNext() } element = undefined } else { element = element.getNext() } } } } ================================================ FILE: exercises/practice/linked-list/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/linked-list/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/linked-list/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/linked-list/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/linked-list/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/linked-list/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/linked-list/linked-list.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { LinkedList } from './linked-list.ts' describe('LinkedList', () => { it('add/extract elements to the end of the list with push/pop', () => { const list = new LinkedList() list.push(10) list.push(20) expect(list.pop()).toBe(20) expect(list.pop()).toBe(10) }) xit('extract elements from the beginning of the list with shift', () => { const list = new LinkedList() list.push(10) list.push(20) expect(list.shift()).toBe(10) expect(list.shift()).toBe(20) }) xit('add/extract elements from the beginning of the list with unshift/shift', () => { const list = new LinkedList() list.unshift(10) list.unshift(20) expect(list.shift()).toBe(20) expect(list.shift()).toBe(10) }) xit('unshift/pop', () => { const list = new LinkedList() list.unshift(10) list.unshift(20) expect(list.pop()).toBe(10) expect(list.pop()).toBe(20) }) xit('example', () => { const list = new LinkedList() list.push(10) list.push(20) expect(list.pop()).toBe(20) list.push(30) expect(list.shift()).toBe(10) list.unshift(40) list.push(50) expect(list.shift()).toBe(40) expect(list.pop()).toBe(50) expect(list.shift()).toBe(30) }) xit('can count its elements', () => { const list = new LinkedList() expect(list.count()).toBe(0) list.push(10) expect(list.count()).toBe(1) list.push(20) expect(list.count()).toBe(2) }) xit('sets head/tail after popping last element', () => { const list = new LinkedList() list.push(10) list.pop() list.unshift(20) expect(list.count()).toBe(1) expect(list.pop()).toBe(20) }) xit('sets head/tail after shifting last element', () => { const list = new LinkedList() list.unshift(10) list.shift() list.push(20) expect(list.count()).toBe(1) expect(list.shift()).toBe(20) }) xit('deletes the element with the specified value from the list', () => { const list = new LinkedList() list.push(10) list.push(20) list.push(30) list.delete(20) expect(list.count()).toBe(2) expect(list.pop()).toBe(30) expect(list.shift()).toBe(10) }) xit('deletes the only element', () => { const list = new LinkedList() list.push(10) list.delete(10) expect(list.count()).toBe(0) }) xit('delete does not modify the list if the element is not found', () => { const list = new LinkedList() list.push(10) list.delete(20) expect(list.count()).toBe(1) }) }) ================================================ FILE: exercises/practice/linked-list/linked-list.ts ================================================ export class LinkedList { public push(element: unknown) { throw new Error('Remove this line and implement the function') } public pop(): unknown { throw new Error('Remove this line and implement the function') } public shift(): unknown { throw new Error('Remove this line and implement the function') } public unshift(element: unknown) { throw new Error('Remove this line and implement the function') } public delete(element: unknown) { throw new Error('Remove this line and implement the function') } public count(): unknown { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/linked-list/package.json ================================================ { "name": "@exercism/typescript-linked-list", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/linked-list/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/linked-list/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/list-ops/.docs/instructions.append.md ================================================ # Instructions append Using core language features to build and deconstruct arrays via destructuring, and using the array literal `[]` are allowed, but no functions from the `Array.prototype` should be used. In order to be able to test your solution, ensure `forEach` is implemented. ```typescript const list = List.create(1, 2) list.forEach((item) => console.log(item)) // => // 1 // 2 ``` ================================================ FILE: exercises/practice/list-ops/.docs/instructions.md ================================================ # Instructions Implement basic list operations. In functional languages list operations like `length`, `map`, and `reduce` are very common. Implement a series of basic list operations, without using existing functions. The precise number and names of the operations to be implemented will be track dependent to avoid conflicts with existing names, but the general operations you will implement include: - `append` (_given two lists, add all items in the second list to the end of the first list_); - `concatenate` (_given a series of lists, combine all items in all lists into one flattened list_); - `filter` (_given a predicate and a list, return the list of all items for which `predicate(item)` is True_); - `length` (_given a list, return the total number of items within it_); - `map` (_given a function and a list, return the list of the results of applying `function(item)` on all items_); - `foldl` (_given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left_); - `foldr` (_given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right_); - `reverse` (_given a list, return a list with all the original items, but in reversed order_). Note, the ordering in which arguments are passed to the fold functions (`foldl`, `foldr`) is significant. ================================================ FILE: exercises/practice/list-ops/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "archanid", "masters3d", "paparomeo", "SleeplessByte" ], "files": { "solution": [ "list-ops.ts" ], "test": [ "list-ops.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Implement basic list operations.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false } } ================================================ FILE: exercises/practice/list-ops/.meta/proof.ci.ts ================================================ const Null: Cons = { get value() { return undefined }, get next() { return this }, get() { return this.value }, push(item: T): Cons { // eslint-disable-next-line @typescript-eslint/no-use-before-define return new Cons(item, this) as Cons }, length() { return 0 }, append(other: Cons): Cons { return other }, concatenate(): Cons { return this }, forEach(): void { /* done */ }, foldl( _: (initial: TReturn, value: TValue) => TReturn, initial?: TReturn ): TReturn { return initial as TReturn }, foldr( _: (initial: TReturn, value: TValue) => TReturn, initial?: TReturn ): TReturn { return initial as TReturn }, filter(): Cons { return Null }, reverse(): Cons { return this }, map(): Cons { return this as Cons }, } class Cons { constructor( public readonly value: T, public next: Cons = Null as Cons ) {} public get(i: number): T | undefined { return i === 0 ? this.value : this.next.get(i - 1) } public push(item: T): this { this.next = this.next.push(item) return this } public length(): number { return 1 + this.next.length() } public append(other: Cons): Cons { return other.foldl((result, item) => result.push(item), this) } public concatenate(others: Cons>): Cons { return others.foldl>((result, other) => result.append(other), this) } public foldl( callback: (initial: TReturn, value: T) => TReturn ): TReturn public foldl( callback: (initial: TReturn, value: T) => TReturn, initial: TReturn ): TReturn public foldl( callback: (initial: TReturn | undefined, value: T) => TReturn, initial?: TReturn ): TReturn { return this.next.foldl(callback, callback(initial, this.value)) } public forEach(callback: (value: T) => void): void { this.foldl((_, item) => callback(item)) } public foldr( callback: (initial: TReturn, value: T) => TReturn ): TReturn public foldr( callback: (initial: TReturn, value: T) => TReturn, initial: TReturn ): TReturn public foldr( callback: (initial: TReturn, value: T) => TReturn, initial?: TReturn ): TReturn { return callback( this.next.foldr( callback as (initial: TReturn, value: T | undefined) => TReturn, initial as TReturn ), this.value ) } public filter(predicate: (value: T) => boolean): Cons { return this.foldl>( (result, item) => (predicate(item) && result.push(item)) || result, Null as Cons ) } public map( expression: (value: T) => TReturn ): Cons { return this.foldl( (result, item) => result.push(expression(item)), Null as Cons ) } public reverse(): Cons { return this.next.reverse().push(this.value) } } export class List { public static create(...values: T[]): Cons { const [head, ...tail] = values if (head === undefined) { return Null as Cons } return new Cons(head, List.create(...tail)) } } ================================================ FILE: exercises/practice/list-ops/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [485b9452-bf94-40f7-a3db-c3cf4850066a] description = "append entries to a list and return the new list -> empty lists" [2c894696-b609-4569-b149-8672134d340a] description = "append entries to a list and return the new list -> list to empty list" [e842efed-3bf6-4295-b371-4d67a4fdf19c] description = "append entries to a list and return the new list -> empty list to list" [71dcf5eb-73ae-4a0e-b744-a52ee387922f] description = "append entries to a list and return the new list -> non-empty lists" [28444355-201b-4af2-a2f6-5550227bde21] description = "concatenate a list of lists -> empty list" [331451c1-9573-42a1-9869-2d06e3b389a9] description = "concatenate a list of lists -> list of lists" [d6ecd72c-197f-40c3-89a4-aa1f45827e09] description = "concatenate a list of lists -> list of nested lists" [0524fba8-3e0f-4531-ad2b-f7a43da86a16] description = "filter list returning only values that satisfy the filter function -> empty list" [88494bd5-f520-4edb-8631-88e415b62d24] description = "filter list returning only values that satisfy the filter function -> non-empty list" [1cf0b92d-8d96-41d5-9c21-7b3c37cb6aad] description = "returns the length of a list -> empty list" [d7b8d2d9-2d16-44c4-9a19-6e5f237cb71e] description = "returns the length of a list -> non-empty list" [c0bc8962-30e2-4bec-9ae4-668b8ecd75aa] description = "return a list of elements whose values equal the list value transformed by the mapping function -> empty list" [11e71a95-e78b-4909-b8e4-60cdcaec0e91] description = "return a list of elements whose values equal the list value transformed by the mapping function -> non-empty list" [613b20b7-1873-4070-a3a6-70ae5f50d7cc] description = "folds (reduces) the given list from the left with a function -> empty list" include = false [e56df3eb-9405-416a-b13a-aabb4c3b5194] description = "folds (reduces) the given list from the left with a function -> direction independent function applied to non-empty list" include = false [d2cf5644-aee1-4dfc-9b88-06896676fe27] description = "folds (reduces) the given list from the left with a function -> direction dependent function applied to non-empty list" include = false [36549237-f765-4a4c-bfd9-5d3a8f7b07d2] description = "folds (reduces) the given list from the left with a function -> empty list" reimplements = "613b20b7-1873-4070-a3a6-70ae5f50d7cc" [7a626a3c-03ec-42bc-9840-53f280e13067] description = "folds (reduces) the given list from the left with a function -> direction independent function applied to non-empty list" reimplements = "e56df3eb-9405-416a-b13a-aabb4c3b5194" [d7fcad99-e88e-40e1-a539-4c519681f390] description = "folds (reduces) the given list from the left with a function -> direction dependent function applied to non-empty list" reimplements = "d2cf5644-aee1-4dfc-9b88-06896676fe27" [aeb576b9-118e-4a57-a451-db49fac20fdc] description = "folds (reduces) the given list from the right with a function -> empty list" include = false [c4b64e58-313e-4c47-9c68-7764964efb8e] description = "folds (reduces) the given list from the right with a function -> direction independent function applied to non-empty list" include = false [be396a53-c074-4db3-8dd6-f7ed003cce7c] description = "folds (reduces) the given list from the right with a function -> direction dependent function applied to non-empty list" include = false [17214edb-20ba-42fc-bda8-000a5ab525b0] description = "folds (reduces) the given list from the right with a function -> empty list" reimplements = "aeb576b9-118e-4a57-a451-db49fac20fdc" [e1c64db7-9253-4a3d-a7c4-5273b9e2a1bd] description = "folds (reduces) the given list from the right with a function -> direction independent function applied to non-empty list" reimplements = "c4b64e58-313e-4c47-9c68-7764964efb8e" [8066003b-f2ff-437e-9103-66e6df474844] description = "folds (reduces) the given list from the right with a function -> direction dependent function applied to non-empty list" reimplements = "be396a53-c074-4db3-8dd6-f7ed003cce7c" [94231515-050e-4841-943d-d4488ab4ee30] description = "reverse the elements of the list -> empty list" [fcc03d1e-42e0-4712-b689-d54ad761f360] description = "reverse the elements of the list -> non-empty list" [40872990-b5b8-4cb8-9085-d91fc0d05d26] description = "reverse the elements of the list -> list of lists is not flattened" ================================================ FILE: exercises/practice/list-ops/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/list-ops/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/list-ops/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/list-ops/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/list-ops/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/list-ops/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/list-ops/list-ops.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { List } from './list-ops.ts' import type { MatcherFunction } from 'expect' type JestUtils = { utils: { printReceived(object: unknown): string } } const toHaveValues: MatcherFunction = function ( this: JestUtils, received: unknown, ...expected: unknown[] ) { if (typeof received !== 'object' || received === null) { return { pass: false, message: () => `Expected ${this.utils.printReceived(received)} to be a non-null object`, } } if (!('forEach' in received) || typeof received.forEach !== 'function') { return { pass: false, message: (): string => `Implement .forEach(callback) on your list`, } } const values: unknown[] = [] // eslint-disable-next-line @typescript-eslint/no-unsafe-call received.forEach((item: unknown) => { values.push(item) }) const pass = JSON.stringify(values) === JSON.stringify(expected) return { pass, message: (): string => pass ? '' : `Expected to see the following values: ${JSON.stringify( expected )}, actual: ${JSON.stringify(values)}`, } } expect.extend({ toHaveValues, }) declare module 'expect' { interface AsymmetricMatchers { toHaveValues(...expected: unknown[]): void } interface Matchers { toHaveValues(...expected: unknown[]): R } } describe('append entries to a list and return the new list', () => { it('empty lists', () => { const list1 = List.create() const list2 = List.create() expect(list1.append(list2)).toEqual(List.create()) }) xit('list to empty list', () => { const list1 = List.create() const list2 = List.create(1, 2, 3, 4) expect(list1.append(list2)).toEqual(list2) }) xit('empty list to list', () => { const list1 = List.create(1, 2, 3, 4) const list2 = List.create() expect(list1.append(list2)).toEqual(list1) }) xit('non-empty lists', () => { const list1 = List.create(1, 2) const list2 = List.create(2, 3, 4, 5) expect(list1.append(list2)).toHaveValues(1, 2, 2, 3, 4, 5) }) }) describe('concatenate lists and lists of lists into new list', () => { xit('empty list', () => { const list1 = List.create() const list2 = List.create>() expect(list1.concatenate(list2)).toHaveValues() }) xit('list of lists', () => { const list1 = List.create(1, 2) const list2 = List.create(3) const list3 = List.create() const list4 = List.create(4, 5, 6) const listOfLists = List.create(list2, list3, list4) expect(list1.concatenate(listOfLists)).toHaveValues(1, 2, 3, 4, 5, 6) }) }) describe('filter list returning only values that satisfy the filter function', () => { xit('empty list', () => { const list1 = List.create() expect(list1.filter((el) => el % 2 === 1)).toHaveValues() }) xit('non empty list', () => { const list1 = List.create(1, 2, 3, 5) expect(list1.filter((el) => el % 2 === 1)).toHaveValues(1, 3, 5) }) }) describe('returns the length of a list', () => { xit('empty list', () => { const list1 = List.create() expect(list1.length()).toEqual(0) }) xit('non-empty list', () => { const list1 = List.create(1, 2, 3, 4) expect(list1.length()).toEqual(4) }) }) describe('returns a list of elements whose values equal the list value transformed by the mapping function', () => { xit('empty list', () => { const list1 = List.create() expect(list1.map((el) => ++el)).toHaveValues() }) xit('non-empty list', () => { const list1 = List.create(1, 3, 5, 7) expect(list1.map((el) => ++el)).toHaveValues(2, 4, 6, 8) }) }) describe('folds (reduces) the given list from the left with a function', () => { xit('empty list', () => { const list1 = List.create() expect(list1.foldl((acc, el) => el * acc, 2)).toEqual(2) }) xit('direction independent function applied to non-empty list', () => { const list1 = List.create(1, 2, 3, 4) expect(list1.foldl((acc, el) => acc + el, 5)).toEqual(15) }) xit('direction dependent function applied to non-empty list', () => { const list1 = List.create(1, 2, 3, 4) expect(list1.foldl((acc, el) => el / acc, 24)).toEqual(64) }) }) describe('folds (reduces) the given list from the right with a function', () => { xit('empty list', () => { const list1 = List.create() expect(list1.foldr((acc, el) => el * acc, 2)).toEqual(2) }) xit('direction independent function applied to non-empty list', () => { const list1 = List.create(1, 2, 3, 4) expect(list1.foldr((acc, el) => acc + el, 5)).toEqual(15) }) xit('direction dependent function applied to non-empty list', () => { const list1 = List.create(1, 2, 3, 4) expect(list1.foldr((acc, el) => el / acc, 24)).toEqual(9) }) }) describe('reverse the elements of a list', () => { xit('empty list', () => { const list1 = List.create() expect(list1.reverse()).toHaveValues() }) xit('non-empty list', () => { const list1 = List.create(1, 3, 5, 7) expect(list1.reverse()).toHaveValues(7, 5, 3, 1) }) xit('list of lists is not flattened', () => { const list1 = List.create([1, 2], [3], [], [4, 5, 6]) expect(list1.reverse()).toHaveValues([4, 5, 6], [], [3], [1, 2]) }) }) ================================================ FILE: exercises/practice/list-ops/list-ops.ts ================================================ export class List { public static create(...values: unknown[]): unknown { // Do *not* construct any array literal ([]) in your solution. // Do *not* construct any arrays through new Array in your solution. // DO *not* use any of the Array.prototype methods in your solution. // You may use the destructuring and spreading (...) syntax from Iterable. throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/list-ops/package.json ================================================ { "name": "@exercism/typescript-list-ops", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/list-ops/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/list-ops/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/luhn/.docs/instructions.md ================================================ # Instructions Determine whether a number is valid according to the [Luhn formula][luhn]. The number will be provided as a string. ## Validating a number Strings of length 1 or less are not valid. Spaces are allowed in the input, but they should be stripped before checking. All other non-digit characters are disallowed. ## Examples ### Valid credit card number The number to be checked is `4539 3195 0343 6467`. The first step of the Luhn algorithm is to start at the end of the number and double every second digit, beginning with the second digit from the right and moving left. ```text 4539 3195 0343 6467 ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ (double these) ``` If the result of doubling a digit is greater than 9, we subtract 9 from that result. We end up with: ```text 8569 6195 0383 3437 ``` Finally, we sum all digits. If the sum is evenly divisible by 10, the original number is valid. ```text 8 + 5 + 6 + 9 + 6 + 1 + 9 + 5 + 0 + 3 + 8 + 3 + 3 + 4 + 3 + 7 = 80 ``` 80 is evenly divisible by 10, so number `4539 3195 0343 6467` is valid! ### Invalid Canadian SIN The number to be checked is `066 123 478`. We start at the end of the number and double every second digit, beginning with the second digit from the right and moving left. ```text 066 123 478 ↑ ↑ ↑ ↑ (double these) ``` If the result of doubling a digit is greater than 9, we subtract 9 from that result. We end up with: ```text 036 226 458 ``` We sum the digits: ```text 0 + 3 + 6 + 2 + 2 + 6 + 4 + 5 + 8 = 36 ``` 36 is not evenly divisible by 10, so number `066 123 478` is not valid! [luhn]: https://en.wikipedia.org/wiki/Luhn_algorithm ================================================ FILE: exercises/practice/luhn/.docs/introduction.md ================================================ # Introduction At the Global Verification Authority, you've just been entrusted with a critical assignment. Across the city, from online purchases to secure logins, countless operations rely on the accuracy of numerical identifiers like credit card numbers, bank account numbers, transaction codes, and tracking IDs. The Luhn algorithm is a simple checksum formula used to help identify mistyped numbers. A batch of identifiers has just arrived on your desk. All of them must pass the Luhn test to ensure they're legitimate. If any fail, they'll be flagged as invalid, preventing mistakes such as incorrect transactions or failed account verifications. Can you ensure this is done right? The integrity of many services depends on you. ================================================ FILE: exercises/practice/luhn/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte", "angelikatyborska" ], "files": { "solution": [ "luhn.ts" ], "test": [ "luhn.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a number determine whether or not it is valid per the Luhn formula.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "The Luhn Algorithm on Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Luhn_algorithm" } ================================================ FILE: exercises/practice/luhn/.meta/proof.ci.ts ================================================ export function valid(value: string): boolean { const valueWithoutSpaces = value.replace(/\s/g, '') const digits = [...valueWithoutSpaces] const sum = digits // convert to integers .map((d) => parseInt(d, 10)) .reverse() // double even positions (odd indexes) .map((d, i) => { if (i % 2 !== 0) { return d * 2 } return d }) // limit to digits less than 10 .map((d) => { if (d > 9) { return d - 9 } return d }) // sum all digits .reduce((d, acc) => d + acc, 0) return valueWithoutSpaces.length > 1 && sum % 10 === 0 } ================================================ FILE: exercises/practice/luhn/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [792a7082-feb7-48c7-b88b-bbfec160865e] description = "single digit strings can not be valid" [698a7924-64d4-4d89-8daa-32e1aadc271e] description = "a single zero is invalid" [73c2f62b-9b10-4c9f-9a04-83cee7367965] description = "a simple valid SIN that remains valid if reversed" [9369092e-b095-439f-948d-498bd076be11] description = "a simple valid SIN that becomes invalid if reversed" [8f9f2350-1faf-4008-ba84-85cbb93ffeca] description = "a valid Canadian SIN" [1cdcf269-6560-44fc-91f6-5819a7548737] description = "invalid Canadian SIN" [656c48c1-34e8-4e60-9a5a-aad8a367810a] description = "invalid credit card" [20e67fad-2121-43ed-99a8-14b5b856adb9] description = "invalid long number with an even remainder" [7e7c9fc1-d994-457c-811e-d390d52fba5e] description = "invalid long number with a remainder divisible by 5" [ad2a0c5f-84ed-4e5b-95da-6011d6f4f0aa] description = "valid number with an even number of digits" [ef081c06-a41f-4761-8492-385e13c8202d] description = "valid number with an odd number of spaces" [bef66f64-6100-4cbb-8f94-4c9713c5e5b2] description = "valid strings with a non-digit added at the end become invalid" [2177e225-9ce7-40f6-b55d-fa420e62938e] description = "valid strings with punctuation included become invalid" [ebf04f27-9698-45e1-9afe-7e0851d0fe8d] description = "valid strings with symbols included become invalid" [08195c5e-ce7f-422c-a5eb-3e45fece68ba] description = "single zero with space is invalid" [12e63a3c-f866-4a79-8c14-b359fc386091] description = "more than a single zero is valid" [ab56fa80-5de8-4735-8a4a-14dae588663e] description = "input digit 9 is correctly converted to output digit 9" [b9887ee8-8337-46c5-bc45-3bcab51bc36f] description = "very long input is valid" [8a7c0e24-85ea-4154-9cf1-c2db90eabc08] description = "valid luhn with an odd number of digits and non zero first digit" [39a06a5a-5bad-4e0f-b215-b042d46209b1] description = "using ascii value for non-doubled non-digit isn't allowed" [f94cf191-a62f-4868-bc72-7253114aa157] description = "using ascii value for doubled non-digit isn't allowed" [8b72ad26-c8be-49a2-b99c-bcc3bf631b33] description = "non-numeric, non-space char in the middle with a sum that's divisible by 10 isn't allowed" ================================================ FILE: exercises/practice/luhn/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/luhn/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/luhn/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/luhn/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/luhn/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/luhn/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/luhn/luhn.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { valid } from './luhn.ts' describe('Luhn', () => { it('single digit strings can not be valid', () => { expect(valid('1')).toBeFalsy() }) xit('a single zero is invalid', () => { expect(valid('0')).toBeFalsy() }) xit('a simple valid SIN that remains valid if reversed', () => { expect(valid('059')).toBeTruthy() }) xit('a simple valid SIN that becomes invalid if reversed', () => { expect(valid('59')).toBeTruthy() }) xit('a valid Canadian SIN', () => { expect(valid('055 444 285')).toBeTruthy() }) xit('invalid Canadian SIN', () => { expect(valid('055 444 286')).toBeFalsy() }) xit('invalid credit card', () => { expect(valid('8273 1232 7352 0569')).toBeFalsy() }) xit('invalid long number with an even remainder', () => { expect(valid('1 2345 6789 1234 5678 9012')).toBeFalsy() }) xit('invalid long number with a remainder divisible by 5', () => { expect(valid('1 2345 6789 1234 5678 9013')).toBeFalsy() }) xit('valid number with an even number of digits', () => { expect(valid('095 245 88')).toBeTruthy() }) xit('valid number with an odd number of spaces', () => { expect(valid('234 567 891 234')).toBeTruthy() }) xit('valid strings with a non-digit added at the end become invalid', () => { expect(valid('059a')).toBeFalsy() }) xit('valid strings with punctuation included become invalid', () => { expect(valid('055-444-285')).toBeFalsy() }) xit('valid strings with symbols included become invalid', () => { expect(valid('055# 444$ 285')).toBeFalsy() }) xit('single zero with space is invalid', () => { expect(valid(' 0')).toBeFalsy() }) xit('more than a single zero is valid', () => { expect(valid('0000 0')).toBeTruthy() }) xit('input digit 9 is correctly converted to output digit 9', () => { expect(valid('091')).toBeTruthy() }) xit('very long input is valid', () => { expect(valid('9999999999 9999999999 9999999999 9999999999')).toBeTruthy() }) xit('valid luhn with an odd number of digits and non zero first digit', () => { expect(valid('109')).toBeTruthy() }) xit("using ascii value for non-doubled non-digit isn't allowed", () => { expect(valid('055b 444 285')).toBeFalsy() }) xit("using ascii value for doubled non-digit isn't allowed", () => { expect(valid(':9')).toBeFalsy() }) xit("non-numeric, non-space char in the middle with a sum that's divisible by 10 isn't allowed", () => { expect(valid('59%59')).toBeFalsy() }) }) ================================================ FILE: exercises/practice/luhn/luhn.ts ================================================ export function valid(digitString: unknown): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/luhn/package.json ================================================ { "name": "@exercism/typescript-luhn", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/luhn/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/luhn/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/matching-brackets/.docs/instructions.md ================================================ # Instructions Given a string containing brackets `[]`, braces `{}`, parentheses `()`, or any combination thereof, verify that any and all pairs are matched and nested correctly. Any other characters should be ignored. For example, `"{what is (42)}?"` is balanced and `"[text}"` is not. ================================================ FILE: exercises/practice/matching-brackets/.docs/introduction.md ================================================ # Introduction You're given the opportunity to write software for the Bracketeer™, an ancient but powerful mainframe. The software that runs on it is written in a proprietary language. Much of its syntax is familiar, but you notice _lots_ of brackets, braces and parentheses. Despite the Bracketeer™ being powerful, it lacks flexibility. If the source code has any unbalanced brackets, braces or parentheses, the Bracketeer™ crashes and must be rebooted. To avoid such a scenario, you start writing code that can verify that brackets, braces, and parentheses are balanced before attempting to run it on the Bracketeer™. ================================================ FILE: exercises/practice/matching-brackets/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "matching-brackets.ts" ], "test": [ "matching-brackets.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Make sure the brackets and braces all match.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Ginna Baker" } ================================================ FILE: exercises/practice/matching-brackets/.meta/proof.ci.ts ================================================ type LeftBracket = '(' | '[' | '{' type RightBracket = ')' | ']' | '}' type AnyBracket = LeftBracket | RightBracket function bracketsAreMatching( leftBracket: AnyBracket, rightBracket: AnyBracket ): boolean { return ( (leftBracket === '(' && rightBracket === ')') || (leftBracket === '[' && rightBracket === ']') || (leftBracket === '{' && rightBracket === '}') ) } export function isPaired(input: string): boolean { const brackets = input.replace(/[^{([\])}]/g, '') const openBrackets: AnyBracket[] = [] for (const letter of brackets) { const bracket = letter as AnyBracket if (openBrackets.length >= 1) { const lastBracket = openBrackets[openBrackets.length - 1] if (bracketsAreMatching(lastBracket, bracket)) { openBrackets.pop() } else { openBrackets.push(bracket) } } else { openBrackets.push(bracket) } } return openBrackets.length === 0 } ================================================ FILE: exercises/practice/matching-brackets/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [81ec11da-38dd-442a-bcf9-3de7754609a5] description = "paired square brackets" [287f0167-ac60-4b64-8452-a0aa8f4e5238] description = "empty string" [6c3615a3-df01-4130-a731-8ef5f5d78dac] description = "unpaired brackets" [9d414171-9b98-4cac-a4e5-941039a97a77] description = "wrong ordered brackets" [f0f97c94-a149-4736-bc61-f2c5148ffb85] description = "wrong closing bracket" [754468e0-4696-4582-a30e-534d47d69756] description = "paired with whitespace" [ba84f6ee-8164-434a-9c3e-b02c7f8e8545] description = "partially paired brackets" [3c86c897-5ff3-4a2b-ad9b-47ac3a30651d] description = "simple nested brackets" [2d137f2c-a19e-4993-9830-83967a2d4726] description = "several paired brackets" [2e1f7b56-c137-4c92-9781-958638885a44] description = "paired and nested brackets" [84f6233b-e0f7-4077-8966-8085d295c19b] description = "unopened closing brackets" [9b18c67d-7595-4982-b2c5-4cb949745d49] description = "unpaired and nested brackets" [a0205e34-c2ac-49e6-a88a-899508d7d68e] description = "paired and wrong nested brackets" [1d5c093f-fc84-41fb-8c2a-e052f9581602] description = "paired and wrong nested brackets but innermost are correct" [ef47c21b-bcfd-4998-844c-7ad5daad90a8] description = "paired and incomplete brackets" [a4675a40-a8be-4fc2-bc47-2a282ce6edbe] description = "too many closing brackets" [a345a753-d889-4b7e-99ae-34ac85910d1a] description = "early unexpected brackets" [21f81d61-1608-465a-b850-baa44c5def83] description = "early mismatched brackets" [99255f93-261b-4435-a352-02bdecc9bdf2] description = "math expression" [8e357d79-f302-469a-8515-2561877256a1] description = "complex latex expression" ================================================ FILE: exercises/practice/matching-brackets/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/matching-brackets/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/matching-brackets/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/matching-brackets/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/matching-brackets/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/matching-brackets/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/matching-brackets/matching-brackets.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { isPaired } from './matching-brackets.ts' describe('Matching Brackets', () => { it('paired square brackets', () => { expect(isPaired('[]')).toEqual(true) }) xit('empty string', () => { expect(isPaired('')).toEqual(true) }) xit('unpaired brackets', () => { expect(isPaired('[[')).toEqual(false) }) xit('wrong ordered brackets', () => { expect(isPaired('}{')).toEqual(false) }) xit('wrong closing bracket', () => { expect(isPaired('{]')).toEqual(false) }) xit('paired with whitespace', () => { expect(isPaired('{ }')).toEqual(true) }) xit('partially paired brackets', () => { expect(isPaired('{[])')).toEqual(false) }) xit('simple nested brackets', () => { expect(isPaired('{[]}')).toEqual(true) }) xit('several paired brackets', () => { expect(isPaired('{}[]')).toEqual(true) }) xit('paired and nested brackets', () => { expect(isPaired('([{}({}[])])')).toEqual(true) }) xit('unopened closing brackets', () => { expect(isPaired('{[)][]}')).toEqual(false) }) xit('unpaired and nested brackets', () => { expect(isPaired('([{])')).toEqual(false) }) xit('paired and wrong nested brackets', () => { expect(isPaired('[({]})')).toEqual(false) }) xit('paired and wrong nested brackets but innermost are correct', () => { expect(isPaired('[({}])')).toEqual(false) }) xit('paired and incomplete brackets', () => { expect(isPaired('{}[')).toEqual(false) }) xit('too many closing brackets', () => { expect(isPaired('[]]')).toEqual(false) }) xit('early unexpected brackets', () => { expect(isPaired(')()')).toEqual(false) }) xit('early mismatched brackets', () => { expect(isPaired('{)()')).toEqual(false) }) xit('math expression', () => { expect(isPaired('(((185 + 223.85) * 15) - 543)/2')).toEqual(true) }) xit('complex latex expression', () => { expect( isPaired( '\\left(\\begin{array}{cc} \\frac{1}{3} & x\\\\ \\mathrm{e}^{x} &... x^2 \\end{array}\\right)' ) ).toEqual(true) }) }) ================================================ FILE: exercises/practice/matching-brackets/matching-brackets.ts ================================================ export function isPaired(input: unknown): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/matching-brackets/package.json ================================================ { "name": "@exercism/typescript-matching-brackets", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/matching-brackets/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/matching-brackets/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/matrix/.docs/instructions.md ================================================ # Instructions Given a string representing a matrix of numbers, return the rows and columns of that matrix. So given a string with embedded newlines like: ```text 9 8 7 5 3 2 6 6 7 ``` representing this matrix: ```text 1 2 3 |--------- 1 | 9 8 7 2 | 5 3 2 3 | 6 6 7 ``` your code should be able to spit out: - A list of the rows, reading each row left-to-right while moving top-to-bottom across the rows, - A list of the columns, reading each column top-to-bottom while moving from left-to-right. The rows for our example matrix: - 9, 8, 7 - 5, 3, 2 - 6, 6, 7 And its columns: - 9, 5, 6 - 8, 3, 6 - 7, 2, 7 ================================================ FILE: exercises/practice/matrix/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "matrix.ts" ], "test": [ "matrix.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a string representing a matrix of numbers, return the rows and columns of that matrix.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", "source_url": "https://www.turing.edu/" } ================================================ FILE: exercises/practice/matrix/.meta/proof.ci.ts ================================================ export class Matrix { private description: string public rows: number[][] = new Array() public columns: number[][] = new Array() constructor(description: string) { this.description = description this.parseMatrix() } private parseMatrix(): void { this.parseRows() this.parseColumns() } private parseRows(): void { this.rows = this.description.split('\n').map((row) => { return row.split(' ').map((char) => { return parseInt(char, 10) }) }) } private parseColumns(): void { const rowsLength = this.rows.length const columnsLength = this.rows[0].length for (let i = 0; i < columnsLength; i++) { this.columns.push([]) for (let j = 0; j < rowsLength; j++) { this.columns[i].push(this.rows[j][i]) } } } } ================================================ FILE: exercises/practice/matrix/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [ca733dab-9d85-4065-9ef6-a880a951dafd] description = "extract row from one number matrix" [5c93ec93-80e1-4268-9fc2-63bc7d23385c] description = "can extract row" [2f1aad89-ad0f-4bd2-9919-99a8bff0305a] description = "extract row where numbers have different widths" [68f7f6ba-57e2-4e87-82d0-ad09889b5204] description = "can extract row from non-square matrix with no corresponding column" [e8c74391-c93b-4aed-8bfe-f3c9beb89ebb] description = "extract column from one number matrix" [7136bdbd-b3dc-48c4-a10c-8230976d3727] description = "can extract column" [ad64f8d7-bba6-4182-8adf-0c14de3d0eca] description = "can extract column from non-square matrix with no corresponding row" [9eddfa5c-8474-440e-ae0a-f018c2a0dd89] description = "extract column where numbers have different widths" ================================================ FILE: exercises/practice/matrix/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/matrix/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/matrix/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/matrix/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/matrix/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/matrix/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/matrix/matrix.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { Matrix } from './matrix.ts' describe('Matrix', () => { it('extract row from one number matrix', () => { const expected = [1] expect(new Matrix('1').rows[0]).toEqual(expected) }) xit('can extract row', () => { const expected = [3, 4] expect(new Matrix('1 2\n3 4').rows[1]).toEqual(expected) }) xit('extract row where numbers have different widths', () => { const expected = [10, 20] expect(new Matrix('1 2\n10 20').rows[1]).toEqual(expected) }) xit('can extract row from non-square matrix', () => { const expected = [7, 8, 9] expect(new Matrix('1 2 3\n4 5 6\n7 8 9\n8 7 6').rows[2]).toEqual(expected) }) xit('extract column from one number matrix', () => { const expected = [1] expect(new Matrix('1').columns[0]).toEqual(expected) }) xit('can extract column', () => { const expected = [3, 6, 9] expect(new Matrix('1 2 3\n4 5 6\n7 8 9').columns[2]).toEqual(expected) }) xit('can extract column from non-square matrix', () => { const expected = [3, 6, 9, 6] expect(new Matrix('1 2 3\n4 5 6\n7 8 9\n8 7 6').columns[2]).toEqual( expected ) }) xit('extract column where numbers have different widths', () => { const expected = [1903, 3, 4] expect(new Matrix('89 1903 3\n18 3 1\n9 4 800').columns[1]).toEqual( expected ) }) }) ================================================ FILE: exercises/practice/matrix/matrix.ts ================================================ export class Matrix { constructor() { throw new Error('Remove this line and implement the function') } get rows(): unknown { throw new Error('Remove this line and implement the function') } get columns(): unknown { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/matrix/package.json ================================================ { "name": "@exercism/typescript-matrix", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/matrix/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/matrix/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/minesweeper/.docs/instructions.md ================================================ # Instructions Your task is to add the mine counts to empty squares in a completed Minesweeper board. The board itself is a rectangle composed of squares that are either empty (`' '`) or a mine (`'*'`). For each empty square, count the number of mines adjacent to it (horizontally, vertically, diagonally). If the empty square has no adjacent mines, leave it empty. Otherwise replace it with the adjacent mines count. For example, you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen): ```text ·*·*· ··*·· ··*·· ····· ``` Which your code should transform into this: ```text 1*3*1 13*31 ·2*2· ·111· ``` ================================================ FILE: exercises/practice/minesweeper/.docs/introduction.md ================================================ # Introduction [Minesweeper][wikipedia] is a popular game where the user has to find the mines using numeric hints that indicate how many mines are directly adjacent (horizontally, vertically, diagonally) to a square. [wikipedia]: https://en.wikipedia.org/wiki/Minesweeper_(video_game) ================================================ FILE: exercises/practice/minesweeper/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "iignatov", "masters3d", "SleeplessByte" ], "files": { "solution": [ "minesweeper.ts" ], "test": [ "minesweeper.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Add the numbers to a minesweeper board.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false } } ================================================ FILE: exercises/practice/minesweeper/.meta/proof.ci.ts ================================================ const MINE = '*' const DELTAS = [ [-1, -1], [-1, 0], [-1, 1], [1, 1], [1, 0], [1, -1], [0, 1], [0, -1], ] export function annotate(rows: string[]): string[] { if (noDataPresent(rows)) { return rows } const inputBoard = rows.map((row) => [...row]) const result = inputBoard.map((row, x) => [...row].map((cell, y) => cellToMineOrCount(cell, inputBoard, x, y)) ) return stringify(result as string[][]) } function cellToMineOrCount( cell: string, inputBoard: string[][], x: number, y: number ): number | '*' | ' ' { if (cell === MINE) { return MINE } return countAdjacentMines(inputBoard, x, y) || ' ' } function countAdjacentMines(board: string[][], x: number, y: number): number { return DELTAS.filter((d) => adjacentSquareIsOnBoard(board, x, d)).filter( (d) => adjacentSquareHasMine(board, x, y, d) ).length } function stringify(board: string[][]): string[] { return board.map((row) => row.join('')) } function noDataPresent(rows: string[]): boolean { return rows.length === 0 || rows[0].length === 0 } function adjacentSquareIsOnBoard( board: string[][], x: number, d: number[] ): string[] { return board[x + d[0]] } function adjacentSquareHasMine( board: string[][], x: number, y: number, d: number[] ): boolean { return board[x + d[0]][y + d[1]] === MINE } ================================================ FILE: exercises/practice/minesweeper/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [0c5ec4bd-dea7-4138-8651-1203e1cb9f44] description = "no rows" [650ac4c0-ad6b-4b41-acde-e4ea5852c3b8] description = "no columns" [6fbf8f6d-a03b-42c9-9a58-b489e9235478] description = "no mines" [61aff1c4-fb31-4078-acad-cd5f1e635655] description = "minefield with only mines" [84167147-c504-4896-85d7-246b01dea7c5] description = "mine surrounded by spaces" [cb878f35-43e3-4c9d-93d9-139012cccc4a] description = "space surrounded by mines" [7037f483-ddb4-4b35-b005-0d0f4ef4606f] description = "horizontal line" [e359820f-bb8b-4eda-8762-47b64dba30a6] description = "horizontal line, mines at edges" [c5198b50-804f-47e9-ae02-c3b42f7ce3ab] description = "vertical line" [0c79a64d-703d-4660-9e90-5adfa5408939] description = "vertical line, mines at edges" [4b098563-b7f3-401c-97c6-79dd1b708f34] description = "cross" [04a260f1-b40a-4e89-839e-8dd8525abe0e] description = "large minefield" ================================================ FILE: exercises/practice/minesweeper/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/minesweeper/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/minesweeper/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/minesweeper/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/minesweeper/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/minesweeper/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/minesweeper/minesweeper.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { annotate } from './minesweeper.ts' describe('Minesweeper annotate', () => { it('handles no rows', () => { expect(annotate([])).toEqual([]) }) xit('handles no columns', () => { expect(annotate([''])).toEqual(['']) }) xit('handles no mines', () => { const input = [' ', ' ', ' '] const expected = [' ', ' ', ' '] expect(annotate(input)).toEqual(expected) }) xit('handles board with only mines', () => { const input = ['***', '***', '***'] const expected = ['***', '***', '***'] expect(annotate(input)).toEqual(expected) }) xit('handles mine surrounded by spaces', () => { const input = [' ', ' * ', ' '] const expected = ['111', '1*1', '111'] expect(annotate(input)).toEqual(expected) }) xit('handles space surrounded by mines', () => { const input = ['***', '* *', '***'] const expected = ['***', '*8*', '***'] expect(annotate(input)).toEqual(expected) }) xit('handles horizontal line', () => { const input = [' * * '] const expected = ['1*2*1'] expect(annotate(input)).toEqual(expected) }) xit('handles horizontal line, mines at edges', () => { const input = ['* *'] const expected = ['*1 1*'] expect(annotate(input)).toEqual(expected) }) xit('handles vertical line', () => { const input = [' ', '*', ' ', '*', ' '] const expected = ['1', '*', '2', '*', '1'] expect(annotate(input)).toEqual(expected) }) xit('handles vertical line, mines at edges', () => { const input = ['*', ' ', ' ', ' ', '*'] const expected = ['*', '1', ' ', '1', '*'] expect(annotate(input)).toEqual(expected) }) xit('handles cross', () => { const input = [' * ', ' * ', '*****', ' * ', ' * '] const expected = [' 2*2 ', '25*52', '*****', '25*52', ' 2*2 '] expect(annotate(input)).toEqual(expected) }) xit('handles large board', () => { const input = [' * * ', ' * ', ' * ', ' * *', ' * * ', ' '] const expected = [ '1*22*1', '12*322', ' 123*2', '112*4*', '1*22*2', '111111', ] expect(annotate(input)).toEqual(expected) }) }) ================================================ FILE: exercises/practice/minesweeper/minesweeper.ts ================================================ export function annotate(field: unknown): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/minesweeper/package.json ================================================ { "name": "@exercism/typescript-minesweeper", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/minesweeper/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/minesweeper/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/nth-prime/.docs/instructions.md ================================================ # Instructions Given a number n, determine what the nth prime is. By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13. If your language provides methods in the standard library to deal with prime numbers, pretend they don't exist and implement them yourself. ================================================ FILE: exercises/practice/nth-prime/.meta/config.json ================================================ { "authors": [], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "nth-prime.ts" ], "test": [ "nth-prime.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a number n, determine what the nth prime is.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "A variation on Problem 7 at Project Euler", "source_url": "https://projecteuler.net/problem=7" } ================================================ FILE: exercises/practice/nth-prime/.meta/proof.ci.ts ================================================ const isPrime = (n: number): boolean => { for (let i = 2; i * i <= n; i++) { if (n % i === 0) { return false } } return true } const range = (min: number, max: number): number[] => { const result: number[] = [] for (let i = min; i < max; i++) { result.push(i) } return result } export function nth(nthPrime: number): number { if (nthPrime === 0) { throw new Error('Prime is not possible') } return range(2, 2e6).filter(isPrime)[nthPrime - 1] } ================================================ FILE: exercises/practice/nth-prime/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [75c65189-8aef-471a-81de-0a90c728160c] description = "first prime" [2c38804c-295f-4701-b728-56dea34fd1a0] description = "second prime" [56692534-781e-4e8c-b1f9-3e82c1640259] description = "sixth prime" [fce1e979-0edb-412d-93aa-2c744e8f50ff] description = "big prime" [bd0a9eae-6df7-485b-a144-80e13c7d55b2] description = "there is no zeroth prime" ================================================ FILE: exercises/practice/nth-prime/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/nth-prime/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/nth-prime/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/nth-prime/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/nth-prime/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/nth-prime/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/nth-prime/nth-prime.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { nth } from './nth-prime.ts' describe('Prime', () => { it('first', () => { expect(nth(1)).toEqual(2) }) xit('second', () => { expect(nth(2)).toEqual(3) }) xit('sixth', () => { expect(nth(6)).toEqual(13) }) xit('big prime', () => { expect(nth(10001)).toEqual(104743) }) xit('weird case', () => { expect(() => nth(0)).toThrow('Prime is not possible') }) }) ================================================ FILE: exercises/practice/nth-prime/nth-prime.ts ================================================ export function nth() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/nth-prime/package.json ================================================ { "name": "@exercism/typescript-nth-prime", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/nth-prime/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/nth-prime/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/nucleotide-count/.docs/instructions.md ================================================ # Instructions Each of us inherits from our biological parents a set of chemical instructions known as DNA that influence how our bodies are constructed. All known life depends on DNA! > Note: You do not need to understand anything about nucleotides or DNA to complete this exercise. DNA is a long chain of other chemicals and the most important are the four nucleotides, adenine, cytosine, guanine and thymine. A single DNA chain can contain billions of these four nucleotides and the order in which they occur is important! We call the order of these nucleotides in a bit of DNA a "DNA sequence". We represent a DNA sequence as an ordered collection of these four nucleotides and a common way to do that is with a string of characters such as "ATTACG" for a DNA sequence of 6 nucleotides. 'A' for adenine, 'C' for cytosine, 'G' for guanine, and 'T' for thymine. Given a string representing a DNA sequence, count how many of each nucleotide is present. If the string contains characters that aren't A, C, G, or T then it is invalid and you should signal an error. For example: ```text "GATTACA" -> 'A': 3, 'C': 1, 'G': 1, 'T': 2 "INVALID" -> error ``` ================================================ FILE: exercises/practice/nucleotide-count/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "nucleotide-count.ts" ], "test": [ "nucleotide-count.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a DNA string, compute how many times each nucleotide occurs in the string.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "The Calculating DNA Nucleotides_problem at Rosalind", "source_url": "https://rosalind.info/problems/dna/" } ================================================ FILE: exercises/practice/nucleotide-count/.meta/proof.ci.ts ================================================ type Counts = Record<'A' | 'C' | 'G' | 'T', number> export function nucleotideCounts(strand: string): Counts { const nucleotideOccurrences: Counts = { A: 0, C: 0, G: 0, T: 0, } strand.split('').forEach((nucleotide) => { if (nucleotide in nucleotideOccurrences) { if (nucleotide === 'A') { nucleotideOccurrences.A++ } if (nucleotide === 'C') { nucleotideOccurrences.C++ } if (nucleotide === 'G') { nucleotideOccurrences.G++ } if (nucleotide === 'T') { nucleotideOccurrences.T++ } } else { throw new Error('Invalid nucleotide in strand') } }) return nucleotideOccurrences } ================================================ FILE: exercises/practice/nucleotide-count/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [3e5c30a8-87e2-4845-a815-a49671ade970] description = "empty strand" [a0ea42a6-06d9-4ac6-828c-7ccaccf98fec] description = "can count one nucleotide in single-character input" [eca0d565-ed8c-43e7-9033-6cefbf5115b5] description = "strand with repeated nucleotide" [40a45eac-c83f-4740-901a-20b22d15a39f] description = "strand with multiple nucleotides" [b4c47851-ee9e-4b0a-be70-a86e343bd851] description = "strand with invalid nucleotides" ================================================ FILE: exercises/practice/nucleotide-count/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/nucleotide-count/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/nucleotide-count/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/nucleotide-count/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/nucleotide-count/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/nucleotide-count/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/nucleotide-count/nucleotide-count.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { nucleotideCounts } from './nucleotide-count.ts' describe('count all nucleotides in a strand', () => { it('empty strand', () => { const expected = { A: 0, C: 0, G: 0, T: 0, } expect(nucleotideCounts('')).toEqual(expected) }) xit('can count one nucleotide in single-character input', () => { const expected = { A: 0, C: 0, G: 1, T: 0, } expect(nucleotideCounts('G')).toEqual(expected) }) xit('strand with repeated nucleotide', () => { const expected = { A: 0, C: 0, G: 7, T: 0, } expect(nucleotideCounts('GGGGGGG')).toEqual(expected) }) xit('strand with multiple nucleotides', () => { const expected = { A: 20, C: 12, G: 17, T: 21, } expect( nucleotideCounts( 'AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC' ) ).toEqual(expected) }) xit('strand with invalid nucleotides', () => { const expected = 'Invalid nucleotide in strand' expect(() => { nucleotideCounts('AGXXACT') }).toThrow(expected) }) }) ================================================ FILE: exercises/practice/nucleotide-count/nucleotide-count.ts ================================================ export function nucleotideCounts(/* Parameters go here */) { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/nucleotide-count/package.json ================================================ { "name": "@exercism/typescript-nucleotide-count", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/nucleotide-count/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/nucleotide-count/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/ocr-numbers/.docs/instructions.md ================================================ # Instructions Optical Character Recognition or OCR is software that converts images of text into machine-readable text. Given a grid of characters representing some digits, convert the grid to a string of digits. If the grid has multiple rows of cells, the rows should be separated in the output with a `","`. - The grid is made of one of more lines of cells. - Each line of the grid is made of one or more cells. - Each cell is three columns wide and four rows high (3x4) and represents one digit. - Digits are drawn using pipes (`"|"`), underscores (`"_"`), and spaces (`" "`). ## Edge cases - If the input is not a valid size, your program should indicate there is an error. - If the input is the correct size, but a cell is not recognizable, your program should output a `"?"` for that character. ## Examples The following input (without the comments) is converted to `"1234567890"`. ```text _ _ _ _ _ _ _ _ # | _| _||_||_ |_ ||_||_|| | # Decimal numbers. ||_ _| | _||_| ||_| _||_| # # The fourth line is always blank, ``` The following input is converted to `"123,456,789"`. ```text _ _ | _| _| ||_ _| _ _ |_||_ |_ | _||_| _ _ _ ||_||_| ||_| _| ``` ================================================ FILE: exercises/practice/ocr-numbers/.docs/introduction.md ================================================ # Introduction Your best friend Marta recently landed their dream job working with a local history museum's collections. Knowing of your interests in programming, they confide in you about an issue at work for an upcoming exhibit on computing history. A local university's math department had donated several boxes of historical printouts, but given the poor condition of the documents, the decision has been made to digitize the text. However, the university's old printer had some quirks in how text was represented, and your friend could use your help to extract the data successfully. ================================================ FILE: exercises/practice/ocr-numbers/.meta/config.json ================================================ { "authors": [ "mdowds" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "ocr-numbers.ts" ], "test": [ "ocr-numbers.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a 3 x 4 grid of pipes, underscores, and spaces, determine which number is represented, or whether it is garbled.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Inspired by the Bank OCR kata", "source_url": "https://codingdojo.org/kata/BankOCR/" } ================================================ FILE: exercises/practice/ocr-numbers/.meta/proof.ci.ts ================================================ interface Patterns { [key: number]: string[] } const PATTERNS: Patterns = { 0: [' _ ', '| |', '|_|', ' '], 1: [' ', ' |', ' |', ' '], 2: [' _ ', ' _|', '|_ ', ' '], 3: [' _ ', ' _|', ' _|', ' '], 4: [' ', '|_|', ' |', ' '], 5: [' _ ', '|_ ', ' _|', ' '], 6: [' _ ', '|_ ', '|_|', ' '], 7: [' _ ', ' |', ' |', ' '], 8: [' _ ', '|_|', '|_|', ' '], 9: [' _ ', '|_|', ' _|', ' '], } type Digit = number | '?' const splitIntoRows = (text: string): string[] => { const rows: string[] = [] const lines = text.split('\n') for (let rowNumber = 0; rowNumber < lines.length; rowNumber += 4) { let row = '' for (let rowLine = 0; rowLine < 4; rowLine += 1) { row += `${lines[rowNumber + rowLine]}\n` } rows.push(row.slice(0, -1)) } return rows } const splitIntoDigits = (row: string): string[] => { const digits: string[] = [] const rows = row.split('\n') for (let digitNumber = 0; digitNumber < rows[0].length; digitNumber += 3) { let digit = '' for (let rowNumber = 0; rowNumber < rows.length; rowNumber += 1) { digit += rows[rowNumber].substr(digitNumber, 3) } digits.push(digit) } return digits } const getDigit = (text: string): Digit => { const digit = ( Object.values(PATTERNS) as (typeof PATTERNS)[keyof typeof PATTERNS][] ) .map((x) => x.join('')) .indexOf(text) if (digit === -1) { return '?' } return digit } const valuesInRow = (row: string): string => splitIntoDigits(row).map(getDigit).join('') export const convert = (text: string): string => splitIntoRows(text).map(valuesInRow).join(',') ================================================ FILE: exercises/practice/ocr-numbers/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [5ee54e1a-b554-4bf3-a056-9a7976c3f7e8] description = "Recognizes 0" [027ada25-17fd-4d78-aee6-35a19623639d] description = "Recognizes 1" [3cce2dbd-01d9-4f94-8fae-419a822e89bb] description = "Unreadable but correctly sized inputs return ?" [cb19b733-4e36-4cf9-a4a1-6e6aac808b9a] description = "Input with a number of lines that is not a multiple of four raises an error" [235f7bd1-991b-4587-98d4-84206eec4cc6] description = "Input with a number of columns that is not a multiple of three raises an error" [4a841794-73c9-4da9-a779-1f9837faff66] description = "Recognizes 110101100" [70c338f9-85b1-4296-a3a8-122901cdfde8] description = "Garbled numbers in a string are replaced with ?" [ea494ff4-3610-44d7-ab7e-72fdef0e0802] description = "Recognizes 2" [1acd2c00-412b-4268-93c2-bd7ff8e05a2c] description = "Recognizes 3" [eaec6a15-be17-4b6d-b895-596fae5d1329] description = "Recognizes 4" [440f397a-f046-4243-a6ca-81ab5406c56e] description = "Recognizes 5" [f4c9cf6a-f1e2-4878-bfc3-9b85b657caa0] description = "Recognizes 6" [e24ebf80-c611-41bb-a25a-ac2c0f232df5] description = "Recognizes 7" [b79cad4f-e264-4818-9d9e-77766792e233] description = "Recognizes 8" [5efc9cfc-9227-4688-b77d-845049299e66] description = "Recognizes 9" [f60cb04a-42be-494e-a535-3451c8e097a4] description = "Recognizes string of decimal numbers" [b73ecf8b-4423-4b36-860d-3710bdb8a491] description = "Numbers separated by empty lines are recognized. Lines are joined by commas." ================================================ FILE: exercises/practice/ocr-numbers/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/ocr-numbers/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/ocr-numbers/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/ocr-numbers/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/ocr-numbers/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/ocr-numbers/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/ocr-numbers/ocr-numbers.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { convert } from './ocr-numbers.ts' describe('ocr', () => { it('recognizes zero', () => { expect(convert(' _ \n' + '| |\n' + '|_|\n' + ' ')).toBe('0') }) xit('recognizes one', () => { expect(convert(' \n' + ' |\n' + ' |\n' + ' ')).toBe('1') }) xit('recognizes two', () => { expect(convert(' _ \n' + ' _|\n' + '|_ \n' + ' ')).toBe('2') }) xit('recognizes three', () => { expect(convert(' _ \n' + ' _|\n' + ' _|\n' + ' ')).toBe('3') }) xit('recognizes four', () => { expect(convert(' \n' + '|_|\n' + ' |\n' + ' ')).toBe('4') }) xit('recognizes five', () => { expect(convert(' _ \n' + '|_ \n' + ' _|\n' + ' ')).toBe('5') }) xit('recognizes six', () => { expect(convert(' _ \n' + '|_ \n' + '|_|\n' + ' ')).toBe('6') }) xit('recognizes seven', () => { expect(convert(' _ \n' + ' |\n' + ' |\n' + ' ')).toBe('7') }) xit('recognizes eight', () => { expect(convert(' _ \n' + '|_|\n' + '|_|\n' + ' ')).toBe('8') }) xit('recognizes nine', () => { expect(convert(' _ \n' + '|_|\n' + ' _|\n' + ' ')).toBe('9') }) xit('recognizes ten', () => { expect(convert(' _ \n' + ' || |\n' + ' ||_|\n' + ' ')).toBe('10') }) xit('identifies garble', () => { expect(convert(' \n' + '| |\n' + '| |\n' + ' ')).toBe('?') }) xit('converts 110101100', () => { expect( convert( ' _ _ _ _ \n' + ' | || | || | | || || |\n' + ' | ||_| ||_| | ||_||_|\n' + ' ' ) ).toBe('110101100') }) xit('identifies garble mixed in', () => { expect( convert( ' _ _ _ \n' + ' | || | || | || || |\n' + ' | | _| ||_| | ||_||_|\n' + ' ' ) ).toBe('11?10?1?0') }) xit('converts 1234567890', () => { expect( convert( ' _ _ _ _ _ _ _ _ \n' + ' | _| _||_||_ |_ ||_||_|| |\n' + ' ||_ _| | _||_| ||_| _||_|\n' + ' ' ) ).toBe('1234567890') }) xit('converts 123 456 789', () => { expect( convert( ' _ _ \n' + ' | _| _|\n' + ' ||_ _|\n' + ' \n' + ' _ _ \n' + '|_||_ |_ \n' + ' | _||_|\n' + ' \n' + ' _ _ _ \n' + ' ||_||_|\n' + ' ||_| _|\n' + ' ' ) ).toBe('123,456,789') }) }) ================================================ FILE: exercises/practice/ocr-numbers/ocr-numbers.ts ================================================ export function convert(/* Parameters go here */) { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/ocr-numbers/package.json ================================================ { "name": "@exercism/typescript-ocr-numbers", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/ocr-numbers/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/ocr-numbers/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/palindrome-products/.docs/instructions.md ================================================ # Instructions Detect palindrome products in a given range. A palindromic number is a number that remains the same when its digits are reversed. For example, `121` is a palindromic number but `112` is not. Given a range of numbers, find the largest and smallest palindromes which are products of two numbers within that range. Your solution should return the largest and smallest palindromes, along with the factors of each within the range. If the largest or smallest palindrome has more than one pair of factors within the range, then return all the pairs. ## Example 1 Given the range `[1, 9]` (both inclusive)... And given the list of all possible products within this range: `[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 15, 21, 24, 27, 20, 28, 32, 36, 25, 30, 35, 40, 45, 42, 48, 54, 49, 56, 63, 64, 72, 81]` The palindrome products are all single digit numbers (in this case): `[1, 2, 3, 4, 5, 6, 7, 8, 9]` The smallest palindrome product is `1`. Its factors are `(1, 1)`. The largest palindrome product is `9`. Its factors are `(1, 9)` and `(3, 3)`. ## Example 2 Given the range `[10, 99]` (both inclusive)... The smallest palindrome product is `121`. Its factors are `(11, 11)`. The largest palindrome product is `9009`. Its factors are `(91, 99)`. ================================================ FILE: exercises/practice/palindrome-products/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "averaart", "masters3d", "SleeplessByte" ], "files": { "solution": [ "palindrome-products.ts" ], "test": [ "palindrome-products.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Detect palindrome products in a given range.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": true, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Problem 4 at Project Euler", "source_url": "https://projecteuler.net/problem=4" } ================================================ FILE: exercises/practice/palindrome-products/.meta/proof.ci.ts ================================================ interface Input { maxFactor: number minFactor?: number } type Factors = [number, number][] interface PalindromeShape { value: number | null factors: Factors } const reverseString = (str: string): string => str.split('').reverse().join('') class Palindrome implements PalindromeShape { public readonly value: number public readonly factors: Factors constructor(factor1: number, factor2: number) { this.value = factor1 * factor2 this.factors = [[factor1, factor2].sort() as [number, number]] } public withFactors(factors: Factors[number]): this { this.factors.push(factors.sort()) this.factors.sort() return this } public valid(): boolean { const s = `${this.value}` return s === reverseString(s) } public merge(other: Palindrome): this { other.factors.forEach((f) => { this.factors.push(f) }) this.factors.sort() return this } } class Palindromes { constructor( public maxFactor: number, public minFactor = 1 ) {} public get largest(): PalindromeShape { let best = new Palindrome(this.minFactor, this.minFactor) for (let m = this.maxFactor; m >= this.minFactor; m -= 1) { let p = null for (let n = m; n >= this.minFactor && (!p || !p.valid()); n -= 1) { p = new Palindrome(m, n) if (p.valid()) { if (best.value < p.value) { best = p } else if (best.value === p.value) { best = p.merge(best) } } } } if (best.valid()) { return best } return { value: null, factors: [] } } public get smallest(): PalindromeShape { let smallest: PalindromeShape = { value: null, factors: [] } for (let m = this.minFactor; m <= this.maxFactor; m += 1) { for (let n = this.minFactor; n <= this.maxFactor; n += 1) { const p = new Palindrome(m, n) if (p.valid() && (!smallest.value || smallest.value > p.value)) { smallest = p } } } return smallest } } export function generate(params: Input): Palindromes { if ((params.minFactor || 1) > params.maxFactor) { throw new Error('min must be <= max') } return new Palindromes(params.maxFactor, params.minFactor || 1) } ================================================ FILE: exercises/practice/palindrome-products/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [5cff78fe-cf02-459d-85c2-ce584679f887] description = "find the smallest palindrome from single digit factors" [0853f82c-5fc4-44ae-be38-fadb2cced92d] description = "find the largest palindrome from single digit factors" [66c3b496-bdec-4103-9129-3fcb5a9063e1] description = "find the smallest palindrome from double digit factors" [a10682ae-530a-4e56-b89d-69664feafe53] description = "find the largest palindrome from double digit factors" [cecb5a35-46d1-4666-9719-fa2c3af7499d] description = "find the smallest palindrome from triple digit factors" [edab43e1-c35f-4ea3-8c55-2f31dddd92e5] description = "find the largest palindrome from triple digit factors" [4f802b5a-9d74-4026-a70f-b53ff9234e4e] description = "find the smallest palindrome from four digit factors" [787525e0-a5f9-40f3-8cb2-23b52cf5d0be] description = "find the largest palindrome from four digit factors" [58fb1d63-fddb-4409-ab84-a7a8e58d9ea0] description = "empty result for smallest if no palindrome in the range" [9de9e9da-f1d9-49a5-8bfc-3d322efbdd02] description = "empty result for largest if no palindrome in the range" [12e73aac-d7ee-4877-b8aa-2aa3dcdb9f8a] description = "error result for smallest if min is more than max" [eeeb5bff-3f47-4b1e-892f-05829277bd74] description = "error result for largest if min is more than max" [16481711-26c4-42e0-9180-e2e4e8b29c23] description = "smallest product does not use the smallest factor" ================================================ FILE: exercises/practice/palindrome-products/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/palindrome-products/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/palindrome-products/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/palindrome-products/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/palindrome-products/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/palindrome-products/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/palindrome-products/package.json ================================================ { "name": "@exercism/typescript-palindrome-products", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/palindrome-products/palindrome-products.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { generate } from './palindrome-products.ts' describe('Palindromes', () => { it('smallest palindrome from single digit factors', () => { const palindromes = generate({ maxFactor: 9, minFactor: 1 }) const smallest = palindromes.smallest const expected = { value: 1, factors: [[1, 1]] } expect(smallest.value).toEqual(expected.value) expect(sortFactors(smallest.factors)).toEqual(expected.factors) }) xit('largest palindrome from single digit factors', () => { const palindromes = generate({ maxFactor: 9, minFactor: 1 }) const largest = palindromes.largest const expected = { value: 9, factors: [ [1, 9], [3, 3], ], } expect(largest.value).toEqual(expected.value) expect(sortFactors(largest.factors)).toEqual(expected.factors) }) xit('smallest palindrome from double digit factors', () => { const palindromes = generate({ maxFactor: 99, minFactor: 10 }) const smallest = palindromes.smallest const expected = { value: 121, factors: [[11, 11]] } expect(smallest.value).toEqual(expected.value) expect(sortFactors(smallest.factors)).toEqual(expected.factors) }) xit('largest palindrome from double digit factors', () => { const palindromes = generate({ maxFactor: 99, minFactor: 10 }) const largest = palindromes.largest const expected = { value: 9009, factors: [[91, 99]] } expect(largest.value).toEqual(expected.value) expect(sortFactors(largest.factors)).toEqual(expected.factors) }) xit('smallest palindrome from triple digit factors', () => { const palindromes = generate({ maxFactor: 999, minFactor: 100, }) const smallest = palindromes.smallest const expected = { value: 10201, factors: [[101, 101]] } expect(smallest.value).toEqual(expected.value) expect(sortFactors(smallest.factors)).toEqual(expected.factors) }) xit('largest palindrome from triple digit factors', () => { const palindromes = generate({ maxFactor: 999, minFactor: 100, }) const largest = palindromes.largest const expected = { value: 906609, factors: [[913, 993]] } expect(largest.value).toEqual(expected.value) expect(sortFactors(largest.factors)).toEqual(expected.factors) }) xit('smallest palindrome from four digit factors', () => { const palindromes = generate({ maxFactor: 9999, minFactor: 1000, }) const smallest = palindromes.smallest const expected = { value: 1002001, factors: [[1001, 1001]] } expect(smallest.value).toEqual(expected.value) expect(sortFactors(smallest.factors)).toEqual(expected.factors) }) xit('largest palindrome from four digit factors', () => { const palindromes = generate({ maxFactor: 9999, minFactor: 1000, }) const largest = palindromes.largest const expected = { value: 99000099, factors: [[9901, 9999]] } expect(largest.value).toEqual(expected.value) expect(sortFactors(largest.factors)).toEqual(expected.factors) }) xit('empty result for smallest if no palindrome in range', () => { const palindromes = generate({ maxFactor: 1003, minFactor: 1002, }) const smallest = palindromes.smallest expect(smallest.value).toBe(null) expect(smallest.factors).toEqual([]) }) xit('empty result for largest if no palindrome in range', () => { const palindromes = generate({ maxFactor: 15, minFactor: 15 }) const largest = palindromes.largest expect(largest.value).toBe(null) expect(largest.factors).toEqual([]) }) xit('error for smallest if min is more than max', () => { expect(() => { const palindromes = generate({ maxFactor: 1, minFactor: 10000, }) // eslint-disable-next-line @typescript-eslint/no-unused-expressions palindromes.smallest }).toThrow(new Error('min must be <= max')) }) xit('error for largest if min is more than max', () => { expect(() => { const palindromes = generate({ maxFactor: 1, minFactor: 2 }) // eslint-disable-next-line @typescript-eslint/no-unused-expressions palindromes.largest }).toThrow(new Error('min must be <= max')) }) xit('smallest product does not use the smallest factor', () => { const palindromes = generate({ maxFactor: 4000, minFactor: 3215, }) const smallest = palindromes.smallest const expected = { value: 10988901, factors: [[3297, 3333]] } expect(smallest.value).toEqual(expected.value) expect(sortFactors(smallest.factors)).toEqual(expected.factors) }) }) type Factors = ReturnType['smallest']['factors'] function sortFactors(factors: Factors): Factors { return factors.map((f) => f.sort()).sort() } ================================================ FILE: exercises/practice/palindrome-products/palindrome-products.ts ================================================ interface Input { maxFactor: number minFactor?: number } export function generate(params: Input): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/palindrome-products/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/palindrome-products/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/pangram/.docs/instructions.md ================================================ # Instructions Your task is to figure out if a sentence is a pangram. A pangram is a sentence using every letter of the alphabet at least once. It is case insensitive, so it doesn't matter if a letter is lower-case (e.g. `k`) or upper-case (e.g. `K`). For this exercise, a sentence is a pangram if it contains each of the 26 letters in the English alphabet. ================================================ FILE: exercises/practice/pangram/.docs/introduction.md ================================================ # Introduction You work for a company that sells fonts through their website. They'd like to show a different sentence each time someone views a font on their website. To give a comprehensive sense of the font, the random sentences should use **all** the letters in the English alphabet. They're running a competition to get suggestions for sentences that they can use. You're in charge of checking the submissions to see if they are valid. ~~~~exercism/note Pangram comes from Greek, παν γράμμα, pan gramma, which means "every letter". The best known English pangram is: > The quick brown fox jumps over the lazy dog. ~~~~ ================================================ FILE: exercises/practice/pangram/.meta/config.json ================================================ { "authors": [ "masters3d" ], "contributors": [ "CRivasGomez", "DFXLuna", "lukaszklis", "SleeplessByte" ], "files": { "solution": [ "pangram.ts" ], "test": [ "pangram.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Determine if a sentence is a pangram.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Pangram" } ================================================ FILE: exercises/practice/pangram/.meta/proof.ci.ts ================================================ const aToZ = [...Array(26)].map((_, index) => { return String.fromCharCode(index + 65) }) export function isPangram(value: string): boolean { const myMap = new Map() aToZ.forEach((key: string) => { myMap.set(key.toLowerCase(), 0) }) for (const each of value) { const item = myMap.get(each) || 0 myMap.set(each.toLowerCase(), item + 1) } for (const each of myMap.values()) { if (each === 0) { return false } } return true } ================================================ FILE: exercises/practice/pangram/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [64f61791-508e-4f5c-83ab-05de042b0149] description = "empty sentence" [74858f80-4a4d-478b-8a5e-c6477e4e4e84] description = "perfect lower case" [61288860-35ca-4abe-ba08-f5df76ecbdcd] description = "only lower case" [6564267d-8ac5-4d29-baf2-e7d2e304a743] description = "missing the letter 'x'" [c79af1be-d715-4cdb-a5f2-b2fa3e7e0de0] description = "missing the letter 'h'" [d835ec38-bc8f-48e4-9e36-eb232427b1df] description = "with underscores" [8cc1e080-a178-4494-b4b3-06982c9be2a8] description = "with numbers" [bed96b1c-ff95-45b8-9731-fdbdcb6ede9a] description = "missing letters replaced by numbers" [938bd5d8-ade5-40e2-a2d9-55a338a01030] description = "mixed case and punctuation" [2577bf54-83c8-402d-a64b-a2c0f7bb213a] description = "case insensitive" include = false [7138e389-83e4-4c6e-8413-1e40a0076951] description = "a-m and A-M are 26 different characters but not a pangram" reimplements = "2577bf54-83c8-402d-a64b-a2c0f7bb213a" ================================================ FILE: exercises/practice/pangram/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/pangram/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/pangram/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/pangram/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/pangram/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/pangram/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/pangram/package.json ================================================ { "name": "@exercism/typescript-pangram", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/pangram/pangram.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { isPangram } from './pangram.ts' describe('Pangram()', () => { it('empty sentence', () => { expect(isPangram('')).toBe(false) }) xit('perfect lower case', () => { expect(isPangram('abcdefghijklmnopqrstuvwxyz')).toBe(true) }) xit('only lower case', () => { expect(isPangram('the quick brown fox jumps over the lazy dog')).toBe(true) }) xit("missing the letter 'x'", () => { expect( isPangram('a quick movement of the enemy will jeopardize five gunboats') ).toBe(false) }) xit("missing the letter 'h'", () => { expect(isPangram('five boxing wizards jump quickly at it')).toBe(false) }) xit('with underscores', () => { expect(isPangram('the_quick_brown_fox_jumps_over_the_lazy_dog')).toBe(true) }) xit('with numbers', () => { expect(isPangram('the 1 quick brown fox jumps over the 2 lazy dogs')).toBe( true ) }) xit('missing letters replaced by numbers', () => { expect(isPangram('7h3 qu1ck brown fox jumps ov3r 7h3 lazy dog')).toBe(false) }) xit('mixed case and punctuation', () => { expect(isPangram('"Five quacking Zephyrs jolt my wax bed."')).toBe(true) }) xit('a-m and A-M are 26 different characters but not a pangram', () => { expect(isPangram('abcdefghijklm ABCDEFGHIJKLM')).toBe(false) }) }) ================================================ FILE: exercises/practice/pangram/pangram.ts ================================================ export function isPangram() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/pangram/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/pangram/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/pascals-triangle/.docs/instructions.md ================================================ # Instructions Your task is to output the first N rows of Pascal's triangle. [Pascal's triangle][wikipedia] is a triangular array of positive integers. In Pascal's triangle, the number of values in a row is equal to its row number (which starts at one). Therefore, the first row has one value, the second row has two values, and so on. The first (topmost) row has a single value: `1`. Subsequent rows' values are computed by adding the numbers directly to the right and left of the current position in the previous row. If the previous row does _not_ have a value to the left or right of the current position (which only happens for the leftmost and rightmost positions), treat that position's value as zero (effectively "ignoring" it in the summation). ## Example Let's look at the first 5 rows of Pascal's Triangle: ```text 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 ``` The topmost row has one value, which is `1`. The leftmost and rightmost values have only one preceding position to consider, which is the position to its right respectively to its left. With the topmost value being `1`, it follows from this that all the leftmost and rightmost values are also `1`. The other values all have two positions to consider. For example, the fifth row's (`1 4 6 4 1`) middle value is `6`, as the values to its left and right in the preceding row are `3` and `3`: [wikipedia]: https://en.wikipedia.org/wiki/Pascal%27s_triangle ================================================ FILE: exercises/practice/pascals-triangle/.docs/introduction.md ================================================ # Introduction With the weather being great, you're not looking forward to spending an hour in a classroom. Annoyed, you enter the class room, where you notice a strangely satisfying triangle shape on the blackboard. Whilst waiting for your math teacher to arrive, you can't help but notice some patterns in the triangle: the outer values are all ones, each subsequent row has one more value than its previous row and the triangle is symmetrical. Weird! Not long after you sit down, your teacher enters the room and explains that this triangle is the famous [Pascal's triangle][wikipedia]. Over the next hour, your teacher reveals some amazing things hidden in this triangle: - It can be used to compute how many ways you can pick K elements from N values. - It contains the Fibonacci sequence. - If you color odd and even numbers differently, you get a beautiful pattern called the [Sierpiński triangle][wikipedia-sierpinski-triangle]. The teacher implores you and your classmates to look up other uses, and assures you that there are lots more! At that moment, the school bell rings. You realize that for the past hour, you were completely absorbed in learning about Pascal's triangle. You quickly grab your laptop from your bag and go outside, ready to enjoy both the sunshine _and_ the wonders of Pascal's triangle. [wikipedia]: https://en.wikipedia.org/wiki/Pascal%27s_triangle [wikipedia-sierpinski-triangle]: https://en.wikipedia.org/wiki/Sierpi%C5%84ski_triangle ================================================ FILE: exercises/practice/pascals-triangle/.meta/config.json ================================================ { "authors": [ "anuragsoni" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "pascals-triangle.ts" ], "test": [ "pascals-triangle.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Compute Pascal's triangle up to a given number of rows.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Pascal's Triangle at Wolfram Math World", "source_url": "https://www.wolframalpha.com/input/?i=Pascal%27s+triangle" } ================================================ FILE: exercises/practice/pascals-triangle/.meta/proof.ci.ts ================================================ export class Triangle { public readonly rows: number[][] public readonly lastRow: number[] constructor(rows: number) { this.rows = this.fillRows(rows) this.lastRow = this.rows[this.rows.length - 1] } private newRow(prevRow: number[]): number[] { let prev = 0 const nextRow = [] for (const x of prevRow) { nextRow.push(prev + x) prev = x } nextRow.push(1) return nextRow } private fillRows(rows: number): number[][] { const result = [] let prevRow: number[] = [] for (let x = 0; x < rows; x++) { const nextRow = this.newRow(prevRow) result.push(nextRow) prevRow = nextRow } return result } } ================================================ FILE: exercises/practice/pascals-triangle/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [9920ce55-9629-46d5-85d6-4201f4a4234d] description = "zero rows" [70d643ce-a46d-4e93-af58-12d88dd01f21] description = "single row" [a6e5a2a2-fc9a-4b47-9f4f-ed9ad9fbe4bd] description = "two rows" [97206a99-79ba-4b04-b1c5-3c0fa1e16925] description = "three rows" [565a0431-c797-417c-a2c8-2935e01ce306] description = "four rows" [06f9ea50-9f51-4eb2-b9a9-c00975686c27] description = "five rows" [c3912965-ddb4-46a9-848e-3363e6b00b13] description = "six rows" [6cb26c66-7b57-4161-962c-81ec8c99f16b] description = "ten rows" ================================================ FILE: exercises/practice/pascals-triangle/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/pascals-triangle/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/pascals-triangle/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/pascals-triangle/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/pascals-triangle/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/pascals-triangle/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/pascals-triangle/package.json ================================================ { "name": "@exercism/typescript-pascals-triangle", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/pascals-triangle/pascals-triangle.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { Triangle } from './pascals-triangle.ts' describe('Triangle', () => { it('with one row', () => { expect(new Triangle(1).rows).toEqual([[1]]) }) xit('with two rows', () => { expect(new Triangle(2).rows).toEqual([[1], [1, 1]]) }) xit('with three rows', () => { expect(new Triangle(3).rows).toEqual([[1], [1, 1], [1, 2, 1]]) }) xit('last row', () => { expect(new Triangle(4).lastRow).toEqual([1, 3, 3, 1]) }) xit('fifth row', () => { expect(new Triangle(5).lastRow).toEqual([1, 4, 6, 4, 1]) }) xit('twentieth row', () => { const twentieth = [ 1, 19, 171, 969, 3876, 11628, 27132, 50388, 75582, 92378, 92378, 75582, 50388, 27132, 11628, 3876, 969, 171, 19, 1, ] expect(new Triangle(20).lastRow).toEqual(twentieth) }) }) ================================================ FILE: exercises/practice/pascals-triangle/pascals-triangle.ts ================================================ export class Triangle {} ================================================ FILE: exercises/practice/pascals-triangle/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/pascals-triangle/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/perfect-numbers/.docs/instructions.md ================================================ # Instructions Determine if a number is perfect, abundant, or deficient based on Nicomachus' (60 - 120 CE) classification scheme for positive integers. The Greek mathematician [Nicomachus][nicomachus] devised a classification scheme for positive integers, identifying each as belonging uniquely to the categories of [perfect](#perfect), [abundant](#abundant), or [deficient](#deficient) based on their [aliquot sum][aliquot-sum]. The _aliquot sum_ is defined as the sum of the factors of a number not including the number itself. For example, the aliquot sum of `15` is `1 + 3 + 5 = 9`. ## Perfect A number is perfect when it equals its aliquot sum. For example: - `6` is a perfect number because `1 + 2 + 3 = 6` - `28` is a perfect number because `1 + 2 + 4 + 7 + 14 = 28` ## Abundant A number is abundant when it is less than its aliquot sum. For example: - `12` is an abundant number because `1 + 2 + 3 + 4 + 6 = 16` - `24` is an abundant number because `1 + 2 + 3 + 4 + 6 + 8 + 12 = 36` ## Deficient A number is deficient when it is greater than its aliquot sum. For example: - `8` is a deficient number because `1 + 2 + 4 = 7` - Prime numbers are deficient ## Task Implement a way to determine whether a given number is [perfect](#perfect). Depending on your language track, you may also need to implement a way to determine whether a given number is [abundant](#abundant) or [deficient](#deficient). [nicomachus]: https://en.wikipedia.org/wiki/Nicomachus [aliquot-sum]: https://en.wikipedia.org/wiki/Aliquot_sum ================================================ FILE: exercises/practice/perfect-numbers/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "G-Rath", "masters3d", "SleeplessByte", "vjba" ], "files": { "solution": [ "perfect-numbers.ts" ], "test": [ "perfect-numbers.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Determine if a number is perfect, abundant, or deficient based on Nicomachus' (60 - 120 CE) classification scheme for positive integers.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Taken from Chapter 2 of Functional Thinking by Neal Ford.", "source_url": "https://www.oreilly.com/library/view/functional-thinking/9781449365509/" } ================================================ FILE: exercises/practice/perfect-numbers/.meta/proof.ci.ts ================================================ export function classify(n: number): string { let i let sum let result // Check if the input is valid if (n <= 0) { throw new Error('Classification is only possible for natural numbers.') } // Factorize the current number. const divsArray = getDivisors(n) // Sum the factors. sum = 0 for (i = 0; i < divsArray.length; i++) { sum = sum + divsArray[i] } // Check if the number is perfect. if (sum === n) { result = 'perfect' } else if (sum > n) { result = 'abundant' } else { result = 'deficient' } return result } function getDivisors(n: number): number[] { const divs: number[] = [] // Accepts only natural numbers greater than 1. if (n <= 1) { return divs } // 1 always divides everyone! divs.push(1) // Calculate the divisors up the the half of the number + 1 for (let i = 2; i <= n / 2; i++) { if (n % i === 0) { divs.push(i) } } return divs } ================================================ FILE: exercises/practice/perfect-numbers/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [163e8e86-7bfd-4ee2-bd68-d083dc3381a3] description = "Perfect numbers -> Smallest perfect number is classified correctly" [169a7854-0431-4ae0-9815-c3b6d967436d] description = "Perfect numbers -> Medium perfect number is classified correctly" [ee3627c4-7b36-4245-ba7c-8727d585f402] description = "Perfect numbers -> Large perfect number is classified correctly" [80ef7cf8-9ea8-49b9-8b2d-d9cb3db3ed7e] description = "Abundant numbers -> Smallest abundant number is classified correctly" [3e300e0d-1a12-4f11-8c48-d1027165ab60] description = "Abundant numbers -> Medium abundant number is classified correctly" [ec7792e6-8786-449c-b005-ce6dd89a772b] description = "Abundant numbers -> Large abundant number is classified correctly" [05f15b93-849c-45e9-9c7d-1ea131ef7d10] description = "Abundant numbers -> Perfect square abundant number is classified correctly" [e610fdc7-2b6e-43c3-a51c-b70fb37413ba] description = "Deficient numbers -> Smallest prime deficient number is classified correctly" [0beb7f66-753a-443f-8075-ad7fbd9018f3] description = "Deficient numbers -> Smallest non-prime deficient number is classified correctly" [1c802e45-b4c6-4962-93d7-1cad245821ef] description = "Deficient numbers -> Medium deficient number is classified correctly" [47dd569f-9e5a-4a11-9a47-a4e91c8c28aa] description = "Deficient numbers -> Large deficient number is classified correctly" [a696dec8-6147-4d68-afad-d38de5476a56] description = "Deficient numbers -> Edge case (no factors other than itself) is classified correctly" [72445cee-660c-4d75-8506-6c40089dc302] description = "Invalid inputs -> Zero is rejected (as it is not a positive integer)" [2d72ce2c-6802-49ac-8ece-c790ba3dae13] description = "Invalid inputs -> Negative integer is rejected (as it is not a positive integer)" ================================================ FILE: exercises/practice/perfect-numbers/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/perfect-numbers/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/perfect-numbers/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/perfect-numbers/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/perfect-numbers/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/perfect-numbers/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/perfect-numbers/package.json ================================================ { "name": "@exercism/typescript-perfect-numbers", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/perfect-numbers/perfect-numbers.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { classify } from './perfect-numbers.ts' describe('Perfect numbers', () => { it('Smallest perfect number is classified correctly', () => { const expected = 'perfect' expect(classify(6)).toEqual(expected) }) xit('Medium perfect number is classified correctly', () => { const expected = 'perfect' expect(classify(28)).toEqual(expected) }) xit('Large perfect number is classified correctly', () => { const expected = 'perfect' expect(classify(33550336)).toEqual(expected) }) }) describe('Abundant numbers', () => { xit('Smallest abundant number is classified correctly', () => { const expected = 'abundant' expect(classify(12)).toEqual(expected) }) xit('Medium abundant number is classified correctly', () => { const expected = 'abundant' expect(classify(30)).toEqual(expected) }) xit('Large abundant number is classified correctly', () => { const expected = 'abundant' expect(classify(33550335)).toEqual(expected) }) xit('Perfect square abundant number is classified correctly', () => { const expected = 'abundant' expect(classify(196)).toEqual(expected) }) }) describe('Deficient numbers', () => { xit('Smallest prime deficient number is classified correctly', () => { const expected = 'deficient' expect(classify(2)).toEqual(expected) }) xit('Smallest non-prime deficient number is classified correctly', () => { const expected = 'deficient' expect(classify(4)).toEqual(expected) }) xit('Medium deficient number is classified correctly', () => { const expected = 'deficient' expect(classify(32)).toEqual(expected) }) xit('Large deficient number is classified correctly', () => { const expected = 'deficient' expect(classify(33550337)).toEqual(expected) }) xit('Edge case (no factors other than itself) is classified correctly', () => { const expected = 'deficient' expect(classify(1)).toEqual(expected) }) }) describe('Invalid inputs', () => { xit('Zero is rejected (not a natural number)', () => { expect(() => { classify(0) }).toThrow('Classification is only possible for natural numbers.') }) xit('Negative integer is rejected (not a natural number)', () => { expect(() => { classify(-1) }).toThrow('Classification is only possible for natural numbers.') }) }) ================================================ FILE: exercises/practice/perfect-numbers/perfect-numbers.ts ================================================ export function classify() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/perfect-numbers/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/perfect-numbers/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/phone-number/.docs/instructions.md ================================================ # Instructions Clean up phone numbers so that they can be sent SMS messages. The **North American Numbering Plan (NANP)** is a telephone numbering system used by many countries in North America like the United States, Canada or Bermuda. All NANP-countries share the same international country code: `1`. NANP numbers are ten-digit numbers consisting of a three-digit Numbering Plan Area code, commonly known as _area code_, followed by a seven-digit local number. The first three digits of the local number represent the _exchange code_, followed by the unique four-digit number which is the _subscriber number_. The format is usually represented as ```text NXX NXX-XXXX ``` where `N` is any digit from 2 through 9 and `X` is any digit from 0 through 9. Sometimes they also have the country code (represented as `1` or `+1`) prefixed. Your task is to clean up differently formatted telephone numbers by removing punctuation and the country code if present. For example, the inputs - `+1 (613)-995-0253` - `613-995-0253` - `1 613 995 0253` - `613.995.0253` should all produce the output `6139950253` **Note:** As this exercise only deals with telephone numbers used in NANP-countries, only 1 is considered a valid country code. ================================================ FILE: exercises/practice/phone-number/.docs/introduction.md ================================================ # Introduction You've joined LinkLine, a leading communications company working to ensure reliable connections for everyone. The team faces a big challenge: users submit phone numbers in all sorts of formats — dashes, spaces, dots, parentheses, and even prefixes. Some numbers are valid, while others are impossible to use. Your mission is to turn this chaos into order. You'll clean up valid numbers, formatting them appropriately for use in the system. At the same time, you'll identify and filter out any invalid entries. The success of LinkLine's operations depends on your ability to separate the useful from the unusable. Are you ready to take on the challenge and keep the connections running smoothly? ================================================ FILE: exercises/practice/phone-number/.meta/config.json ================================================ { "authors": [ "masters3d" ], "contributors": [ "ffflorian", "lukaszklis", "SleeplessByte", "snowfrogdev" ], "files": { "solution": [ "phone-number.ts" ], "test": [ "phone-number.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Clean up user-entered phone numbers so that they can be sent SMS messages.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", "source_url": "https://www.turing.edu/" } ================================================ FILE: exercises/practice/phone-number/.meta/proof.ci.ts ================================================ export const clean = (number: string): string => { if (/[a-zA-Z]/.test(number)) { throw new Error('Letters not permitted') } else if (/[@:!]/.test(number)) { throw new Error('Punctuations not permitted') } let strippedNumber = number.replace(/\D/g, '') const numberLength = strippedNumber.length if (numberLength === 11) { if (strippedNumber.substring(0, 1) !== '1') { throw new Error('11 digits must start with 1') } else { strippedNumber = strippedNumber.substring(1) } } if (numberLength < 10) { throw new Error('Must not be fewer than 10 digits') } if (numberLength > 11) { throw new Error('Must not be greater than 11 digits') } if (strippedNumber.substring(0, 1) === '0') { throw new Error('Area code cannot start with zero') } else if (strippedNumber.substring(0, 1) === '1') { throw new Error('Area code cannot start with one') } if (strippedNumber.substring(3, 4) === '0') { throw new Error('Exchange code cannot start with zero') } else if (strippedNumber.substring(3, 4) === '1') { throw new Error('Exchange code cannot start with one') } return strippedNumber } ================================================ FILE: exercises/practice/phone-number/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [79666dce-e0f1-46de-95a1-563802913c35] description = "cleans the number" [c360451f-549f-43e4-8aba-fdf6cb0bf83f] description = "cleans numbers with dots" [08f94c34-9a37-46a2-a123-2a8e9727395d] description = "cleans numbers with multiple spaces" [598d8432-0659-4019-a78b-1c6a73691d21] description = "invalid when 9 digits" include = false [2de74156-f646-42b5-8638-0ef1d8b58bc2] description = "invalid when 9 digits" reimplements = "598d8432-0659-4019-a78b-1c6a73691d21" [57061c72-07b5-431f-9766-d97da7c4399d] description = "invalid when 11 digits does not start with a 1" [9962cbf3-97bb-4118-ba9b-38ff49c64430] description = "valid when 11 digits and starting with 1" [fa724fbf-054c-4d91-95da-f65ab5b6dbca] description = "valid when 11 digits and starting with 1 even with punctuation" [c6a5f007-895a-4fc5-90bc-a7e70f9b5cad] description = "invalid when more than 11 digits" include = false [4a1509b7-8953-4eec-981b-c483358ff531] description = "invalid when more than 11 digits" reimplements = "c6a5f007-895a-4fc5-90bc-a7e70f9b5cad" [63f38f37-53f6-4a5f-bd86-e9b404f10a60] description = "invalid with letters" include = false [eb8a1fc0-64e5-46d3-b0c6-33184208e28a] description = "invalid with letters" reimplements = "63f38f37-53f6-4a5f-bd86-e9b404f10a60" [4bd97d90-52fd-45d3-b0db-06ab95b1244e] description = "invalid with punctuations" include = false [065f6363-8394-4759-b080-e6c8c351dd1f] description = "invalid with punctuations" reimplements = "4bd97d90-52fd-45d3-b0db-06ab95b1244e" [d77d07f8-873c-4b17-8978-5f66139bf7d7] description = "invalid if area code starts with 0" [c7485cfb-1e7b-4081-8e96-8cdb3b77f15e] description = "invalid if area code starts with 1" [4d622293-6976-413d-b8bf-dd8a94d4e2ac] description = "invalid if exchange code starts with 0" [4cef57b4-7d8e-43aa-8328-1e1b89001262] description = "invalid if exchange code starts with 1" [9925b09c-1a0d-4960-a197-5d163cbe308c] description = "invalid if area code starts with 0 on valid 11-digit number" [3f809d37-40f3-44b5-ad90-535838b1a816] description = "invalid if area code starts with 1 on valid 11-digit number" [e08e5532-d621-40d4-b0cc-96c159276b65] description = "invalid if exchange code starts with 0 on valid 11-digit number" [57b32f3d-696a-455c-8bf1-137b6d171cdf] description = "invalid if exchange code starts with 1 on valid 11-digit number" ================================================ FILE: exercises/practice/phone-number/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/phone-number/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/phone-number/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/phone-number/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/phone-number/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/phone-number/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/phone-number/package.json ================================================ { "name": "@exercism/typescript-phone-number", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/phone-number/phone-number.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { clean } from './phone-number.ts' describe('Phone Number', () => { describe('Cleanup user-entered phone numbers', () => { it('cleans the number', () => { expect(clean('(223) 456-7890')).toEqual('2234567890') }) xit('cleans numbers with dots', () => { expect(clean('223.456.7890')).toEqual('2234567890') }) xit('cleans numbers with multiple spaces', () => { expect(clean('223 456 7890 ')).toEqual('2234567890') }) xit('invalid when 9 digits', () => { expect(() => clean('123456789')).toThrow( new Error('Must not be fewer than 10 digits') ) }) xit('invalid when 11 digits does not start with a 1', () => { expect(() => clean('22234567890')).toThrow( new Error('11 digits must start with 1') ) }) xit('valid when 11 digits and starting with 1', () => { expect(clean('12234567890')).toEqual('2234567890') }) xit('valid when 11 digits and starting with 1 even with punctuation', () => { expect(clean('+1 (223) 456-7890')).toEqual('2234567890') }) xit('invalid when more than 11 digits', () => { expect(() => clean('321234567890')).toThrow( new Error('Must not be greater than 11 digits') ) }) xit('invalid with letters', () => { expect(() => clean('523-abc-7890')).toThrow( new Error('Letters not permitted') ) }) xit('invalid with punctuations', () => { expect(() => clean('523-@:!-7890')).toThrow( new Error('Punctuations not permitted') ) }) xit('invalid if area code starts with 0', () => { expect(() => clean('(023) 456-7890')).toThrow( new Error('Area code cannot start with zero') ) }) xit('invalid if area code starts with 1', () => { expect(() => clean('(123) 456-7890')).toThrow( new Error('Area code cannot start with one') ) }) xit('invalid if exchange code starts with 0', () => { expect(() => clean('(223) 056-7890')).toThrow( new Error('Exchange code cannot start with zero') ) }) xit('invalid if exchange code starts with 1', () => { expect(() => clean('(223) 156-7890')).toThrow( new Error('Exchange code cannot start with one') ) }) xit('invalid if area code starts with 0 on valid 11-digit number', () => { expect(() => clean('1 (023) 456-7890')).toThrow( new Error('Area code cannot start with zero') ) }) xit('invalid if area code starts with 1 on valid 11-digit number', () => { expect(() => clean('1 (123) 456-7890')).toThrow( new Error('Area code cannot start with one') ) }) xit('invalid if exchange code starts with 0 on valid 11-digit number', () => { expect(() => clean('1 (223) 056-7890')).toThrow( new Error('Exchange code cannot start with zero') ) }) xit('invalid if exchange code starts with 1 on valid 11-digit number', () => { expect(() => clean('1 (223) 156-7890')).toThrow( new Error('Exchange code cannot start with one') ) }) }) }) ================================================ FILE: exercises/practice/phone-number/phone-number.ts ================================================ export function clean() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/phone-number/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/phone-number/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/pig-latin/.docs/instructions.md ================================================ # Instructions Your task is to translate text from English to Pig Latin. The translation is defined using four rules, which look at the pattern of vowels and consonants at the beginning of a word. These rules look at each word's use of vowels and consonants: - vowels: the letters `a`, `e`, `i`, `o`, and `u` - consonants: the other 21 letters of the English alphabet ## Rule 1 If a word begins with a vowel, or starts with `"xr"` or `"yt"`, add an `"ay"` sound to the end of the word. For example: - `"apple"` -> `"appleay"` (starts with vowel) - `"xray"` -> `"xrayay"` (starts with `"xr"`) - `"yttria"` -> `"yttriaay"` (starts with `"yt"`) ## Rule 2 If a word begins with one or more consonants, first move those consonants to the end of the word and then add an `"ay"` sound to the end of the word. For example: - `"pig"` -> `"igp"` -> `"igpay"` (starts with single consonant) - `"chair"` -> `"airch"` -> `"airchay"` (starts with multiple consonants) - `"thrush"` -> `"ushthr"` -> `"ushthray"` (starts with multiple consonants) ## Rule 3 If a word starts with zero or more consonants followed by `"qu"`, first move those consonants (if any) and the `"qu"` part to the end of the word, and then add an `"ay"` sound to the end of the word. For example: - `"quick"` -> `"ickqu"` -> `"ickquay"` (starts with `"qu"`, no preceding consonants) - `"square"` -> `"aresqu"` -> `"aresquay"` (starts with one consonant followed by `"qu`") ## Rule 4 If a word starts with one or more consonants followed by `"y"`, first move the consonants preceding the `"y"`to the end of the word, and then add an `"ay"` sound to the end of the word. Some examples: - `"my"` -> `"ym"` -> `"ymay"` (starts with single consonant followed by `"y"`) - `"rhythm"` -> `"ythmrh"` -> `"ythmrhay"` (starts with multiple consonants followed by `"y"`) ================================================ FILE: exercises/practice/pig-latin/.docs/introduction.md ================================================ # Introduction Your parents have challenged you and your sibling to a game of two-on-two basketball. Confident they'll win, they let you score the first couple of points, but then start taking over the game. Needing a little boost, you start speaking in [Pig Latin][pig-latin], which is a made-up children's language that's difficult for non-children to understand. This will give you the edge to prevail over your parents! [pig-latin]: https://en.wikipedia.org/wiki/Pig_latin ================================================ FILE: exercises/practice/pig-latin/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "pig-latin.ts" ], "test": [ "pig-latin.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Implement a program that translates from English to Pig Latin.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "The Pig Latin exercise at Test First Teaching by Ultrasaurus", "source_url": "https://github.com/ultrasaurus/test-first-teaching/tree/master/learn_ruby/pig_latin" } ================================================ FILE: exercises/practice/pig-latin/.meta/proof.ci.ts ================================================ export function translate(phrase: string): string { const words = phrase.split(' ') const translated: string[] = [] words.forEach((word) => { const parts = word.match(/^([^aeiou]?qu|[^aeiou]*)(.+)/) const beginning = parts![1] const ending = parts![2] if (beginning.length === 0) { translated.push(`${word}ay`) } else { translated.push(`${ending}${beginning}ay`) } }) return translated.join(' ') } ================================================ FILE: exercises/practice/pig-latin/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [11567f84-e8c6-4918-aedb-435f0b73db57] description = "ay is added to words that start with vowels -> word beginning with a" [f623f581-bc59-4f45-9032-90c3ca9d2d90] description = "ay is added to words that start with vowels -> word beginning with e" [7dcb08b3-23a6-4e8a-b9aa-d4e859450d58] description = "ay is added to words that start with vowels -> word beginning with i" [0e5c3bff-266d-41c8-909f-364e4d16e09c] description = "ay is added to words that start with vowels -> word beginning with o" [614ba363-ca3c-4e96-ab09-c7320799723c] description = "ay is added to words that start with vowels -> word beginning with u" [bf2538c6-69eb-4fa7-a494-5a3fec911326] description = "ay is added to words that start with vowels -> word beginning with a vowel and followed by a qu" [e5be8a01-2d8a-45eb-abb4-3fcc9582a303] description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with p" [d36d1e13-a7ed-464d-a282-8820cb2261ce] description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with k" [d838b56f-0a89-4c90-b326-f16ff4e1dddc] description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with x" [bce94a7a-a94e-4e2b-80f4-b2bb02e40f71] description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with q without a following u" [e59dbbe8-ccee-4619-a8e9-ce017489bfc0] description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with consonant and vowel containing qu" [c01e049a-e3e2-451c-bf8e-e2abb7e438b8] description = "some letter clusters are treated like a single consonant -> word beginning with ch" [9ba1669e-c43f-4b93-837a-cfc731fd1425] description = "some letter clusters are treated like a single consonant -> word beginning with qu" [92e82277-d5e4-43d7-8dd3-3a3b316c41f7] description = "some letter clusters are treated like a single consonant -> word beginning with qu and a preceding consonant" [79ae4248-3499-4d5b-af46-5cb05fa073ac] description = "some letter clusters are treated like a single consonant -> word beginning with th" [e0b3ae65-f508-4de3-8999-19c2f8e243e1] description = "some letter clusters are treated like a single consonant -> word beginning with thr" [20bc19f9-5a35-4341-9d69-1627d6ee6b43] description = "some letter clusters are treated like a single consonant -> word beginning with sch" [54b796cb-613d-4509-8c82-8fbf8fc0af9e] description = "some letter clusters are treated like a single vowel -> word beginning with yt" [8c37c5e1-872e-4630-ba6e-d20a959b67f6] description = "some letter clusters are treated like a single vowel -> word beginning with xr" [a4a36d33-96f3-422c-a233-d4021460ff00] description = "position of y in a word determines if it is a consonant or a vowel -> y is treated like a consonant at the beginning of a word" [adc90017-1a12-4100-b595-e346105042c7] description = "position of y in a word determines if it is a consonant or a vowel -> y is treated like a vowel at the end of a consonant cluster" [29b4ca3d-efe5-4a95-9a54-8467f2e5e59a] description = "position of y in a word determines if it is a consonant or a vowel -> y as second letter in two letter word" [44616581-5ce3-4a81-82d0-40c7ab13d2cf] description = "phrases are translated -> a whole phrase" ================================================ FILE: exercises/practice/pig-latin/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/pig-latin/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/pig-latin/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/pig-latin/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/pig-latin/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/pig-latin/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/pig-latin/package.json ================================================ { "name": "@exercism/typescript-pig-latin", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/pig-latin/pig-latin.test.ts ================================================ import { describe, xdescribe, it, expect, xit } from '@jest/globals' import { translate } from './pig-latin.ts' describe('ay is added to words that start with vowels', () => { it('word beginning with a', () => { const expected = 'appleay' expect(translate('apple')).toEqual(expected) }) xit('word beginning with e', () => { const expected = 'earay' expect(translate('ear')).toEqual(expected) }) xit('word beginning with i', () => { const expected = 'iglooay' expect(translate('igloo')).toEqual(expected) }) xit('word beginning with o', () => { const expected = 'objectay' expect(translate('object')).toEqual(expected) }) xit('word beginning with u', () => { const expected = 'underay' expect(translate('under')).toEqual(expected) }) xit('word beginning with a vowel and followed by a qu', () => { const expected = 'equalay' expect(translate('equal')).toEqual(expected) }) }) xdescribe('first letter and ay are moved to the end of words that start with consonants', () => { xit('word beginning with p', () => { const expected = 'igpay' expect(translate('pig')).toEqual(expected) }) xit('word beginning with k', () => { const expected = 'oalakay' expect(translate('koala')).toEqual(expected) }) xit('word beginning with x', () => { const expected = 'enonxay' expect(translate('xenon')).toEqual(expected) }) xit('word beginning with q without a following u', () => { const expected = 'atqay' expect(translate('qat')).toEqual(expected) }) xit('word beginning with consonant and vowel containing qu', () => { const expected = 'iquidlay' expect(translate('liquid')).toEqual(expected) }) }) xdescribe('some letter clusters are treated like a single consonant', () => { xit('word beginning with ch', () => { const expected = 'airchay' expect(translate('chair')).toEqual(expected) }) xit('word beginning with qu', () => { const expected = 'eenquay' expect(translate('queen')).toEqual(expected) }) xit('word beginning with qu and a preceding consonant', () => { const expected = 'aresquay' expect(translate('square')).toEqual(expected) }) xit('word beginning with th', () => { const expected = 'erapythay' expect(translate('therapy')).toEqual(expected) }) xit('word beginning with thr', () => { const expected = 'ushthray' expect(translate('thrush')).toEqual(expected) }) xit('word beginning with sch', () => { const expected = 'oolschay' expect(translate('school')).toEqual(expected) }) }) xdescribe('position of y in a word determines if it is a consonant or a vowel', () => { xit('y is treated like a consonant at the beginning of a word', () => { const expected = 'ellowyay' expect(translate('yellow')).toEqual(expected) }) xit('y as second letter in two letter word', () => { const expected = 'ymay' expect(translate('my')).toEqual(expected) }) }) xdescribe('phrases are translated', () => { xit('a whole phrase', () => { const expected = 'ickquay astfay unray' expect(translate('quick fast run')).toEqual(expected) }) }) ================================================ FILE: exercises/practice/pig-latin/pig-latin.ts ================================================ export function translate() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/pig-latin/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/pig-latin/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/prime-factors/.docs/instructions.md ================================================ # Instructions Compute the prime factors of a given natural number. A prime number is only evenly divisible by itself and 1. Note that 1 is not a prime number. ## Example What are the prime factors of 60? - Our first divisor is 2. 2 goes into 60, leaving 30. - 2 goes into 30, leaving 15. - 2 doesn't go cleanly into 15. So let's move on to our next divisor, 3. - 3 goes cleanly into 15, leaving 5. - 3 does not go cleanly into 5. The next possible factor is 4. - 4 does not go cleanly into 5. The next possible factor is 5. - 5 does go cleanly into 5. - We're left only with 1, so now, we're done. Our successful divisors in that computation represent the list of prime factors of 60: 2, 2, 3, and 5. You can check this yourself: ```text 2 * 2 * 3 * 5 = 4 * 15 = 60 ``` Success! ================================================ FILE: exercises/practice/prime-factors/.meta/config.json ================================================ { "authors": [ "jspengeman" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "prime-factors.ts" ], "test": [ "prime-factors.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Compute the prime factors of a given natural number.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "The Prime Factors Kata by Uncle Bob", "source_url": "https://web.archive.org/web/20221026171801/http://butunclebob.com/ArticleS.UncleBob.ThePrimeFactorsKata" } ================================================ FILE: exercises/practice/prime-factors/.meta/proof.ci.ts ================================================ export function calculatePrimeFactors(num: number): number[] { const factors = [] let currentFactor = 2 while (num !== 1) { if (num % currentFactor === 0) { factors.push(currentFactor) num /= currentFactor currentFactor = 2 } else { currentFactor++ } } return factors } ================================================ FILE: exercises/practice/prime-factors/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [924fc966-a8f5-4288-82f2-6b9224819ccd] description = "no factors" [17e30670-b105-4305-af53-ddde182cb6ad] description = "prime number" [238d57c8-4c12-42ef-af34-ae4929f94789] description = "another prime number" [f59b8350-a180-495a-8fb1-1712fbee1158] description = "square of a prime" [756949d3-3158-4e3d-91f2-c4f9f043ee70] description = "product of first prime" [bc8c113f-9580-4516-8669-c5fc29512ceb] description = "cube of a prime" [7d6a3300-a4cb-4065-bd33-0ced1de6cb44] description = "product of second prime" [073ac0b2-c915-4362-929d-fc45f7b9a9e4] description = "product of third prime" [6e0e4912-7fb6-47f3-a9ad-dbcd79340c75] description = "product of first and second prime" [00485cd3-a3fe-4fbe-a64a-a4308fc1f870] description = "product of primes and non-primes" [02251d54-3ca1-4a9b-85e1-b38f4b0ccb91] description = "product of primes" [070cf8dc-e202-4285-aa37-8d775c9cd473] description = "factors include a large prime" ================================================ FILE: exercises/practice/prime-factors/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/prime-factors/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/prime-factors/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/prime-factors/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/prime-factors/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/prime-factors/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/prime-factors/package.json ================================================ { "name": "@exercism/typescript-prime-factors", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/prime-factors/prime-factors.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { calculatePrimeFactors } from './prime-factors.ts' describe('calculatePrimeFactors', () => { it('returns an empty array for 1', () => expect(calculatePrimeFactors(1)).toEqual([])) xit('factors 2', () => expect(calculatePrimeFactors(2)).toEqual([2])) xit('factors 3', () => expect(calculatePrimeFactors(3)).toEqual([3])) xit('factors 4', () => expect(calculatePrimeFactors(4)).toEqual([2, 2])) xit('factors 6', () => expect(calculatePrimeFactors(6)).toEqual([2, 3])) xit('factors 8', () => expect(calculatePrimeFactors(8)).toEqual([2, 2, 2])) xit('factors 9', () => expect(calculatePrimeFactors(9)).toEqual([3, 3])) xit('factors 12', () => expect(calculatePrimeFactors(12)).toEqual([2, 2, 3])) xit('factors 27', () => expect(calculatePrimeFactors(27)).toEqual([3, 3, 3])) xit('factors 625', () => expect(calculatePrimeFactors(625)).toEqual([5, 5, 5, 5])) xit('factors 901255', () => expect(calculatePrimeFactors(901255)).toEqual([5, 17, 23, 461])) xit('factors 93819012551', () => expect(calculatePrimeFactors(93819012551)).toEqual([11, 9539, 894119])) }) ================================================ FILE: exercises/practice/prime-factors/prime-factors.ts ================================================ export function calculatePrimeFactors() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/prime-factors/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/prime-factors/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/prism/.docs/instructions.md ================================================ # Instructions Before activating the laser array, you must predict the exact order in which crystals will be hit, identified by their sample IDs. ## Example Test Case Consider this crystal array configuration: ```json { "start": { "x": 0, "y": 0, "angle": 0 }, "prisms": [ { "id": 3, "x": 30, "y": 10, "angle": 45 }, { "id": 1, "x": 10, "y": 10, "angle": -90 }, { "id": 2, "x": 10, "y": 0, "angle": 90 }, { "id": 4, "x": 20, "y": 0, "angle": 0 } ] } ``` ## What's Happening The laser starts at the origin `(0, 0)` and fires horizontally to the right at angle 0°. Here's the step-by-step beam path: **Step 1**: The beam travels along the x-axis (y = 0) and first encounters **Crystal #2** at position `(10, 0)`. This crystal has a refraction angle of 90°, which means it bends the beam perpendicular to its current path. The beam, originally traveling at 0°, is now redirected to 90° (straight up). **Step 2**: The beam now travels vertically upward from position `(10, 0)` and strikes **Crystal #1** at position `(10, 10)`. This crystal has a refraction angle of -90°, bending the beam by -90° relative to its current direction. The beam was traveling at 90°, so after refraction it's now at 0° (90° + (-90°) = 0°), traveling horizontally to the right again. **Step 3**: From position `(10, 10)`, the beam travels horizontally and encounters **Crystal #3** at position `(30, 10)`. This crystal refracts the beam by 45°, changing its direction to 45°. The beam continues into empty space beyond the array. ================================================ FILE: exercises/practice/prism/.docs/introduction.md ================================================ # Introduction You're a researcher at **PRISM** (Precariously Redirected Illumination Safety Management), working with a precision laser calibration system that tests experimental crystal prisms. These crystals are being developed for next-generation optical computers, and each one has unique refractive properties based on its molecular structure. The lab's laser system can damage crystals if they receive unexpected illumination, so precise path prediction is critical. ================================================ FILE: exercises/practice/prism/.meta/config.json ================================================ { "authors": [ "BNAndras" ], "files": { "solution": [ "prism.ts" ], "test": [ "prism.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Calculate the path of a laser through reflective prisms.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "FraSanga", "source_url": "https://github.com/exercism/problem-specifications/pull/2625" } ================================================ FILE: exercises/practice/prism/.meta/proof.ci.ts ================================================ type Point = { x: number y: number angle: number } type Prism = Point & { id: number } export const findSequence = (start: Point, prisms: Prism[]): number[] => { let { x, y, angle } = start const sequence = [] while (true) { const rad = (angle * Math.PI) / 180 const dirX = Math.cos(rad) const dirY = Math.sin(rad) let nearest: Prism let nearestDist = Infinity for (const prism of prisms) { const dx = prism.x - x const dy = prism.y - y const dist = dx * dirX + dy * dirY const baseTolerance = 1e-6 if (dist <= baseTolerance) continue const crossProductSquared = (dx - dist * dirX) ** 2 + (dy - dist * dirY) ** 2 const relativeTolerance = baseTolerance * Math.max(1, dist * dist) if (crossProductSquared >= relativeTolerance) continue if (dist < nearestDist) { nearestDist = dist nearest = prism } } if (!nearest) break sequence.push(nearest.id) x = nearest.x y = nearest.y angle = (angle + nearest.angle) % 360 } return sequence } ================================================ FILE: exercises/practice/prism/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [ec65d3b3-f7bf-4015-8156-0609c141c4c4] description = "zero prisms" [ec0ca17c-0c5f-44fb-89ba-b76395bdaf1c] description = "one prism one hit" [0db955f2-0a27-4c82-ba67-197bd6202069] description = "one prism zero hits" [8d92485b-ebc0-4ee9-9b88-cdddb16b52da] description = "going up zero hits" [78295b3c-7438-492d-8010-9c63f5c223d7] description = "going down zero hits" [acc723ea-597b-4a50-8d1b-b980fe867d4c] description = "going left zero hits" [3f19b9df-9eaa-4f18-a2db-76132f466d17] description = "negative angle" [96dacffb-d821-4cdf-aed8-f152ce063195] description = "large angle" [513a7caa-957f-4c5d-9820-076842de113c] description = "upward refraction two hits" [d452b7c7-9761-4ea9-81a9-2de1d73eb9ef] description = "downward refraction two hits" [be1a2167-bf4c-4834-acc9-e4d68e1a0203] description = "same prism twice" [df5a60dd-7c7d-4937-ac4f-c832dae79e2e] description = "simple path" [8d9a3cc8-e846-4a3b-a137-4bfc4aa70bd1] description = "multiple prisms floating point precision" [e077fc91-4e4a-46b3-a0f5-0ba00321da56] description = "complex path with multiple prisms floating point precision" ================================================ FILE: exercises/practice/prism/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/prism/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/prism/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/prism/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/prism/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/prism/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/prism/package.json ================================================ { "name": "@exercism/typescript-prism", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/prism/prism.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { findSequence } from './prism.ts' describe('Prism', () => { it('zero prisms', () => { const start = { x: 0, y: 0, angle: 0 } const prisms: { id: number; x: number; y: number; angle: number }[] = [] const result = findSequence(start, prisms) const expected: number[] = [] expect(result).toEqual(expected) }) xit('one prism one hit', () => { const start = { x: 0, y: 0, angle: 0 } const prisms = [{ id: 1, x: 10, y: 0, angle: 0 }] const result = findSequence(start, prisms) const expected = [1] expect(result).toEqual(expected) }) xit('one prism zero hits', () => { const start = { x: 0, y: 0, angle: 0 } const prisms = [{ id: 1, x: -10, y: 0, angle: 0 }] const result = findSequence(start, prisms) const expected: number[] = [] expect(result).toEqual(expected) }) xit('going up zero hits', () => { const start = { x: 0, y: 0, angle: 90 } const prisms = [ { id: 3, x: 0, y: -10, angle: 0 }, { id: 1, x: -10, y: 0, angle: 0 }, { id: 2, x: 10, y: 0, angle: 0 }, ] const result = findSequence(start, prisms) const expected: number[] = [] expect(result).toEqual(expected) }) xit('going down zero hits', () => { const start = { x: 0, y: 0, angle: -90 } const prisms = [ { id: 1, x: 10, y: 0, angle: 0 }, { id: 2, x: 0, y: 10, angle: 0 }, { id: 3, x: -10, y: 0, angle: 0 }, ] const result = findSequence(start, prisms) const expected: number[] = [] expect(result).toEqual(expected) }) xit('going left zero hits', () => { const start = { x: 0, y: 0, angle: 180 } const prisms = [ { id: 2, x: 0, y: 10, angle: 0 }, { id: 3, x: 10, y: 0, angle: 0 }, { id: 1, x: 0, y: -10, angle: 0 }, ] const result = findSequence(start, prisms) const expected: number[] = [] expect(result).toEqual(expected) }) xit('negative angle', () => { const start = { x: 0, y: 0, angle: -180 } const prisms = [ { id: 1, x: 0, y: -10, angle: 0 }, { id: 2, x: 0, y: 10, angle: 0 }, { id: 3, x: 10, y: 0, angle: 0 }, ] const result = findSequence(start, prisms) const expected: number[] = [] expect(result).toEqual(expected) }) xit('large angle', () => { const start = { x: 0, y: 0, angle: 2340 } const prisms = [{ id: 1, x: 10, y: 0, angle: 0 }] const result = findSequence(start, prisms) const expected: number[] = [] expect(result).toEqual(expected) }) xit('upward refraction two hits', () => { const start = { x: 0, y: 0, angle: 0 } const prisms = [ { id: 1, x: 10, y: 10, angle: 0 }, { id: 2, x: 10, y: 0, angle: 90 }, ] const result = findSequence(start, prisms) const expected = [2, 1] expect(result).toEqual(expected) }) xit('downward refraction two hits', () => { const start = { x: 0, y: 0, angle: 0 } const prisms = [ { id: 1, x: 10, y: 0, angle: -90 }, { id: 2, x: 10, y: -10, angle: 0 }, ] const result = findSequence(start, prisms) const expected = [1, 2] expect(result).toEqual(expected) }) xit('same prism twice', () => { const start = { x: 0, y: 0, angle: 0 } const prisms = [ { id: 2, x: 10, y: 0, angle: 0 }, { id: 1, x: 20, y: 0, angle: -180 }, ] const result = findSequence(start, prisms) const expected = [2, 1, 2] expect(result).toEqual(expected) }) xit('simple path', () => { const start = { x: 0, y: 0, angle: 0 } const prisms = [ { id: 3, x: 30, y: 10, angle: 45 }, { id: 1, x: 10, y: 10, angle: -90 }, { id: 2, x: 10, y: 0, angle: 90 }, { id: 4, x: 20, y: 0, angle: 0 }, ] const result = findSequence(start, prisms) const expected = [2, 1, 3] expect(result).toEqual(expected) }) xit('multiple prisms floating point precision', () => { const start = { x: 0, y: 0, angle: -6.429 } const prisms = [ { id: 26, x: 5.8, y: 73.4, angle: 6.555 }, { id: 24, x: 36.2, y: 65.2, angle: -0.304 }, { id: 20, x: 20.4, y: 82.8, angle: 45.17 }, { id: 31, x: -20.2, y: 48.8, angle: 30.615 }, { id: 30, x: 24.0, y: 0.6, angle: 28.771 }, { id: 29, x: 31.4, y: 79.4, angle: 61.327 }, { id: 28, x: 36.4, y: 31.4, angle: -18.157 }, { id: 22, x: 47.0, y: 57.8, angle: 54.745 }, { id: 38, x: 36.4, y: 79.2, angle: 49.05 }, { id: 10, x: 37.8, y: 55.2, angle: 11.978 }, { id: 18, x: -26.0, y: 42.6, angle: 22.661 }, { id: 25, x: 38.8, y: 76.2, angle: 51.958 }, { id: 2, x: 0.0, y: 42.4, angle: -21.817 }, { id: 35, x: 21.4, y: 44.8, angle: -171.579 }, { id: 7, x: 14.2, y: -1.6, angle: 19.081 }, { id: 33, x: 11.2, y: 44.4, angle: -165.941 }, { id: 11, x: 15.4, y: 82.6, angle: 66.262 }, { id: 16, x: 30.8, y: 6.6, angle: 35.852 }, { id: 15, x: -3.0, y: 79.2, angle: 53.782 }, { id: 4, x: 29.0, y: 75.4, angle: 17.016 }, { id: 23, x: 41.6, y: 59.8, angle: 70.763 }, { id: 8, x: -10.0, y: 15.8, angle: -9.24 }, { id: 13, x: 48.6, y: 51.8, angle: 45.812 }, { id: 1, x: 13.2, y: 77.0, angle: 17.937 }, { id: 34, x: -8.8, y: 36.8, angle: -4.199 }, { id: 21, x: 24.4, y: 75.8, angle: 20.783 }, { id: 17, x: -4.4, y: 74.6, angle: 24.709 }, { id: 9, x: 30.8, y: 41.8, angle: -165.413 }, { id: 32, x: 4.2, y: 78.6, angle: 40.892 }, { id: 37, x: -15.8, y: 47.0, angle: 33.29 }, { id: 6, x: 1.0, y: 80.6, angle: 51.295 }, { id: 36, x: -27.0, y: 47.8, angle: 92.52 }, { id: 14, x: -2.0, y: 34.4, angle: -52.001 }, { id: 5, x: 23.2, y: 80.2, angle: 31.866 }, { id: 27, x: -5.6, y: 32.8, angle: -75.303 }, { id: 12, x: -1.0, y: 0.2, angle: 0.0 }, { id: 3, x: -6.6, y: 3.2, angle: 46.72 }, { id: 19, x: -13.8, y: 24.2, angle: -9.205 }, ] const result = findSequence(start, prisms) const expected = [ 7, 30, 16, 28, 13, 22, 23, 10, 9, 24, 25, 38, 29, 4, 35, 21, 5, 20, 11, 1, 33, 26, 32, 6, 15, 17, 2, 14, 27, 34, 37, 31, 36, 18, 19, 8, 3, 12, ] expect(result).toEqual(expected) }) xit('complex path with multiple prisms floating point precision', () => { const start = { x: 0, y: 0, angle: 0.0 } const prisms = [ { id: 46, x: 37.4, y: 20.6, angle: -88.332 }, { id: 72, x: -24.2, y: 23.4, angle: -90.774 }, { id: 25, x: 78.6, y: 7.8, angle: 98.562 }, { id: 60, x: -58.8, y: 31.6, angle: 115.56 }, { id: 22, x: 75.2, y: 28.0, angle: 63.515 }, { id: 2, x: 89.8, y: 27.8, angle: 91.176 }, { id: 23, x: 9.8, y: 30.8, angle: 30.829 }, { id: 69, x: 22.8, y: 20.6, angle: -88.315 }, { id: 44, x: -0.8, y: 15.6, angle: -116.565 }, { id: 36, x: -24.2, y: 8.2, angle: -90.0 }, { id: 53, x: -1.2, y: 0.0, angle: 0.0 }, { id: 52, x: 14.2, y: 24.0, angle: -143.896 }, { id: 5, x: -65.2, y: 21.6, angle: 93.128 }, { id: 66, x: 5.4, y: 15.6, angle: 31.608 }, { id: 51, x: -72.6, y: 21.0, angle: -100.976 }, { id: 65, x: 48.0, y: 10.2, angle: 87.455 }, { id: 21, x: -41.8, y: 0.0, angle: 68.352 }, { id: 18, x: -46.2, y: 19.2, angle: -128.362 }, { id: 10, x: 74.4, y: 0.4, angle: 90.939 }, { id: 15, x: 67.6, y: 0.4, angle: 84.958 }, { id: 35, x: 14.8, y: -0.4, angle: 89.176 }, { id: 1, x: 83.0, y: 0.2, angle: 89.105 }, { id: 68, x: 14.6, y: 28.0, angle: -29.867 }, { id: 67, x: 79.8, y: 18.6, angle: -136.643 }, { id: 38, x: 53.0, y: 14.6, angle: -90.848 }, { id: 31, x: -58.0, y: 6.6, angle: -61.837 }, { id: 74, x: -30.8, y: 0.4, angle: 85.966 }, { id: 48, x: -4.6, y: 10.0, angle: -161.222 }, { id: 12, x: 59.0, y: 5.0, angle: -91.164 }, { id: 33, x: -16.4, y: 18.4, angle: 90.734 }, { id: 4, x: 82.6, y: 27.6, angle: 71.127 }, { id: 75, x: -10.2, y: 30.6, angle: -1.108 }, { id: 28, x: 38.0, y: 0.0, angle: 86.863 }, { id: 11, x: 64.4, y: -0.2, angle: 92.353 }, { id: 9, x: -51.4, y: 31.6, angle: 67.249 }, { id: 26, x: -39.8, y: 30.8, angle: 61.113 }, { id: 30, x: -34.2, y: 0.6, angle: 111.33 }, { id: 56, x: -51.0, y: 0.2, angle: 70.445 }, { id: 41, x: -12.0, y: 0.0, angle: 91.219 }, { id: 24, x: 63.8, y: 14.4, angle: 86.586 }, { id: 70, x: -72.8, y: 13.4, angle: -87.238 }, { id: 3, x: 22.4, y: 7.0, angle: -91.685 }, { id: 13, x: 34.4, y: 7.0, angle: 90.0 }, { id: 16, x: -47.4, y: 11.4, angle: -136.02 }, { id: 6, x: 90.0, y: 0.2, angle: 90.415 }, { id: 54, x: 44.0, y: 27.8, angle: 85.969 }, { id: 32, x: -9.0, y: 0.0, angle: 91.615 }, { id: 8, x: -31.6, y: 30.8, angle: 0.535 }, { id: 39, x: -12.0, y: 8.2, angle: 90.0 }, { id: 14, x: -79.6, y: 32.4, angle: 92.342 }, { id: 42, x: 65.8, y: 20.8, angle: -85.867 }, { id: 40, x: -65.0, y: 14.0, angle: 87.109 }, { id: 45, x: 10.6, y: 18.8, angle: 23.697 }, { id: 71, x: -24.2, y: 18.6, angle: -88.531 }, { id: 7, x: -72.6, y: 6.4, angle: -89.148 }, { id: 62, x: -32.0, y: 24.8, angle: -140.8 }, { id: 49, x: 34.4, y: -0.2, angle: 89.415 }, { id: 63, x: 74.2, y: 12.6, angle: -138.429 }, { id: 59, x: 82.8, y: 13.0, angle: -140.177 }, { id: 34, x: -9.4, y: 23.2, angle: -88.238 }, { id: 76, x: -57.6, y: 0.0, angle: 1.2 }, { id: 43, x: 7.0, y: 0.0, angle: 116.565 }, { id: 20, x: 45.8, y: -0.2, angle: 1.469 }, { id: 37, x: -16.6, y: 13.2, angle: 84.785 }, { id: 58, x: -79.0, y: -0.2, angle: 89.481 }, { id: 50, x: -24.2, y: 12.8, angle: -86.987 }, { id: 64, x: 59.2, y: 10.2, angle: -92.203 }, { id: 61, x: -72.0, y: 26.4, angle: -83.66 }, { id: 47, x: 45.4, y: 5.8, angle: -82.992 }, { id: 17, x: -52.2, y: 17.8, angle: -52.938 }, { id: 57, x: -61.8, y: 32.0, angle: 84.627 }, { id: 29, x: 47.2, y: 28.2, angle: 92.954 }, { id: 27, x: -4.6, y: 0.2, angle: 87.397 }, { id: 55, x: -61.4, y: 26.4, angle: 94.086 }, { id: 73, x: -40.4, y: 13.4, angle: -62.229 }, { id: 19, x: 53.2, y: 20.6, angle: -87.181 }, ] const result = findSequence(start, prisms) const expected = [ 43, 44, 66, 45, 52, 35, 49, 13, 3, 69, 46, 28, 20, 11, 24, 38, 19, 42, 15, 10, 63, 25, 59, 1, 6, 2, 4, 67, 22, 29, 65, 64, 12, 47, 54, 68, 23, 75, 8, 26, 18, 9, 60, 17, 31, 7, 70, 40, 5, 51, 61, 55, 57, 14, 58, 76, 56, 16, 21, 30, 73, 62, 74, 41, 39, 36, 50, 37, 33, 71, 72, 34, 32, 27, 48, 53, ] expect(result).toEqual(expected) }) }) ================================================ FILE: exercises/practice/prism/prism.ts ================================================ export function findSequence(start: unknown, prisms: unknown): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/prism/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/prism/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/protein-translation/.docs/instructions.md ================================================ # Instructions Your job is to translate RNA sequences into proteins. RNA strands are made up of three-nucleotide sequences called **codons**. Each codon translates to an **amino acid**. When joined together, those amino acids make a protein. In the real world, there are 64 codons, which in turn correspond to 20 amino acids. However, for this exercise, you’ll only use a few of the possible 64. They are listed below: | Codon | Amino Acid | | ------------------ | ------------- | | AUG | Methionine | | UUU, UUC | Phenylalanine | | UUA, UUG | Leucine | | UCU, UCC, UCA, UCG | Serine | | UAU, UAC | Tyrosine | | UGU, UGC | Cysteine | | UGG | Tryptophan | | UAA, UAG, UGA | STOP | For example, the RNA string “AUGUUUUCU” has three codons: “AUG”, “UUU” and “UCU”. These map to Methionine, Phenylalanine, and Serine. ## “STOP” Codons You’ll note from the table above that there are three **“STOP” codons**. If you encounter any of these codons, ignore the rest of the sequence — the protein is complete. For example, “AUGUUUUCUUAAAUG” contains a STOP codon (“UAA”). Once we reach that point, we stop processing. We therefore only consider the part before it (i.e. “AUGUUUUCU”), not any further codons after it (i.e. “AUG”). Learn more about [protein translation on Wikipedia][protein-translation]. [protein-translation]: https://en.wikipedia.org/wiki/Translation_(biology) ================================================ FILE: exercises/practice/protein-translation/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "protein-translation.ts" ], "test": [ "protein-translation.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Translate RNA sequences into proteins.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Tyler Long" } ================================================ FILE: exercises/practice/protein-translation/.meta/proof.ci.ts ================================================ const ACID_PROTEIN_MAP = { AUG: 'Methionine', UUU: 'Phenylalanine', UUC: 'Phenylalanine', UUA: 'Leucine', UUG: 'Leucine', UCU: 'Serine', UCC: 'Serine', UCA: 'Serine', UCG: 'Serine', UAU: 'Tyrosine', UAC: 'Tyrosine', UGU: 'Cysteine', UGC: 'Cysteine', UGG: 'Tryptophan', UAA: 'STOP', UAG: 'STOP', UGA: 'STOP', } as const type Codon = keyof typeof ACID_PROTEIN_MAP type Protein = (typeof ACID_PROTEIN_MAP)[Codon] const getProtein = (codon: string): Protein | 'INVALID' => ACID_PROTEIN_MAP[codon as Codon] || 'INVALID' export const translate = (rnaStrand: string): Protein[] => { const proteins: (typeof ACID_PROTEIN_MAP)[keyof typeof ACID_PROTEIN_MAP][] = [] if (rnaStrand) { for (let i = 0; i < rnaStrand.length; i += 3) { const protein = getProtein(rnaStrand.substring(i, i + 3)) if (protein) { if (protein === 'STOP') { break } if (protein === 'INVALID') { throw new Error('Invalid codon') } proteins.push(protein) } } } return proteins } ================================================ FILE: exercises/practice/protein-translation/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [2c44f7bf-ba20-43f7-a3bf-f2219c0c3f98] description = "Empty RNA sequence results in no proteins" [96d3d44f-34a2-4db4-84cd-fff523e069be] description = "Methionine RNA sequence" [1b4c56d8-d69f-44eb-be0e-7b17546143d9] description = "Phenylalanine RNA sequence 1" [81b53646-bd57-4732-b2cb-6b1880e36d11] description = "Phenylalanine RNA sequence 2" [42f69d4f-19d2-4d2c-a8b0-f0ae9ee1b6b4] description = "Leucine RNA sequence 1" [ac5edadd-08ed-40a3-b2b9-d82bb50424c4] description = "Leucine RNA sequence 2" [8bc36e22-f984-44c3-9f6b-ee5d4e73f120] description = "Serine RNA sequence 1" [5c3fa5da-4268-44e5-9f4b-f016ccf90131] description = "Serine RNA sequence 2" [00579891-b594-42b4-96dc-7ff8bf519606] description = "Serine RNA sequence 3" [08c61c3b-fa34-4950-8c4a-133945570ef6] description = "Serine RNA sequence 4" [54e1e7d8-63c0-456d-91d2-062c72f8eef5] description = "Tyrosine RNA sequence 1" [47bcfba2-9d72-46ad-bbce-22f7666b7eb1] description = "Tyrosine RNA sequence 2" [3a691829-fe72-43a7-8c8e-1bd083163f72] description = "Cysteine RNA sequence 1" [1b6f8a26-ca2f-43b8-8262-3ee446021767] description = "Cysteine RNA sequence 2" [1e91c1eb-02c0-48a0-9e35-168ad0cb5f39] description = "Tryptophan RNA sequence" [e547af0b-aeab-49c7-9f13-801773a73557] description = "STOP codon RNA sequence 1" [67640947-ff02-4f23-a2ef-816f8a2ba72e] description = "STOP codon RNA sequence 2" [9c2ad527-ebc9-4ace-808b-2b6447cb54cb] description = "STOP codon RNA sequence 3" [f4d9d8ee-00a8-47bf-a1e3-1641d4428e54] description = "Sequence of two protein codons translates into proteins" [dd22eef3-b4f1-4ad6-bb0b-27093c090a9d] description = "Sequence of two different protein codons translates into proteins" [d0f295df-fb70-425c-946c-ec2ec185388e] description = "Translate RNA strand into correct protein list" [e30e8505-97ec-4e5f-a73e-5726a1faa1f4] description = "Translation stops if STOP codon at beginning of sequence" [5358a20b-6f4c-4893-bce4-f929001710f3] description = "Translation stops if STOP codon at end of two-codon sequence" [ba16703a-1a55-482f-bb07-b21eef5093a3] description = "Translation stops if STOP codon at end of three-codon sequence" [4089bb5a-d5b4-4e71-b79e-b8d1f14a2911] description = "Translation stops if STOP codon in middle of three-codon sequence" [2c2a2a60-401f-4a80-b977-e0715b23b93d] description = "Translation stops if STOP codon in middle of six-codon sequence" [f6f92714-769f-4187-9524-e353e8a41a80] description = "Sequence of two non-STOP codons does not translate to a STOP codon" [1e75ea2a-f907-4994-ae5c-118632a1cb0f] description = "Non-existing codon can't translate" [9eac93f3-627a-4c90-8653-6d0a0595bc6f] description = "Unknown amino acids, not part of a codon, can't translate" reimplements = "1e75ea2a-f907-4994-ae5c-118632a1cb0f" [9d73899f-e68e-4291-b1e2-7bf87c00f024] description = "Incomplete RNA sequence can't translate" [43945cf7-9968-402d-ab9f-b8a28750b050] description = "Incomplete RNA sequence can translate if valid until a STOP codon" ================================================ FILE: exercises/practice/protein-translation/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/protein-translation/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/protein-translation/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/protein-translation/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/protein-translation/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/protein-translation/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/protein-translation/package.json ================================================ { "name": "@exercism/typescript-protein-translation", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/protein-translation/protein-translation.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { translate } from './protein-translation.ts' describe('Translate input RNA sequences into proteins', () => { it('Methionine RNA sequence', () => { const expected = ['Methionine'] expect(translate('AUG')).toEqual(expected) }) xit('Phenylalanine RNA sequence 1', () => { const expected = ['Phenylalanine'] expect(translate('UUU')).toEqual(expected) }) xit('Phenylalanine RNA sequence 2', () => { const expected = ['Phenylalanine'] expect(translate('UUC')).toEqual(expected) }) xit('Leucine RNA sequence 1', () => { const expected = ['Leucine'] expect(translate('UUA')).toEqual(expected) }) xit('Leucine RNA sequence 2', () => { const expected = ['Leucine'] expect(translate('UUG')).toEqual(expected) }) xit('Serine RNA sequence 1', () => { const expected = ['Serine'] expect(translate('UCU')).toEqual(expected) }) xit('Serine RNA sequence 2', () => { const expected = ['Serine'] expect(translate('UCC')).toEqual(expected) }) xit('Serine RNA sequence 3', () => { const expected = ['Serine'] expect(translate('UCA')).toEqual(expected) }) xit('Serine RNA sequence 4', () => { const expected = ['Serine'] expect(translate('UCG')).toEqual(expected) }) xit('Tyrosine RNA sequence 1', () => { const expected = ['Tyrosine'] expect(translate('UAU')).toEqual(expected) }) xit('Tyrosine RNA sequence 2', () => { const expected = ['Tyrosine'] expect(translate('UAC')).toEqual(expected) }) xit('Cysteine RNA sequence 1', () => { const expected = ['Cysteine'] expect(translate('UGU')).toEqual(expected) }) xit('Cysteine RNA sequence 2', () => { const expected = ['Cysteine'] expect(translate('UGC')).toEqual(expected) }) xit('Tryptophan RNA sequence', () => { const expected = ['Tryptophan'] expect(translate('UGG')).toEqual(expected) }) xit('STOP codon RNA sequence 1', () => { const expected: string[] = [] expect(translate('UAA')).toEqual(expected) }) xit('STOP codon RNA sequence 2', () => { const expected: string[] = [] expect(translate('UAG')).toEqual(expected) }) xit('STOP codon RNA sequence 3', () => { const expected: string[] = [] expect(translate('UGA')).toEqual(expected) }) xit('Sequence of two protein codons translates into proteins', () => { const expected = ['Phenylalanine', 'Phenylalanine'] expect(translate('UUUUUU')).toEqual(expected) }) xit('Sequence of two different protein codons translates into proteins', () => { const expected = ['Leucine', 'Leucine'] expect(translate('UUAUUG')).toEqual(expected) }) xit('Translate RNA strand into correct protein list', () => { const expected = ['Methionine', 'Phenylalanine', 'Tryptophan'] expect(translate('AUGUUUUGG')).toEqual(expected) }) xit('Translation stops if STOP codon at beginning of sequence', () => { const expected: string[] = [] expect(translate('UAGUGG')).toEqual(expected) }) xit('Translation stops if STOP codon at end of two-codon sequence', () => { const expected = ['Tryptophan'] expect(translate('UGGUAG')).toEqual(expected) }) xit('Translation stops if STOP codon at end of three-codon sequence', () => { const expected = ['Methionine', 'Phenylalanine'] expect(translate('AUGUUUUAA')).toEqual(expected) }) xit('Translation stops if STOP codon in middle of three-codon sequence', () => { const expected = ['Tryptophan'] expect(translate('UGGUAGUGG')).toEqual(expected) }) xit('Translation stops if STOP codon in middle of six-codon sequence', () => { const expected = ['Tryptophan', 'Cysteine', 'Tyrosine'] expect(translate('UGGUGUUAUUAAUGGUUU')).toEqual(expected) }) xit('Sequence of two non-STOP codons does not translate to a STOP codon', () => { const expected = ['Methionine', 'Methionine'] expect(translate('AUGAUG')).toEqual(expected) }) xit("Unknown amino acids, not part of a codon, can't translate", () => { expect(() => { translate('XYZ') }).toThrow('Invalid codon') }) xit("Incomplete RNA sequence can't translate", () => { expect(() => { translate('AUGU') }).toThrow('Invalid codon') }) xit('Incomplete RNA sequence can translate if valid until a STOP codon', () => { const expected = ['Phenylalanine', 'Phenylalanine'] expect(translate('UUCUUCUAAUGGU')).toEqual(expected) }) }) ================================================ FILE: exercises/practice/protein-translation/protein-translation.ts ================================================ export function translate() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/protein-translation/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/protein-translation/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/proverb/.docs/instructions.md ================================================ # Instructions For want of a horseshoe nail, a kingdom was lost, or so the saying goes. Given a list of inputs, generate the relevant proverb. For example, given the list `["nail", "shoe", "horse", "rider", "message", "battle", "kingdom"]`, you will output the full text of this proverbial rhyme: ```text For want of a nail the shoe was lost. For want of a shoe the horse was lost. For want of a horse the rider was lost. For want of a rider the message was lost. For want of a message the battle was lost. For want of a battle the kingdom was lost. And all for the want of a nail. ``` Note that the list of inputs may vary; your solution should be able to handle lists of arbitrary length and content. No line of the output text should be a static, unchanging string; all should vary according to the input given. ================================================ FILE: exercises/practice/proverb/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "proverb.ts" ], "test": [ "proverb.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "For want of a horseshoe nail, a kingdom was lost, or so the saying goes. Output the full text of this proverbial rhyme.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/For_Want_of_a_Nail" } ================================================ FILE: exercises/practice/proverb/.meta/proof.ci.ts ================================================ const conclusion = (firstArg: string): string => `And all for the want of a ${firstArg}.` export const proverb = (...args: string[]): string => { const allExceptLastArg = args.slice(0, -1) const chainOfEvents = allExceptLastArg.map( (arg, index) => `For want of a ${arg} the ${args[index + 1]} was lost.` ) chainOfEvents.push(conclusion(args[0])) return chainOfEvents.join('\n') } ================================================ FILE: exercises/practice/proverb/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [e974b73e-7851-484f-8d6d-92e07fe742fc] description = "zero pieces" [2fcd5f5e-8b82-4e74-b51d-df28a5e0faa4] description = "one piece" [d9d0a8a1-d933-46e2-aa94-eecf679f4b0e] description = "two pieces" [c95ef757-5e94-4f0d-a6cb-d2083f5e5a83] description = "three pieces" [433fb91c-35a2-4d41-aeab-4de1e82b2126] description = "full proverb" [c1eefa5a-e8d9-41c7-91d4-99fab6d6b9f7] description = "four pieces modernized" ================================================ FILE: exercises/practice/proverb/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/proverb/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/proverb/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/proverb/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/proverb/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/proverb/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/proverb/package.json ================================================ { "name": "@exercism/typescript-proverb", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/proverb/proverb.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { proverb } from './proverb.ts' describe('Proverb', () => { it('a single consequence', () => { const expected = `For want of a nail the shoe was lost.\nAnd all for the want of a nail.` expect(proverb('nail', 'shoe')).toEqual(expected) }) xit('a short chain of consequences', () => { const expected = `For want of a nail the shoe was lost.\nFor want of a shoe the horse was lost.\nAnd all for the want of a nail.` expect(proverb('nail', 'shoe', 'horse')).toEqual(expected) }) xit('a longer chain of consequences', () => { const expected = `For want of a nail the shoe was lost.\nFor want of a shoe the horse was lost.\nFor want of a horse the rider was lost.\nAnd all for the want of a nail.` expect(proverb('nail', 'shoe', 'horse', 'rider')).toEqual(expected) }) xit('proverb function does not hard code the rhyme dictionary', () => { const expected = `For want of a key the value was lost.\nAnd all for the want of a key.` expect(proverb('key', 'value')).toEqual(expected) }) xit('the whole proverb', () => { const expected = `For want of a nail the shoe was lost.\nFor want of a shoe the horse was lost.\nFor want of a horse the rider was lost.\nFor want of a rider the message was lost.\nFor want of a message the battle was lost.\nFor want of a battle the kingdom was lost.\nAnd all for the want of a nail.` expect( proverb('nail', 'shoe', 'horse', 'rider', 'message', 'battle', 'kingdom') ).toEqual(expected) }) xit('proverb is the same each time', () => { expect(proverb('nail', 'shoe')).toEqual(proverb('nail', 'shoe')) }) }) ================================================ FILE: exercises/practice/proverb/proverb.ts ================================================ export function proverb() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/proverb/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/proverb/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/pythagorean-triplet/.docs/instructions.md ================================================ # Description A Pythagorean triplet is a set of three natural numbers, {a, b, c}, for which, ```text a² + b² = c² ``` and such that, ```text a < b < c ``` For example, ```text 3² + 4² = 5². ``` Given an input integer N, find all Pythagorean triplets for which `a + b + c = N`. For example, with N = 1000, there is exactly one Pythagorean triplet for which `a + b + c = 1000`: `{200, 375, 425}`. ================================================ FILE: exercises/practice/pythagorean-triplet/.docs/introduction.md ================================================ # Introduction You are an accomplished problem-solver, known for your ability to tackle the most challenging mathematical puzzles. One evening, you receive an urgent letter from an inventor called the Triangle Tinkerer, who is working on a groundbreaking new project. The letter reads: > Dear Mathematician, > > I need your help. > I am designing a device that relies on the unique properties of Pythagorean triplets — sets of three integers that satisfy the equation a² + b² = c². > This device will revolutionize navigation, but for it to work, I must program it with every possible triplet where the sum of a, b, and c equals a specific number, N. > Calculating these triplets by hand would take me years, but I hear you are more than up to the task. > > Time is of the essence. > The future of my invention — and perhaps even the future of mathematical innovation — rests on your ability to solve this problem. Motivated by the importance of the task, you set out to find all Pythagorean triplets that satisfy the condition. Your work could have far-reaching implications, unlocking new possibilities in science and engineering. Can you rise to the challenge and make history? ================================================ FILE: exercises/practice/pythagorean-triplet/.meta/config.json ================================================ { "authors": [ "mdowds" ], "contributors": [ "masters3d", "mdmower", "SleeplessByte" ], "files": { "solution": [ "pythagorean-triplet.ts" ], "test": [ "pythagorean-triplet.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given an integer N, find all Pythagorean triplets for which a + b + c = N.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": true, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "A variation of Problem 9 from Project Euler", "source_url": "https://projecteuler.net/problem=9" } ================================================ FILE: exercises/practice/pythagorean-triplet/.meta/proof.ci.ts ================================================ type Options = { minFactor?: number maxFactor?: number sum: number } type TripletArray = [number, number, number] class Triplet { constructor( private readonly a: TripletArray[0], private readonly b: TripletArray[1], private readonly c: TripletArray[2] ) {} public toArray(): TripletArray { return [this.a, this.b, this.c] } public get pythagorean(): boolean { return this.a * this.a + this.b * this.b === this.c * this.c } public get sum(): number { return this.a + this.b + this.c } } export function triplets({ minFactor, maxFactor, sum }: Options): Triplet[] { const min = minFactor || 1 const max = maxFactor || sum - 1 const isDesired = (triplet: Triplet): boolean => { return triplet.pythagorean && (!sum || triplet.sum === sum) } const result: Triplet[] = [] for (let a = min; a < max - 1; a += 1) { for (let b = a + 1; b < max; b += 1) { for (let c = b + 1; c <= max; c += 1) { const triplet = new Triplet(a, b, c) if (isDesired(triplet)) { result.push(triplet) } } } } return result } ================================================ FILE: exercises/practice/pythagorean-triplet/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [a19de65d-35b8-4480-b1af-371d9541e706] description = "triplets whose sum is 12" [48b21332-0a3d-43b2-9a52-90b2a6e5c9f5] description = "triplets whose sum is 108" [dffc1266-418e-4daa-81af-54c3e95c3bb5] description = "triplets whose sum is 1000" [5f86a2d4-6383-4cce-93a5-e4489e79b186] description = "no matching triplets for 1001" [bf17ba80-1596-409a-bb13-343bdb3b2904] description = "returns all matching triplets" [9d8fb5d5-6c6f-42df-9f95-d3165963ac57] description = "several matching triplets" [f5be5734-8aa0-4bd1-99a2-02adcc4402b4] description = "triplets for large number" ================================================ FILE: exercises/practice/pythagorean-triplet/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/pythagorean-triplet/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/pythagorean-triplet/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/pythagorean-triplet/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/pythagorean-triplet/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/pythagorean-triplet/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/pythagorean-triplet/package.json ================================================ { "name": "@exercism/typescript-pythagorean-triplet", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/pythagorean-triplet/pythagorean-triplet.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { triplets } from './pythagorean-triplet.ts' type Triplet = [number, number, number] function tripletsWithSum(sum: number, options = {}): Triplet[] { return triplets({ ...options, sum }).map((triplet) => triplet.toArray().sort((a, b) => a - b) ) } describe('Triplet', () => { it('triplets whose sum is 12', () => { expect(tripletsWithSum(12)).toEqual([[3, 4, 5]]) }) xit('triplets whose sum is 108', () => { expect(tripletsWithSum(108)).toEqual([[27, 36, 45]]) }) xit('triplets whose sum is 1000', () => { expect(tripletsWithSum(1000)).toEqual([[200, 375, 425]]) }) xit('no matching triplets for 1001', () => { expect(tripletsWithSum(1001)).toEqual([]) }) xit('returns all matching triplets', () => { expect(tripletsWithSum(90)).toEqual([ [9, 40, 41], [15, 36, 39], ]) }) xit('several matching triplets', () => { expect(tripletsWithSum(840)).toEqual([ [40, 399, 401], [56, 390, 394], [105, 360, 375], [120, 350, 370], [140, 336, 364], [168, 315, 357], [210, 280, 350], [240, 252, 348], ]) }) xit('returns triplets with no factor smaller than minimum factor', () => { expect(tripletsWithSum(90, { minFactor: 10 })).toEqual([[15, 36, 39]]) }) xit('returns triplets with no factor larger than maximum factor', () => { expect(tripletsWithSum(840, { maxFactor: 349 })).toEqual([[240, 252, 348]]) }) xit('returns triplets with factors in range', () => { expect(tripletsWithSum(840, { maxFactor: 352, minFactor: 150 })).toEqual([ [210, 280, 350], [240, 252, 348], ]) }) it.skip('triplets for large number', () => { expect(tripletsWithSum(30000)).toEqual([ [1200, 14375, 14425], [1875, 14000, 14125], [5000, 12000, 13000], [6000, 11250, 12750], [7500, 10000, 12500], ]) }) }) ================================================ FILE: exercises/practice/pythagorean-triplet/pythagorean-triplet.ts ================================================ type Options = { minFactor?: number maxFactor?: number sum: number } export function triplets({}: Options): Triplet[] { throw new Error('Remove this line and implement the function') } class Triplet { constructor() { throw new Error('Remove this line and implement the function') } toArray(): [number, number, number] { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/pythagorean-triplet/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/pythagorean-triplet/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/queen-attack/.docs/instructions.append.md ================================================ # Instructions append A queen must be placed on a valid position on the board. Two queens cannot share the same position. If a position has not been given, the queens are at their [default starting positions](https://en.wikipedia.org/wiki/Rules_of_chess#Initial_setup). That's the bottom row (1) for the white queen and the top row (8) for the black queen. Both queens start in the fourth column (d). ```text a b c d e f g h 8 _ _ _ B _ _ _ _ 8 7 _ _ _ _ _ _ _ _ 7 6 _ _ _ _ _ _ _ _ 6 5 _ _ _ _ _ _ _ _ 5 4 _ _ _ _ _ _ _ _ 4 3 _ _ _ _ _ _ _ _ 3 2 _ _ _ _ _ _ _ _ 2 1 _ _ _ W _ _ _ _ 1 a b c d e f g h ``` ================================================ FILE: exercises/practice/queen-attack/.docs/instructions.md ================================================ # Instructions Given the position of two queens on a chess board, indicate whether or not they are positioned so that they can attack each other. In the game of chess, a queen can attack pieces which are on the same row, column, or diagonal. A chessboard can be represented by an 8 by 8 array. So if you are told the white queen is at `c5` (zero-indexed at column 2, row 3) and the black queen at `f2` (zero-indexed at column 5, row 6), then you know that the set-up is like so: ![A chess board with two queens. Arrows emanating from the queen at c5 indicate possible directions of capture along file, rank and diagonal.](https://assets.exercism.org/images/exercises/queen-attack/queen-capture.svg) You are also able to answer whether the queens can attack each other. In this case, that answer would be yes, they can, because both pieces share a diagonal. ## Credit The chessboard image was made by [habere-et-dispertire][habere-et-dispertire] using LaTeX and the [chessboard package][chessboard-package] by Ulrike Fischer. [habere-et-dispertire]: https://exercism.org/profiles/habere-et-dispertire [chessboard-package]: https://github.com/u-fischer/chessboard ================================================ FILE: exercises/practice/queen-attack/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "queen-attack.ts" ], "test": [ "queen-attack.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given the position of two queens on a chess board, indicate whether or not they are positioned so that they can attack each other.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "J Dalbey's Programming Practice problems", "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" } ================================================ FILE: exercises/practice/queen-attack/.meta/proof.ci.ts ================================================ const W = 8 const H = 8 const STARTING: Positions = { black: [0, 3], white: [7, 3] } as const type Position = readonly [number, number] type Board = string[] type Positions = { white: Position black: Position } function invalidPosition({ white, black }: Positions): boolean { if (white[0] < 0 || white[0] >= H || white[1] < 0 || white[1] >= W) { return true } if (black[0] < 0 || black[0] >= H || black[1] < 0 || black[1] >= W) { return true } return false } function samePosition({ white, black }: Positions): boolean { return white[0] === black[0] && white[1] === black[1] } function constructBoard(): Board { return new Array(W * H).fill('_') } function placePieces(self: QueenAttack): void { const board = self.board const [blackRow, blackColumn] = self.black const [whiteRow, whiteColumn] = self.white board[blackRow * W + blackColumn] = 'B' board[whiteRow * W + whiteColumn] = 'W' } export class QueenAttack { public readonly black: Position public readonly white: Position public readonly board: string[] constructor(params: Partial = {}) { const fullParams = { ...STARTING, ...params } if (invalidPosition(fullParams)) { throw new Error('Queen must be placed on the board') } if (samePosition(fullParams)) { throw new Error('Queens cannot share the same space') } this.black = fullParams.black this.white = fullParams.white this.board = constructBoard() placePieces(this) return this } public get canAttack(): boolean { // Same row or column if (this.black[0] === this.white[0] || this.black[1] === this.white[1]) { return true } // Diagonally return ( Math.abs(this.black[0] - this.white[0]) === Math.abs(this.black[1] - this.white[1]) ) } public toString(): string { return Array.from({ length: H }, (_, row) => this.board.slice(row * H, row * H + W).join(' ') ).join('\n') } } ================================================ FILE: exercises/practice/queen-attack/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [3ac4f735-d36c-44c4-a3e2-316f79704203] description = "Test creation of Queens with valid and invalid positions -> queen with a valid position" [4e812d5d-b974-4e38-9a6b-8e0492bfa7be] description = "Test creation of Queens with valid and invalid positions -> queen must have positive row" [f07b7536-b66b-4f08-beb9-4d70d891d5c8] description = "Test creation of Queens with valid and invalid positions -> queen must have row on board" [15a10794-36d9-4907-ae6b-e5a0d4c54ebe] description = "Test creation of Queens with valid and invalid positions -> queen must have positive column" [6907762d-0e8a-4c38-87fb-12f2f65f0ce4] description = "Test creation of Queens with valid and invalid positions -> queen must have column on board" [33ae4113-d237-42ee-bac1-e1e699c0c007] description = "Test the ability of one queen to attack another -> cannot attack" [eaa65540-ea7c-4152-8c21-003c7a68c914] description = "Test the ability of one queen to attack another -> can attack on same row" [bae6f609-2c0e-4154-af71-af82b7c31cea] description = "Test the ability of one queen to attack another -> can attack on same column" [0e1b4139-b90d-4562-bd58-dfa04f1746c7] description = "Test the ability of one queen to attack another -> can attack on first diagonal" [ff9b7ed4-e4b6-401b-8d16-bc894d6d3dcd] description = "Test the ability of one queen to attack another -> can attack on second diagonal" [0a71e605-6e28-4cc2-aa47-d20a2e71037a] description = "Test the ability of one queen to attack another -> can attack on third diagonal" [0790b588-ae73-4f1f-a968-dd0b34f45f86] description = "Test the ability of one queen to attack another -> can attack on fourth diagonal" [543f8fd4-2597-4aad-8d77-cbdab63619f8] description = "Test the ability of one queen to attack another -> cannot attack if falling diagonals are only the same when reflected across the longest falling diagonal" ================================================ FILE: exercises/practice/queen-attack/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/queen-attack/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/queen-attack/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/queen-attack/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/queen-attack/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/queen-attack/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/queen-attack/package.json ================================================ { "name": "@exercism/typescript-queen-attack", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/queen-attack/queen-attack.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { QueenAttack } from './queen-attack.ts' describe('Queens', () => { describe('Test creation of Queens with valid and invalid positions', () => { it('queen with a valid position', () => { const queens = new QueenAttack({ white: [2, 2] }) expect(queens.white).toEqual([2, 2]) }) xit('queen must have positive row', () => { const positioning = { white: [-2, 2] } as const const expectedError = 'Queen must be placed on the board' expect(() => new QueenAttack(positioning)).toThrow(expectedError) }) xit('queen must have row on board', () => { const positioning = { white: [8, 4] } as const const expectedError = 'Queen must be placed on the board' expect(() => new QueenAttack(positioning)).toThrow(expectedError) }) xit('queen must have positive column', () => { const positioning = { white: [2, -2] } as const const expectedError = 'Queen must be placed on the board' expect(() => new QueenAttack(positioning)).toThrow(expectedError) }) xit('queen must have column on board', () => { const positioning = { white: [4, 8] } as const const expectedError = 'Queen must be placed on the board' expect(() => new QueenAttack(positioning)).toThrow(expectedError) }) xit('two queens cannot occupy the same space', () => { const positioning = { white: [2, 4], black: [2, 4] } as const const expectedError = 'Queens cannot share the same space' expect(() => new QueenAttack(positioning)).toThrow(expectedError) }) }) describe('Test the ability of one queen to attack another', () => { xit('queens cannot attack', () => { const queens = new QueenAttack({ white: [2, 4], black: [6, 6] }) expect(queens.canAttack).toEqual(false) }) xit('queens can attack when they are on the same row', () => { const queens = new QueenAttack({ white: [2, 4], black: [2, 6] }) expect(queens.canAttack).toEqual(true) }) xit('queens can attack when they are on the same column', () => { const queens = new QueenAttack({ white: [4, 5], black: [2, 5] }) expect(queens.canAttack).toEqual(true) }) xit('queens can attack diagonally', () => { const queens = new QueenAttack({ white: [2, 2], black: [0, 4] }) expect(queens.canAttack).toEqual(true) }) xit('queens can attack another diagonally', () => { const queens = new QueenAttack({ white: [2, 2], black: [3, 1] }) expect(queens.canAttack).toEqual(true) }) xit('queens can attack yet another diagonally', () => { const queens = new QueenAttack({ white: [2, 2], black: [1, 1] }) expect(queens.canAttack).toEqual(true) }) xit('queens can attack diagonally, really', () => { const queens = new QueenAttack({ white: [1, 7], black: [0, 6] }) expect(queens.canAttack).toEqual(true) }) xit('queens can attack on a north-east/south-west diagonal', () => { const queens = new QueenAttack({ white: [7, 0], black: [0, 7] }) expect(queens.canAttack).toEqual(true) }) xit('queens can attack on another ne/sw diagonal', () => { const queens = new QueenAttack({ white: [2, 6], black: [5, 3] }) expect(queens.canAttack).toEqual(true) }) xit('queens cannot attack if falling diagonals are only the same when reflected across the longest falling diagonal', () => { const queens = new QueenAttack({ white: [4, 1], black: [2, 5] }) expect(queens.canAttack).toEqual(false) }) }) describe('Test the board visualisation', () => { xit('board', () => { const positioning = { white: [3, 2], black: [6, 5] } as const const queens = new QueenAttack(positioning) const board = [ '_ _ _ _ _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ W _ _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ _ _ _ B _ _', '_ _ _ _ _ _ _ _', ].join('\n') expect(queens.toString()).toEqual(board) }) xit('board with queens at their starting positions', () => { const queens = new QueenAttack() const board = [ '_ _ _ B _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ _ W _ _ _ _', ].join('\n') expect(queens.toString()).toEqual(board) }) xit('board with the black queen at her starting positions', () => { const queens = new QueenAttack({ white: [1, 6] }) const board = [ '_ _ _ B _ _ _ _', '_ _ _ _ _ _ W _', '_ _ _ _ _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ _ _ _ _ _ _', ].join('\n') expect(queens.toString()).toEqual(board) }) xit('board with queens at the edges', () => { const positioning = { white: [0, 0], black: [7, 7] } as const const queens = new QueenAttack(positioning) const board = [ 'W _ _ _ _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ _ _ _ _ _ _', '_ _ _ _ _ _ _ B', ].join('\n') expect(queens.toString()).toEqual(board) }) }) }) ================================================ FILE: exercises/practice/queen-attack/queen-attack.ts ================================================ type Position = readonly [number, number] type Positions = { white: Position black: Position } export class QueenAttack { public readonly black: Position public readonly white: Position // white: [whiteRow, whiteColumn] // black: [blackRow, blackColumn] constructor({}: Partial = {}) { throw new Error('Remove this line and implement the function') } toString() { throw new Error('Remove this line and implement the function') } get canAttack() { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/queen-attack/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/queen-attack/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/raindrops/.docs/instructions.md ================================================ # Instructions Your task is to convert a number into its corresponding raindrop sounds. If a given number: - is divisible by 3, add "Pling" to the result. - is divisible by 5, add "Plang" to the result. - is divisible by 7, add "Plong" to the result. - **is not** divisible by 3, 5, or 7, the result should be the number as a string. ## Examples - 28 is divisible by 7, but not 3 or 5, so the result would be `"Plong"`. - 30 is divisible by 3 and 5, but not 7, so the result would be `"PlingPlang"`. - 34 is not divisible by 3, 5, or 7, so the result would be `"34"`. ~~~~exercism/note A common way to test if one number is evenly divisible by another is to compare the [remainder][remainder] or [modulus][modulo] to zero. Most languages provide operators or functions for one (or both) of these. [remainder]: https://exercism.org/docs/programming/operators/remainder [modulo]: https://en.wikipedia.org/wiki/Modulo_operation ~~~~ ================================================ FILE: exercises/practice/raindrops/.docs/introduction.md ================================================ # Introduction Raindrops is a slightly more complex version of the FizzBuzz challenge, a classic interview question. ================================================ FILE: exercises/practice/raindrops/.meta/config.json ================================================ { "authors": [], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "raindrops.ts" ], "test": [ "raindrops.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Convert a number into its corresponding raindrop sounds - Pling, Plang and Plong.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "A variation on FizzBuzz, a famous technical interview question that is intended to weed out potential candidates. That question is itself derived from Fizz Buzz, a popular children's game for teaching division.", "source_url": "https://en.wikipedia.org/wiki/Fizz_buzz" } ================================================ FILE: exercises/practice/raindrops/.meta/proof.ci.ts ================================================ export function convert(drops: number): string { let converted = '' if (drops % 3 === 0) { converted += 'Pling' } if (drops % 5 === 0) { converted += 'Plang' } if (drops % 7 === 0) { converted += 'Plong' } return converted ? converted : drops.toString() } ================================================ FILE: exercises/practice/raindrops/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [1575d549-e502-46d4-a8e1-6b7bec6123d8] description = "the sound for 1 is 1" [1f51a9f9-4895-4539-b182-d7b0a5ab2913] description = "the sound for 3 is Pling" [2d9bfae5-2b21-4bcd-9629-c8c0e388f3e0] description = "the sound for 5 is Plang" [d7e60daa-32ef-4c23-b688-2abff46c4806] description = "the sound for 7 is Plong" [6bb4947b-a724-430c-923f-f0dc3d62e56a] description = "the sound for 6 is Pling as it has a factor 3" [ce51e0e8-d9d4-446d-9949-96eac4458c2d] description = "2 to the power 3 does not make a raindrop sound as 3 is the exponent not the base" [0dd66175-e3e2-47fc-8750-d01739856671] description = "the sound for 9 is Pling as it has a factor 3" [022c44d3-2182-4471-95d7-c575af225c96] description = "the sound for 10 is Plang as it has a factor 5" [37ab74db-fed3-40ff-b7b9-04acdfea8edf] description = "the sound for 14 is Plong as it has a factor of 7" [31f92999-6afb-40ee-9aa4-6d15e3334d0f] description = "the sound for 15 is PlingPlang as it has factors 3 and 5" [ff9bb95d-6361-4602-be2c-653fe5239b54] description = "the sound for 21 is PlingPlong as it has factors 3 and 7" [d2e75317-b72e-40ab-8a64-6734a21dece1] description = "the sound for 25 is Plang as it has a factor 5" [a09c4c58-c662-4e32-97fe-f1501ef7125c] description = "the sound for 27 is Pling as it has a factor 3" [bdf061de-8564-4899-a843-14b48b722789] description = "the sound for 35 is PlangPlong as it has factors 5 and 7" [c4680bee-69ba-439d-99b5-70c5fd1a7a83] description = "the sound for 49 is Plong as it has a factor 7" [17f2bc9a-b65a-4d23-8ccd-266e8c271444] description = "the sound for 52 is 52" [e46677ed-ff1a-419f-a740-5c713d2830e4] description = "the sound for 105 is PlingPlangPlong as it has factors 3, 5 and 7" [13c6837a-0fcd-4b86-a0eb-20572f7deb0b] description = "the sound for 3125 is Plang as it has a factor 5" ================================================ FILE: exercises/practice/raindrops/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/raindrops/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/raindrops/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/raindrops/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/raindrops/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/raindrops/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/raindrops/package.json ================================================ { "name": "@exercism/typescript-raindrops", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/raindrops/raindrops.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { convert } from './raindrops.ts' describe('Raindrops', () => { it('converts 1', () => expect(convert(1)).toEqual('1')) xit('converts 3', () => expect(convert(3)).toEqual('Pling')) xit('converts 5', () => expect(convert(5)).toEqual('Plang')) xit('converts 7', () => expect(convert(7)).toEqual('Plong')) xit('converts 6', () => expect(convert(6)).toEqual('Pling')) xit('converts 9', () => expect(convert(9)).toEqual('Pling')) xit('converts 10', () => expect(convert(10)).toEqual('Plang')) xit('converts 14', () => expect(convert(14)).toEqual('Plong')) xit('converts 15', () => expect(convert(15)).toEqual('PlingPlang')) xit('converts 21', () => expect(convert(21)).toEqual('PlingPlong')) xit('converts 25', () => expect(convert(25)).toEqual('Plang')) xit('converts 35', () => expect(convert(35)).toEqual('PlangPlong')) xit('converts 49', () => expect(convert(49)).toEqual('Plong')) xit('converts 52', () => expect(convert(52)).toEqual('52')) xit('converts 105', () => expect(convert(105)).toEqual('PlingPlangPlong')) xit('converts 12121', () => expect(convert(12121)).toEqual('12121')) }) ================================================ FILE: exercises/practice/raindrops/raindrops.ts ================================================ export function convert() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/raindrops/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/raindrops/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/rational-numbers/.docs/instructions.md ================================================ # Instructions A rational number is defined as the quotient of two integers `a` and `b`, called the numerator and denominator, respectively, where `b != 0`. ~~~~exercism/note Note that mathematically, the denominator can't be zero. However in many implementations of rational numbers, you will find that the denominator is allowed to be zero with behaviour similar to positive or negative infinity in floating point numbers. In those cases, the denominator and numerator generally still can't both be zero at once. ~~~~ The absolute value `|r|` of the rational number `r = a/b` is equal to `|a|/|b|`. The sum of two rational numbers `r₁ = a₁/b₁` and `r₂ = a₂/b₂` is `r₁ + r₂ = a₁/b₁ + a₂/b₂ = (a₁ * b₂ + a₂ * b₁) / (b₁ * b₂)`. The difference of two rational numbers `r₁ = a₁/b₁` and `r₂ = a₂/b₂` is `r₁ - r₂ = a₁/b₁ - a₂/b₂ = (a₁ * b₂ - a₂ * b₁) / (b₁ * b₂)`. The product (multiplication) of two rational numbers `r₁ = a₁/b₁` and `r₂ = a₂/b₂` is `r₁ * r₂ = (a₁ * a₂) / (b₁ * b₂)`. Dividing a rational number `r₁ = a₁/b₁` by another `r₂ = a₂/b₂` is `r₁ / r₂ = (a₁ * b₂) / (a₂ * b₁)` if `a₂` is not zero. Exponentiation of a rational number `r = a/b` to a non-negative integer power `n` is `r^n = (a^n)/(b^n)`. Exponentiation of a rational number `r = a/b` to a negative integer power `n` is `r^n = (b^m)/(a^m)`, where `m = |n|`. Exponentiation of a rational number `r = a/b` to a real (floating-point) number `x` is the quotient `(a^x)/(b^x)`, which is a real number. Exponentiation of a real number `x` to a rational number `r = a/b` is `x^(a/b) = root(x^a, b)`, where `root(p, q)` is the `q`th root of `p`. Implement the following operations: - addition, subtraction, multiplication and division of two rational numbers, - absolute value, exponentiation of a given rational number to an integer power, exponentiation of a given rational number to a real (floating-point) power, exponentiation of a real number to a rational number. Your implementation of rational numbers should always be reduced to lowest terms. For example, `4/4` should reduce to `1/1`, `30/60` should reduce to `1/2`, `12/8` should reduce to `3/2`, etc. To reduce a rational number `r = a/b`, divide `a` and `b` by the greatest common divisor (gcd) of `a` and `b`. So, for example, `gcd(12, 8) = 4`, so `r = 12/8` can be reduced to `(12/4)/(8/4) = 3/2`. The reduced form of a rational number should be in "standard form" (the denominator should always be a positive integer). If a denominator with a negative integer is present, multiply both numerator and denominator by `-1` to ensure standard form is reached. For example, `3/-4` should be reduced to `-3/4` Assume that the programming language you are using does not have an implementation of rational numbers. ================================================ FILE: exercises/practice/rational-numbers/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte", "angelikatyborska" ], "files": { "solution": [ "rational-numbers.ts" ], "test": [ "rational-numbers.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Implement rational numbers.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Rational_number" } ================================================ FILE: exercises/practice/rational-numbers/.meta/proof.ci.ts ================================================ export class Rational { public numerator: number public denominator: number constructor(numerator: number, denominator: number) { if (denominator === 0) { throw new Error('Denominator must not be zero.') } this.numerator = numerator this.denominator = denominator this.reduce() this.ensureSignInNumerator() } public add(that: Rational): Rational { const commonDenominator = this.denominator * that.denominator return new Rational( this.numerator * that.denominator + that.numerator * this.denominator, commonDenominator ) } public sub(that: Rational): Rational { const commonDenominator = this.denominator * that.denominator return new Rational( this.numerator * that.denominator - that.numerator * this.denominator, commonDenominator ) } public mul(that: Rational): Rational { return new Rational( this.numerator * that.numerator, this.denominator * that.denominator ) } public div(that: Rational): Rational { return new Rational( this.numerator * that.denominator, this.denominator * that.numerator ) } public abs(): Rational { return new Rational(Math.abs(this.numerator), Math.abs(this.denominator)) } public exprational(n: number): Rational { if (n > 0) { return new Rational( Math.pow(this.numerator, n), Math.pow(this.denominator, n) ) } else { return new Rational( Math.pow(this.denominator, Math.abs(n)), Math.pow(this.numerator, Math.abs(n)) ) } } public expreal(base: number): number { return Math.pow( 10.0, Math.log10(Math.pow(base, this.numerator)) / this.denominator ) } public reduce(): this { const commonDivisor = this.gcd(this.numerator, this.denominator) this.numerator /= commonDivisor this.denominator /= commonDivisor this.ensureSignInNumerator() return this } private gcd(a: number, b: number): number { let localA = a let localB = b while (localB !== 0) { const t = localB localB = localA % localB localA = t } return localA } private ensureSignInNumerator(): void { if (this.denominator < 0) { this.denominator = -this.denominator this.numerator = -this.numerator } } } ================================================ FILE: exercises/practice/rational-numbers/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [0ba4d988-044c-4ed5-9215-4d0bb8d0ae9f] description = "Arithmetic -> Addition -> Add two positive rational numbers" [88ebc342-a2ac-4812-a656-7b664f718b6a] description = "Arithmetic -> Addition -> Add a positive rational number and a negative rational number" [92ed09c2-991e-4082-a602-13557080205c] description = "Arithmetic -> Addition -> Add two negative rational numbers" [6e58999e-3350-45fb-a104-aac7f4a9dd11] description = "Arithmetic -> Addition -> Add a rational number to its additive inverse" [47bba350-9db1-4ab9-b412-4a7e1f72a66e] description = "Arithmetic -> Subtraction -> Subtract two positive rational numbers" [93926e2a-3e82-4aee-98a7-fc33fb328e87] description = "Arithmetic -> Subtraction -> Subtract a positive rational number and a negative rational number" [a965ba45-9b26-442b-bdc7-7728e4b8d4cc] description = "Arithmetic -> Subtraction -> Subtract two negative rational numbers" [0df0e003-f68e-4209-8c6e-6a4e76af5058] description = "Arithmetic -> Subtraction -> Subtract a rational number from itself" [34fde77a-75f4-4204-8050-8d3a937958d3] description = "Arithmetic -> Multiplication -> Multiply two positive rational numbers" [6d015cf0-0ea3-41f1-93de-0b8e38e88bae] description = "Arithmetic -> Multiplication -> Multiply a negative rational number by a positive rational number" [d1bf1b55-954e-41b1-8c92-9fc6beeb76fa] description = "Arithmetic -> Multiplication -> Multiply two negative rational numbers" [a9b8f529-9ec7-4c79-a517-19365d779040] description = "Arithmetic -> Multiplication -> Multiply a rational number by its reciprocal" [d89d6429-22fa-4368-ab04-9e01a44d3b48] description = "Arithmetic -> Multiplication -> Multiply a rational number by 1" [0d95c8b9-1482-4ed7-bac9-b8694fa90145] description = "Arithmetic -> Multiplication -> Multiply a rational number by 0" [1de088f4-64be-4e6e-93fd-5997ae7c9798] description = "Arithmetic -> Division -> Divide two positive rational numbers" [7d7983db-652a-4e66-981a-e921fb38d9a9] description = "Arithmetic -> Division -> Divide a positive rational number by a negative rational number" [1b434d1b-5b38-4cee-aaf5-b9495c399e34] description = "Arithmetic -> Division -> Divide two negative rational numbers" [d81c2ebf-3612-45a6-b4e0-f0d47812bd59] description = "Arithmetic -> Division -> Divide a rational number by 1" [5fee0d8e-5955-4324-acbe-54cdca94ddaa] description = "Absolute value -> Absolute value of a positive rational number" [3cb570b6-c36a-4963-a380-c0834321bcaa] description = "Absolute value -> Absolute value of a positive rational number with negative numerator and denominator" [6a05f9a0-1f6b-470b-8ff7-41af81773f25] description = "Absolute value -> Absolute value of a negative rational number" [5d0f2336-3694-464f-8df9-f5852fda99dd] description = "Absolute value -> Absolute value of a negative rational number with negative denominator" [f8e1ed4b-9dca-47fb-a01e-5311457b3118] description = "Absolute value -> Absolute value of zero" [4a8c939f-f958-473b-9f88-6ad0f83bb4c4] description = "Absolute value -> Absolute value of a rational number is reduced to lowest terms" [ea2ad2af-3dab-41e7-bb9f-bd6819668a84] description = "Exponentiation of a rational number -> Raise a positive rational number to a positive integer power" [8168edd2-0af3-45b1-b03f-72c01332e10a] description = "Exponentiation of a rational number -> Raise a negative rational number to a positive integer power" [c291cfae-cfd8-44f5-aa6c-b175c148a492] description = "Exponentiation of a rational number -> Raise a positive rational number to a negative integer power" [45cb3288-4ae4-4465-9ae5-c129de4fac8e] description = "Exponentiation of a rational number -> Raise a negative rational number to an even negative integer power" [2d47f945-ffe1-4916-a399-c2e8c27d7f72] description = "Exponentiation of a rational number -> Raise a negative rational number to an odd negative integer power" [e2f25b1d-e4de-4102-abc3-c2bb7c4591e4] description = "Exponentiation of a rational number -> Raise zero to an integer power" [431cac50-ab8b-4d58-8e73-319d5404b762] description = "Exponentiation of a rational number -> Raise one to an integer power" [7d164739-d68a-4a9c-b99f-dd77ce5d55e6] description = "Exponentiation of a rational number -> Raise a positive rational number to the power of zero" [eb6bd5f5-f880-4bcd-8103-e736cb6e41d1] description = "Exponentiation of a rational number -> Raise a negative rational number to the power of zero" [30b467dd-c158-46f5-9ffb-c106de2fd6fa] description = "Exponentiation of a real number to a rational number -> Raise a real number to a positive rational number" [6e026bcc-be40-4b7b-ae22-eeaafc5a1789] description = "Exponentiation of a real number to a rational number -> Raise a real number to a negative rational number" [9f866da7-e893-407f-8cd2-ee85d496eec5] description = "Exponentiation of a real number to a rational number -> Raise a real number to a zero rational number" [0a63fbde-b59c-4c26-8237-1e0c73354d0a] description = "Reduction to lowest terms -> Reduce a positive rational number to lowest terms" [5ed6f248-ad8d-4d4e-a545-9146c6727f33] description = "Reduction to lowest terms -> Reduce places the minus sign on the numerator" [f87c2a4e-d29c-496e-a193-318c503e4402] description = "Reduction to lowest terms -> Reduce a negative rational number to lowest terms" [3b92ffc0-5b70-4a43-8885-8acee79cdaaf] description = "Reduction to lowest terms -> Reduce a rational number with a negative denominator to lowest terms" [c9dbd2e6-5ac0-4a41-84c1-48b645b4f663] description = "Reduction to lowest terms -> Reduce zero to lowest terms" [297b45ad-2054-4874-84d4-0358dc1b8887] description = "Reduction to lowest terms -> Reduce an integer to lowest terms" [a73a17fe-fe8c-4a1c-a63b-e7579e333d9e] description = "Reduction to lowest terms -> Reduce one to lowest terms" ================================================ FILE: exercises/practice/rational-numbers/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/rational-numbers/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/rational-numbers/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/rational-numbers/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/rational-numbers/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/rational-numbers/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/rational-numbers/package.json ================================================ { "name": "@exercism/typescript-rational-numbers", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/rational-numbers/rational-numbers.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { Rational } from './rational-numbers.ts' /* eslint-disable jest/expect-expect */ function assertRational( actual: Rational, expectedNumerator: number, expectedDenominator: number ): void { expect(actual.numerator).toEqual(expectedNumerator) expect(actual.denominator).toEqual(expectedDenominator) } describe('Addition', () => { it('Add two positive rational numbers', () => { const actual = new Rational(1, 2).add(new Rational(2, 3)) assertRational(actual, 7, 6) }) xit('Add a positive rational number and a negative rational number', () => { const actual = new Rational(1, 2).add(new Rational(-2, 3)) assertRational(actual, -1, 6) }) xit('Add two negative rational numbers', () => { const actual = new Rational(-1, 2).add(new Rational(-2, 3)) assertRational(actual, -7, 6) }) xit('Add a rational number to its additive inverse', () => { const actual = new Rational(1, 2).add(new Rational(-1, 2)) assertRational(actual, 0, 1) }) }) describe('Subtraction', () => { xit('Subtract two positive rational numbers', () => { const actual = new Rational(1, 2).sub(new Rational(2, 3)) assertRational(actual, -1, 6) }) xit('Subtract a positive rational number and a negative rational number', () => { const actual = new Rational(1, 2).sub(new Rational(-2, 3)) assertRational(actual, 7, 6) }) xit('Subtract two negative rational numbers', () => { const actual = new Rational(-1, 2).sub(new Rational(-2, 3)) assertRational(actual, 1, 6) }) xit('Subtract a rational number from itself', () => { const actual = new Rational(1, 2).sub(new Rational(1, 2)) assertRational(actual, 0, 1) }) }) describe('Multiplication', () => { xit('Multiply two positive rational numbers', () => { const actual = new Rational(1, 2).mul(new Rational(2, 3)) assertRational(actual, 1, 3) }) xit('Multiply a negative rational number by a positive rational number', () => { const actual = new Rational(-1, 2).mul(new Rational(2, 3)) assertRational(actual, -1, 3) }) xit('Multiply two negative rational numbers', () => { const actual = new Rational(-1, 2).mul(new Rational(-2, 3)) assertRational(actual, 1, 3) }) xit('Multiply a rational number by its reciprocal', () => { const actual = new Rational(1, 2).mul(new Rational(2, 1)) assertRational(actual, 1, 1) }) xit('Multiply a rational number by 1', () => { const actual = new Rational(1, 2).mul(new Rational(1, 1)) assertRational(actual, 1, 2) }) xit('Multiply a rational number by 0', () => { const actual = new Rational(1, 2).mul(new Rational(0, 1)) assertRational(actual, 0, 1) }) }) describe('Division', () => { xit('Divide two positive rational numbers', () => { const actual = new Rational(1, 2).div(new Rational(2, 3)) assertRational(actual, 3, 4) }) xit('Divide a positive rational number by a negative rational number', () => { const actual = new Rational(1, 2).div(new Rational(-2, 3)) assertRational(actual, -3, 4) }) xit('Divide two negative rational numbers', () => { const actual = new Rational(-1, 2).div(new Rational(-2, 3)) assertRational(actual, 3, 4) }) xit('Divide a rational number by 1', () => { const actual = new Rational(1, 2).div(new Rational(1, 1)) assertRational(actual, 1, 2) }) }) describe('Absolute value', () => { xit('Absolute value of a positive rational number', () => { const actual = new Rational(1, 2).abs() assertRational(actual, 1, 2) }) xit('Absolute value of a positive rational number with negative numerator and denominator', () => { const actual = new Rational(-1, -2).abs() assertRational(actual, 1, 2) }) xit('Absolute value of a negative rational number', () => { const actual = new Rational(-1, 2).abs() assertRational(actual, 1, 2) }) xit('Absolute value of a negative rational number with negative denominator', () => { const actual = new Rational(1, -2).abs() assertRational(actual, 1, 2) }) xit('Absolute value of zero', () => { const actual = new Rational(0, 1).abs() assertRational(actual, 0, 1) }) xit('Absolute value of a rational number is reduced to lowest terms', () => { const actual = new Rational(2, 4).abs() assertRational(actual, 1, 2) }) }) describe('Exponentiation of a rational number', () => { xit('Raise a positive rational number to a positive integer power', () => { const actual = new Rational(1, 2).exprational(3) assertRational(actual, 1, 8) }) xit('Raise a negative rational number to a positive integer power', () => { const actual = new Rational(-1, 2).exprational(3) assertRational(actual, -1, 8) }) xit('Raise a positive rational number to a negative integer power', () => { const actual = new Rational(3, 5).exprational(-2) assertRational(actual, 25, 9) }) xit('Raise a negative rational number to an even negative integer power', () => { const actual = new Rational(-3, 5).exprational(-2) assertRational(actual, 25, 9) }) xit('Raise a negative rational number to an odd negative integer power', () => { const actual = new Rational(-3, 5).exprational(-3) assertRational(actual, -125, 27) }) xit('Raise zero to an integer power', () => { const actual = new Rational(0, 1).exprational(5) assertRational(actual, 0, 1) }) xit('Raise one to an integer power', () => { const actual = new Rational(1, 1).exprational(4) assertRational(actual, 1, 1) }) xit('Raise a positive rational number to the power of zero', () => { const actual = new Rational(1, 2).exprational(0) assertRational(actual, 1, 1) }) xit('Raise a negative rational number to the power of zero', () => { const actual = new Rational(-1, 2).exprational(0) assertRational(actual, 1, 1) }) }) describe('Exponentiation of a real number to a rational number', () => { xit('Raise a real number to a positive rational number', () => { const actual = new Rational(4, 3).expreal(8) expect(actual).toBeCloseTo(16.0, 10) }) xit('Raise a real number to a negative rational number', () => { const actual = new Rational(-1, 2).expreal(9) expect(actual).toBeCloseTo(1.0 / 3.0, 10) }) xit('Raise a real number to a zero rational number', () => { const actual = new Rational(0, 1).expreal(2) expect(actual).toBeCloseTo(1.0, 10) }) }) describe('Reduction to lowest terms', () => { xit('Reduce a positive rational number to lowest terms', () => { const actual = new Rational(2, 4).reduce() assertRational(actual, 1, 2) }) xit('Reduce places the minus sign on the numerator', () => { const actual = new Rational(3, -4).reduce() assertRational(actual, -3, 4) }) xit('Reduce a negative rational number to lowest terms', () => { const actual = new Rational(-4, 6).reduce() assertRational(actual, -2, 3) }) xit('Reduce a rational number with a negative denominator to lowest terms', () => { const actual = new Rational(3, -9).reduce() assertRational(actual, -1, 3) }) xit('Reduce zero to lowest terms', () => { const actual = new Rational(0, 6).reduce() assertRational(actual, 0, 1) }) xit('Reduce an integer to lowest terms', () => { const actual = new Rational(-14, 7).reduce() assertRational(actual, -2, 1) }) xit('Reduce one to lowest terms', () => { const actual = new Rational(13, 13).reduce() assertRational(actual, 1, 1) }) }) ================================================ FILE: exercises/practice/rational-numbers/rational-numbers.ts ================================================ export class Rational { constructor() { throw new Error('Remove this line and implement the function') } add() { throw new Error('Remove this line and implement the function') } sub() { throw new Error('Remove this line and implement the function') } mul() { throw new Error('Remove this line and implement the function') } div() { throw new Error('Remove this line and implement the function') } abs() { throw new Error('Remove this line and implement the function') } exprational() { throw new Error('Remove this line and implement the function') } expreal() { throw new Error('Remove this line and implement the function') } reduce() { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/rational-numbers/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/rational-numbers/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/react/.docs/instructions.md ================================================ # Instructions Implement a basic reactive system. Reactive programming is a programming paradigm that focuses on how values are computed in terms of each other to allow a change to one value to automatically propagate to other values, like in a spreadsheet. Implement a basic reactive system with cells with settable values ("input" cells) and cells with values computed in terms of other cells ("compute" cells). Implement updates so that when an input value is changed, values propagate to reach a new stable system state. In addition, compute cells should allow for registering change notification callbacks. Call a cell’s callbacks when the cell’s value in a new stable state has changed from the previous stable state. ================================================ FILE: exercises/practice/react/.meta/config.json ================================================ { "authors": [ "peerreynders" ], "files": { "solution": [ "react.ts" ], "test": [ "react.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Implement a basic reactive system.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false } } ================================================ FILE: exercises/practice/react/.meta/proof.ci.ts ================================================ // // Heavily inspired by signal.ts from [https://github.com/ryansolid/solid] // and by extension S.js [https://github.com/adamhaile/S] // // See: // https://indepth.dev/posts/1269/finding-fine-grained-reactive-programming#how-it-works // https://levelup.gitconnected.com/finding-fine-grained-reactive-programming-89741994ddee?source=friends_link&sk=31c66a70c1dce7dd5f3f4229423ad127#4543 // enum Status { OK, Pending, Stale, } /** * Type for the closure's value equality predicate. * * @typeParam T - Type of the values being compared for * equality. * * @remarks * Conceptually this function should be equivalent * to: `lhs === rhs` * * @param lhs - left hand side value * @param rhs - right hand side value * @returns - `true` if values are considered * equal; `false` otherwise. */ type EqualFn = (lhs: T, rhs: T) => boolean type GetterFn = () => T type SetterFn = (value: T) => T type UnsubscribeFn = () => void type UpdateFn = (value?: T) => T type InputPair = [GetterFn, SetterFn] type Options = { name: string } type ObserverR = { name?: string pure: boolean status: Status subjects: Set } type ObserverV = { value?: T updateFn: UpdateFn } type Observer = ObserverR & ObserverV type SubjectR = { name?: string observers: Set } type SubjectV = { value?: T equalFn?: EqualFn } type Subject = SubjectR & SubjectV type ComputedR = ObserverR & SubjectR type Computed = ComputedR & ObserverV & SubjectV function isObserverRComputedR(observer: ObserverR): observer is ComputedR { const computed = observer as ComputedR return computed.observers !== undefined } function isSubjectComputed(subject: Subject): subject is Computed { const computed = subject as Computed return computed.subjects !== undefined } function isObserverComputed(observer: Observer): observer is Computed { const computed = observer as Computed return computed.observers !== undefined } const defaultEqual = (lhs: T, rhs: T): boolean => lhs === rhs function selectEqualFn( equal: boolean | EqualFn | undefined ): EqualFn | undefined { if (typeof equal === 'function') return equal if (equal === true) return defaultEqual return undefined } // module context values let activeObserver: ObserverR let updateQueue: ObserverR[] | undefined let callbackQueue: ObserverR[] | undefined function link(subject: SubjectR, observer: ObserverR): void { observer.subjects.add(subject) subject.observers.add(observer) } function unsubscribe(observer: ObserverR): void { observer.subjects.forEach((sub) => { sub.observers.delete(observer) }) } function makeUnsubscribe(observer: ObserverR | undefined): UnsubscribeFn { return (): void => { if (!observer) return const o = observer observer = undefined unsubscribe(o) } } function prepareForUpdate(observer: ObserverR): void { if (isObserverRComputedR(observer) && observer.status !== Status.Pending) { markDeepObservers(observer) } observer.status = Status.Stale if (observer.pure) updateQueue!.push(observer) else callbackQueue!.push(observer) } function markDeep(observer: ObserverR): void { if (observer.status === Status.OK) { observer.status = Status.Pending if (isObserverRComputedR(observer)) markDeepObservers(observer) } } function markDeepObservers(subject: SubjectR): void { subject.observers.forEach(markDeep) } function runUpdates(prepareUpdates: () => void): void { if (updateQueue) return prepareUpdates() const updates: ObserverR[] = [] updateQueue = updates const [callbacks, delayCallbacks] = callbackQueue ? [callbackQueue, true] : [[], false] callbackQueue = callbacks prepareUpdates() updateQueued(updates) updateQueue = undefined if (delayCallbacks) return updateQueued(callbacks) callbackQueue = undefined } function updateQueued(queued: ObserverR[]): void { for (let i = 0; i < queued.length; i++) { updateViaStatus(queued[i] as Observer, true) } } function updateViaStatus( observer: Observer, saveQueue: boolean = false ): void { switch (observer.status) { case Status.Pending: { if (saveQueue !== true) { updateDeepStaleSubjects(observer) } else { const prevUpdate = updateQueue updateQueue = undefined updateDeepStaleSubjects(observer) updateQueue = prevUpdate } break } case Status.Stale: updateObserver(observer) break } } function updateDeep(subject: Subject): void { if (isSubjectComputed(subject)) updateViaStatus(subject) } function updateDeepStaleSubjects(observer: Observer): void { observer.subjects.forEach(updateDeep) } function updateObserver(observer: Observer): void { unsubscribe(observer) const prevObserver = activeObserver activeObserver = observer observer.status = Status.OK const nextValue = observer.updateFn(observer.value) if (isObserverComputed(observer)) writeSubject(observer, nextValue) else observer.value = nextValue activeObserver = prevObserver } function readSubject(subject: Subject): T { if (isSubjectComputed(subject) && subject.status !== Status.OK) { const updates = updateQueue updateQueue = undefined updateViaStatus(subject) updateQueue = updates } if (activeObserver) link(subject, activeObserver) return subject.value! } function writeSubject(subject: Subject, value: T): T { if (subject.equalFn && subject.value && subject.equalFn(subject.value, value)) return value subject.value = value if (subject.observers.size) { runUpdates(() => subject.observers.forEach(prepareForUpdate)) } return subject.value } /** * Creates an input closure. The value is accessed * via the accessor and changed via the * mutator returned as part an `InputPair`. * * @typeParam T - Type of the closure's value. * By extension the type of the return * value of the accessor and the type * of the mutator's single argument. * * @param value - Input closure's initial value. * @param equal - By default the current and previous * values are not compared so invoking * the mutator with identical values * will trigger updates on any * subscribers. When `true` is * specified the * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality | strict equality operator} * is used to compare values and * mutations with unchanging values * **are** suppressed. * When `T` is a structural type * it is necessary to provide a * `(a: T, b: T) => boolean` comparison * predicate instead. * @param options - Holder object for relevant options. * Assigning a `name` to a subject can * be useful during debugging. * @returns - An `InputPair`. The 1st * element is the accessor (getter * function), the 2nd element is * the mutator (setter function). */ function createInput( value: T, equal?: boolean | EqualFn, options?: Options ): InputPair { const subject: Subject = { name: options?.name, observers: new Set(), value, equalFn: selectEqualFn(equal), } return [ (): T => readSubject(subject), (next: T): T => writeSubject(subject, next), ] } /** * Creates a computed (derived) closure with the * supplied function which computes the current value * of the closure. * * @typeParam T - Type of the closure's value. * By extension the type of the value * returned by the update function and * of the value * accepted by the function. * * @param updateFn - Update function. This function * references one or more accessors of * other subjects. It **should not** * perform side effects. It is expected * to return a value which will be the * value of the closure until the next * update. The closure's value is * supplied to this update function * on the next update. * @param value - Initial value that is passed to * `updateFn` when it executes for the * first time. * @param equal - By default the current and previous * values are not compared so updates * will be triggered even if the value * doesn't _change_. When `true` is * specified the * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality | strict equality operator} * is used to compare values and updates * with identical values **are** * suppressed. When `T` is a structural * type it is necessary to provide a * `(a: T, b: T) => boolean` comparison * predicate instead. * @param options - Holder object for relevant options. * Assigning a `name` to a subject can * be useful during debugging. * @returns - The accessor to the closure's * value (getter function). Retrieves * the closure's current value. Used by * observers (or more accurately their * update function) to obtain the * value (and to subscribe for * updates). */ function createComputed( updateFn: UpdateFn, value?: T, equal?: boolean | EqualFn, options?: Options ): GetterFn { const computed: Computed = { name: options?.name, observers: new Set(), value, equalFn: selectEqualFn(equal), updateFn, status: Status.Stale, pure: true, subjects: new Set(), } updateObserver(computed) return (): T => readSubject(computed) } /** * Creates a callback closure with the supplied * function which is expected to perform side effects. * * @typeParam T - Type of the closure's value. * By extension the type of the value * returned by the callback function * and of the value accepted by the * function. * * @param updateFn - Callback function. This function * references one or more accessors of * subjects. It may perform side effects. * It will also be passed the * value that it returned the last time it * was invoked. * @param value - Initial value that is passed to * `updateFn` when it executes for * the first time. * @returns - The `unsubscribe` function. Once * invoked the callback closure will * stop receiving updates from the * subjects it subscribed to. */ function createCallback(updateFn: UpdateFn, value?: T): UnsubscribeFn { const o: Observer = { value, updateFn, status: Status.Stale, pure: false, subjects: new Set(), } if (callbackQueue) callbackQueue.push(o) else updateObserver(o) return makeUnsubscribe(o) } export { createInput, createComputed, createCallback } // ------------------------------------------------------- // // A `Subject` provides its output value **to** // its dependents (`Observer`s). // // An `Observer` gets its input values **from** // its dependencies (`Subject`s). // // A `Computed` merges the _aspects_ of // both `Observer` and `Subject`. // Of the three types `Computed` is the most _general_. // So while `Computed` is composed of both // `Subject` and `Observer`, both // `Subject`, `Observer` could be viewed as // _constrained_ versions of `Computed` (as // opposed to _specialized_ versions). // // Each type is further split into its _value_ aspect // (e.g. `ObserverV`) and _relation_ (or _rest_) // aspect (e.g. `ObserverR`). This tactic helps to // avoid using generic type references with an // explicit _any_ type parameter in the update // routing logic which has no actual dependency // on the type `T` being managed by the // `Subject`/ `Observer`/`Computed` // instance. // // The existence of the value aspect is only // acknowledged in `updateQueued()` where the // `ObserverR` type is asserted to be an // `Observer` which from this point on is // then handled by generic functions. // // So `createInput()` internally creates a // `Subject`, `createCallback()` an `Observer`, and // `createComputed()` a `Computed`. // // `createInput()` returns two functions in an // `InputCouple` tuple, a `GetterFn` accessor // and a `SetterFn` mutator. The mutator triggers // the update of all the `Subject`'s dependents // while the accessor returns the `Subject`'s // current value. // // The accessor also has the hidden responsibility // of subscribing the `Observer` that is accessing // the `Subject`. The `Observer` shares its // reference via the module's `activeObserver` context // value. The `Subject` stores that dependency in // its `observers` property. // // `createComputed()` only returns a `GetterFn` // accessor to obtain the internal `Computed`'s // current value. The primary argument to // `createComputed()` is `fn: UpdateFn` - the // function responsible for deriving the // `Computed`'s value from its dependencies // (and the `Computed`'s own previous value). This // function is invoked whenever at least one of the // `Computed`'s dependencies has an updated value. // The function is run before `createComputed()` // exits via `updateObserver()` so that // * the `Computed` can calculate its current value // * the `Computed` subscribes to all its // relevant dependencies. // // To that end `updateObserver()` sets the // module's `activeObserver` context value before // invoking `fn: UpdateFn` so that all the // `Subject`'s being accessed can register the // subscription of the `Observer` (or `Computed`). // // (Note that `updateObserver()` unsubscribes the // `Observer` (or `Computed`) first - this is // essential later so that the `Observer` isn't // updated for irrelevant dependencies - e.g. an // input accessor that is guarded by an `if` condition // that currently evaluates to `false`.) // // `createCallback()` returns an `UnsubscribeFn` // function that deactivates the callback when invoked. // There is no `GetterFn` because the callbacks // act by _side effect_ - i.e. changing values that // exist in their enclosing scope (closure: // https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/scope-closures/ch7.md // ). Internally a callback is based on a `Observer`. // The primary argument to // `createCallback()` is `fn: UpdateFn` - the // function is responsible for accessing the `Subject` // dependencies (and thereby subscribing to them) // and implementing the side effect(s). This // function is called whenever at least one of the // `Observer`'s dependencies has an updated value. // `updateObserver()` is only invoked if currently no // update is in progress - otherwise the callback is // queued for later invocation. // // All three `create*` functions take a `value: T` // argument - required for `createInput()`, // optional for the others. For `createInput()` // this is the initial value - for the others it is the // argument that is passed to the `fn: UpdateFn` // the first time it executes. // // `createInput()` and `createComputed()` have // an (optional) ` equal?: boolean | EqualFn` // argument. Given the generic implementation of the // capabilities, by default `Subject`s don't // limit updates to occasions when their `value` // _changes_ - as there is no standardized way // to check for equality of `T` in TypeScript // (compared to for example Rust): // https://doc.rust-lang.org/std/cmp/trait.PartialEq.html // https://doc.rust-lang.org/std/cmp/trait.Eq.html // // A value of `true` directs the use of the default // equality function `defaultEqual` which will work // for primitive types and references. Otherwise a // custom equality predicate must be provided // if updates for identical values of `T` are to // be suppressed. // // `createCallback` doesn't have this argument. // Therefore it is necessary to configure the // `Subject`s it depends on to suppress updates // on identical values. // // `createInput()` and `createComputed()` have // an optional `Options` value that may carry a // `name` property. This can come in handy for // debugging. // // The core capability is driven by the // `writeSubject` function which is invoked whenever // an input is set with its `SetterFn` or a // `Computed` has its `value` set by a dependency. // The update of dependents is suppressed for identical // values provided the `Subject` is configured // accordingly. Otherwise all the observers are // prepared for update before the actual updates are // made. // // `prepareForUpdate()` first marks the dependents of a // `Computed` as `Status.Pending` _in depth_. The // observer itself is marked as `Status.Stale` before // being pushed onto `updateQueue` or `callbackQueue`. // // * `Status.Pending` - an `Observer` has this // status when one of its dependencies _descendents_ // is marked as `Status.Stale`. It acts as a // pre-`Status.Stale` status. The `Observer` // instance is "out-of-sync" but not yet ready // for update (so it isn't on `updateQueue`). // * `Status.Stale` - an `Observer` enters this // state when at least one of its direct // dependencies has been updated. The `Observer` // instance is on `updateQueue`. // // `runUpdates` coordinates the current update wave. // The passed `prepareUpdate` function is invoked if an // update wave is already underway. Otherwise an empty // `updateQueue` is set up. If callbacks are already // being queued this particular `runUpdates` invocation // won't be processing callbacks // (`delayCallbacks = true`) - otherwise an empty // `callbackQueue` is set up. // // Finally the passed `prepareUpdates` function is // invoked causing "computeds" to be queued up on // `updateQueue` and "callbacks" to be queued up on // `callbackQueue`. The `updateQueue` is processed // with the `updateQueued()` function. // // Callbacks are only processed if this is the // top-level invocation of `runUpdates()` - this // ensures that callbacks are updated as late as // possible to avoid unnecessary, multiple // updates. // // Updates routed through `updateViaStatus` by // `updateQueued` start a nested (deep) update wave // for an `Observer` in the `Status.Pending` // status - i.e. there are dependencies in need of // update before the `Observer` can perform // a meaningful update of itself. `Observer`'s // in the `Status.Stale` status can immediately update // themselves via their `fn: UpdateFn`. // // `readSubject()` will issue a nested update wave // if it discovers that its `Subject` is currently // **not** in `Status.OK` status as it needs those // updates to complete before an up-to-date `value` // can be returned. // // References: // // Enums // https://www.typescriptlang.org/docs/handbook/enums.html // // Type Aliases // https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-aliases // // Function Types // https://www.typescriptlang.org/docs/handbook/functions.html#function-types // // Generics // https://www.typescriptlang.org/docs/handbook/generics.html // // Optional and Default Types // https://www.typescriptlang.org/docs/handbook/functions.html#optional-and-default-parameters // // Tuple // https://www.typescriptlang.org/docs/handbook/basic-types.html#tuple // // Optional Properties // https://www.typescriptlang.org/docs/handbook/interfaces.html#optional-properties // // Set // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set // // Intersection Types // https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#intersection-types // // User-Defined Type Guards // https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards // // Type Assertions // https://www.typescriptlang.org/docs/handbook/basic-types.html#type-assertions // // Union Types // https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#union-types // // typeof // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof // // Unknown // https://www.typescriptlang.org/docs/handbook/basic-types.html#unknown // // Non-null assertion operator // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator // // Optional chaining (?.) // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining // // ------------------------------------------------------- ================================================ FILE: exercises/practice/react/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [c51ee736-d001-4f30-88d1-0c8e8b43cd07] description = "input cells have a value" [dedf0fe0-da0c-4d5d-a582-ffaf5f4d0851] description = "an input cell's value can be set" [5854b975-f545-4f93-8968-cc324cde746e] description = "compute cells calculate initial value" [25795a3d-b86c-4e91-abe7-1c340e71560c] description = "compute cells take inputs in the right order" [c62689bf-7be5-41bb-b9f8-65178ef3e8ba] description = "compute cells update value when dependencies are changed" [5ff36b09-0a88-48d4-b7f8-69dcf3feea40] description = "compute cells can depend on other compute cells" [abe33eaf-68ad-42a5-b728-05519ca88d2d] description = "compute cells fire callbacks" [9e5cb3a4-78e5-4290-80f8-a78612c52db2] description = "callback cells only fire on change" [ada17cb6-7332-448a-b934-e3d7495c13d3] description = "callbacks do not report already reported values" [ac271900-ea5c-461c-9add-eeebcb8c03e5] description = "callbacks can fire from multiple cells" [95a82dcc-8280-4de3-a4cd-4f19a84e3d6f] description = "callbacks can be added and removed" [f2a7b445-f783-4e0e-8393-469ab4915f2a] description = "removing a callback multiple times doesn't interfere with other callbacks" [daf6feca-09e0-4ce5-801d-770ddfe1c268] description = "callbacks should only be called once even if multiple dependencies change" [9a5b159f-b7aa-4729-807e-f1c38a46d377] description = "callbacks should not be called if dependencies change but output value doesn't change" ================================================ FILE: exercises/practice/react/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/react/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/react/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/react/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/react/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/react/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/react/package.json ================================================ { "name": "@exercism/typescript-react", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/react/react.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { createInput, createComputed, createCallback } from './react.ts' describe('React module', () => { // c51ee736-d001-4f30-88d1-0c8e8b43cd07 xit('input cells have a value', () => { const initialValue = 10 const [input, _setInput] = createInput(initialValue) expect(input()).toEqual(initialValue) }) // dedf0fe0-da0c-4d5d-a582-ffaf5f4d0851 xit("an input cell's value can be set", () => { const newValue = 20 const [input, setInput] = createInput(4) setInput(newValue) expect(input()).toEqual(newValue) }) // 5854b975-f545-4f93-8968-cc324cde746e xit('compute cells calculate initial value', () => { const [input] = createInput(1) const output = createComputed(() => input() + 1) expect(output()).toEqual(2) }) // 25795a3d-b86c-4e91-abe7-1c340e71560c xit('compute cell takes inputs in correct order', () => { const [[one], [two]] = [createInput(1), createInput(2)] const output = createComputed(() => one() + two() * 10) expect(output()).toEqual(21) }) // c62689bf-7be5-41bb-b9f8-65178ef3e8ba it('compute cells update value when inputs are changed', () => { const [input, setInput] = createInput(1) const output = createComputed(() => input() + 1) setInput(3) expect(output()).toEqual(4) }) // 5ff36b09-0a88-48d4-b7f8-69dcf3feea40 xit('compute cells can depend on other compute cells', () => { const [input, setInput] = createInput(1) const timesTwo = createComputed(() => input() * 2) const timesThirty = createComputed(() => input() * 30) const sum = createComputed(() => timesTwo() + timesThirty()) expect(sum()).toEqual(32) setInput(3) expect(sum()).toEqual(96) }) // abe33eaf-68ad-42a5-b728-05519ca88d2d xit('compute cells fire callbacks', () => { const [input, setInput] = createInput(1) const output = createComputed(() => input() + 1) let value = 0 createCallback(() => (value = output())) setInput(3) expect(value).toEqual(4) }) // 9e5cb3a4-78e5-4290-80f8-a78612c52db2 xit('callbacks fire only when output values change', () => { const [input, setInput] = createInput(1) const output = createComputed( () => (input() < 3 ? 111 : 222), undefined, true // i.e. equality check - don't propagate if value doesn't change ) let value: number | undefined createCallback(() => (value = output())) value = undefined // discard initial value from registration setInput(2) expect(value).toBeUndefined() setInput(4) expect(value).toEqual(222) }) // ada17cb6-7332-448a-b934-e3d7495c13d xit('callbacks do not report already reported values', () => { const [input, setInput] = createInput(1) const output = createComputed(() => input() + 1) let value: number | undefined createCallback(() => (value = output())) setInput(2) expect(value).toEqual(3) setInput(3) expect(value).toEqual(4) }) // ac271900-ea5c-461c-9add-eeebcb8c03e5 xit('callbacks can fire from multiple cells', () => { const [input, setInput] = createInput(1) const plus_one = createComputed(() => input() + 1) const minus_one = createComputed(() => input() - 1) let value1 = 0 createCallback(() => (value1 = plus_one())) let value2 = 0 createCallback(() => (value2 = minus_one())) setInput(10) expect(value1).toEqual(11) expect(value2).toEqual(9) }) // From JavaScript track xit('static callbacks fire even if their own value has not changed', () => { const [input, setInput] = createInput(1) const output = createComputed( () => (input() < 3 ? 111 : 222), undefined, true // i.e. equality check - don't propagate if value doesn't change ) const values: string[] = [] createCallback(() => { const _dontCare = output() values.push('cell changed') }) values.pop() // discard initial value from registration setInput(2) expect(values).toEqual([]) setInput(4) setInput(2) setInput(4) expect(values).toEqual(['cell changed', 'cell changed', 'cell changed']) }) // 95a82dcc-8280-4de3-a4cd-4f19a84e3d6f xit('callbacks can be added and removed', () => { const [input, setInput] = createInput(11) const output = createComputed(() => input() + 1) const values1: number[] = [] const unsubscribe1 = createCallback(() => values1.push(output())) values1.pop() // discard initial value from registration const values2: number[] = [] createCallback(() => values2.push(output())) values2.pop() // discard initial value ... setInput(31) unsubscribe1() const values3: number[] = [] createCallback(() => values3.push(output())) values3.pop() // discard initial value ... setInput(41) expect(values1).toEqual([32]) expect(values2).toEqual([32, 42]) expect(values3).toEqual([42]) }) // f2a7b445-f783-4e0e-8393-469ab4915f2a xit("removing a callback multiple times doesn't interfere with other callbacks", () => { const [input, setInput] = createInput(1) const output = createComputed(() => input() + 1) const values1: number[] = [] const unsubscribe1 = createCallback(() => values1.push(output())) values1.pop() // discard initial value from registration const values2: number[] = [] createCallback(() => values2.push(output())) values2.pop() // discard initial value ... unsubscribe1() unsubscribe1() unsubscribe1() setInput(2) expect(values1).toEqual([]) expect(values2).toEqual([3]) }) // daf6feca-09e0-4ce5-801d-770ddfe1c268 xit('callbacks should only be called once, even if multiple dependencies change', () => { const [input, setInput] = createInput(1) const plusOne = createComputed(() => input() + 1) const minusOne1 = createComputed(() => input() - 1) const minusOne2 = createComputed(() => minusOne1() - 1) const output = createComputed(() => plusOne() * minusOne2()) const values: number[] = [] createCallback(() => values.push(output())) values.pop() // discard initial value from registration setInput(4) expect(values).toEqual([10]) }) // 9a5b159f-b7aa-4729-807e-f1c38a46d377 xit("callbacks should not be called if dependencies change but output value doesn't change", () => { const [input, setInput] = createInput(1) const plusOne = createComputed(() => input() + 1) const minusOne = createComputed(() => input() - 1) const alwaysTwo = createComputed( () => plusOne() - minusOne(), undefined, true // i.e. equality check - don't propagate if value doesn't change ) const values: number[] = [] createCallback(() => values.push(alwaysTwo())) values.pop() // discard initial value from registration setInput(2) setInput(3) setInput(4) setInput(5) expect(values).toEqual([]) }) }) ================================================ FILE: exercises/practice/react/react.ts ================================================ // // Delete and replace stub with your own implementation // // Inspired by "How it Works": // https://indepth.dev/posts/1269/finding-fine-grained-reactive-programming#how-it-works // https://levelup.gitconnected.com/finding-fine-grained-reactive-programming-89741994ddee?source=friends_link&sk=31c66a70c1dce7dd5f3f4229423ad127#4543 // // and "Computations": // https://github.com/ryansolid/solid/blob/master/documentation/reactivity.md#user-content-computations // /** * Type for the closure's value equality predicate. * * @typeParam T - Type of the values being compared for * equality. * * @remarks * Conceptually this function should be equivalent * to: `lhs === rhs` * * @param lhs - left hand side value * @param rhs - right hand side value * @returns - `true` if values are considered * equal; `false` otherwise. */ type EqualFn = (lhs: T, rhs: T) => boolean type GetterFn = () => T type SetterFn = (value: T) => T type UnsubscribeFn = () => void type UpdateFn = (value?: T) => T type InputPair = [GetterFn, SetterFn] type Options = { name: string // for debugging } type ObserverR = { name?: string } type ObserverV = { value?: T updateFn: UpdateFn } type Observer = ObserverR & ObserverV type SubjectR = { name?: string observer: ObserverR | undefined } type SubjectV = { value: T equalFn?: EqualFn } type Subject = SubjectR & SubjectV // module Context value let activeObserver: ObserverR function updateObserver(observer: Observer): void { const prevObserver = activeObserver activeObserver = observer observer.value = observer.updateFn(observer.value) activeObserver = prevObserver } /** * Creates an input closure. The value is accessed * via the accessor and changed via the * mutator returned as part an `InputPair`. * * @typeParam T - Type of the closure's value. * By extension the type of the return * value of the accessor and the type * of the mutator's single argument. * * @param value - Input closure's initial value. * @param equal - By default the current and previous * values are not compared so invoking * the mutator with identical values * will trigger updates on any * subscribers. When `true` is * specified the * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality | strict equality operator} * is used to compare values and * mutations with unchanging values * **are** suppressed. * When `T` is a structural type * it is necessary to provide a * `(a: T, b: T) => boolean` comparison * predicate instead. * @param options - Holder object for relevant options. * Assigning a `name` to a subject can * be useful during debugging. * @returns - An `InputPair`. The 1st * element is the accessor (getter * function), the 2nd element is * the mutator (setter function). */ function createInput( value: T, _equal?: boolean | EqualFn, options?: Options ): InputPair { const s: Subject = { name: options?.name, observer: undefined, value, equalFn: undefined, } const read: GetterFn = () => { if (activeObserver) s.observer = activeObserver return s.value } const write: SetterFn = (nextValue) => { s.value = nextValue if (s.observer) updateObserver(s.observer as Observer) return s.value } return [read, write] } /** * Creates a computed (derived) closure with the * supplied function which computes the current value * of the closure. * * @privateRemarks * `Observer` may be good enough to get through * the enabled test case but more is needed to * get further ... * * @typeParam T - Type of the closure's value. * By extension the type of the value * returned by the update function and * of the value * accepted by the function. * * @param updateFn - Update function. This function * references one or more accessors of * other subjects. It **should not** * perform side effects. It is expected * to return a value which will be the * value of the closure until the next * update. The closure's value is * supplied to this update function * on the next update. * @param value - Initial value that is passed to * `updateFn` when it executes for the * first time. * @param equal - By default the current and previous * values are not compared so updates * will be triggered even if the value * doesn't _change_. When `true` is * specified the * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality | strict equality operator} * is used to compare values and updates * with identical values **are** * suppressed. When `T` is a structural * type it is necessary to provide a * `(a: T, b: T) => boolean` comparison * predicate instead. * @param options - Holder object for relevant options. * Assigning a `name` to a subject can * be useful during debugging. * @returns - The accessor to the closure's * value (getter function). Retrieves * the closure's current value. Used by * observers (or more accurately their * update function) to obtain the * value (and to subscribe for * updates). */ function createComputed( updateFn: UpdateFn, value?: T, _equal?: boolean | EqualFn, options?: { name?: string } ): GetterFn { const o: Observer = { name: options?.name, value, updateFn, } updateObserver(o) return (): T => o.value! } /** * Creates a callback closure with the supplied * function which is expected to perform side effects. * * @privateRemarks * `observer` isn't mean't to be an empty object literal. * Replace it with something more appropriate to its * purpose. * * @typeParam T - Type of the closure's value. * By extension the type of the value * returned by the callback function * and of the value accepted by the * function. * * @param updateFn - Callback function. This function * references one or more accessors of * subjects. It may perform side effects. * It will also be passed the * value that it returned the last time it * was invoked. * @param value - Initial value that is passed to * `updateFn` when it executes for * the first time. * @returns - The `unsubscribe` function. Once * invoked the callback closure will * stop receiving updates from the * subjects it subscribed to. */ function createCallback(_updateFn: UpdateFn, _value?: T): UnsubscribeFn { const observer = {} return ((observer: unknown | undefined) => (): void => { if (!observer) return observer = undefined // i.e. dispose of any active subscriptions })(observer) } export { createInput, createComputed, createCallback } ================================================ FILE: exercises/practice/react/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/react/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/rectangles/.docs/instructions.md ================================================ # Instructions Count the rectangles in an ASCII diagram like the one below. ```text +--+ ++ | +-++--+ | | | +--+--+ ``` The above diagram contains these 6 rectangles: ```text +-----+ | | +-----+ ``` ```text +--+ | | | | | | +--+ ``` ```text +--+ | | +--+ ``` ```text +--+ | | +--+ ``` ```text +--+ | | +--+ ``` ```text ++ ++ ``` You may assume that the input is always a proper rectangle (i.e. the length of every line equals the length of the first line). ================================================ FILE: exercises/practice/rectangles/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "rectangles.ts" ], "test": [ "rectangles.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Count the rectangles in an ASCII diagram.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false } } ================================================ FILE: exercises/practice/rectangles/.meta/proof.ci.ts ================================================ export function count(diagram: string[]): number { const rows = diagram.length const cols = rows ? diagram[0].length : 0 let rectangles = 0 // All possible topleft corners for (let y = 0; y < rows - 1; y++) { for (let x = 0; x < cols - 1; x++) { if (diagram[y].charAt(x) === '+') { // All possible bottomright corners for (let j = y + 1; j < rows; j++) { for (let i = x + 1; i < cols; i++) { // Check if all corners are valid if ( diagram[j].charAt(i) === '+' && diagram[y].charAt(i) === '+' && diagram[j].charAt(x) === '+' ) { let validRectangle = true // Check if all sides are valid for (let s = x + 1; s < i; s++) { if (!'+-'.includes(diagram[y].charAt(s))) { validRectangle = false } } for (let s = x + 1; s < i; s++) { if (!'+-'.includes(diagram[j].charAt(s))) { validRectangle = false } } for (let t = y + 1; t < j; t++) { if (!'+|'.includes(diagram[t].charAt(x))) { validRectangle = false } } for (let t = y + 1; t < j; t++) { if (!'+|'.includes(diagram[t].charAt(i))) { validRectangle = false } } if (validRectangle) { rectangles++ } } } } } } } return rectangles } ================================================ FILE: exercises/practice/rectangles/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [485b7bab-4150-40aa-a8db-73013427d08c] description = "no rows" [076929ed-27e8-45dc-b14b-08279944dc49] description = "no columns" [0a8abbd1-a0a4-4180-aa4e-65c1b1a073fa] description = "no rectangles" [a4ba42e9-4e7f-4973-b7c7-4ce0760ac6cd] description = "one rectangle" [ced06550-83da-4d23-98b7-d24152e0db93] description = "two rectangles without shared parts" [5942d69a-a07c-41c8-8b93-2d13877c706a] description = "five rectangles with shared parts" [82d70be4-ab37-4bf2-a433-e33778d3bbf1] description = "rectangle of height 1 is counted" [57f1bc0e-2782-401e-ab12-7c01d8bfc2e0] description = "rectangle of width 1 is counted" [ef0bb65c-bd80-4561-9535-efc4067054f9] description = "1x1 square is counted" [e1e1d444-e926-4d30-9bf3-7d8ec9a9e330] description = "only complete rectangles are counted" [ca021a84-1281-4a56-9b9b-af14113933a4] description = "rectangles can be of different sizes" [51f689a7-ef3f-41ae-aa2f-5ea09ad897ff] description = "corner is required for a rectangle to be complete" [d78fe379-8c1b-4d3c-bdf7-29bfb6f6dc66] description = "large input with many rectangles" [6ef24e0f-d191-46da-b929-4faca24b4cd2] description = "rectangles must have four sides" ================================================ FILE: exercises/practice/rectangles/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/rectangles/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/rectangles/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/rectangles/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/rectangles/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/rectangles/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/rectangles/package.json ================================================ { "name": "@exercism/typescript-rectangles", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/rectangles/rectangles.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { count } from './rectangles.ts' describe('Rectangles', () => { it('no rows', () => { const expected = 0 const actual = count([]) expect(actual).toEqual(expected) }) xit('no columns', () => { const expected = 0 const actual = count(['']) expect(actual).toEqual(expected) }) xit('no rectangles', () => { const expected = 0 const actual = count([' ']) expect(actual).toEqual(expected) }) xit('one rectangle', () => { const expected = 1 const actual = count(['+-+', '| |', '+-+']) expect(actual).toEqual(expected) }) xit('two rectangles without shared parts', () => { const expected = 2 const actual = count([' +-+', ' | |', '+-+-+', '| | ', '+-+ ']) expect(actual).toEqual(expected) }) xit('five rectangles with shared parts', () => { const expected = 5 const actual = count([' +-+', ' | |', '+-+-+', '| | |', '+-+-+']) expect(actual).toEqual(expected) }) xit('rectangle of height 1 is counted', () => { const expected = 1 const actual = count(['+--+', '+--+']) expect(actual).toEqual(expected) }) xit('rectangle of width 1 is counted', () => { const expected = 1 const actual = count(['++', '||', '++']) expect(actual).toEqual(expected) }) xit('1x1 square is counted', () => { const expected = 1 const actual = count(['++', '++']) expect(actual).toEqual(expected) }) xit('only complete rectangles are counted', () => { const expected = 1 const actual = count([' +-+', ' |', '+-+-+', '| | -', '+-+-+']) expect(actual).toEqual(expected) }) xit('rectangles can be of different sizes', () => { const expected = 3 const actual = count([ '+------+----+', '| | |', '+---+--+ |', '| | |', '+---+-------+', ]) expect(actual).toEqual(expected) }) xit('corner is required for a rectangle to be complete', () => { const expected = 2 const actual = count([ '+------+----+', '| | |', '+------+ |', '| | |', '+---+-------+', ]) expect(actual).toEqual(expected) }) xit('large input with many rectangles', () => { const expected = 60 const actual = count([ '+---+--+----+', '| +--+----+', '+---+--+ |', '| +--+----+', '+---+--+--+-+', '+---+--+--+-+', '+------+ | |', ' +-+', ]) expect(actual).toEqual(expected) }) xit('rectangles must have four sides', () => { const expected = 5 const actual = count([ '+-+ +-+', '| | | |', '+-+-+-+', ' | | ', '+-+-+-+', '| | | |', '+-+ +-+', ]) expect(actual).toEqual(expected) }) }) ================================================ FILE: exercises/practice/rectangles/rectangles.ts ================================================ export function count() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/rectangles/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/rectangles/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/relative-distance/.docs/instructions.md ================================================ # Instructions Your task is to determine the degree of separation between two individuals in a family tree. This is similar to the pop culture idea that every Hollywood actor is [within six degrees of Kevin Bacon][six-bacons]. - You will be given an input, with all parent names and their children. - Each name is unique, a child _can_ have one or two parents. - The degree of separation is defined as the shortest number of connections from one person to another. - If two individuals are not connected, return a value that represents "no known relationship." Please see the test cases for the actual implementation. ## Example Given the following family tree: ```text ┌──────────┐ ┌──────────┐ ┌───────────┐ │ Helena │ │ Erdős ├─────┤ Shusaku │ └───┬───┬──┘ └─────┬────┘ └────┬──────┘ ┌───┘ └───────┐ └───────┬───────┘ ┌─────┴────┐ ┌────┴───┐ ┌─────┴────┐ │ Isla ├─────┤ Tariq │ │ Kevin │ └────┬─────┘ └────┬───┘ └──────────┘ │ │ ┌────┴────┐ ┌────┴───┐ │ Uma │ │ Morphy │ └─────────┘ └────────┘ ``` The degree of separation between Tariq and Uma is 2 (Tariq → Isla → Uma). There's no known relationship between Isla and Kevin, as there is no connection in the given data. The degree of separation between Uma and Isla is 1. ~~~~exercism/note Isla and Tariq are siblings and have a separation of 1. Similarly, this implementation would report a separation of 2 from you to your father's brother. ~~~~ [six-bacons]: https://en.wikipedia.org/wiki/Six_Degrees_of_Kevin_Bacon ================================================ FILE: exercises/practice/relative-distance/.docs/introduction.md ================================================ # Introduction You've been hired to develop **Noble Knots**, the hottest new dating app for nobility! With centuries of royal intermarriage, things have gotten… _complicated_. To avoid any _oops-we're-twins_ situations, your job is to build a system that checks how closely two people are related. Noble Knots is inspired by Iceland's "[Islendinga-App][islendiga-app]," which is backed up by a database that traces all known family connections between Icelanders from the time of the settlement of Iceland. Your algorithm will determine the **degree of separation** between two individuals in the royal family tree. Will your app help crown a perfect match? [islendiga-app]: https://web.archive.org/web/20250816223614/http://www.islendingaapp.is/information-in-english/ ================================================ FILE: exercises/practice/relative-distance/.meta/config.json ================================================ { "authors": [ "BNAndras" ], "files": { "solution": [ "relative-distance.ts" ], "test": [ "relative-distance.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a family tree, calculate the degree of separation.", "source": "vaeng", "source_url": "https://github.com/exercism/problem-specifications/pull/2537", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false } } ================================================ FILE: exercises/practice/relative-distance/.meta/proof.ci.ts ================================================ export const degreesOfSeparation = ( familyTree: Record, personA: string, personB: string ) => { const neighbors = new Map>() // Build adjacency list for (const [parent, children] of Object.entries(familyTree)) { const parentNeighbors = neighbors.get(parent) ?? new Set() neighbors.set(parent, parentNeighbors) for (const child of children) { const childNeighbors = neighbors.get(child) ?? new Set() neighbors.set(child, childNeighbors) parentNeighbors.add(child) childNeighbors.add(parent) // Connect siblings for (const sibling of children) { if (child !== sibling) { childNeighbors.add(sibling) } } } } if (!neighbors.has(personA) || !neighbors.has(personB)) return -1 // BFS setup const queue: [string, number][] = [[personA, 0]] const visited = new Set([personA]) while (queue.length > 0) { const [current, degree] = queue.shift()! if (current === personB) return degree for (const neighbor of neighbors.get(current)!) { if (!visited.has(neighbor)) { visited.add(neighbor) queue.push([neighbor, degree + 1]) } } } return -1 } ================================================ FILE: exercises/practice/relative-distance/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [4a1ded74-5d32-47fb-8ae5-321f51d06b5b] description = "Direct parent-child relation" [30d17269-83e9-4f82-a0d7-8ef9656d8dce] description = "Sibling relationship" [8dffa27d-a8ab-496d-80b3-2f21c77648b5] description = "Two degrees of separation, grandchild" [34e56ec1-d528-4a42-908e-020a4606ee60] description = "Unrelated individuals" [93ffe989-bad2-48c4-878f-3acb1ce2611b] description = "Complex graph, cousins" [2cc2e76b-013a-433c-9486-1dbe29bf06e5] description = "Complex graph, no shortcut, far removed nephew" [46c9fbcb-e464-455f-a718-049ea3c7400a] description = "Complex graph, some shortcuts, cross-down and cross-up, cousins several times removed, with unrelated family tree" ================================================ FILE: exercises/practice/relative-distance/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/relative-distance/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/relative-distance/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/relative-distance/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/relative-distance/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/relative-distance/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/relative-distance/package.json ================================================ { "name": "@exercism/typescript-relative-distance", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/relative-distance/relative-distance.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { degreesOfSeparation } from './relative-distance.ts' describe('RelativeDistance', () => { it('Direct parent-child relation', () => { const familyTree = { Vera: ['Tomoko'], Tomoko: ['Aditi'], } const actual = degreesOfSeparation(familyTree, 'Vera', 'Tomoko') expect(actual).toEqual(1) }) xit('Sibling relationship', () => { const familyTree = { Dalia: ['Olga', 'Yassin'], } const actual = degreesOfSeparation(familyTree, 'Olga', 'Yassin') expect(actual).toEqual(1) }) xit('Two degrees of separation, grandchild', () => { const familyTree = { Khadija: ['Mateo'], Mateo: ['Rami'], } const actual = degreesOfSeparation(familyTree, 'Khadija', 'Rami') expect(actual).toEqual(2) }) xit('Unrelated individuals', () => { const familyTree = { Priya: ['Rami'], Kaito: ['Elif'], } const actual = degreesOfSeparation(familyTree, 'Priya', 'Kaito') expect(actual).toEqual(-1) }) xit('Complex graph, cousins', () => { const familyTree = { Aiko: ['Bao', 'Carlos'], Bao: ['Dalia', 'Elias'], Carlos: ['Fatima', 'Gustavo'], Dalia: ['Hassan', 'Isla'], Elias: ['Javier'], Fatima: ['Khadija', 'Liam'], Gustavo: ['Mina'], Hassan: ['Noah', 'Olga'], Isla: ['Pedro'], Javier: ['Quynh', 'Ravi'], Khadija: ['Sofia'], Liam: ['Tariq', 'Uma'], Mina: ['Viktor', 'Wang'], Noah: ['Xiomara'], Olga: ['Yuki'], Pedro: ['Zane', 'Aditi'], Quynh: ['Boris'], Ravi: ['Celine'], Sofia: ['Diego', 'Elif'], Tariq: ['Farah'], Uma: ['Giorgio'], Viktor: ['Hana', 'Ian'], Wang: ['Jing'], Xiomara: ['Kaito'], Yuki: ['Leila'], Zane: ['Mateo'], Aditi: ['Nia'], Boris: ['Oscar'], Celine: ['Priya'], Diego: ['Qi'], Elif: ['Rami'], Farah: ['Sven'], Giorgio: ['Tomoko'], Hana: ['Umar'], Ian: ['Vera'], Jing: ['Wyatt'], Kaito: ['Xia'], Leila: ['Yassin'], Mateo: ['Zara'], Nia: ['Antonio'], Oscar: ['Bianca'], Priya: ['Cai'], Qi: ['Dimitri'], Rami: ['Ewa'], Sven: ['Fabio'], Tomoko: ['Gabriela'], Umar: ['Helena'], Vera: ['Igor'], Wyatt: ['Jun'], Xia: ['Kim'], Yassin: ['Lucia'], Zara: ['Mohammed'], } const actual = degreesOfSeparation(familyTree, 'Dimitri', 'Fabio') expect(actual).toEqual(9) }) xit('Complex graph, no shortcut, far removed nephew', () => { const familyTree = { Mina: ['Viktor', 'Wang'], Olga: ['Yuki'], Javier: ['Quynh', 'Ravi'], Tariq: ['Farah'], Viktor: ['Hana', 'Ian'], Diego: ['Qi'], Carlos: ['Fatima', 'Gustavo'], Hana: ['Umar'], Jing: ['Wyatt'], Sven: ['Fabio'], Zane: ['Mateo'], Isla: ['Pedro'], Quynh: ['Boris'], Kaito: ['Xia'], Liam: ['Tariq', 'Uma'], Priya: ['Cai'], Qi: ['Dimitri'], Wang: ['Jing'], Yuki: ['Leila'], Xia: ['Kim'], Pedro: ['Zane', 'Aditi'], Uma: ['Giorgio'], Giorgio: ['Tomoko'], Gustavo: ['Mina'], Sofia: ['Diego', 'Elif'], Leila: ['Yassin'], Umar: ['Helena'], Aiko: ['Bao', 'Carlos'], Fatima: ['Khadija', 'Liam'], Oscar: ['Bianca'], Wyatt: ['Jun'], Ian: ['Vera'], Mateo: ['Zara'], Noah: ['Xiomara'], Celine: ['Priya'], Xiomara: ['Kaito'], Bao: ['Dalia', 'Elias'], Elif: ['Rami'], Farah: ['Sven'], Aditi: ['Nia'], Vera: ['Igor'], Boris: ['Oscar'], Khadija: ['Sofia'], Zara: ['Mohammed'], Dalia: ['Hassan', 'Isla'], Ravi: ['Celine'], Yassin: ['Lucia'], Elias: ['Javier'], Nia: ['Antonio'], Rami: ['Ewa'], Hassan: ['Noah', 'Olga'], Tomoko: ['Gabriela'], } const actual = degreesOfSeparation(familyTree, 'Lucia', 'Jun') expect(actual).toEqual(14) }) xit('Complex graph, some shortcuts, cross-down and cross-up, cousins several times removed', () => { const familyTree = { Mina: ['Viktor', 'Wang'], Olga: ['Yuki'], Javier: ['Quynh', 'Ravi'], Tariq: ['Farah'], Viktor: ['Hana', 'Ian'], Diego: ['Qi'], Carlos: ['Fatima', 'Gustavo'], Hana: ['Umar'], Jing: ['Wyatt'], Sven: ['Fabio'], Zane: ['Mateo'], Isla: ['Pedro'], Quynh: ['Boris'], Kaito: ['Xia'], Liam: ['Tariq', 'Uma'], Priya: ['Cai'], Qi: ['Dimitri'], Wang: ['Jing'], Yuki: ['Leila'], Xia: ['Kim'], Pedro: ['Zane', 'Aditi'], Uma: ['Giorgio'], Giorgio: ['Tomoko'], Gustavo: ['Mina'], Sofia: ['Diego', 'Elif'], Leila: ['Yassin'], Umar: ['Helena'], Aiko: ['Bao', 'Carlos'], Fatima: ['Khadija', 'Liam'], Oscar: ['Bianca'], Wyatt: ['Jun'], Ian: ['Vera'], Mateo: ['Zara'], Noah: ['Xiomara'], Celine: ['Priya'], Xiomara: ['Kaito'], Bao: ['Dalia'], Elif: ['Rami'], Farah: ['Sven'], Aditi: ['Nia'], Vera: ['Igor'], Boris: ['Oscar'], Khadija: ['Sofia'], Zara: ['Mohammed'], Dalia: ['Hassan', 'Isla'], Ravi: ['Celine'], Yassin: ['Lucia'], Nia: ['Antonio'], Rami: ['Ewa'], Hassan: ['Noah', 'Olga'], Tomoko: ['Gabriela'], } const actual = degreesOfSeparation(familyTree, 'Wyatt', 'Xia') expect(actual).toEqual(12) }) }) ================================================ FILE: exercises/practice/relative-distance/relative-distance.ts ================================================ export function degreesOfSeparation( familyTree: unknown, personA: unknown, personB: unknown ): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/relative-distance/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/relative-distance/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/resistor-color/.docs/instructions.md ================================================ # Instructions If you want to build something using a Raspberry Pi, you'll probably use _resistors_. For this exercise, you need to know two things about them: - Each resistor has a resistance value. - Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read. To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values. Each band has a position and a numeric value. The first 2 bands of a resistor have a simple encoding scheme: each color maps to a single number. In this exercise you are going to create a helpful program so that you don't have to remember the values of the bands. These colors are encoded as follows: - black: 0 - brown: 1 - red: 2 - orange: 3 - yellow: 4 - green: 5 - blue: 6 - violet: 7 - grey: 8 - white: 9 The goal of this exercise is to create a way: - to look up the numerical value associated with a particular color band - to list the different band colors Mnemonics map the colors to the numbers, that, when stored as an array, happen to map to their index in the array: Better Be Right Or Your Great Big Values Go Wrong. More information on the color encoding of resistors can be found in the [Electronic color code Wikipedia article][e-color-code]. [e-color-code]: https://en.wikipedia.org/wiki/Electronic_color_code ================================================ FILE: exercises/practice/resistor-color/.meta/config.json ================================================ { "authors": [ "Roshanjossey" ], "contributors": [ "SleeplessByte" ], "files": { "solution": [ "resistor-color.ts" ], "test": [ "resistor-color.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Convert a resistor band's color to its numeric representation.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Maud de Vries, Erik Schierboom", "source_url": "https://github.com/exercism/problem-specifications/issues/1458" } ================================================ FILE: exercises/practice/resistor-color/.meta/proof.ci.ts ================================================ export const COLORS = [ 'black', 'brown', 'red', 'orange', 'yellow', 'green', 'blue', 'violet', 'grey', 'white', ] export const colorCode = (color: string): number => COLORS.indexOf(color) ================================================ FILE: exercises/practice/resistor-color/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [49eb31c5-10a8-4180-9f7f-fea632ab87ef] description = "Color codes -> Black" [0a4df94b-92da-4579-a907-65040ce0b3fc] description = "Color codes -> White" [5f81608d-f36f-4190-8084-f45116b6f380] description = "Color codes -> Orange" [581d68fa-f968-4be2-9f9d-880f2fb73cf7] description = "Colors" ================================================ FILE: exercises/practice/resistor-color/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/resistor-color/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/resistor-color/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/resistor-color/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/resistor-color/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/resistor-color/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/resistor-color/package.json ================================================ { "name": "@exercism/typescript-resistor-color", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/resistor-color/resistor-color.test.ts ================================================ import { describe, xdescribe, it, expect, xit } from '@jest/globals' import { colorCode, COLORS } from './resistor-color.ts' describe('color code', () => { it('Black', () => { expect(colorCode('black')).toEqual(0) }) xit('White', () => { expect(colorCode('white')).toEqual(9) }) xit('Orange', () => { expect(colorCode('orange')).toEqual(3) }) }) xdescribe('Colors', () => { xit('returns all colors', () => { expect(COLORS).toEqual([ 'black', 'brown', 'red', 'orange', 'yellow', 'green', 'blue', 'violet', 'grey', 'white', ]) }) }) ================================================ FILE: exercises/practice/resistor-color/resistor-color.ts ================================================ export const colorCode = () => { throw new Error('Remove this line and implement the function') } export const COLORS = undefined ================================================ FILE: exercises/practice/resistor-color/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/resistor-color/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/resistor-color-duo/.docs/instructions.md ================================================ # Instructions If you want to build something using a Raspberry Pi, you'll probably use _resistors_. For this exercise, you need to know two things about them: - Each resistor has a resistance value. - Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read. To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values. Each band has a position and a numeric value. The first 2 bands of a resistor have a simple encoding scheme: each color maps to a single number. For example, if they printed a brown band (value 1) followed by a green band (value 5), it would translate to the number 15. In this exercise you are going to create a helpful program so that you don't have to remember the values of the bands. The program will take color names as input and output a two digit number, even if the input is more than two colors! The band colors are encoded as follows: - black: 0 - brown: 1 - red: 2 - orange: 3 - yellow: 4 - green: 5 - blue: 6 - violet: 7 - grey: 8 - white: 9 From the example above: brown-green should return 15, and brown-green-violet should return 15 too, ignoring the third color. ================================================ FILE: exercises/practice/resistor-color-duo/.meta/config.json ================================================ { "authors": [ "msomji" ], "contributors": [ "EduardoBautista", "SleeplessByte" ], "files": { "solution": [ "resistor-color-duo.ts" ], "test": [ "resistor-color-duo.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Convert color codes, as used on resistors, to a numeric value.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Maud de Vries, Erik Schierboom", "source_url": "https://github.com/exercism/problem-specifications/issues/1464" } ================================================ FILE: exercises/practice/resistor-color-duo/.meta/proof.ci.ts ================================================ // resistor-color solution START const COLORS = [ 'black', 'brown', 'red', 'orange', 'yellow', 'green', 'blue', 'violet', 'grey', 'white', ] const colorCode = (color: string): number => COLORS.indexOf(color) // resistor-color solution END export const decodedValue = ([tens, ones]: string[]): number => colorCode(tens) * 10 + colorCode(ones) ================================================ FILE: exercises/practice/resistor-color-duo/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [ce11995a-5b93-4950-a5e9-93423693b2fc] description = "Brown and black" [7bf82f7a-af23-48ba-a97d-38d59406a920] description = "Blue and grey" [f1886361-fdfd-4693-acf8-46726fe24e0c] description = "Yellow and violet" [b7a6cbd2-ae3c-470a-93eb-56670b305640] description = "White and red" [77a8293d-2a83-4016-b1af-991acc12b9fe] description = "Orange and orange" [0c4fb44f-db7c-4d03-afa8-054350f156a8] description = "Ignore additional colors" [4a8ceec5-0ab4-4904-88a4-daf953a5e818] description = "Black and brown, one-digit" ================================================ FILE: exercises/practice/resistor-color-duo/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/resistor-color-duo/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/resistor-color-duo/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/resistor-color-duo/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/resistor-color-duo/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/resistor-color-duo/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/resistor-color-duo/package.json ================================================ { "name": "@exercism/typescript-resistor-color-duo", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/resistor-color-duo/resistor-color-duo.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { decodedValue } from './resistor-color-duo.ts' describe('Resistor Colors', () => { it('Brown and black', () => { expect(decodedValue(['brown', 'black'])).toEqual(10) }) xit('Blue and grey', () => { expect(decodedValue(['blue', 'grey'])).toEqual(68) }) xit('White and red', () => { expect(decodedValue(['white', 'red'])).toEqual(92) }) xit('Yellow and violet', () => { expect(decodedValue(['yellow', 'violet'])).toEqual(47) }) xit('Orange and orange', () => { expect(decodedValue(['orange', 'orange'])).toEqual(33) }) xit('Ignore additional colors', () => { expect(decodedValue(['green', 'brown', 'orange'])).toEqual(51) }) xit('Black and brown, one-digit', () => { expect(decodedValue(['black', 'brown'])).toEqual(1) }) }) ================================================ FILE: exercises/practice/resistor-color-duo/resistor-color-duo.ts ================================================ export function decodedValue() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/resistor-color-duo/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/resistor-color-duo/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/resistor-color-trio/.docs/instructions.md ================================================ # Instructions If you want to build something using a Raspberry Pi, you'll probably use _resistors_. For this exercise, you need to know only three things about them: - Each resistor has a resistance value. - Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read. To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values. - Each band acts as a digit of a number. For example, if they printed a brown band (value 1) followed by a green band (value 5), it would translate to the number 15. In this exercise, you are going to create a helpful program so that you don't have to remember the values of the bands. The program will take 3 colors as input, and outputs the correct value, in ohms. The color bands are encoded as follows: - black: 0 - brown: 1 - red: 2 - orange: 3 - yellow: 4 - green: 5 - blue: 6 - violet: 7 - grey: 8 - white: 9 In Resistor Color Duo you decoded the first two colors. For instance: orange-orange got the main value `33`. The third color stands for how many zeros need to be added to the main value. The main value plus the zeros gives us a value in ohms. For the exercise it doesn't matter what ohms really are. For example: - orange-orange-black would be 33 and no zeros, which becomes 33 ohms. - orange-orange-red would be 33 and 2 zeros, which becomes 3300 ohms. - orange-orange-orange would be 33 and 3 zeros, which becomes 33000 ohms. (If Math is your thing, you may want to think of the zeros as exponents of 10. If Math is not your thing, go with the zeros. It really is the same thing, just in plain English instead of Math lingo.) This exercise is about translating the colors into a label: > "... ohms" So an input of `"orange", "orange", "black"` should return: > "33 ohms" When we get to larger resistors, a [metric prefix][metric-prefix] is used to indicate a larger magnitude of ohms, such as "kiloohms". That is similar to saying "2 kilometers" instead of "2000 meters", or "2 kilograms" for "2000 grams". For example, an input of `"orange", "orange", "orange"` should return: > "33 kiloohms" [metric-prefix]: https://en.wikipedia.org/wiki/Metric_prefix ================================================ FILE: exercises/practice/resistor-color-trio/.meta/config.json ================================================ { "authors": [ "rodmagaldi" ], "contributors": [ "SleeplessByte" ], "files": { "solution": [ "resistor-color-trio.ts" ], "test": [ "resistor-color-trio.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Convert color codes, as used on resistors, to a human-readable label.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Maud de Vries, Erik Schierboom", "source_url": "https://github.com/exercism/problem-specifications/issues/1549" } ================================================ FILE: exercises/practice/resistor-color-trio/.meta/proof.ci.ts ================================================ // resistor-color solution START const COLORS = [ 'black', 'brown', 'red', 'orange', 'yellow', 'green', 'blue', 'violet', 'grey', 'white', ] const colorCode = (color: string): number => COLORS.indexOf(color) // resistor-color solution END interface IUnits { name: string numberOfZeros: number representation: string } const UNITS: IUnits[] = [ { name: 'giga', numberOfZeros: 9, representation: '000000000', }, { name: 'mega', numberOfZeros: 6, representation: '000000', }, { name: 'kilo', numberOfZeros: 3, representation: '000', }, ] export const decodedResistorValue = ([ tens, ones, exponent, ]: string[]): string => { const numericValue = (colorCode(tens) * 10 + colorCode(ones)) * 10 ** colorCode(exponent) const stringifiedValue = numericValue.toString() let readableUnit = '' let readableValue: string = stringifiedValue for (const unit of UNITS) { if (stringifiedValue.endsWith(unit.representation)) { readableUnit = unit.name readableValue = readableValue.slice(0, -unit.numberOfZeros) break } } return `${readableValue} ${readableUnit}ohms` } ================================================ FILE: exercises/practice/resistor-color-trio/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [d6863355-15b7-40bb-abe0-bfb1a25512ed] description = "Orange and orange and black" [1224a3a9-8c8e-4032-843a-5224e04647d6] description = "Blue and grey and brown" [b8bda7dc-6b95-4539-abb2-2ad51d66a207] description = "Red and black and red" [5b1e74bc-d838-4eda-bbb3-eaba988e733b] description = "Green and brown and orange" [f5d37ef9-1919-4719-a90d-a33c5a6934c9] description = "Yellow and violet and yellow" [5f6404a7-5bb3-4283-877d-3d39bcc33854] description = "Blue and violet and blue" [7d3a6ab8-e40e-46c3-98b1-91639fff2344] description = "Minimum possible value" [ca0aa0ac-3825-42de-9f07-dac68cc580fd] description = "Maximum possible value" [0061a76c-903a-4714-8ce2-f26ce23b0e09] description = "First two colors make an invalid octal number" [30872c92-f567-4b69-a105-8455611c10c4] description = "Ignore extra colors" ================================================ FILE: exercises/practice/resistor-color-trio/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/resistor-color-trio/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/resistor-color-trio/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/resistor-color-trio/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/resistor-color-trio/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/resistor-color-trio/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/resistor-color-trio/package.json ================================================ { "name": "@exercism/typescript-resistor-color-trio", "version": "1.0.0", "description": "Exercism practice exercise on resistor-color-trio", "author": "Katrina Owen", "contributors": [ "Derk-Jan Karrenbeld (https://derk-jan.com)" ], "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/resistor-color-trio/resistor-color-trio.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { decodedResistorValue } from './resistor-color-trio.ts' describe('Resistor Colors', () => { it('Orange and orange and black', () => { expect(decodedResistorValue(['orange', 'orange', 'black'])).toEqual( '33 ohms' ) }) xit('Blue and grey and brown', () => { expect(decodedResistorValue(['blue', 'grey', 'brown'])).toEqual('680 ohms') }) xit('Red and black and red', () => { expect(decodedResistorValue(['red', 'black', 'red'])).toEqual('2 kiloohms') }) xit('Green and brown and orange', () => { expect(decodedResistorValue(['green', 'brown', 'orange'])).toEqual( '51 kiloohms' ) }) xit('Yellow and violet and yellow', () => { expect(decodedResistorValue(['yellow', 'violet', 'yellow'])).toEqual( '470 kiloohms' ) }) xit('Blue and violet and blue', () => { expect(decodedResistorValue(['blue', 'violet', 'blue'])).toEqual( '67 megaohms' ) }) xit('Minimum possible value', () => { expect(decodedResistorValue(['black', 'black', 'black'])).toEqual('0 ohms') }) xit('Maximum possible value', () => { expect(decodedResistorValue(['white', 'white', 'white'])).toEqual( '99 gigaohms' ) }) xit('First two colors make an invalid octal number', () => { expect(decodedResistorValue(['black', 'grey', 'black'])).toEqual('8 ohms') }) xit('Ignore extra colors', () => { expect(decodedResistorValue(['blue', 'green', 'yellow', 'orange'])).toEqual( '650 kiloohms' ) }) }) ================================================ FILE: exercises/practice/resistor-color-trio/resistor-color-trio.ts ================================================ export function decodedResistorValue() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/resistor-color-trio/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/resistor-color-trio/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/reverse-string/.docs/instructions.md ================================================ # Instructions Your task is to reverse a given string. Some examples: - Turn `"stressed"` into `"desserts"`. - Turn `"strops"` into `"sports"`. - Turn `"racecar"` into `"racecar"`. ================================================ FILE: exercises/practice/reverse-string/.docs/introduction.md ================================================ # Introduction Reversing strings (reading them from right to left, rather than from left to right) is a surprisingly common task in programming. For example, in bioinformatics, reversing the sequence of DNA or RNA strings is often important for various analyses, such as finding complementary strands or identifying palindromic sequences that have biological significance. ================================================ FILE: exercises/practice/reverse-string/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "reverse-string.ts" ], "test": [ "reverse-string.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Reverse a given string.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Introductory challenge to reverse an input string", "source_url": "https://www.freecodecamp.org/news/how-to-reverse-a-string-in-javascript-in-3-different-ways-75e4763c68cb" } ================================================ FILE: exercises/practice/reverse-string/.meta/proof.ci.ts ================================================ export function reverse(value: string): string { return value.split('').reverse().join('') } ================================================ FILE: exercises/practice/reverse-string/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [c3b7d806-dced-49ee-8543-933fd1719b1c] description = "an empty string" [01ebf55b-bebb-414e-9dec-06f7bb0bee3c] description = "a word" [0f7c07e4-efd1-4aaa-a07a-90b49ce0b746] description = "a capitalized word" [71854b9c-f200-4469-9f5c-1e8e5eff5614] description = "a sentence with punctuation" [1f8ed2f3-56f3-459b-8f3e-6d8d654a1f6c] description = "a palindrome" [b9e7dec1-c6df-40bd-9fa3-cd7ded010c4c] description = "an even-sized word" [1bed0f8a-13b0-4bd3-9d59-3d0593326fa2] description = "wide characters" [93d7e1b8-f60f-4f3c-9559-4056e10d2ead] description = "grapheme cluster with pre-combined form" include = false comment = "revisit once after upgrade to es2022 or higher" [1028b2c1-6763-4459-8540-2da47ca512d9] description = "grapheme clusters" include = false comment = "revisit once after upgrade to es2022 or higher" ================================================ FILE: exercises/practice/reverse-string/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/reverse-string/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/reverse-string/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/reverse-string/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/reverse-string/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/reverse-string/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/reverse-string/package.json ================================================ { "name": "@exercism/typescript-reverse-string", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/reverse-string/reverse-string.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { reverse } from './reverse-string.ts' describe('Reverse String', () => { it('an empty string', () => { const expected = '' expect(reverse('')).toEqual(expected) }) xit('a word', () => { const expected = 'tobor' expect(reverse('robot')).toEqual(expected) }) xit('a capitalized word', () => { const expected = 'nemaR' expect(reverse('Ramen')).toEqual(expected) }) xit('a sentence with punctuation', () => { const expected = `!yrgnuh m'I` expect(reverse(`I'm hungry!`)).toEqual(expected) }) xit('a palindrome', () => { const expected = 'racecar' expect(reverse('racecar')).toEqual(expected) }) xit('an even-sized word', () => { const expected = 'reward' expect(reverse('drawer')).toEqual(expected) }) xit('wide characters', () => { const expected = '猫子' expect(reverse('子猫')).toEqual(expected) }) }) ================================================ FILE: exercises/practice/reverse-string/reverse-string.ts ================================================ export function reverse(/* Parameters go here */) { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/reverse-string/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/reverse-string/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/rna-transcription/.docs/instructions.md ================================================ # Instructions Your task is to determine the RNA complement of a given DNA sequence. Both DNA and RNA strands are a sequence of nucleotides. The four nucleotides found in DNA are adenine (**A**), cytosine (**C**), guanine (**G**), and thymine (**T**). The four nucleotides found in RNA are adenine (**A**), cytosine (**C**), guanine (**G**), and uracil (**U**). Given a DNA strand, its transcribed RNA strand is formed by replacing each nucleotide with its complement: - `G` -> `C` - `C` -> `G` - `T` -> `A` - `A` -> `U` ~~~~exercism/note If you want to look at how the inputs and outputs are structured, take a look at the examples in the test suite. ~~~~ ================================================ FILE: exercises/practice/rna-transcription/.docs/introduction.md ================================================ # Introduction You work for a bioengineering company that specializes in developing therapeutic solutions. Your team has just been given a new project to develop a targeted therapy for a rare type of cancer. ~~~~exercism/note It's all very complicated, but the basic idea is that sometimes people's bodies produce too much of a given protein. That can cause all sorts of havoc. But if you can create a very specific molecule (called a micro-RNA), it can prevent the protein from being produced. This technique is called [RNA Interference][rnai]. [rnai]: https://admin.acceleratingscience.com/ask-a-scientist/what-is-rnai/ ~~~~ ================================================ FILE: exercises/practice/rna-transcription/.meta/config.json ================================================ { "authors": [ "masters3d" ], "contributors": [ "DFXLuna", "lukaszklis", "SleeplessByte" ], "files": { "solution": [ "rna-transcription.ts" ], "test": [ "rna-transcription.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a DNA strand, return its RNA complement.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Hyperphysics", "source_url": "https://web.archive.org/web/20220408112140/http://hyperphysics.phy-astr.gsu.edu/hbase/Organic/transcription.html" } ================================================ FILE: exercises/practice/rna-transcription/.meta/proof.ci.ts ================================================ export function toRna(input: string): string { const dictionary: { [key: string]: string } = { G: 'C', C: 'G', T: 'A', A: 'U', } let temp = '' input.split('').forEach((element) => { const current = dictionary[element] if (current === undefined) { throw new Error('Invalid input DNA.') } temp += current }) return temp } ================================================ FILE: exercises/practice/rna-transcription/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [b4631f82-c98c-4a2f-90b3-c5c2b6c6f661] description = "Empty RNA sequence" [a9558a3c-318c-4240-9256-5d5ed47005a6] description = "RNA complement of cytosine is guanine" [6eedbb5c-12cb-4c8b-9f51-f8320b4dc2e7] description = "RNA complement of guanine is cytosine" [870bd3ec-8487-471d-8d9a-a25046488d3e] description = "RNA complement of thymine is adenine" [aade8964-02e1-4073-872f-42d3ffd74c5f] description = "RNA complement of adenine is uracil" [79ed2757-f018-4f47-a1d7-34a559392dbf] description = "RNA complement" ================================================ FILE: exercises/practice/rna-transcription/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/rna-transcription/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/rna-transcription/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/rna-transcription/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/rna-transcription/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/rna-transcription/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/rna-transcription/package.json ================================================ { "name": "@exercism/typescript-rna-transcription", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/rna-transcription/rna-transcription.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { toRna } from './rna-transcription.ts' describe('Transcriptor', () => { it('transcribes cytosine to guanine', () => { expect(toRna('C')).toEqual('G') }) xit('transcribes guanine to cytosine', () => { expect(toRna('G')).toEqual('C') }) xit('transcribes adenine to uracil', () => { expect(toRna('A')).toEqual('U') }) xit('transcribes thymine to adenine', () => { expect(toRna('T')).toEqual('A') }) xit('transcribes all dna nucleotides to their rna complements', () => { expect(toRna('ACGTGGTCTTAA')).toEqual('UGCACCAGAAUU') }) xit('correctly handles invalid input', () => { expect(() => toRna('U')).toThrow('Invalid input DNA.') }) xit('correctly handles completely invalid input', () => { expect(() => toRna('XXX')).toThrow('Invalid input DNA.') }) xit('correctly handles partially invalid input', () => { expect(() => toRna('ACGTXXXCTTAA')).toThrow('Invalid input DNA.') }) }) ================================================ FILE: exercises/practice/rna-transcription/rna-transcription.ts ================================================ export function toRna() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/rna-transcription/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/rna-transcription/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/robot-name/.docs/instructions.md ================================================ # Instructions Manage robot factory settings. When a robot comes off the factory floor, it has no name. The first time you turn on a robot, a random name is generated in the format of two uppercase letters followed by three digits, such as RX837 or BC811. Every once in a while we need to reset a robot to its factory settings, which means that its name gets wiped. The next time you ask, that robot will respond with a new random name. The names must be random: they should not follow a predictable sequence. Using random names means a risk of collisions. Your solution must ensure that every existing robot has a unique name. ================================================ FILE: exercises/practice/robot-name/.meta/config.json ================================================ { "authors": [ "masters3d" ], "contributors": [ "joshgoebel", "lukaszklis", "SleeplessByte" ], "files": { "solution": [ "robot-name.ts" ], "test": [ "robot-name.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Manage robot factory settings.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": true, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "A debugging session with Paul Blackwell at gSchool." } ================================================ FILE: exercises/practice/robot-name/.meta/proof.ci.ts ================================================ /** * generates a random number from 0 to maximum-1 * * random(5) => generates an integer 0 - 4 * * @param maximum - max limit of generated number */ const rand = (maximum: number): number => Math.floor(Math.random() * maximum) class NameDatabase { private static ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' private availableNames!: string[] constructor() { this.releaseNames() } public releaseNames(): void { this.availableNames = this.allPossibleNames() } public allPossibleNames(): string[] { const names = [] for (const a of NameDatabase.ALPHABET) { for (const b of NameDatabase.ALPHABET) { for (let i = 0; i < 1000; i++) { names.push(`${a}${b}${i.toString().padStart(3, '0')}`) } } } return names } public fetchNewName(): string { if (this.availableNames.length === 0) { throw new Error('no more names') } const randomPosition = rand(this.availableNames.length) const name = this.availableNames[randomPosition] const lastName = this.availableNames.pop()! // swap the last name into the position of the name // we just removed (unless we happened to randomly // pick the very last name already) if (name !== lastName) this.availableNames[randomPosition] = lastName return name } } const RobotsDB = new NameDatabase() export class Robot { private _name!: string public get name(): string { return this._name } constructor() { this.resetName() } public resetName(): void { this._name = RobotsDB.fetchNewName() } public static releaseNames(): void { RobotsDB.releaseNames() } } ================================================ FILE: exercises/practice/robot-name/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/robot-name/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/robot-name/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/robot-name/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/robot-name/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/robot-name/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/robot-name/package.json ================================================ { "name": "@exercism/typescript-robot-name", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/robot-name/robot-name.test.ts ================================================ import { beforeEach, afterEach, describe, it, expect, xit } from '@jest/globals' import { Robot } from './robot-name.ts' const areSequential = (name1: string, name2: string): boolean => { const alpha1 = name1.substring(0, 2) const alpha2 = name2.substring(0, 2) const num1 = Number(name1.substring(2, 5)) const num2 = Number(name2.substring(2, 5)) const numDiff = num2 - num1 const alphaDiff = (alpha2.charCodeAt(0) - alpha1.charCodeAt(0)) * 26 + (alpha2.charCodeAt(1) - alpha1.charCodeAt(1)) const totalDiff = alphaDiff * 1000 + numDiff return Math.abs(totalDiff) <= 1 } const NAME_RE = /^[A-Z]{2}\d{3}$/ const TOTAL_NUMBER_OF_NAMES = 26 * // A-Z 26 * // A-Z 10 * // 0-9 10 * // 0-9 10 // 0-9 describe('Robot', () => { let robot: Robot beforeEach(() => { robot = new Robot() }) afterEach(() => { Robot.releaseNames() }) it('has a name', () => { expect(robot.name).toMatch(NAME_RE) }) xit('name is the same each time', () => { expect(robot.name).toEqual(robot.name) }) xit('different robots have different names', () => { const differentRobot = new Robot() expect(differentRobot.name).not.toEqual(robot.name) }) xit('is able to reset the name', () => { const originalName = robot.name robot.resetName() const newName = robot.name expect(newName).toMatch(NAME_RE) expect(originalName).not.toEqual(newName) }) xit('should set a unique name after reset', () => { const NUMBER_OF_ROBOTS = 10000 const usedNames = new Set() usedNames.add(robot.name) for (let i = 0; i < NUMBER_OF_ROBOTS; i++) { robot.resetName() usedNames.add(robot.name) } expect(usedNames.size).toEqual(NUMBER_OF_ROBOTS + 1) }) xit('new names should not be sequential', () => { const name1 = robot.name const name2 = new Robot().name const name3 = new Robot().name expect(areSequential(name1, name1)).toBe(true) expect(areSequential(name1, name2)).toBe(false) expect(areSequential(name2, name3)).toBe(false) }) xit('names from reset should not be sequential', () => { const name1 = robot.name robot.resetName() const name2 = robot.name robot.resetName() const name3 = robot.name expect(areSequential(name1, name2)).toBe(false) expect(areSequential(name2, name3)).toBe(false) expect(areSequential(name3, name3)).toBe(true) }) xit('uses all letters', () => { let letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' for (let i = 0; i < 1000 - 1; i += 1) { const newRobot = new Robot() const lettersInName = newRobot.name.slice(0, 2) letters = letters.replace(lettersInName[0], '') letters = letters.replace(lettersInName[1], '') if (letters.length === 0) { break } } expect(letters).toEqual('') }) xit('uses all numbers', () => { let numbers = '0123456789' for (let i = 0; i < 1000 - 1; i += 1) { const newRobot = new Robot() const digitsInName = newRobot.name.slice(2, 5) numbers = numbers.replace(digitsInName[0], '') numbers = numbers.replace(digitsInName[1], '') numbers = numbers.replace(digitsInName[2], '') if (numbers.length === 0) { break } } expect(numbers).toEqual('') }) // This test is optional. // // This test doesn't run on our online test runner because it will time-out // with most implementations. It's up to you to test your solution locally. it.skip('all the names can be generated', () => { const usedNames = new Set() usedNames.add(robot.name) for (let i = 0; i < TOTAL_NUMBER_OF_NAMES - 1; i += 1) { const newRobot = new Robot() usedNames.add(newRobot.name) } expect(usedNames.size).toEqual(TOTAL_NUMBER_OF_NAMES) }) }) ================================================ FILE: exercises/practice/robot-name/robot-name.ts ================================================ export class Robot { constructor() {} public get name(): string { throw new Error('Remove this line and implement the function') } public resetName(): void { throw new Error('Remove this line and implement the function') } public static releaseNames(): void { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/robot-name/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/robot-name/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/robot-simulator/.docs/instructions.md ================================================ # Instructions Write a robot simulator. A robot factory's test facility needs a program to verify robot movements. The robots have three possible movements: - turn right - turn left - advance Robots are placed on a hypothetical infinite grid, facing a particular direction (north, east, south, or west) at a set of {x,y} coordinates, e.g., {3,8}, with coordinates increasing to the north and east. The robot then receives a number of instructions, at which point the testing facility verifies the robot's new position, and in which direction it is pointing. - The letter-string "RAALAL" means: - Turn right - Advance twice - Turn left - Advance once - Turn left yet again - Say a robot starts at {7, 3} facing north. Then running this stream of instructions should leave it at {9, 4} facing west. ================================================ FILE: exercises/practice/robot-simulator/.meta/config.json ================================================ { "authors": [ "deyshin" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "robot-simulator.ts" ], "test": [ "robot-simulator.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Write a robot simulator.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Inspired by an interview question at a famous company." } ================================================ FILE: exercises/practice/robot-simulator/.meta/proof.ci.ts ================================================ export class InvalidInputError extends Error { constructor(message: string) { super() this.message = message || 'Invalid Input' } } type Bearing = 'north' | 'south' | 'east' | 'west' type Instruction = 'turnLeft' | 'turnRight' | 'advance' const validDirections: Bearing[] = ['north', 'south', 'east', 'west'] function isValidBearing(next: string): next is Bearing { return validDirections.includes(next as Bearing) } export class Robot { public coordinates: [number, number] public bearing: Bearing private static instructions(s: string): Instruction[] { return [...s].map((character) => { switch (character) { case 'L': return 'turnLeft' case 'R': return 'turnRight' case 'A': return 'advance' default: throw new InvalidInputError( `${character} is not a valid instruction character.` ) } }) } constructor() { this.coordinates = [0, 0] this.bearing = 'north' } private set direction(next: string) { if (!isValidBearing(next)) { throw new InvalidInputError('Invalid Robot Bearing') } this.bearing = next } private advance(): void { if (this.bearing === 'north') { this.coordinates[1] += 1 } else if (this.bearing === 'south') { this.coordinates[1] -= 1 } else if (this.bearing === 'east') { this.coordinates[0] += 1 } else if (this.bearing === 'west') { this.coordinates[0] -= 1 } } private turnLeft(): void { if (this.bearing === 'north') { this.direction = 'west' } else if (this.bearing === 'south') { this.direction = 'east' } else if (this.bearing === 'east') { this.direction = 'north' } else if (this.bearing === 'west') { this.direction = 'south' } } private turnRight(): void { if (this.bearing === 'north') { this.direction = 'east' } else if (this.bearing === 'south') { this.direction = 'west' } else if (this.bearing === 'east') { this.direction = 'south' } else if (this.bearing === 'west') { this.direction = 'north' } } public place(args: { x: number; y: number; direction: string }): void { this.coordinates = [args.x, args.y] this.direction = args.direction } public evaluate(s: string): void { Robot.instructions(s).forEach((instruction) => { this[instruction]() }) } } ================================================ FILE: exercises/practice/robot-simulator/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [c557c16d-26c1-4e06-827c-f6602cd0785c] description = "at origin facing north" [bf0dffce-f11c-4cdb-8a5e-2c89d8a5a67d] description = "at negative position facing south" [8cbd0086-6392-4680-b9b9-73cf491e67e5] description = "changes north to east" [8abc87fc-eab2-4276-93b7-9c009e866ba1] description = "changes east to south" [3cfe1b85-bbf2-4bae-b54d-d73e7e93617a] description = "changes south to west" [5ea9fb99-3f2c-47bd-86f7-46b7d8c3c716] description = "changes west to north" [fa0c40f5-6ba3-443d-a4b3-58cbd6cb8d63] description = "changes north to west" [da33d734-831f-445c-9907-d66d7d2a92e2] description = "changes west to south" [bd1ca4b9-4548-45f4-b32e-900fc7c19389] description = "changes south to east" [2de27b67-a25c-4b59-9883-bc03b1b55bba] description = "changes east to north" [f0dc2388-cddc-4f83-9bed-bcf46b8fc7b8] description = "facing north increments Y" [2786cf80-5bbf-44b0-9503-a89a9c5789da] description = "facing south decrements Y" [84bf3c8c-241f-434d-883d-69817dbd6a48] description = "facing east increments X" [bb69c4a7-3bbf-4f64-b415-666fa72d7b04] description = "facing west decrements X" [e34ac672-4ed4-4be3-a0b8-d9af259cbaa1] description = "moving east and north from README" [f30e4955-4b47-4aa3-8b39-ae98cfbd515b] description = "moving west and north" [3e466bf6-20ab-4d79-8b51-264165182fca] description = "moving west and south" [41f0bb96-c617-4e6b-acff-a4b279d44514] description = "moving east and north" ================================================ FILE: exercises/practice/robot-simulator/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/robot-simulator/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/robot-simulator/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/robot-simulator/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/robot-simulator/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/robot-simulator/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/robot-simulator/package.json ================================================ { "name": "@exercism/typescript-robot-simulator", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/robot-simulator/robot-simulator.test.ts ================================================ import { describe, xdescribe, it, expect, xit } from '@jest/globals' import { Robot, InvalidInputError } from './robot-simulator.ts' function turnRight(robot: Robot): void { robot.evaluate('R') } function turnLeft(robot: Robot): void { robot.evaluate('L') } function advance(robot: Robot): void { robot.evaluate('A') } describe('Robot', () => { describe('Create robot', () => { it('facing north by default', () => { const robot = new Robot() expect(robot.bearing).toEqual('north') }) it('facing east', () => { const robot = new Robot() robot.place({ direction: 'east', x: 0, y: 0 }) expect(robot.bearing).toEqual('east') }) it('facing west, at origin', () => { const robot = new Robot() robot.place({ direction: 'west', x: 0, y: 0 }) expect(robot.bearing).toEqual('west') expect(robot.coordinates).toEqual([0, 0]) }) it('at negative position facing south', () => { const robot = new Robot() robot.place({ direction: 'south', x: -1, y: -1 }) expect(robot.bearing).toEqual('south') expect(robot.coordinates).toEqual([-1, -1]) }) xit('invalid robot bearing', () => { const robot = new Robot() expect(InvalidInputError.prototype).toBeInstanceOf(Error) expect(() => robot.place({ direction: 'crood', x: 0, y: 0 })).toThrow( InvalidInputError ) }) }) xdescribe('Rotating clockwise', () => { const robot = new Robot() xit('changes north to east', () => { robot.place({ direction: 'north', x: 0, y: 0 }) turnRight(robot) expect(robot.bearing).toEqual('east') expect(robot.coordinates).toEqual([0, 0]) }) xit('changes east to south', () => { robot.place({ direction: 'east', x: 0, y: 0 }) turnRight(robot) expect(robot.bearing).toEqual('south') expect(robot.coordinates).toEqual([0, 0]) }) xit('changes south to west', () => { robot.place({ direction: 'south', x: 0, y: 0 }) turnRight(robot) expect(robot.bearing).toEqual('west') expect(robot.coordinates).toEqual([0, 0]) }) xit('changes west to north', () => { robot.place({ direction: 'west', x: 0, y: 0 }) turnRight(robot) expect(robot.bearing).toEqual('north') expect(robot.coordinates).toEqual([0, 0]) }) }) xdescribe('Rotating counter-clockwise', () => { const robot = new Robot() xit('changes north to west', () => { robot.place({ direction: 'north', x: 0, y: 0 }) turnLeft(robot) expect(robot.bearing).toEqual('west') expect(robot.coordinates).toEqual([0, 0]) }) xit('changes west to south', () => { robot.place({ direction: 'west', x: 0, y: 0 }) turnLeft(robot) expect(robot.bearing).toEqual('south') expect(robot.coordinates).toEqual([0, 0]) }) xit('changes south to east', () => { robot.place({ direction: 'south', x: 0, y: 0 }) turnLeft(robot) expect(robot.bearing).toEqual('east') expect(robot.coordinates).toEqual([0, 0]) }) xit('changes east to north', () => { robot.place({ direction: 'east', x: 0, y: 0 }) turnLeft(robot) expect(robot.bearing).toEqual('north') expect(robot.coordinates).toEqual([0, 0]) }) }) xdescribe('Moving forward one', () => { const robot = new Robot() xit('advance when facing north', () => { robot.place({ direction: 'north', x: 0, y: 0 }) advance(robot) expect(robot.coordinates).toEqual([0, 1]) expect(robot.bearing).toEqual('north') }) xit('advance when facing south', () => { robot.place({ direction: 'south', x: 0, y: 0 }) advance(robot) expect(robot.coordinates).toEqual([0, -1]) expect(robot.bearing).toEqual('south') }) xit('advance when facing east', () => { robot.place({ direction: 'east', x: 0, y: 0 }) advance(robot) expect(robot.coordinates).toEqual([1, 0]) expect(robot.bearing).toEqual('east') }) xit('advance when facing west', () => { robot.place({ direction: 'west', x: 0, y: 0 }) advance(robot) expect(robot.coordinates).toEqual([-1, 0]) expect(robot.bearing).toEqual('west') }) }) xdescribe('Follow series of instructions', () => { const robot = new Robot() xit('moving east and north from README', () => { robot.place({ x: 7, y: 3, direction: 'north' }) robot.evaluate('RAALAL') expect(robot.coordinates).toEqual([9, 4]) expect(robot.bearing).toEqual('west') }) xit('moving west and north', () => { robot.place({ x: 0, y: 0, direction: 'north' }) robot.evaluate('LAAARALA') expect(robot.coordinates).toEqual([-4, 1]) expect(robot.bearing).toEqual('west') }) xit('moving west and south', () => { robot.place({ x: 2, y: -7, direction: 'east' }) robot.evaluate('RRAAAAALA') expect(robot.coordinates).toEqual([-3, -8]) expect(robot.bearing).toEqual('south') }) xit('moving east and north', () => { robot.place({ x: 8, y: 4, direction: 'south' }) robot.evaluate('LAAARRRALLLL') expect(robot.coordinates).toEqual([11, 5]) expect(robot.bearing).toEqual('north') }) xit('instruct many robots', () => { const robot1 = new Robot() const robot2 = new Robot() const robot3 = new Robot() robot1.place({ x: 0, y: 0, direction: 'north' }) robot2.place({ x: 2, y: -7, direction: 'east' }) robot3.place({ x: 8, y: 4, direction: 'south' }) robot1.evaluate('LAAARALA') robot2.evaluate('RRAAAAALA') robot3.evaluate('LAAARRRALLLL') expect(robot1.coordinates).toEqual([-4, 1]) expect(robot1.bearing).toEqual('west') expect(robot2.coordinates).toEqual([-3, -8]) expect(robot2.bearing).toEqual('south') expect(robot3.coordinates).toEqual([11, 5]) expect(robot3.bearing).toEqual('north') }) }) }) ================================================ FILE: exercises/practice/robot-simulator/robot-simulator.ts ================================================ export class InvalidInputError extends Error { constructor(message: string) { super() this.message = message || 'Invalid Input' } } type Direction = 'north' | 'east' | 'south' | 'west' type Coordinates = [number, number] export class Robot { get bearing(): Direction { throw new Error('Remove this line and implement the function') } get coordinates(): Coordinates { throw new Error('Remove this line and implement the function') } place({}: { x: number; y: number; direction: string }) { throw new Error('Remove this line and implement the function') } evaluate(instructions: string) { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/robot-simulator/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/robot-simulator/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/roman-numerals/.docs/instructions.md ================================================ # Introduction Your task is to convert a number from Arabic numerals to Roman numerals. For this exercise, we are only concerned about traditional Roman numerals, in which the largest number is MMMCMXCIX (or 3,999). ~~~~exercism/note There are lots of different ways to convert between Arabic and Roman numerals. We recommend taking a naive approach first to familiarise yourself with the concept of Roman numerals and then search for more efficient methods. Make sure to check out our Deep Dive video at the end to explore the different approaches you can take! ~~~~ ================================================ FILE: exercises/practice/roman-numerals/.docs/introduction.md ================================================ # Description Today, most people in the world use Arabic numerals (0–9). But if you travelled back two thousand years, you'd find that most Europeans were using Roman numerals instead. To write a Roman numeral we use the following Latin letters, each of which has a value: | M | D | C | L | X | V | I | | ---- | --- | --- | --- | --- | --- | --- | | 1000 | 500 | 100 | 50 | 10 | 5 | 1 | A Roman numeral is a sequence of these letters, and its value is the sum of the letters' values. For example, `XVIII` has the value 18 (`10 + 5 + 1 + 1 + 1 = 18`). There's one rule that makes things trickier though, and that's that **the same letter cannot be used more than three times in succession**. That means that we can't express numbers such as 4 with the seemingly natural `IIII`. Instead, for those numbers, we use a subtraction method between two letters. So we think of `4` not as `1 + 1 + 1 + 1` but instead as `5 - 1`. And slightly confusingly to our modern thinking, we write the smaller number first. This applies only in the following cases: 4 (`IV`), 9 (`IX`), 40 (`XL`), 90 (`XC`), 400 (`CD`) and 900 (`CM`). Order matters in Roman numerals! Letters (and the special compounds above) must be ordered by decreasing value from left to right. Here are some examples: ```text 105 => CV ---- => -- 100 => C + 5 => V ``` ```text 106 => CVI ---- => -- 100 => C + 5 => V + 1 => I ``` ```text 104 => CIV ---- => --- 100 => C + 4 => IV ``` And a final more complex example: ```text 1996 => MCMXCVI ----- => ------- 1000 => M + 900 => CM + 90 => XC + 5 => V + 1 => I ``` ================================================ FILE: exercises/practice/roman-numerals/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "roman-numerals.ts" ], "test": [ "roman-numerals.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Convert modern Arabic numbers into Roman numerals.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "The Roman Numeral Kata", "source_url": "https://codingdojo.org/kata/RomanNumerals/" } ================================================ FILE: exercises/practice/roman-numerals/.meta/proof.ci.ts ================================================ export function toRoman(number: number): string { let result = '' let remainingNumber = number const mappings = [ { arabic: 1000, roman: 'M' }, { arabic: 900, roman: 'CM' }, { arabic: 500, roman: 'D' }, { arabic: 400, roman: 'CD' }, { arabic: 100, roman: 'C' }, { arabic: 90, roman: 'XC' }, { arabic: 50, roman: 'L' }, { arabic: 40, roman: 'XL' }, { arabic: 10, roman: 'X' }, { arabic: 9, roman: 'IX' }, { arabic: 5, roman: 'V' }, { arabic: 4, roman: 'IV' }, { arabic: 1, roman: 'I' }, ] mappings.forEach((mapping) => { while (remainingNumber >= mapping.arabic) { result += mapping.roman remainingNumber -= mapping.arabic } }) return result } ================================================ FILE: exercises/practice/roman-numerals/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [19828a3a-fbf7-4661-8ddd-cbaeee0e2178] description = "1 is I" [f088f064-2d35-4476-9a41-f576da3f7b03] description = "2 is II" [b374a79c-3bea-43e6-8db8-1286f79c7106] description = "3 is III" [05a0a1d4-a140-4db1-82e8-fcc21fdb49bb] description = "4 is IV" [57c0f9ad-5024-46ab-975d-de18c430b290] description = "5 is V" [20a2b47f-e57f-4797-a541-0b3825d7f249] description = "6 is VI" [ff3fb08c-4917-4aab-9f4e-d663491d083d] description = "9 is IX" [6d1d82d5-bf3e-48af-9139-87d7165ed509] description = "16 is XVI" [2bda64ca-7d28-4c56-b08d-16ce65716cf6] description = "27 is XXVII" [a1f812ef-84da-4e02-b4f0-89c907d0962c] description = "48 is XLVIII" [607ead62-23d6-4c11-a396-ef821e2e5f75] description = "49 is XLIX" [d5b283d4-455d-4e68-aacf-add6c4b51915] description = "59 is LIX" [4465ffd5-34dc-44f3-ada5-56f5007b6dad] description = "66 is LXVI" [46b46e5b-24da-4180-bfe2-2ef30b39d0d0] description = "93 is XCIII" [30494be1-9afb-4f84-9d71-db9df18b55e3] description = "141 is CXLI" [267f0207-3c55-459a-b81d-67cec7a46ed9] description = "163 is CLXIII" [902ad132-0b4d-40e3-8597-ba5ed611dd8d] description = "166 is CLXVI" [cdb06885-4485-4d71-8bfb-c9d0f496b404] description = "402 is CDII" [6b71841d-13b2-46b4-ba97-dec28133ea80] description = "575 is DLXXV" [dacb84b9-ea1c-4a61-acbb-ce6b36674906] description = "666 is DCLXVI" [432de891-7fd6-4748-a7f6-156082eeca2f] description = "911 is CMXI" [e6de6d24-f668-41c0-88d7-889c0254d173] description = "1024 is MXXIV" [efbe1d6a-9f98-4eb5-82bc-72753e3ac328] description = "1666 is MDCLXVI" [bb550038-d4eb-4be2-a9ce-f21961ac3bc6] description = "3000 is MMM" [3bc4b41c-c2e6-49d9-9142-420691504336] description = "3001 is MMMI" [2f89cad7-73f6-4d1b-857b-0ef531f68b7e] description = "3888 is MMMDCCCLXXXVIII" [4e18e96b-5fbb-43df-a91b-9cb511fe0856] description = "3999 is MMMCMXCIX" ================================================ FILE: exercises/practice/roman-numerals/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/roman-numerals/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/roman-numerals/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/roman-numerals/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/roman-numerals/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/roman-numerals/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/roman-numerals/package.json ================================================ { "name": "@exercism/typescript-roman-numerals", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/roman-numerals/roman-numerals.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { toRoman } from './roman-numerals.ts' describe('toRoman()', () => { it('converts 1', () => expect(toRoman(1)).toEqual('I')) xit('converts 2', () => expect(toRoman(2)).toEqual('II')) xit('converts 3', () => expect(toRoman(3)).toEqual('III')) xit('converts 4', () => expect(toRoman(4)).toEqual('IV')) xit('converts 5', () => expect(toRoman(5)).toEqual('V')) xit('converts 6', () => expect(toRoman(6)).toEqual('VI')) xit('converts 9', () => expect(toRoman(9)).toEqual('IX')) xit('converts 16', () => expect(toRoman(16)).toEqual('XVI')) xit('converts 27', () => expect(toRoman(27)).toEqual('XXVII')) xit('converts 48', () => expect(toRoman(48)).toEqual('XLVIII')) xit('converts 49', () => expect(toRoman(49)).toEqual('XLIX')) xit('converts 59', () => expect(toRoman(59)).toEqual('LIX')) xit('converts 66', () => expect(toRoman(66)).toEqual('LXVI')) xit('converts 93', () => expect(toRoman(93)).toEqual('XCIII')) xit('converts 141', () => expect(toRoman(141)).toEqual('CXLI')) xit('converts 163', () => expect(toRoman(163)).toEqual('CLXIII')) xit('converts 166', () => expect(toRoman(166)).toEqual('CLXVI')) xit('converts 402', () => expect(toRoman(402)).toEqual('CDII')) xit('converts 575', () => expect(toRoman(575)).toEqual('DLXXV')) xit('converts 666', () => expect(toRoman(666)).toEqual('DCLXVI')) xit('converts 911', () => expect(toRoman(911)).toEqual('CMXI')) xit('converts 1024', () => expect(toRoman(1024)).toEqual('MXXIV')) xit('converts 1666', () => expect(toRoman(1666)).toEqual('MDCLXVI')) xit('converts 3000', () => expect(toRoman(3000)).toEqual('MMM')) xit('converts 3001', () => expect(toRoman(3001)).toEqual('MMMI')) xit('converts 3888', () => expect(toRoman(3888)).toEqual('MMMDCCCLXXXVIII')) xit('converts 3999', () => expect(toRoman(3999)).toEqual('MMMCMXCIX')) }) ================================================ FILE: exercises/practice/roman-numerals/roman-numerals.ts ================================================ export const toRoman = () => { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/roman-numerals/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/roman-numerals/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/rotational-cipher/.docs/instructions.md ================================================ # Instructions Create an implementation of the rotational cipher, also sometimes called the Caesar cipher. The Caesar cipher is a simple shift cipher that relies on transposing all the letters in the alphabet using an integer key between `0` and `26`. Using a key of `0` or `26` will always yield the same output due to modular arithmetic. The letter is shifted for as many values as the value of the key. The general notation for rotational ciphers is `ROT + `. The most commonly used rotational cipher is `ROT13`. A `ROT13` on the Latin alphabet would be as follows: ```text Plain: abcdefghijklmnopqrstuvwxyz Cipher: nopqrstuvwxyzabcdefghijklm ``` It is stronger than the Atbash cipher because it has 27 possible keys, and 25 usable keys. Ciphertext is written out in the same formatting as the input including spaces and punctuation. ## Examples - ROT5 `omg` gives `trl` - ROT0 `c` gives `c` - ROT26 `Cool` gives `Cool` - ROT13 `The quick brown fox jumps over the lazy dog.` gives `Gur dhvpx oebja sbk whzcf bire gur ynml qbt.` - ROT13 `Gur dhvpx oebja sbk whzcf bire gur ynml qbt.` gives `The quick brown fox jumps over the lazy dog.` ================================================ FILE: exercises/practice/rotational-cipher/.meta/config.json ================================================ { "authors": [ "anuragsoni" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "rotational-cipher.ts" ], "test": [ "rotational-cipher.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Create an implementation of the rotational cipher, also sometimes called the Caesar cipher.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Caesar_cipher" } ================================================ FILE: exercises/practice/rotational-cipher/.meta/proof.ci.ts ================================================ export function rotate(text: string, shiftBy: number): string { return [...text] .map((c) => { const isUpper = c.toUpperCase() === c const isAlpha = c.match(/[a-z]/i) const caseCharCode = (isUpper ? 'A' : 'a').charCodeAt(0) if (isAlpha) { const charCode = c.charCodeAt(0) return String.fromCharCode( ((charCode - caseCharCode + shiftBy) % 26) + caseCharCode ) } else { return c } }) .join('') } ================================================ FILE: exercises/practice/rotational-cipher/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [74e58a38-e484-43f1-9466-877a7515e10f] description = "rotate a by 0, same output as input" [7ee352c6-e6b0-4930-b903-d09943ecb8f5] description = "rotate a by 1" [edf0a733-4231-4594-a5ee-46a4009ad764] description = "rotate a by 26, same output as input" [e3e82cb9-2a5b-403f-9931-e43213879300] description = "rotate m by 13" [19f9eb78-e2ad-4da4-8fe3-9291d47c1709] description = "rotate n by 13 with wrap around alphabet" [a116aef4-225b-4da9-884f-e8023ca6408a] description = "rotate capital letters" [71b541bb-819c-4dc6-a9c3-132ef9bb737b] description = "rotate spaces" [ef32601d-e9ef-4b29-b2b5-8971392282e6] description = "rotate numbers" [32dd74f6-db2b-41a6-b02c-82eb4f93e549] description = "rotate punctuation" [9fb93fe6-42b0-46e6-9ec1-0bf0a062d8c9] description = "rotate all letters" ================================================ FILE: exercises/practice/rotational-cipher/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/rotational-cipher/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/rotational-cipher/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/rotational-cipher/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/rotational-cipher/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/rotational-cipher/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/rotational-cipher/package.json ================================================ { "name": "@exercism/typescript-rotational-cipher", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/rotational-cipher/rotational-cipher.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { rotate } from './rotational-cipher.ts' describe('RotationalCipher', () => { it('rotate a by 1', () => { const result = rotate('a', 1) expect(result).toEqual('b') }) xit('rotate a by 26, same output as input', () => { const result = rotate('a', 26) expect(result).toEqual('a') }) xit('rotate a by 0, same output as input', () => { const result = rotate('a', 0) expect(result).toEqual('a') }) xit('rotate m by 13', () => { const result = rotate('m', 13) expect(result).toEqual('z') }) xit('rotate n by 13 with wrap around alphabet', () => { const result = rotate('n', 13) expect(result).toEqual('a') }) xit('rotate capital letters', () => { const result = rotate('OMG', 5) expect(result).toEqual('TRL') }) xit('rotate spaces', () => { const result = rotate('O M G', 5) expect(result).toEqual('T R L') }) xit('rotate numbers', () => { const result = rotate('Testing 1 2 3 testing', 4) expect(result).toEqual('Xiwxmrk 1 2 3 xiwxmrk') }) xit('rotate punctuation', () => { const result = rotate("Let's eat, Grandma!", 21) expect(result).toEqual("Gzo'n zvo, Bmviyhv!") }) xit('rotate all letters', () => { const result = rotate('The quick brown fox jumps over the lazy dog.', 13) expect(result).toEqual('Gur dhvpx oebja sbk whzcf bire gur ynml qbt.') }) }) ================================================ FILE: exercises/practice/rotational-cipher/rotational-cipher.ts ================================================ export function rotate() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/rotational-cipher/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/rotational-cipher/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/run-length-encoding/.docs/instructions.md ================================================ # Instructions Implement run-length encoding and decoding. Run-length encoding (RLE) is a simple form of data compression, where runs (consecutive data elements) are replaced by just one data value and count. For example we can represent the original 53 characters with only 13. ```text "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB" -> "12WB12W3B24WB" ``` RLE allows the original data to be perfectly reconstructed from the compressed data, which makes it a lossless data compression. ```text "AABCCCDEEEE" -> "2AB3CD4E" -> "AABCCCDEEEE" ``` For simplicity, you can assume that the unencoded string will only contain the letters A through Z (either lower or upper case) and whitespace. This way data to be encoded will never contain any numbers and numbers inside data to be decoded always represent the count for the following character. ================================================ FILE: exercises/practice/run-length-encoding/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "run-length-encoding.ts" ], "test": [ "run-length-encoding.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Implement run-length encoding and decoding.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Run-length_encoding" } ================================================ FILE: exercises/practice/run-length-encoding/.meta/proof.ci.ts ================================================ export function encode(plaintext: string): string { return plaintext.replace(/([\w\s])\1*/g, (match) => { return match.length > 1 ? match.length + match[0] : match[0] }) } export function decode(cypher: string): string { return cypher.replace(/(\d+)(\w|\s)/g, (_match, repeats, char) => { return new Array(Number(repeats) + 1).join(char as string) }) } ================================================ FILE: exercises/practice/run-length-encoding/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [ad53b61b-6ffc-422f-81a6-61f7df92a231] description = "empty string" [52012823-b7e6-4277-893c-5b96d42f82de] description = "single characters only are encoded without count" [b7868492-7e3a-415f-8da3-d88f51f80409] description = "string with no single characters" [859b822b-6e9f-44d6-9c46-6091ee6ae358] description = "single characters mixed with repeated characters" [1b34de62-e152-47be-bc88-469746df63b3] description = "multiple whitespace mixed in string" [abf176e2-3fbd-40ad-bb2f-2dd6d4df721a] description = "lowercase characters" [7ec5c390-f03c-4acf-ac29-5f65861cdeb5] description = "empty string" [ad23f455-1ac2-4b0e-87d0-b85b10696098] description = "single characters only" [21e37583-5a20-4a0e-826c-3dee2c375f54] description = "string with no single characters" [1389ad09-c3a8-4813-9324-99363fba429c] description = "single characters with repeated characters" [3f8e3c51-6aca-4670-b86c-a213bf4706b0] description = "multiple whitespace mixed in string" [29f721de-9aad-435f-ba37-7662df4fb551] description = "lower case string" [2a762efd-8695-4e04-b0d6-9736899fbc16] description = "encode followed by decode gives original string" ================================================ FILE: exercises/practice/run-length-encoding/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/run-length-encoding/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/run-length-encoding/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/run-length-encoding/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/run-length-encoding/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/run-length-encoding/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/run-length-encoding/package.json ================================================ { "name": "@exercism/typescript-run-length-encoding", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/run-length-encoding/run-length-encoding.test.ts ================================================ import { describe, xdescribe, it, expect, xit } from '@jest/globals' import { encode, decode } from './run-length-encoding.ts' describe('run-length encode a string', () => { it('empty string', () => { const expected = '' expect(encode('')).toEqual(expected) }) xit('single characters only are encoded without count', () => { const expected = 'XYZ' expect(encode('XYZ')).toEqual(expected) }) xit('string with no single characters', () => { const expected = '2A3B4C' expect(encode('AABBBCCCC')).toEqual(expected) }) xit('single characters mixed with repeated characters', () => { const expected = '12WB12W3B24WB' expect( encode('WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB') ).toEqual(expected) }) xit('multiple whitespace mixed in string', () => { const expected = '2 hs2q q2w2 ' expect(encode(' hsqq qww ')).toEqual(expected) }) xit('lowercase characters', () => { const expected = '2a3b4c' expect(encode('aabbbcccc')).toEqual(expected) }) }) xdescribe('run-length decode a string', () => { xit('empty string', () => { const expected = '' expect(decode('')).toEqual(expected) }) xit('single characters only', () => { const expected = 'XYZ' expect(decode('XYZ')).toEqual(expected) }) xit('string with no single characters', () => { const expected = 'AABBBCCCC' expect(decode('2A3B4C')).toEqual(expected) }) xit('single characters with repeated characters', () => { const expected = 'WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB' expect(decode('12WB12W3B24WB')).toEqual(expected) }) xit('multiple whitespace mixed in string', () => { const expected = ' hsqq qww ' expect(decode('2 hs2q q2w2 ')).toEqual(expected) }) xit('lower case string', () => { const expected = 'aabbbcccc' expect(decode('2a3b4c')).toEqual(expected) }) }) xdescribe('encode and then decode', () => { xit('encode followed by decode gives original string', () => { expect(decode(encode('zzz ZZ zZ'))).toEqual('zzz ZZ zZ') }) }) ================================================ FILE: exercises/practice/run-length-encoding/run-length-encoding.ts ================================================ export function encode() { throw new Error('Remove this line and implement the function') } export function decode() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/run-length-encoding/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/run-length-encoding/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/saddle-points/.docs/instructions.md ================================================ # Instructions Your task is to find the potential trees where you could build your tree house. The data company provides the data as grids that show the heights of the trees. The rows of the grid represent the east-west direction, and the columns represent the north-south direction. An acceptable tree will be the largest in its row, while being the smallest in its column. A grid might not have any good trees at all. Or it might have one, or even several. Here is a grid that has exactly one candidate tree. ```text ↓ 1 2 3 4 |----------- 1 | 9 8 7 8 → 2 |[5] 3 2 4 3 | 6 6 7 1 ``` - Row 2 has values 5, 3, 2, and 4. The largest value is 5. - Column 1 has values 9, 5, and 6. The smallest value is 5. So the point at `[2, 1]` (row: 2, column: 1) is a great spot for a tree house. ================================================ FILE: exercises/practice/saddle-points/.docs/introduction.md ================================================ # Introduction You plan to build a tree house in the woods near your house so that you can watch the sun rise and set. You've obtained data from a local survey company that show the height of every tree in each rectangular section of the map. You need to analyze each grid on the map to find good trees for your tree house. A good tree is both: - taller than every tree to the east and west, so that you have the best possible view of the sunrises and sunsets. - shorter than every tree to the north and south, to minimize the amount of tree climbing. ================================================ FILE: exercises/practice/saddle-points/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "saddle-points.ts" ], "test": [ "saddle-points.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Detect saddle points in a matrix.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "J Dalbey's Programming Practice problems", "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" } ================================================ FILE: exercises/practice/saddle-points/.meta/proof.ci.ts ================================================ export function saddlePoints( matrix: number[][] ): Array<{ row: number; column: number }> { const maximumRowValues: number[] = [] const minimumColumnValues: number[] = [] for (let i = 0; i <= matrix.length - 1; i++) { let maximumRowValue = Number.MIN_VALUE for (let j = 0; j < matrix[0].length; j++) { maximumRowValue = Math.max(maximumRowValue, matrix[i][j]) } maximumRowValues.push(maximumRowValue) } for (let i = 0; i <= matrix[0].length - 1; i++) { let minimumColumnValue = Number.MAX_VALUE for (let j = 0; j <= matrix.length - 1; j++) { minimumColumnValue = Math.min(minimumColumnValue, matrix[j][i]) } minimumColumnValues.push(minimumColumnValue) } const resultPoints: Array<{ row: number; column: number }> = [] for (let i = 0; i < maximumRowValues.length; i++) { for (let j = 0; j < minimumColumnValues.length; j++) { if (maximumRowValues[i] === minimumColumnValues[j]) { resultPoints.push({ row: i + 1, column: j + 1 }) } } } return resultPoints } ================================================ FILE: exercises/practice/saddle-points/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [3e374e63-a2e0-4530-a39a-d53c560382bd] description = "Can identify single saddle point" [6b501e2b-6c1f-491f-b1bb-7f278f760534] description = "Can identify that empty matrix has no saddle points" [8c27cc64-e573-4fcb-a099-f0ae863fb02f] description = "Can identify lack of saddle points when there are none" [6d1399bd-e105-40fd-a2c9-c6609507d7a3] description = "Can identify multiple saddle points in a column" [3e81dce9-53b3-44e6-bf26-e328885fd5d1] description = "Can identify multiple saddle points in a row" [88868621-b6f4-4837-bb8b-3fad8b25d46b] description = "Can identify saddle point in bottom right corner" [5b9499ca-fcea-4195-830a-9c4584a0ee79] description = "Can identify saddle points in a non square matrix" [ee99ccd2-a1f1-4283-ad39-f8c70f0cf594] description = "Can identify that saddle points in a single column matrix are those with the minimum value" [63abf709-a84b-407f-a1b3-456638689713] description = "Can identify that saddle points in a single row matrix are those with the maximum value" ================================================ FILE: exercises/practice/saddle-points/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/saddle-points/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/saddle-points/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/saddle-points/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/saddle-points/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/saddle-points/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/saddle-points/package.json ================================================ { "name": "@exercism/typescript-saddle-points", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/saddle-points/saddle-points.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { saddlePoints } from './saddle-points.ts' describe('Saddle Points', () => { it('Can identify single saddle point', () => { const expected = [{ row: 2, column: 1 }] expect( saddlePoints([ [9, 8, 7], [5, 3, 2], [6, 6, 7], ]) ).toEqual(expected) }) xit('Can identify that empty matrix has no saddle points', () => { const expected: number[] = [] expect(saddlePoints([[]])).toEqual(expected) }) xit('Can identify lack of saddle points when there are none', () => { const expected: number[] = [] expect( saddlePoints([ [1, 2, 3], [3, 1, 2], [2, 3, 1], ]) ).toEqual(expected) }) xit('Can identify multiple saddle points in a column', () => { const expected = [ { row: 1, column: 2 }, { row: 2, column: 2 }, { row: 3, column: 2 }, ] expect( saddlePoints([ [4, 5, 4], [3, 5, 5], [1, 5, 4], ]) ).toEqual(expected) }) xit('Can identify multiple saddle points in a row', () => { const expected = [ { row: 2, column: 1 }, { row: 2, column: 2 }, { row: 2, column: 3 }, ] expect( saddlePoints([ [6, 7, 8], [5, 5, 5], [7, 5, 6], ]) ).toEqual(expected) }) xit('Can identify saddle point in bottom right corner', () => { const expected = [{ row: 3, column: 3 }] expect( saddlePoints([ [8, 7, 9], [6, 7, 6], [3, 2, 5], ]) ).toEqual(expected) }) xit('Can identify saddle points in a non square matrix', () => { const expected = [ { row: 1, column: 1 }, { row: 1, column: 3 }, ] expect( saddlePoints([ [3, 1, 3], [3, 2, 4], ]) ).toEqual(expected) }) xit('Can identify that saddle points in a single column matrix are those with the minimum value', () => { const expected = [ { row: 2, column: 1 }, { row: 4, column: 1 }, ] expect(saddlePoints([[2], [1], [4], [1]])).toEqual(expected) }) xit('Can identify that saddle points in a single row matrix are those with the maximum value', () => { const expected = [ { row: 1, column: 2 }, { row: 1, column: 4 }, ] expect(saddlePoints([[2, 5, 3, 5]])).toEqual(expected) }) }) ================================================ FILE: exercises/practice/saddle-points/saddle-points.ts ================================================ export function saddlePoints(/* Parameters go here */) { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/saddle-points/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/saddle-points/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/say/.docs/instructions.md ================================================ # Instructions Given a number, your task is to express it in English words exactly as your friend should say it out loud. Yaʻqūb expects to use numbers from 0 up to 999,999,999,999. Examples: - 0 → zero - 1 → one - 12 → twelve - 123 → one hundred twenty-three - 1,234 → one thousand two hundred thirty-four ================================================ FILE: exercises/practice/say/.docs/introduction.md ================================================ # Introduction Your friend Yaʻqūb works the counter at the busiest deli in town, slicing, weighing, and wrapping orders for a never-ending line of hungry customers. To keep things moving, each customer takes a numbered ticket when they arrive. When it’s time to call the next person, Yaʻqūb reads their number out loud, always in full English words to make sure everyone hears it clearly. ================================================ FILE: exercises/practice/say/.meta/config.json ================================================ { "authors": [ "masters3d" ], "contributors": [ "lukaszklis", "SleeplessByte" ], "files": { "solution": [ "say.ts" ], "test": [ "say.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a number from 0 to 999,999,999,999, spell out that number in English.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "A variation on the JavaRanch CattleDrive, Assignment 4", "source_url": "https://web.archive.org/web/20240907035912/https://coderanch.com/wiki/718804" } ================================================ FILE: exercises/practice/say/.meta/proof.ci.ts ================================================ const numbersMap = new Map([ [0, 'zero'], [1, 'one'], [2, 'two'], [3, 'three'], [4, 'four'], [5, 'five'], [6, 'six'], [7, 'seven'], [8, 'eight'], [9, 'nine'], [10, 'ten'], [11, 'eleven'], [12, 'twelve'], [13, 'thirteen'], [14, 'fourteen'], [15, 'fifteen'], [16, 'sixteen'], [17, 'seventeen'], [18, 'eighteen'], [19, 'nineteen'], [20, 'twenty'], [30, 'thirty'], [40, 'forty'], [50, 'fifty'], [60, 'sixty'], [70, 'seventy'], [80, 'eighty'], [90, 'ninety'], [100, 'hundred'], [1000, 'thousand'], [1000000, 'million'], [1000000000, 'billion'], ]) function divideNoDecimal(input: number, by: number): number { return (input / by) | 0 // this is like a hack to get int width } function classifier(input: number): number { if (input >= 100 && input < 1000) { return 100 } if (input < 1000000 && input >= 1000) { return 1000 } if (input < 1000000000 && input >= 1000000) { return 1000000 } if (input < 1000000000000 && input >= 1000000000) { return 1000000000 } throw new Error( `${classifier.name} only accepts values between 100 to 1000000000000 - 1` ) } function numberGenerator(input: number): string { if (input < 100) { return zeroTo99(input) } const level = classifier(input) const thirdPlace = divideNoDecimal(input, level) const remainder = input % level let builder = '' builder += thirdPlace === 0 ? '' : `${numberGenerator(thirdPlace)} ${numbersMap.get(level)!}` builder += remainder === 0 ? '' : ' ' + `${numberGenerator(remainder)}` return builder } function zeroTo99(input: number): string { if (input > 100) { throw new Error(`${zeroTo99.name} only accepts values 0 to 99`) } if (input <= 20) { return numbersMap.get(input)! } if (input > 20 && input < 100) { const teen = divideNoDecimal(input, 10) * 10 const tens = numbersMap.get(teen)! const remainder = numbersMap.get(input % teen)! if (remainder === 'zero') { return tens } else { return `${tens}-${remainder}` } } return '' } export function sayInEnglish(input: number): string { if (input < 0 || input > 999999999999) { throw new Error('Number must be between 0 and 999,999,999,999.') } return numberGenerator(input) } ================================================ FILE: exercises/practice/say/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [5d22a120-ba0c-428c-bd25-8682235d83e8] description = "zero" [9b5eed77-dbf6-439d-b920-3f7eb58928f6] description = "one" [7c499be1-612e-4096-a5e1-43b2f719406d] description = "fourteen" [f541dd8e-f070-4329-92b4-b7ce2fcf06b4] description = "twenty" [d78601eb-4a84-4bfa-bf0e-665aeb8abe94] description = "twenty-two" [f010d4ca-12c9-44e9-803a-27789841adb1] description = "thirty" [738ce12d-ee5c-4dfb-ad26-534753a98327] description = "ninety-nine" [e417d452-129e-4056-bd5b-6eb1df334dce] description = "one hundred" [d6924f30-80ba-4597-acf6-ea3f16269da8] description = "one hundred twenty-three" [2f061132-54bc-4fd4-b5df-0a3b778959b9] description = "two hundred" [feed6627-5387-4d38-9692-87c0dbc55c33] description = "nine hundred ninety-nine" [3d83da89-a372-46d3-b10d-de0c792432b3] description = "one thousand" [865af898-1d5b-495f-8ff0-2f06d3c73709] description = "one thousand two hundred thirty-four" [b6a3f442-266e-47a3-835d-7f8a35f6cf7f] description = "one million" [2cea9303-e77e-4212-b8ff-c39f1978fc70] description = "one million two thousand three hundred forty-five" [3e240eeb-f564-4b80-9421-db123f66a38f] description = "one billion" [9a43fed1-c875-4710-8286-5065d73b8a9e] description = "a big number" [49a6a17b-084e-423e-994d-a87c0ecc05ef] description = "numbers below zero are out of range" [4d6492eb-5853-4d16-9d34-b0f61b261fd9] description = "numbers above 999,999,999,999 are out of range" ================================================ FILE: exercises/practice/say/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/say/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/say/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/say/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/say/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/say/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/say/package.json ================================================ { "name": "@exercism/typescript-say", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/say/say.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { sayInEnglish } from './say.ts' describe('say', () => { it('zero', () => { expect(sayInEnglish(0)).toBe('zero') }) xit('one', () => { expect(sayInEnglish(1)).toBe('one') }) xit('fourteen', () => { expect(sayInEnglish(14)).toBe('fourteen') }) xit('twenty', () => { expect(sayInEnglish(20)).toBe('twenty') }) xit('twenty-two', () => { expect(sayInEnglish(22)).toBe('twenty-two') }) xit('thirty', () => { expect(sayInEnglish(30)).toBe('thirty') }) xit('ninety-nine', () => { expect(sayInEnglish(99)).toBe('ninety-nine') }) xit('one hundred', () => { expect(sayInEnglish(100)).toBe('one hundred') }) xit('one hundred twenty-three', () => { expect(sayInEnglish(123)).toBe('one hundred twenty-three') }) xit('two hundred', () => { expect(sayInEnglish(200)).toBe('two hundred') }) xit('nine hundred ninety-nine', () => { expect(sayInEnglish(999)).toBe('nine hundred ninety-nine') }) xit('one thousand', () => { expect(sayInEnglish(1000)).toBe('one thousand') }) xit('one thousand two hundred thirty-four', () => { expect(sayInEnglish(1234)).toBe('one thousand two hundred thirty-four') }) xit('one million', () => { expect(sayInEnglish(1000000)).toBe('one million') }) xit('one million two', () => { expect(sayInEnglish(1000002)).toBe('one million two') }) xit('one million two thousand three hundred forty-five', () => { expect(sayInEnglish(1002345)).toBe( 'one million two thousand three hundred forty-five' ) }) xit('one billion', () => { expect(sayInEnglish(1000000000)).toBe('one billion') }) xit('a really big number', () => { let expected = 'nine hundred eighty-seven billion ' expected += 'six hundred fifty-four million ' expected += 'three hundred twenty-one thousand ' expected += 'one hundred twenty-three' expect(sayInEnglish(987654321123)).toBe(expected) }) xit('raises an error below zero', () => { expect(() => { sayInEnglish(-1) }).toThrow('Number must be between 0 and 999,999,999,999.') }) xit('raises an error above 999,999,999,999', () => { expect(() => { sayInEnglish(1000000000000) }).toThrow('Number must be between 0 and 999,999,999,999.') }) }) ================================================ FILE: exercises/practice/say/say.ts ================================================ export function sayInEnglish() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/say/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/say/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/scrabble-score/.docs/instructions.md ================================================ # Instructions Your task is to compute a word's Scrabble score by summing the values of its letters. The letters are valued as follows: | Letter | Value | | ---------------------------- | ----- | | A, E, I, O, U, L, N, R, S, T | 1 | | D, G | 2 | | B, C, M, P | 3 | | F, H, V, W, Y | 4 | | K | 5 | | J, X | 8 | | Q, Z | 10 | For example, the word "cabbage" is worth 14 points: - 3 points for C - 1 point for A - 3 points for B - 3 points for B - 1 point for A - 2 points for G - 1 point for E ================================================ FILE: exercises/practice/scrabble-score/.docs/introduction.md ================================================ # Introduction [Scrabble][wikipedia] is a word game where players place letter tiles on a board to form words. Each letter has a value. A word's score is the sum of its letters' values. [wikipedia]: https://en.wikipedia.org/wiki/Scrabble ================================================ FILE: exercises/practice/scrabble-score/.meta/config.json ================================================ { "authors": [ "zgavin1" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "scrabble-score.ts" ], "test": [ "scrabble-score.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a word, compute the Scrabble score for that word.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Inspired by the Extreme Startup game", "source_url": "https://github.com/rchatley/extreme_startup" } ================================================ FILE: exercises/practice/scrabble-score/.meta/proof.ci.ts ================================================ interface LetterScore { [key: string]: number } const letterScores: LetterScore = { a: 1, e: 1, i: 1, o: 1, u: 1, l: 1, n: 1, r: 1, s: 1, t: 1, d: 2, g: 2, b: 3, c: 3, m: 3, p: 3, f: 4, h: 4, v: 4, w: 4, y: 4, k: 5, j: 8, x: 8, q: 10, z: 10, } export const score = (word: string | undefined): number => { word = word ? word.toLowerCase() : '' let output = 0 word.split('').forEach((letter: string): void => { output += letterScores[letter] }) return output } ================================================ FILE: exercises/practice/scrabble-score/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [f46cda29-1ca5-4ef2-bd45-388a767e3db2] description = "lowercase letter" [f7794b49-f13e-45d1-a933-4e48459b2201] description = "uppercase letter" [eaba9c76-f9fa-49c9-a1b0-d1ba3a5b31fa] description = "valuable letter" [f3c8c94e-bb48-4da2-b09f-e832e103151e] description = "short word" [71e3d8fa-900d-4548-930e-68e7067c4615] description = "short, valuable word" [d3088ad9-570c-4b51-8764-c75d5a430e99] description = "medium word" [fa20c572-ad86-400a-8511-64512daac352] description = "medium, valuable word" [9336f0ba-9c2b-4fa0-bd1c-2e2d328cf967] description = "long, mixed-case word" [1e34e2c3-e444-4ea7-b598-3c2b46fd2c10] description = "english-like word" [4efe3169-b3b6-4334-8bae-ff4ef24a7e4f] description = "empty input" [3b305c1c-f260-4e15-a5b5-cb7d3ea7c3d7] description = "entire alphabet available" ================================================ FILE: exercises/practice/scrabble-score/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/scrabble-score/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/scrabble-score/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/scrabble-score/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/scrabble-score/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/scrabble-score/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/scrabble-score/package.json ================================================ { "name": "@exercism/typescript-scrabble-score", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/scrabble-score/scrabble-score.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { score } from './scrabble-score.ts' describe('Scrabble', () => { it('scores an empty word as zero', () => expect(score('')).toEqual(0)) xit('scores a null as zero', () => expect(score(undefined)).toEqual(0)) xit('scores a very short word', () => expect(score('a')).toEqual(1)) xit('scores the word by the number of letters', () => expect(score('street')).toEqual(6)) xit('scores more complicated words with more', () => expect(score('quirky')).toEqual(22)) xit('scores case insensitive words', () => expect(score('OXYPHENBUTAZONE')).toEqual(41)) }) ================================================ FILE: exercises/practice/scrabble-score/scrabble-score.ts ================================================ export function score() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/scrabble-score/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/scrabble-score/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/secret-handshake/.docs/instructions.md ================================================ # Instructions Your task is to convert a number between 1 and 31 to a sequence of actions in the secret handshake. The sequence of actions is chosen by looking at the rightmost five digits of the number once it's been converted to binary. Start at the right-most digit and move left. The actions for each number place are: ```plaintext 00001 = wink 00010 = double blink 00100 = close your eyes 01000 = jump 10000 = Reverse the order of the operations in the secret handshake. ``` Let's use the number `9` as an example: - 9 in binary is `1001`. - The digit that is farthest to the right is 1, so the first action is `wink`. - Going left, the next digit is 0, so there is no double-blink. - Going left again, the next digit is 0, so you leave your eyes open. - Going left again, the next digit is 1, so you jump. That was the last digit, so the final code is: ```plaintext wink, jump ``` Given the number 26, which is `11010` in binary, we get the following actions: - double blink - jump - reverse actions The secret handshake for 26 is therefore: ```plaintext jump, double blink ``` ~~~~exercism/note If you aren't sure what binary is or how it works, check out [this binary tutorial][intro-to-binary]. [intro-to-binary]: https://medium.com/basecs/bits-bytes-building-with-binary-13cb4289aafa ~~~~ ================================================ FILE: exercises/practice/secret-handshake/.docs/introduction.md ================================================ # Introduction You are starting a secret coding club with some friends and friends-of-friends. Not everyone knows each other, so you and your friends have decided to create a secret handshake that you can use to recognize that someone is a member. You don't want anyone who isn't in the know to be able to crack the code. You've designed the code so that one person says a number between 1 and 31, and the other person turns it into a series of actions. ================================================ FILE: exercises/practice/secret-handshake/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "secret-handshake.ts" ], "test": [ "secret-handshake.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a decimal number, convert it to the appropriate sequence of events for a secret handshake.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Bert, in Mary Poppins", "source_url": "https://www.imdb.com/title/tt0058331/quotes/?item=qt0437047" } ================================================ FILE: exercises/practice/secret-handshake/.meta/proof.ci.ts ================================================ const COMMANDS = ['wink', 'double blink', 'close your eyes', 'jump'] as const type Command = (typeof COMMANDS)[number] type Commands = Command[] export const commands = (handshake: number): Commands => { const shakeWith = COMMANDS.filter((_, i) => handshake & Math.pow(2, i)) if (handshake & Math.pow(2, 4)) { shakeWith.reverse() } return shakeWith } ================================================ FILE: exercises/practice/secret-handshake/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [b8496fbd-6778-468c-8054-648d03c4bb23] description = "wink for 1" [83ec6c58-81a9-4fd1-bfaf-0160514fc0e3] description = "double blink for 10" [0e20e466-3519-4134-8082-5639d85fef71] description = "close your eyes for 100" [b339ddbb-88b7-4b7d-9b19-4134030d9ac0] description = "jump for 1000" [40499fb4-e60c-43d7-8b98-0de3ca44e0eb] description = "combine two actions" [9730cdd5-ef27-494b-afd3-5c91ad6c3d9d] description = "reverse two actions" [0b828205-51ca-45cd-90d5-f2506013f25f] description = "reversing one action gives the same action" [9949e2ac-6c9c-4330-b685-2089ab28b05f] description = "reversing no actions still gives no actions" [23fdca98-676b-4848-970d-cfed7be39f81] description = "all possible actions" [ae8fe006-d910-4d6f-be00-54b7c3799e79] description = "reverse all possible actions" [3d36da37-b31f-4cdb-a396-d93a2ee1c4a5] description = "do nothing for zero" ================================================ FILE: exercises/practice/secret-handshake/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/secret-handshake/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/secret-handshake/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/secret-handshake/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/secret-handshake/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/secret-handshake/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/secret-handshake/package.json ================================================ { "name": "@exercism/typescript-secret-handshake", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/secret-handshake/secret-handshake.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { commands } from './secret-handshake.ts' describe('Secret Handshake', () => { describe('Create A Handshake For A Number', () => { it('wink for 1', () => { expect(commands(1)).toEqual(['wink']) }) xit('double blink for 10', () => { expect(commands(2)).toEqual(['double blink']) }) xit('close your eyes for 100', () => { expect(commands(4)).toEqual(['close your eyes']) }) xit('jump for 1000', () => { expect(commands(8)).toEqual(['jump']) }) xit('combine two actions', () => { expect(commands(3)).toEqual(['wink', 'double blink']) }) xit('reverse two actions', () => { expect(commands(19)).toEqual(['double blink', 'wink']) }) xit('reversing one action gives the same action', () => { expect(commands(24)).toEqual(['jump']) }) xit('reversing no actions still gives no actions', () => { expect(commands(16)).toEqual([]) }) xit('all possible actions', () => { expect(commands(15)).toEqual([ 'wink', 'double blink', 'close your eyes', 'jump', ]) }) xit('reverse all possible actions', () => { expect(commands(31)).toEqual([ 'jump', 'close your eyes', 'double blink', 'wink', ]) }) xit('do nothing for zero', () => { expect(commands(0)).toEqual([]) }) }) }) ================================================ FILE: exercises/practice/secret-handshake/secret-handshake.ts ================================================ export function commands() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/secret-handshake/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/secret-handshake/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/series/.docs/instructions.md ================================================ # Instructions Given a string of digits, output all the contiguous substrings of length `n` in that string in the order that they appear. For example, the string "49142" has the following 3-digit series: - "491" - "914" - "142" And the following 4-digit series: - "4914" - "9142" And if you ask for a 6-digit series from a 5-digit string, you deserve whatever you get. Note that these series are only required to occupy _adjacent positions_ in the input; the digits need not be _numerically consecutive_. ================================================ FILE: exercises/practice/series/.meta/config.json ================================================ { "authors": [ "foxfriends" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "series.ts" ], "test": [ "series.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a string of digits, output all the contiguous substrings of length `n` in that string.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "A subset of the Problem 8 at Project Euler", "source_url": "https://projecteuler.net/problem=8" } ================================================ FILE: exercises/practice/series/.meta/proof.ci.ts ================================================ export class Series { private readonly numberString: string private readonly digits: number[] constructor(numberString: string) { if (!numberString) { throw new Error('series cannot be empty') } this.numberString = numberString this.digits = this.getDigits() } private getDigits(): number[] { return [...this.numberString].map((digit) => parseInt(digit, 10)) } public slices(sliceSize: number): number[][] { const result: number[][] = [] let slice: number[] = [] if (sliceSize < 0) { throw new Error('slice length cannot be negative') } if (sliceSize === 0) { throw new Error('slice length cannot be zero') } if (sliceSize > this.digits.length) { throw new Error('slice length cannot be greater than series length') } for (let i = 0; i < this.digits.length - sliceSize + 1; i += 1) { for (let j = 0; j < sliceSize; j += 1) { slice.push(this.digits[i + j]) } result.push(slice) slice = [] } return result } } ================================================ FILE: exercises/practice/series/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [7ae7a46a-d992-4c2a-9c15-a112d125ebad] description = "slices of one from one" [3143b71d-f6a5-4221-aeae-619f906244d2] description = "slices of one from two" [dbb68ff5-76c5-4ccd-895a-93dbec6d5805] description = "slices of two" [19bbea47-c987-4e11-a7d1-e103442adf86] description = "slices of two overlap" [8e17148d-ba0a-4007-a07f-d7f87015d84c] description = "slices can include duplicates" [bd5b085e-f612-4f81-97a8-6314258278b0] description = "slices of a long series" [6d235d85-46cf-4fae-9955-14b6efef27cd] description = "slice length is too large" [d7957455-346d-4e47-8e4b-87ed1564c6d7] description = "slice length is way too large" [d34004ad-8765-4c09-8ba1-ada8ce776806] description = "slice length cannot be zero" [10ab822d-8410-470a-a85d-23fbeb549e54] description = "slice length cannot be negative" [c7ed0812-0e4b-4bf3-99c4-28cbbfc246a2] description = "empty series is invalid" ================================================ FILE: exercises/practice/series/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/series/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/series/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/series/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/series/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/series/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/series/package.json ================================================ { "name": "@exercism/typescript-series", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/series/series.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { Series } from './series.ts' describe('Series', () => { it('slices of one from one', () => { expect(new Series('1').slices(1)).toEqual([[1]]) }) xit('slices of one from two', () => { expect(new Series('12').slices(1)).toEqual([[1], [2]]) }) xit('slices of two', () => { expect(new Series('35').slices(2)).toEqual([[3, 5]]) }) xit('slices of two overlap', () => { expect(new Series('9142').slices(2)).toEqual([ [9, 1], [1, 4], [4, 2], ]) }) xit('slices can include duplicates', () => { expect(new Series('777777').slices(3)).toEqual([ [7, 7, 7], [7, 7, 7], [7, 7, 7], [7, 7, 7], ]) }) xit('slices of long series', () => { expect(new Series('918493904243').slices(5)).toEqual([ [9, 1, 8, 4, 9], [1, 8, 4, 9, 3], [8, 4, 9, 3, 9], [4, 9, 3, 9, 0], [9, 3, 9, 0, 4], [3, 9, 0, 4, 2], [9, 0, 4, 2, 4], [0, 4, 2, 4, 3], ]) }) xit('slice length is too large', () => { expect(() => { new Series('12345').slices(6) }).toThrow(new Error('slice length cannot be greater than series length')) }) xit('slice length is way too large', () => { expect(() => { new Series('12345').slices(42) }).toThrow(new Error('slice length cannot be greater than series length')) }) xit('slice length cannot be zero', () => { expect(() => { new Series('12345').slices(0) }).toThrow(new Error('slice length cannot be zero')) }) xit('slice length cannot be negative', () => { expect(() => { new Series('123').slices(-1) }).toThrow(new Error('slice length cannot be negative')) }) xit('empty series is invalid', () => { expect(() => { new Series('').slices(1) }).toThrow(new Error('series cannot be empty')) }) }) ================================================ FILE: exercises/practice/series/series.ts ================================================ export class Series { constructor(series: unknown) { throw new Error('Remove this line and implement the function') } slices(sliceLength: unknown): unknown { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/series/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/series/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/sieve/.docs/instructions.md ================================================ # Instructions Your task is to create a program that implements the Sieve of Eratosthenes algorithm to find all prime numbers less than or equal to a given number. A prime number is a number larger than 1 that is only divisible by 1 and itself. For example, 2, 3, 5, 7, 11, and 13 are prime numbers. By contrast, 6 is _not_ a prime number as it not only divisible by 1 and itself, but also by 2 and 3. To use the Sieve of Eratosthenes, first, write out all the numbers from 2 up to and including your given number. Then, follow these steps: 1. Find the next unmarked number (skipping over marked numbers). This is a prime number. 2. Mark all the multiples of that prime number as **not** prime. Repeat the steps until you've gone through every number. At the end, all the unmarked numbers are prime. ~~~~exercism/note The Sieve of Eratosthenes marks off multiples of each prime using addition (repeatedly adding the prime) or multiplication (directly computing its multiples), rather than checking each number for divisibility. The tests don't check that you've implemented the algorithm, only that you've come up with the correct primes. ~~~~ ## Example Let's say you're finding the primes less than or equal to 10. - Write out 2, 3, 4, 5, 6, 7, 8, 9, 10, leaving them all unmarked. ```text 2 3 4 5 6 7 8 9 10 ``` - 2 is unmarked and is therefore a prime. Mark 4, 6, 8 and 10 as "not prime". ```text 2 3 [4] 5 [6] 7 [8] 9 [10] ↑ ``` - 3 is unmarked and is therefore a prime. Mark 6 and 9 as not prime _(marking 6 is optional - as it's already been marked)_. ```text 2 3 [4] 5 [6] 7 [8] [9] [10] ↑ ``` - 4 is marked as "not prime", so we skip over it. ```text 2 3 [4] 5 [6] 7 [8] [9] [10] ↑ ``` - 5 is unmarked and is therefore a prime. Mark 10 as not prime _(optional - as it's already been marked)_. ```text 2 3 [4] 5 [6] 7 [8] [9] [10] ↑ ``` - 6 is marked as "not prime", so we skip over it. ```text 2 3 [4] 5 [6] 7 [8] [9] [10] ↑ ``` - 7 is unmarked and is therefore a prime. ```text 2 3 [4] 5 [6] 7 [8] [9] [10] ↑ ``` - 8 is marked as "not prime", so we skip over it. ```text 2 3 [4] 5 [6] 7 [8] [9] [10] ↑ ``` - 9 is marked as "not prime", so we skip over it. ```text 2 3 [4] 5 [6] 7 [8] [9] [10] ↑ ``` - 10 is marked as "not prime", so we stop as there are no more numbers to check. ```text 2 3 [4] 5 [6] 7 [8] [9] [10] ↑ ``` You've examined all the numbers and found that 2, 3, 5, and 7 are still unmarked, meaning they're the primes less than or equal to 10. ================================================ FILE: exercises/practice/sieve/.docs/introduction.md ================================================ # Introduction You bought a big box of random computer parts at a garage sale. You've started putting the parts together to build custom computers. You want to test the performance of different combinations of parts, and decide to create your own benchmarking program to see how your computers compare. You choose the famous "Sieve of Eratosthenes" algorithm, an ancient algorithm, but one that should push your computers to the limits. ================================================ FILE: exercises/practice/sieve/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "sieve.ts" ], "test": [ "sieve.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Use the Sieve of Eratosthenes to find all the primes from 2 up to a given number.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": true, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Sieve of Eratosthenes at Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes" } ================================================ FILE: exercises/practice/sieve/.meta/proof.ci.ts ================================================ export function primes(limit: number): number[] { if (limit === 2) { return [limit] } const sieve: boolean[] = [] for (let i = 0; i < limit; i++) { sieve[i] = true } const found: number[] = [] const maxCandidate = Math.floor(Math.sqrt(limit)) for (let candidate = 2; candidate <= maxCandidate + 1; candidate++) { if (!sieve[candidate - 1]) { continue } found.push(candidate) let multiple = candidate * candidate while (multiple <= limit) { sieve[multiple - 1] = false multiple += candidate } } for (let i = maxCandidate + 1; i <= limit; i++) { if (sieve[i - 1]) { found.push(i) } } return found } ================================================ FILE: exercises/practice/sieve/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [88529125-c4ce-43cc-bb36-1eb4ddd7b44f] description = "no primes under two" [4afe9474-c705-4477-9923-840e1024cc2b] description = "find first prime" [974945d8-8cd9-4f00-9463-7d813c7f17b7] description = "find primes up to 10" [2e2417b7-3f3a-452a-8594-b9af08af6d82] description = "limit is prime" [92102a05-4c7c-47de-9ed0-b7d5fcd00f21] description = "find primes up to 1000" ================================================ FILE: exercises/practice/sieve/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/sieve/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/sieve/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/sieve/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/sieve/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/sieve/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/sieve/package.json ================================================ { "name": "@exercism/typescript-sieve", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/sieve/sieve.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { primes } from './sieve.ts' describe('Sieve', () => { it('no primes under two', () => { const expected: number[] = [] expect(primes(1)).toEqual(expected) }) xit('find first prime', () => { const expected = [2] expect(primes(2)).toEqual(expected) }) xit('find primes up to 10', () => { const expected = [2, 3, 5, 7] expect(primes(10)).toEqual(expected) }) xit('limit is prime', () => { const expected = [2, 3, 5, 7, 11, 13] expect(primes(13)).toEqual(expected) }) xit('find primes up to 1000', () => { const expected: number[] = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, ] expect(primes(1000)).toEqual(expected) }) }) ================================================ FILE: exercises/practice/sieve/sieve.ts ================================================ export function primes() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/sieve/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/sieve/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/simple-cipher/.docs/instructions.md ================================================ # Instructions Create an implementation of the [Vigenère cipher][wiki]. The Vigenère cipher is a simple substitution cipher. ## Cipher terminology A cipher is an algorithm used to encrypt, or encode, a string. The unencrypted string is called the _plaintext_ and the encrypted string is called the _ciphertext_. Converting plaintext to ciphertext is called _encoding_ while the reverse is called _decoding_. In a _substitution cipher_, each plaintext letter is replaced with a ciphertext letter which is computed with the help of a _key_. (Note, it is possible for replacement letter to be the same as the original letter.) ## Encoding details In this cipher, the key is a series of lowercase letters, such as `"abcd"`. Each letter of the plaintext is _shifted_ or _rotated_ by a distance based on a corresponding letter in the key. An `"a"` in the key means a shift of 0 (that is, no shift). A `"b"` in the key means a shift of 1. A `"c"` in the key means a shift of 2, and so on. The first letter of the plaintext uses the first letter of the key, the second letter of the plaintext uses the second letter of the key and so on. If you run out of letters in the key before you run out of letters in the plaintext, start over from the start of the key again. If the key only contains one letter, such as `"dddddd"`, then all letters of the plaintext are shifted by the same amount (three in this example), which would make this the same as a rotational cipher or shift cipher (sometimes called a Caesar cipher). For example, the plaintext `"iamapandabear"` would become `"ldpdsdqgdehdu"`. If the key only contains the letter `"a"` (one or more times), the shift distance is zero and the ciphertext is the same as the plaintext. Usually the key is more complicated than that, though! If the key is `"abcd"` then letters of the plaintext would be shifted by a distance of 0, 1, 2, and 3. If the plaintext is `"hello"`, we need 5 shifts so the key would wrap around, giving shift distances of 0, 1, 2, 3, and 0. Applying those shifts to the letters of `"hello"` we get `"hfnoo"`. ## Random keys If no key is provided, generate a key which consists of at least 100 random lowercase letters from the Latin alphabet. [wiki]: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher ================================================ FILE: exercises/practice/simple-cipher/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "daveyarwood", "janczer", "masters3d", "SleeplessByte", "tekerson" ], "files": { "solution": [ "simple-cipher.ts" ], "test": [ "simple-cipher.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Implement the Vigenère cipher, a simple substitution cipher.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Substitution Cipher at Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Substitution_cipher" } ================================================ FILE: exercises/practice/simple-cipher/.meta/proof.ci.ts ================================================ export class SimpleCipher { public key: string constructor(key?: string) { if (key === undefined) { this.key = '' for (let i = 0; i < 100; i++) { this.key += String.fromCharCode(Math.random() * 26 + 97) } } else if (!/^[a-z]+$/.test(key)) { throw new Error('Bad key') } else { this.key = key } } public encode(decodedMessage: string): string { let encodedMessage = '' for (let i = 0; i < decodedMessage.length; i++) { let encodedChar = String.fromCharCode( decodedMessage.charCodeAt(i) + (this.key.charCodeAt(i % this.key.length) - 97) ) if (encodedChar.charCodeAt(0) > 122) { encodedChar = String.fromCharCode(encodedChar.charCodeAt(0) - 26) } encodedMessage += encodedChar } return encodedMessage } public decode(encodedMessage: string): string { let decodedMessage = '' for (let i = 0; i < encodedMessage.length; i++) { let decodedChar = String.fromCharCode( encodedMessage.charCodeAt(i) - (this.key.charCodeAt(i % this.key.length) - 97) ) if (decodedChar.charCodeAt(0) < 97) { decodedChar = String.fromCharCode(decodedChar.charCodeAt(0) + 26) } decodedMessage += decodedChar } return decodedMessage } } ================================================ FILE: exercises/practice/simple-cipher/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [b8bdfbe1-bea3-41bb-a999-b41403f2b15d] description = "Can encode" [3dff7f36-75db-46b4-ab70-644b3f38b81c] description = "Can decode" [8143c684-6df6-46ba-bd1f-dea8fcb5d265] description = "Is reversible. I.e., if you apply decode in a encoded result, you must see the same plaintext encode parameter as a result of the decode method" [defc0050-e87d-4840-85e4-51a1ab9dd6aa] description = "Key is made only of lowercase letters" [565e5158-5b3b-41dd-b99d-33b9f413c39f] description = "Can encode" [d44e4f6a-b8af-4e90-9d08-fd407e31e67b] description = "Can decode" [70a16473-7339-43df-902d-93408c69e9d1] description = "Is reversible. I.e., if you apply decode in a encoded result, you must see the same plaintext encode parameter as a result of the decode method" [69a1458b-92a6-433a-a02d-7beac3ea91f9] description = "Can double shift encode" [21d207c1-98de-40aa-994f-86197ae230fb] description = "Can wrap on encode" [a3d7a4d7-24a9-4de6-bdc4-a6614ced0cb3] description = "Can wrap on decode" [e31c9b8c-8eb6-45c9-a4b5-8344a36b9641] description = "Can encode messages longer than the key" [93cfaae0-17da-4627-9a04-d6d1e1be52e3] description = "Can decode messages longer than the key" ================================================ FILE: exercises/practice/simple-cipher/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/simple-cipher/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/simple-cipher/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/simple-cipher/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/simple-cipher/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/simple-cipher/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/simple-cipher/package.json ================================================ { "name": "@exercism/typescript-simple-cipher", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/simple-cipher/simple-cipher.test.ts ================================================ import { describe, xdescribe, it, expect, xit } from '@jest/globals' import { SimpleCipher } from './simple-cipher.ts' describe('Random key generation', () => { xit('generates keys at random', () => { // Strictly speaking, this is difficult to test with 100% certainty. // But, if you have a generator that generates 100-character-long // strings of lowercase letters at random, the odds of two consecutively // generated keys being identical are astronomically low. expect(new SimpleCipher().key).not.toEqual(new SimpleCipher().key) }) }) xdescribe('Random key cipher', () => { const simpleCipher = new SimpleCipher() it('has a key made of letters', () => { expect(simpleCipher.key).toMatch(/^[a-z]+$/) }) xit('has a key that is at least 100 characters long', () => { expect(simpleCipher.key.length).toBeGreaterThanOrEqual(100) }) // Here we take advantage of the fact that plaintext of "aaa..." // outputs the key. This is a critical problem with shift ciphers, some // characters will always output the key verbatim. xit('can encode', () => { expect(simpleCipher.encode('aaaaaaaaaa')).toEqual( simpleCipher.key.substring(0, 10) ) }) xit('can decode', () => { expect(simpleCipher.decode(simpleCipher.key.substring(0, 10))).toEqual( 'aaaaaaaaaa' ) }) xit('is reversible', () => { const plaintext = 'abcdefghij' expect(simpleCipher.decode(simpleCipher.encode(plaintext))).toEqual( plaintext ) }) }) xdescribe('Substitution cipher', () => { const key = 'abcdefghij' const simpleCipher = new SimpleCipher(key) xit('keeps the submitted key', () => { expect(simpleCipher.key).toEqual(key) }) xit('can encode', () => { expect(simpleCipher.encode('aaaaaaaaaa')).toEqual('abcdefghij') }) xit('can decode', () => { expect(simpleCipher.decode('abcdefghij')).toEqual('aaaaaaaaaa') }) xit('is reversible', () => { expect(simpleCipher.decode(simpleCipher.encode('abcdefghij'))).toEqual( 'abcdefghij' ) }) xit(': double shift encode', () => { expect(new SimpleCipher('iamapandabear').encode('iamapandabear')).toEqual( 'qayaeaagaciai' ) }) xit('can wrap on encode', () => { expect(simpleCipher.encode('zzzzzzzzzz')).toEqual('zabcdefghi') }) xit('can wrap on decode', () => { expect(simpleCipher.decode('zabcdefghi')).toEqual('zzzzzzzzzz') }) xit('can encode messages longer than the key"', () => { expect(new SimpleCipher('abc').encode('iamapandabear')).toEqual( 'iboaqcnecbfcr' ) }) xit('can decode messages longer than the key', () => { expect(new SimpleCipher('abc').decode('iboaqcnecbfcr')).toEqual( 'iamapandabear' ) }) }) ================================================ FILE: exercises/practice/simple-cipher/simple-cipher.ts ================================================ export class SimpleCipher { encode(/* Parameters go here */) { throw new Error('Remove this line and implement the function') } decode(/* Parameters go here */) { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/simple-cipher/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/simple-cipher/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/space-age/.docs/instructions.md ================================================ # Instructions Given an age in seconds, calculate how old someone would be on a planet in our Solar System. One Earth year equals 365.25 Earth days, or 31,557,600 seconds. If you were told someone was 1,000,000,000 seconds old, their age would be 31.69 Earth-years. For the other planets, you have to account for their orbital period in Earth Years: | Planet | Orbital period in Earth Years | | ------- | ----------------------------- | | Mercury | 0.2408467 | | Venus | 0.61519726 | | Earth | 1.0 | | Mars | 1.8808158 | | Jupiter | 11.862615 | | Saturn | 29.447498 | | Uranus | 84.016846 | | Neptune | 164.79132 | ~~~~exercism/note The actual length of one complete orbit of the Earth around the sun is closer to 365.256 days (1 sidereal year). The Gregorian calendar has, on average, 365.2425 days. While not entirely accurate, 365.25 is the value used in this exercise. See [Year on Wikipedia][year] for more ways to measure a year. [year]: https://en.wikipedia.org/wiki/Year#Summary ~~~~ ================================================ FILE: exercises/practice/space-age/.docs/introduction.md ================================================ # Introduction The year is 2525 and you've just embarked on a journey to visit all planets in the Solar System (Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus and Neptune). The first stop is Mercury, where customs require you to fill out a form (bureaucracy is apparently _not_ Earth-specific). As you hand over the form to the customs officer, they scrutinize it and frown. "Do you _really_ expect me to believe you're just 50 years old? You must be closer to 200 years old!" Amused, you wait for the customs officer to start laughing, but they appear to be dead serious. You realize that you've entered your age in _Earth years_, but the officer expected it in _Mercury years_! As Mercury's orbital period around the sun is significantly shorter than Earth, you're actually a lot older in Mercury years. After some quick calculations, you're able to provide your age in Mercury Years. The customs officer smiles, satisfied, and waves you through. You make a mental note to pre-calculate your planet-specific age _before_ future customs checks, to avoid such mix-ups. ~~~~exercism/note If you're wondering why Pluto didn't make the cut, go watch [this YouTube video][pluto-video]. [pluto-video]: https://www.youtube.com/watch?v=Z_2gbGXzFbs ~~~~ ================================================ FILE: exercises/practice/space-age/.meta/config.json ================================================ { "authors": [ "masters3d" ], "contributors": [ "lukaszklis", "SleeplessByte" ], "files": { "solution": [ "space-age.ts" ], "test": [ "space-age.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given an age in seconds, calculate how old someone is in terms of a given planet's solar years.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Partially inspired by Chapter 1 in Chris Pine's online Learn to Program tutorial.", "source_url": "https://pine.fm/LearnToProgram/chap_01.html" } ================================================ FILE: exercises/practice/space-age/.meta/proof.ci.ts ================================================ const EARTH_TO_OTHER_PLANETS = { mercury: 0.2408467, venus: 0.61519726, earth: 1, mars: 1.8808158, jupiter: 11.862615, saturn: 29.447498, uranus: 84.016846, neptune: 164.79132, } export const age = ( planet: keyof typeof EARTH_TO_OTHER_PLANETS, seconds: number ): number => { const earthYears = seconds / 31557600 const years = earthYears / EARTH_TO_OTHER_PLANETS[planet] return Number(years.toFixed(2)) } ================================================ FILE: exercises/practice/space-age/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [84f609af-5a91-4d68-90a3-9e32d8a5cd34] description = "age on Earth" [ca20c4e9-6054-458c-9312-79679ffab40b] description = "age on Mercury" [502c6529-fd1b-41d3-8fab-65e03082b024] description = "age on Venus" [9ceadf5e-a0d5-4388-9d40-2c459227ceb8] description = "age on Mars" [42927dc3-fe5e-4f76-a5b5-f737fc19bcde] description = "age on Jupiter" [8469b332-7837-4ada-b27c-00ee043ebcad] description = "age on Saturn" [999354c1-76f8-4bb5-a672-f317b6436743] description = "age on Uranus" [80096d30-a0d4-4449-903e-a381178355d8] description = "age on Neptune" [57b96e2a-1178-40b7-b34d-f3c9c34e4bf4] description = "invalid planet causes error" include = false ================================================ FILE: exercises/practice/space-age/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/space-age/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/space-age/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/space-age/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/space-age/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/space-age/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/space-age/package.json ================================================ { "name": "@exercism/typescript-space-age", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/space-age/space-age.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { age } from './space-age.ts' describe('Space Age', () => { it('age on Earth', () => { expect(age('earth', 1000000000)).toEqual(31.69) }) xit('age on Mercury', () => { expect(age('mercury', 2134835688)).toEqual(280.88) }) xit('age on Venus', () => { expect(age('venus', 189839836)).toEqual(9.78) }) xit('age on Mars', () => { expect(age('mars', 2129871239)).toEqual(35.88) }) xit('age on Jupiter', () => { expect(age('jupiter', 901876382)).toEqual(2.41) }) xit('age on Saturn', () => { expect(age('saturn', 2000000000)).toEqual(2.15) }) xit('age on Uranus', () => { expect(age('uranus', 1210123456)).toEqual(0.46) }) xit('age on Neptune', () => { expect(age('neptune', 1821023456)).toEqual(0.35) }) }) ================================================ FILE: exercises/practice/space-age/space-age.ts ================================================ export function age(planet: unknown, seconds: unknown): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/space-age/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/space-age/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/spiral-matrix/.docs/instructions.md ================================================ # Instructions Your task is to return a square matrix of a given size. The matrix should be filled with natural numbers, starting from 1 in the top-left corner, increasing in an inward, clockwise spiral order, like these examples: ## Examples ### Spiral matrix of size 3 ```text 1 2 3 8 9 4 7 6 5 ``` ### Spiral matrix of size 4 ```text 1 2 3 4 12 13 14 5 11 16 15 6 10 9 8 7 ``` ================================================ FILE: exercises/practice/spiral-matrix/.docs/introduction.md ================================================ # Introduction In a small village near an ancient forest, there was a legend of a hidden treasure buried deep within the woods. Despite numerous attempts, no one had ever succeeded in finding it. This was about to change, however, thanks to a young explorer named Elara. She had discovered an old document containing instructions on how to locate the treasure. Using these instructions, Elara was able to draw a map that revealed the path to the treasure. To her surprise, the path followed a peculiar clockwise spiral. It was no wonder no one had been able to find the treasure before! With the map in hand, Elara embarks on her journey to uncover the hidden treasure. ================================================ FILE: exercises/practice/spiral-matrix/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "spiral-matrix.ts" ], "test": [ "spiral-matrix.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given the size, return a square matrix of numbers in spiral order.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Reddit r/dailyprogrammer challenge #320 [Easy] Spiral Ascension.", "source_url": "https://web.archive.org/web/20230607064729/https://old.reddit.com/r/dailyprogrammer/comments/6i60lr/20170619_challenge_320_easy_spiral_ascension/" } ================================================ FILE: exercises/practice/spiral-matrix/.meta/proof.ci.ts ================================================ export function ofSize(size: number): number[][] { const spiral = Array(size) .fill(Array()) .map(() => Array(size).fill(0)) const totalNumbers = size ** 2 let currentNumber = 1 let topLeft = 0 let bottomRight = size - 1 while (currentNumber <= totalNumbers) { for (let x = topLeft; x <= bottomRight; x++) { spiral[topLeft][x] = currentNumber++ } for (let y = topLeft + 1; y <= bottomRight; y++) { spiral[y][bottomRight] = currentNumber++ } for (let x = bottomRight - 1; x >= topLeft; x--) { spiral[bottomRight][x] = currentNumber++ } for (let y = bottomRight - 1; y >= topLeft + 1; y--) { spiral[y][topLeft] = currentNumber++ } topLeft++ bottomRight-- } return spiral } ================================================ FILE: exercises/practice/spiral-matrix/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [8f584201-b446-4bc9-b132-811c8edd9040] description = "empty spiral" [e40ae5f3-e2c9-4639-8116-8a119d632ab2] description = "trivial spiral" [cf05e42d-eb78-4098-a36e-cdaf0991bc48] description = "spiral of size 2" [1c475667-c896-4c23-82e2-e033929de939] description = "spiral of size 3" [05ccbc48-d891-44f5-9137-f4ce462a759d] description = "spiral of size 4" [f4d2165b-1738-4e0c-bed0-c459045ae50d] description = "spiral of size 5" ================================================ FILE: exercises/practice/spiral-matrix/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/spiral-matrix/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/spiral-matrix/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/spiral-matrix/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/spiral-matrix/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/spiral-matrix/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/spiral-matrix/package.json ================================================ { "name": "@exercism/typescript-spiral-matrix", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/spiral-matrix/spiral-matrix.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { ofSize } from './spiral-matrix.ts' describe('Spiral Matrix', () => { it('empty spiral', () => { const expected: number[][] = [] const actual = ofSize(0) expect(actual).toEqual(expected) }) xit('trivial spiral', () => { const expected = [[1]] const actual = ofSize(1) expect(actual).toEqual(expected) }) xit('spiral of size 2', () => { const expected = [ [1, 2], [4, 3], ] const actual = ofSize(2) expect(actual).toEqual(expected) }) xit('spiral of size 3', () => { const expected = [ [1, 2, 3], [8, 9, 4], [7, 6, 5], ] const actual = ofSize(3) expect(actual).toEqual(expected) }) xit('spiral of size 4', () => { const expected = [ [1, 2, 3, 4], [12, 13, 14, 5], [11, 16, 15, 6], [10, 9, 8, 7], ] const actual = ofSize(4) expect(actual).toEqual(expected) }) xit('spiral of size 5', () => { const expected = [ [1, 2, 3, 4, 5], [16, 17, 18, 19, 6], [15, 24, 25, 20, 7], [14, 23, 22, 21, 8], [13, 12, 11, 10, 9], ] const actual = ofSize(5) expect(expected).toEqual(actual) }) }) ================================================ FILE: exercises/practice/spiral-matrix/spiral-matrix.ts ================================================ export function ofSize() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/spiral-matrix/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/spiral-matrix/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/split-second-stopwatch/.docs/instructions.md ================================================ # Instructions Your task is to build a stopwatch to keep precise track of lap times. The stopwatch uses four commands (start, stop, lap, and reset) to keep track of: 1. The current lap's tracked time 2. Previously recorded lap times What commands can be used depends on which state the stopwatch is in: 1. Ready: initial state 2. Running: tracking time 3. Stopped: not tracking time | Command | Begin state | End state | Effect | | ------- | ----------- | --------- | -------------------------------------------------------- | | Start | Ready | Running | Start tracking time | | Start | Stopped | Running | Resume tracking time | | Stop | Running | Stopped | Stop tracking time | | Lap | Running | Running | Add current lap to previous laps, then reset current lap | | Reset | Stopped | Ready | Reset current lap and clear previous laps | ================================================ FILE: exercises/practice/split-second-stopwatch/.docs/introduction.md ================================================ # Introduction You've always run for the thrill of it — no schedules, no timers, just the sound of your feet on the pavement. But now that you've joined a competitive running crew, things are getting serious. Training sessions are timed to the second, and every split second counts. To keep pace, you've picked up the _Split-Second Stopwatch_ — a sleek, high-tech gadget that's about to become your new best friend. ================================================ FILE: exercises/practice/split-second-stopwatch/.meta/config.json ================================================ { "authors": [ "BNAndras" ], "files": { "solution": [ "split-second-stopwatch.ts" ], "test": [ "split-second-stopwatch.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Keep track of time through a digital stopwatch.", "source": "Erik Schierboom", "source_url": "https://github.com/exercism/problem-specifications/pull/2547" } ================================================ FILE: exercises/practice/split-second-stopwatch/.meta/proof.ci.ts ================================================ export type State = 'ready' | 'running' | 'stopped' export class SplitSecondStopwatch { private _state: State = 'ready' private totalSeconds = 0 private currentLapSeconds = 0 private previousLapSeconds: number[] = [] public get state(): State { return this._state } public get currentLap(): string { return this.formatTime(this.currentLapSeconds) } public get total(): string { return this.formatTime(this.totalSeconds) } public get previousLaps(): string[] { return this.previousLapSeconds.map((s) => this.formatTime(s)) } public start(): void { if (this._state === 'running') { throw new Error('cannot start an already running stopwatch') } this._state = 'running' } public stop(): void { if (this._state !== 'running') { throw new Error('cannot stop a stopwatch that is not running') } this._state = 'stopped' } public lap(): void { if (this._state !== 'running') { throw new Error('cannot lap a stopwatch that is not running') } this.previousLapSeconds.push(this.currentLapSeconds) this.currentLapSeconds = 0 } public reset(): void { if (this._state !== 'stopped') { throw new Error('cannot reset a stopwatch that is not stopped') } this._state = 'ready' this.totalSeconds = 0 this.currentLapSeconds = 0 this.previousLapSeconds = [] } public advanceTime(duration: string): void { if (this._state === 'running') { const seconds = this.toSeconds(duration) this.currentLapSeconds += seconds this.totalSeconds += seconds } } private toSeconds(duration: string): number { const [h, m, s] = duration.split(':').map(Number) return h * 3600 + m * 60 + s } private formatTime(seconds: number): string { const h = Math.floor(seconds / 3600) const m = Math.floor((seconds % 3600) / 60) const s = seconds % 60 return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}` } } ================================================ FILE: exercises/practice/split-second-stopwatch/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [ddb238ea-99d4-4eaa-a81d-3c917a525a23] description = "new stopwatch starts in ready state" [b19635d4-08ad-4ac3-b87f-aca10e844071] description = "new stopwatch's current lap has no elapsed time" [492eb532-268d-43ea-8a19-2a032067d335] description = "new stopwatch's total has no elapsed time" [8a892c1e-9ef7-4690-894e-e155a1fe4484] description = "new stopwatch does not have previous laps" [5b2705b6-a584-4042-ba3a-4ab8d0ab0281] description = "start from ready state changes state to running" [748235ce-1109-440b-9898-0a431ea179b6] description = "start does not change previous laps" [491487b1-593d-423e-a075-aa78d449ff1f] description = "start initiates time tracking for current lap" [a0a7ba2c-8db6-412c-b1b6-cb890e9b72ed] description = "start initiates time tracking for total" [7f558a17-ef6d-4a5b-803a-f313af7c41d3] description = "start cannot be called from running state" [32466eef-b2be-4d60-a927-e24fce52dab9] description = "stop from running state changes state to stopped" [621eac4c-8f43-4d99-919c-4cad776d93df] description = "stop pauses time tracking for current lap" [465bcc82-7643-41f2-97ff-5e817cef8db4] description = "stop pauses time tracking for total" [b1ba7454-d627-41ee-a078-891b2ed266fc] description = "stop cannot be called from ready state" [5c041078-0898-44dc-9d5b-8ebb5352626c] description = "stop cannot be called from stopped state" [3f32171d-8fbf-46b6-bc2b-0810e1ec53b7] description = "start from stopped state changes state to running" [626997cb-78d5-4fe8-b501-29fdef804799] description = "start from stopped state resumes time tracking for current lap" [58487c53-ab26-471c-a171-807ef6363319] description = "start from stopped state resumes time tracking for total" [091966e3-ed25-4397-908b-8bb0330118f8] description = "lap adds current lap to previous laps" [1aa4c5ee-a7d5-4d59-9679-419deef3c88f] description = "lap resets current lap and resumes time tracking" [4b46b92e-1b3f-46f6-97d2-0082caf56e80] description = "lap continues time tracking for total" [ea75d36e-63eb-4f34-97ce-8c70e620bdba] description = "lap cannot be called from ready state" [63731154-a23a-412d-a13f-c562f208eb1e] description = "lap cannot be called from stopped state" [e585ee15-3b3f-4785-976b-dd96e7cc978b] description = "stop does not change previous laps" [fc3645e2-86cf-4d11-97c6-489f031103f6] description = "reset from stopped state changes state to ready" [20fbfbf7-68ad-4310-975a-f5f132886c4e] description = "reset resets current lap" [00a8f7bb-dd5c-43e5-8705-3ef124007662] description = "reset clears previous laps" [76cea936-6214-4e95-b6d1-4d4edcf90499] description = "reset cannot be called from ready state" [ba4d8e69-f200-4721-b59e-90d8cf615153] description = "reset cannot be called from running state" [0b01751a-cb57-493f-bb86-409de6e84306] description = "supports very long laps" ================================================ FILE: exercises/practice/split-second-stopwatch/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/split-second-stopwatch/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/split-second-stopwatch/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/split-second-stopwatch/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/split-second-stopwatch/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/split-second-stopwatch/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/split-second-stopwatch/package.json ================================================ { "name": "@exercism/typescript-split-second-stopwatch", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/split-second-stopwatch/split-second-stopwatch.test.ts ================================================ import { describe, expect, it, xit } from '@jest/globals' import { SplitSecondStopwatch } from './split-second-stopwatch.ts' describe('SplitSecondStopwatch', () => { it('new stopwatch starts in ready state', () => { const stopwatch = new SplitSecondStopwatch() expect(stopwatch.state).toBe('ready') }) xit("new stopwatch's current lap has no elapsed time", () => { const stopwatch = new SplitSecondStopwatch() expect(stopwatch.currentLap).toBe('00:00:00') }) xit("new stopwatch's total has no elapsed time", () => { const stopwatch = new SplitSecondStopwatch() expect(stopwatch.total).toBe('00:00:00') }) xit('new stopwatch does not have previous laps', () => { const stopwatch = new SplitSecondStopwatch() expect(stopwatch.previousLaps).toEqual([]) }) xit('start from ready state changes state to running', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() expect(stopwatch.state).toBe('running') }) xit('start does not change previous laps', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() expect(stopwatch.previousLaps).toEqual([]) }) xit('start initiates time tracking for current lap', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() stopwatch.advanceTime('00:00:05') expect(stopwatch.currentLap).toBe('00:00:05') }) xit('start initiates time tracking for total', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() stopwatch.advanceTime('00:00:23') expect(stopwatch.total).toBe('00:00:23') }) xit('start cannot be called from running state', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() expect(() => stopwatch.start()).toThrow( 'cannot start an already running stopwatch' ) }) xit('stop from running state changes state to stopped', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() stopwatch.stop() expect(stopwatch.state).toBe('stopped') }) xit('stop pauses time tracking for current lap', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() stopwatch.advanceTime('00:00:05') stopwatch.stop() stopwatch.advanceTime('00:00:08') expect(stopwatch.currentLap).toBe('00:00:05') }) xit('stop pauses time tracking for total', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() stopwatch.advanceTime('00:00:13') stopwatch.stop() stopwatch.advanceTime('00:00:44') expect(stopwatch.total).toBe('00:00:13') }) xit('stop cannot be called from ready state', () => { const stopwatch = new SplitSecondStopwatch() expect(() => stopwatch.stop()).toThrow( 'cannot stop a stopwatch that is not running' ) }) xit('stop cannot be called from stopped state', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() stopwatch.stop() expect(() => stopwatch.stop()).toThrow( 'cannot stop a stopwatch that is not running' ) }) xit('start from stopped state changes state to running', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() stopwatch.stop() stopwatch.start() expect(stopwatch.state).toBe('running') }) xit('start from stopped state resumes time tracking for current lap', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() stopwatch.advanceTime('00:01:20') stopwatch.stop() stopwatch.advanceTime('00:00:20') stopwatch.start() stopwatch.advanceTime('00:00:08') expect(stopwatch.currentLap).toBe('00:01:28') }) xit('start from stopped state resumes time tracking for total', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() stopwatch.advanceTime('00:00:23') stopwatch.stop() stopwatch.advanceTime('00:00:44') stopwatch.start() stopwatch.advanceTime('00:00:09') expect(stopwatch.total).toBe('00:00:32') }) xit('lap adds current lap to previous laps', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() stopwatch.advanceTime('00:01:38') stopwatch.lap() expect(stopwatch.previousLaps).toEqual(['00:01:38']) stopwatch.advanceTime('00:00:44') stopwatch.lap() expect(stopwatch.previousLaps).toEqual(['00:01:38', '00:00:44']) }) xit('lap resets current lap and resumes time tracking', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() stopwatch.advanceTime('00:08:22') stopwatch.lap() expect(stopwatch.currentLap).toBe('00:00:00') stopwatch.advanceTime('00:00:15') expect(stopwatch.currentLap).toBe('00:00:15') }) xit('lap continues time tracking for total', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() stopwatch.advanceTime('00:00:22') stopwatch.lap() stopwatch.advanceTime('00:00:33') expect(stopwatch.total).toBe('00:00:55') }) xit('lap cannot be called from ready state', () => { const stopwatch = new SplitSecondStopwatch() expect(() => stopwatch.lap()).toThrow( 'cannot lap a stopwatch that is not running' ) }) xit('lap cannot be called from stopped state', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() stopwatch.stop() expect(() => stopwatch.lap()).toThrow( 'cannot lap a stopwatch that is not running' ) }) xit('stop does not change previous laps', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() stopwatch.advanceTime('00:11:22') stopwatch.lap() expect(stopwatch.previousLaps).toEqual(['00:11:22']) stopwatch.stop() expect(stopwatch.previousLaps).toEqual(['00:11:22']) }) xit('reset from stopped state changes state to ready', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() stopwatch.stop() stopwatch.reset() expect(stopwatch.state).toBe('ready') }) xit('reset resets current lap', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() stopwatch.advanceTime('00:00:10') stopwatch.stop() stopwatch.reset() expect(stopwatch.currentLap).toBe('00:00:00') }) xit('reset clears previous laps', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() stopwatch.advanceTime('00:00:10') stopwatch.lap() stopwatch.advanceTime('00:00:20') stopwatch.lap() expect(stopwatch.previousLaps).toEqual(['00:00:10', '00:00:20']) stopwatch.stop() stopwatch.reset() expect(stopwatch.previousLaps).toEqual([]) }) xit('reset cannot be called from ready state', () => { const stopwatch = new SplitSecondStopwatch() expect(() => stopwatch.reset()).toThrow( 'cannot reset a stopwatch that is not stopped' ) }) xit('reset cannot be called from running state', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() expect(() => stopwatch.reset()).toThrow( 'cannot reset a stopwatch that is not stopped' ) }) xit('supports very long laps', () => { const stopwatch = new SplitSecondStopwatch() stopwatch.start() stopwatch.advanceTime('01:23:45') expect(stopwatch.currentLap).toBe('01:23:45') stopwatch.lap() expect(stopwatch.previousLaps).toEqual(['01:23:45']) stopwatch.advanceTime('04:01:40') expect(stopwatch.currentLap).toBe('04:01:40') expect(stopwatch.total).toBe('05:25:25') stopwatch.lap() expect(stopwatch.previousLaps).toEqual(['01:23:45', '04:01:40']) stopwatch.advanceTime('08:43:05') expect(stopwatch.currentLap).toBe('08:43:05') expect(stopwatch.total).toBe('14:08:30') stopwatch.lap() expect(stopwatch.previousLaps).toEqual(['01:23:45', '04:01:40', '08:43:05']) }) }) ================================================ FILE: exercises/practice/split-second-stopwatch/split-second-stopwatch.ts ================================================ export class SplitSecondStopwatch { constructor() { throw new Error('Remove this line and implement the function') } public get state(): unknown { throw new Error('Remove this line and implement the function') } public get currentLap(): unknown { throw new Error('Remove this line and implement the function') } public get total(): unknown { throw new Error('Remove this line and implement the function') } public get previousLaps(): unknown[] { throw new Error('Remove this line and implement the function') } public start(): unknown { throw new Error('Remove this line and implement the function') } public stop(): unknown { throw new Error('Remove this line and implement the function') } public lap(): unknown { throw new Error('Remove this line and implement the function') } public reset(): unknown { throw new Error('Remove this line and implement the function') } public advanceTime(duration: unknown): unknown { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/split-second-stopwatch/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/split-second-stopwatch/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/square-root/.docs/instructions.md ================================================ # Instructions Your task is to calculate the square root of a given number. - Try to avoid using the pre-existing math libraries of your language. - As input you'll be given a positive whole number, i.e. 1, 2, 3, 4… - You are only required to handle cases where the result is a positive whole number. Some potential approaches: - Linear or binary search for a number that gives the input number when squared. - Successive approximation using Newton's or Heron's method. - Calculating one digit at a time or one bit at a time. You can check out the Wikipedia pages on [integer square root][integer-square-root] and [methods of computing square roots][computing-square-roots] to help with choosing a method of calculation. [integer-square-root]: https://en.wikipedia.org/wiki/Integer_square_root [computing-square-roots]: https://en.wikipedia.org/wiki/Methods_of_computing_square_roots ================================================ FILE: exercises/practice/square-root/.docs/introduction.md ================================================ # Introduction We are launching a deep space exploration rocket and we need a way to make sure the navigation system stays on target. As the first step in our calculation, we take a target number and find its square root (that is, the number that when multiplied by itself equals the target number). The journey will be very long. To make the batteries last as long as possible, we had to make our rocket's onboard computer very power efficient. Unfortunately that means that we can't rely on fancy math libraries and functions, as they use more power. Instead we want to implement our own square root calculation. ================================================ FILE: exercises/practice/square-root/.meta/config.json ================================================ { "authors": [ "BNAndras" ], "files": { "solution": [ "square-root.ts" ], "test": [ "square-root.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a natural radicand, return its square root.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "wolf99", "source_url": "https://github.com/exercism/problem-specifications/pull/1582" } ================================================ FILE: exercises/practice/square-root/.meta/proof.ci.ts ================================================ export function squareRoot(radicand: number): number { if (radicand === 1) { return 1 } let guess = Math.floor(radicand / 2) for (let i = 0; i < 10; i++) { guess = Math.floor((guess + radicand / guess) / 2) } return guess } ================================================ FILE: exercises/practice/square-root/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [9b748478-7b0a-490c-b87a-609dacf631fd] description = "root of 1" [7d3aa9ba-9ac6-4e93-a18b-2e8b477139bb] description = "root of 4" [6624aabf-3659-4ae0-a1c8-25ae7f33c6ef] description = "root of 25" [93beac69-265e-4429-abb1-94506b431f81] description = "root of 81" [fbddfeda-8c4f-4bc4-87ca-6991af35360e] description = "root of 196" [c03d0532-8368-4734-a8e0-f96a9eb7fc1d] description = "root of 65025" ================================================ FILE: exercises/practice/square-root/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/square-root/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/square-root/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/square-root/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/square-root/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/square-root/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/square-root/package.json ================================================ { "name": "@exercism/typescript-square-root", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/square-root/square-root.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { squareRoot } from './square-root.ts' describe('Square Root', () => { // Root of 1 it('root of 1', () => { expect(squareRoot(1)).toEqual(1) }) // Root of 4 xit('root of 4', () => { expect(squareRoot(4)).toEqual(2) }) // Root of 25 xit('root of 25', () => { expect(squareRoot(25)).toEqual(5) }) // Root of 81 xit('root of 81', () => { expect(squareRoot(81)).toEqual(9) }) // Root of 196 xit('root of 196', () => { expect(squareRoot(196)).toEqual(14) }) // Root of 65025 xit('root of 65025', () => { expect(squareRoot(65025)).toEqual(255) }) }) ================================================ FILE: exercises/practice/square-root/square-root.ts ================================================ export function squareRoot(radicand: unknown): unknown { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/square-root/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/square-root/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/state-of-tic-tac-toe/.docs/instructions.md ================================================ # Instructions In this exercise, you're going to implement a program that determines the state of a [tic-tac-toe][] game. (_You may also know the game as "noughts and crosses" or "Xs and Os"._) The game is played on a 3×3 grid. Players take turns to place `X`s and `O`s on the grid. The game ends when one player has won by placing three of marks in a row, column, or along a diagonal of the grid, or when the entire grid is filled up. In this exercise, we will assume that `X` starts. It's your job to determine which state a given game is in. There are 3 potential game states: - The game is **ongoing**. - The game ended in a **draw**. - The game ended in a **win**. If the given board is invalid, throw an appropriate error. If a board meets the following conditions, it is invalid: - The given board cannot be reached when turns are taken in the correct order (remember that `X` starts). - The game was played after it already ended. ## Examples ### Ongoing game ```text | | X | | ___|___|___ | | | X | O ___|___|___ | | O | X | | | ``` ### Draw ```text | | X | O | X ___|___|___ | | X | X | O ___|___|___ | | O | X | O | | ``` ### Win ```text | | X | X | X ___|___|___ | | | O | O ___|___|___ | | | | | | ``` ### Invalid #### Wrong turn order ```text | | O | O | X ___|___|___ | | | | ___|___|___ | | | | | | ``` #### Continued playing after win ```text | | X | X | X ___|___|___ | | O | O | O ___|___|___ | | | | | | ``` [tic-tac-toe]: https://en.wikipedia.org/wiki/Tic-tac-toe ================================================ FILE: exercises/practice/state-of-tic-tac-toe/.meta/config.json ================================================ { "authors": [ "Cool-Katt" ], "contributors": [ "BNAndras" ], "files": { "solution": [ "state-of-tic-tac-toe.ts" ], "test": [ "state-of-tic-tac-toe.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Determine the game state of a match of Tic-Tac-Toe.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Created by Sascha Mann for the Julia track of the Exercism Research Experiment.", "source_url": "https://github.com/exercism/research_experiment_1/tree/julia-dev/exercises/julia-1-a" } ================================================ FILE: exercises/practice/state-of-tic-tac-toe/.meta/proof.ci.ts ================================================ export const gamestate = (board: string[]): string => { const gridSize = board.length const numberOfX = (board.join('').match(/X/g) || []).length const numberOfO = (board.join('').match(/O/g) || []).length const scoringArray = Array(gridSize * 2 + 2).fill(0) const boardAsNumbers = board.flatMap((row) => row.split('').map((element) => { if (element === 'X') return 1 if (element === 'O') return -1 return 0 }) ) boardAsNumbers.forEach((element, index) => { const row = Math.floor(index / gridSize) const col = index % gridSize scoringArray[row] += element scoringArray[gridSize + col] += element if (row === col) { scoringArray[2 * gridSize] += element } if (gridSize - 1 - col === row) { scoringArray[2 * gridSize + 1] += element } }) if (numberOfX - numberOfO > 1) { throw new Error('Wrong turn order: X went twice') } if (numberOfX - numberOfO < 0) { throw new Error('Wrong turn order: O started') } const xWins = scoringArray.some((score) => score === gridSize) const oWins = scoringArray.some((score) => score === -gridSize) if (xWins && oWins) { throw new Error( 'Impossible board: game should have ended after the game was won' ) } if (xWins || oWins) { return 'win' } if (boardAsNumbers.includes(0)) { return 'ongoing' } return 'draw' } ================================================ FILE: exercises/practice/state-of-tic-tac-toe/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [fe8e9fa9-37af-4d7e-aa24-2f4b8517161a] description = "Won games -> Finished game where X won via left column victory" [96c30df5-ae23-4cf6-bf09-5ef056dddea1] description = "Won games -> Finished game where X won via middle column victory" [0d7a4b0a-2afd-4a75-8389-5fb88ab05eda] description = "Won games -> Finished game where X won via right column victory" [bd1007c0-ec5d-4c60-bb9f-1a4f22177d51] description = "Won games -> Finished game where O won via left column victory" [c032f800-5735-4354-b1b9-46f14d4ee955] description = "Won games -> Finished game where O won via middle column victory" [662c8902-c94a-4c4c-9d9c-e8ca513db2b4] description = "Won games -> Finished game where O won via right column victory" [2d62121f-7e3a-44a0-9032-0d73e3494941] description = "Won games -> Finished game where X won via top row victory" [108a5e82-cc61-409f-aece-d7a18c1beceb] description = "Won games -> Finished game where X won via middle row victory" include = false [346527db-4db9-4a96-b262-d7023dc022b0] description = "Won games -> Finished game where X won via middle row victory" reimplements = "108a5e82-cc61-409f-aece-d7a18c1beceb" [a013c583-75f8-4ab2-8d68-57688ff04574] description = "Won games -> Finished game where X won via bottom row victory" [2c08e7d7-7d00-487f-9442-e7398c8f1727] description = "Won games -> Finished game where O won via top row victory" [bb1d6c62-3e3f-4d1a-9766-f8803c8ed70f] description = "Won games -> Finished game where O won via middle row victory" [6ef641e9-12ec-44f5-a21c-660ea93907af] description = "Won games -> Finished game where O won via bottom row victory" [ab145b7b-26a7-426c-ab71-bf418cd07f81] description = "Won games -> Finished game where X won via falling diagonal victory" [7450caab-08f5-4f03-a74b-99b98c4b7a4b] description = "Won games -> Finished game where X won via rising diagonal victory" [c2a652ee-2f93-48aa-a710-a70cd2edce61] description = "Won games -> Finished game where O won via falling diagonal victory" [5b20ceea-494d-4f0c-a986-b99efc163bcf] description = "Won games -> Finished game where O won via rising diagonal victory" [035a49b9-dc35-47d3-9d7c-de197161b9d4] description = "Won games -> Finished game where X won via a row and a column victory" [e5dfdeb0-d2bf-4b5a-b307-e673f69d4a53] description = "Won games -> Finished game where X won via two diagonal victories" [b42ed767-194c-4364-b36e-efbfb3de8788] description = "Drawn games -> Draw" [227a76b2-0fef-4e16-a4bd-8f9d7e4c3b13] description = "Drawn games -> Another draw" [4d93f15c-0c40-43d6-b966-418b040012a9] description = "Ongoing games -> Ongoing game: one move in" [c407ae32-4c44-4989-b124-2890cf531f19] description = "Ongoing games -> Ongoing game: two moves in" [199b7a8d-e2b6-4526-a85e-78b416e7a8a9] description = "Ongoing games -> Ongoing game: five moves in" [1670145b-1e3d-4269-a7eb-53cd327b302e] description = "Invalid boards -> Invalid board: X went twice" [47c048e8-b404-4bcf-9e51-8acbb3253f3b] description = "Invalid boards -> Invalid board: O started" [b1dc8b13-46c4-47db-a96d-aa90eedc4e8d] description = "Invalid boards -> Invalid board" include = false [6c1920f2-ab5c-4648-a0c9-997414dda5eb] description = "Invalid boards -> Invalid board: X won and O kept playing" reimplements = "b1dc8b13-46c4-47db-a96d-aa90eedc4e8d" [4801cda2-f5b7-4c36-8317-3cdd167ac22c] description = "Invalid boards -> Invalid board: players kept playing after a win" ================================================ FILE: exercises/practice/state-of-tic-tac-toe/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/state-of-tic-tac-toe/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/state-of-tic-tac-toe/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/state-of-tic-tac-toe/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/state-of-tic-tac-toe/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/state-of-tic-tac-toe/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/state-of-tic-tac-toe/package.json ================================================ { "name": "@exercism/typescript-state-of-tic-tac-toe", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/state-of-tic-tac-toe/state-of-tic-tac-toe.test.ts ================================================ import { describe, expect, it, xit } from '@jest/globals' import { gamestate } from './state-of-tic-tac-toe.ts' describe('state-of-tic-tac-toe', () => { describe('Won games', () => { it('Finished game where X won via left column victory', () => { const board = ['XOO', 'X ', 'X '] expect(gamestate(board)).toEqual('win') }) xit('Finished game where X won via middle column victory', () => { const board = ['OXO', ' X ', ' X '] expect(gamestate(board)).toEqual('win') }) xit('Finished game where X won via right column victory', () => { const board = ['OOX', ' X', ' X'] expect(gamestate(board)).toEqual('win') }) xit('Finished game where O won via left column victory', () => { const board = ['OXX', 'OX ', 'O '] expect(gamestate(board)).toEqual('win') }) xit('Finished game where O won via middle column victory', () => { const board = ['XOX', ' OX', ' O '] expect(gamestate(board)).toEqual('win') }) xit('Finished game where O won via right column victory', () => { const board = ['XXO', ' XO', ' O'] expect(gamestate(board)).toEqual('win') }) xit('Finished game where X won via top row victory', () => { const board = ['XXX', 'XOO', 'O '] expect(gamestate(board)).toEqual('win') }) xit('Finished game where X won via middle row victory', () => { const board = ['O ', 'XXX', ' O '] expect(gamestate(board)).toEqual('win') }) xit('Finished game where X won via bottom row victory', () => { const board = [' OO', 'O X', 'XXX'] expect(gamestate(board)).toEqual('win') }) xit('Finished game where O won via top row victory', () => { const board = ['OOO', 'XXO', 'XX '] expect(gamestate(board)).toEqual('win') }) xit('Finished game where O won via middle row victory', () => { const board = ['XX ', 'OOO', 'X '] expect(gamestate(board)).toEqual('win') }) xit('Finished game where O won via bottom row victory', () => { const board = ['XOX', ' XX', 'OOO'] expect(gamestate(board)).toEqual('win') }) xit('Finished game where X won via falling diagonal victory', () => { const board = ['XOO', ' X ', ' X'] expect(gamestate(board)).toEqual('win') }) xit('Finished game where X won via rising diagonal victory', () => { const board = ['O X', 'OX ', 'X '] expect(gamestate(board)).toEqual('win') }) xit('Finished game where O won via falling diagonal victory', () => { const board = ['OXX', 'OOX', 'X O'] expect(gamestate(board)).toEqual('win') }) xit('Finished game where O won via rising diagonal victory', () => { const board = [' O', ' OX', 'OXX'] expect(gamestate(board)).toEqual('win') }) xit('Finished game where X won via a row and a column victory', () => { const board = ['XXX', 'XOO', 'XOO'] expect(gamestate(board)).toEqual('win') }) xit('Finished game where X won via two diagonal victories', () => { const board = ['XOX', 'OXO', 'XOX'] expect(gamestate(board)).toEqual('win') }) }) describe('Draw games', () => { xit('Draw', () => { const board = ['XOX', 'XXO', 'OXO'] expect(gamestate(board)).toEqual('draw') }) xit('Another draw', () => { const board = ['XXO', 'OXX', 'XOO'] expect(gamestate(board)).toEqual('draw') }) }) describe('Ongoing games', () => { xit('Ongoing game: one move in', () => { const board = [' ', 'X ', ' '] expect(gamestate(board)).toEqual('ongoing') }) xit('Ongoing game: two moves in', () => { const board = ['O ', ' X ', ' '] expect(gamestate(board)).toEqual('ongoing') }) xit('Ongoing game: five moves in', () => { const board = ['X ', ' XO', 'OX '] expect(gamestate(board)).toEqual('ongoing') }) }) describe('Invalid boards', () => { xit('Invalid board: X went twice', () => { const board = ['XX ', ' ', ' '] expect(() => { gamestate(board) }).toThrow('Wrong turn order: X went twice') }) xit('Invalid board: O started', () => { const board = ['OOX', ' ', ' '] expect(() => { gamestate(board) }).toThrow('Wrong turn order: O started') }) xit('Invalid board: X won and O kept playing', () => { const board = ['XXX', 'OOO', ' '] expect(() => { gamestate(board) }).toThrow( 'Impossible board: game should have ended after the game was won' ) }) xit('Invalid board: players kept playing after a win', () => { const board = ['XXX', 'OOO', 'XOX'] expect(() => { gamestate(board) }).toThrow( 'Impossible board: game should have ended after the game was won' ) }) }) }) ================================================ FILE: exercises/practice/state-of-tic-tac-toe/state-of-tic-tac-toe.ts ================================================ export const gamestate = (board: unknown): unknown => { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/state-of-tic-tac-toe/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/state-of-tic-tac-toe/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/strain/.docs/instructions.md ================================================ # Instructions Implement the `keep` and `discard` operation on collections. Given a collection and a predicate on the collection's elements, `keep` returns a new collection containing those elements where the predicate is true, while `discard` returns a new collection containing those elements where the predicate is false. For example, given the collection of numbers: - 1, 2, 3, 4, 5 And the predicate: - is the number even? Then your keep operation should produce: - 2, 4 While your discard operation should produce: - 1, 3, 5 Note that the union of keep and discard is all the elements. The functions may be called `keep` and `discard`, or they may need different names in order to not clash with existing functions or concepts in your language. ## Restrictions Keep your hands off that filter/reject/whatchamacallit functionality provided by your standard library! Solve this one yourself using other basic tools instead. ================================================ FILE: exercises/practice/strain/.meta/config.json ================================================ { "authors": [ "jspengeman" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "strain.ts" ], "test": [ "strain.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Implement the `keep` and `discard` operation on collections.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Conversation with James Edward Gray II", "source_url": "http://graysoftinc.com/" } ================================================ FILE: exercises/practice/strain/.meta/proof.ci.ts ================================================ interface Predicate { (element: T): boolean } export function keep(array: T[], predicate: Predicate): T[] { const output: T[] = [] array.forEach((el: T) => { if (predicate(el)) { output.push(el) } }) return output } export function discard(array: T[], predicate: Predicate): T[] { const output: T[] = [] array.forEach((el: T) => { if (!predicate(el)) { output.push(el) } }) return output } ================================================ FILE: exercises/practice/strain/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [26af8c32-ba6a-4eb3-aa0a-ebd8f136e003] description = "keep on empty list returns empty list" [f535cb4d-e99b-472a-bd52-9fa0ffccf454] description = "keeps everything" [950b8e8e-f628-42a8-85e2-9b30f09cde38] description = "keeps nothing" [92694259-6e76-470c-af87-156bdf75018a] description = "keeps first and last" [938f7867-bfc7-449e-a21b-7b00cbb56994] description = "keeps neither first nor last" [8908e351-4437-4d2b-a0f7-770811e48816] description = "keeps strings" [2728036b-102a-4f1e-a3ef-eac6160d876a] description = "keeps lists" [ef16beb9-8d84-451a-996a-14e80607fce6] description = "discard on empty list returns empty list" [2f42f9bc-8e06-4afe-a222-051b5d8cd12a] description = "discards everything" [ca990fdd-08c2-4f95-aa50-e0f5e1d6802b] description = "discards nothing" [71595dae-d283-48ca-a52b-45fa96819d2f] description = "discards first and last" [ae141f79-f86d-4567-b407-919eaca0f3dd] description = "discards neither first nor last" [daf25b36-a59f-4f29-bcfe-302eb4e43609] description = "discards strings" [a38d03f9-95ad-4459-80d1-48e937e4acaf] description = "discards lists" ================================================ FILE: exercises/practice/strain/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/strain/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/strain/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/strain/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/strain/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/strain/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/strain/package.json ================================================ { "name": "@exercism/typescript-strain", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/strain/strain.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { keep, discard } from './strain.ts' describe('strain', () => { it('keeps on empty array returns empty array', () => { const predicate = (_: number): boolean => true expect(keep([], predicate)).toEqual([]) }) xit('keeps everything', () => { const predicate = (_: number): boolean => true expect(keep([1, 3, 5], predicate)).toEqual([1, 3, 5]) }) xit('keeps nothing', () => { const predicate = (_: number): boolean => false expect(keep([1, 3, 5], predicate)).toEqual([]) }) xit('keeps first and last', () => { const predicate = (e: number): boolean => e % 2 === 1 expect(keep([1, 2, 3], predicate)).toEqual([1, 3]) }) xit('keeps neither first nor last', () => { const predicate = (e: number): boolean => e % 2 === 0 expect(keep([1, 2, 3], predicate)).toEqual([2]) }) xit('keeps strings', () => { const words = ['apple', 'zebra', 'banana', 'zombies', 'cherimoya', 'zealot'] const predicate = (word: string): boolean => word.indexOf('z') === 0 const result = keep(words, predicate) expect(result).toEqual(['zebra', 'zombies', 'zealot']) }) xit('keeps arrays', () => { const rows = [ [1, 2, 3], [5, 5, 5], [5, 1, 2], [2, 1, 2], [1, 5, 2], [2, 2, 1], [1, 2, 5], ] const predicate = (row: number[]): boolean => row.indexOf(5) > -1 const result = keep(rows, predicate) expect(result).toEqual([ [5, 5, 5], [5, 1, 2], [1, 5, 2], [1, 2, 5], ]) }) xit('empty discard', () => { const predicate = (_: number): boolean => true expect(discard([], predicate)).toEqual([]) }) xit('discards everything', () => { const predicate = (_: number): boolean => true expect(discard([1, 3, 5], predicate)).toEqual([]) }) it('discards nothing', () => { const predicate = (_: number): boolean => false expect(discard([1, 3, 5], predicate)).toEqual([1, 3, 5]) }) xit('discards first and last', () => { const predicate = (e: number): boolean => e % 2 === 1 expect(discard([1, 2, 3], predicate)).toEqual([2]) }) xit('discards neither first nor last', () => { const predicate = (e: number): boolean => e % 2 === 0 const result = discard([1, 2, 3], predicate) expect(result).toEqual([1, 3]) }) xit('discards strings', () => { const words = ['apple', 'zebra', 'banana', 'zombies', 'cherimoya', 'zealot'] const predicate = (word: string): boolean => word.indexOf('z') === 0 const result = discard(words, predicate) expect(result).toEqual(['apple', 'banana', 'cherimoya']) }) xit('discards arrays', () => { const rows = [ [1, 2, 3], [5, 5, 5], [5, 1, 2], [2, 1, 2], [1, 5, 2], [2, 2, 1], [1, 2, 5], ] const predicate = (row: number[]): boolean => row.indexOf(5) > -1 const result = discard(rows, predicate) expect(result).toEqual([ [1, 2, 3], [2, 1, 2], [2, 2, 1], ]) }) }) ================================================ FILE: exercises/practice/strain/strain.ts ================================================ export function keep() { throw new Error('Remove this line and implement the function') } export function discard() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/strain/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/strain/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/sublist/.docs/instructions.md ================================================ # Instructions Given any two lists `A` and `B`, determine if: - List `A` is equal to list `B`; or - List `A` contains list `B` (`A` is a superlist of `B`); or - List `A` is contained by list `B` (`A` is a sublist of `B`); or - None of the above is true, thus lists `A` and `B` are unequal Specifically, list `A` is equal to list `B` if both lists have the same values in the same order. List `A` is a superlist of `B` if `A` contains a contiguous sub-sequence of values equal to `B`. List `A` is a sublist of `B` if `B` contains a contiguous sub-sequence of values equal to `A`. Examples: - If `A = []` and `B = []` (both lists are empty), then `A` and `B` are equal - If `A = [1, 2, 3]` and `B = []`, then `A` is a superlist of `B` - If `A = []` and `B = [1, 2, 3]`, then `A` is a sublist of `B` - If `A = [1, 2, 3]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B` - If `A = [3, 4, 5]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B` - If `A = [3, 4]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B` - If `A = [1, 2, 3]` and `B = [1, 2, 3]`, then `A` and `B` are equal - If `A = [1, 2, 3, 4, 5]` and `B = [2, 3, 4]`, then `A` is a superlist of `B` - If `A = [1, 2, 4]` and `B = [1, 2, 3, 4, 5]`, then `A` and `B` are unequal - If `A = [1, 2, 3]` and `B = [1, 3, 2]`, then `A` and `B` are unequal ================================================ FILE: exercises/practice/sublist/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "sublist.ts" ], "test": [ "sublist.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Write a function to determine if a list is a sublist of another list.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false } } ================================================ FILE: exercises/practice/sublist/.meta/proof.ci.ts ================================================ export class List { private readonly list: number[] constructor(...list: number[]) { this.list = list } public compare(other: List): 'sublist' | 'superlist' | 'unequal' | 'equal' { switch (this.lengthDiff(this, other)) { case '-1': return this.isSublist(other.list, this.list) ? 'sublist' : 'unequal' case '0': return this.isSublist(other.list, this.list) ? 'equal' : 'unequal' case '1': return this.isSublist(this.list, other.list) ? 'superlist' : 'unequal' } return 'unequal' } private lengthDiff(listOne: List, listTwo: List): string { return String(Math.sign(listOne.list.length - listTwo.list.length)) } private isSublist(listOne: number[], listTwo: number[]): boolean { const join = (list: number[]): string => { const joiner = ',' return list.join(joiner) + joiner } return Boolean(join(listOne).match(join(listTwo))) } } ================================================ FILE: exercises/practice/sublist/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [97319c93-ebc5-47ab-a022-02a1980e1d29] description = "empty lists" [de27dbd4-df52-46fe-a336-30be58457382] description = "empty list within non empty list" [5487cfd1-bc7d-429f-ac6f-1177b857d4fb] description = "non empty list contains empty list" [1f390b47-f6b2-4a93-bc23-858ba5dda9a6] description = "list equals itself" [7ed2bfb2-922b-4363-ae75-f3a05e8274f5] description = "different lists" [3b8a2568-6144-4f06-b0a1-9d266b365341] description = "false start" [dc39ed58-6311-4814-be30-05a64bc8d9b1] description = "consecutive" [d1270dab-a1ce-41aa-b29d-b3257241ac26] description = "sublist at start" [81f3d3f7-4f25-4ada-bcdc-897c403de1b6] description = "sublist in middle" [43bcae1e-a9cf-470e-923e-0946e04d8fdd] description = "sublist at end" [76cf99ed-0ff0-4b00-94af-4dfb43fe5caa] description = "at start of superlist" [b83989ec-8bdf-4655-95aa-9f38f3e357fd] description = "in middle of superlist" [26f9f7c3-6cf6-4610-984a-662f71f8689b] description = "at end of superlist" [0a6db763-3588-416a-8f47-76b1cedde31e] description = "first list missing element from second list" [83ffe6d8-a445-4a3c-8795-1e51a95e65c3] description = "second list missing element from first list" [7bc76cb8-5003-49ca-bc47-cdfbe6c2bb89] description = "first list missing additional digits from second list" [0d7ee7c1-0347-45c8-9ef5-b88db152b30b] description = "order matters to a list" [5f47ce86-944e-40f9-9f31-6368aad70aa6] description = "same digits but different numbers" ================================================ FILE: exercises/practice/sublist/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/sublist/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/sublist/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/sublist/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/sublist/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/sublist/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/sublist/package.json ================================================ { "name": "@exercism/typescript-sublist", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/sublist/sublist.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { List } from './sublist.ts' describe('Sublist', () => { it('empty lists', () => { const listOne = new List() const listTwo = new List() const expected = 'equal' expect(listOne.compare(listTwo)).toEqual(expected) }) xit('empty list within non empty list', () => { const listOne = new List() const listTwo = new List(1, 2, 3) const expected = 'sublist' expect(listOne.compare(listTwo)).toEqual(expected) }) xit('non empty list contains empty list', () => { const listOne = new List(1, 2, 3) const listTwo = new List() const expected = 'superlist' expect(listOne.compare(listTwo)).toEqual(expected) }) xit('list equals itself', () => { const listOne = new List(1, 2, 3) const listTwo = new List(1, 2, 3) const expected = 'equal' expect(listOne.compare(listTwo)).toEqual(expected) }) xit('different lists', () => { const listOne = new List(1, 2, 3) const listTwo = new List(2, 3, 4) const expected = 'unequal' expect(listOne.compare(listTwo)).toEqual(expected) }) xit('false start', () => { const listOne = new List(1, 2, 5) const listTwo = new List(0, 1, 2, 3, 1, 2, 5, 6) const expected = 'sublist' expect(listOne.compare(listTwo)).toEqual(expected) }) xit('consecutive', () => { const listOne = new List(1, 1, 2) const listTwo = new List(0, 1, 1, 1, 2, 1, 2) const expected = 'sublist' expect(listOne.compare(listTwo)).toEqual(expected) }) xit('sublist at start', () => { const listOne = new List(0, 1, 2) const listTwo = new List(0, 1, 2, 3, 4, 5) const expected = 'sublist' expect(listOne.compare(listTwo)).toEqual(expected) }) xit('sublist in middle', () => { const listOne = new List(2, 3, 4) const listTwo = new List(0, 1, 2, 3, 4, 5) const expected = 'sublist' expect(listOne.compare(listTwo)).toEqual(expected) }) xit('sublist at end', () => { const listOne = new List(3, 4, 5) const listTwo = new List(0, 1, 2, 3, 4, 5) const expected = 'sublist' expect(listOne.compare(listTwo)).toEqual(expected) }) xit('at start of superlist', () => { const listOne = new List(0, 1, 2, 3, 4, 5) const listTwo = new List(0, 1, 2) const expected = 'superlist' expect(listOne.compare(listTwo)).toEqual(expected) }) xit('in middle of superlist', () => { const listOne = new List(0, 1, 2, 3, 4, 5) const listTwo = new List(2, 3) const expected = 'superlist' expect(listOne.compare(listTwo)).toEqual(expected) }) xit('at end of superlist', () => { const listOne = new List(0, 1, 2, 3, 4, 5) const listTwo = new List(3, 4, 5) const expected = 'superlist' expect(listOne.compare(listTwo)).toEqual(expected) }) xit('first list missing element from second list', () => { const listOne = new List(1, 3) const listTwo = new List(1, 2, 3) const expected = 'unequal' expect(listOne.compare(listTwo)).toEqual(expected) }) xit('second list missing element from first list', () => { const listOne = new List(1, 2, 3) const listTwo = new List(1, 3) const expected = 'unequal' expect(listOne.compare(listTwo)).toEqual(expected) }) xit('first list missing additional digits from second list', () => { const listOne = new List(1, 2) const listTwo = new List(1, 22) const expected = 'unequal' expect(listOne.compare(listTwo)).toEqual(expected) }) xit('order matters to a list', () => { const listOne = new List(1, 2, 3) const listTwo = new List(3, 2, 1) const expected = 'unequal' expect(listOne.compare(listTwo)).toEqual(expected) }) xit('same digits but different numbers', () => { const listOne = new List(1, 0, 1) const listTwo = new List(10, 1) const expected = 'unequal' expect(listOne.compare(listTwo)).toEqual(expected) }) }) ================================================ FILE: exercises/practice/sublist/sublist.ts ================================================ export class List { constructor() { throw new Error('Remove this line and implement the function') } compare() { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/sublist/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/sublist/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/sum-of-multiples/.docs/instructions.md ================================================ # Instructions Your task is to write the code that calculates the energy points that get awarded to players when they complete a level. The points awarded depend on two things: - The level (a number) that the player completed. - The base value of each magical item collected by the player during that level. The energy points are awarded according to the following rules: 1. For each magical item, take the base value and find all the multiples of that value that are less than the level number. 2. Combine the sets of numbers. 3. Remove any duplicates. 4. Calculate the sum of all the numbers that are left. Let's look at an example: **The player completed level 20 and found two magical items with base values of 3 and 5.** To calculate the energy points earned by the player, we need to find all the unique multiples of these base values that are less than level 20. - Multiples of 3 less than 20: `{3, 6, 9, 12, 15, 18}` - Multiples of 5 less than 20: `{5, 10, 15}` - Combine the sets and remove duplicates: `{3, 5, 6, 9, 10, 12, 15, 18}` - Sum the unique multiples: `3 + 5 + 6 + 9 + 10 + 12 + 15 + 18 = 78` - Therefore, the player earns **78** energy points for completing level 20 and finding the two magical items with base values of 3 and 5. ================================================ FILE: exercises/practice/sum-of-multiples/.docs/introduction.md ================================================ # Introduction You work for a company that makes an online, fantasy-survival game. When a player finishes a level, they are awarded energy points. The amount of energy awarded depends on which magical items the player found while exploring that level. ================================================ FILE: exercises/practice/sum-of-multiples/.meta/config.json ================================================ { "authors": [], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "sum-of-multiples.ts" ], "test": [ "sum-of-multiples.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a number, find the sum of all the multiples of particular numbers up to but not including that number.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "A variation on Problem 1 at Project Euler", "source_url": "https://projecteuler.net/problem=1" } ================================================ FILE: exercises/practice/sum-of-multiples/.meta/proof.ci.ts ================================================ export const sum = (multiples: number[], limit: number): number => { let result = 0 for (let i = 1; i < limit; i++) { if (multiples.some((multiple) => i % multiple === 0)) { result += i } } return result } ================================================ FILE: exercises/practice/sum-of-multiples/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [54aaab5a-ce86-4edc-8b40-d3ab2400a279] description = "no multiples within limit" [361e4e50-c89b-4f60-95ef-5bc5c595490a] description = "one factor has multiples within limit" [e644e070-040e-4ae0-9910-93c69fc3f7ce] description = "more than one multiple within limit" [607d6eb9-535c-41ce-91b5-3a61da3fa57f] description = "more than one factor with multiples within limit" [f47e8209-c0c5-4786-b07b-dc273bf86b9b] description = "each multiple is only counted once" [28c4b267-c980-4054-93e9-07723db615ac] description = "a much larger limit" [09c4494d-ff2d-4e0f-8421-f5532821ee12] description = "three factors" [2d0d5faa-f177-4ad6-bde9-ebb865083751] description = "factors not relatively prime" [ece8f2e8-96aa-4166-bbb7-6ce71261e354] description = "some pairs of factors relatively prime and some not" [624fdade-6ffb-400e-8472-456a38c171c0] description = "one factor is a multiple of another" [949ee7eb-db51-479c-b5cb-4a22b40ac057] description = "much larger factors" [41093673-acbd-482c-ab80-d00a0cbedecd] description = "all numbers are multiples of 1" [1730453b-baaa-438e-a9c2-d754497b2a76] description = "no factors means an empty sum" [214a01e9-f4bf-45bb-80f1-1dce9fbb0310] description = "the only multiple of 0 is 0" [c423ae21-a0cb-4ec7-aeb1-32971af5b510] description = "the factor 0 does not affect the sum of multiples of other factors" [17053ba9-112f-4ac0-aadb-0519dd836342] description = "solutions using include-exclude must extend to cardinality greater than 3" ================================================ FILE: exercises/practice/sum-of-multiples/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/sum-of-multiples/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/sum-of-multiples/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/sum-of-multiples/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/sum-of-multiples/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/sum-of-multiples/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/sum-of-multiples/package.json ================================================ { "name": "@exercism/typescript-sum-of-multiples", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/sum-of-multiples/sum-of-multiples.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { sum } from './sum-of-multiples.ts' describe('Sum Of Multiples', () => { it('no multiples within limit', () => { expect(sum([3, 5], 1)).toEqual(0) }) xit('one factor has multiples within limit', () => { expect(sum([3, 5], 4)).toEqual(3) }) xit('more than one multiple within limit', () => { expect(sum([3], 7)).toEqual(9) }) xit('more than one factor with multiples within limit', () => { expect(sum([3, 5], 10)).toEqual(23) }) xit('each multiple is only counted once', () => { expect(sum([3, 5], 100)).toEqual(2318) }) xit('a much larger limit', () => { expect(sum([3, 5], 1000)).toEqual(233168) }) xit('three factors', () => { expect(sum([7, 13, 17], 20)).toEqual(51) }) xit('factors not relatively prime', () => { expect(sum([4, 6], 15)).toEqual(30) }) xit('some pairs of factors relatively prime and some not', () => { expect(sum([5, 6, 8], 150)).toEqual(4419) }) xit('one factor is a multiple of another', () => { expect(sum([5, 25], 51)).toEqual(275) }) xit('much larger factors', () => { expect(sum([43, 47], 10000)).toEqual(2203160) }) xit('all numbers are multiples of 1', () => { expect(sum([1], 100)).toEqual(4950) }) xit('no factors means an empty sum', () => { expect(sum([], 10000)).toEqual(0) }) xit('the only multiple of 0 is 0', () => { expect(sum([0], 1)).toEqual(0) }) xit('the factor 0 does not affect the sum of multiples of other factors', () => { expect(sum([3, 0], 4)).toEqual(3) }) xit('solutions using include-exclude must extend to cardinality greater than 3', () => { expect(sum([2, 3, 5, 7, 11], 10000)).toEqual(39614537) }) }) ================================================ FILE: exercises/practice/sum-of-multiples/sum-of-multiples.ts ================================================ export function sum() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/sum-of-multiples/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/sum-of-multiples/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/tournament/.docs/instructions.md ================================================ # Instructions Tally the results of a small football competition. Based on an input file containing which team played against which and what the outcome was, create a file with a table like this: ```text Team | MP | W | D | L | P Devastating Donkeys | 3 | 2 | 1 | 0 | 7 Allegoric Alaskans | 3 | 2 | 0 | 1 | 6 Blithering Badgers | 3 | 1 | 0 | 2 | 3 Courageous Californians | 3 | 0 | 1 | 2 | 1 ``` What do those abbreviations mean? - MP: Matches Played - W: Matches Won - D: Matches Drawn (Tied) - L: Matches Lost - P: Points A win earns a team 3 points. A draw earns 1. A loss earns 0. The outcome is ordered by points, descending. In case of a tie, teams are ordered alphabetically. ## Input Your tallying program will receive input that looks like: ```text Allegoric Alaskans;Blithering Badgers;win Devastating Donkeys;Courageous Californians;draw Devastating Donkeys;Allegoric Alaskans;win Courageous Californians;Blithering Badgers;loss Blithering Badgers;Devastating Donkeys;loss Allegoric Alaskans;Courageous Californians;win ``` The result of the match refers to the first team listed. So this line: ```text Allegoric Alaskans;Blithering Badgers;win ``` means that the Allegoric Alaskans beat the Blithering Badgers. This line: ```text Courageous Californians;Blithering Badgers;loss ``` means that the Blithering Badgers beat the Courageous Californians. And this line: ```text Devastating Donkeys;Courageous Californians;draw ``` means that the Devastating Donkeys and Courageous Californians tied. ================================================ FILE: exercises/practice/tournament/.meta/config.json ================================================ { "authors": [ "BrianDGLS" ], "files": { "solution": [ "tournament.ts" ], "test": [ "tournament.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Tally the results of a small football competition.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false } } ================================================ FILE: exercises/practice/tournament/.meta/proof.ci.ts ================================================ class TeamStats { public won = 0 public lost = 0 public drawn = 0 public played = 0 public get points(): number { return this.won * 3 + this.drawn } constructor(public name: string) {} public getStatTableRow(): string { const { name, won, lost, played, drawn } = this const namePad = name.padEnd(31, ' ') const wonPad = won.toString().padStart(3 - won.toString().length, ' ') const lostPad = lost.toString().padStart(3 - lost.toString().length, ' ') const drawnPad = drawn.toString().padStart(3 - drawn.toString().length, ' ') const playedPad = played .toString() .padStart(3 - played.toString().length, ' ') const poinntsPad = this.points .toString() .padStart(3 - this.points.toString().length, ' ') return `${namePad}| ${playedPad} | ${wonPad} | ${drawnPad} | ${lostPad} | ${poinntsPad}` } } export class Tournament { private tableHeader = 'Team | MP | W | D | L | P' private teams: { [key: string]: TeamStats } = {} public tally(input: string): string { if (!input) return this.tableHeader for (const line of input.split('\n')) { const [teamAName, teamBName, result] = line.split(';') if (!this.teams[teamAName]) { this.teams[teamAName] = new TeamStats(teamAName) } if (!this.teams[teamBName]) { this.teams[teamBName] = new TeamStats(teamBName) } this.applyResult(this.teams[teamAName], this.teams[teamBName], result) } return Object.keys(this.teams) .sort(this.sortByPointsAlphabetically) .reduce( (acc, team) => acc + this.teams[team].getStatTableRow() + '\n', this.tableHeader + '\n' ) .trimEnd() } private sortByPointsAlphabetically = (a: string, b: string): number => { const teamA = this.teams[a] const teamB = this.teams[b] if (teamA.points === teamB.points) { return teamA.name.localeCompare(teamB.name) } return teamB.points - teamA.points } private applyResult( teamA: TeamStats, teamB: TeamStats, result: string ): void { if (result === 'draw') { teamA.drawn++ teamB.drawn++ } if (result === 'win') { teamA.won++ teamB.lost++ } if (result === 'loss') { teamA.lost++ teamB.won++ } teamA.played++ teamB.played++ } } ================================================ FILE: exercises/practice/tournament/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [67e9fab1-07c1-49cf-9159-bc8671cc7c9c] description = "just the header if no input" [1b4a8aef-0734-4007-80a2-0626178c88f4] description = "a win is three points, a loss is zero points" [5f45ac09-4efe-46e7-8ddb-75ad85f86e05] description = "a win can also be expressed as a loss" [fd297368-efa0-442d-9f37-dd3f9a437239] description = "a different team can win" [26c016f9-e753-4a93-94e9-842f7b4d70fc] description = "a draw is one point each" [731204f6-4f34-4928-97eb-1c307ba83e62] description = "There can be more than one match" [49dc2463-42af-4ea6-95dc-f06cc5776adf] description = "There can be more than one winner" [6d930f33-435c-4e6f-9e2d-63fa85ce7dc7] description = "There can be more than two teams" [97022974-0c8a-4a50-8fe7-e36bdd8a5945] description = "typical input" [fe562f0d-ac0a-4c62-b9c9-44ee3236392b] description = "incomplete competition (not all pairs have played)" [3aa0386f-150b-4f99-90bb-5195e7b7d3b8] description = "ties broken alphabetically" [f9e20931-8a65-442a-81f6-503c0205b17a] description = "ensure points sorted numerically" ================================================ FILE: exercises/practice/tournament/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/tournament/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/tournament/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/tournament/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/tournament/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/tournament/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/tournament/package.json ================================================ { "name": "@exercism/typescript-tournament", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/tournament/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/tournament/tournament.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { Tournament } from './tournament.ts' describe('Tournament', () => { it('just the header if no input', () => { expect(new Tournament().tally('')).toBe( 'Team | MP | W | D | L | P' ) }) xit('a win is three points, a loss is zero points', () => { expect( new Tournament().tally('Allegoric Alaskans;Blithering Badgers;win') ).toBe( [ 'Team | MP | W | D | L | P', 'Allegoric Alaskans | 1 | 1 | 0 | 0 | 3', 'Blithering Badgers | 1 | 0 | 0 | 1 | 0', ].join('\n') ) }) xit('a win can also be expressed as a loss', () => { expect( new Tournament().tally('Blithering Badgers;Allegoric Alaskans;loss') ).toBe( [ 'Team | MP | W | D | L | P', 'Allegoric Alaskans | 1 | 1 | 0 | 0 | 3', 'Blithering Badgers | 1 | 0 | 0 | 1 | 0', ].join('\n') ) }) xit('a different team can win', () => { expect( new Tournament().tally('Blithering Badgers;Allegoric Alaskans;win') ).toBe( [ 'Team | MP | W | D | L | P', 'Blithering Badgers | 1 | 1 | 0 | 0 | 3', 'Allegoric Alaskans | 1 | 0 | 0 | 1 | 0', ].join('\n') ) }) xit('a draw is one point each', () => { expect( new Tournament().tally('Allegoric Alaskans;Blithering Badgers;draw') ).toBe( [ 'Team | MP | W | D | L | P', 'Allegoric Alaskans | 1 | 0 | 1 | 0 | 1', 'Blithering Badgers | 1 | 0 | 1 | 0 | 1', ].join('\n') ) }) xit('There can be more than one match', () => { expect( new Tournament().tally( [ 'Allegoric Alaskans;Blithering Badgers;win', 'Allegoric Alaskans;Blithering Badgers;win', ].join('\n') ) ).toBe( [ 'Team | MP | W | D | L | P', 'Allegoric Alaskans | 2 | 2 | 0 | 0 | 6', 'Blithering Badgers | 2 | 0 | 0 | 2 | 0', ].join('\n') ) }) xit('There can be more than one winner', () => { expect( new Tournament().tally( [ 'Allegoric Alaskans;Blithering Badgers;loss', 'Allegoric Alaskans;Blithering Badgers;win', ].join('\n') ) ).toBe( [ 'Team | MP | W | D | L | P', 'Allegoric Alaskans | 2 | 1 | 0 | 1 | 3', 'Blithering Badgers | 2 | 1 | 0 | 1 | 3', ].join('\n') ) }) xit('There can be more than two teams', () => { expect( new Tournament().tally( [ 'Allegoric Alaskans;Blithering Badgers;win', 'Blithering Badgers;Courageous Californians;win', 'Courageous Californians;Allegoric Alaskans;loss', ].join('\n') ) ).toBe( [ 'Team | MP | W | D | L | P', 'Allegoric Alaskans | 2 | 2 | 0 | 0 | 6', 'Blithering Badgers | 2 | 1 | 0 | 1 | 3', 'Courageous Californians | 2 | 0 | 0 | 2 | 0', ].join('\n') ) }) xit('typical input', () => { expect( new Tournament().tally( [ 'Allegoric Alaskans;Blithering Badgers;win', 'Devastating Donkeys;Courageous Californians;draw', 'Devastating Donkeys;Allegoric Alaskans;win', 'Courageous Californians;Blithering Badgers;loss', 'Blithering Badgers;Devastating Donkeys;loss', 'Allegoric Alaskans;Courageous Californians;win', ].join('\n') ) ).toBe( [ 'Team | MP | W | D | L | P', 'Devastating Donkeys | 3 | 2 | 1 | 0 | 7', 'Allegoric Alaskans | 3 | 2 | 0 | 1 | 6', 'Blithering Badgers | 3 | 1 | 0 | 2 | 3', 'Courageous Californians | 3 | 0 | 1 | 2 | 1', ].join('\n') ) }) xit('incomplete competition (not all pairs have played)', () => { expect( new Tournament().tally( [ 'Allegoric Alaskans;Blithering Badgers;loss', 'Devastating Donkeys;Allegoric Alaskans;loss', 'Courageous Californians;Blithering Badgers;draw', 'Allegoric Alaskans;Courageous Californians;win', ].join('\n') ) ).toBe( [ 'Team | MP | W | D | L | P', 'Allegoric Alaskans | 3 | 2 | 0 | 1 | 6', 'Blithering Badgers | 2 | 1 | 1 | 0 | 4', 'Courageous Californians | 2 | 0 | 1 | 1 | 1', 'Devastating Donkeys | 1 | 0 | 0 | 1 | 0', ].join('\n') ) }) xit('ties broken alphabetically', () => { expect( new Tournament().tally( [ 'Courageous Californians;Devastating Donkeys;win', 'Allegoric Alaskans;Blithering Badgers;win', 'Devastating Donkeys;Allegoric Alaskans;loss', 'Courageous Californians;Blithering Badgers;win', 'Blithering Badgers;Devastating Donkeys;draw', 'Allegoric Alaskans;Courageous Californians;draw', ].join('\n') ) ).toBe( [ 'Team | MP | W | D | L | P', 'Allegoric Alaskans | 3 | 2 | 1 | 0 | 7', 'Courageous Californians | 3 | 2 | 1 | 0 | 7', 'Blithering Badgers | 3 | 0 | 1 | 2 | 1', 'Devastating Donkeys | 3 | 0 | 1 | 2 | 1', ].join('\n') ) }) xit('ensure points sorted numerically', () => { expect( new Tournament().tally( [ 'Devastating Donkeys;Blithering Badgers;win', 'Devastating Donkeys;Blithering Badgers;win', 'Devastating Donkeys;Blithering Badgers;win', 'Devastating Donkeys;Blithering Badgers;win', 'Blithering Badgers;Devastating Donkeys;win', ].join('\n') ) ).toBe( [ 'Team | MP | W | D | L | P', 'Devastating Donkeys | 5 | 4 | 0 | 1 | 12', 'Blithering Badgers | 5 | 1 | 0 | 4 | 3', ].join('\n') ) }) }) ================================================ FILE: exercises/practice/tournament/tournament.ts ================================================ export class Tournament { // eslint-disable-next-line no-unused-vars public tally(input: string): string { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/tournament/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/transpose/.docs/instructions.md ================================================ # Instructions Given an input text output it transposed. Roughly explained, the transpose of a matrix: ```text ABC DEF ``` is given by: ```text AD BE CF ``` Rows become columns and columns become rows. See [transpose][]. If the input has rows of different lengths, this is to be solved as follows: - Pad to the left with spaces. - Don't pad to the right. Therefore, transposing this matrix: ```text ABC DE ``` results in: ```text AD BE C ``` And transposing: ```text AB DEF ``` results in: ```text AD BE F ``` In general, all characters from the input should also be present in the transposed output. That means that if a column in the input text contains only spaces on its bottom-most row(s), the corresponding output row should contain the spaces in its right-most column(s). [transpose]: https://en.wikipedia.org/wiki/Transpose ================================================ FILE: exercises/practice/transpose/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "peerreynders", "SleeplessByte" ], "files": { "solution": [ "transpose.ts" ], "test": [ "transpose.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Take input text and output it transposed.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Reddit r/dailyprogrammer challenge #270 [Easy].", "source_url": "https://web.archive.org/web/20230630051421/https://old.reddit.com/r/dailyprogrammer/comments/4msu2x/challenge_270_easy_transpose_the_input_text/" } ================================================ FILE: exercises/practice/transpose/.meta/proof.ci.ts ================================================ const fillColumn: (t: string[], l: string, r: number) => void = ( transposed, fromSource, toColumn ) => { for (let row = 0; row < fromSource.length; row++) { transposed[row] = (row in transposed ? transposed[row] : '').padEnd(toColumn) + fromSource.charAt(row) } } export function transpose(lines: string[]): string[] { const transposed: string[] = [] for (let toColumn = 0; toColumn < lines.length; toColumn++) fillColumn(transposed, lines[toColumn], toColumn) return transposed } ================================================ FILE: exercises/practice/transpose/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [404b7262-c050-4df0-a2a2-0cb06cd6a821] description = "empty string" [a89ce8a3-c940-4703-a688-3ea39412fbcb] description = "two characters in a row" [855bb6ae-4180-457c-abd0-ce489803ce98] description = "two characters in a column" [5ceda1c0-f940-441c-a244-0ced197769c8] description = "simple" [a54675dd-ae7d-4a58-a9c4-0c20e99a7c1f] description = "single line" [0dc2ec0b-549d-4047-aeeb-8029fec8d5c5] description = "first line longer than second line" [984e2ec3-b3d3-4b53-8bd6-96f5ef404102] description = "second line longer than first line" [eccd3784-45f0-4a3f-865a-360cb323d314] description = "mixed line length" [85b96b3f-d00c-4f80-8ca2-c8a5c9216c2d] description = "square" [b9257625-7a53-4748-8863-e08e9d27071d] description = "rectangle" [b80badc9-057e-4543-bd07-ce1296a1ea2c] description = "triangle" [76acfd50-5596-4d05-89f1-5116328a7dd9] description = "jagged triangle" ================================================ FILE: exercises/practice/transpose/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/transpose/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/transpose/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/transpose/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/transpose/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/transpose/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/transpose/package.json ================================================ { "name": "@exercism/typescript-transpose", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/transpose/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/transpose/transpose.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { transpose } from './transpose.ts' describe('Transpose', () => { it('empty string', () => { const expected: string[] = [] expect(transpose([])).toEqual(expected) }) xit('two characters in a row', () => { const expected = ['A', '1'] expect(transpose(['A1'])).toEqual(expected) }) xit('two characters in a column', () => { const expected = ['A1'] expect(transpose(['A', '1'])).toEqual(expected) }) xit('simple', () => { const expected = ['A1', 'B2', 'C3'] expect(transpose(['ABC', '123'])).toEqual(expected) }) xit('single line', () => { const expected = [ 'S', 'i', 'n', 'g', 'l', 'e', ' ', 'l', 'i', 'n', 'e', '.', ] expect(transpose(['Single line.'])).toEqual(expected) }) xit('first line longer than second line', () => { const expected = [ 'TT', 'hh', 'ee', ' ', 'ff', 'oi', 'uf', 'rt', 'th', 'h ', ' l', 'li', 'in', 'ne', 'e.', '.', ] expect(transpose(['The fourth line.', 'The fifth line.'])).toEqual(expected) }) xit('second line longer than first line', () => { const expected = [ 'TT', 'hh', 'ee', ' ', 'fs', 'ie', 'rc', 'so', 'tn', ' d', 'l ', 'il', 'ni', 'en', '.e', ' .', ] expect(transpose(['The first line.', 'The second line.'])).toEqual(expected) }) xit('square', () => { const expected = ['HEART', 'EMBER', 'ABUSE', 'RESIN', 'TREND'] expect(transpose(['HEART', 'EMBER', 'ABUSE', 'RESIN', 'TREND'])).toEqual( expected ) }) xit('rectangle', () => { const expected = [ 'FOBS', 'RULE', 'ATOP', 'CLOT', 'TIME', 'UNIT', 'RENT', 'EDGE', ] expect(transpose(['FRACTURE', 'OUTLINED', 'BLOOMING', 'SEPTETTE'])).toEqual( expected ) }) xit('triangle', () => { const expected = [ 'TEASER', ' EASER', ' ASER', ' SER', ' ER', ' R', ] expect(transpose(['T', 'EE', 'AAA', 'SSSS', 'EEEEE', 'RRRRRR'])).toEqual( expected ) }) xit('jagged triangle', () => { const expected = ['123456', '1 3456', ' 3456', ' 3 56', ' 56', ' 5'] expect(transpose(['11', '2', '3333', '444', '555555', '66666'])).toEqual( expected ) }) xit('test many lines', () => { const expected = [ 'CIFWFAWDTAWITW', 'hnrhr hohnhshh', 'o oeopotedi ea', 'rfmrmash cn t', '.a e ie fthow ', ' ia fr weh,whh', 'Trnco miae ie', 'w ciroitr btcr', 'oVivtfshfcuhhe', ' eeih a uote ', 'hrnl sdtln is', 'oot ttvh tttfh', 'un bhaeepihw a', 'saglernianeoyl', 'e,ro -trsui ol', 'h uofcu sarhu ', 'owddarrdan o m', "lhg to'egccuwi", 'deemasdaeehris', 'sr als t ists', ",ebk 'phool'h,", ' reldi ffd ', 'bweso tb rtpo', 'oea ileutterau', 't kcnoorhhnatr', "hl isvuyee'fi ", ' atv es iisfet', 'ayoior trr ino', 'l lfsoh ecti', 'ion vedpn l', 'kuehtteieadoe ', 'erwaharrar,fas', ' nekt te rh', 'ismdsehphnnosa', 'ncuse ra-tau l', ' et tormsural', "dniuthwea'g t ", 'iennwesnr hsts', 'g,ycoi tkrttet', "n ,l r s'a anr", "i ef 'dgcgdi", 't aol eoe,v', 'y nei sl,u; e', ', .sf to l ', ' e rv d t', ' ; ie o', ' f, r ', ' e e m', ' . m e', ' o n', ' v d', ' e .', ' ,', ] expect( transpose([ 'Chor. Two households, both alike in dignity,', 'In fair Verona, where we lay our scene,', 'From ancient grudge break to new mutiny,', 'Where civil blood makes civil hands unclean.', 'From forth the fatal loins of these two foes', "A pair of star-cross'd lovers take their life;", "Whose misadventur'd piteous overthrows", "Doth with their death bury their parents' strife.", "The fearful passage of their death-mark'd love,", "And the continuance of their parents' rage,", "Which, but their children's end, naught could remove,", "Is now the two hours' traffic of our stage;", 'The which if you with patient ears attend,', 'What here shall miss, our toil shall strive to mend.', ]) ).toEqual(expected) }) }) ================================================ FILE: exercises/practice/transpose/transpose.ts ================================================ export function transpose() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/transpose/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/triangle/.docs/instructions.md ================================================ # Instructions Determine if a triangle is equilateral, isosceles, or scalene. An _equilateral_ triangle has all three sides the same length. An _isosceles_ triangle has at least two sides the same length. (It is sometimes specified as having exactly two sides the same length, but for the purposes of this exercise we'll say at least two.) A _scalene_ triangle has all sides of different lengths. ## Note For a shape to be a triangle at all, all sides have to be of length > 0, and the sum of the lengths of any two sides must be greater than or equal to the length of the third side. ~~~~exercism/note _Degenerate triangles_ are triangles where the sum of the length of two sides is **equal** to the length of the third side, e.g. `1, 1, 2`. We opted to not include tests for degenerate triangles in this exercise. You may handle those situations if you wish to do so, or safely ignore them. ~~~~ In equations: Let `a`, `b`, and `c` be sides of the triangle. Then all three of the following expressions must be true: ```text a + b ≥ c b + c ≥ a a + c ≥ b ``` See [Triangle Inequality][triangle-inequality] [triangle-inequality]: https://en.wikipedia.org/wiki/Triangle_inequality ================================================ FILE: exercises/practice/triangle/.meta/config.json ================================================ { "authors": [ "anuragsoni" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "triangle.ts" ], "test": [ "triangle.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Determine if a triangle is equilateral, isosceles, or scalene.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "The Ruby Koans triangle project, parts 1 & 2", "source_url": "https://www.rubykoans.com/" } ================================================ FILE: exercises/practice/triangle/.meta/proof.ci.ts ================================================ export class Triangle { private readonly sides: number[] constructor(...sides: number[]) { this.sides = sides } public get isValid(): boolean { const [s1, s2, s3] = this.sides const sidesArePositive = s1 > 0 && s2 > 0 && s3 > 0 const validatesTriangleInequality = s1 + s2 >= s3 && s1 + s3 >= s2 && s2 + s3 >= s1 return sidesArePositive && validatesTriangleInequality } public get isEquilateral(): boolean { if (!this.isValid) { return false } const [s1, s2, s3] = this.sides return s1 === s2 && s2 === s3 && s1 === s3 } public get isIsosceles(): boolean { if (!this.isValid) { return false } const [s1, s2, s3] = this.sides return s1 === s2 || s1 === s3 || s2 === s3 } public get isScalene(): boolean { if (!this.isValid) { return false } return !this.isIsosceles } } ================================================ FILE: exercises/practice/triangle/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [8b2c43ac-7257-43f9-b552-7631a91988af] description = "equilateral triangle -> all sides are equal" [33eb6f87-0498-4ccf-9573-7f8c3ce92b7b] description = "equilateral triangle -> any side is unequal" [c6585b7d-a8c0-4ad8-8a34-e21d36f7ad87] description = "equilateral triangle -> no sides are equal" [16e8ceb0-eadb-46d1-b892-c50327479251] description = "equilateral triangle -> all zero sides is not a triangle" [3022f537-b8e5-4cc1-8f12-fd775827a00c] description = "equilateral triangle -> sides may be floats" [cbc612dc-d75a-4c1c-87fc-e2d5edd70b71] description = "isosceles triangle -> last two sides are equal" [e388ce93-f25e-4daf-b977-4b7ede992217] description = "isosceles triangle -> first two sides are equal" [d2080b79-4523-4c3f-9d42-2da6e81ab30f] description = "isosceles triangle -> first and last sides are equal" [8d71e185-2bd7-4841-b7e1-71689a5491d8] description = "isosceles triangle -> equilateral triangles are also isosceles" [840ed5f8-366f-43c5-ac69-8f05e6f10bbb] description = "isosceles triangle -> no sides are equal" [2eba0cfb-6c65-4c40-8146-30b608905eae] description = "isosceles triangle -> first triangle inequality violation" [278469cb-ac6b-41f0-81d4-66d9b828f8ac] description = "isosceles triangle -> second triangle inequality violation" [90efb0c7-72bb-4514-b320-3a3892e278ff] description = "isosceles triangle -> third triangle inequality violation" [adb4ee20-532f-43dc-8d31-e9271b7ef2bc] description = "isosceles triangle -> sides may be floats" [e8b5f09c-ec2e-47c1-abec-f35095733afb] description = "scalene triangle -> no sides are equal" [2510001f-b44d-4d18-9872-2303e7977dc1] description = "scalene triangle -> all sides are equal" [c6e15a92-90d9-4fb3-90a2-eef64f8d3e1e] description = "scalene triangle -> first and second sides are equal" [3da23a91-a166-419a-9abf-baf4868fd985] description = "scalene triangle -> first and third sides are equal" [b6a75d98-1fef-4c42-8e9a-9db854ba0a4d] description = "scalene triangle -> second and third sides are equal" [70ad5154-0033-48b7-af2c-b8d739cd9fdc] description = "scalene triangle -> may not violate triangle inequality" [26d9d59d-f8f1-40d3-ad58-ae4d54123d7d] description = "scalene triangle -> sides may be floats" ================================================ FILE: exercises/practice/triangle/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/triangle/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/triangle/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/triangle/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/triangle/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/triangle/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/triangle/package.json ================================================ { "name": "@exercism/typescript-triangle", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/triangle/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/triangle/triangle.test.ts ================================================ import { describe, xdescribe, it, expect, xit } from '@jest/globals' import { Triangle } from './triangle.ts' describe('Triangle', () => { describe('equilateral triangle', () => { it('all sides are equal', () => { const triangle = new Triangle(2, 2, 2) expect(triangle.isEquilateral).toBe(true) }) xit('any side is unequal', () => { const triangle = new Triangle(2, 3, 2) expect(triangle.isEquilateral).toBe(false) }) xit('no sides are equal', () => { const triangle = new Triangle(5, 4, 6) expect(triangle.isEquilateral).toBe(false) }) xit('all zero sides is not a triangle', () => { const triangle = new Triangle(0, 0, 0) expect(triangle.isEquilateral).toBe(false) }) xit('sides may be floats', () => { const triangle = new Triangle(0.5, 0.5, 0.5) expect(triangle.isEquilateral).toBe(true) }) }) xdescribe('isosceles triangle', () => { xit('last two sides are equal', () => { const triangle = new Triangle(3, 4, 4) expect(triangle.isIsosceles).toBe(true) }) xit('first two sides are equal', () => { const triangle = new Triangle(4, 4, 3) expect(triangle.isIsosceles).toBe(true) }) xit('first and last sides are equal', () => { const triangle = new Triangle(4, 3, 4) expect(triangle.isIsosceles).toBe(true) }) xit('equilateral triangles are also isosceles', () => { const triangle = new Triangle(4, 4, 4) expect(triangle.isIsosceles).toBe(true) }) xit('no sides are equal', () => { const triangle = new Triangle(2, 3, 4) expect(triangle.isIsosceles).toBe(false) }) xit('first triangle inequality violation', () => { const triangle = new Triangle(1, 1, 3) expect(triangle.isIsosceles).toBe(false) }) xit('second triangle inequality violation', () => { const triangle = new Triangle(1, 3, 1) expect(triangle.isIsosceles).toBe(false) }) xit('third triangle inequality violation', () => { const triangle = new Triangle(3, 1, 1) expect(triangle.isIsosceles).toBe(false) }) xit('sides may be floats', () => { const triangle = new Triangle(0.5, 0.4, 0.5) expect(triangle.isIsosceles).toBe(true) }) }) xdescribe('scalene triangle', () => { xit('no sides are equal', () => { const triangle = new Triangle(5, 4, 6) expect(triangle.isScalene).toBe(true) }) xit('all sides are equal', () => { const triangle = new Triangle(4, 4, 4) expect(triangle.isScalene).toBe(false) }) xit('first and second sides are equal', () => { const triangle = new Triangle(4, 4, 3) expect(triangle.isScalene).toBe(false) }) xit('first and third sides are equal', () => { const triangle = new Triangle(3, 4, 3) expect(triangle.isScalene).toBe(false) }) xit('second and third sides are equal', () => { const triangle = new Triangle(4, 3, 3) expect(triangle.isScalene).toBe(false) }) xit('may not violate triangle inequality', () => { const triangle = new Triangle(7, 3, 2) expect(triangle.isScalene).toBe(false) }) xit('sides may be floats', () => { const triangle = new Triangle(0.5, 0.4, 0.6) expect(triangle.isScalene).toBe(true) }) }) }) ================================================ FILE: exercises/practice/triangle/triangle.ts ================================================ export class Triangle { constructor(...sides) { throw new Error('Remove this line and implement the function') } get isEquilateral() { throw new Error('Remove this line and implement the function') } get isIsosceles() { throw new Error('Remove this line and implement the function') } get isScalene() { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/triangle/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/twelve-days/.docs/instructions.md ================================================ # Instructions Your task in this exercise is to write code that returns the lyrics of the song: "The Twelve Days of Christmas." "The Twelve Days of Christmas" is a common English Christmas carol. Each subsequent verse of the song builds on the previous verse. The lyrics your code returns should _exactly_ match the full song text shown below. ## Lyrics ```text On the first day of Christmas my true love gave to me: a Partridge in a Pear Tree. On the second day of Christmas my true love gave to me: two Turtle Doves, and a Partridge in a Pear Tree. On the third day of Christmas my true love gave to me: three French Hens, two Turtle Doves, and a Partridge in a Pear Tree. On the fourth day of Christmas my true love gave to me: four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree. On the fifth day of Christmas my true love gave to me: five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree. On the sixth day of Christmas my true love gave to me: six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree. On the seventh day of Christmas my true love gave to me: seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree. On the eighth day of Christmas my true love gave to me: eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree. On the ninth day of Christmas my true love gave to me: nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree. On the tenth day of Christmas my true love gave to me: ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree. On the eleventh day of Christmas my true love gave to me: eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree. On the twelfth day of Christmas my true love gave to me: twelve Drummers Drumming, eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree. ``` ================================================ FILE: exercises/practice/twelve-days/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "PatrickMcSweeny", "SleeplessByte" ], "files": { "solution": [ "twelve-days.ts" ], "test": [ "twelve-days.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Output the lyrics to 'The Twelve Days of Christmas'.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/The_Twelve_Days_of_Christmas_(song)" } ================================================ FILE: exercises/practice/twelve-days/.meta/proof.ci.ts ================================================ const days = [ 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth', 'ninth', 'tenth', 'eleventh', 'twelfth', ] as const const gifts = [ 'a Partridge in a Pear Tree.', 'two Turtle Doves, ', 'three French Hens, ', 'four Calling Birds, ', 'five Gold Rings, ', 'six Geese-a-Laying, ', 'seven Swans-a-Swimming, ', 'eight Maids-a-Milking, ', 'nine Ladies Dancing, ', 'ten Lords-a-Leaping, ', 'eleven Pipers Piping, ', 'twelve Drummers Drumming, ', ] as const export function recite(startVerse: number, endVerse: number): string { let lyrics = reciteVerse(startVerse) for (let i = startVerse + 1; i <= endVerse; i++) { lyrics += reciteVerse(i) } return lyrics } function reciteVerse(verse: number): string { let result = 'On the ' + days[verse - 1] + ' day of Christmas my true love gave to me: ' for (let i = verse; i > 0; i--) { if (verse !== 1 && i === 1) { result += 'and ' } result += gifts[i - 1] } result += '\n' return result } ================================================ FILE: exercises/practice/twelve-days/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [c0b5a5e6-c89d-49b1-a6b2-9f523bff33f7] description = "first day a partridge in a pear tree" [1c64508a-df3d-420a-b8e1-fe408847854a] description = "second day two turtle doves" [a919e09c-75b2-4e64-bb23-de4a692060a8] description = "third day three french hens" [9bed8631-ec60-4894-a3bb-4f0ec9fbe68d] description = "fourth day four calling birds" [cf1024f0-73b6-4545-be57-e9cea565289a] description = "fifth day five gold rings" [50bd3393-868a-4f24-a618-68df3d02ff04] description = "sixth day six geese-a-laying" [8f29638c-9bf1-4680-94be-e8b84e4ade83] description = "seventh day seven swans-a-swimming" [7038d6e1-e377-47ad-8c37-10670a05bc05] description = "eighth day eight maids-a-milking" [37a800a6-7a56-4352-8d72-0f51eb37cfe8] description = "ninth day nine ladies dancing" [10b158aa-49ff-4b2d-afc3-13af9133510d] description = "tenth day ten lords-a-leaping" [08d7d453-f2ba-478d-8df0-d39ea6a4f457] description = "eleventh day eleven pipers piping" [0620fea7-1704-4e48-b557-c05bf43967f0] description = "twelfth day twelve drummers drumming" [da8b9013-b1e8-49df-b6ef-ddec0219e398] description = "recites first three verses of the song" [c095af0d-3137-4653-ad32-bfb899eda24c] description = "recites three verses from the middle of the song" [20921bc9-cc52-4627-80b3-198cbbfcf9b7] description = "recites the whole song" ================================================ FILE: exercises/practice/twelve-days/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/twelve-days/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/twelve-days/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/twelve-days/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/twelve-days/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/twelve-days/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/twelve-days/package.json ================================================ { "name": "@exercism/typescript-twelve-days", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/twelve-days/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/twelve-days/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/twelve-days/twelve-days.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { recite } from './twelve-days.ts' describe('verse', () => { it('first day a partridge in a pear tree', () => { const expected = 'On the first day of Christmas my true love gave to me: a Partridge in a Pear Tree.\n' expect(recite(1, 1)).toEqual(expected) }) xit('second day two turtle doves', () => { const expected = 'On the second day of Christmas my true love gave to me: two Turtle Doves, and a Partridge in a Pear Tree.\n' expect(recite(2, 2)).toEqual(expected) }) xit('third day three french hens', () => { const expected = 'On the third day of Christmas my true love gave to me: three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' expect(recite(3, 3)).toEqual(expected) }) xit('fourth day four calling birds', () => { const expected = 'On the fourth day of Christmas my true love gave to me: four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' expect(recite(4, 4)).toEqual(expected) }) xit('fifth day five gold rings', () => { const expected = 'On the fifth day of Christmas my true love gave to me: five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' expect(recite(5, 5)).toEqual(expected) }) xit('sixth day six geese-a-laying', () => { const expected = 'On the sixth day of Christmas my true love gave to me: six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' expect(recite(6, 6)).toEqual(expected) }) xit('seventh day seven swans-a-swimming', () => { const expected = 'On the seventh day of Christmas my true love gave to me: seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' expect(recite(7, 7)).toEqual(expected) }) xit('eighth day eight maids-a-milking', () => { const expected = 'On the eighth day of Christmas my true love gave to me: eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' expect(recite(8, 8)).toEqual(expected) }) xit('ninth day nine ladies dancing', () => { const expected = 'On the ninth day of Christmas my true love gave to me: nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' expect(recite(9, 9)).toEqual(expected) }) xit('tenth day ten lords-a-leaping', () => { const expected = 'On the tenth day of Christmas my true love gave to me: ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' expect(recite(10, 10)).toEqual(expected) }) xit('eleventh day eleven pipers piping', () => { const expected = 'On the eleventh day of Christmas my true love gave to me: eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' expect(recite(11, 11)).toEqual(expected) }) xit('twelfth day twelve drummers drumming', () => { const expected = 'On the twelfth day of Christmas my true love gave to me: twelve Drummers Drumming, eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' expect(recite(12, 12)).toEqual(expected) }) }) describe('lyrics', () => { xit('recites first three verses of the song', () => { const expected = 'On the first day of Christmas my true love gave to me: a Partridge in a Pear Tree.\nOn the second day of Christmas my true love gave to me: two Turtle Doves, and a Partridge in a Pear Tree.\nOn the third day of Christmas my true love gave to me: three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' expect(recite(1, 3)).toEqual(expected) }) xit('recites three verses from the middle of the song', () => { const expected = 'On the fourth day of Christmas my true love gave to me: four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the fifth day of Christmas my true love gave to me: five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the sixth day of Christmas my true love gave to me: six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' expect(recite(4, 6)).toEqual(expected) }) xit('recites the whole song', () => { const expected = 'On the first day of Christmas my true love gave to me: a Partridge in a Pear Tree.\nOn the second day of Christmas my true love gave to me: two Turtle Doves, and a Partridge in a Pear Tree.\nOn the third day of Christmas my true love gave to me: three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the fourth day of Christmas my true love gave to me: four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the fifth day of Christmas my true love gave to me: five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the sixth day of Christmas my true love gave to me: six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the seventh day of Christmas my true love gave to me: seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the eighth day of Christmas my true love gave to me: eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the ninth day of Christmas my true love gave to me: nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the tenth day of Christmas my true love gave to me: ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the eleventh day of Christmas my true love gave to me: eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\nOn the twelfth day of Christmas my true love gave to me: twelve Drummers Drumming, eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n' expect(recite(1, 12)).toEqual(expected) }) }) ================================================ FILE: exercises/practice/twelve-days/twelve-days.ts ================================================ export function recite() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/two-bucket/.docs/instructions.md ================================================ # Instructions Given two buckets of different size and which bucket to fill first, determine how many actions are required to measure an exact number of liters by strategically transferring fluid between the buckets. There are some rules that your solution must follow: - You can only do one action at a time. - There are only 3 possible actions: 1. Pouring one bucket into the other bucket until either: a) the first bucket is empty b) the second bucket is full 2. Emptying a bucket and doing nothing to the other. 3. Filling a bucket and doing nothing to the other. - After an action, you may not arrive at a state where the initial starting bucket is empty and the other bucket is full. Your program will take as input: - the size of bucket one - the size of bucket two - the desired number of liters to reach - which bucket to fill first, either bucket one or bucket two Your program should determine: - the total number of actions it should take to reach the desired number of liters, including the first fill of the starting bucket - which bucket should end up with the desired number of liters - either bucket one or bucket two - how many liters are left in the other bucket Note: any time a change is made to either or both buckets counts as one (1) action. Example: Bucket one can hold up to 7 liters, and bucket two can hold up to 11 liters. Let's say at a given step, bucket one is holding 7 liters and bucket two is holding 8 liters (7,8). If you empty bucket one and make no change to bucket two, leaving you with 0 liters and 8 liters respectively (0,8), that counts as one action. Instead, if you had poured from bucket one into bucket two until bucket two was full, resulting in 4 liters in bucket one and 11 liters in bucket two (4,11), that would also only count as one action. Another Example: Bucket one can hold 3 liters, and bucket two can hold up to 5 liters. You are told you must start with bucket one. So your first action is to fill bucket one. You choose to empty bucket one for your second action. For your third action, you may not fill bucket two, because this violates the third rule -- you may not end up in a state after any action where the starting bucket is empty and the other bucket is full. Written with <3 at [Fullstack Academy][fullstack] by Lindsay Levine. [fullstack]: https://www.fullstackacademy.com/ ================================================ FILE: exercises/practice/two-bucket/.meta/config.json ================================================ { "authors": [], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "two-bucket.ts" ], "test": [ "two-bucket.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given two buckets of different size, demonstrate how to measure an exact number of liters.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Water Pouring Problem", "source_url": "https://demonstrations.wolfram.com/WaterPouringProblem/" } ================================================ FILE: exercises/practice/two-bucket/.meta/proof.ci.ts ================================================ class Bucket { public name: string public readonly size: number public amount: number constructor(name: string, size: number) { this.name = name this.size = size this.amount = 0 } // accessors public get available(): number { return this.size - this.amount } public get isFull(): boolean { return this.amount === this.size } public get isEmpty(): boolean { return this.amount === 0 } public fill(): void { this.amount = this.size } public empty(): void { this.amount = 0 } public pourInto(other: Bucket): void { const quantity = Math.min(this.amount, other.available) this.amount -= quantity other.amount += quantity } } const gcd: (a: number, b: number) => number = (a, b) => b === 0 ? a : gcd(b, a % b) export class TwoBucket { private readonly buckets: Bucket[] private readonly goal: number public goalBucket: string | undefined public otherBucket: number | undefined constructor(size1: number, size2: number, goal: number, start: string) { this.goal = goal this.buckets = [new Bucket('one', size1), new Bucket('two', size2)] if (start === 'two') { this.buckets.reverse() } } private get first(): Bucket { return this.buckets[0] } private get second(): Bucket { return this.buckets[1] } private validate(): void { if (this.goal > Math.max(this.first.size, this.second.size)) { throw new Error('Goal is bigger than the largest bucket.') } if (this.goal % gcd(this.first.size, this.second.size) !== 0) { throw new Error( 'Goal must be a multiple of the GCD of the sizes of the two buckets.' ) } } private moves(): number { this.validate() this.first.empty() this.second.empty() let moves = 0 // fill the start bucket with the first move this.first.fill() moves += 1 // optimization: if the other bucket is the right size, // fill it immediately with the second move if (this.second.size === this.goal) { this.second.fill() moves += 1 } while (true) { if (this.first.amount === this.goal) { this.goalBucket = this.first.name this.otherBucket = this.second.amount return moves } if (this.second.amount === this.goal) { this.goalBucket = this.second.name this.otherBucket = this.first.amount return moves } if (this.first.isEmpty) { this.first.fill() } else if (this.second.isFull) { this.second.empty() } else { this.first.pourInto(this.second) } moves += 1 } } } ================================================ FILE: exercises/practice/two-bucket/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [a6f2b4ba-065f-4dca-b6f0-e3eee51cb661] description = "Measure using bucket one of size 3 and bucket two of size 5 - start with bucket one" [6c4ea451-9678-4926-b9b3-68364e066d40] description = "Measure using bucket one of size 3 and bucket two of size 5 - start with bucket two" [3389f45e-6a56-46d5-9607-75aa930502ff] description = "Measure using bucket one of size 7 and bucket two of size 11 - start with bucket one" [fe0ff9a0-3ea5-4bf7-b17d-6d4243961aa1] description = "Measure using bucket one of size 7 and bucket two of size 11 - start with bucket two" [0ee1f57e-da84-44f7-ac91-38b878691602] description = "Measure one step using bucket one of size 1 and bucket two of size 3 - start with bucket two" [eb329c63-5540-4735-b30b-97f7f4df0f84] description = "Measure using bucket one of size 2 and bucket two of size 3 - start with bucket one and end with bucket two" [58d70152-bf2b-46bb-ad54-be58ebe94c03] description = "Measure using bucket one much bigger than bucket two" [9dbe6499-caa5-4a58-b5ce-c988d71b8981] description = "Measure using bucket one much smaller than bucket two" [449be72d-b10a-4f4b-a959-ca741e333b72] description = "Not possible to reach the goal" [aac38b7a-77f4-4d62-9b91-8846d533b054] description = "With the same buckets but a different goal, then it is possible" [74633132-0ccf-49de-8450-af4ab2e3b299] description = "Goal larger than both buckets is impossible" ================================================ FILE: exercises/practice/two-bucket/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/two-bucket/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/two-bucket/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/two-bucket/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/two-bucket/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/two-bucket/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/two-bucket/package.json ================================================ { "name": "@exercism/typescript-two-bucket", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/two-bucket/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/two-bucket/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/two-bucket/two-bucket.test.ts ================================================ import { describe, xdescribe, it, expect, xit } from '@jest/globals' import { TwoBucket } from './two-bucket.ts' describe('TwoBucket', () => { describe('Measure using bucket one of size 3 and bucket two of size 5', () => { const buckOne = 3 const buckTwo = 5 const goal = 1 it('start with bucket one', () => { // indicates which bucket to fill first const starterBuck = 'one' const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck) // includes the first fill expect(twoBucket.moves()).toEqual(4) // which bucket should end up with the desired # of liters expect(twoBucket.goalBucket).toEqual('one') // leftover value in the "other" bucket once the goal has been reached expect(twoBucket.otherBucket).toEqual(5) }) xit('start with bucket two', () => { const starterBuck = 'two' const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck) expect(twoBucket.moves()).toEqual(8) expect(twoBucket.goalBucket).toEqual('two') expect(twoBucket.otherBucket).toEqual(3) }) }) xdescribe('Measure using bucket one of size 7 and bucket two of size 11', () => { const buckOne = 7 const buckTwo = 11 const goal = 2 xit('start with bucket one', () => { const starterBuck = 'one' const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck) expect(twoBucket.moves()).toEqual(14) expect(twoBucket.goalBucket).toEqual('one') expect(twoBucket.otherBucket).toEqual(11) }) xit('start with bucket two', () => { const starterBuck = 'two' const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck) expect(twoBucket.moves()).toEqual(18) expect(twoBucket.goalBucket).toEqual('two') expect(twoBucket.otherBucket).toEqual(7) }) }) xdescribe('Measure one step using bucket one of size 1 and bucket two of size 3', () => { xit('start with bucket two', () => { const twoBucket = new TwoBucket(1, 3, 3, 'two') expect(twoBucket.moves()).toEqual(1) expect(twoBucket.goalBucket).toEqual('two') expect(twoBucket.otherBucket).toEqual(0) }) }) xdescribe('Measure using bucket one of size 2 and bucket two of size 3', () => { xit('start with bucket one and end with bucket two', () => { const twoBucket = new TwoBucket(2, 3, 3, 'one') expect(twoBucket.moves()).toEqual(2) expect(twoBucket.goalBucket).toEqual('two') expect(twoBucket.otherBucket).toEqual(2) }) }) xdescribe('Very different sizes', () => { xit('measure using bucket one much bigger than bucket two', () => { const twoBucket = new TwoBucket(5, 1, 2, 'one') expect(twoBucket.moves()).toEqual(6) expect(twoBucket.goalBucket).toEqual('one') expect(twoBucket.otherBucket).toEqual(1) }) xit('measure using bucket one much smaller than bucket two', () => { const twoBucket = new TwoBucket(3, 15, 9, 'one') expect(twoBucket.moves()).toEqual(6) expect(twoBucket.goalBucket).toEqual('two') expect(twoBucket.otherBucket).toEqual(0) }) }) xdescribe('Reachability', () => { const buckOne = 6 const buckTwo = 15 const starterBuck = 'one' xit('Not possible to reach the goal', () => { const goal = 5 const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck) expect(() => twoBucket.moves()).toThrow() }) xit('With the same buckets but a different goal, then it is possible', () => { const goal = 9 const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck) expect(twoBucket.moves()).toEqual(10) expect(twoBucket.goalBucket).toEqual('two') expect(twoBucket.otherBucket).toEqual(0) }) }) xdescribe('Goal larger than both buckets', () => { xit('Is impossible', () => { const twoBucket = new TwoBucket(5, 7, 8, 'one') expect(() => twoBucket.moves()).toThrow() }) }) }) ================================================ FILE: exercises/practice/two-bucket/two-bucket.ts ================================================ export class TwoBucket { constructor() { throw new Error('Remove this line and implement the function') } moves() { throw new Error('Remove this line and implement the function') } get goalBucket() { throw new Error('Remove this line and implement the function') } get otherBucket() { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/two-fer/.docs/instructions.md ================================================ # Instructions Your task is to determine what you will say as you give away the extra cookie. If you know the person's name (e.g. if they're named Do-yun), then you will say: ```text One for Do-yun, one for me. ``` If you don't know the person's name, you will say _you_ instead. ```text One for you, one for me. ``` Here are some examples: | Name | Dialogue | | :----- | :-------------------------- | | Alice | One for Alice, one for me. | | Bohdan | One for Bohdan, one for me. | | | One for you, one for me. | | Zaphod | One for Zaphod, one for me. | ================================================ FILE: exercises/practice/two-fer/.docs/introduction.md ================================================ # Introduction In some English accents, when you say "two for" quickly, it sounds like "two fer". Two-for-one is a way of saying that if you buy one, you also get one for free. So the phrase "two-fer" often implies a two-for-one offer. Imagine a bakery that has a holiday offer where you can buy two cookies for the price of one ("two-fer one!"). You take the offer and (very generously) decide to give the extra cookie to someone else in the queue. ================================================ FILE: exercises/practice/two-fer/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "two-fer.ts" ], "test": [ "two-fer.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Create a sentence of the form \"One for X, one for me.\".", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source_url": "https://github.com/exercism/problem-specifications/issues/757" } ================================================ FILE: exercises/practice/two-fer/.meta/proof.ci.ts ================================================ export function twoFer(name = 'you'): string { return `One for ${name}, one for me.` } ================================================ FILE: exercises/practice/two-fer/.meta/tests.toml ================================================ # This is an auto-generated file. Regular comments will be removed when this # file is regenerated. Regenerating will not touch any manually added keys, # so comments can be added in a "comment" key. [1cf3e15a-a3d7-4a87-aeb3-ba1b43bc8dce] description = "no name given" [b4c6dbb8-b4fb-42c2-bafd-10785abe7709] description = "a name given" [3549048d-1a6e-4653-9a79-b0bda163e8d5] description = "another name given" ================================================ FILE: exercises/practice/two-fer/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/two-fer/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/two-fer/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/two-fer/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/two-fer/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/two-fer/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/two-fer/package.json ================================================ { "name": "@exercism/typescript-two-fer", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/two-fer/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/two-fer/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/two-fer/two-fer.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { twoFer } from './two-fer.ts' describe('TwoFer', () => { it('no name given', () => { const expected = 'One for you, one for me.' expect(twoFer()).toEqual(expected) }) xit('a name given', () => { const expected = 'One for Alice, one for me.' expect(twoFer('Alice')).toEqual(expected) }) xit('another name given', () => { const expected = 'One for Bob, one for me.' expect(twoFer('Bob')).toEqual(expected) }) }) ================================================ FILE: exercises/practice/two-fer/two-fer.ts ================================================ /** * This stub is provided to make it straightforward to get started. */ export function twoFer(): string { // ^ ^ ^ this is called a return type; it's the type of the // ^ ^ value that is returned from this function // ^ ^ // ^ parameters go here // ^ // allows the tests to import this function and call it // <-- Your code goes here. You may remove all the commentary in this file. } ================================================ FILE: exercises/practice/variable-length-quantity/.docs/instructions.md ================================================ # Instructions Implement variable length quantity encoding and decoding. The goal of this exercise is to implement [VLQ][vlq] encoding/decoding. In short, the goal of this encoding is to encode integer values in a way that would save bytes. Only the first 7 bits of each byte are significant (right-justified; sort of like an ASCII byte). So, if you have a 32-bit value, you have to unpack it into a series of 7-bit bytes. Of course, you will have a variable number of bytes depending upon your integer. To indicate which is the last byte of the series, you leave bit #7 clear. In all of the preceding bytes, you set bit #7. So, if an integer is between `0-127`, it can be represented as one byte. Although VLQ can deal with numbers of arbitrary sizes, for this exercise we will restrict ourselves to only numbers that fit in a 32-bit unsigned integer. Here are examples of integers as 32-bit values, and the variable length quantities that they translate to: ```text NUMBER VARIABLE QUANTITY 00000000 00 00000040 40 0000007F 7F 00000080 81 00 00002000 C0 00 00003FFF FF 7F 00004000 81 80 00 00100000 C0 80 00 001FFFFF FF FF 7F 00200000 81 80 80 00 08000000 C0 80 80 00 0FFFFFFF FF FF FF 7F ``` [vlq]: https://en.wikipedia.org/wiki/Variable-length_quantity ================================================ FILE: exercises/practice/variable-length-quantity/.meta/config.json ================================================ { "authors": [ "CRivasGomez" ], "contributors": [ "masters3d", "SleeplessByte" ], "files": { "solution": [ "variable-length-quantity.ts" ], "test": [ "variable-length-quantity.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Implement variable length quantity encoding and decoding.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "A poor Splice developer having to implement MIDI encoding/decoding.", "source_url": "https://splice.com" } ================================================ FILE: exercises/practice/variable-length-quantity/.meta/proof.ci.ts ================================================ const LENGTH = 7 const CONT_BITS = 1 << LENGTH const DATA_BITS = CONT_BITS - 1 function encodeOne(val: number): number[] { const buf: number[] = [] let left = val while (left) { const bits = (left & DATA_BITS) | CONT_BITS // set continuation everywhere left = left >>> LENGTH buf.push(bits) } buf[0] = buf[0] & DATA_BITS // cancel the last continuation return buf.reverse() } function decodeOne(buf: number[]): number { let val = 0 for (let i = 0; i <= buf.length - 1; i++) { val = (val << LENGTH) | (buf[i] & DATA_BITS) } return val >>> 0 // convert to unsigned 32-bit } export function encode(data: number[]): number[] { let buf: number[] = [] for (let i = 0; i <= data.length - 1; i++) { buf = buf.concat(encodeOne(data[i])) } return buf } export function decode(data: number[]): number[] { let start = 0 const vals = [] for (let i = 0; i < data.length; i++) { if (~data[i] & CONT_BITS) { vals.push(decodeOne(data.slice(start, i + 1))) start = i + 1 } } if (start < data.length) { throw new Error('Incomplete sequence') } return vals } ================================================ FILE: exercises/practice/variable-length-quantity/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [35c9db2e-f781-4c52-b73b-8e76427defd0] description = "Encode a series of integers, producing a series of bytes. -> zero" [be44d299-a151-4604-a10e-d4b867f41540] description = "Encode a series of integers, producing a series of bytes. -> arbitrary single byte" [890bc344-cb80-45af-b316-6806a6971e81] description = "Encode a series of integers, producing a series of bytes. -> asymmetric single byte" [ea399615-d274-4af6-bbef-a1c23c9e1346] description = "Encode a series of integers, producing a series of bytes. -> largest single byte" [77b07086-bd3f-4882-8476-8dcafee79b1c] description = "Encode a series of integers, producing a series of bytes. -> smallest double byte" [63955a49-2690-4e22-a556-0040648d6b2d] description = "Encode a series of integers, producing a series of bytes. -> arbitrary double byte" [4977d113-251b-4d10-a3ad-2f5a7756bb58] description = "Encode a series of integers, producing a series of bytes. -> asymmetric double byte" [29da7031-0067-43d3-83a7-4f14b29ed97a] description = "Encode a series of integers, producing a series of bytes. -> largest double byte" [3345d2e3-79a9-4999-869e-d4856e3a8e01] description = "Encode a series of integers, producing a series of bytes. -> smallest triple byte" [5df0bc2d-2a57-4300-a653-a75ee4bd0bee] description = "Encode a series of integers, producing a series of bytes. -> arbitrary triple byte" [6731045f-1e00-4192-b5ae-98b22e17e9f7] description = "Encode a series of integers, producing a series of bytes. -> asymmetric triple byte" [f51d8539-312d-4db1-945c-250222c6aa22] description = "Encode a series of integers, producing a series of bytes. -> largest triple byte" [da78228b-544f-47b7-8bfe-d16b35bbe570] description = "Encode a series of integers, producing a series of bytes. -> smallest quadruple byte" [11ed3469-a933-46f1-996f-2231e05d7bb6] description = "Encode a series of integers, producing a series of bytes. -> arbitrary quadruple byte" [b45ef770-cbba-48c2-bd3c-c6362679516e] description = "Encode a series of integers, producing a series of bytes. -> asymmetric quadruple byte" [d5f3f3c3-e0f1-4e7f-aad0-18a44f223d1c] description = "Encode a series of integers, producing a series of bytes. -> largest quadruple byte" [91a18b33-24e7-4bfb-bbca-eca78ff4fc47] description = "Encode a series of integers, producing a series of bytes. -> smallest quintuple byte" [5f34ff12-2952-4669-95fe-2d11b693d331] description = "Encode a series of integers, producing a series of bytes. -> arbitrary quintuple byte" [9be46731-7cd5-415c-b960-48061cbc1154] description = "Encode a series of integers, producing a series of bytes. -> asymmetric quintuple byte" [7489694b-88c3-4078-9864-6fe802411009] description = "Encode a series of integers, producing a series of bytes. -> maximum 32-bit integer input" [f9b91821-cada-4a73-9421-3c81d6ff3661] description = "Encode a series of integers, producing a series of bytes. -> two single-byte values" [68694449-25d2-4974-ba75-fa7bb36db212] description = "Encode a series of integers, producing a series of bytes. -> two multi-byte values" [51a06b5c-de1b-4487-9a50-9db1b8930d85] description = "Encode a series of integers, producing a series of bytes. -> many multi-byte values" [baa73993-4514-4915-bac0-f7f585e0e59a] description = "Decode a series of bytes, producing a series of integers. -> one byte" [72e94369-29f9-46f2-8c95-6c5b7a595aee] description = "Decode a series of bytes, producing a series of integers. -> two bytes" [df5a44c4-56f7-464e-a997-1db5f63ce691] description = "Decode a series of bytes, producing a series of integers. -> three bytes" [1bb58684-f2dc-450a-8406-1f3452aa1947] description = "Decode a series of bytes, producing a series of integers. -> four bytes" [cecd5233-49f1-4dd1-a41a-9840a40f09cd] description = "Decode a series of bytes, producing a series of integers. -> maximum 32-bit integer" [e7d74ba3-8b8e-4bcb-858d-d08302e15695] description = "Decode a series of bytes, producing a series of integers. -> incomplete sequence causes error" [aa378291-9043-4724-bc53-aca1b4a3fcb6] description = "Decode a series of bytes, producing a series of integers. -> incomplete sequence causes error, even if value is zero" [a91e6f5a-c64a-48e3-8a75-ce1a81e0ebee] description = "Decode a series of bytes, producing a series of integers. -> multiple values" ================================================ FILE: exercises/practice/variable-length-quantity/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/variable-length-quantity/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/variable-length-quantity/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/variable-length-quantity/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/variable-length-quantity/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/variable-length-quantity/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/variable-length-quantity/package.json ================================================ { "name": "@exercism/typescript-variable-length-quantity", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/variable-length-quantity/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/variable-length-quantity/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/variable-length-quantity/variable-length-quantity.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { encode, decode } from './variable-length-quantity.ts' describe('VariableLengthQuantity', () => { describe('Encode a series of integers, producing a series of bytes.', () => { it('zero', () => { expect(encode([0])).toEqual([0]) }) xit('arbitrary single byte', () => { expect(encode([0x40])).toEqual([0x40]) }) xit('asymmetric single byte', () => { expect(encode([0x53])).toEqual([0x53]) }) xit('largest single byte', () => { expect(encode([0x7f])).toEqual([0x7f]) }) xit('smallest double byte', () => { expect(encode([0x80])).toEqual([0x81, 0]) }) xit('arbitrary double byte', () => { expect(encode([0x2000])).toEqual([0xc0, 0]) }) xit('asymmetric double byte', () => { expect(encode([0xad])).toEqual([0x81, 0x2d]) }) xit('largest double byte', () => { expect(encode([0x3fff])).toEqual([0xff, 0x7f]) }) xit('smallest triple byte', () => { expect(encode([0x4000])).toEqual([0x81, 0x80, 0]) }) xit('arbitrary triple byte', () => { expect(encode([0x100000])).toEqual([0xc0, 0x80, 0]) }) xit('asymmetric triple byte', () => { expect(encode([0x1d59c])).toEqual([0x87, 0xab, 0x1c]) }) xit('largest triple byte', () => { expect(encode([0x1fffff])).toEqual([0xff, 0xff, 0x7f]) }) xit('smallest quadruple byte', () => { expect(encode([0x200000])).toEqual([0x81, 0x80, 0x80, 0]) }) xit('arbitrary quadruple byte', () => { expect(encode([0x8000000])).toEqual([0xc0, 0x80, 0x80, 0]) }) xit('asymmetric quadruple byte', () => { expect(encode([0x357704])).toEqual([0x81, 0xd5, 0xee, 0x04]) }) xit('largest quadruple byte', () => { expect(encode([0xfffffff])).toEqual([0xff, 0xff, 0xff, 0x7f]) }) xit('smallest quintuple byte', () => { expect(encode([0x10000000])).toEqual([0x81, 0x80, 0x80, 0x80, 0]) }) xit('arbitrary quintuple byte', () => { expect(encode([0xff000000])).toEqual([0x8f, 0xf8, 0x80, 0x80, 0]) }) xit('asymmetric quintuple byte', () => { expect(encode([0x86656105])).toEqual([0x88, 0xb3, 0x95, 0xc2, 0x05]) }) xit('maximum 32-bit integer input', () => { expect(encode([0xffffffff])).toEqual([0x8f, 0xff, 0xff, 0xff, 0x7f]) }) xit('two single-byte values', () => { expect(encode([0x40, 0x7f])).toEqual([0x40, 0x7f]) }) xit('two multi-byte values', () => { expect(encode([0x4000, 0x123456])).toEqual([ 0x81, 0x80, 0, 0xc8, 0xe8, 0x56, ]) }) xit('many multi-byte values', () => { const input = [0x2000, 0x123456, 0xfffffff, 0, 0x3fff, 0x4000] const expected = [ 0xc0, 0, 0xc8, 0xe8, 0x56, 0xff, 0xff, 0xff, 0x7f, 0, 0xff, 0x7f, 0x81, 0x80, 0, ] expect(encode(input)).toEqual(expected) }) }) describe('Decode a series of bytes, producing a series of integers.', () => { xit('one byte', () => { expect(decode([0x7f])).toEqual([0x7f]) }) xit('two bytes', () => { expect(decode([0xc0, 0])).toEqual([0x2000]) }) xit('three bytes', () => { expect(decode([0xff, 0xff, 0x7f])).toEqual([0x1fffff]) }) xit('four bytes', () => { expect(decode([0x81, 0x80, 0x80, 0])).toEqual([0x200000]) }) xit('maximum 32-bit integer', () => { expect(decode([0x8f, 0xff, 0xff, 0xff, 0x7f])).toEqual([0xffffffff]) }) xit('incomplete sequence causes error', () => { expect(() => { decode([0xff]) }).toThrow('Incomplete sequence') }) xit('incomplete sequence causes error, even if value is zero', () => { expect(() => { decode([0x80]) }).toThrow('Incomplete sequence') }) xit('multiple values', () => { const input = [ 0xc0, 0, 0xc8, 0xe8, 0x56, 0xff, 0xff, 0xff, 0x7f, 0, 0xff, 0x7f, 0x81, 0x80, 0, ] const expected = [0x2000, 0x123456, 0xfffffff, 0, 0x3fff, 0x4000] expect(decode(input)).toEqual(expected) }) }) }) ================================================ FILE: exercises/practice/variable-length-quantity/variable-length-quantity.ts ================================================ export function encode() { throw new Error('Remove this line and implement the function') } export function decode() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/word-count/.docs/instructions.md ================================================ # Instructions Your task is to count how many times each word occurs in a subtitle of a drama. The subtitles from these dramas use only ASCII characters. The characters often speak in casual English, using contractions like _they're_ or _it's_. Though these contractions come from two words (e.g. _we are_), the contraction (_we're_) is considered a single word. Words can be separated by any form of punctuation (e.g. ":", "!", or "?") or whitespace (e.g. "\t", "\n", or " "). The only punctuation that does not separate words is the apostrophe in contractions. Numbers are considered words. If the subtitles say _It costs 100 dollars._ then _100_ will be its own word. Words are case insensitive. For example, the word _you_ occurs three times in the following sentence: > You come back, you hear me? DO YOU HEAR ME? The ordering of the word counts in the results doesn't matter. Here's an example that incorporates several of the elements discussed above: - simple words - contractions - numbers - case insensitive words - punctuation (including apostrophes) to separate words - different forms of whitespace to separate words `"That's the password: 'PASSWORD 123'!", cried the Special Agent.\nSo I fled.` The mapping for this subtitle would be: ```text 123: 1 agent: 1 cried: 1 fled: 1 i: 1 password: 2 so: 1 special: 1 that's: 1 the: 2 ``` ================================================ FILE: exercises/practice/word-count/.docs/introduction.md ================================================ # Introduction You teach English as a foreign language to high school students. You've decided to base your entire curriculum on TV shows. You need to analyze which words are used, and how often they're repeated. This will let you choose the simplest shows to start with, and to gradually increase the difficulty as time passes. ================================================ FILE: exercises/practice/word-count/.meta/config.json ================================================ { "authors": [ "masters3d" ], "contributors": [ "lukaszklis", "SleeplessByte", "angelikatyborska" ], "files": { "solution": [ "word-count.ts" ], "test": [ "word-count.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Given a phrase, count the occurrences of each word in that phrase.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "This is a classic toy problem, but we were reminded of it by seeing it in the Go Tour." } ================================================ FILE: exercises/practice/word-count/.meta/proof.ci.ts ================================================ export function count(input: string): Map { const inputArray = input .trim() .toLowerCase() .split(/[ ,\n]+/g) const inputMap = new Map() for (const each of inputArray) { const word = each.replace(/[.,!:"&@$%^?]|^'|'$/g, '') if (word === '') { continue } const value = inputMap.get(word) || 0 inputMap.set(word, value + 1) } return inputMap } ================================================ FILE: exercises/practice/word-count/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [61559d5f-2cad-48fb-af53-d3973a9ee9ef] description = "count one word" [5abd53a3-1aed-43a4-a15a-29f88c09cbbd] description = "count one of each word" [2a3091e5-952e-4099-9fac-8f85d9655c0e] description = "multiple occurrences of a word" [e81877ae-d4da-4af4-931c-d923cd621ca6] description = "handles cramped lists" [7349f682-9707-47c0-a9af-be56e1e7ff30] description = "handles expanded lists" [a514a0f2-8589-4279-8892-887f76a14c82] description = "ignore punctuation" [d2e5cee6-d2ec-497b-bdc9-3ebe092ce55e] description = "include numbers" [dac6bc6a-21ae-4954-945d-d7f716392dbf] description = "normalize case" [4185a902-bdb0-4074-864c-f416e42a0f19] description = "with apostrophes" include = false [4ff6c7d7-fcfc-43ef-b8e7-34ff1837a2d3] description = "with apostrophes" reimplements = "4185a902-bdb0-4074-864c-f416e42a0f19" [be72af2b-8afe-4337-b151-b297202e4a7b] description = "with quotations" [8d6815fe-8a51-4a65-96f9-2fb3f6dc6ed6] description = "substrings from the beginning" [c5f4ef26-f3f7-4725-b314-855c04fb4c13] description = "multiple spaces not detected as a word" [50176e8a-fe8e-4f4c-b6b6-aa9cf8f20360] description = "alternating word separators not detected as a word" [6d00f1db-901c-4bec-9829-d20eb3044557] description = "quotation for word with apostrophe" ================================================ FILE: exercises/practice/word-count/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/word-count/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/word-count/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/word-count/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/word-count/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/word-count/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/word-count/package.json ================================================ { "name": "@exercism/typescript-word-count", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/word-count/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/word-count/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/word-count/word-count.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { count } from './word-count.ts' describe('count()', () => { it('counts one word', () => { const expectedCounts = new Map(Object.entries({ word: 1 })) expect(count('word')).toEqual(expectedCounts) }) xit('counts one of each', () => { const expectedCounts = new Map(Object.entries({ one: 1, of: 1, each: 1 })) expect(count('one of each')).toEqual(expectedCounts) }) xit('counts multiple occurrences', () => { const expectedCounts = new Map( Object.entries({ one: 1, fish: 4, two: 1, red: 1, blue: 1 }) ) expect(count('one fish two fish red fish blue fish')).toEqual( expectedCounts ) }) xit('handles cramped lists', () => { const expectedCounts = new Map(Object.entries({ one: 1, two: 1, three: 1 })) expect(count('one,two,three')).toEqual(expectedCounts) }) xit('handles expanded lists', () => { const expectedCounts = new Map(Object.entries({ one: 1, two: 1, three: 1 })) expect(count('one,\ntwo,\nthree')).toEqual(expectedCounts) }) xit('ignores punctuation', () => { const expectedCounts = new Map( Object.entries({ car: 1, carpet: 1, as: 1, java: 1, javascript: 1, }) ) expect(count('car: carpet as java: javascript!!&@$%^&"')).toEqual( expectedCounts ) }) xit('includes numbers', () => { const expectedCounts = new Map(Object.entries({ testing: 2, 1: 1, 2: 1 })) expect(count('testing, 1, 2 testing')).toEqual(expectedCounts) }) xit('normalizes case', () => { const expectedCounts = new Map(Object.entries({ go: 3, stop: 2 })) expect(count('go Go GO Stop stop')).toEqual(expectedCounts) }) xit('with apostrophes', () => { const expectedCounts = new Map( Object.entries({ first: 1, "don't": 2, laugh: 1, then: 1, cry: 1, "you're": 1, getting: 1, it: 1, }) ) expect( count("'First: don't laugh. Then: don't cry. You're getting it.'") ).toEqual(expectedCounts) }) xit('substrings from the beginning', () => { const expectedCounts = new Map( Object.entries({ joe: 1, "can't": 1, tell: 1, between: 1, app: 1, apple: 1, and: 1, a: 1, }) ) expect(count("Joe can't tell between app, apple and a.")).toEqual( expectedCounts ) }) xit('multiple spaces not detected as a word', () => { const expectedCounts = new Map( Object.entries({ multiple: 1, whitespaces: 1 }) ) expect(count(' multiple whitespaces')).toEqual(expectedCounts) }) xit('alternating word separators not detected as a word', () => { const expectedCounts = new Map(Object.entries({ one: 1, two: 1, three: 1 })) expect(count(",\n,one,\n ,two \n 'three'")).toEqual(expectedCounts) }) xit('quotation for word with apostrophe', () => { const expectedCounts = new Map(Object.entries({ can: 1, "can't": 2 })) expect(count("can, can't, 'can't'")).toEqual(expectedCounts) }) xit('handles properties that exist on Object’s prototype', () => { const expectedCounts = new Map( Object.entries({ reserved: 1, words: 1, like: 1, constructor: 1, and: 1, tostring: 1, ok: 1, }) ) expect(count('reserved words like constructor and toString ok?')).toEqual( expectedCounts ) }) }) ================================================ FILE: exercises/practice/word-count/word-count.ts ================================================ export function count() { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/word-search/.docs/instructions.md ================================================ # Instructions In word search puzzles you get a square of letters and have to find specific words in them. For example: ```text jefblpepre camdcimgtc oivokprjsm pbwasqroua rixilelhrs wolcqlirpc screeaumgr alxhpburyi jalaycalmp clojurermt ``` There are several programming languages hidden in the above square. Words can be hidden in all kinds of directions: left-to-right, right-to-left, vertical and diagonal. Given a puzzle and a list of words return the location of the first and last letter of each word. ================================================ FILE: exercises/practice/word-search/.meta/config.json ================================================ { "authors": [ "msomji" ], "contributors": [ "ErikSchierboom", "SleeplessByte" ], "files": { "solution": [ "word-search.ts" ], "test": [ "word-search.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Create a program to solve a word search puzzle.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false } } ================================================ FILE: exercises/practice/word-search/.meta/proof.ci.ts ================================================ interface Result { start: number[] end: number[] } export class WordSearch { private grid: string[] constructor(grid: string[]) { this.grid = grid } private findCoordsWhereLetterMatch( currentLetter: string, board: string[] ): number[][] { return board.reduce( (accumulatedCoordinates: number[][], row: string, rowNumber: number) => [ ...accumulatedCoordinates, ...row .split('') .reduce( ( matchingLetterIndecies: number[], letter: string, index: number ) => letter === currentLetter ? [...matchingLetterIndecies, index] : matchingLetterIndecies, [] ) .reduce( (coordinates: number[][], col) => [ ...coordinates, [rowNumber, col], ], [] ), ], [] ) } private getCoordsOfSurroundingLetters( initialCoord: number[], totalRows: number, totalColumns: number ): number[][] { const top: number[] = [initialCoord[0] - 1, initialCoord[1]] const bottom: number[] = [initialCoord[0] + 1, initialCoord[1]] const right: number[] = [initialCoord[0], initialCoord[1] + 1] const left: number[] = [initialCoord[0], initialCoord[1] - 1] const topRight: number[] = [initialCoord[0] + 1, initialCoord[1] + 1] const topLeft: number[] = [initialCoord[0] + 1, initialCoord[1] - 1] const bottomRight: number[] = [initialCoord[0] - 1, initialCoord[1] + 1] const bottomLeft: number[] = [initialCoord[0] - 1, initialCoord[1] - 1] return [ top, bottom, right, left, topRight, topLeft, bottomRight, bottomLeft, ].filter( (coordinates: number[]) => coordinates[0] <= totalRows - 1 && coordinates[1] <= totalColumns - 1 && coordinates[0] >= 0 && coordinates[1] >= 0 ) } private getValueFromCoordinate( board: string[], coordinates: number[] ): string { return ( board[coordinates[0]] && board[coordinates[0]].split('')[coordinates[1]] ) } private matchingValues( board: string[], coordinates: number[], letter: string ): boolean { return this.getValueFromCoordinate(board, coordinates) === letter } private getDirectionFunction( originCoords: number[], destinationCoords: number[] ): (currentCoords: number[]) => number[] { return (nextCoord: number[]): number[] => [ nextCoord[0] + (destinationCoords[0] - originCoords[0]), nextCoord[1] + (destinationCoords[1] - originCoords[1]), ] } private getValidNeighbouringCoordinates( initial: number[], board: string[], letter: string ): number[][] { return this.getCoordsOfSurroundingLetters( initial, board.length, board[0].length ).filter((neighbouringCoordinate) => this.matchingValues(board, neighbouringCoordinate, letter) ) } private findOne(word: string, board: string[]): Result { const allPossibleStartCoords: number[][] = this.findCoordsWhereLetterMatch( word[0], board ) const allPossibleCoordsForFirstTwoLetters: number[][][] = allPossibleStartCoords.reduce((accum: number[][][], initial) => { return [ ...accum, ...this.getValidNeighbouringCoordinates(initial, board, word[1]).map( (secondCoordinate) => [initial, secondCoordinate] ), ] }, []) const allPossiblePaths: number[][][] = allPossibleCoordsForFirstTwoLetters.map((coordsSoFar: number[][]) => { const incrementFunction = this.getDirectionFunction( coordsSoFar[0], coordsSoFar[1] ) return word .substr(2, word.length) .split('') .reduce( (accum: number[][], _) => [ ...accum, incrementFunction(accum[accum.length - 1]), ], coordsSoFar ) }) const validPaths: number[][][] = allPossiblePaths.reduce( (result: number[][][], path: number[][]) => word .split('') .map((letter, index) => this.matchingValues(board, path[index], letter) ) .includes(false) ? result : [...result, path], [] as number[][][] ) return validPaths.reduce( (_: Result, path: number[][]) => ({ start: path[0].map((c) => c + 1), end: path[path.length - 1].map((c) => c + 1), }), {} as Result ) } public find( words: string[] ): { [word: string]: Result } | { [word: string]: undefined } { return words.reduce( ( accum: { [word: string]: Result } | { [word: string]: undefined }, word ) => { const result = this.findOne(word, this.grid) accum[word] = Object.keys(result).length === 0 ? undefined : result return accum }, {} as { [word: string]: Result } | { [word: string]: undefined } ) } } ================================================ FILE: exercises/practice/word-search/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [b4057815-0d01-41f0-9119-6a91f54b2a0a] description = "Should accept an initial game grid and a target search word" [6b22bcc5-6cbf-4674-931b-d2edbff73132] description = "Should locate one word written left to right" [ff462410-434b-442d-9bc3-3360c75f34a8] description = "Should locate the same word written left to right in a different position" [a02febae-6347-443e-b99c-ab0afb0b8fca] description = "Should locate a different left to right word" [e42e9987-6304-4e13-8232-fa07d5280130] description = "Should locate that different left to right word in a different position" [9bff3cee-49b9-4775-bdfb-d55b43a70b2f] description = "Should locate a left to right word in two line grid" [851a35fb-f499-4ec1-9581-395a87903a22] description = "Should locate a left to right word in three line grid" [2f3dcf84-ba7d-4b75-8b8d-a3672b32c035] description = "Should locate a left to right word in ten line grid" [006d4856-f365-4e84-a18c-7d129ce9eefb] description = "Should locate that left to right word in a different position in a ten line grid" [eff7ac9f-ff11-443e-9747-40850c12ab60] description = "Should locate a different left to right word in a ten line grid" [dea39f86-8c67-4164-8884-13bfc48bd13b] description = "Should locate multiple words" [29e6a6a5-f80c-48a6-8e68-05bbbe187a09] description = "Should locate a single word written right to left" [3cf34428-b43f-48b6-b332-ea0b8836011d] description = "Should locate multiple words written in different horizontal directions" [2c8cd344-a02f-464b-93b6-8bf1bd890003] description = "Should locate words written top to bottom" [9ee1e43d-e59d-4c32-9a5f-6a22d4a1550f] description = "Should locate words written bottom to top" [6a21a676-f59e-4238-8e88-9f81015afae9] description = "Should locate words written top left to bottom right" [c9125189-1861-4b0d-a14e-ba5dab29ca7c] description = "Should locate words written bottom right to top left" [b19e2149-7fc5-41ec-a8a9-9bc6c6c38c40] description = "Should locate words written bottom left to top right" [69e1d994-a6d7-4e24-9b5a-db76751c2ef8] description = "Should locate words written top right to bottom left" [695531db-69eb-463f-8bad-8de3bf5ef198] description = "Should fail to locate a word that is not in the puzzle" [fda5b937-6774-4a52-8f89-f64ed833b175] description = "Should fail to locate words that are not on horizontal, vertical, or diagonal lines" [5b6198eb-2847-4e2f-8efe-65045df16bd3] description = "Should not concatenate different lines to find a horizontal word" [eba44139-a34f-4a92-98e1-bd5f259e5769] description = "Should not wrap around horizontally to find a word" [cd1f0fa8-76af-4167-b105-935f78364dac] description = "Should not wrap around vertically to find a word" ================================================ FILE: exercises/practice/word-search/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/word-search/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/word-search/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/word-search/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/word-search/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/word-search/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/word-search/package.json ================================================ { "name": "@exercism/typescript-word-search", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/word-search/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/word-search/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/word-search/word-search.test.ts ================================================ import { describe, xdescribe, it, expect, xit } from '@jest/globals' import { WordSearch } from './word-search.ts' describe('single line grids', () => { it('should accept an initial game grid', () => { const grid = ['jefblpepre'] const wordSearch = new WordSearch(grid) expect(wordSearch instanceof WordSearch).toEqual(true) }) xit('should accept a target search word', () => { const grid = ['jefblpepre'] const wordSearch = new WordSearch(grid) expect(wordSearch.find(['glasnost'])).toEqual({ glasnost: undefined }) }) xit('should locate a word written left to right', () => { const grid = ['clojurermt'] const expectedResults = { clojure: { start: [1, 1], end: [1, 7], }, } const wordSearch = new WordSearch(grid) expect(wordSearch.find(['clojure'])).toEqual(expectedResults) }) xit('should locate the same word written left to right in a different position', () => { const grid = ['mtclojurer'] const expectedResults = { clojure: { start: [1, 3], end: [1, 9], }, } const wordSearch = new WordSearch(grid) expect(wordSearch.find(['clojure'])).toEqual(expectedResults) }) xit('should locate a different left to right word', () => { const grid = ['coffeelplx'] const expectedResults = { coffee: { start: [1, 1], end: [1, 6], }, } const wordSearch = new WordSearch(grid) expect(wordSearch.find(['coffee'])).toEqual(expectedResults) }) xit('should locate that different left to right word in a different position', () => { const grid = ['xcoffeezlp'] const expectedResults = { coffee: { start: [1, 2], end: [1, 7], }, } const wordSearch = new WordSearch(grid) expect(wordSearch.find(['coffee'])).toEqual(expectedResults) }) }) xdescribe('multi line grids', () => { xit('should locate a left to right word in a two line grid', () => { const grid = ['jefblpepre', 'clojurermt'] const expectedResults = { clojure: { start: [2, 1], end: [2, 7], }, } const wordSearch = new WordSearch(grid) expect(wordSearch.find(['clojure'])).toEqual(expectedResults) }) xit('should locate a left to right word in a different position in a two line grid', () => { const grid = ['jefblpepre', 'tclojurerm'] const expectedResults = { clojure: { start: [2, 2], end: [2, 8], }, } const wordSearch = new WordSearch(grid) expect(wordSearch.find(['clojure'])).toEqual(expectedResults) }) xit('should locate a left to right word in a three line grid', () => { const grid = ['camdcimgtc', 'jefblpepre', 'clojurermt'] const expectedResults = { clojure: { start: [3, 1], end: [3, 7], }, } const wordSearch = new WordSearch(grid) expect(wordSearch.find(['clojure'])).toEqual(expectedResults) }) xit('should locate a left to right word in a ten line grid', () => { const grid = [ 'jefblpepre', 'camdcimgtc', 'oivokprjsm', 'pbwasqroua', 'rixilelhrs', 'wolcqlirpc', 'screeaumgr', 'alxhpburyi', 'jalaycalmp', 'clojurermt', ] const expectedResults = { clojure: { start: [10, 1], end: [10, 7], }, } const wordSearch = new WordSearch(grid) expect(wordSearch.find(['clojure'])).toEqual(expectedResults) }) xit('should locate a left to right word in a different position in a ten line grid', () => { const grid = [ 'jefblpepre', 'camdcimgtc', 'oivokprjsm', 'pbwasqroua', 'rixilelhrs', 'wolcqlirpc', 'screeaumgr', 'alxhpburyi', 'clojurermt', 'jalaycalmp', ] const expectedResults = { clojure: { start: [9, 1], end: [9, 7], }, } const wordSearch = new WordSearch(grid) expect(wordSearch.find(['clojure'])).toEqual(expectedResults) }) xit('should locate a different left to right word in a ten line grid', () => { const grid = [ 'jefblpepre', 'camdcimgtc', 'oivokprjsm', 'pbwasqroua', 'rixilelhrs', 'wolcqlirpc', 'screeaumgr', 'alxhpburyi', 'clojurermt', 'jalaycalmp', ] const expectedResults = { scree: { start: [7, 1], end: [7, 5], }, } const wordSearch = new WordSearch(grid) expect(wordSearch.find(['scree'])).toEqual(expectedResults) }) }) xdescribe('multiple words', () => { xit('should locate two words written left to right', () => { const grid = [ 'aefblpepre', 'camdcimgtc', 'oivokprjsm', 'pbwasqroua', 'rixilelhrs', 'wolcqlirpc', 'screeaumgr', 'alxhpburyi', 'jalaycalmp', 'clojurermt', 'xjavamtzlp', ] const expectedResults = { clojure: { start: [10, 1], end: [10, 7], }, java: { start: [11, 2], end: [11, 5], }, } const wordSearch = new WordSearch(grid) expect(wordSearch.find(['java', 'clojure'])).toEqual(expectedResults) }) }) xdescribe('different directions', () => { xit('should locate a single word written right to left', () => { const grid = ['rixilelhrs'] const expectedResults = { elixir: { start: [1, 6], end: [1, 1], }, } const wordSearch = new WordSearch(grid) expect(wordSearch.find(['elixir'])).toEqual(expectedResults) }) xit('should locate multiple words written in different horizontal directions', () => { const grid = [ 'jefblpepre', 'camdcimgtc', 'oivokprjsm', 'pbwasqroua', 'rixilelhrs', 'wolcqlirpc', 'screeaumgr', 'alxhpburyi', 'jalaycalmp', 'clojurermt', ] const expectedResults = { clojure: { start: [10, 1], end: [10, 7], }, elixir: { start: [5, 6], end: [5, 1], }, } const wordSearch = new WordSearch(grid) expect(wordSearch.find(['elixir', 'clojure'])).toEqual(expectedResults) }) }) xdescribe('vertical directions', () => { xit('should locate words written top to bottom', () => { const grid = [ 'jefblpepre', 'camdcimgtc', 'oivokprjsm', 'pbwasqroua', 'rixilelhrs', 'wolcqlirpc', 'screeaumgr', 'alxhpburyi', 'jalaycalmp', 'clojurermt', ] const expectedResults = { clojure: { start: [10, 1], end: [10, 7], }, elixir: { start: [5, 6], end: [5, 1], }, ecmascript: { start: [1, 10], end: [10, 10], }, } const wordSearch = new WordSearch(grid) expect(wordSearch.find(['elixir', 'clojure', 'ecmascript'])).toEqual( expectedResults ) }) xit('should locate words written bottom to top', () => { const grid = [ 'jefblpepre', 'camdcimgtc', 'oivokprjsm', 'pbwasqroua', 'rixilelhrs', 'wolcqlirpc', 'screeaumgr', 'alxhpburyi', 'jalaycalmp', 'clojurermt', ] const expectedResults = { clojure: { start: [10, 1], end: [10, 7], }, elixir: { start: [5, 6], end: [5, 1], }, ecmascript: { start: [1, 10], end: [10, 10], }, rust: { start: [5, 9], end: [2, 9], }, } const wordSearch = new WordSearch(grid) expect( wordSearch.find(['elixir', 'clojure', 'ecmascript', 'rust']) ).toEqual(expectedResults) }) xit('should locate words written top left to bottom right', () => { const grid = [ 'jefblpepre', 'camdcimgtc', 'oivokprjsm', 'pbwasqroua', 'rixilelhrs', 'wolcqlirpc', 'screeaumgr', 'alxhpburyi', 'jalaycalmp', 'clojurermt', ] const expectedResults = { clojure: { start: [10, 1], end: [10, 7], }, elixir: { start: [5, 6], end: [5, 1], }, ecmascript: { start: [1, 10], end: [10, 10], }, rust: { start: [5, 9], end: [2, 9], }, java: { start: [1, 1], end: [4, 4], }, } const wordSearch = new WordSearch(grid) expect( wordSearch.find(['clojure', 'elixir', 'ecmascript', 'rust', 'java']) ).toEqual(expectedResults) }) xit('should locate words written bottom right to top left', () => { const grid = [ 'jefblpepre', 'camdcimgtc', 'oivokprjsm', 'pbwasqroua', 'rixilelhrs', 'wolcqlirpc', 'screeaumgr', 'alxhpburyi', 'jalaycalmp', 'clojurermt', ] const expectedResults = { clojure: { start: [10, 1], end: [10, 7], }, elixir: { start: [5, 6], end: [5, 1], }, ecmascript: { start: [1, 10], end: [10, 10], }, rust: { start: [5, 9], end: [2, 9], }, java: { start: [1, 1], end: [4, 4], }, lua: { start: [9, 8], end: [7, 6], }, } const wordSearch = new WordSearch(grid) expect( wordSearch.find([ 'clojure', 'elixir', 'ecmascript', 'rust', 'java', 'lua', ]) ).toEqual(expectedResults) }) xit('should locate words written bottom left to top right', () => { const grid = [ 'jefblpepre', 'camdcimgtc', 'oivokprjsm', 'pbwasqroua', 'rixilelhrs', 'wolcqlirpc', 'screeaumgr', 'alxhpburyi', 'jalaycalmp', 'clojurermt', ] const expectedResults = { clojure: { start: [10, 1], end: [10, 7], }, elixir: { start: [5, 6], end: [5, 1], }, ecmascript: { start: [1, 10], end: [10, 10], }, rust: { start: [5, 9], end: [2, 9], }, java: { start: [1, 1], end: [4, 4], }, lua: { start: [9, 8], end: [7, 6], }, lisp: { start: [6, 3], end: [3, 6], }, } const wordSearch = new WordSearch(grid) expect( wordSearch.find([ 'clojure', 'elixir', 'ecmascript', 'rust', 'java', 'lua', 'lisp', ]) ).toEqual(expectedResults) }) xit('should locate words written top right to bottom left', () => { const grid = [ 'jefblpepre', 'camdcimgtc', 'oivokprjsm', 'pbwasqroua', 'rixilelhrs', 'wolcqlirpc', 'screeaumgr', 'alxhpburyi', 'jalaycalmp', 'clojurermt', ] const expectedResults = { clojure: { start: [10, 1], end: [10, 7], }, elixir: { start: [5, 6], end: [5, 1], }, ecmascript: { start: [1, 10], end: [10, 10], }, rust: { start: [5, 9], end: [2, 9], }, java: { start: [1, 1], end: [4, 4], }, lua: { start: [9, 8], end: [7, 6], }, lisp: { start: [6, 3], end: [3, 6], }, ruby: { start: [6, 8], end: [9, 5], }, } const wordSearch = new WordSearch(grid) expect( wordSearch.find([ 'clojure', 'elixir', 'ecmascript', 'rust', 'java', 'lua', 'lisp', 'ruby', ]) ).toEqual(expectedResults) }) }) xdescribe('avoiding false-positives', () => { xit('should fail to locate a word that is not in the puzzle', () => { const grid = [ 'jefblpepre', 'camdcimgtc', 'oivokprjsm', 'pbwasqroua', 'rixilelhrs', 'wolcqlirpc', 'screeaumgr', 'alxhpburyi', 'jalaycalmp', 'clojurermt', ] const expectedResults = { clojure: { start: [10, 1], end: [10, 7], }, elixir: { start: [5, 6], end: [5, 1], }, ecmascript: { start: [1, 10], end: [10, 10], }, rust: { start: [5, 9], end: [2, 9], }, java: { start: [1, 1], end: [4, 4], }, lua: { start: [9, 8], end: [7, 6], }, lisp: { start: [6, 3], end: [3, 6], }, ruby: { start: [6, 8], end: [9, 5], }, haskell: undefined, } const wordSearch = new WordSearch(grid) expect( wordSearch.find([ 'clojure', 'elixir', 'ecmascript', 'rust', 'java', 'lua', 'lisp', 'ruby', 'haskell', ]) ).toEqual(expectedResults) }) xit('should fail to locate words that are not on horizontal, vertical, or diagonal lines', () => { const grid = ['abc', 'def'] const expectedResults = { aef: undefined, ced: undefined, abf: undefined, cbd: undefined, } const wordSearch = new WordSearch(grid) expect(wordSearch.find(['aef', 'ced', 'abf', 'cbd'])).toEqual( expectedResults ) }) xit('should not concatenate different lines to find a horizontal word', () => { const grid = ['abceli', 'xirdfg'] const expectedResults = { elixir: undefined, } const wordSearch = new WordSearch(grid) expect(wordSearch.find(['elixir'])).toEqual(expectedResults) }) xit('should not wrap around horizontally to find a word', () => { const grid = ['silabcdefp'] const expectedResults = { lisp: undefined, } const wordSearch = new WordSearch(grid) expect(wordSearch.find(['lisp'])).toEqual(expectedResults) }) xit('should not wrap around vertically to find a word', () => { const grid = ['s', 'u', 'r', 'a', 'b', 'c', 't'] const expectedResults = { rust: undefined, } const wordSearch = new WordSearch(grid) expect(wordSearch.find(['rust'])).toEqual(expectedResults) }) }) ================================================ FILE: exercises/practice/word-search/word-search.ts ================================================ export class WordSearch { constructor() { throw new Error('Remove this line and implement the function') } public find(words: string[]) { throw new Error('Remove this line and implement the function') } } ================================================ FILE: exercises/practice/wordy/.docs/instructions.md ================================================ # Instructions Parse and evaluate simple math word problems returning the answer as an integer. ## Iteration 0 — Numbers Problems with no operations simply evaluate to the number given. > What is 5? Evaluates to 5. ## Iteration 1 — Addition Add two numbers together. > What is 5 plus 13? Evaluates to 18. Handle large numbers and negative numbers. ## Iteration 2 — Subtraction, Multiplication and Division Now, perform the other three operations. > What is 7 minus 5? 2 > What is 6 multiplied by 4? 24 > What is 25 divided by 5? 5 ## Iteration 3 — Multiple Operations Handle a set of operations, in sequence. Since these are verbal word problems, evaluate the expression from left-to-right, _ignoring the typical order of operations._ > What is 5 plus 13 plus 6? 24 > What is 3 plus 2 multiplied by 3? 15 (i.e. not 9) ## Iteration 4 — Errors The parser should reject: - Unsupported operations ("What is 52 cubed?") - Non-math questions ("Who is the President of the United States") - Word problems with invalid syntax ("What is 1 plus plus 2?") ================================================ FILE: exercises/practice/wordy/.meta/config.json ================================================ { "authors": [ "masters3d" ], "contributors": [ "lukaszklis", "SleeplessByte" ], "files": { "solution": [ "wordy.ts" ], "test": [ "wordy.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Parse and evaluate simple math word problems returning the answer as an integer.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "Inspired by one of the generated questions in the Extreme Startup game.", "source_url": "https://github.com/rchatley/extreme_startup" } ================================================ FILE: exercises/practice/wordy/.meta/proof.ci.ts ================================================ const compute = ( operand1: number, operand2: number, operation: string ): number => { switch (operation) { case 'plus': return operand1 + operand2 case 'minus': return operand1 - operand2 case 'divided by': return operand1 / operand2 case 'multiplied by': return operand1 * operand2 } } export const answer = (question: string): number => { const operationsPattern = new RegExp(/plus|minus|divided by|multiplied by/g) if ( !operationsPattern.test(question) && !/^What is ?-?\d*\?$/.test(question) ) { throw new Error('Unknown operation') } const generalPattern = /^What is -?\d+( (plus|minus|multiplied by|divided by) -?\d+)*\?$/g if (!generalPattern.test(question)) { throw new Error('Syntax error') } const operations = question.match(operationsPattern) const operands = question.match(/-?\d+/g) let result = parseInt(operands[0]) if (!operations) { return result } for (let i = 0; i < operations.length; i++) { result = compute(result, parseInt(operands[i + 1]), operations[i]) } return result } ================================================ FILE: exercises/practice/wordy/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [88bf4b28-0de3-4883-93c7-db1b14aa806e] description = "just a number" [18983214-1dfc-4ebd-ac77-c110dde699ce] description = "just a zero" [607c08ee-2241-4288-916d-dae5455c87e6] description = "just a negative number" [bb8c655c-cf42-4dfc-90e0-152fcfd8d4e0] description = "addition" [bb9f2082-171c-46ad-ad4e-c3f72087c1b5] description = "addition with a left hand zero" [6fa05f17-405a-4742-80ae-5d1a8edb0d5d] description = "addition with a right hand zero" [79e49e06-c5ae-40aa-a352-7a3a01f70015] description = "more addition" [b345dbe0-f733-44e1-863c-5ae3568f3803] description = "addition with negative numbers" [cd070f39-c4cc-45c4-97fb-1be5e5846f87] description = "large addition" [0d86474a-cd93-4649-a4fa-f6109a011191] description = "subtraction" [30bc8395-5500-4712-a0cf-1d788a529be5] description = "multiplication" [34c36b08-8605-4217-bb57-9a01472c427f] description = "division" [da6d2ce4-fb94-4d26-8f5f-b078adad0596] description = "multiple additions" [7fd74c50-9911-4597-be09-8de7f2fea2bb] description = "addition and subtraction" [b120ffd5-bad6-4e22-81c8-5512e8faf905] description = "multiple subtraction" [4f4a5749-ef0c-4f60-841f-abcfaf05d2ae] description = "subtraction then addition" [312d908c-f68f-42c9-aa75-961623cc033f] description = "multiple multiplication" [38e33587-8940-4cc1-bc28-bfd7e3966276] description = "addition and multiplication" [3c854f97-9311-46e8-b574-92b60d17d394] description = "multiple division" [3ad3e433-8af7-41ec-aa9b-97b42ab49357] description = "unknown operation" [8a7e85a8-9e7b-4d46-868f-6d759f4648f8] description = "Non math question" [42d78b5f-dbd7-4cdb-8b30-00f794bb24cf] description = "reject problem missing an operand" [c2c3cbfc-1a72-42f2-b597-246e617e66f5] description = "reject problem with no operands or operators" [4b3df66d-6ed5-4c95-a0a1-d38891fbdab6] description = "reject two operations in a row" [6abd7a50-75b4-4665-aa33-2030fd08bab1] description = "reject two numbers in a row" [10a56c22-e0aa-405f-b1d2-c642d9c4c9de] description = "reject postfix notation" [0035bc63-ac43-4bb5-ad6d-e8651b7d954e] description = "reject prefix notation" ================================================ FILE: exercises/practice/wordy/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/wordy/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/wordy/.yarnrc.yml ================================================ compressionLevel: mixed enableGlobalCache: true ================================================ FILE: exercises/practice/wordy/babel.config.cjs ================================================ module.exports = { // eslint-disable-next-line @typescript-eslint/no-require-imports presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], plugins: [], } ================================================ FILE: exercises/practice/wordy/eslint.config.mjs ================================================ // @ts-check import tsEslint from 'typescript-eslint' import config from '@exercism/eslint-config-typescript' import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' export default [ ...tsEslint.config(...config, { files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], extends: maintainersConfig, }), { ignores: [ // # Protected or generated '.git/**/*', '.vscode/**/*', //# When using npm 'node_modules/**/*', // # Configuration files 'babel.config.cjs', 'jest.config.cjs', ], }, ] ================================================ FILE: exercises/practice/wordy/jest.config.cjs ================================================ module.exports = { verbose: true, projects: [''], testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], testPathIgnorePatterns: [ '/(?:production_)?node_modules/', '.d.ts$', '/test/fixtures', '/test/helpers', '__mocks__', ], transform: { '^.+\\.[jt]sx?$': 'babel-jest', }, moduleNameMapper: { '^(\\.\\/.+)\\.js$': '$1', }, } ================================================ FILE: exercises/practice/wordy/package.json ================================================ { "name": "@exercism/typescript-wordy", "version": "1.0.0", "description": "Exercism exercises in Typescript.", "private": true, "repository": { "type": "git", "url": "https://github.com/exercism/typescript" }, "type": "module", "engines": { "node": "^18.16.0 || >=20.0.0" }, "devDependencies": { "@exercism/babel-preset-typescript": "^0.6.0", "@exercism/eslint-config-typescript": "^0.8.0", "@jest/globals": "^29.7.0", "@types/node": "~22.7.6", "babel-jest": "^29.7.0", "core-js": "~3.38.1", "eslint": "^9.12.0", "expect": "^29.7.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tstyche": "^2.1.1", "typescript": "~5.6.3", "typescript-eslint": "^8.10.0" }, "scripts": { "test": "corepack yarn node test-runner.mjs", "test:types": "corepack yarn tstyche", "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", "lint": "corepack yarn lint:types && corepack yarn lint:ci", "lint:types": "corepack yarn tsc --noEmit -p .", "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" }, "packageManager": "yarn@4.5.1" } ================================================ FILE: exercises/practice/wordy/test-runner.mjs ================================================ #!/usr/bin/env node /** * 👋🏽 Hello there reader, * * It looks like you are working on this solution using the Exercism CLI and * not the online editor. That's great! The file you are looking at executes * the various steps the online test-runner also takes. * * @see https://github.com/exercism/typescript-test-runner * * TypeScript track exercises generally consist of at least two out of three * types of tests to run. * * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid * 2. tstyche, static analysis tests to see if the types used are expected * 3. jest, runtime implementation tests to see if the solution is correct * * If one of these three fails, this script terminates with -1, -2, or -3 * respectively. If it succeeds, it terminates with exit code 0. * * @note you need corepack (bundled with node LTS) enabled in order for this * test runner to work as expected. Follow the installation and test * instructions if you see errors about corepack or pnp. */ import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { exit } from 'node:process' import { URL } from 'node:url' /** * Before executing any tests, the test runner attempts to find the * exercise config.json file which has metadata about which types of tests * to run for this solution. */ const metaDirectory = new URL('./.meta/', import.meta.url) const exercismDirectory = new URL('./.exercism/', import.meta.url) const configDirectory = existsSync(metaDirectory) ? metaDirectory : existsSync(exercismDirectory) ? exercismDirectory : null if (configDirectory === null) { throw new Error( 'Expected .meta or .exercism directory to exist, but I cannot find it.' ) } const configFile = new URL('./config.json', configDirectory) if (!existsSync(configFile)) { throw new Error('Expected config.json to exist at ' + configFile.toString()) } // Experimental: import config from './config.json' with { type: 'json' } /** @type {import('./config.json') } */ const config = JSON.parse(readFileSync(configFile)) const jest = !config.custom || config.custom['flag.tests.jest'] const tstyche = config.custom?.['flag.tests.tstyche'] console.log( `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` ) /** * 1. tsc: the typescript compiler */ try { console.log('[tests] tsc (compile)') execSync('corepack yarn lint:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-1) } /** * 2. tstyche: type tests */ if (tstyche) { try { console.log('[tests] tstyche (type tests)') execSync('corepack yarn test:types', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-2) } } /** * 3. jest: implementation tests */ if (jest) { try { console.log('[tests] tstyche (implementation tests)') execSync('corepack yarn test:implementation', { stdio: 'inherit', cwd: process.cwd(), }) } catch { exit(-3) } } /** * Done! 🥳 */ ================================================ FILE: exercises/practice/wordy/tsconfig.json ================================================ { "display": "Configuration for Exercism TypeScript Exercises", "compilerOptions": { // Allows you to use the newest syntax, and have access to console.log // https://www.typescriptlang.org/tsconfig#lib "lib": ["ES2020", "dom"], // Make sure typescript is configured to output ESM // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm "module": "Node16", // Since this project is using babel, TypeScript may target something very // high, and babel will make sure it runs on your local Node version. // https://babeljs.io/docs/en/ "target": "ES2020", // ESLint doesn't support this yet: "es2022", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, // Because jest-resolve isn't like node resolve, the absolute path must be .ts "allowImportingTsExtensions": true, "noEmit": true, // Because we'll be using babel: ensure that Babel can safely transpile // files in the TypeScript project. // // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats "isolatedModules": true }, "include": [ "*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx", "__typetests__/*.tst.ts" ], "exclude": ["node_modules"] } ================================================ FILE: exercises/practice/wordy/wordy.test.ts ================================================ import { describe, it, expect, xit } from '@jest/globals' import { answer } from './wordy.ts' describe('Wordy', () => { it('just a number', () => { expect(answer('What is 5?')).toEqual(5) }) xit('just a zero', () => { expect(answer('What is 0?')).toEqual(0) }) xit('just a negative number', () => { expect(answer('What is -123?')).toEqual(-123) }) xit('addition', () => { expect(answer('What is 1 plus 1?')).toEqual(2) }) xit('addition with a left hand zero', () => { expect(answer('What is 0 plus 2?')).toEqual(2) }) xit('addition with a right hand zero', () => { expect(answer('What is 3 plus 0?')).toEqual(3) }) xit('more addition', () => { expect(answer('What is 53 plus 2?')).toEqual(55) }) xit('addition with negative numbers', () => { expect(answer('What is -1 plus -10?')).toEqual(-11) }) xit('large addition', () => { expect(answer('What is 123 plus 45678?')).toEqual(45801) }) xit('subtraction', () => { expect(answer('What is 4 minus -12?')).toEqual(16) }) xit('multiplication', () => { expect(answer('What is -3 multiplied by 25?')).toEqual(-75) }) xit('division', () => { expect(answer('What is 33 divided by -3?')).toEqual(-11) }) xit('multiple additions', () => { expect(answer('What is 1 plus 1 plus 1?')).toEqual(3) }) xit('addition and subtraction', () => { expect(answer('What is 1 plus 5 minus -2?')).toEqual(8) }) xit('multiple subtraction', () => { expect(answer('What is 20 minus 4 minus 13?')).toEqual(3) }) xit('subtraction then addition', () => { expect(answer('What is 17 minus 6 plus 3?')).toEqual(14) }) xit('multiple multiplication', () => { expect(answer('What is 2 multiplied by -2 multiplied by 3?')).toEqual(-12) }) xit('addition and multiplication', () => { expect(answer('What is -3 plus 7 multiplied by -2?')).toEqual(-8) }) xit('multiple division', () => { expect(answer('What is -12 divided by 2 divided by -3?')).toEqual(2) }) xit('unknown operation', () => { expect(() => answer('What is 52 cubed?')).toThrow( new Error('Unknown operation') ) }) xit('Non math question', () => { expect(() => answer('Who is the President of the United States?')).toThrow( new Error('Unknown operation') ) }) xit('reject problem missing an operand', () => { expect(() => answer('What is 1 plus?')).toThrow(new Error('Syntax error')) }) xit('reject problem with no operands or operators', () => { expect(() => answer('What is?')).toThrow(new Error('Syntax error')) }) xit('reject two operations in a row', () => { expect(() => answer('What is 1 plus plus 2?')).toThrow( new Error('Syntax error') ) }) xit('reject two numbers in a row', () => { expect(() => answer('What is 1 plus 2 1?')).toThrow( new Error('Syntax error') ) }) xit('reject postfix notation', () => { expect(() => answer('What is 1 2 plus?')).toThrow(new Error('Syntax error')) }) xit('reject prefix notation', () => { expect(() => answer('What is plus 1 2?')).toThrow(new Error('Syntax error')) }) }) ================================================ FILE: exercises/practice/wordy/wordy.ts ================================================ export const answer = () => { throw new Error('Remove this line and implement the function') } ================================================ FILE: exercises/practice/yacht/.docs/instructions.md ================================================ # Instructions Given five dice and a category, calculate the score of the dice for that category. ~~~~exercism/note You'll always be presented with five dice. Each dice's value will be between one and six inclusively. The dice may be unordered. ~~~~ ## Scores in Yacht | Category | Score | Description | Example | | --------------- | ---------------------- | ---------------------------------------- | ------------------- | | Ones | 1 × number of ones | Any combination | 1 1 1 4 5 scores 3 | | Twos | 2 × number of twos | Any combination | 2 2 3 4 5 scores 4 | | Threes | 3 × number of threes | Any combination | 3 3 3 3 3 scores 15 | | Fours | 4 × number of fours | Any combination | 1 2 3 3 5 scores 0 | | Fives | 5 × number of fives | Any combination | 5 1 5 2 5 scores 15 | | Sixes | 6 × number of sixes | Any combination | 2 3 4 5 6 scores 6 | | Full House | Total of the dice | Three of one number and two of another | 3 3 3 5 5 scores 19 | | Four of a Kind | Total of the four dice | At least four dice showing the same face | 4 4 4 4 6 scores 16 | | Little Straight | 30 points | 1-2-3-4-5 | 1 2 3 4 5 scores 30 | | Big Straight | 30 points | 2-3-4-5-6 | 2 3 4 5 6 scores 30 | | Choice | Sum of the dice | Any combination | 2 3 3 4 6 scores 18 | | Yacht | 50 points | All five dice showing the same face | 4 4 4 4 4 scores 50 | If the dice do **not** satisfy the requirements of a category, the score is zero. If, for example, _Four Of A Kind_ is entered in the _Yacht_ category, zero points are scored. A _Yacht_ scores zero if entered in the _Full House_ category. ================================================ FILE: exercises/practice/yacht/.docs/introduction.md ================================================ # Introduction Each year, something new is "all the rage" in your high school. This year it is a dice game: [Yacht][yacht]. The game of Yacht is from the same family as Poker Dice, Generala and particularly Yahtzee, of which it is a precursor. The game consists of twelve rounds. In each, five dice are rolled and the player chooses one of twelve categories. The chosen category is then used to score the throw of the dice. [yacht]: https://en.wikipedia.org/wiki/Yacht_(dice_game) ================================================ FILE: exercises/practice/yacht/.meta/config.json ================================================ { "authors": [ "xavdid" ], "contributors": [ "BNAndras" ], "files": { "solution": [ "yacht.ts" ], "test": [ "yacht.test.ts" ], "example": [ ".meta/proof.ci.ts" ] }, "blurb": "Score a single throw of dice in the game Yacht.", "custom": { "version.tests.compatibility": "jest-29", "flag.tests.task-per-describe": false, "flag.tests.may-run-long": false, "flag.tests.includes-optional": false, "flag.tests.jest": true, "flag.tests.tstyche": false }, "source": "James Kilfiger, using Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Yacht_(dice_game)" } ================================================ FILE: exercises/practice/yacht/.meta/proof.ci.ts ================================================ export const enum Category { ONES, TWOS, THREES, FOURS, FIVES, SIXES, FULL_HOUSE, FOUR_OF_A_KIND, LITTLE_STRAIGHT, BIG_STRAIGHT, CHOICE, YACHT, } /** * multiplies the number of occurances of a specific value by that number */ const countSingles = (dice: number[], value: number): number => value * dice.filter((d) => d === value).length /** * sorts a list of numbers in numerically increasing order */ const sortNumbers = (dice: number[]): number[] => [...dice].sort((a, b) => a - b) /** * build a mapping of the number of occurances of each die. For example. a full house of 1s and 6s will return `{1: 2, 6: 3}` */ const frequencies = (dice: number[]): Record => dice.reduce( (res, d) => ({ ...res, [d]: (res[d] ?? 0) + 1, }), {} as Record ) /** * sums a list of numbers */ const sum = (dice: number[]): number => dice.reduce((res, d) => d + res, 0) /** * compares two list of numbers for equality */ const eq = (l: number[], r: number[]): boolean => JSON.stringify(l) === JSON.stringify(r) export const score = (dice: number[], category: Category): number => { switch (category) { case Category.ONES: return countSingles(dice, 1) case Category.TWOS: return countSingles(dice, 2) case Category.THREES: return countSingles(dice, 3) case Category.FOURS: return countSingles(dice, 4) case Category.FIVES: return countSingles(dice, 5) case Category.SIXES: return countSingles(dice, 6) case Category.FULL_HOUSE: return eq(sortNumbers(Object.values(frequencies(dice))), [2, 3]) ? sum(dice) : 0 case Category.FOUR_OF_A_KIND: return sum( Object.entries(frequencies(dice)).map(([d, count]) => count >= 4 ? parseInt(d) * 4 : 0 ) ) case Category.LITTLE_STRAIGHT: return eq(sortNumbers(dice), [1, 2, 3, 4, 5]) ? 30 : 0 case Category.BIG_STRAIGHT: return eq(sortNumbers(dice), [2, 3, 4, 5, 6]) ? 30 : 0 case Category.CHOICE: return sum(dice) case Category.YACHT: return new Set(dice).size === 1 ? 50 : 0 default: throw new Error('unhandled case') } } ================================================ FILE: exercises/practice/yacht/.meta/tests.toml ================================================ # This is an auto-generated file. # # Regenerating this file via `configlet sync` will: # - Recreate every `description` key/value pair # - Recreate every `reimplements` key/value pair, where they exist in problem-specifications # - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) # - Preserve any other key/value pair # # As user-added comments (using the # character) will be removed when this file # is regenerated, comments can be added via a `comment` key. [3060e4a5-4063-4deb-a380-a630b43a84b6] description = "Yacht" [15026df2-f567-482f-b4d5-5297d57769d9] description = "Not Yacht" [36b6af0c-ca06-4666-97de-5d31213957a4] description = "Ones" [023a07c8-6c6e-44d0-bc17-efc5e1b8205a] description = "Ones, out of order" [7189afac-cccd-4a74-8182-1cb1f374e496] description = "No ones" [793c4292-dd14-49c4-9707-6d9c56cee725] description = "Twos" [dc41bceb-d0c5-4634-a734-c01b4233a0c6] description = "Fours" [f6125417-5c8a-4bca-bc5b-b4b76d0d28c8] description = "Yacht counted as threes" [464fc809-96ed-46e4-acb8-d44e302e9726] description = "Yacht of 3s counted as fives" [d054227f-3a71-4565-a684-5c7e621ec1e9] description = "Fives" [e8a036e0-9d21-443a-8b5f-e15a9e19a761] description = "Sixes" [51cb26db-6b24-49af-a9ff-12f53b252eea] description = "Full house two small, three big" [1822ca9d-f235-4447-b430-2e8cfc448f0c] description = "Full house three small, two big" [b208a3fc-db2e-4363-a936-9e9a71e69c07] description = "Two pair is not a full house" [b90209c3-5956-445b-8a0b-0ac8b906b1c2] description = "Four of a kind is not a full house" [32a3f4ee-9142-4edf-ba70-6c0f96eb4b0c] description = "Yacht is not a full house" [b286084d-0568-4460-844a-ba79d71d79c6] description = "Four of a Kind" [f25c0c90-5397-4732-9779-b1e9b5f612ca] description = "Yacht can be scored as Four of a Kind" [9f8ef4f0-72bb-401a-a871-cbad39c9cb08] description = "Full house is not Four of a Kind" [b4743c82-1eb8-4a65-98f7-33ad126905cd] description = "Little Straight" [7ac08422-41bf-459c-8187-a38a12d080bc] description = "Little Straight as Big Straight" [97bde8f7-9058-43ea-9de7-0bc3ed6d3002] description = "Four in order but not a little straight" [cef35ff9-9c5e-4fd2-ae95-6e4af5e95a99] description = "No pairs but not a little straight" [fd785ad2-c060-4e45-81c6-ea2bbb781b9d] description = "Minimum is 1, maximum is 5, but not a little straight" [35bd74a6-5cf6-431a-97a3-4f713663f467] description = "Big Straight" [87c67e1e-3e87-4f3a-a9b1-62927822b250] description = "Big Straight as little straight" [c1fa0a3a-40ba-4153-a42d-32bc34d2521e] description = "No pairs but not a big straight" [207e7300-5d10-43e5-afdd-213e3ac8827d] description = "Choice" [b524c0cf-32d2-4b40-8fb3-be3500f3f135] description = "Yacht as choice" ================================================ FILE: exercises/practice/yacht/.vscode/extensions.json ================================================ { "recommendations": [ "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } ================================================ FILE: exercises/practice/yacht/.vscode/settings.json ================================================ { "cSpell.words": ["exercism"], "search.exclude": { "**/.yarn": true, "**/.pnp.*": true } } ================================================ FILE: exercises/practice/yacht/.yarn/releases/yarn-3.6.4.cjs ================================================ #!/usr/bin/env node /* eslint-disable */ //prettier-ignore (()=>{var Dge=Object.create;var lS=Object.defineProperty;var kge=Object.getOwnPropertyDescriptor;var Rge=Object.getOwnPropertyNames;var Fge=Object.getPrototypeOf,Nge=Object.prototype.hasOwnProperty;var J=(r=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(r,{get:(e,t)=>(typeof require<"u"?require:e)[t]}):r)(function(r){if(typeof require<"u")return require.apply(this,arguments);throw new Error('Dynamic require of "'+r+'" is not supported')});var Tge=(r,e)=>()=>(r&&(e=r(r=0)),e);var w=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports),ut=(r,e)=>{for(var t in e)lS(r,t,{get:e[t],enumerable:!0})},Lge=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Rge(e))!Nge.call(r,n)&&n!==t&&lS(r,n,{get:()=>e[n],enumerable:!(i=kge(e,n))||i.enumerable});return r};var Pe=(r,e,t)=>(t=r!=null?Dge(Fge(r)):{},Lge(e||!r||!r.__esModule?lS(t,"default",{value:r,enumerable:!0}):t,r));var PK=w((zXe,xK)=>{xK.exports=vK;vK.sync=ife;var QK=J("fs");function rfe(r,e){var t=e.pathExt!==void 0?e.pathExt:process.env.PATHEXT;if(!t||(t=t.split(";"),t.indexOf("")!==-1))return!0;for(var i=0;i{FK.exports=kK;kK.sync=nfe;var DK=J("fs");function kK(r,e,t){DK.stat(r,function(i,n){t(i,i?!1:RK(n,e))})}function nfe(r,e){return RK(DK.statSync(r),e)}function RK(r,e){return r.isFile()&&sfe(r,e)}function sfe(r,e){var t=r.mode,i=r.uid,n=r.gid,s=e.uid!==void 0?e.uid:process.getuid&&process.getuid(),o=e.gid!==void 0?e.gid:process.getgid&&process.getgid(),a=parseInt("100",8),l=parseInt("010",8),c=parseInt("001",8),u=a|l,g=t&c||t&l&&n===o||t&a&&i===s||t&u&&s===0;return g}});var LK=w((ZXe,TK)=>{var XXe=J("fs"),lI;process.platform==="win32"||global.TESTING_WINDOWS?lI=PK():lI=NK();TK.exports=SS;SS.sync=ofe;function SS(r,e,t){if(typeof e=="function"&&(t=e,e={}),!t){if(typeof Promise!="function")throw new TypeError("callback not provided");return new Promise(function(i,n){SS(r,e||{},function(s,o){s?n(s):i(o)})})}lI(r,e||{},function(i,n){i&&(i.code==="EACCES"||e&&e.ignoreErrors)&&(i=null,n=!1),t(i,n)})}function ofe(r,e){try{return lI.sync(r,e||{})}catch(t){if(e&&e.ignoreErrors||t.code==="EACCES")return!1;throw t}}});var YK=w((_Xe,GK)=>{var Dg=process.platform==="win32"||process.env.OSTYPE==="cygwin"||process.env.OSTYPE==="msys",OK=J("path"),afe=Dg?";":":",MK=LK(),KK=r=>Object.assign(new Error(`not found: ${r}`),{code:"ENOENT"}),UK=(r,e)=>{let t=e.colon||afe,i=r.match(/\//)||Dg&&r.match(/\\/)?[""]:[...Dg?[process.cwd()]:[],...(e.path||process.env.PATH||"").split(t)],n=Dg?e.pathExt||process.env.PATHEXT||".EXE;.CMD;.BAT;.COM":"",s=Dg?n.split(t):[""];return Dg&&r.indexOf(".")!==-1&&s[0]!==""&&s.unshift(""),{pathEnv:i,pathExt:s,pathExtExe:n}},HK=(r,e,t)=>{typeof e=="function"&&(t=e,e={}),e||(e={});let{pathEnv:i,pathExt:n,pathExtExe:s}=UK(r,e),o=[],a=c=>new Promise((u,g)=>{if(c===i.length)return e.all&&o.length?u(o):g(KK(r));let f=i[c],h=/^".*"$/.test(f)?f.slice(1,-1):f,p=OK.join(h,r),C=!h&&/^\.[\\\/]/.test(r)?r.slice(0,2)+p:p;u(l(C,c,0))}),l=(c,u,g)=>new Promise((f,h)=>{if(g===n.length)return f(a(u+1));let p=n[g];MK(c+p,{pathExt:s},(C,y)=>{if(!C&&y)if(e.all)o.push(c+p);else return f(c+p);return f(l(c,u,g+1))})});return t?a(0).then(c=>t(null,c),t):a(0)},Afe=(r,e)=>{e=e||{};let{pathEnv:t,pathExt:i,pathExtExe:n}=UK(r,e),s=[];for(let o=0;o{"use strict";var jK=(r={})=>{let e=r.env||process.env;return(r.platform||process.platform)!=="win32"?"PATH":Object.keys(e).reverse().find(i=>i.toUpperCase()==="PATH")||"Path"};vS.exports=jK;vS.exports.default=jK});var VK=w((eZe,zK)=>{"use strict";var JK=J("path"),lfe=YK(),cfe=qK();function WK(r,e){let t=r.options.env||process.env,i=process.cwd(),n=r.options.cwd!=null,s=n&&process.chdir!==void 0&&!process.chdir.disabled;if(s)try{process.chdir(r.options.cwd)}catch{}let o;try{o=lfe.sync(r.command,{path:t[cfe({env:t})],pathExt:e?JK.delimiter:void 0})}catch{}finally{s&&process.chdir(i)}return o&&(o=JK.resolve(n?r.options.cwd:"",o)),o}function ufe(r){return WK(r)||WK(r,!0)}zK.exports=ufe});var XK=w((tZe,PS)=>{"use strict";var xS=/([()\][%!^"`<>&|;, *?])/g;function gfe(r){return r=r.replace(xS,"^$1"),r}function ffe(r,e){return r=`${r}`,r=r.replace(/(\\*)"/g,'$1$1\\"'),r=r.replace(/(\\*)$/,"$1$1"),r=`"${r}"`,r=r.replace(xS,"^$1"),e&&(r=r.replace(xS,"^$1")),r}PS.exports.command=gfe;PS.exports.argument=ffe});var _K=w((rZe,ZK)=>{"use strict";ZK.exports=/^#!(.*)/});var eU=w((iZe,$K)=>{"use strict";var hfe=_K();$K.exports=(r="")=>{let e=r.match(hfe);if(!e)return null;let[t,i]=e[0].replace(/#! ?/,"").split(" "),n=t.split("/").pop();return n==="env"?i:i?`${n} ${i}`:n}});var rU=w((nZe,tU)=>{"use strict";var DS=J("fs"),pfe=eU();function dfe(r){let t=Buffer.alloc(150),i;try{i=DS.openSync(r,"r"),DS.readSync(i,t,0,150,0),DS.closeSync(i)}catch{}return pfe(t.toString())}tU.exports=dfe});var oU=w((sZe,sU)=>{"use strict";var Cfe=J("path"),iU=VK(),nU=XK(),mfe=rU(),Efe=process.platform==="win32",Ife=/\.(?:com|exe)$/i,yfe=/node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;function wfe(r){r.file=iU(r);let e=r.file&&mfe(r.file);return e?(r.args.unshift(r.file),r.command=e,iU(r)):r.file}function Bfe(r){if(!Efe)return r;let e=wfe(r),t=!Ife.test(e);if(r.options.forceShell||t){let i=yfe.test(e);r.command=Cfe.normalize(r.command),r.command=nU.command(r.command),r.args=r.args.map(s=>nU.argument(s,i));let n=[r.command].concat(r.args).join(" ");r.args=["/d","/s","/c",`"${n}"`],r.command=process.env.comspec||"cmd.exe",r.options.windowsVerbatimArguments=!0}return r}function bfe(r,e,t){e&&!Array.isArray(e)&&(t=e,e=null),e=e?e.slice(0):[],t=Object.assign({},t);let i={command:r,args:e,options:t,file:void 0,original:{command:r,args:e}};return t.shell?i:Bfe(i)}sU.exports=bfe});var lU=w((oZe,AU)=>{"use strict";var kS=process.platform==="win32";function RS(r,e){return Object.assign(new Error(`${e} ${r.command} ENOENT`),{code:"ENOENT",errno:"ENOENT",syscall:`${e} ${r.command}`,path:r.command,spawnargs:r.args})}function Qfe(r,e){if(!kS)return;let t=r.emit;r.emit=function(i,n){if(i==="exit"){let s=aU(n,e,"spawn");if(s)return t.call(r,"error",s)}return t.apply(r,arguments)}}function aU(r,e){return kS&&r===1&&!e.file?RS(e.original,"spawn"):null}function Sfe(r,e){return kS&&r===1&&!e.file?RS(e.original,"spawnSync"):null}AU.exports={hookChildProcess:Qfe,verifyENOENT:aU,verifyENOENTSync:Sfe,notFoundError:RS}});var TS=w((aZe,kg)=>{"use strict";var cU=J("child_process"),FS=oU(),NS=lU();function uU(r,e,t){let i=FS(r,e,t),n=cU.spawn(i.command,i.args,i.options);return NS.hookChildProcess(n,i),n}function vfe(r,e,t){let i=FS(r,e,t),n=cU.spawnSync(i.command,i.args,i.options);return n.error=n.error||NS.verifyENOENTSync(n.status,i),n}kg.exports=uU;kg.exports.spawn=uU;kg.exports.sync=vfe;kg.exports._parse=FS;kg.exports._enoent=NS});var fU=w((AZe,gU)=>{"use strict";function xfe(r,e){function t(){this.constructor=r}t.prototype=e.prototype,r.prototype=new t}function Zl(r,e,t,i){this.message=r,this.expected=e,this.found=t,this.location=i,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,Zl)}xfe(Zl,Error);Zl.buildMessage=function(r,e){var t={literal:function(c){return'"'+n(c.text)+'"'},class:function(c){var u="",g;for(g=0;g0){for(g=1,f=1;g>",ie=me(">>",!1),de=">&",_e=me(">&",!1),Pt=">",It=me(">",!1),Or="<<<",ii=me("<<<",!1),gi="<&",hr=me("<&",!1),fi="<",ni=me("<",!1),Ks=function(m){return{type:"argument",segments:[].concat(...m)}},pr=function(m){return m},Ii="$'",rs=me("$'",!1),fa="'",dA=me("'",!1),cg=function(m){return[{type:"text",text:m}]},is='""',CA=me('""',!1),ha=function(){return{type:"text",text:""}},wp='"',mA=me('"',!1),EA=function(m){return m},wr=function(m){return{type:"arithmetic",arithmetic:m,quoted:!0}},Tl=function(m){return{type:"shell",shell:m,quoted:!0}},ug=function(m){return{type:"variable",...m,quoted:!0}},yo=function(m){return{type:"text",text:m}},gg=function(m){return{type:"arithmetic",arithmetic:m,quoted:!1}},Bp=function(m){return{type:"shell",shell:m,quoted:!1}},bp=function(m){return{type:"variable",...m,quoted:!1}},vr=function(m){return{type:"glob",pattern:m}},se=/^[^']/,wo=Je(["'"],!0,!1),Fn=function(m){return m.join("")},fg=/^[^$"]/,bt=Je(["$",'"'],!0,!1),Ll=`\\ `,Nn=me(`\\ `,!1),ns=function(){return""},ss="\\",gt=me("\\",!1),Bo=/^[\\$"`]/,At=Je(["\\","$",'"',"`"],!1,!1),ln=function(m){return m},S="\\a",Lt=me("\\a",!1),hg=function(){return"a"},Ol="\\b",Qp=me("\\b",!1),Sp=function(){return"\b"},vp=/^[Ee]/,xp=Je(["E","e"],!1,!1),Pp=function(){return"\x1B"},G="\\f",yt=me("\\f",!1),IA=function(){return"\f"},zi="\\n",Ml=me("\\n",!1),Xe=function(){return` `},pa="\\r",pg=me("\\r",!1),OE=function(){return"\r"},Dp="\\t",ME=me("\\t",!1),ar=function(){return" "},Tn="\\v",Kl=me("\\v",!1),kp=function(){return"\v"},Us=/^[\\'"?]/,da=Je(["\\","'",'"',"?"],!1,!1),cn=function(m){return String.fromCharCode(parseInt(m,16))},Le="\\x",dg=me("\\x",!1),Ul="\\u",Hs=me("\\u",!1),Hl="\\U",yA=me("\\U",!1),Cg=function(m){return String.fromCodePoint(parseInt(m,16))},mg=/^[0-7]/,Ca=Je([["0","7"]],!1,!1),ma=/^[0-9a-fA-f]/,rt=Je([["0","9"],["a","f"],["A","f"]],!1,!1),bo=nt(),wA="-",Gl=me("-",!1),Gs="+",Yl=me("+",!1),KE=".",Rp=me(".",!1),Eg=function(m,Q,N){return{type:"number",value:(m==="-"?-1:1)*parseFloat(Q.join("")+"."+N.join(""))}},Fp=function(m,Q){return{type:"number",value:(m==="-"?-1:1)*parseInt(Q.join(""))}},UE=function(m){return{type:"variable",...m}},jl=function(m){return{type:"variable",name:m}},HE=function(m){return m},Ig="*",BA=me("*",!1),Rr="/",GE=me("/",!1),Ys=function(m,Q,N){return{type:Q==="*"?"multiplication":"division",right:N}},js=function(m,Q){return Q.reduce((N,U)=>({left:N,...U}),m)},yg=function(m,Q,N){return{type:Q==="+"?"addition":"subtraction",right:N}},bA="$((",R=me("$((",!1),q="))",Ce=me("))",!1),Ke=function(m){return m},Re="$(",ze=me("$(",!1),dt=function(m){return m},Ft="${",Ln=me("${",!1),JQ=":-",k1=me(":-",!1),R1=function(m,Q){return{name:m,defaultValue:Q}},WQ=":-}",F1=me(":-}",!1),N1=function(m){return{name:m,defaultValue:[]}},zQ=":+",T1=me(":+",!1),L1=function(m,Q){return{name:m,alternativeValue:Q}},VQ=":+}",O1=me(":+}",!1),M1=function(m){return{name:m,alternativeValue:[]}},XQ=function(m){return{name:m}},K1="$",U1=me("$",!1),H1=function(m){return e.isGlobPattern(m)},G1=function(m){return m},ZQ=/^[a-zA-Z0-9_]/,_Q=Je([["a","z"],["A","Z"],["0","9"],"_"],!1,!1),$Q=function(){return L()},eS=/^[$@*?#a-zA-Z0-9_\-]/,tS=Je(["$","@","*","?","#",["a","z"],["A","Z"],["0","9"],"_","-"],!1,!1),Y1=/^[(){}<>$|&; \t"']/,wg=Je(["(",")","{","}","<",">","$","|","&",";"," "," ",'"',"'"],!1,!1),rS=/^[<>&; \t"']/,iS=Je(["<",">","&",";"," "," ",'"',"'"],!1,!1),YE=/^[ \t]/,jE=Je([" "," "],!1,!1),b=0,Me=0,QA=[{line:1,column:1}],d=0,E=[],I=0,k;if("startRule"in e){if(!(e.startRule in i))throw new Error(`Can't start parsing from rule "`+e.startRule+'".');n=i[e.startRule]}function L(){return r.substring(Me,b)}function Z(){return Et(Me,b)}function te(m,Q){throw Q=Q!==void 0?Q:Et(Me,b),Ri([lt(m)],r.substring(Me,b),Q)}function we(m,Q){throw Q=Q!==void 0?Q:Et(Me,b),On(m,Q)}function me(m,Q){return{type:"literal",text:m,ignoreCase:Q}}function Je(m,Q,N){return{type:"class",parts:m,inverted:Q,ignoreCase:N}}function nt(){return{type:"any"}}function wt(){return{type:"end"}}function lt(m){return{type:"other",description:m}}function it(m){var Q=QA[m],N;if(Q)return Q;for(N=m-1;!QA[N];)N--;for(Q=QA[N],Q={line:Q.line,column:Q.column};Nd&&(d=b,E=[]),E.push(m))}function On(m,Q){return new Zl(m,null,null,Q)}function Ri(m,Q,N){return new Zl(Zl.buildMessage(m,Q),m,Q,N)}function SA(){var m,Q;return m=b,Q=Mr(),Q===t&&(Q=null),Q!==t&&(Me=m,Q=s(Q)),m=Q,m}function Mr(){var m,Q,N,U,ce;if(m=b,Q=Kr(),Q!==t){for(N=[],U=He();U!==t;)N.push(U),U=He();N!==t?(U=Ea(),U!==t?(ce=os(),ce===t&&(ce=null),ce!==t?(Me=m,Q=o(Q,U,ce),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t)}else b=m,m=t;if(m===t)if(m=b,Q=Kr(),Q!==t){for(N=[],U=He();U!==t;)N.push(U),U=He();N!==t?(U=Ea(),U===t&&(U=null),U!==t?(Me=m,Q=a(Q,U),m=Q):(b=m,m=t)):(b=m,m=t)}else b=m,m=t;return m}function os(){var m,Q,N,U,ce;for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();if(Q!==t)if(N=Mr(),N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();U!==t?(Me=m,Q=l(N),m=Q):(b=m,m=t)}else b=m,m=t;else b=m,m=t;return m}function Ea(){var m;return r.charCodeAt(b)===59?(m=c,b++):(m=t,I===0&&be(u)),m===t&&(r.charCodeAt(b)===38?(m=g,b++):(m=t,I===0&&be(f))),m}function Kr(){var m,Q,N;return m=b,Q=j1(),Q!==t?(N=fge(),N===t&&(N=null),N!==t?(Me=m,Q=h(Q,N),m=Q):(b=m,m=t)):(b=m,m=t),m}function fge(){var m,Q,N,U,ce,Se,ht;for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();if(Q!==t)if(N=hge(),N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();if(U!==t)if(ce=Kr(),ce!==t){for(Se=[],ht=He();ht!==t;)Se.push(ht),ht=He();Se!==t?(Me=m,Q=p(N,ce),m=Q):(b=m,m=t)}else b=m,m=t;else b=m,m=t}else b=m,m=t;else b=m,m=t;return m}function hge(){var m;return r.substr(b,2)===C?(m=C,b+=2):(m=t,I===0&&be(y)),m===t&&(r.substr(b,2)===B?(m=B,b+=2):(m=t,I===0&&be(v))),m}function j1(){var m,Q,N;return m=b,Q=Cge(),Q!==t?(N=pge(),N===t&&(N=null),N!==t?(Me=m,Q=D(Q,N),m=Q):(b=m,m=t)):(b=m,m=t),m}function pge(){var m,Q,N,U,ce,Se,ht;for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();if(Q!==t)if(N=dge(),N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();if(U!==t)if(ce=j1(),ce!==t){for(Se=[],ht=He();ht!==t;)Se.push(ht),ht=He();Se!==t?(Me=m,Q=T(N,ce),m=Q):(b=m,m=t)}else b=m,m=t;else b=m,m=t}else b=m,m=t;else b=m,m=t;return m}function dge(){var m;return r.substr(b,2)===H?(m=H,b+=2):(m=t,I===0&&be(j)),m===t&&(r.charCodeAt(b)===124?(m=$,b++):(m=t,I===0&&be(V))),m}function qE(){var m,Q,N,U,ce,Se;if(m=b,Q=rK(),Q!==t)if(r.charCodeAt(b)===61?(N=W,b++):(N=t,I===0&&be(_)),N!==t)if(U=W1(),U!==t){for(ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();ce!==t?(Me=m,Q=A(Q,U),m=Q):(b=m,m=t)}else b=m,m=t;else b=m,m=t;else b=m,m=t;if(m===t)if(m=b,Q=rK(),Q!==t)if(r.charCodeAt(b)===61?(N=W,b++):(N=t,I===0&&be(_)),N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();U!==t?(Me=m,Q=Ae(Q),m=Q):(b=m,m=t)}else b=m,m=t;else b=m,m=t;return m}function Cge(){var m,Q,N,U,ce,Se,ht,Bt,qr,hi,as;for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();if(Q!==t)if(r.charCodeAt(b)===40?(N=ge,b++):(N=t,I===0&&be(re)),N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();if(U!==t)if(ce=Mr(),ce!==t){for(Se=[],ht=He();ht!==t;)Se.push(ht),ht=He();if(Se!==t)if(r.charCodeAt(b)===41?(ht=O,b++):(ht=t,I===0&&be(F)),ht!==t){for(Bt=[],qr=He();qr!==t;)Bt.push(qr),qr=He();if(Bt!==t){for(qr=[],hi=Np();hi!==t;)qr.push(hi),hi=Np();if(qr!==t){for(hi=[],as=He();as!==t;)hi.push(as),as=He();hi!==t?(Me=m,Q=ue(ce,qr),m=Q):(b=m,m=t)}else b=m,m=t}else b=m,m=t}else b=m,m=t;else b=m,m=t}else b=m,m=t;else b=m,m=t}else b=m,m=t;else b=m,m=t;if(m===t){for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();if(Q!==t)if(r.charCodeAt(b)===123?(N=pe,b++):(N=t,I===0&&be(ke)),N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();if(U!==t)if(ce=Mr(),ce!==t){for(Se=[],ht=He();ht!==t;)Se.push(ht),ht=He();if(Se!==t)if(r.charCodeAt(b)===125?(ht=Fe,b++):(ht=t,I===0&&be(Ne)),ht!==t){for(Bt=[],qr=He();qr!==t;)Bt.push(qr),qr=He();if(Bt!==t){for(qr=[],hi=Np();hi!==t;)qr.push(hi),hi=Np();if(qr!==t){for(hi=[],as=He();as!==t;)hi.push(as),as=He();hi!==t?(Me=m,Q=oe(ce,qr),m=Q):(b=m,m=t)}else b=m,m=t}else b=m,m=t}else b=m,m=t;else b=m,m=t}else b=m,m=t;else b=m,m=t}else b=m,m=t;else b=m,m=t;if(m===t){for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();if(Q!==t){for(N=[],U=qE();U!==t;)N.push(U),U=qE();if(N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();if(U!==t){if(ce=[],Se=J1(),Se!==t)for(;Se!==t;)ce.push(Se),Se=J1();else ce=t;if(ce!==t){for(Se=[],ht=He();ht!==t;)Se.push(ht),ht=He();Se!==t?(Me=m,Q=le(N,ce),m=Q):(b=m,m=t)}else b=m,m=t}else b=m,m=t}else b=m,m=t}else b=m,m=t;if(m===t){for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();if(Q!==t){if(N=[],U=qE(),U!==t)for(;U!==t;)N.push(U),U=qE();else N=t;if(N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();U!==t?(Me=m,Q=Be(N),m=Q):(b=m,m=t)}else b=m,m=t}else b=m,m=t}}}return m}function q1(){var m,Q,N,U,ce;for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();if(Q!==t){if(N=[],U=JE(),U!==t)for(;U!==t;)N.push(U),U=JE();else N=t;if(N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();U!==t?(Me=m,Q=fe(N),m=Q):(b=m,m=t)}else b=m,m=t}else b=m,m=t;return m}function J1(){var m,Q,N;for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();if(Q!==t?(N=Np(),N!==t?(Me=m,Q=ae(N),m=Q):(b=m,m=t)):(b=m,m=t),m===t){for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();Q!==t?(N=JE(),N!==t?(Me=m,Q=ae(N),m=Q):(b=m,m=t)):(b=m,m=t)}return m}function Np(){var m,Q,N,U,ce;for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();return Q!==t?(qe.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(ne)),N===t&&(N=null),N!==t?(U=mge(),U!==t?(ce=JE(),ce!==t?(Me=m,Q=Y(N,U,ce),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t)):(b=m,m=t),m}function mge(){var m;return r.substr(b,2)===he?(m=he,b+=2):(m=t,I===0&&be(ie)),m===t&&(r.substr(b,2)===de?(m=de,b+=2):(m=t,I===0&&be(_e)),m===t&&(r.charCodeAt(b)===62?(m=Pt,b++):(m=t,I===0&&be(It)),m===t&&(r.substr(b,3)===Or?(m=Or,b+=3):(m=t,I===0&&be(ii)),m===t&&(r.substr(b,2)===gi?(m=gi,b+=2):(m=t,I===0&&be(hr)),m===t&&(r.charCodeAt(b)===60?(m=fi,b++):(m=t,I===0&&be(ni))))))),m}function JE(){var m,Q,N;for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();return Q!==t?(N=W1(),N!==t?(Me=m,Q=ae(N),m=Q):(b=m,m=t)):(b=m,m=t),m}function W1(){var m,Q,N;if(m=b,Q=[],N=z1(),N!==t)for(;N!==t;)Q.push(N),N=z1();else Q=t;return Q!==t&&(Me=m,Q=Ks(Q)),m=Q,m}function z1(){var m,Q;return m=b,Q=Ege(),Q!==t&&(Me=m,Q=pr(Q)),m=Q,m===t&&(m=b,Q=Ige(),Q!==t&&(Me=m,Q=pr(Q)),m=Q,m===t&&(m=b,Q=yge(),Q!==t&&(Me=m,Q=pr(Q)),m=Q,m===t&&(m=b,Q=wge(),Q!==t&&(Me=m,Q=pr(Q)),m=Q))),m}function Ege(){var m,Q,N,U;return m=b,r.substr(b,2)===Ii?(Q=Ii,b+=2):(Q=t,I===0&&be(rs)),Q!==t?(N=Qge(),N!==t?(r.charCodeAt(b)===39?(U=fa,b++):(U=t,I===0&&be(dA)),U!==t?(Me=m,Q=cg(N),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t),m}function Ige(){var m,Q,N,U;return m=b,r.charCodeAt(b)===39?(Q=fa,b++):(Q=t,I===0&&be(dA)),Q!==t?(N=Bge(),N!==t?(r.charCodeAt(b)===39?(U=fa,b++):(U=t,I===0&&be(dA)),U!==t?(Me=m,Q=cg(N),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t),m}function yge(){var m,Q,N,U;if(m=b,r.substr(b,2)===is?(Q=is,b+=2):(Q=t,I===0&&be(CA)),Q!==t&&(Me=m,Q=ha()),m=Q,m===t)if(m=b,r.charCodeAt(b)===34?(Q=wp,b++):(Q=t,I===0&&be(mA)),Q!==t){for(N=[],U=V1();U!==t;)N.push(U),U=V1();N!==t?(r.charCodeAt(b)===34?(U=wp,b++):(U=t,I===0&&be(mA)),U!==t?(Me=m,Q=EA(N),m=Q):(b=m,m=t)):(b=m,m=t)}else b=m,m=t;return m}function wge(){var m,Q,N;if(m=b,Q=[],N=X1(),N!==t)for(;N!==t;)Q.push(N),N=X1();else Q=t;return Q!==t&&(Me=m,Q=EA(Q)),m=Q,m}function V1(){var m,Q;return m=b,Q=eK(),Q!==t&&(Me=m,Q=wr(Q)),m=Q,m===t&&(m=b,Q=tK(),Q!==t&&(Me=m,Q=Tl(Q)),m=Q,m===t&&(m=b,Q=aS(),Q!==t&&(Me=m,Q=ug(Q)),m=Q,m===t&&(m=b,Q=bge(),Q!==t&&(Me=m,Q=yo(Q)),m=Q))),m}function X1(){var m,Q;return m=b,Q=eK(),Q!==t&&(Me=m,Q=gg(Q)),m=Q,m===t&&(m=b,Q=tK(),Q!==t&&(Me=m,Q=Bp(Q)),m=Q,m===t&&(m=b,Q=aS(),Q!==t&&(Me=m,Q=bp(Q)),m=Q,m===t&&(m=b,Q=xge(),Q!==t&&(Me=m,Q=vr(Q)),m=Q,m===t&&(m=b,Q=vge(),Q!==t&&(Me=m,Q=yo(Q)),m=Q)))),m}function Bge(){var m,Q,N;for(m=b,Q=[],se.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(wo));N!==t;)Q.push(N),se.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(wo));return Q!==t&&(Me=m,Q=Fn(Q)),m=Q,m}function bge(){var m,Q,N;if(m=b,Q=[],N=Z1(),N===t&&(fg.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(bt))),N!==t)for(;N!==t;)Q.push(N),N=Z1(),N===t&&(fg.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(bt)));else Q=t;return Q!==t&&(Me=m,Q=Fn(Q)),m=Q,m}function Z1(){var m,Q,N;return m=b,r.substr(b,2)===Ll?(Q=Ll,b+=2):(Q=t,I===0&&be(Nn)),Q!==t&&(Me=m,Q=ns()),m=Q,m===t&&(m=b,r.charCodeAt(b)===92?(Q=ss,b++):(Q=t,I===0&&be(gt)),Q!==t?(Bo.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(At)),N!==t?(Me=m,Q=ln(N),m=Q):(b=m,m=t)):(b=m,m=t)),m}function Qge(){var m,Q,N;for(m=b,Q=[],N=_1(),N===t&&(se.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(wo)));N!==t;)Q.push(N),N=_1(),N===t&&(se.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(wo)));return Q!==t&&(Me=m,Q=Fn(Q)),m=Q,m}function _1(){var m,Q,N;return m=b,r.substr(b,2)===S?(Q=S,b+=2):(Q=t,I===0&&be(Lt)),Q!==t&&(Me=m,Q=hg()),m=Q,m===t&&(m=b,r.substr(b,2)===Ol?(Q=Ol,b+=2):(Q=t,I===0&&be(Qp)),Q!==t&&(Me=m,Q=Sp()),m=Q,m===t&&(m=b,r.charCodeAt(b)===92?(Q=ss,b++):(Q=t,I===0&&be(gt)),Q!==t?(vp.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(xp)),N!==t?(Me=m,Q=Pp(),m=Q):(b=m,m=t)):(b=m,m=t),m===t&&(m=b,r.substr(b,2)===G?(Q=G,b+=2):(Q=t,I===0&&be(yt)),Q!==t&&(Me=m,Q=IA()),m=Q,m===t&&(m=b,r.substr(b,2)===zi?(Q=zi,b+=2):(Q=t,I===0&&be(Ml)),Q!==t&&(Me=m,Q=Xe()),m=Q,m===t&&(m=b,r.substr(b,2)===pa?(Q=pa,b+=2):(Q=t,I===0&&be(pg)),Q!==t&&(Me=m,Q=OE()),m=Q,m===t&&(m=b,r.substr(b,2)===Dp?(Q=Dp,b+=2):(Q=t,I===0&&be(ME)),Q!==t&&(Me=m,Q=ar()),m=Q,m===t&&(m=b,r.substr(b,2)===Tn?(Q=Tn,b+=2):(Q=t,I===0&&be(Kl)),Q!==t&&(Me=m,Q=kp()),m=Q,m===t&&(m=b,r.charCodeAt(b)===92?(Q=ss,b++):(Q=t,I===0&&be(gt)),Q!==t?(Us.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(da)),N!==t?(Me=m,Q=ln(N),m=Q):(b=m,m=t)):(b=m,m=t),m===t&&(m=Sge()))))))))),m}function Sge(){var m,Q,N,U,ce,Se,ht,Bt,qr,hi,as,AS;return m=b,r.charCodeAt(b)===92?(Q=ss,b++):(Q=t,I===0&&be(gt)),Q!==t?(N=nS(),N!==t?(Me=m,Q=cn(N),m=Q):(b=m,m=t)):(b=m,m=t),m===t&&(m=b,r.substr(b,2)===Le?(Q=Le,b+=2):(Q=t,I===0&&be(dg)),Q!==t?(N=b,U=b,ce=nS(),ce!==t?(Se=Mn(),Se!==t?(ce=[ce,Se],U=ce):(b=U,U=t)):(b=U,U=t),U===t&&(U=nS()),U!==t?N=r.substring(N,b):N=U,N!==t?(Me=m,Q=cn(N),m=Q):(b=m,m=t)):(b=m,m=t),m===t&&(m=b,r.substr(b,2)===Ul?(Q=Ul,b+=2):(Q=t,I===0&&be(Hs)),Q!==t?(N=b,U=b,ce=Mn(),ce!==t?(Se=Mn(),Se!==t?(ht=Mn(),ht!==t?(Bt=Mn(),Bt!==t?(ce=[ce,Se,ht,Bt],U=ce):(b=U,U=t)):(b=U,U=t)):(b=U,U=t)):(b=U,U=t),U!==t?N=r.substring(N,b):N=U,N!==t?(Me=m,Q=cn(N),m=Q):(b=m,m=t)):(b=m,m=t),m===t&&(m=b,r.substr(b,2)===Hl?(Q=Hl,b+=2):(Q=t,I===0&&be(yA)),Q!==t?(N=b,U=b,ce=Mn(),ce!==t?(Se=Mn(),Se!==t?(ht=Mn(),ht!==t?(Bt=Mn(),Bt!==t?(qr=Mn(),qr!==t?(hi=Mn(),hi!==t?(as=Mn(),as!==t?(AS=Mn(),AS!==t?(ce=[ce,Se,ht,Bt,qr,hi,as,AS],U=ce):(b=U,U=t)):(b=U,U=t)):(b=U,U=t)):(b=U,U=t)):(b=U,U=t)):(b=U,U=t)):(b=U,U=t)):(b=U,U=t),U!==t?N=r.substring(N,b):N=U,N!==t?(Me=m,Q=Cg(N),m=Q):(b=m,m=t)):(b=m,m=t)))),m}function nS(){var m;return mg.test(r.charAt(b))?(m=r.charAt(b),b++):(m=t,I===0&&be(Ca)),m}function Mn(){var m;return ma.test(r.charAt(b))?(m=r.charAt(b),b++):(m=t,I===0&&be(rt)),m}function vge(){var m,Q,N,U,ce;if(m=b,Q=[],N=b,r.charCodeAt(b)===92?(U=ss,b++):(U=t,I===0&&be(gt)),U!==t?(r.length>b?(ce=r.charAt(b),b++):(ce=t,I===0&&be(bo)),ce!==t?(Me=N,U=ln(ce),N=U):(b=N,N=t)):(b=N,N=t),N===t&&(N=b,U=b,I++,ce=iK(),I--,ce===t?U=void 0:(b=U,U=t),U!==t?(r.length>b?(ce=r.charAt(b),b++):(ce=t,I===0&&be(bo)),ce!==t?(Me=N,U=ln(ce),N=U):(b=N,N=t)):(b=N,N=t)),N!==t)for(;N!==t;)Q.push(N),N=b,r.charCodeAt(b)===92?(U=ss,b++):(U=t,I===0&&be(gt)),U!==t?(r.length>b?(ce=r.charAt(b),b++):(ce=t,I===0&&be(bo)),ce!==t?(Me=N,U=ln(ce),N=U):(b=N,N=t)):(b=N,N=t),N===t&&(N=b,U=b,I++,ce=iK(),I--,ce===t?U=void 0:(b=U,U=t),U!==t?(r.length>b?(ce=r.charAt(b),b++):(ce=t,I===0&&be(bo)),ce!==t?(Me=N,U=ln(ce),N=U):(b=N,N=t)):(b=N,N=t));else Q=t;return Q!==t&&(Me=m,Q=Fn(Q)),m=Q,m}function sS(){var m,Q,N,U,ce,Se;if(m=b,r.charCodeAt(b)===45?(Q=wA,b++):(Q=t,I===0&&be(Gl)),Q===t&&(r.charCodeAt(b)===43?(Q=Gs,b++):(Q=t,I===0&&be(Yl))),Q===t&&(Q=null),Q!==t){if(N=[],qe.test(r.charAt(b))?(U=r.charAt(b),b++):(U=t,I===0&&be(ne)),U!==t)for(;U!==t;)N.push(U),qe.test(r.charAt(b))?(U=r.charAt(b),b++):(U=t,I===0&&be(ne));else N=t;if(N!==t)if(r.charCodeAt(b)===46?(U=KE,b++):(U=t,I===0&&be(Rp)),U!==t){if(ce=[],qe.test(r.charAt(b))?(Se=r.charAt(b),b++):(Se=t,I===0&&be(ne)),Se!==t)for(;Se!==t;)ce.push(Se),qe.test(r.charAt(b))?(Se=r.charAt(b),b++):(Se=t,I===0&&be(ne));else ce=t;ce!==t?(Me=m,Q=Eg(Q,N,ce),m=Q):(b=m,m=t)}else b=m,m=t;else b=m,m=t}else b=m,m=t;if(m===t){if(m=b,r.charCodeAt(b)===45?(Q=wA,b++):(Q=t,I===0&&be(Gl)),Q===t&&(r.charCodeAt(b)===43?(Q=Gs,b++):(Q=t,I===0&&be(Yl))),Q===t&&(Q=null),Q!==t){if(N=[],qe.test(r.charAt(b))?(U=r.charAt(b),b++):(U=t,I===0&&be(ne)),U!==t)for(;U!==t;)N.push(U),qe.test(r.charAt(b))?(U=r.charAt(b),b++):(U=t,I===0&&be(ne));else N=t;N!==t?(Me=m,Q=Fp(Q,N),m=Q):(b=m,m=t)}else b=m,m=t;if(m===t&&(m=b,Q=aS(),Q!==t&&(Me=m,Q=UE(Q)),m=Q,m===t&&(m=b,Q=ql(),Q!==t&&(Me=m,Q=jl(Q)),m=Q,m===t)))if(m=b,r.charCodeAt(b)===40?(Q=ge,b++):(Q=t,I===0&&be(re)),Q!==t){for(N=[],U=He();U!==t;)N.push(U),U=He();if(N!==t)if(U=$1(),U!==t){for(ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();ce!==t?(r.charCodeAt(b)===41?(Se=O,b++):(Se=t,I===0&&be(F)),Se!==t?(Me=m,Q=HE(U),m=Q):(b=m,m=t)):(b=m,m=t)}else b=m,m=t;else b=m,m=t}else b=m,m=t}return m}function oS(){var m,Q,N,U,ce,Se,ht,Bt;if(m=b,Q=sS(),Q!==t){for(N=[],U=b,ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();if(ce!==t)if(r.charCodeAt(b)===42?(Se=Ig,b++):(Se=t,I===0&&be(BA)),Se===t&&(r.charCodeAt(b)===47?(Se=Rr,b++):(Se=t,I===0&&be(GE))),Se!==t){for(ht=[],Bt=He();Bt!==t;)ht.push(Bt),Bt=He();ht!==t?(Bt=sS(),Bt!==t?(Me=U,ce=Ys(Q,Se,Bt),U=ce):(b=U,U=t)):(b=U,U=t)}else b=U,U=t;else b=U,U=t;for(;U!==t;){for(N.push(U),U=b,ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();if(ce!==t)if(r.charCodeAt(b)===42?(Se=Ig,b++):(Se=t,I===0&&be(BA)),Se===t&&(r.charCodeAt(b)===47?(Se=Rr,b++):(Se=t,I===0&&be(GE))),Se!==t){for(ht=[],Bt=He();Bt!==t;)ht.push(Bt),Bt=He();ht!==t?(Bt=sS(),Bt!==t?(Me=U,ce=Ys(Q,Se,Bt),U=ce):(b=U,U=t)):(b=U,U=t)}else b=U,U=t;else b=U,U=t}N!==t?(Me=m,Q=js(Q,N),m=Q):(b=m,m=t)}else b=m,m=t;return m}function $1(){var m,Q,N,U,ce,Se,ht,Bt;if(m=b,Q=oS(),Q!==t){for(N=[],U=b,ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();if(ce!==t)if(r.charCodeAt(b)===43?(Se=Gs,b++):(Se=t,I===0&&be(Yl)),Se===t&&(r.charCodeAt(b)===45?(Se=wA,b++):(Se=t,I===0&&be(Gl))),Se!==t){for(ht=[],Bt=He();Bt!==t;)ht.push(Bt),Bt=He();ht!==t?(Bt=oS(),Bt!==t?(Me=U,ce=yg(Q,Se,Bt),U=ce):(b=U,U=t)):(b=U,U=t)}else b=U,U=t;else b=U,U=t;for(;U!==t;){for(N.push(U),U=b,ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();if(ce!==t)if(r.charCodeAt(b)===43?(Se=Gs,b++):(Se=t,I===0&&be(Yl)),Se===t&&(r.charCodeAt(b)===45?(Se=wA,b++):(Se=t,I===0&&be(Gl))),Se!==t){for(ht=[],Bt=He();Bt!==t;)ht.push(Bt),Bt=He();ht!==t?(Bt=oS(),Bt!==t?(Me=U,ce=yg(Q,Se,Bt),U=ce):(b=U,U=t)):(b=U,U=t)}else b=U,U=t;else b=U,U=t}N!==t?(Me=m,Q=js(Q,N),m=Q):(b=m,m=t)}else b=m,m=t;return m}function eK(){var m,Q,N,U,ce,Se;if(m=b,r.substr(b,3)===bA?(Q=bA,b+=3):(Q=t,I===0&&be(R)),Q!==t){for(N=[],U=He();U!==t;)N.push(U),U=He();if(N!==t)if(U=$1(),U!==t){for(ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();ce!==t?(r.substr(b,2)===q?(Se=q,b+=2):(Se=t,I===0&&be(Ce)),Se!==t?(Me=m,Q=Ke(U),m=Q):(b=m,m=t)):(b=m,m=t)}else b=m,m=t;else b=m,m=t}else b=m,m=t;return m}function tK(){var m,Q,N,U;return m=b,r.substr(b,2)===Re?(Q=Re,b+=2):(Q=t,I===0&&be(ze)),Q!==t?(N=Mr(),N!==t?(r.charCodeAt(b)===41?(U=O,b++):(U=t,I===0&&be(F)),U!==t?(Me=m,Q=dt(N),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t),m}function aS(){var m,Q,N,U,ce,Se;return m=b,r.substr(b,2)===Ft?(Q=Ft,b+=2):(Q=t,I===0&&be(Ln)),Q!==t?(N=ql(),N!==t?(r.substr(b,2)===JQ?(U=JQ,b+=2):(U=t,I===0&&be(k1)),U!==t?(ce=q1(),ce!==t?(r.charCodeAt(b)===125?(Se=Fe,b++):(Se=t,I===0&&be(Ne)),Se!==t?(Me=m,Q=R1(N,ce),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t)):(b=m,m=t)):(b=m,m=t),m===t&&(m=b,r.substr(b,2)===Ft?(Q=Ft,b+=2):(Q=t,I===0&&be(Ln)),Q!==t?(N=ql(),N!==t?(r.substr(b,3)===WQ?(U=WQ,b+=3):(U=t,I===0&&be(F1)),U!==t?(Me=m,Q=N1(N),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t),m===t&&(m=b,r.substr(b,2)===Ft?(Q=Ft,b+=2):(Q=t,I===0&&be(Ln)),Q!==t?(N=ql(),N!==t?(r.substr(b,2)===zQ?(U=zQ,b+=2):(U=t,I===0&&be(T1)),U!==t?(ce=q1(),ce!==t?(r.charCodeAt(b)===125?(Se=Fe,b++):(Se=t,I===0&&be(Ne)),Se!==t?(Me=m,Q=L1(N,ce),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t)):(b=m,m=t)):(b=m,m=t),m===t&&(m=b,r.substr(b,2)===Ft?(Q=Ft,b+=2):(Q=t,I===0&&be(Ln)),Q!==t?(N=ql(),N!==t?(r.substr(b,3)===VQ?(U=VQ,b+=3):(U=t,I===0&&be(O1)),U!==t?(Me=m,Q=M1(N),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t),m===t&&(m=b,r.substr(b,2)===Ft?(Q=Ft,b+=2):(Q=t,I===0&&be(Ln)),Q!==t?(N=ql(),N!==t?(r.charCodeAt(b)===125?(U=Fe,b++):(U=t,I===0&&be(Ne)),U!==t?(Me=m,Q=XQ(N),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t),m===t&&(m=b,r.charCodeAt(b)===36?(Q=K1,b++):(Q=t,I===0&&be(U1)),Q!==t?(N=ql(),N!==t?(Me=m,Q=XQ(N),m=Q):(b=m,m=t)):(b=m,m=t)))))),m}function xge(){var m,Q,N;return m=b,Q=Pge(),Q!==t?(Me=b,N=H1(Q),N?N=void 0:N=t,N!==t?(Me=m,Q=G1(Q),m=Q):(b=m,m=t)):(b=m,m=t),m}function Pge(){var m,Q,N,U,ce;if(m=b,Q=[],N=b,U=b,I++,ce=nK(),I--,ce===t?U=void 0:(b=U,U=t),U!==t?(r.length>b?(ce=r.charAt(b),b++):(ce=t,I===0&&be(bo)),ce!==t?(Me=N,U=ln(ce),N=U):(b=N,N=t)):(b=N,N=t),N!==t)for(;N!==t;)Q.push(N),N=b,U=b,I++,ce=nK(),I--,ce===t?U=void 0:(b=U,U=t),U!==t?(r.length>b?(ce=r.charAt(b),b++):(ce=t,I===0&&be(bo)),ce!==t?(Me=N,U=ln(ce),N=U):(b=N,N=t)):(b=N,N=t);else Q=t;return Q!==t&&(Me=m,Q=Fn(Q)),m=Q,m}function rK(){var m,Q,N;if(m=b,Q=[],ZQ.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(_Q)),N!==t)for(;N!==t;)Q.push(N),ZQ.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(_Q));else Q=t;return Q!==t&&(Me=m,Q=$Q()),m=Q,m}function ql(){var m,Q,N;if(m=b,Q=[],eS.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(tS)),N!==t)for(;N!==t;)Q.push(N),eS.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(tS));else Q=t;return Q!==t&&(Me=m,Q=$Q()),m=Q,m}function iK(){var m;return Y1.test(r.charAt(b))?(m=r.charAt(b),b++):(m=t,I===0&&be(wg)),m}function nK(){var m;return rS.test(r.charAt(b))?(m=r.charAt(b),b++):(m=t,I===0&&be(iS)),m}function He(){var m,Q;if(m=[],YE.test(r.charAt(b))?(Q=r.charAt(b),b++):(Q=t,I===0&&be(jE)),Q!==t)for(;Q!==t;)m.push(Q),YE.test(r.charAt(b))?(Q=r.charAt(b),b++):(Q=t,I===0&&be(jE));else m=t;return m}if(k=n(),k!==t&&b===r.length)return k;throw k!==t&&b{"use strict";function Dfe(r,e){function t(){this.constructor=r}t.prototype=e.prototype,r.prototype=new t}function $l(r,e,t,i){this.message=r,this.expected=e,this.found=t,this.location=i,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,$l)}Dfe($l,Error);$l.buildMessage=function(r,e){var t={literal:function(c){return'"'+n(c.text)+'"'},class:function(c){var u="",g;for(g=0;g0){for(g=1,f=1;gH&&(H=v,j=[]),j.push(ne))}function Ne(ne,Y){return new $l(ne,null,null,Y)}function oe(ne,Y,he){return new $l($l.buildMessage(ne,Y),ne,Y,he)}function le(){var ne,Y,he,ie;return ne=v,Y=Be(),Y!==t?(r.charCodeAt(v)===47?(he=s,v++):(he=t,$===0&&Fe(o)),he!==t?(ie=Be(),ie!==t?(D=ne,Y=a(Y,ie),ne=Y):(v=ne,ne=t)):(v=ne,ne=t)):(v=ne,ne=t),ne===t&&(ne=v,Y=Be(),Y!==t&&(D=ne,Y=l(Y)),ne=Y),ne}function Be(){var ne,Y,he,ie;return ne=v,Y=fe(),Y!==t?(r.charCodeAt(v)===64?(he=c,v++):(he=t,$===0&&Fe(u)),he!==t?(ie=qe(),ie!==t?(D=ne,Y=g(Y,ie),ne=Y):(v=ne,ne=t)):(v=ne,ne=t)):(v=ne,ne=t),ne===t&&(ne=v,Y=fe(),Y!==t&&(D=ne,Y=f(Y)),ne=Y),ne}function fe(){var ne,Y,he,ie,de;return ne=v,r.charCodeAt(v)===64?(Y=c,v++):(Y=t,$===0&&Fe(u)),Y!==t?(he=ae(),he!==t?(r.charCodeAt(v)===47?(ie=s,v++):(ie=t,$===0&&Fe(o)),ie!==t?(de=ae(),de!==t?(D=ne,Y=h(),ne=Y):(v=ne,ne=t)):(v=ne,ne=t)):(v=ne,ne=t)):(v=ne,ne=t),ne===t&&(ne=v,Y=ae(),Y!==t&&(D=ne,Y=h()),ne=Y),ne}function ae(){var ne,Y,he;if(ne=v,Y=[],p.test(r.charAt(v))?(he=r.charAt(v),v++):(he=t,$===0&&Fe(C)),he!==t)for(;he!==t;)Y.push(he),p.test(r.charAt(v))?(he=r.charAt(v),v++):(he=t,$===0&&Fe(C));else Y=t;return Y!==t&&(D=ne,Y=h()),ne=Y,ne}function qe(){var ne,Y,he;if(ne=v,Y=[],y.test(r.charAt(v))?(he=r.charAt(v),v++):(he=t,$===0&&Fe(B)),he!==t)for(;he!==t;)Y.push(he),y.test(r.charAt(v))?(he=r.charAt(v),v++):(he=t,$===0&&Fe(B));else Y=t;return Y!==t&&(D=ne,Y=h()),ne=Y,ne}if(V=n(),V!==t&&v===r.length)return V;throw V!==t&&v{"use strict";function mU(r){return typeof r>"u"||r===null}function Rfe(r){return typeof r=="object"&&r!==null}function Ffe(r){return Array.isArray(r)?r:mU(r)?[]:[r]}function Nfe(r,e){var t,i,n,s;if(e)for(s=Object.keys(e),t=0,i=s.length;t{"use strict";function Vp(r,e){Error.call(this),this.name="YAMLException",this.reason=r,this.mark=e,this.message=(this.reason||"(unknown reason)")+(this.mark?" "+this.mark.toString():""),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error().stack||""}Vp.prototype=Object.create(Error.prototype);Vp.prototype.constructor=Vp;Vp.prototype.toString=function(e){var t=this.name+": ";return t+=this.reason||"(unknown reason)",!e&&this.mark&&(t+=" "+this.mark.toString()),t};EU.exports=Vp});var wU=w((SZe,yU)=>{"use strict";var IU=tc();function HS(r,e,t,i,n){this.name=r,this.buffer=e,this.position=t,this.line=i,this.column=n}HS.prototype.getSnippet=function(e,t){var i,n,s,o,a;if(!this.buffer)return null;for(e=e||4,t=t||75,i="",n=this.position;n>0&&`\0\r \x85\u2028\u2029`.indexOf(this.buffer.charAt(n-1))===-1;)if(n-=1,this.position-n>t/2-1){i=" ... ",n+=5;break}for(s="",o=this.position;ot/2-1){s=" ... ",o-=5;break}return a=this.buffer.slice(n,o),IU.repeat(" ",e)+i+a+s+` `+IU.repeat(" ",e+this.position-n+i.length)+"^"};HS.prototype.toString=function(e){var t,i="";return this.name&&(i+='in "'+this.name+'" '),i+="at line "+(this.line+1)+", column "+(this.column+1),e||(t=this.getSnippet(),t&&(i+=`: `+t)),i};yU.exports=HS});var si=w((vZe,bU)=>{"use strict";var BU=Ng(),Ofe=["kind","resolve","construct","instanceOf","predicate","represent","defaultStyle","styleAliases"],Mfe=["scalar","sequence","mapping"];function Kfe(r){var e={};return r!==null&&Object.keys(r).forEach(function(t){r[t].forEach(function(i){e[String(i)]=t})}),e}function Ufe(r,e){if(e=e||{},Object.keys(e).forEach(function(t){if(Ofe.indexOf(t)===-1)throw new BU('Unknown option "'+t+'" is met in definition of "'+r+'" YAML type.')}),this.tag=r,this.kind=e.kind||null,this.resolve=e.resolve||function(){return!0},this.construct=e.construct||function(t){return t},this.instanceOf=e.instanceOf||null,this.predicate=e.predicate||null,this.represent=e.represent||null,this.defaultStyle=e.defaultStyle||null,this.styleAliases=Kfe(e.styleAliases||null),Mfe.indexOf(this.kind)===-1)throw new BU('Unknown kind "'+this.kind+'" is specified for "'+r+'" YAML type.')}bU.exports=Ufe});var rc=w((xZe,SU)=>{"use strict";var QU=tc(),dI=Ng(),Hfe=si();function GS(r,e,t){var i=[];return r.include.forEach(function(n){t=GS(n,e,t)}),r[e].forEach(function(n){t.forEach(function(s,o){s.tag===n.tag&&s.kind===n.kind&&i.push(o)}),t.push(n)}),t.filter(function(n,s){return i.indexOf(s)===-1})}function Gfe(){var r={scalar:{},sequence:{},mapping:{},fallback:{}},e,t;function i(n){r[n.kind][n.tag]=r.fallback[n.tag]=n}for(e=0,t=arguments.length;e{"use strict";var Yfe=si();vU.exports=new Yfe("tag:yaml.org,2002:str",{kind:"scalar",construct:function(r){return r!==null?r:""}})});var DU=w((DZe,PU)=>{"use strict";var jfe=si();PU.exports=new jfe("tag:yaml.org,2002:seq",{kind:"sequence",construct:function(r){return r!==null?r:[]}})});var RU=w((kZe,kU)=>{"use strict";var qfe=si();kU.exports=new qfe("tag:yaml.org,2002:map",{kind:"mapping",construct:function(r){return r!==null?r:{}}})});var CI=w((RZe,FU)=>{"use strict";var Jfe=rc();FU.exports=new Jfe({explicit:[xU(),DU(),RU()]})});var TU=w((FZe,NU)=>{"use strict";var Wfe=si();function zfe(r){if(r===null)return!0;var e=r.length;return e===1&&r==="~"||e===4&&(r==="null"||r==="Null"||r==="NULL")}function Vfe(){return null}function Xfe(r){return r===null}NU.exports=new Wfe("tag:yaml.org,2002:null",{kind:"scalar",resolve:zfe,construct:Vfe,predicate:Xfe,represent:{canonical:function(){return"~"},lowercase:function(){return"null"},uppercase:function(){return"NULL"},camelcase:function(){return"Null"}},defaultStyle:"lowercase"})});var OU=w((NZe,LU)=>{"use strict";var Zfe=si();function _fe(r){if(r===null)return!1;var e=r.length;return e===4&&(r==="true"||r==="True"||r==="TRUE")||e===5&&(r==="false"||r==="False"||r==="FALSE")}function $fe(r){return r==="true"||r==="True"||r==="TRUE"}function ehe(r){return Object.prototype.toString.call(r)==="[object Boolean]"}LU.exports=new Zfe("tag:yaml.org,2002:bool",{kind:"scalar",resolve:_fe,construct:$fe,predicate:ehe,represent:{lowercase:function(r){return r?"true":"false"},uppercase:function(r){return r?"TRUE":"FALSE"},camelcase:function(r){return r?"True":"False"}},defaultStyle:"lowercase"})});var KU=w((TZe,MU)=>{"use strict";var the=tc(),rhe=si();function ihe(r){return 48<=r&&r<=57||65<=r&&r<=70||97<=r&&r<=102}function nhe(r){return 48<=r&&r<=55}function she(r){return 48<=r&&r<=57}function ohe(r){if(r===null)return!1;var e=r.length,t=0,i=!1,n;if(!e)return!1;if(n=r[t],(n==="-"||n==="+")&&(n=r[++t]),n==="0"){if(t+1===e)return!0;if(n=r[++t],n==="b"){for(t++;t=0?"0b"+r.toString(2):"-0b"+r.toString(2).slice(1)},octal:function(r){return r>=0?"0"+r.toString(8):"-0"+r.toString(8).slice(1)},decimal:function(r){return r.toString(10)},hexadecimal:function(r){return r>=0?"0x"+r.toString(16).toUpperCase():"-0x"+r.toString(16).toUpperCase().slice(1)}},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}})});var GU=w((LZe,HU)=>{"use strict";var UU=tc(),lhe=si(),che=new RegExp("^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$");function uhe(r){return!(r===null||!che.test(r)||r[r.length-1]==="_")}function ghe(r){var e,t,i,n;return e=r.replace(/_/g,"").toLowerCase(),t=e[0]==="-"?-1:1,n=[],"+-".indexOf(e[0])>=0&&(e=e.slice(1)),e===".inf"?t===1?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:e===".nan"?NaN:e.indexOf(":")>=0?(e.split(":").forEach(function(s){n.unshift(parseFloat(s,10))}),e=0,i=1,n.forEach(function(s){e+=s*i,i*=60}),t*e):t*parseFloat(e,10)}var fhe=/^[-+]?[0-9]+e/;function hhe(r,e){var t;if(isNaN(r))switch(e){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===r)switch(e){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===r)switch(e){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(UU.isNegativeZero(r))return"-0.0";return t=r.toString(10),fhe.test(t)?t.replace("e",".e"):t}function phe(r){return Object.prototype.toString.call(r)==="[object Number]"&&(r%1!==0||UU.isNegativeZero(r))}HU.exports=new lhe("tag:yaml.org,2002:float",{kind:"scalar",resolve:uhe,construct:ghe,predicate:phe,represent:hhe,defaultStyle:"lowercase"})});var YS=w((OZe,YU)=>{"use strict";var dhe=rc();YU.exports=new dhe({include:[CI()],implicit:[TU(),OU(),KU(),GU()]})});var jS=w((MZe,jU)=>{"use strict";var Che=rc();jU.exports=new Che({include:[YS()]})});var zU=w((KZe,WU)=>{"use strict";var mhe=si(),qU=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"),JU=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$");function Ehe(r){return r===null?!1:qU.exec(r)!==null||JU.exec(r)!==null}function Ihe(r){var e,t,i,n,s,o,a,l=0,c=null,u,g,f;if(e=qU.exec(r),e===null&&(e=JU.exec(r)),e===null)throw new Error("Date resolve error");if(t=+e[1],i=+e[2]-1,n=+e[3],!e[4])return new Date(Date.UTC(t,i,n));if(s=+e[4],o=+e[5],a=+e[6],e[7]){for(l=e[7].slice(0,3);l.length<3;)l+="0";l=+l}return e[9]&&(u=+e[10],g=+(e[11]||0),c=(u*60+g)*6e4,e[9]==="-"&&(c=-c)),f=new Date(Date.UTC(t,i,n,s,o,a,l)),c&&f.setTime(f.getTime()-c),f}function yhe(r){return r.toISOString()}WU.exports=new mhe("tag:yaml.org,2002:timestamp",{kind:"scalar",resolve:Ehe,construct:Ihe,instanceOf:Date,represent:yhe})});var XU=w((UZe,VU)=>{"use strict";var whe=si();function Bhe(r){return r==="<<"||r===null}VU.exports=new whe("tag:yaml.org,2002:merge",{kind:"scalar",resolve:Bhe})});var $U=w((HZe,_U)=>{"use strict";var ic;try{ZU=J,ic=ZU("buffer").Buffer}catch{}var ZU,bhe=si(),qS=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/= \r`;function Qhe(r){if(r===null)return!1;var e,t,i=0,n=r.length,s=qS;for(t=0;t64)){if(e<0)return!1;i+=6}return i%8===0}function She(r){var e,t,i=r.replace(/[\r\n=]/g,""),n=i.length,s=qS,o=0,a=[];for(e=0;e>16&255),a.push(o>>8&255),a.push(o&255)),o=o<<6|s.indexOf(i.charAt(e));return t=n%4*6,t===0?(a.push(o>>16&255),a.push(o>>8&255),a.push(o&255)):t===18?(a.push(o>>10&255),a.push(o>>2&255)):t===12&&a.push(o>>4&255),ic?ic.from?ic.from(a):new ic(a):a}function vhe(r){var e="",t=0,i,n,s=r.length,o=qS;for(i=0;i>18&63],e+=o[t>>12&63],e+=o[t>>6&63],e+=o[t&63]),t=(t<<8)+r[i];return n=s%3,n===0?(e+=o[t>>18&63],e+=o[t>>12&63],e+=o[t>>6&63],e+=o[t&63]):n===2?(e+=o[t>>10&63],e+=o[t>>4&63],e+=o[t<<2&63],e+=o[64]):n===1&&(e+=o[t>>2&63],e+=o[t<<4&63],e+=o[64],e+=o[64]),e}function xhe(r){return ic&&ic.isBuffer(r)}_U.exports=new bhe("tag:yaml.org,2002:binary",{kind:"scalar",resolve:Qhe,construct:She,predicate:xhe,represent:vhe})});var t2=w((YZe,e2)=>{"use strict";var Phe=si(),Dhe=Object.prototype.hasOwnProperty,khe=Object.prototype.toString;function Rhe(r){if(r===null)return!0;var e=[],t,i,n,s,o,a=r;for(t=0,i=a.length;t{"use strict";var Nhe=si(),The=Object.prototype.toString;function Lhe(r){if(r===null)return!0;var e,t,i,n,s,o=r;for(s=new Array(o.length),e=0,t=o.length;e{"use strict";var Mhe=si(),Khe=Object.prototype.hasOwnProperty;function Uhe(r){if(r===null)return!0;var e,t=r;for(e in t)if(Khe.call(t,e)&&t[e]!==null)return!1;return!0}function Hhe(r){return r!==null?r:{}}n2.exports=new Mhe("tag:yaml.org,2002:set",{kind:"mapping",resolve:Uhe,construct:Hhe})});var Lg=w((JZe,o2)=>{"use strict";var Ghe=rc();o2.exports=new Ghe({include:[jS()],implicit:[zU(),XU()],explicit:[$U(),t2(),i2(),s2()]})});var A2=w((WZe,a2)=>{"use strict";var Yhe=si();function jhe(){return!0}function qhe(){}function Jhe(){return""}function Whe(r){return typeof r>"u"}a2.exports=new Yhe("tag:yaml.org,2002:js/undefined",{kind:"scalar",resolve:jhe,construct:qhe,predicate:Whe,represent:Jhe})});var c2=w((zZe,l2)=>{"use strict";var zhe=si();function Vhe(r){if(r===null||r.length===0)return!1;var e=r,t=/\/([gim]*)$/.exec(r),i="";return!(e[0]==="/"&&(t&&(i=t[1]),i.length>3||e[e.length-i.length-1]!=="/"))}function Xhe(r){var e=r,t=/\/([gim]*)$/.exec(r),i="";return e[0]==="/"&&(t&&(i=t[1]),e=e.slice(1,e.length-i.length-1)),new RegExp(e,i)}function Zhe(r){var e="/"+r.source+"/";return r.global&&(e+="g"),r.multiline&&(e+="m"),r.ignoreCase&&(e+="i"),e}function _he(r){return Object.prototype.toString.call(r)==="[object RegExp]"}l2.exports=new zhe("tag:yaml.org,2002:js/regexp",{kind:"scalar",resolve:Vhe,construct:Xhe,predicate:_he,represent:Zhe})});var f2=w((VZe,g2)=>{"use strict";var mI;try{u2=J,mI=u2("esprima")}catch{typeof window<"u"&&(mI=window.esprima)}var u2,$he=si();function epe(r){if(r===null)return!1;try{var e="("+r+")",t=mI.parse(e,{range:!0});return!(t.type!=="Program"||t.body.length!==1||t.body[0].type!=="ExpressionStatement"||t.body[0].expression.type!=="ArrowFunctionExpression"&&t.body[0].expression.type!=="FunctionExpression")}catch{return!1}}function tpe(r){var e="("+r+")",t=mI.parse(e,{range:!0}),i=[],n;if(t.type!=="Program"||t.body.length!==1||t.body[0].type!=="ExpressionStatement"||t.body[0].expression.type!=="ArrowFunctionExpression"&&t.body[0].expression.type!=="FunctionExpression")throw new Error("Failed to resolve function");return t.body[0].expression.params.forEach(function(s){i.push(s.name)}),n=t.body[0].expression.body.range,t.body[0].expression.body.type==="BlockStatement"?new Function(i,e.slice(n[0]+1,n[1]-1)):new Function(i,"return "+e.slice(n[0],n[1]))}function rpe(r){return r.toString()}function ipe(r){return Object.prototype.toString.call(r)==="[object Function]"}g2.exports=new $he("tag:yaml.org,2002:js/function",{kind:"scalar",resolve:epe,construct:tpe,predicate:ipe,represent:rpe})});var Xp=w((ZZe,p2)=>{"use strict";var h2=rc();p2.exports=h2.DEFAULT=new h2({include:[Lg()],explicit:[A2(),c2(),f2()]})});var N2=w((_Ze,Zp)=>{"use strict";var Ba=tc(),w2=Ng(),npe=wU(),B2=Lg(),spe=Xp(),kA=Object.prototype.hasOwnProperty,EI=1,b2=2,Q2=3,II=4,JS=1,ope=2,d2=3,ape=/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,Ape=/[\x85\u2028\u2029]/,lpe=/[,\[\]\{\}]/,S2=/^(?:!|!!|![a-z\-]+!)$/i,v2=/^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i;function C2(r){return Object.prototype.toString.call(r)}function xo(r){return r===10||r===13}function sc(r){return r===9||r===32}function fn(r){return r===9||r===32||r===10||r===13}function Og(r){return r===44||r===91||r===93||r===123||r===125}function cpe(r){var e;return 48<=r&&r<=57?r-48:(e=r|32,97<=e&&e<=102?e-97+10:-1)}function upe(r){return r===120?2:r===117?4:r===85?8:0}function gpe(r){return 48<=r&&r<=57?r-48:-1}function m2(r){return r===48?"\0":r===97?"\x07":r===98?"\b":r===116||r===9?" ":r===110?` `:r===118?"\v":r===102?"\f":r===114?"\r":r===101?"\x1B":r===32?" ":r===34?'"':r===47?"/":r===92?"\\":r===78?"\x85":r===95?"\xA0":r===76?"\u2028":r===80?"\u2029":""}function fpe(r){return r<=65535?String.fromCharCode(r):String.fromCharCode((r-65536>>10)+55296,(r-65536&1023)+56320)}var x2=new Array(256),P2=new Array(256);for(nc=0;nc<256;nc++)x2[nc]=m2(nc)?1:0,P2[nc]=m2(nc);var nc;function hpe(r,e){this.input=r,this.filename=e.filename||null,this.schema=e.schema||spe,this.onWarning=e.onWarning||null,this.legacy=e.legacy||!1,this.json=e.json||!1,this.listener=e.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=r.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.documents=[]}function D2(r,e){return new w2(e,new npe(r.filename,r.input,r.position,r.line,r.position-r.lineStart))}function ft(r,e){throw D2(r,e)}function yI(r,e){r.onWarning&&r.onWarning.call(null,D2(r,e))}var E2={YAML:function(e,t,i){var n,s,o;e.version!==null&&ft(e,"duplication of %YAML directive"),i.length!==1&&ft(e,"YAML directive accepts exactly one argument"),n=/^([0-9]+)\.([0-9]+)$/.exec(i[0]),n===null&&ft(e,"ill-formed argument of the YAML directive"),s=parseInt(n[1],10),o=parseInt(n[2],10),s!==1&&ft(e,"unacceptable YAML version of the document"),e.version=i[0],e.checkLineBreaks=o<2,o!==1&&o!==2&&yI(e,"unsupported YAML version of the document")},TAG:function(e,t,i){var n,s;i.length!==2&&ft(e,"TAG directive accepts exactly two arguments"),n=i[0],s=i[1],S2.test(n)||ft(e,"ill-formed tag handle (first argument) of the TAG directive"),kA.call(e.tagMap,n)&&ft(e,'there is a previously declared suffix for "'+n+'" tag handle'),v2.test(s)||ft(e,"ill-formed tag prefix (second argument) of the TAG directive"),e.tagMap[n]=s}};function DA(r,e,t,i){var n,s,o,a;if(e1&&(r.result+=Ba.repeat(` `,e-1))}function ppe(r,e,t){var i,n,s,o,a,l,c,u,g=r.kind,f=r.result,h;if(h=r.input.charCodeAt(r.position),fn(h)||Og(h)||h===35||h===38||h===42||h===33||h===124||h===62||h===39||h===34||h===37||h===64||h===96||(h===63||h===45)&&(n=r.input.charCodeAt(r.position+1),fn(n)||t&&Og(n)))return!1;for(r.kind="scalar",r.result="",s=o=r.position,a=!1;h!==0;){if(h===58){if(n=r.input.charCodeAt(r.position+1),fn(n)||t&&Og(n))break}else if(h===35){if(i=r.input.charCodeAt(r.position-1),fn(i))break}else{if(r.position===r.lineStart&&wI(r)||t&&Og(h))break;if(xo(h))if(l=r.line,c=r.lineStart,u=r.lineIndent,zr(r,!1,-1),r.lineIndent>=e){a=!0,h=r.input.charCodeAt(r.position);continue}else{r.position=o,r.line=l,r.lineStart=c,r.lineIndent=u;break}}a&&(DA(r,s,o,!1),zS(r,r.line-l),s=o=r.position,a=!1),sc(h)||(o=r.position+1),h=r.input.charCodeAt(++r.position)}return DA(r,s,o,!1),r.result?!0:(r.kind=g,r.result=f,!1)}function dpe(r,e){var t,i,n;if(t=r.input.charCodeAt(r.position),t!==39)return!1;for(r.kind="scalar",r.result="",r.position++,i=n=r.position;(t=r.input.charCodeAt(r.position))!==0;)if(t===39)if(DA(r,i,r.position,!0),t=r.input.charCodeAt(++r.position),t===39)i=r.position,r.position++,n=r.position;else return!0;else xo(t)?(DA(r,i,n,!0),zS(r,zr(r,!1,e)),i=n=r.position):r.position===r.lineStart&&wI(r)?ft(r,"unexpected end of the document within a single quoted scalar"):(r.position++,n=r.position);ft(r,"unexpected end of the stream within a single quoted scalar")}function Cpe(r,e){var t,i,n,s,o,a;if(a=r.input.charCodeAt(r.position),a!==34)return!1;for(r.kind="scalar",r.result="",r.position++,t=i=r.position;(a=r.input.charCodeAt(r.position))!==0;){if(a===34)return DA(r,t,r.position,!0),r.position++,!0;if(a===92){if(DA(r,t,r.position,!0),a=r.input.charCodeAt(++r.position),xo(a))zr(r,!1,e);else if(a<256&&x2[a])r.result+=P2[a],r.position++;else if((o=upe(a))>0){for(n=o,s=0;n>0;n--)a=r.input.charCodeAt(++r.position),(o=cpe(a))>=0?s=(s<<4)+o:ft(r,"expected hexadecimal character");r.result+=fpe(s),r.position++}else ft(r,"unknown escape sequence");t=i=r.position}else xo(a)?(DA(r,t,i,!0),zS(r,zr(r,!1,e)),t=i=r.position):r.position===r.lineStart&&wI(r)?ft(r,"unexpected end of the document within a double quoted scalar"):(r.position++,i=r.position)}ft(r,"unexpected end of the stream within a double quoted scalar")}function mpe(r,e){var t=!0,i,n=r.tag,s,o=r.anchor,a,l,c,u,g,f={},h,p,C,y;if(y=r.input.charCodeAt(r.position),y===91)l=93,g=!1,s=[];else if(y===123)l=125,g=!0,s={};else return!1;for(r.anchor!==null&&(r.anchorMap[r.anchor]=s),y=r.input.charCodeAt(++r.position);y!==0;){if(zr(r,!0,e),y=r.input.charCodeAt(r.position),y===l)return r.position++,r.tag=n,r.anchor=o,r.kind=g?"mapping":"sequence",r.result=s,!0;t||ft(r,"missed comma between flow collection entries"),p=h=C=null,c=u=!1,y===63&&(a=r.input.charCodeAt(r.position+1),fn(a)&&(c=u=!0,r.position++,zr(r,!0,e))),i=r.line,Kg(r,e,EI,!1,!0),p=r.tag,h=r.result,zr(r,!0,e),y=r.input.charCodeAt(r.position),(u||r.line===i)&&y===58&&(c=!0,y=r.input.charCodeAt(++r.position),zr(r,!0,e),Kg(r,e,EI,!1,!0),C=r.result),g?Mg(r,s,f,p,h,C):c?s.push(Mg(r,null,f,p,h,C)):s.push(h),zr(r,!0,e),y=r.input.charCodeAt(r.position),y===44?(t=!0,y=r.input.charCodeAt(++r.position)):t=!1}ft(r,"unexpected end of the stream within a flow collection")}function Epe(r,e){var t,i,n=JS,s=!1,o=!1,a=e,l=0,c=!1,u,g;if(g=r.input.charCodeAt(r.position),g===124)i=!1;else if(g===62)i=!0;else return!1;for(r.kind="scalar",r.result="";g!==0;)if(g=r.input.charCodeAt(++r.position),g===43||g===45)JS===n?n=g===43?d2:ope:ft(r,"repeat of a chomping mode identifier");else if((u=gpe(g))>=0)u===0?ft(r,"bad explicit indentation width of a block scalar; it cannot be less than one"):o?ft(r,"repeat of an indentation width identifier"):(a=e+u-1,o=!0);else break;if(sc(g)){do g=r.input.charCodeAt(++r.position);while(sc(g));if(g===35)do g=r.input.charCodeAt(++r.position);while(!xo(g)&&g!==0)}for(;g!==0;){for(WS(r),r.lineIndent=0,g=r.input.charCodeAt(r.position);(!o||r.lineIndenta&&(a=r.lineIndent),xo(g)){l++;continue}if(r.lineIndente)&&l!==0)ft(r,"bad indentation of a sequence entry");else if(r.lineIndente)&&(Kg(r,e,II,!0,n)&&(p?f=r.result:h=r.result),p||(Mg(r,c,u,g,f,h,s,o),g=f=h=null),zr(r,!0,-1),y=r.input.charCodeAt(r.position)),r.lineIndent>e&&y!==0)ft(r,"bad indentation of a mapping entry");else if(r.lineIndente?l=1:r.lineIndent===e?l=0:r.lineIndente?l=1:r.lineIndent===e?l=0:r.lineIndent tag; it should be "scalar", not "'+r.kind+'"'),g=0,f=r.implicitTypes.length;g tag; it should be "'+h.kind+'", not "'+r.kind+'"'),h.resolve(r.result)?(r.result=h.construct(r.result),r.anchor!==null&&(r.anchorMap[r.anchor]=r.result)):ft(r,"cannot resolve a node with !<"+r.tag+"> explicit tag")):ft(r,"unknown tag !<"+r.tag+">");return r.listener!==null&&r.listener("close",r),r.tag!==null||r.anchor!==null||u}function bpe(r){var e=r.position,t,i,n,s=!1,o;for(r.version=null,r.checkLineBreaks=r.legacy,r.tagMap={},r.anchorMap={};(o=r.input.charCodeAt(r.position))!==0&&(zr(r,!0,-1),o=r.input.charCodeAt(r.position),!(r.lineIndent>0||o!==37));){for(s=!0,o=r.input.charCodeAt(++r.position),t=r.position;o!==0&&!fn(o);)o=r.input.charCodeAt(++r.position);for(i=r.input.slice(t,r.position),n=[],i.length<1&&ft(r,"directive name must not be less than one character in length");o!==0;){for(;sc(o);)o=r.input.charCodeAt(++r.position);if(o===35){do o=r.input.charCodeAt(++r.position);while(o!==0&&!xo(o));break}if(xo(o))break;for(t=r.position;o!==0&&!fn(o);)o=r.input.charCodeAt(++r.position);n.push(r.input.slice(t,r.position))}o!==0&&WS(r),kA.call(E2,i)?E2[i](r,i,n):yI(r,'unknown document directive "'+i+'"')}if(zr(r,!0,-1),r.lineIndent===0&&r.input.charCodeAt(r.position)===45&&r.input.charCodeAt(r.position+1)===45&&r.input.charCodeAt(r.position+2)===45?(r.position+=3,zr(r,!0,-1)):s&&ft(r,"directives end mark is expected"),Kg(r,r.lineIndent-1,II,!1,!0),zr(r,!0,-1),r.checkLineBreaks&&Ape.test(r.input.slice(e,r.position))&&yI(r,"non-ASCII line breaks are interpreted as content"),r.documents.push(r.result),r.position===r.lineStart&&wI(r)){r.input.charCodeAt(r.position)===46&&(r.position+=3,zr(r,!0,-1));return}if(r.position"u"&&(t=e,e=null);var i=k2(r,t);if(typeof e!="function")return i;for(var n=0,s=i.length;n"u"&&(t=e,e=null),R2(r,e,Ba.extend({schema:B2},t))}function Spe(r,e){return F2(r,Ba.extend({schema:B2},e))}Zp.exports.loadAll=R2;Zp.exports.load=F2;Zp.exports.safeLoadAll=Qpe;Zp.exports.safeLoad=Spe});var iH=w(($Ze,_S)=>{"use strict";var $p=tc(),ed=Ng(),vpe=Xp(),xpe=Lg(),G2=Object.prototype.toString,Y2=Object.prototype.hasOwnProperty,Ppe=9,_p=10,Dpe=13,kpe=32,Rpe=33,Fpe=34,j2=35,Npe=37,Tpe=38,Lpe=39,Ope=42,q2=44,Mpe=45,J2=58,Kpe=61,Upe=62,Hpe=63,Gpe=64,W2=91,z2=93,Ype=96,V2=123,jpe=124,X2=125,Ni={};Ni[0]="\\0";Ni[7]="\\a";Ni[8]="\\b";Ni[9]="\\t";Ni[10]="\\n";Ni[11]="\\v";Ni[12]="\\f";Ni[13]="\\r";Ni[27]="\\e";Ni[34]='\\"';Ni[92]="\\\\";Ni[133]="\\N";Ni[160]="\\_";Ni[8232]="\\L";Ni[8233]="\\P";var qpe=["y","Y","yes","Yes","YES","on","On","ON","n","N","no","No","NO","off","Off","OFF"];function Jpe(r,e){var t,i,n,s,o,a,l;if(e===null)return{};for(t={},i=Object.keys(e),n=0,s=i.length;n0?r.charCodeAt(s-1):null,f=f&&O2(o,a)}else{for(s=0;si&&r[g+1]!==" ",g=s);else if(!Ug(o))return BI;a=s>0?r.charCodeAt(s-1):null,f=f&&O2(o,a)}c=c||u&&s-g-1>i&&r[g+1]!==" "}return!l&&!c?f&&!n(r)?_2:$2:t>9&&Z2(r)?BI:c?tH:eH}function _pe(r,e,t,i){r.dump=function(){if(e.length===0)return"''";if(!r.noCompatMode&&qpe.indexOf(e)!==-1)return"'"+e+"'";var n=r.indent*Math.max(1,t),s=r.lineWidth===-1?-1:Math.max(Math.min(r.lineWidth,40),r.lineWidth-n),o=i||r.flowLevel>-1&&t>=r.flowLevel;function a(l){return zpe(r,l)}switch(Zpe(e,o,r.indent,s,a)){case _2:return e;case $2:return"'"+e.replace(/'/g,"''")+"'";case eH:return"|"+M2(e,r.indent)+K2(L2(e,n));case tH:return">"+M2(e,r.indent)+K2(L2($pe(e,s),n));case BI:return'"'+ede(e,s)+'"';default:throw new ed("impossible error: invalid scalar style")}}()}function M2(r,e){var t=Z2(r)?String(e):"",i=r[r.length-1]===` `,n=i&&(r[r.length-2]===` `||r===` `),s=n?"+":i?"":"-";return t+s+` `}function K2(r){return r[r.length-1]===` `?r.slice(0,-1):r}function $pe(r,e){for(var t=/(\n+)([^\n]*)/g,i=function(){var c=r.indexOf(` `);return c=c!==-1?c:r.length,t.lastIndex=c,U2(r.slice(0,c),e)}(),n=r[0]===` `||r[0]===" ",s,o;o=t.exec(r);){var a=o[1],l=o[2];s=l[0]===" ",i+=a+(!n&&!s&&l!==""?` `:"")+U2(l,e),n=s}return i}function U2(r,e){if(r===""||r[0]===" ")return r;for(var t=/ [^ ]/g,i,n=0,s,o=0,a=0,l="";i=t.exec(r);)a=i.index,a-n>e&&(s=o>n?o:a,l+=` `+r.slice(n,s),n=s+1),o=a;return l+=` `,r.length-n>e&&o>n?l+=r.slice(n,o)+` `+r.slice(o+1):l+=r.slice(n),l.slice(1)}function ede(r){for(var e="",t,i,n,s=0;s=55296&&t<=56319&&(i=r.charCodeAt(s+1),i>=56320&&i<=57343)){e+=T2((t-55296)*1024+i-56320+65536),s++;continue}n=Ni[t],e+=!n&&Ug(t)?r[s]:n||T2(t)}return e}function tde(r,e,t){var i="",n=r.tag,s,o;for(s=0,o=t.length;s1024&&(u+="? "),u+=r.dump+(r.condenseFlow?'"':"")+":"+(r.condenseFlow?"":" "),oc(r,e,c,!1,!1)&&(u+=r.dump,i+=u));r.tag=n,r.dump="{"+i+"}"}function nde(r,e,t,i){var n="",s=r.tag,o=Object.keys(t),a,l,c,u,g,f;if(r.sortKeys===!0)o.sort();else if(typeof r.sortKeys=="function")o.sort(r.sortKeys);else if(r.sortKeys)throw new ed("sortKeys must be a boolean or a function");for(a=0,l=o.length;a1024,g&&(r.dump&&_p===r.dump.charCodeAt(0)?f+="?":f+="? "),f+=r.dump,g&&(f+=VS(r,e)),oc(r,e+1,u,!0,g)&&(r.dump&&_p===r.dump.charCodeAt(0)?f+=":":f+=": ",f+=r.dump,n+=f));r.tag=s,r.dump=n||"{}"}function H2(r,e,t){var i,n,s,o,a,l;for(n=t?r.explicitTypes:r.implicitTypes,s=0,o=n.length;s tag resolver accepts not "'+l+'" style');r.dump=i}return!0}return!1}function oc(r,e,t,i,n,s){r.tag=null,r.dump=t,H2(r,t,!1)||H2(r,t,!0);var o=G2.call(r.dump);i&&(i=r.flowLevel<0||r.flowLevel>e);var a=o==="[object Object]"||o==="[object Array]",l,c;if(a&&(l=r.duplicates.indexOf(t),c=l!==-1),(r.tag!==null&&r.tag!=="?"||c||r.indent!==2&&e>0)&&(n=!1),c&&r.usedDuplicates[l])r.dump="*ref_"+l;else{if(a&&c&&!r.usedDuplicates[l]&&(r.usedDuplicates[l]=!0),o==="[object Object]")i&&Object.keys(r.dump).length!==0?(nde(r,e,r.dump,n),c&&(r.dump="&ref_"+l+r.dump)):(ide(r,e,r.dump),c&&(r.dump="&ref_"+l+" "+r.dump));else if(o==="[object Array]"){var u=r.noArrayIndent&&e>0?e-1:e;i&&r.dump.length!==0?(rde(r,u,r.dump,n),c&&(r.dump="&ref_"+l+r.dump)):(tde(r,u,r.dump),c&&(r.dump="&ref_"+l+" "+r.dump))}else if(o==="[object String]")r.tag!=="?"&&_pe(r,r.dump,e,s);else{if(r.skipInvalid)return!1;throw new ed("unacceptable kind of an object to dump "+o)}r.tag!==null&&r.tag!=="?"&&(r.dump="!<"+r.tag+"> "+r.dump)}return!0}function sde(r,e){var t=[],i=[],n,s;for(XS(r,t,i),n=0,s=i.length;n{"use strict";var bI=N2(),nH=iH();function QI(r){return function(){throw new Error("Function "+r+" is deprecated and cannot be used.")}}Fr.exports.Type=si();Fr.exports.Schema=rc();Fr.exports.FAILSAFE_SCHEMA=CI();Fr.exports.JSON_SCHEMA=YS();Fr.exports.CORE_SCHEMA=jS();Fr.exports.DEFAULT_SAFE_SCHEMA=Lg();Fr.exports.DEFAULT_FULL_SCHEMA=Xp();Fr.exports.load=bI.load;Fr.exports.loadAll=bI.loadAll;Fr.exports.safeLoad=bI.safeLoad;Fr.exports.safeLoadAll=bI.safeLoadAll;Fr.exports.dump=nH.dump;Fr.exports.safeDump=nH.safeDump;Fr.exports.YAMLException=Ng();Fr.exports.MINIMAL_SCHEMA=CI();Fr.exports.SAFE_SCHEMA=Lg();Fr.exports.DEFAULT_SCHEMA=Xp();Fr.exports.scan=QI("scan");Fr.exports.parse=QI("parse");Fr.exports.compose=QI("compose");Fr.exports.addConstructor=QI("addConstructor")});var aH=w((t_e,oH)=>{"use strict";var ade=sH();oH.exports=ade});var lH=w((r_e,AH)=>{"use strict";function Ade(r,e){function t(){this.constructor=r}t.prototype=e.prototype,r.prototype=new t}function ac(r,e,t,i){this.message=r,this.expected=e,this.found=t,this.location=i,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,ac)}Ade(ac,Error);ac.buildMessage=function(r,e){var t={literal:function(c){return'"'+n(c.text)+'"'},class:function(c){var u="",g;for(g=0;g0){for(g=1,f=1;g({[Ke]:Ce})))},H=function(R){return R},j=function(R){return R},$=Us("correct indentation"),V=" ",W=ar(" ",!1),_=function(R){return R.length===bA*yg},A=function(R){return R.length===(bA+1)*yg},Ae=function(){return bA++,!0},ge=function(){return bA--,!0},re=function(){return pg()},O=Us("pseudostring"),F=/^[^\r\n\t ?:,\][{}#&*!|>'"%@`\-]/,ue=Tn(["\r",` `," "," ","?",":",",","]","[","{","}","#","&","*","!","|",">","'",'"',"%","@","`","-"],!0,!1),pe=/^[^\r\n\t ,\][{}:#"']/,ke=Tn(["\r",` `," "," ",",","]","[","{","}",":","#",'"',"'"],!0,!1),Fe=function(){return pg().replace(/^ *| *$/g,"")},Ne="--",oe=ar("--",!1),le=/^[a-zA-Z\/0-9]/,Be=Tn([["a","z"],["A","Z"],"/",["0","9"]],!1,!1),fe=/^[^\r\n\t :,]/,ae=Tn(["\r",` `," "," ",":",","],!0,!1),qe="null",ne=ar("null",!1),Y=function(){return null},he="true",ie=ar("true",!1),de=function(){return!0},_e="false",Pt=ar("false",!1),It=function(){return!1},Or=Us("string"),ii='"',gi=ar('"',!1),hr=function(){return""},fi=function(R){return R},ni=function(R){return R.join("")},Ks=/^[^"\\\0-\x1F\x7F]/,pr=Tn(['"',"\\",["\0",""],"\x7F"],!0,!1),Ii='\\"',rs=ar('\\"',!1),fa=function(){return'"'},dA="\\\\",cg=ar("\\\\",!1),is=function(){return"\\"},CA="\\/",ha=ar("\\/",!1),wp=function(){return"/"},mA="\\b",EA=ar("\\b",!1),wr=function(){return"\b"},Tl="\\f",ug=ar("\\f",!1),yo=function(){return"\f"},gg="\\n",Bp=ar("\\n",!1),bp=function(){return` `},vr="\\r",se=ar("\\r",!1),wo=function(){return"\r"},Fn="\\t",fg=ar("\\t",!1),bt=function(){return" "},Ll="\\u",Nn=ar("\\u",!1),ns=function(R,q,Ce,Ke){return String.fromCharCode(parseInt(`0x${R}${q}${Ce}${Ke}`))},ss=/^[0-9a-fA-F]/,gt=Tn([["0","9"],["a","f"],["A","F"]],!1,!1),Bo=Us("blank space"),At=/^[ \t]/,ln=Tn([" "," "],!1,!1),S=Us("white space"),Lt=/^[ \t\n\r]/,hg=Tn([" "," ",` `,"\r"],!1,!1),Ol=`\r `,Qp=ar(`\r `,!1),Sp=` `,vp=ar(` `,!1),xp="\r",Pp=ar("\r",!1),G=0,yt=0,IA=[{line:1,column:1}],zi=0,Ml=[],Xe=0,pa;if("startRule"in e){if(!(e.startRule in i))throw new Error(`Can't start parsing from rule "`+e.startRule+'".');n=i[e.startRule]}function pg(){return r.substring(yt,G)}function OE(){return cn(yt,G)}function Dp(R,q){throw q=q!==void 0?q:cn(yt,G),Ul([Us(R)],r.substring(yt,G),q)}function ME(R,q){throw q=q!==void 0?q:cn(yt,G),dg(R,q)}function ar(R,q){return{type:"literal",text:R,ignoreCase:q}}function Tn(R,q,Ce){return{type:"class",parts:R,inverted:q,ignoreCase:Ce}}function Kl(){return{type:"any"}}function kp(){return{type:"end"}}function Us(R){return{type:"other",description:R}}function da(R){var q=IA[R],Ce;if(q)return q;for(Ce=R-1;!IA[Ce];)Ce--;for(q=IA[Ce],q={line:q.line,column:q.column};Cezi&&(zi=G,Ml=[]),Ml.push(R))}function dg(R,q){return new ac(R,null,null,q)}function Ul(R,q,Ce){return new ac(ac.buildMessage(R,q),R,q,Ce)}function Hs(){var R;return R=Cg(),R}function Hl(){var R,q,Ce;for(R=G,q=[],Ce=yA();Ce!==t;)q.push(Ce),Ce=yA();return q!==t&&(yt=R,q=s(q)),R=q,R}function yA(){var R,q,Ce,Ke,Re;return R=G,q=ma(),q!==t?(r.charCodeAt(G)===45?(Ce=o,G++):(Ce=t,Xe===0&&Le(a)),Ce!==t?(Ke=Rr(),Ke!==t?(Re=Ca(),Re!==t?(yt=R,q=l(Re),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t),R}function Cg(){var R,q,Ce;for(R=G,q=[],Ce=mg();Ce!==t;)q.push(Ce),Ce=mg();return q!==t&&(yt=R,q=c(q)),R=q,R}function mg(){var R,q,Ce,Ke,Re,ze,dt,Ft,Ln;if(R=G,q=Rr(),q===t&&(q=null),q!==t){if(Ce=G,r.charCodeAt(G)===35?(Ke=u,G++):(Ke=t,Xe===0&&Le(g)),Ke!==t){if(Re=[],ze=G,dt=G,Xe++,Ft=js(),Xe--,Ft===t?dt=void 0:(G=dt,dt=t),dt!==t?(r.length>G?(Ft=r.charAt(G),G++):(Ft=t,Xe===0&&Le(f)),Ft!==t?(dt=[dt,Ft],ze=dt):(G=ze,ze=t)):(G=ze,ze=t),ze!==t)for(;ze!==t;)Re.push(ze),ze=G,dt=G,Xe++,Ft=js(),Xe--,Ft===t?dt=void 0:(G=dt,dt=t),dt!==t?(r.length>G?(Ft=r.charAt(G),G++):(Ft=t,Xe===0&&Le(f)),Ft!==t?(dt=[dt,Ft],ze=dt):(G=ze,ze=t)):(G=ze,ze=t);else Re=t;Re!==t?(Ke=[Ke,Re],Ce=Ke):(G=Ce,Ce=t)}else G=Ce,Ce=t;if(Ce===t&&(Ce=null),Ce!==t){if(Ke=[],Re=Ys(),Re!==t)for(;Re!==t;)Ke.push(Re),Re=Ys();else Ke=t;Ke!==t?(yt=R,q=h(),R=q):(G=R,R=t)}else G=R,R=t}else G=R,R=t;if(R===t&&(R=G,q=ma(),q!==t?(Ce=Gl(),Ce!==t?(Ke=Rr(),Ke===t&&(Ke=null),Ke!==t?(r.charCodeAt(G)===58?(Re=p,G++):(Re=t,Xe===0&&Le(C)),Re!==t?(ze=Rr(),ze===t&&(ze=null),ze!==t?(dt=Ca(),dt!==t?(yt=R,q=y(Ce,dt),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t),R===t&&(R=G,q=ma(),q!==t?(Ce=Gs(),Ce!==t?(Ke=Rr(),Ke===t&&(Ke=null),Ke!==t?(r.charCodeAt(G)===58?(Re=p,G++):(Re=t,Xe===0&&Le(C)),Re!==t?(ze=Rr(),ze===t&&(ze=null),ze!==t?(dt=Ca(),dt!==t?(yt=R,q=y(Ce,dt),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t),R===t))){if(R=G,q=ma(),q!==t)if(Ce=Gs(),Ce!==t)if(Ke=Rr(),Ke!==t)if(Re=KE(),Re!==t){if(ze=[],dt=Ys(),dt!==t)for(;dt!==t;)ze.push(dt),dt=Ys();else ze=t;ze!==t?(yt=R,q=y(Ce,Re),R=q):(G=R,R=t)}else G=R,R=t;else G=R,R=t;else G=R,R=t;else G=R,R=t;if(R===t)if(R=G,q=ma(),q!==t)if(Ce=Gs(),Ce!==t){if(Ke=[],Re=G,ze=Rr(),ze===t&&(ze=null),ze!==t?(r.charCodeAt(G)===44?(dt=B,G++):(dt=t,Xe===0&&Le(v)),dt!==t?(Ft=Rr(),Ft===t&&(Ft=null),Ft!==t?(Ln=Gs(),Ln!==t?(yt=Re,ze=D(Ce,Ln),Re=ze):(G=Re,Re=t)):(G=Re,Re=t)):(G=Re,Re=t)):(G=Re,Re=t),Re!==t)for(;Re!==t;)Ke.push(Re),Re=G,ze=Rr(),ze===t&&(ze=null),ze!==t?(r.charCodeAt(G)===44?(dt=B,G++):(dt=t,Xe===0&&Le(v)),dt!==t?(Ft=Rr(),Ft===t&&(Ft=null),Ft!==t?(Ln=Gs(),Ln!==t?(yt=Re,ze=D(Ce,Ln),Re=ze):(G=Re,Re=t)):(G=Re,Re=t)):(G=Re,Re=t)):(G=Re,Re=t);else Ke=t;Ke!==t?(Re=Rr(),Re===t&&(Re=null),Re!==t?(r.charCodeAt(G)===58?(ze=p,G++):(ze=t,Xe===0&&Le(C)),ze!==t?(dt=Rr(),dt===t&&(dt=null),dt!==t?(Ft=Ca(),Ft!==t?(yt=R,q=T(Ce,Ke,Ft),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)}else G=R,R=t;else G=R,R=t}return R}function Ca(){var R,q,Ce,Ke,Re,ze,dt;if(R=G,q=G,Xe++,Ce=G,Ke=js(),Ke!==t?(Re=rt(),Re!==t?(r.charCodeAt(G)===45?(ze=o,G++):(ze=t,Xe===0&&Le(a)),ze!==t?(dt=Rr(),dt!==t?(Ke=[Ke,Re,ze,dt],Ce=Ke):(G=Ce,Ce=t)):(G=Ce,Ce=t)):(G=Ce,Ce=t)):(G=Ce,Ce=t),Xe--,Ce!==t?(G=q,q=void 0):q=t,q!==t?(Ce=Ys(),Ce!==t?(Ke=bo(),Ke!==t?(Re=Hl(),Re!==t?(ze=wA(),ze!==t?(yt=R,q=H(Re),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t),R===t&&(R=G,q=js(),q!==t?(Ce=bo(),Ce!==t?(Ke=Cg(),Ke!==t?(Re=wA(),Re!==t?(yt=R,q=H(Ke),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t),R===t))if(R=G,q=Yl(),q!==t){if(Ce=[],Ke=Ys(),Ke!==t)for(;Ke!==t;)Ce.push(Ke),Ke=Ys();else Ce=t;Ce!==t?(yt=R,q=j(q),R=q):(G=R,R=t)}else G=R,R=t;return R}function ma(){var R,q,Ce;for(Xe++,R=G,q=[],r.charCodeAt(G)===32?(Ce=V,G++):(Ce=t,Xe===0&&Le(W));Ce!==t;)q.push(Ce),r.charCodeAt(G)===32?(Ce=V,G++):(Ce=t,Xe===0&&Le(W));return q!==t?(yt=G,Ce=_(q),Ce?Ce=void 0:Ce=t,Ce!==t?(q=[q,Ce],R=q):(G=R,R=t)):(G=R,R=t),Xe--,R===t&&(q=t,Xe===0&&Le($)),R}function rt(){var R,q,Ce;for(R=G,q=[],r.charCodeAt(G)===32?(Ce=V,G++):(Ce=t,Xe===0&&Le(W));Ce!==t;)q.push(Ce),r.charCodeAt(G)===32?(Ce=V,G++):(Ce=t,Xe===0&&Le(W));return q!==t?(yt=G,Ce=A(q),Ce?Ce=void 0:Ce=t,Ce!==t?(q=[q,Ce],R=q):(G=R,R=t)):(G=R,R=t),R}function bo(){var R;return yt=G,R=Ae(),R?R=void 0:R=t,R}function wA(){var R;return yt=G,R=ge(),R?R=void 0:R=t,R}function Gl(){var R;return R=jl(),R===t&&(R=Rp()),R}function Gs(){var R,q,Ce;if(R=jl(),R===t){if(R=G,q=[],Ce=Eg(),Ce!==t)for(;Ce!==t;)q.push(Ce),Ce=Eg();else q=t;q!==t&&(yt=R,q=re()),R=q}return R}function Yl(){var R;return R=Fp(),R===t&&(R=UE(),R===t&&(R=jl(),R===t&&(R=Rp()))),R}function KE(){var R;return R=Fp(),R===t&&(R=jl(),R===t&&(R=Eg())),R}function Rp(){var R,q,Ce,Ke,Re,ze;if(Xe++,R=G,F.test(r.charAt(G))?(q=r.charAt(G),G++):(q=t,Xe===0&&Le(ue)),q!==t){for(Ce=[],Ke=G,Re=Rr(),Re===t&&(Re=null),Re!==t?(pe.test(r.charAt(G))?(ze=r.charAt(G),G++):(ze=t,Xe===0&&Le(ke)),ze!==t?(Re=[Re,ze],Ke=Re):(G=Ke,Ke=t)):(G=Ke,Ke=t);Ke!==t;)Ce.push(Ke),Ke=G,Re=Rr(),Re===t&&(Re=null),Re!==t?(pe.test(r.charAt(G))?(ze=r.charAt(G),G++):(ze=t,Xe===0&&Le(ke)),ze!==t?(Re=[Re,ze],Ke=Re):(G=Ke,Ke=t)):(G=Ke,Ke=t);Ce!==t?(yt=R,q=Fe(),R=q):(G=R,R=t)}else G=R,R=t;return Xe--,R===t&&(q=t,Xe===0&&Le(O)),R}function Eg(){var R,q,Ce,Ke,Re;if(R=G,r.substr(G,2)===Ne?(q=Ne,G+=2):(q=t,Xe===0&&Le(oe)),q===t&&(q=null),q!==t)if(le.test(r.charAt(G))?(Ce=r.charAt(G),G++):(Ce=t,Xe===0&&Le(Be)),Ce!==t){for(Ke=[],fe.test(r.charAt(G))?(Re=r.charAt(G),G++):(Re=t,Xe===0&&Le(ae));Re!==t;)Ke.push(Re),fe.test(r.charAt(G))?(Re=r.charAt(G),G++):(Re=t,Xe===0&&Le(ae));Ke!==t?(yt=R,q=Fe(),R=q):(G=R,R=t)}else G=R,R=t;else G=R,R=t;return R}function Fp(){var R,q;return R=G,r.substr(G,4)===qe?(q=qe,G+=4):(q=t,Xe===0&&Le(ne)),q!==t&&(yt=R,q=Y()),R=q,R}function UE(){var R,q;return R=G,r.substr(G,4)===he?(q=he,G+=4):(q=t,Xe===0&&Le(ie)),q!==t&&(yt=R,q=de()),R=q,R===t&&(R=G,r.substr(G,5)===_e?(q=_e,G+=5):(q=t,Xe===0&&Le(Pt)),q!==t&&(yt=R,q=It()),R=q),R}function jl(){var R,q,Ce,Ke;return Xe++,R=G,r.charCodeAt(G)===34?(q=ii,G++):(q=t,Xe===0&&Le(gi)),q!==t?(r.charCodeAt(G)===34?(Ce=ii,G++):(Ce=t,Xe===0&&Le(gi)),Ce!==t?(yt=R,q=hr(),R=q):(G=R,R=t)):(G=R,R=t),R===t&&(R=G,r.charCodeAt(G)===34?(q=ii,G++):(q=t,Xe===0&&Le(gi)),q!==t?(Ce=HE(),Ce!==t?(r.charCodeAt(G)===34?(Ke=ii,G++):(Ke=t,Xe===0&&Le(gi)),Ke!==t?(yt=R,q=fi(Ce),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)),Xe--,R===t&&(q=t,Xe===0&&Le(Or)),R}function HE(){var R,q,Ce;if(R=G,q=[],Ce=Ig(),Ce!==t)for(;Ce!==t;)q.push(Ce),Ce=Ig();else q=t;return q!==t&&(yt=R,q=ni(q)),R=q,R}function Ig(){var R,q,Ce,Ke,Re,ze;return Ks.test(r.charAt(G))?(R=r.charAt(G),G++):(R=t,Xe===0&&Le(pr)),R===t&&(R=G,r.substr(G,2)===Ii?(q=Ii,G+=2):(q=t,Xe===0&&Le(rs)),q!==t&&(yt=R,q=fa()),R=q,R===t&&(R=G,r.substr(G,2)===dA?(q=dA,G+=2):(q=t,Xe===0&&Le(cg)),q!==t&&(yt=R,q=is()),R=q,R===t&&(R=G,r.substr(G,2)===CA?(q=CA,G+=2):(q=t,Xe===0&&Le(ha)),q!==t&&(yt=R,q=wp()),R=q,R===t&&(R=G,r.substr(G,2)===mA?(q=mA,G+=2):(q=t,Xe===0&&Le(EA)),q!==t&&(yt=R,q=wr()),R=q,R===t&&(R=G,r.substr(G,2)===Tl?(q=Tl,G+=2):(q=t,Xe===0&&Le(ug)),q!==t&&(yt=R,q=yo()),R=q,R===t&&(R=G,r.substr(G,2)===gg?(q=gg,G+=2):(q=t,Xe===0&&Le(Bp)),q!==t&&(yt=R,q=bp()),R=q,R===t&&(R=G,r.substr(G,2)===vr?(q=vr,G+=2):(q=t,Xe===0&&Le(se)),q!==t&&(yt=R,q=wo()),R=q,R===t&&(R=G,r.substr(G,2)===Fn?(q=Fn,G+=2):(q=t,Xe===0&&Le(fg)),q!==t&&(yt=R,q=bt()),R=q,R===t&&(R=G,r.substr(G,2)===Ll?(q=Ll,G+=2):(q=t,Xe===0&&Le(Nn)),q!==t?(Ce=BA(),Ce!==t?(Ke=BA(),Ke!==t?(Re=BA(),Re!==t?(ze=BA(),ze!==t?(yt=R,q=ns(Ce,Ke,Re,ze),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)))))))))),R}function BA(){var R;return ss.test(r.charAt(G))?(R=r.charAt(G),G++):(R=t,Xe===0&&Le(gt)),R}function Rr(){var R,q;if(Xe++,R=[],At.test(r.charAt(G))?(q=r.charAt(G),G++):(q=t,Xe===0&&Le(ln)),q!==t)for(;q!==t;)R.push(q),At.test(r.charAt(G))?(q=r.charAt(G),G++):(q=t,Xe===0&&Le(ln));else R=t;return Xe--,R===t&&(q=t,Xe===0&&Le(Bo)),R}function GE(){var R,q;if(Xe++,R=[],Lt.test(r.charAt(G))?(q=r.charAt(G),G++):(q=t,Xe===0&&Le(hg)),q!==t)for(;q!==t;)R.push(q),Lt.test(r.charAt(G))?(q=r.charAt(G),G++):(q=t,Xe===0&&Le(hg));else R=t;return Xe--,R===t&&(q=t,Xe===0&&Le(S)),R}function Ys(){var R,q,Ce,Ke,Re,ze;if(R=G,q=js(),q!==t){for(Ce=[],Ke=G,Re=Rr(),Re===t&&(Re=null),Re!==t?(ze=js(),ze!==t?(Re=[Re,ze],Ke=Re):(G=Ke,Ke=t)):(G=Ke,Ke=t);Ke!==t;)Ce.push(Ke),Ke=G,Re=Rr(),Re===t&&(Re=null),Re!==t?(ze=js(),ze!==t?(Re=[Re,ze],Ke=Re):(G=Ke,Ke=t)):(G=Ke,Ke=t);Ce!==t?(q=[q,Ce],R=q):(G=R,R=t)}else G=R,R=t;return R}function js(){var R;return r.substr(G,2)===Ol?(R=Ol,G+=2):(R=t,Xe===0&&Le(Qp)),R===t&&(r.charCodeAt(G)===10?(R=Sp,G++):(R=t,Xe===0&&Le(vp)),R===t&&(r.charCodeAt(G)===13?(R=xp,G++):(R=t,Xe===0&&Le(Pp)))),R}let yg=2,bA=0;if(pa=n(),pa!==t&&G===r.length)return pa;throw pa!==t&&G{"use strict";var hde=r=>{let e=!1,t=!1,i=!1;for(let n=0;n{if(!(typeof r=="string"||Array.isArray(r)))throw new TypeError("Expected the input to be `string | string[]`");e=Object.assign({pascalCase:!1},e);let t=n=>e.pascalCase?n.charAt(0).toUpperCase()+n.slice(1):n;return Array.isArray(r)?r=r.map(n=>n.trim()).filter(n=>n.length).join("-"):r=r.trim(),r.length===0?"":r.length===1?e.pascalCase?r.toUpperCase():r.toLowerCase():(r!==r.toLowerCase()&&(r=hde(r)),r=r.replace(/^[_.\- ]+/,"").toLowerCase().replace(/[_.\- ]+(\w|$)/g,(n,s)=>s.toUpperCase()).replace(/\d+(\w|$)/g,n=>n.toUpperCase()),t(r))};ev.exports=hH;ev.exports.default=hH});var dH=w((A_e,pde)=>{pde.exports=[{name:"AppVeyor",constant:"APPVEYOR",env:"APPVEYOR",pr:"APPVEYOR_PULL_REQUEST_NUMBER"},{name:"Azure Pipelines",constant:"AZURE_PIPELINES",env:"SYSTEM_TEAMFOUNDATIONCOLLECTIONURI",pr:"SYSTEM_PULLREQUEST_PULLREQUESTID"},{name:"Appcircle",constant:"APPCIRCLE",env:"AC_APPCIRCLE"},{name:"Bamboo",constant:"BAMBOO",env:"bamboo_planKey"},{name:"Bitbucket Pipelines",constant:"BITBUCKET",env:"BITBUCKET_COMMIT",pr:"BITBUCKET_PR_ID"},{name:"Bitrise",constant:"BITRISE",env:"BITRISE_IO",pr:"BITRISE_PULL_REQUEST"},{name:"Buddy",constant:"BUDDY",env:"BUDDY_WORKSPACE_ID",pr:"BUDDY_EXECUTION_PULL_REQUEST_ID"},{name:"Buildkite",constant:"BUILDKITE",env:"BUILDKITE",pr:{env:"BUILDKITE_PULL_REQUEST",ne:"false"}},{name:"CircleCI",constant:"CIRCLE",env:"CIRCLECI",pr:"CIRCLE_PULL_REQUEST"},{name:"Cirrus CI",constant:"CIRRUS",env:"CIRRUS_CI",pr:"CIRRUS_PR"},{name:"AWS CodeBuild",constant:"CODEBUILD",env:"CODEBUILD_BUILD_ARN"},{name:"Codefresh",constant:"CODEFRESH",env:"CF_BUILD_ID",pr:{any:["CF_PULL_REQUEST_NUMBER","CF_PULL_REQUEST_ID"]}},{name:"Codeship",constant:"CODESHIP",env:{CI_NAME:"codeship"}},{name:"Drone",constant:"DRONE",env:"DRONE",pr:{DRONE_BUILD_EVENT:"pull_request"}},{name:"dsari",constant:"DSARI",env:"DSARI"},{name:"GitHub Actions",constant:"GITHUB_ACTIONS",env:"GITHUB_ACTIONS",pr:{GITHUB_EVENT_NAME:"pull_request"}},{name:"GitLab CI",constant:"GITLAB",env:"GITLAB_CI",pr:"CI_MERGE_REQUEST_ID"},{name:"GoCD",constant:"GOCD",env:"GO_PIPELINE_LABEL"},{name:"LayerCI",constant:"LAYERCI",env:"LAYERCI",pr:"LAYERCI_PULL_REQUEST"},{name:"Hudson",constant:"HUDSON",env:"HUDSON_URL"},{name:"Jenkins",constant:"JENKINS",env:["JENKINS_URL","BUILD_ID"],pr:{any:["ghprbPullId","CHANGE_ID"]}},{name:"Magnum CI",constant:"MAGNUM",env:"MAGNUM"},{name:"Netlify CI",constant:"NETLIFY",env:"NETLIFY",pr:{env:"PULL_REQUEST",ne:"false"}},{name:"Nevercode",constant:"NEVERCODE",env:"NEVERCODE",pr:{env:"NEVERCODE_PULL_REQUEST",ne:"false"}},{name:"Render",constant:"RENDER",env:"RENDER",pr:{IS_PULL_REQUEST:"true"}},{name:"Sail CI",constant:"SAIL",env:"SAILCI",pr:"SAIL_PULL_REQUEST_NUMBER"},{name:"Semaphore",constant:"SEMAPHORE",env:"SEMAPHORE",pr:"PULL_REQUEST_NUMBER"},{name:"Screwdriver",constant:"SCREWDRIVER",env:"SCREWDRIVER",pr:{env:"SD_PULL_REQUEST",ne:"false"}},{name:"Shippable",constant:"SHIPPABLE",env:"SHIPPABLE",pr:{IS_PULL_REQUEST:"true"}},{name:"Solano CI",constant:"SOLANO",env:"TDDIUM",pr:"TDDIUM_PR_ID"},{name:"Strider CD",constant:"STRIDER",env:"STRIDER"},{name:"TaskCluster",constant:"TASKCLUSTER",env:["TASK_ID","RUN_ID"]},{name:"TeamCity",constant:"TEAMCITY",env:"TEAMCITY_VERSION"},{name:"Travis CI",constant:"TRAVIS",env:"TRAVIS",pr:{env:"TRAVIS_PULL_REQUEST",ne:"false"}},{name:"Vercel",constant:"VERCEL",env:"NOW_BUILDER"},{name:"Visual Studio App Center",constant:"APPCENTER",env:"APPCENTER_BUILD_ID"}]});var Ac=w(Un=>{"use strict";var mH=dH(),Po=process.env;Object.defineProperty(Un,"_vendors",{value:mH.map(function(r){return r.constant})});Un.name=null;Un.isPR=null;mH.forEach(function(r){let t=(Array.isArray(r.env)?r.env:[r.env]).every(function(i){return CH(i)});if(Un[r.constant]=t,t)switch(Un.name=r.name,typeof r.pr){case"string":Un.isPR=!!Po[r.pr];break;case"object":"env"in r.pr?Un.isPR=r.pr.env in Po&&Po[r.pr.env]!==r.pr.ne:"any"in r.pr?Un.isPR=r.pr.any.some(function(i){return!!Po[i]}):Un.isPR=CH(r.pr);break;default:Un.isPR=null}});Un.isCI=!!(Po.CI||Po.CONTINUOUS_INTEGRATION||Po.BUILD_NUMBER||Po.RUN_ID||Un.name);function CH(r){return typeof r=="string"?!!Po[r]:Object.keys(r).every(function(e){return Po[e]===r[e]})}});var hn={};ut(hn,{KeyRelationship:()=>lc,applyCascade:()=>od,base64RegExp:()=>BH,colorStringAlphaRegExp:()=>wH,colorStringRegExp:()=>yH,computeKey:()=>RA,getPrintable:()=>Vr,hasExactLength:()=>xH,hasForbiddenKeys:()=>Wde,hasKeyRelationship:()=>av,hasMaxLength:()=>Dde,hasMinLength:()=>Pde,hasMutuallyExclusiveKeys:()=>zde,hasRequiredKeys:()=>Jde,hasUniqueItems:()=>kde,isArray:()=>yde,isAtLeast:()=>Nde,isAtMost:()=>Tde,isBase64:()=>jde,isBoolean:()=>mde,isDate:()=>Ide,isDict:()=>Bde,isEnum:()=>Zi,isHexColor:()=>Yde,isISO8601:()=>Gde,isInExclusiveRange:()=>Ode,isInInclusiveRange:()=>Lde,isInstanceOf:()=>Qde,isInteger:()=>Mde,isJSON:()=>qde,isLiteral:()=>dde,isLowerCase:()=>Kde,isNegative:()=>Rde,isNullable:()=>xde,isNumber:()=>Ede,isObject:()=>bde,isOneOf:()=>Sde,isOptional:()=>vde,isPositive:()=>Fde,isString:()=>sd,isTuple:()=>wde,isUUID4:()=>Hde,isUnknown:()=>vH,isUpperCase:()=>Ude,iso8601RegExp:()=>ov,makeCoercionFn:()=>cc,makeSetter:()=>SH,makeTrait:()=>QH,makeValidator:()=>Qt,matchesRegExp:()=>ad,plural:()=>kI,pushError:()=>pt,simpleKeyRegExp:()=>IH,uuid4RegExp:()=>bH});function Qt({test:r}){return QH(r)()}function Vr(r){return r===null?"null":r===void 0?"undefined":r===""?"an empty string":JSON.stringify(r)}function RA(r,e){var t,i,n;return typeof e=="number"?`${(t=r==null?void 0:r.p)!==null&&t!==void 0?t:"."}[${e}]`:IH.test(e)?`${(i=r==null?void 0:r.p)!==null&&i!==void 0?i:""}.${e}`:`${(n=r==null?void 0:r.p)!==null&&n!==void 0?n:"."}[${JSON.stringify(e)}]`}function cc(r,e){return t=>{let i=r[e];return r[e]=t,cc(r,e).bind(null,i)}}function SH(r,e){return t=>{r[e]=t}}function kI(r,e,t){return r===1?e:t}function pt({errors:r,p:e}={},t){return r==null||r.push(`${e!=null?e:"."}: ${t}`),!1}function dde(r){return Qt({test:(e,t)=>e!==r?pt(t,`Expected a literal (got ${Vr(r)})`):!0})}function Zi(r){let e=Array.isArray(r)?r:Object.values(r),t=new Set(e);return Qt({test:(i,n)=>t.has(i)?!0:pt(n,`Expected a valid enumeration value (got ${Vr(i)})`)})}var IH,yH,wH,BH,bH,ov,QH,vH,sd,Cde,mde,Ede,Ide,yde,wde,Bde,bde,Qde,Sde,od,vde,xde,Pde,Dde,xH,kde,Rde,Fde,Nde,Tde,Lde,Ode,Mde,ad,Kde,Ude,Hde,Gde,Yde,jde,qde,Jde,Wde,zde,lc,Vde,av,ls=Tge(()=>{IH=/^[a-zA-Z_][a-zA-Z0-9_]*$/,yH=/^#[0-9a-f]{6}$/i,wH=/^#[0-9a-f]{6}([0-9a-f]{2})?$/i,BH=/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/,bH=/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}$/i,ov=/^(?:[1-9]\d{3}(-?)(?:(?:0[1-9]|1[0-2])\1(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])\1(?:29|30)|(?:0[13578]|1[02])(?:\1)31|00[1-9]|0[1-9]\d|[12]\d{2}|3(?:[0-5]\d|6[0-5]))|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)(?:(-?)02(?:\2)29|-?366))T(?:[01]\d|2[0-3])(:?)[0-5]\d(?:\3[0-5]\d)?(?:Z|[+-][01]\d(?:\3[0-5]\d)?)$/,QH=r=>()=>r;vH=()=>Qt({test:(r,e)=>!0});sd=()=>Qt({test:(r,e)=>typeof r!="string"?pt(e,`Expected a string (got ${Vr(r)})`):!0});Cde=new Map([["true",!0],["True",!0],["1",!0],[1,!0],["false",!1],["False",!1],["0",!1],[0,!1]]),mde=()=>Qt({test:(r,e)=>{var t;if(typeof r!="boolean"){if(typeof(e==null?void 0:e.coercions)<"u"){if(typeof(e==null?void 0:e.coercion)>"u")return pt(e,"Unbound coercion result");let i=Cde.get(r);if(typeof i<"u")return e.coercions.push([(t=e.p)!==null&&t!==void 0?t:".",e.coercion.bind(null,i)]),!0}return pt(e,`Expected a boolean (got ${Vr(r)})`)}return!0}}),Ede=()=>Qt({test:(r,e)=>{var t;if(typeof r!="number"){if(typeof(e==null?void 0:e.coercions)<"u"){if(typeof(e==null?void 0:e.coercion)>"u")return pt(e,"Unbound coercion result");let i;if(typeof r=="string"){let n;try{n=JSON.parse(r)}catch{}if(typeof n=="number")if(JSON.stringify(n)===r)i=n;else return pt(e,`Received a number that can't be safely represented by the runtime (${r})`)}if(typeof i<"u")return e.coercions.push([(t=e.p)!==null&&t!==void 0?t:".",e.coercion.bind(null,i)]),!0}return pt(e,`Expected a number (got ${Vr(r)})`)}return!0}}),Ide=()=>Qt({test:(r,e)=>{var t;if(!(r instanceof Date)){if(typeof(e==null?void 0:e.coercions)<"u"){if(typeof(e==null?void 0:e.coercion)>"u")return pt(e,"Unbound coercion result");let i;if(typeof r=="string"&&ov.test(r))i=new Date(r);else{let n;if(typeof r=="string"){let s;try{s=JSON.parse(r)}catch{}typeof s=="number"&&(n=s)}else typeof r=="number"&&(n=r);if(typeof n<"u")if(Number.isSafeInteger(n)||!Number.isSafeInteger(n*1e3))i=new Date(n*1e3);else return pt(e,`Received a timestamp that can't be safely represented by the runtime (${r})`)}if(typeof i<"u")return e.coercions.push([(t=e.p)!==null&&t!==void 0?t:".",e.coercion.bind(null,i)]),!0}return pt(e,`Expected a date (got ${Vr(r)})`)}return!0}}),yde=(r,{delimiter:e}={})=>Qt({test:(t,i)=>{var n;if(typeof t=="string"&&typeof e<"u"&&typeof(i==null?void 0:i.coercions)<"u"){if(typeof(i==null?void 0:i.coercion)>"u")return pt(i,"Unbound coercion result");t=t.split(e),i.coercions.push([(n=i.p)!==null&&n!==void 0?n:".",i.coercion.bind(null,t)])}if(!Array.isArray(t))return pt(i,`Expected an array (got ${Vr(t)})`);let s=!0;for(let o=0,a=t.length;o{let t=xH(r.length);return Qt({test:(i,n)=>{var s;if(typeof i=="string"&&typeof e<"u"&&typeof(n==null?void 0:n.coercions)<"u"){if(typeof(n==null?void 0:n.coercion)>"u")return pt(n,"Unbound coercion result");i=i.split(e),n.coercions.push([(s=n.p)!==null&&s!==void 0?s:".",n.coercion.bind(null,i)])}if(!Array.isArray(i))return pt(n,`Expected a tuple (got ${Vr(i)})`);let o=t(i,Object.assign({},n));for(let a=0,l=i.length;aQt({test:(t,i)=>{if(typeof t!="object"||t===null)return pt(i,`Expected an object (got ${Vr(t)})`);let n=Object.keys(t),s=!0;for(let o=0,a=n.length;o{let t=Object.keys(r);return Qt({test:(i,n)=>{if(typeof i!="object"||i===null)return pt(n,`Expected an object (got ${Vr(i)})`);let s=new Set([...t,...Object.keys(i)]),o={},a=!0;for(let l of s){if(l==="constructor"||l==="__proto__")a=pt(Object.assign(Object.assign({},n),{p:RA(n,l)}),"Unsafe property name");else{let c=Object.prototype.hasOwnProperty.call(r,l)?r[l]:void 0,u=Object.prototype.hasOwnProperty.call(i,l)?i[l]:void 0;typeof c<"u"?a=c(u,Object.assign(Object.assign({},n),{p:RA(n,l),coercion:cc(i,l)}))&&a:e===null?a=pt(Object.assign(Object.assign({},n),{p:RA(n,l)}),`Extraneous property (got ${Vr(u)})`):Object.defineProperty(o,l,{enumerable:!0,get:()=>u,set:SH(i,l)})}if(!a&&(n==null?void 0:n.errors)==null)break}return e!==null&&(a||(n==null?void 0:n.errors)!=null)&&(a=e(o,n)&&a),a}})},Qde=r=>Qt({test:(e,t)=>e instanceof r?!0:pt(t,`Expected an instance of ${r.name} (got ${Vr(e)})`)}),Sde=(r,{exclusive:e=!1}={})=>Qt({test:(t,i)=>{var n,s,o;let a=[],l=typeof(i==null?void 0:i.errors)<"u"?[]:void 0;for(let c=0,u=r.length;c1?pt(i,`Expected to match exactly a single predicate (matched ${a.join(", ")})`):(o=i==null?void 0:i.errors)===null||o===void 0||o.push(...l),!1}}),od=(r,e)=>Qt({test:(t,i)=>{var n,s;let o={value:t},a=typeof(i==null?void 0:i.coercions)<"u"?cc(o,"value"):void 0,l=typeof(i==null?void 0:i.coercions)<"u"?[]:void 0;if(!r(t,Object.assign(Object.assign({},i),{coercion:a,coercions:l})))return!1;let c=[];if(typeof l<"u")for(let[,u]of l)c.push(u());try{if(typeof(i==null?void 0:i.coercions)<"u"){if(o.value!==t){if(typeof(i==null?void 0:i.coercion)>"u")return pt(i,"Unbound coercion result");i.coercions.push([(n=i.p)!==null&&n!==void 0?n:".",i.coercion.bind(null,o.value)])}(s=i==null?void 0:i.coercions)===null||s===void 0||s.push(...l)}return e.every(u=>u(o.value,i))}finally{for(let u of c)u()}}}),vde=r=>Qt({test:(e,t)=>typeof e>"u"?!0:r(e,t)}),xde=r=>Qt({test:(e,t)=>e===null?!0:r(e,t)}),Pde=r=>Qt({test:(e,t)=>e.length>=r?!0:pt(t,`Expected to have a length of at least ${r} elements (got ${e.length})`)}),Dde=r=>Qt({test:(e,t)=>e.length<=r?!0:pt(t,`Expected to have a length of at most ${r} elements (got ${e.length})`)}),xH=r=>Qt({test:(e,t)=>e.length!==r?pt(t,`Expected to have a length of exactly ${r} elements (got ${e.length})`):!0}),kde=({map:r}={})=>Qt({test:(e,t)=>{let i=new Set,n=new Set;for(let s=0,o=e.length;sQt({test:(r,e)=>r<=0?!0:pt(e,`Expected to be negative (got ${r})`)}),Fde=()=>Qt({test:(r,e)=>r>=0?!0:pt(e,`Expected to be positive (got ${r})`)}),Nde=r=>Qt({test:(e,t)=>e>=r?!0:pt(t,`Expected to be at least ${r} (got ${e})`)}),Tde=r=>Qt({test:(e,t)=>e<=r?!0:pt(t,`Expected to be at most ${r} (got ${e})`)}),Lde=(r,e)=>Qt({test:(t,i)=>t>=r&&t<=e?!0:pt(i,`Expected to be in the [${r}; ${e}] range (got ${t})`)}),Ode=(r,e)=>Qt({test:(t,i)=>t>=r&&tQt({test:(e,t)=>e!==Math.round(e)?pt(t,`Expected to be an integer (got ${e})`):Number.isSafeInteger(e)?!0:pt(t,`Expected to be a safe integer (got ${e})`)}),ad=r=>Qt({test:(e,t)=>r.test(e)?!0:pt(t,`Expected to match the pattern ${r.toString()} (got ${Vr(e)})`)}),Kde=()=>Qt({test:(r,e)=>r!==r.toLowerCase()?pt(e,`Expected to be all-lowercase (got ${r})`):!0}),Ude=()=>Qt({test:(r,e)=>r!==r.toUpperCase()?pt(e,`Expected to be all-uppercase (got ${r})`):!0}),Hde=()=>Qt({test:(r,e)=>bH.test(r)?!0:pt(e,`Expected to be a valid UUID v4 (got ${Vr(r)})`)}),Gde=()=>Qt({test:(r,e)=>ov.test(r)?!1:pt(e,`Expected to be a valid ISO 8601 date string (got ${Vr(r)})`)}),Yde=({alpha:r=!1})=>Qt({test:(e,t)=>(r?yH.test(e):wH.test(e))?!0:pt(t,`Expected to be a valid hexadecimal color string (got ${Vr(e)})`)}),jde=()=>Qt({test:(r,e)=>BH.test(r)?!0:pt(e,`Expected to be a valid base 64 string (got ${Vr(r)})`)}),qde=(r=vH())=>Qt({test:(e,t)=>{let i;try{i=JSON.parse(e)}catch{return pt(t,`Expected to be a valid JSON string (got ${Vr(e)})`)}return r(i,t)}}),Jde=r=>{let e=new Set(r);return Qt({test:(t,i)=>{let n=new Set(Object.keys(t)),s=[];for(let o of e)n.has(o)||s.push(o);return s.length>0?pt(i,`Missing required ${kI(s.length,"property","properties")} ${s.map(o=>`"${o}"`).join(", ")}`):!0}})},Wde=r=>{let e=new Set(r);return Qt({test:(t,i)=>{let n=new Set(Object.keys(t)),s=[];for(let o of e)n.has(o)&&s.push(o);return s.length>0?pt(i,`Forbidden ${kI(s.length,"property","properties")} ${s.map(o=>`"${o}"`).join(", ")}`):!0}})},zde=r=>{let e=new Set(r);return Qt({test:(t,i)=>{let n=new Set(Object.keys(t)),s=[];for(let o of e)n.has(o)&&s.push(o);return s.length>1?pt(i,`Mutually exclusive properties ${s.map(o=>`"${o}"`).join(", ")}`):!0}})};(function(r){r.Forbids="Forbids",r.Requires="Requires"})(lc||(lc={}));Vde={[lc.Forbids]:{expect:!1,message:"forbids using"},[lc.Requires]:{expect:!0,message:"requires using"}},av=(r,e,t,{ignore:i=[]}={})=>{let n=new Set(i),s=new Set(t),o=Vde[e];return Qt({test:(a,l)=>{let c=new Set(Object.keys(a));if(!c.has(r)||n.has(a[r]))return!0;let u=[];for(let g of s)(c.has(g)&&!n.has(a[g]))!==o.expect&&u.push(g);return u.length>=1?pt(l,`Property "${r}" ${o.message} ${kI(u.length,"property","properties")} ${u.map(g=>`"${g}"`).join(", ")}`):!0}})}});var qH=w((A$e,jH)=>{"use strict";jH.exports=(r,...e)=>new Promise(t=>{t(r(...e))})});var Jg=w((l$e,pv)=>{"use strict";var gCe=qH(),JH=r=>{if(r<1)throw new TypeError("Expected `concurrency` to be a number from 1 and up");let e=[],t=0,i=()=>{t--,e.length>0&&e.shift()()},n=(a,l,...c)=>{t++;let u=gCe(a,...c);l(u),u.then(i,i)},s=(a,l,...c)=>{tnew Promise(c=>s(a,c,...l));return Object.defineProperties(o,{activeCount:{get:()=>t},pendingCount:{get:()=>e.length}}),o};pv.exports=JH;pv.exports.default=JH});var gd=w((u$e,WH)=>{var fCe="2.0.0",hCe=Number.MAX_SAFE_INTEGER||9007199254740991,pCe=16;WH.exports={SEMVER_SPEC_VERSION:fCe,MAX_LENGTH:256,MAX_SAFE_INTEGER:hCe,MAX_SAFE_COMPONENT_LENGTH:pCe}});var fd=w((g$e,zH)=>{var dCe=typeof process=="object"&&process.env&&process.env.NODE_DEBUG&&/\bsemver\b/i.test(process.env.NODE_DEBUG)?(...r)=>console.error("SEMVER",...r):()=>{};zH.exports=dCe});var uc=w((NA,VH)=>{var{MAX_SAFE_COMPONENT_LENGTH:dv}=gd(),CCe=fd();NA=VH.exports={};var mCe=NA.re=[],et=NA.src=[],tt=NA.t={},ECe=0,St=(r,e,t)=>{let i=ECe++;CCe(i,e),tt[r]=i,et[i]=e,mCe[i]=new RegExp(e,t?"g":void 0)};St("NUMERICIDENTIFIER","0|[1-9]\\d*");St("NUMERICIDENTIFIERLOOSE","[0-9]+");St("NONNUMERICIDENTIFIER","\\d*[a-zA-Z-][a-zA-Z0-9-]*");St("MAINVERSION",`(${et[tt.NUMERICIDENTIFIER]})\\.(${et[tt.NUMERICIDENTIFIER]})\\.(${et[tt.NUMERICIDENTIFIER]})`);St("MAINVERSIONLOOSE",`(${et[tt.NUMERICIDENTIFIERLOOSE]})\\.(${et[tt.NUMERICIDENTIFIERLOOSE]})\\.(${et[tt.NUMERICIDENTIFIERLOOSE]})`);St("PRERELEASEIDENTIFIER",`(?:${et[tt.NUMERICIDENTIFIER]}|${et[tt.NONNUMERICIDENTIFIER]})`);St("PRERELEASEIDENTIFIERLOOSE",`(?:${et[tt.NUMERICIDENTIFIERLOOSE]}|${et[tt.NONNUMERICIDENTIFIER]})`);St("PRERELEASE",`(?:-(${et[tt.PRERELEASEIDENTIFIER]}(?:\\.${et[tt.PRERELEASEIDENTIFIER]})*))`);St("PRERELEASELOOSE",`(?:-?(${et[tt.PRERELEASEIDENTIFIERLOOSE]}(?:\\.${et[tt.PRERELEASEIDENTIFIERLOOSE]})*))`);St("BUILDIDENTIFIER","[0-9A-Za-z-]+");St("BUILD",`(?:\\+(${et[tt.BUILDIDENTIFIER]}(?:\\.${et[tt.BUILDIDENTIFIER]})*))`);St("FULLPLAIN",`v?${et[tt.MAINVERSION]}${et[tt.PRERELEASE]}?${et[tt.BUILD]}?`);St("FULL",`^${et[tt.FULLPLAIN]}$`);St("LOOSEPLAIN",`[v=\\s]*${et[tt.MAINVERSIONLOOSE]}${et[tt.PRERELEASELOOSE]}?${et[tt.BUILD]}?`);St("LOOSE",`^${et[tt.LOOSEPLAIN]}$`);St("GTLT","((?:<|>)?=?)");St("XRANGEIDENTIFIERLOOSE",`${et[tt.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`);St("XRANGEIDENTIFIER",`${et[tt.NUMERICIDENTIFIER]}|x|X|\\*`);St("XRANGEPLAIN",`[v=\\s]*(${et[tt.XRANGEIDENTIFIER]})(?:\\.(${et[tt.XRANGEIDENTIFIER]})(?:\\.(${et[tt.XRANGEIDENTIFIER]})(?:${et[tt.PRERELEASE]})?${et[tt.BUILD]}?)?)?`);St("XRANGEPLAINLOOSE",`[v=\\s]*(${et[tt.XRANGEIDENTIFIERLOOSE]})(?:\\.(${et[tt.XRANGEIDENTIFIERLOOSE]})(?:\\.(${et[tt.XRANGEIDENTIFIERLOOSE]})(?:${et[tt.PRERELEASELOOSE]})?${et[tt.BUILD]}?)?)?`);St("XRANGE",`^${et[tt.GTLT]}\\s*${et[tt.XRANGEPLAIN]}$`);St("XRANGELOOSE",`^${et[tt.GTLT]}\\s*${et[tt.XRANGEPLAINLOOSE]}$`);St("COERCE",`(^|[^\\d])(\\d{1,${dv}})(?:\\.(\\d{1,${dv}}))?(?:\\.(\\d{1,${dv}}))?(?:$|[^\\d])`);St("COERCERTL",et[tt.COERCE],!0);St("LONETILDE","(?:~>?)");St("TILDETRIM",`(\\s*)${et[tt.LONETILDE]}\\s+`,!0);NA.tildeTrimReplace="$1~";St("TILDE",`^${et[tt.LONETILDE]}${et[tt.XRANGEPLAIN]}$`);St("TILDELOOSE",`^${et[tt.LONETILDE]}${et[tt.XRANGEPLAINLOOSE]}$`);St("LONECARET","(?:\\^)");St("CARETTRIM",`(\\s*)${et[tt.LONECARET]}\\s+`,!0);NA.caretTrimReplace="$1^";St("CARET",`^${et[tt.LONECARET]}${et[tt.XRANGEPLAIN]}$`);St("CARETLOOSE",`^${et[tt.LONECARET]}${et[tt.XRANGEPLAINLOOSE]}$`);St("COMPARATORLOOSE",`^${et[tt.GTLT]}\\s*(${et[tt.LOOSEPLAIN]})$|^$`);St("COMPARATOR",`^${et[tt.GTLT]}\\s*(${et[tt.FULLPLAIN]})$|^$`);St("COMPARATORTRIM",`(\\s*)${et[tt.GTLT]}\\s*(${et[tt.LOOSEPLAIN]}|${et[tt.XRANGEPLAIN]})`,!0);NA.comparatorTrimReplace="$1$2$3";St("HYPHENRANGE",`^\\s*(${et[tt.XRANGEPLAIN]})\\s+-\\s+(${et[tt.XRANGEPLAIN]})\\s*$`);St("HYPHENRANGELOOSE",`^\\s*(${et[tt.XRANGEPLAINLOOSE]})\\s+-\\s+(${et[tt.XRANGEPLAINLOOSE]})\\s*$`);St("STAR","(<|>)?=?\\s*\\*");St("GTE0","^\\s*>=\\s*0.0.0\\s*$");St("GTE0PRE","^\\s*>=\\s*0.0.0-0\\s*$")});var hd=w((f$e,XH)=>{var ICe=["includePrerelease","loose","rtl"],yCe=r=>r?typeof r!="object"?{loose:!0}:ICe.filter(e=>r[e]).reduce((e,t)=>(e[t]=!0,e),{}):{};XH.exports=yCe});var OI=w((h$e,$H)=>{var ZH=/^[0-9]+$/,_H=(r,e)=>{let t=ZH.test(r),i=ZH.test(e);return t&&i&&(r=+r,e=+e),r===e?0:t&&!i?-1:i&&!t?1:r_H(e,r);$H.exports={compareIdentifiers:_H,rcompareIdentifiers:wCe}});var Li=w((p$e,iG)=>{var MI=fd(),{MAX_LENGTH:eG,MAX_SAFE_INTEGER:KI}=gd(),{re:tG,t:rG}=uc(),BCe=hd(),{compareIdentifiers:pd}=OI(),Yn=class{constructor(e,t){if(t=BCe(t),e instanceof Yn){if(e.loose===!!t.loose&&e.includePrerelease===!!t.includePrerelease)return e;e=e.version}else if(typeof e!="string")throw new TypeError(`Invalid Version: ${e}`);if(e.length>eG)throw new TypeError(`version is longer than ${eG} characters`);MI("SemVer",e,t),this.options=t,this.loose=!!t.loose,this.includePrerelease=!!t.includePrerelease;let i=e.trim().match(t.loose?tG[rG.LOOSE]:tG[rG.FULL]);if(!i)throw new TypeError(`Invalid Version: ${e}`);if(this.raw=e,this.major=+i[1],this.minor=+i[2],this.patch=+i[3],this.major>KI||this.major<0)throw new TypeError("Invalid major version");if(this.minor>KI||this.minor<0)throw new TypeError("Invalid minor version");if(this.patch>KI||this.patch<0)throw new TypeError("Invalid patch version");i[4]?this.prerelease=i[4].split(".").map(n=>{if(/^[0-9]+$/.test(n)){let s=+n;if(s>=0&&s=0;)typeof this.prerelease[i]=="number"&&(this.prerelease[i]++,i=-2);i===-1&&this.prerelease.push(0)}t&&(this.prerelease[0]===t?isNaN(this.prerelease[1])&&(this.prerelease=[t,0]):this.prerelease=[t,0]);break;default:throw new Error(`invalid increment argument: ${e}`)}return this.format(),this.raw=this.version,this}};iG.exports=Yn});var gc=w((d$e,aG)=>{var{MAX_LENGTH:bCe}=gd(),{re:nG,t:sG}=uc(),oG=Li(),QCe=hd(),SCe=(r,e)=>{if(e=QCe(e),r instanceof oG)return r;if(typeof r!="string"||r.length>bCe||!(e.loose?nG[sG.LOOSE]:nG[sG.FULL]).test(r))return null;try{return new oG(r,e)}catch{return null}};aG.exports=SCe});var lG=w((C$e,AG)=>{var vCe=gc(),xCe=(r,e)=>{let t=vCe(r,e);return t?t.version:null};AG.exports=xCe});var uG=w((m$e,cG)=>{var PCe=gc(),DCe=(r,e)=>{let t=PCe(r.trim().replace(/^[=v]+/,""),e);return t?t.version:null};cG.exports=DCe});var fG=w((E$e,gG)=>{var kCe=Li(),RCe=(r,e,t,i)=>{typeof t=="string"&&(i=t,t=void 0);try{return new kCe(r,t).inc(e,i).version}catch{return null}};gG.exports=RCe});var cs=w((I$e,pG)=>{var hG=Li(),FCe=(r,e,t)=>new hG(r,t).compare(new hG(e,t));pG.exports=FCe});var UI=w((y$e,dG)=>{var NCe=cs(),TCe=(r,e,t)=>NCe(r,e,t)===0;dG.exports=TCe});var EG=w((w$e,mG)=>{var CG=gc(),LCe=UI(),OCe=(r,e)=>{if(LCe(r,e))return null;{let t=CG(r),i=CG(e),n=t.prerelease.length||i.prerelease.length,s=n?"pre":"",o=n?"prerelease":"";for(let a in t)if((a==="major"||a==="minor"||a==="patch")&&t[a]!==i[a])return s+a;return o}};mG.exports=OCe});var yG=w((B$e,IG)=>{var MCe=Li(),KCe=(r,e)=>new MCe(r,e).major;IG.exports=KCe});var BG=w((b$e,wG)=>{var UCe=Li(),HCe=(r,e)=>new UCe(r,e).minor;wG.exports=HCe});var QG=w((Q$e,bG)=>{var GCe=Li(),YCe=(r,e)=>new GCe(r,e).patch;bG.exports=YCe});var vG=w((S$e,SG)=>{var jCe=gc(),qCe=(r,e)=>{let t=jCe(r,e);return t&&t.prerelease.length?t.prerelease:null};SG.exports=qCe});var PG=w((v$e,xG)=>{var JCe=cs(),WCe=(r,e,t)=>JCe(e,r,t);xG.exports=WCe});var kG=w((x$e,DG)=>{var zCe=cs(),VCe=(r,e)=>zCe(r,e,!0);DG.exports=VCe});var HI=w((P$e,FG)=>{var RG=Li(),XCe=(r,e,t)=>{let i=new RG(r,t),n=new RG(e,t);return i.compare(n)||i.compareBuild(n)};FG.exports=XCe});var TG=w((D$e,NG)=>{var ZCe=HI(),_Ce=(r,e)=>r.sort((t,i)=>ZCe(t,i,e));NG.exports=_Ce});var OG=w((k$e,LG)=>{var $Ce=HI(),eme=(r,e)=>r.sort((t,i)=>$Ce(i,t,e));LG.exports=eme});var dd=w((R$e,MG)=>{var tme=cs(),rme=(r,e,t)=>tme(r,e,t)>0;MG.exports=rme});var GI=w((F$e,KG)=>{var ime=cs(),nme=(r,e,t)=>ime(r,e,t)<0;KG.exports=nme});var Cv=w((N$e,UG)=>{var sme=cs(),ome=(r,e,t)=>sme(r,e,t)!==0;UG.exports=ome});var YI=w((T$e,HG)=>{var ame=cs(),Ame=(r,e,t)=>ame(r,e,t)>=0;HG.exports=Ame});var jI=w((L$e,GG)=>{var lme=cs(),cme=(r,e,t)=>lme(r,e,t)<=0;GG.exports=cme});var mv=w((O$e,YG)=>{var ume=UI(),gme=Cv(),fme=dd(),hme=YI(),pme=GI(),dme=jI(),Cme=(r,e,t,i)=>{switch(e){case"===":return typeof r=="object"&&(r=r.version),typeof t=="object"&&(t=t.version),r===t;case"!==":return typeof r=="object"&&(r=r.version),typeof t=="object"&&(t=t.version),r!==t;case"":case"=":case"==":return ume(r,t,i);case"!=":return gme(r,t,i);case">":return fme(r,t,i);case">=":return hme(r,t,i);case"<":return pme(r,t,i);case"<=":return dme(r,t,i);default:throw new TypeError(`Invalid operator: ${e}`)}};YG.exports=Cme});var qG=w((M$e,jG)=>{var mme=Li(),Eme=gc(),{re:qI,t:JI}=uc(),Ime=(r,e)=>{if(r instanceof mme)return r;if(typeof r=="number"&&(r=String(r)),typeof r!="string")return null;e=e||{};let t=null;if(!e.rtl)t=r.match(qI[JI.COERCE]);else{let i;for(;(i=qI[JI.COERCERTL].exec(r))&&(!t||t.index+t[0].length!==r.length);)(!t||i.index+i[0].length!==t.index+t[0].length)&&(t=i),qI[JI.COERCERTL].lastIndex=i.index+i[1].length+i[2].length;qI[JI.COERCERTL].lastIndex=-1}return t===null?null:Eme(`${t[2]}.${t[3]||"0"}.${t[4]||"0"}`,e)};jG.exports=Ime});var WG=w((K$e,JG)=>{"use strict";JG.exports=function(r){r.prototype[Symbol.iterator]=function*(){for(let e=this.head;e;e=e.next)yield e.value}}});var WI=w((U$e,zG)=>{"use strict";zG.exports=Ht;Ht.Node=fc;Ht.create=Ht;function Ht(r){var e=this;if(e instanceof Ht||(e=new Ht),e.tail=null,e.head=null,e.length=0,r&&typeof r.forEach=="function")r.forEach(function(n){e.push(n)});else if(arguments.length>0)for(var t=0,i=arguments.length;t1)t=e;else if(this.head)i=this.head.next,t=this.head.value;else throw new TypeError("Reduce of empty list with no initial value");for(var n=0;i!==null;n++)t=r(t,i.value,n),i=i.next;return t};Ht.prototype.reduceReverse=function(r,e){var t,i=this.tail;if(arguments.length>1)t=e;else if(this.tail)i=this.tail.prev,t=this.tail.value;else throw new TypeError("Reduce of empty list with no initial value");for(var n=this.length-1;i!==null;n--)t=r(t,i.value,n),i=i.prev;return t};Ht.prototype.toArray=function(){for(var r=new Array(this.length),e=0,t=this.head;t!==null;e++)r[e]=t.value,t=t.next;return r};Ht.prototype.toArrayReverse=function(){for(var r=new Array(this.length),e=0,t=this.tail;t!==null;e++)r[e]=t.value,t=t.prev;return r};Ht.prototype.slice=function(r,e){e=e||this.length,e<0&&(e+=this.length),r=r||0,r<0&&(r+=this.length);var t=new Ht;if(ethis.length&&(e=this.length);for(var i=0,n=this.head;n!==null&&ithis.length&&(e=this.length);for(var i=this.length,n=this.tail;n!==null&&i>e;i--)n=n.prev;for(;n!==null&&i>r;i--,n=n.prev)t.push(n.value);return t};Ht.prototype.splice=function(r,e,...t){r>this.length&&(r=this.length-1),r<0&&(r=this.length+r);for(var i=0,n=this.head;n!==null&&i{"use strict";var bme=WI(),hc=Symbol("max"),va=Symbol("length"),Wg=Symbol("lengthCalculator"),md=Symbol("allowStale"),pc=Symbol("maxAge"),Sa=Symbol("dispose"),VG=Symbol("noDisposeOnSet"),di=Symbol("lruList"),Zs=Symbol("cache"),ZG=Symbol("updateAgeOnGet"),Ev=()=>1,yv=class{constructor(e){if(typeof e=="number"&&(e={max:e}),e||(e={}),e.max&&(typeof e.max!="number"||e.max<0))throw new TypeError("max must be a non-negative number");let t=this[hc]=e.max||1/0,i=e.length||Ev;if(this[Wg]=typeof i!="function"?Ev:i,this[md]=e.stale||!1,e.maxAge&&typeof e.maxAge!="number")throw new TypeError("maxAge must be a number");this[pc]=e.maxAge||0,this[Sa]=e.dispose,this[VG]=e.noDisposeOnSet||!1,this[ZG]=e.updateAgeOnGet||!1,this.reset()}set max(e){if(typeof e!="number"||e<0)throw new TypeError("max must be a non-negative number");this[hc]=e||1/0,Cd(this)}get max(){return this[hc]}set allowStale(e){this[md]=!!e}get allowStale(){return this[md]}set maxAge(e){if(typeof e!="number")throw new TypeError("maxAge must be a non-negative number");this[pc]=e,Cd(this)}get maxAge(){return this[pc]}set lengthCalculator(e){typeof e!="function"&&(e=Ev),e!==this[Wg]&&(this[Wg]=e,this[va]=0,this[di].forEach(t=>{t.length=this[Wg](t.value,t.key),this[va]+=t.length})),Cd(this)}get lengthCalculator(){return this[Wg]}get length(){return this[va]}get itemCount(){return this[di].length}rforEach(e,t){t=t||this;for(let i=this[di].tail;i!==null;){let n=i.prev;XG(this,e,i,t),i=n}}forEach(e,t){t=t||this;for(let i=this[di].head;i!==null;){let n=i.next;XG(this,e,i,t),i=n}}keys(){return this[di].toArray().map(e=>e.key)}values(){return this[di].toArray().map(e=>e.value)}reset(){this[Sa]&&this[di]&&this[di].length&&this[di].forEach(e=>this[Sa](e.key,e.value)),this[Zs]=new Map,this[di]=new bme,this[va]=0}dump(){return this[di].map(e=>zI(this,e)?!1:{k:e.key,v:e.value,e:e.now+(e.maxAge||0)}).toArray().filter(e=>e)}dumpLru(){return this[di]}set(e,t,i){if(i=i||this[pc],i&&typeof i!="number")throw new TypeError("maxAge must be a number");let n=i?Date.now():0,s=this[Wg](t,e);if(this[Zs].has(e)){if(s>this[hc])return zg(this,this[Zs].get(e)),!1;let l=this[Zs].get(e).value;return this[Sa]&&(this[VG]||this[Sa](e,l.value)),l.now=n,l.maxAge=i,l.value=t,this[va]+=s-l.length,l.length=s,this.get(e),Cd(this),!0}let o=new wv(e,t,s,n,i);return o.length>this[hc]?(this[Sa]&&this[Sa](e,t),!1):(this[va]+=o.length,this[di].unshift(o),this[Zs].set(e,this[di].head),Cd(this),!0)}has(e){if(!this[Zs].has(e))return!1;let t=this[Zs].get(e).value;return!zI(this,t)}get(e){return Iv(this,e,!0)}peek(e){return Iv(this,e,!1)}pop(){let e=this[di].tail;return e?(zg(this,e),e.value):null}del(e){zg(this,this[Zs].get(e))}load(e){this.reset();let t=Date.now();for(let i=e.length-1;i>=0;i--){let n=e[i],s=n.e||0;if(s===0)this.set(n.k,n.v);else{let o=s-t;o>0&&this.set(n.k,n.v,o)}}}prune(){this[Zs].forEach((e,t)=>Iv(this,t,!1))}},Iv=(r,e,t)=>{let i=r[Zs].get(e);if(i){let n=i.value;if(zI(r,n)){if(zg(r,i),!r[md])return}else t&&(r[ZG]&&(i.value.now=Date.now()),r[di].unshiftNode(i));return n.value}},zI=(r,e)=>{if(!e||!e.maxAge&&!r[pc])return!1;let t=Date.now()-e.now;return e.maxAge?t>e.maxAge:r[pc]&&t>r[pc]},Cd=r=>{if(r[va]>r[hc])for(let e=r[di].tail;r[va]>r[hc]&&e!==null;){let t=e.prev;zg(r,e),e=t}},zg=(r,e)=>{if(e){let t=e.value;r[Sa]&&r[Sa](t.key,t.value),r[va]-=t.length,r[Zs].delete(t.key),r[di].removeNode(e)}},wv=class{constructor(e,t,i,n,s){this.key=e,this.value=t,this.length=i,this.now=n,this.maxAge=s||0}},XG=(r,e,t,i)=>{let n=t.value;zI(r,n)&&(zg(r,t),r[md]||(n=void 0)),n&&e.call(i,n.value,n.key,r)};_G.exports=yv});var us=w((G$e,iY)=>{var dc=class{constructor(e,t){if(t=Sme(t),e instanceof dc)return e.loose===!!t.loose&&e.includePrerelease===!!t.includePrerelease?e:new dc(e.raw,t);if(e instanceof Bv)return this.raw=e.value,this.set=[[e]],this.format(),this;if(this.options=t,this.loose=!!t.loose,this.includePrerelease=!!t.includePrerelease,this.raw=e,this.set=e.split(/\s*\|\|\s*/).map(i=>this.parseRange(i.trim())).filter(i=>i.length),!this.set.length)throw new TypeError(`Invalid SemVer Range: ${e}`);if(this.set.length>1){let i=this.set[0];if(this.set=this.set.filter(n=>!tY(n[0])),this.set.length===0)this.set=[i];else if(this.set.length>1){for(let n of this.set)if(n.length===1&&kme(n[0])){this.set=[n];break}}}this.format()}format(){return this.range=this.set.map(e=>e.join(" ").trim()).join("||").trim(),this.range}toString(){return this.range}parseRange(e){e=e.trim();let i=`parseRange:${Object.keys(this.options).join(",")}:${e}`,n=eY.get(i);if(n)return n;let s=this.options.loose,o=s?Oi[bi.HYPHENRANGELOOSE]:Oi[bi.HYPHENRANGE];e=e.replace(o,Hme(this.options.includePrerelease)),Hr("hyphen replace",e),e=e.replace(Oi[bi.COMPARATORTRIM],xme),Hr("comparator trim",e,Oi[bi.COMPARATORTRIM]),e=e.replace(Oi[bi.TILDETRIM],Pme),e=e.replace(Oi[bi.CARETTRIM],Dme),e=e.split(/\s+/).join(" ");let a=s?Oi[bi.COMPARATORLOOSE]:Oi[bi.COMPARATOR],l=e.split(" ").map(f=>Rme(f,this.options)).join(" ").split(/\s+/).map(f=>Ume(f,this.options)).filter(this.options.loose?f=>!!f.match(a):()=>!0).map(f=>new Bv(f,this.options)),c=l.length,u=new Map;for(let f of l){if(tY(f))return[f];u.set(f.value,f)}u.size>1&&u.has("")&&u.delete("");let g=[...u.values()];return eY.set(i,g),g}intersects(e,t){if(!(e instanceof dc))throw new TypeError("a Range is required");return this.set.some(i=>rY(i,t)&&e.set.some(n=>rY(n,t)&&i.every(s=>n.every(o=>s.intersects(o,t)))))}test(e){if(!e)return!1;if(typeof e=="string")try{e=new vme(e,this.options)}catch{return!1}for(let t=0;tr.value==="<0.0.0-0",kme=r=>r.value==="",rY=(r,e)=>{let t=!0,i=r.slice(),n=i.pop();for(;t&&i.length;)t=i.every(s=>n.intersects(s,e)),n=i.pop();return t},Rme=(r,e)=>(Hr("comp",r,e),r=Tme(r,e),Hr("caret",r),r=Fme(r,e),Hr("tildes",r),r=Ome(r,e),Hr("xrange",r),r=Kme(r,e),Hr("stars",r),r),$i=r=>!r||r.toLowerCase()==="x"||r==="*",Fme=(r,e)=>r.trim().split(/\s+/).map(t=>Nme(t,e)).join(" "),Nme=(r,e)=>{let t=e.loose?Oi[bi.TILDELOOSE]:Oi[bi.TILDE];return r.replace(t,(i,n,s,o,a)=>{Hr("tilde",r,i,n,s,o,a);let l;return $i(n)?l="":$i(s)?l=`>=${n}.0.0 <${+n+1}.0.0-0`:$i(o)?l=`>=${n}.${s}.0 <${n}.${+s+1}.0-0`:a?(Hr("replaceTilde pr",a),l=`>=${n}.${s}.${o}-${a} <${n}.${+s+1}.0-0`):l=`>=${n}.${s}.${o} <${n}.${+s+1}.0-0`,Hr("tilde return",l),l})},Tme=(r,e)=>r.trim().split(/\s+/).map(t=>Lme(t,e)).join(" "),Lme=(r,e)=>{Hr("caret",r,e);let t=e.loose?Oi[bi.CARETLOOSE]:Oi[bi.CARET],i=e.includePrerelease?"-0":"";return r.replace(t,(n,s,o,a,l)=>{Hr("caret",r,n,s,o,a,l);let c;return $i(s)?c="":$i(o)?c=`>=${s}.0.0${i} <${+s+1}.0.0-0`:$i(a)?s==="0"?c=`>=${s}.${o}.0${i} <${s}.${+o+1}.0-0`:c=`>=${s}.${o}.0${i} <${+s+1}.0.0-0`:l?(Hr("replaceCaret pr",l),s==="0"?o==="0"?c=`>=${s}.${o}.${a}-${l} <${s}.${o}.${+a+1}-0`:c=`>=${s}.${o}.${a}-${l} <${s}.${+o+1}.0-0`:c=`>=${s}.${o}.${a}-${l} <${+s+1}.0.0-0`):(Hr("no pr"),s==="0"?o==="0"?c=`>=${s}.${o}.${a}${i} <${s}.${o}.${+a+1}-0`:c=`>=${s}.${o}.${a}${i} <${s}.${+o+1}.0-0`:c=`>=${s}.${o}.${a} <${+s+1}.0.0-0`),Hr("caret return",c),c})},Ome=(r,e)=>(Hr("replaceXRanges",r,e),r.split(/\s+/).map(t=>Mme(t,e)).join(" ")),Mme=(r,e)=>{r=r.trim();let t=e.loose?Oi[bi.XRANGELOOSE]:Oi[bi.XRANGE];return r.replace(t,(i,n,s,o,a,l)=>{Hr("xRange",r,i,n,s,o,a,l);let c=$i(s),u=c||$i(o),g=u||$i(a),f=g;return n==="="&&f&&(n=""),l=e.includePrerelease?"-0":"",c?n===">"||n==="<"?i="<0.0.0-0":i="*":n&&f?(u&&(o=0),a=0,n===">"?(n=">=",u?(s=+s+1,o=0,a=0):(o=+o+1,a=0)):n==="<="&&(n="<",u?s=+s+1:o=+o+1),n==="<"&&(l="-0"),i=`${n+s}.${o}.${a}${l}`):u?i=`>=${s}.0.0${l} <${+s+1}.0.0-0`:g&&(i=`>=${s}.${o}.0${l} <${s}.${+o+1}.0-0`),Hr("xRange return",i),i})},Kme=(r,e)=>(Hr("replaceStars",r,e),r.trim().replace(Oi[bi.STAR],"")),Ume=(r,e)=>(Hr("replaceGTE0",r,e),r.trim().replace(Oi[e.includePrerelease?bi.GTE0PRE:bi.GTE0],"")),Hme=r=>(e,t,i,n,s,o,a,l,c,u,g,f,h)=>($i(i)?t="":$i(n)?t=`>=${i}.0.0${r?"-0":""}`:$i(s)?t=`>=${i}.${n}.0${r?"-0":""}`:o?t=`>=${t}`:t=`>=${t}${r?"-0":""}`,$i(c)?l="":$i(u)?l=`<${+c+1}.0.0-0`:$i(g)?l=`<${c}.${+u+1}.0-0`:f?l=`<=${c}.${u}.${g}-${f}`:r?l=`<${c}.${u}.${+g+1}-0`:l=`<=${l}`,`${t} ${l}`.trim()),Gme=(r,e,t)=>{for(let i=0;i0){let n=r[i].semver;if(n.major===e.major&&n.minor===e.minor&&n.patch===e.patch)return!0}return!1}return!0}});var Ed=w((Y$e,AY)=>{var Id=Symbol("SemVer ANY"),Vg=class{static get ANY(){return Id}constructor(e,t){if(t=Yme(t),e instanceof Vg){if(e.loose===!!t.loose)return e;e=e.value}Qv("comparator",e,t),this.options=t,this.loose=!!t.loose,this.parse(e),this.semver===Id?this.value="":this.value=this.operator+this.semver.version,Qv("comp",this)}parse(e){let t=this.options.loose?nY[sY.COMPARATORLOOSE]:nY[sY.COMPARATOR],i=e.match(t);if(!i)throw new TypeError(`Invalid comparator: ${e}`);this.operator=i[1]!==void 0?i[1]:"",this.operator==="="&&(this.operator=""),i[2]?this.semver=new oY(i[2],this.options.loose):this.semver=Id}toString(){return this.value}test(e){if(Qv("Comparator.test",e,this.options.loose),this.semver===Id||e===Id)return!0;if(typeof e=="string")try{e=new oY(e,this.options)}catch{return!1}return bv(e,this.operator,this.semver,this.options)}intersects(e,t){if(!(e instanceof Vg))throw new TypeError("a Comparator is required");if((!t||typeof t!="object")&&(t={loose:!!t,includePrerelease:!1}),this.operator==="")return this.value===""?!0:new aY(e.value,t).test(this.value);if(e.operator==="")return e.value===""?!0:new aY(this.value,t).test(e.semver);let i=(this.operator===">="||this.operator===">")&&(e.operator===">="||e.operator===">"),n=(this.operator==="<="||this.operator==="<")&&(e.operator==="<="||e.operator==="<"),s=this.semver.version===e.semver.version,o=(this.operator===">="||this.operator==="<=")&&(e.operator===">="||e.operator==="<="),a=bv(this.semver,"<",e.semver,t)&&(this.operator===">="||this.operator===">")&&(e.operator==="<="||e.operator==="<"),l=bv(this.semver,">",e.semver,t)&&(this.operator==="<="||this.operator==="<")&&(e.operator===">="||e.operator===">");return i||n||s&&o||a||l}};AY.exports=Vg;var Yme=hd(),{re:nY,t:sY}=uc(),bv=mv(),Qv=fd(),oY=Li(),aY=us()});var yd=w((j$e,lY)=>{var jme=us(),qme=(r,e,t)=>{try{e=new jme(e,t)}catch{return!1}return e.test(r)};lY.exports=qme});var uY=w((q$e,cY)=>{var Jme=us(),Wme=(r,e)=>new Jme(r,e).set.map(t=>t.map(i=>i.value).join(" ").trim().split(" "));cY.exports=Wme});var fY=w((J$e,gY)=>{var zme=Li(),Vme=us(),Xme=(r,e,t)=>{let i=null,n=null,s=null;try{s=new Vme(e,t)}catch{return null}return r.forEach(o=>{s.test(o)&&(!i||n.compare(o)===-1)&&(i=o,n=new zme(i,t))}),i};gY.exports=Xme});var pY=w((W$e,hY)=>{var Zme=Li(),_me=us(),$me=(r,e,t)=>{let i=null,n=null,s=null;try{s=new _me(e,t)}catch{return null}return r.forEach(o=>{s.test(o)&&(!i||n.compare(o)===1)&&(i=o,n=new Zme(i,t))}),i};hY.exports=$me});var mY=w((z$e,CY)=>{var Sv=Li(),eEe=us(),dY=dd(),tEe=(r,e)=>{r=new eEe(r,e);let t=new Sv("0.0.0");if(r.test(t)||(t=new Sv("0.0.0-0"),r.test(t)))return t;t=null;for(let i=0;i{let a=new Sv(o.semver.version);switch(o.operator){case">":a.prerelease.length===0?a.patch++:a.prerelease.push(0),a.raw=a.format();case"":case">=":(!s||dY(a,s))&&(s=a);break;case"<":case"<=":break;default:throw new Error(`Unexpected operation: ${o.operator}`)}}),s&&(!t||dY(t,s))&&(t=s)}return t&&r.test(t)?t:null};CY.exports=tEe});var IY=w((V$e,EY)=>{var rEe=us(),iEe=(r,e)=>{try{return new rEe(r,e).range||"*"}catch{return null}};EY.exports=iEe});var VI=w((X$e,bY)=>{var nEe=Li(),BY=Ed(),{ANY:sEe}=BY,oEe=us(),aEe=yd(),yY=dd(),wY=GI(),AEe=jI(),lEe=YI(),cEe=(r,e,t,i)=>{r=new nEe(r,i),e=new oEe(e,i);let n,s,o,a,l;switch(t){case">":n=yY,s=AEe,o=wY,a=">",l=">=";break;case"<":n=wY,s=lEe,o=yY,a="<",l="<=";break;default:throw new TypeError('Must provide a hilo val of "<" or ">"')}if(aEe(r,e,i))return!1;for(let c=0;c{h.semver===sEe&&(h=new BY(">=0.0.0")),g=g||h,f=f||h,n(h.semver,g.semver,i)?g=h:o(h.semver,f.semver,i)&&(f=h)}),g.operator===a||g.operator===l||(!f.operator||f.operator===a)&&s(r,f.semver))return!1;if(f.operator===l&&o(r,f.semver))return!1}return!0};bY.exports=cEe});var SY=w((Z$e,QY)=>{var uEe=VI(),gEe=(r,e,t)=>uEe(r,e,">",t);QY.exports=gEe});var xY=w((_$e,vY)=>{var fEe=VI(),hEe=(r,e,t)=>fEe(r,e,"<",t);vY.exports=hEe});var kY=w(($$e,DY)=>{var PY=us(),pEe=(r,e,t)=>(r=new PY(r,t),e=new PY(e,t),r.intersects(e));DY.exports=pEe});var FY=w((eet,RY)=>{var dEe=yd(),CEe=cs();RY.exports=(r,e,t)=>{let i=[],n=null,s=null,o=r.sort((u,g)=>CEe(u,g,t));for(let u of o)dEe(u,e,t)?(s=u,n||(n=u)):(s&&i.push([n,s]),s=null,n=null);n&&i.push([n,null]);let a=[];for(let[u,g]of i)u===g?a.push(u):!g&&u===o[0]?a.push("*"):g?u===o[0]?a.push(`<=${g}`):a.push(`${u} - ${g}`):a.push(`>=${u}`);let l=a.join(" || "),c=typeof e.raw=="string"?e.raw:String(e);return l.length{var NY=us(),XI=Ed(),{ANY:vv}=XI,wd=yd(),xv=cs(),mEe=(r,e,t={})=>{if(r===e)return!0;r=new NY(r,t),e=new NY(e,t);let i=!1;e:for(let n of r.set){for(let s of e.set){let o=EEe(n,s,t);if(i=i||o!==null,o)continue e}if(i)return!1}return!0},EEe=(r,e,t)=>{if(r===e)return!0;if(r.length===1&&r[0].semver===vv){if(e.length===1&&e[0].semver===vv)return!0;t.includePrerelease?r=[new XI(">=0.0.0-0")]:r=[new XI(">=0.0.0")]}if(e.length===1&&e[0].semver===vv){if(t.includePrerelease)return!0;e=[new XI(">=0.0.0")]}let i=new Set,n,s;for(let h of r)h.operator===">"||h.operator===">="?n=TY(n,h,t):h.operator==="<"||h.operator==="<="?s=LY(s,h,t):i.add(h.semver);if(i.size>1)return null;let o;if(n&&s){if(o=xv(n.semver,s.semver,t),o>0)return null;if(o===0&&(n.operator!==">="||s.operator!=="<="))return null}for(let h of i){if(n&&!wd(h,String(n),t)||s&&!wd(h,String(s),t))return null;for(let p of e)if(!wd(h,String(p),t))return!1;return!0}let a,l,c,u,g=s&&!t.includePrerelease&&s.semver.prerelease.length?s.semver:!1,f=n&&!t.includePrerelease&&n.semver.prerelease.length?n.semver:!1;g&&g.prerelease.length===1&&s.operator==="<"&&g.prerelease[0]===0&&(g=!1);for(let h of e){if(u=u||h.operator===">"||h.operator===">=",c=c||h.operator==="<"||h.operator==="<=",n){if(f&&h.semver.prerelease&&h.semver.prerelease.length&&h.semver.major===f.major&&h.semver.minor===f.minor&&h.semver.patch===f.patch&&(f=!1),h.operator===">"||h.operator===">="){if(a=TY(n,h,t),a===h&&a!==n)return!1}else if(n.operator===">="&&!wd(n.semver,String(h),t))return!1}if(s){if(g&&h.semver.prerelease&&h.semver.prerelease.length&&h.semver.major===g.major&&h.semver.minor===g.minor&&h.semver.patch===g.patch&&(g=!1),h.operator==="<"||h.operator==="<="){if(l=LY(s,h,t),l===h&&l!==s)return!1}else if(s.operator==="<="&&!wd(s.semver,String(h),t))return!1}if(!h.operator&&(s||n)&&o!==0)return!1}return!(n&&c&&!s&&o!==0||s&&u&&!n&&o!==0||f||g)},TY=(r,e,t)=>{if(!r)return e;let i=xv(r.semver,e.semver,t);return i>0?r:i<0||e.operator===">"&&r.operator===">="?e:r},LY=(r,e,t)=>{if(!r)return e;let i=xv(r.semver,e.semver,t);return i<0?r:i>0||e.operator==="<"&&r.operator==="<="?e:r};OY.exports=mEe});var Xr=w((ret,KY)=>{var Pv=uc();KY.exports={re:Pv.re,src:Pv.src,tokens:Pv.t,SEMVER_SPEC_VERSION:gd().SEMVER_SPEC_VERSION,SemVer:Li(),compareIdentifiers:OI().compareIdentifiers,rcompareIdentifiers:OI().rcompareIdentifiers,parse:gc(),valid:lG(),clean:uG(),inc:fG(),diff:EG(),major:yG(),minor:BG(),patch:QG(),prerelease:vG(),compare:cs(),rcompare:PG(),compareLoose:kG(),compareBuild:HI(),sort:TG(),rsort:OG(),gt:dd(),lt:GI(),eq:UI(),neq:Cv(),gte:YI(),lte:jI(),cmp:mv(),coerce:qG(),Comparator:Ed(),Range:us(),satisfies:yd(),toComparators:uY(),maxSatisfying:fY(),minSatisfying:pY(),minVersion:mY(),validRange:IY(),outside:VI(),gtr:SY(),ltr:xY(),intersects:kY(),simplifyRange:FY(),subset:MY()}});var Dv=w(ZI=>{"use strict";Object.defineProperty(ZI,"__esModule",{value:!0});ZI.VERSION=void 0;ZI.VERSION="9.1.0"});var Gt=w((exports,module)=>{"use strict";var __spreadArray=exports&&exports.__spreadArray||function(r,e,t){if(t||arguments.length===2)for(var i=0,n=e.length,s;i{(function(r,e){typeof define=="function"&&define.amd?define([],e):typeof _I=="object"&&_I.exports?_I.exports=e():r.regexpToAst=e()})(typeof self<"u"?self:UY,function(){function r(){}r.prototype.saveState=function(){return{idx:this.idx,input:this.input,groupIdx:this.groupIdx}},r.prototype.restoreState=function(p){this.idx=p.idx,this.input=p.input,this.groupIdx=p.groupIdx},r.prototype.pattern=function(p){this.idx=0,this.input=p,this.groupIdx=0,this.consumeChar("/");var C=this.disjunction();this.consumeChar("/");for(var y={type:"Flags",loc:{begin:this.idx,end:p.length},global:!1,ignoreCase:!1,multiLine:!1,unicode:!1,sticky:!1};this.isRegExpFlag();)switch(this.popChar()){case"g":o(y,"global");break;case"i":o(y,"ignoreCase");break;case"m":o(y,"multiLine");break;case"u":o(y,"unicode");break;case"y":o(y,"sticky");break}if(this.idx!==this.input.length)throw Error("Redundant input: "+this.input.substring(this.idx));return{type:"Pattern",flags:y,value:C,loc:this.loc(0)}},r.prototype.disjunction=function(){var p=[],C=this.idx;for(p.push(this.alternative());this.peekChar()==="|";)this.consumeChar("|"),p.push(this.alternative());return{type:"Disjunction",value:p,loc:this.loc(C)}},r.prototype.alternative=function(){for(var p=[],C=this.idx;this.isTerm();)p.push(this.term());return{type:"Alternative",value:p,loc:this.loc(C)}},r.prototype.term=function(){return this.isAssertion()?this.assertion():this.atom()},r.prototype.assertion=function(){var p=this.idx;switch(this.popChar()){case"^":return{type:"StartAnchor",loc:this.loc(p)};case"$":return{type:"EndAnchor",loc:this.loc(p)};case"\\":switch(this.popChar()){case"b":return{type:"WordBoundary",loc:this.loc(p)};case"B":return{type:"NonWordBoundary",loc:this.loc(p)}}throw Error("Invalid Assertion Escape");case"(":this.consumeChar("?");var C;switch(this.popChar()){case"=":C="Lookahead";break;case"!":C="NegativeLookahead";break}a(C);var y=this.disjunction();return this.consumeChar(")"),{type:C,value:y,loc:this.loc(p)}}l()},r.prototype.quantifier=function(p){var C,y=this.idx;switch(this.popChar()){case"*":C={atLeast:0,atMost:1/0};break;case"+":C={atLeast:1,atMost:1/0};break;case"?":C={atLeast:0,atMost:1};break;case"{":var B=this.integerIncludingZero();switch(this.popChar()){case"}":C={atLeast:B,atMost:B};break;case",":var v;this.isDigit()?(v=this.integerIncludingZero(),C={atLeast:B,atMost:v}):C={atLeast:B,atMost:1/0},this.consumeChar("}");break}if(p===!0&&C===void 0)return;a(C);break}if(!(p===!0&&C===void 0))return a(C),this.peekChar(0)==="?"?(this.consumeChar("?"),C.greedy=!1):C.greedy=!0,C.type="Quantifier",C.loc=this.loc(y),C},r.prototype.atom=function(){var p,C=this.idx;switch(this.peekChar()){case".":p=this.dotAll();break;case"\\":p=this.atomEscape();break;case"[":p=this.characterClass();break;case"(":p=this.group();break}return p===void 0&&this.isPatternCharacter()&&(p=this.patternCharacter()),a(p),p.loc=this.loc(C),this.isQuantifier()&&(p.quantifier=this.quantifier()),p},r.prototype.dotAll=function(){return this.consumeChar("."),{type:"Set",complement:!0,value:[n(` `),n("\r"),n("\u2028"),n("\u2029")]}},r.prototype.atomEscape=function(){switch(this.consumeChar("\\"),this.peekChar()){case"1":case"2":case"3":case"4":case"5":case"6":case"7":case"8":case"9":return this.decimalEscapeAtom();case"d":case"D":case"s":case"S":case"w":case"W":return this.characterClassEscape();case"f":case"n":case"r":case"t":case"v":return this.controlEscapeAtom();case"c":return this.controlLetterEscapeAtom();case"0":return this.nulCharacterAtom();case"x":return this.hexEscapeSequenceAtom();case"u":return this.regExpUnicodeEscapeSequenceAtom();default:return this.identityEscapeAtom()}},r.prototype.decimalEscapeAtom=function(){var p=this.positiveInteger();return{type:"GroupBackReference",value:p}},r.prototype.characterClassEscape=function(){var p,C=!1;switch(this.popChar()){case"d":p=u;break;case"D":p=u,C=!0;break;case"s":p=f;break;case"S":p=f,C=!0;break;case"w":p=g;break;case"W":p=g,C=!0;break}return a(p),{type:"Set",value:p,complement:C}},r.prototype.controlEscapeAtom=function(){var p;switch(this.popChar()){case"f":p=n("\f");break;case"n":p=n(` `);break;case"r":p=n("\r");break;case"t":p=n(" ");break;case"v":p=n("\v");break}return a(p),{type:"Character",value:p}},r.prototype.controlLetterEscapeAtom=function(){this.consumeChar("c");var p=this.popChar();if(/[a-zA-Z]/.test(p)===!1)throw Error("Invalid ");var C=p.toUpperCase().charCodeAt(0)-64;return{type:"Character",value:C}},r.prototype.nulCharacterAtom=function(){return this.consumeChar("0"),{type:"Character",value:n("\0")}},r.prototype.hexEscapeSequenceAtom=function(){return this.consumeChar("x"),this.parseHexDigits(2)},r.prototype.regExpUnicodeEscapeSequenceAtom=function(){return this.consumeChar("u"),this.parseHexDigits(4)},r.prototype.identityEscapeAtom=function(){var p=this.popChar();return{type:"Character",value:n(p)}},r.prototype.classPatternCharacterAtom=function(){switch(this.peekChar()){case` `:case"\r":case"\u2028":case"\u2029":case"\\":case"]":throw Error("TBD");default:var p=this.popChar();return{type:"Character",value:n(p)}}},r.prototype.characterClass=function(){var p=[],C=!1;for(this.consumeChar("["),this.peekChar(0)==="^"&&(this.consumeChar("^"),C=!0);this.isClassAtom();){var y=this.classAtom(),B=y.type==="Character";if(B&&this.isRangeDash()){this.consumeChar("-");var v=this.classAtom(),D=v.type==="Character";if(D){if(v.value=this.input.length)throw Error("Unexpected end of input");this.idx++},r.prototype.loc=function(p){return{begin:p,end:this.idx}};var e=/[0-9a-fA-F]/,t=/[0-9]/,i=/[1-9]/;function n(p){return p.charCodeAt(0)}function s(p,C){p.length!==void 0?p.forEach(function(y){C.push(y)}):C.push(p)}function o(p,C){if(p[C]===!0)throw"duplicate flag "+C;p[C]=!0}function a(p){if(p===void 0)throw Error("Internal Error - Should never get here!")}function l(){throw Error("Internal Error - Should never get here!")}var c,u=[];for(c=n("0");c<=n("9");c++)u.push(c);var g=[n("_")].concat(u);for(c=n("a");c<=n("z");c++)g.push(c);for(c=n("A");c<=n("Z");c++)g.push(c);var f=[n(" "),n("\f"),n(` `),n("\r"),n(" "),n("\v"),n(" "),n("\xA0"),n("\u1680"),n("\u2000"),n("\u2001"),n("\u2002"),n("\u2003"),n("\u2004"),n("\u2005"),n("\u2006"),n("\u2007"),n("\u2008"),n("\u2009"),n("\u200A"),n("\u2028"),n("\u2029"),n("\u202F"),n("\u205F"),n("\u3000"),n("\uFEFF")];function h(){}return h.prototype.visitChildren=function(p){for(var C in p){var y=p[C];p.hasOwnProperty(C)&&(y.type!==void 0?this.visit(y):Array.isArray(y)&&y.forEach(function(B){this.visit(B)},this))}},h.prototype.visit=function(p){switch(p.type){case"Pattern":this.visitPattern(p);break;case"Flags":this.visitFlags(p);break;case"Disjunction":this.visitDisjunction(p);break;case"Alternative":this.visitAlternative(p);break;case"StartAnchor":this.visitStartAnchor(p);break;case"EndAnchor":this.visitEndAnchor(p);break;case"WordBoundary":this.visitWordBoundary(p);break;case"NonWordBoundary":this.visitNonWordBoundary(p);break;case"Lookahead":this.visitLookahead(p);break;case"NegativeLookahead":this.visitNegativeLookahead(p);break;case"Character":this.visitCharacter(p);break;case"Set":this.visitSet(p);break;case"Group":this.visitGroup(p);break;case"GroupBackReference":this.visitGroupBackReference(p);break;case"Quantifier":this.visitQuantifier(p);break}this.visitChildren(p)},h.prototype.visitPattern=function(p){},h.prototype.visitFlags=function(p){},h.prototype.visitDisjunction=function(p){},h.prototype.visitAlternative=function(p){},h.prototype.visitStartAnchor=function(p){},h.prototype.visitEndAnchor=function(p){},h.prototype.visitWordBoundary=function(p){},h.prototype.visitNonWordBoundary=function(p){},h.prototype.visitLookahead=function(p){},h.prototype.visitNegativeLookahead=function(p){},h.prototype.visitCharacter=function(p){},h.prototype.visitSet=function(p){},h.prototype.visitGroup=function(p){},h.prototype.visitGroupBackReference=function(p){},h.prototype.visitQuantifier=function(p){},{RegExpParser:r,BaseRegExpVisitor:h,VERSION:"0.5.0"}})});var ty=w(Xg=>{"use strict";Object.defineProperty(Xg,"__esModule",{value:!0});Xg.clearRegExpParserCache=Xg.getRegExpAst=void 0;var IEe=$I(),ey={},yEe=new IEe.RegExpParser;function wEe(r){var e=r.toString();if(ey.hasOwnProperty(e))return ey[e];var t=yEe.pattern(e);return ey[e]=t,t}Xg.getRegExpAst=wEe;function BEe(){ey={}}Xg.clearRegExpParserCache=BEe});var qY=w(Cn=>{"use strict";var bEe=Cn&&Cn.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(Cn,"__esModule",{value:!0});Cn.canMatchCharCode=Cn.firstCharOptimizedIndices=Cn.getOptimizedStartCodesIndices=Cn.failedOptimizationPrefixMsg=void 0;var GY=$I(),gs=Gt(),YY=ty(),xa=Rv(),jY="Complement Sets are not supported for first char optimization";Cn.failedOptimizationPrefixMsg=`Unable to use "first char" lexer optimizations: `;function QEe(r,e){e===void 0&&(e=!1);try{var t=(0,YY.getRegExpAst)(r),i=iy(t.value,{},t.flags.ignoreCase);return i}catch(s){if(s.message===jY)e&&(0,gs.PRINT_WARNING)(""+Cn.failedOptimizationPrefixMsg+(" Unable to optimize: < "+r.toString()+` > `)+` Complement Sets cannot be automatically optimized. This will disable the lexer's first char optimizations. See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#COMPLEMENT for details.`);else{var n="";e&&(n=` This will disable the lexer's first char optimizations. See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#REGEXP_PARSING for details.`),(0,gs.PRINT_ERROR)(Cn.failedOptimizationPrefixMsg+` `+(" Failed parsing: < "+r.toString()+` > `)+(" Using the regexp-to-ast library version: "+GY.VERSION+` `)+" Please open an issue at: https://github.com/bd82/regexp-to-ast/issues"+n)}}return[]}Cn.getOptimizedStartCodesIndices=QEe;function iy(r,e,t){switch(r.type){case"Disjunction":for(var i=0;i=xa.minOptimizationVal)for(var f=u.from>=xa.minOptimizationVal?u.from:xa.minOptimizationVal,h=u.to,p=(0,xa.charCodeToOptimizedIndex)(f),C=(0,xa.charCodeToOptimizedIndex)(h),y=p;y<=C;y++)e[y]=y}}});break;case"Group":iy(o.value,e,t);break;default:throw Error("Non Exhaustive Match")}var a=o.quantifier!==void 0&&o.quantifier.atLeast===0;if(o.type==="Group"&&kv(o)===!1||o.type!=="Group"&&a===!1)break}break;default:throw Error("non exhaustive match!")}return(0,gs.values)(e)}Cn.firstCharOptimizedIndices=iy;function ry(r,e,t){var i=(0,xa.charCodeToOptimizedIndex)(r);e[i]=i,t===!0&&SEe(r,e)}function SEe(r,e){var t=String.fromCharCode(r),i=t.toUpperCase();if(i!==t){var n=(0,xa.charCodeToOptimizedIndex)(i.charCodeAt(0));e[n]=n}else{var s=t.toLowerCase();if(s!==t){var n=(0,xa.charCodeToOptimizedIndex)(s.charCodeAt(0));e[n]=n}}}function HY(r,e){return(0,gs.find)(r.value,function(t){if(typeof t=="number")return(0,gs.contains)(e,t);var i=t;return(0,gs.find)(e,function(n){return i.from<=n&&n<=i.to})!==void 0})}function kv(r){return r.quantifier&&r.quantifier.atLeast===0?!0:r.value?(0,gs.isArray)(r.value)?(0,gs.every)(r.value,kv):kv(r.value):!1}var vEe=function(r){bEe(e,r);function e(t){var i=r.call(this)||this;return i.targetCharCodes=t,i.found=!1,i}return e.prototype.visitChildren=function(t){if(this.found!==!0){switch(t.type){case"Lookahead":this.visitLookahead(t);return;case"NegativeLookahead":this.visitNegativeLookahead(t);return}r.prototype.visitChildren.call(this,t)}},e.prototype.visitCharacter=function(t){(0,gs.contains)(this.targetCharCodes,t.value)&&(this.found=!0)},e.prototype.visitSet=function(t){t.complement?HY(t,this.targetCharCodes)===void 0&&(this.found=!0):HY(t,this.targetCharCodes)!==void 0&&(this.found=!0)},e}(GY.BaseRegExpVisitor);function xEe(r,e){if(e instanceof RegExp){var t=(0,YY.getRegExpAst)(e),i=new vEe(r);return i.visit(t),i.found}else return(0,gs.find)(e,function(n){return(0,gs.contains)(r,n.charCodeAt(0))})!==void 0}Cn.canMatchCharCode=xEe});var Rv=w(Ve=>{"use strict";var JY=Ve&&Ve.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(Ve,"__esModule",{value:!0});Ve.charCodeToOptimizedIndex=Ve.minOptimizationVal=Ve.buildLineBreakIssueMessage=Ve.LineTerminatorOptimizedTester=Ve.isShortPattern=Ve.isCustomPattern=Ve.cloneEmptyGroups=Ve.performWarningRuntimeChecks=Ve.performRuntimeChecks=Ve.addStickyFlag=Ve.addStartOfInput=Ve.findUnreachablePatterns=Ve.findModesThatDoNotExist=Ve.findInvalidGroupType=Ve.findDuplicatePatterns=Ve.findUnsupportedFlags=Ve.findStartOfInputAnchor=Ve.findEmptyMatchRegExps=Ve.findEndOfInputAnchor=Ve.findInvalidPatterns=Ve.findMissingPatterns=Ve.validatePatterns=Ve.analyzeTokenTypes=Ve.enableSticky=Ve.disableSticky=Ve.SUPPORT_STICKY=Ve.MODES=Ve.DEFAULT_MODE=void 0;var WY=$I(),ir=Bd(),xe=Gt(),Zg=qY(),zY=ty(),ko="PATTERN";Ve.DEFAULT_MODE="defaultMode";Ve.MODES="modes";Ve.SUPPORT_STICKY=typeof new RegExp("(?:)").sticky=="boolean";function PEe(){Ve.SUPPORT_STICKY=!1}Ve.disableSticky=PEe;function DEe(){Ve.SUPPORT_STICKY=!0}Ve.enableSticky=DEe;function kEe(r,e){e=(0,xe.defaults)(e,{useSticky:Ve.SUPPORT_STICKY,debug:!1,safeMode:!1,positionTracking:"full",lineTerminatorCharacters:["\r",` `],tracer:function(v,D){return D()}});var t=e.tracer;t("initCharCodeToOptimizedIndexMap",function(){HEe()});var i;t("Reject Lexer.NA",function(){i=(0,xe.reject)(r,function(v){return v[ko]===ir.Lexer.NA})});var n=!1,s;t("Transform Patterns",function(){n=!1,s=(0,xe.map)(i,function(v){var D=v[ko];if((0,xe.isRegExp)(D)){var T=D.source;return T.length===1&&T!=="^"&&T!=="$"&&T!=="."&&!D.ignoreCase?T:T.length===2&&T[0]==="\\"&&!(0,xe.contains)(["d","D","s","S","t","r","n","t","0","c","b","B","f","v","w","W"],T[1])?T[1]:e.useSticky?Tv(D):Nv(D)}else{if((0,xe.isFunction)(D))return n=!0,{exec:D};if((0,xe.has)(D,"exec"))return n=!0,D;if(typeof D=="string"){if(D.length===1)return D;var H=D.replace(/[\\^$.*+?()[\]{}|]/g,"\\$&"),j=new RegExp(H);return e.useSticky?Tv(j):Nv(j)}else throw Error("non exhaustive match")}})});var o,a,l,c,u;t("misc mapping",function(){o=(0,xe.map)(i,function(v){return v.tokenTypeIdx}),a=(0,xe.map)(i,function(v){var D=v.GROUP;if(D!==ir.Lexer.SKIPPED){if((0,xe.isString)(D))return D;if((0,xe.isUndefined)(D))return!1;throw Error("non exhaustive match")}}),l=(0,xe.map)(i,function(v){var D=v.LONGER_ALT;if(D){var T=(0,xe.isArray)(D)?(0,xe.map)(D,function(H){return(0,xe.indexOf)(i,H)}):[(0,xe.indexOf)(i,D)];return T}}),c=(0,xe.map)(i,function(v){return v.PUSH_MODE}),u=(0,xe.map)(i,function(v){return(0,xe.has)(v,"POP_MODE")})});var g;t("Line Terminator Handling",function(){var v=Aj(e.lineTerminatorCharacters);g=(0,xe.map)(i,function(D){return!1}),e.positionTracking!=="onlyOffset"&&(g=(0,xe.map)(i,function(D){if((0,xe.has)(D,"LINE_BREAKS"))return D.LINE_BREAKS;if(oj(D,v)===!1)return(0,Zg.canMatchCharCode)(v,D.PATTERN)}))});var f,h,p,C;t("Misc Mapping #2",function(){f=(0,xe.map)(i,Ov),h=(0,xe.map)(s,sj),p=(0,xe.reduce)(i,function(v,D){var T=D.GROUP;return(0,xe.isString)(T)&&T!==ir.Lexer.SKIPPED&&(v[T]=[]),v},{}),C=(0,xe.map)(s,function(v,D){return{pattern:s[D],longerAlt:l[D],canLineTerminator:g[D],isCustom:f[D],short:h[D],group:a[D],push:c[D],pop:u[D],tokenTypeIdx:o[D],tokenType:i[D]}})});var y=!0,B=[];return e.safeMode||t("First Char Optimization",function(){B=(0,xe.reduce)(i,function(v,D,T){if(typeof D.PATTERN=="string"){var H=D.PATTERN.charCodeAt(0),j=Lv(H);Fv(v,j,C[T])}else if((0,xe.isArray)(D.START_CHARS_HINT)){var $;(0,xe.forEach)(D.START_CHARS_HINT,function(W){var _=typeof W=="string"?W.charCodeAt(0):W,A=Lv(_);$!==A&&($=A,Fv(v,A,C[T]))})}else if((0,xe.isRegExp)(D.PATTERN))if(D.PATTERN.unicode)y=!1,e.ensureOptimizations&&(0,xe.PRINT_ERROR)(""+Zg.failedOptimizationPrefixMsg+(" Unable to analyze < "+D.PATTERN.toString()+` > pattern. `)+` The regexp unicode flag is not currently supported by the regexp-to-ast library. This will disable the lexer's first char optimizations. For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#UNICODE_OPTIMIZE`);else{var V=(0,Zg.getOptimizedStartCodesIndices)(D.PATTERN,e.ensureOptimizations);(0,xe.isEmpty)(V)&&(y=!1),(0,xe.forEach)(V,function(W){Fv(v,W,C[T])})}else e.ensureOptimizations&&(0,xe.PRINT_ERROR)(""+Zg.failedOptimizationPrefixMsg+(" TokenType: <"+D.name+`> is using a custom token pattern without providing parameter. `)+` This will disable the lexer's first char optimizations. For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#CUSTOM_OPTIMIZE`),y=!1;return v},[])}),t("ArrayPacking",function(){B=(0,xe.packArray)(B)}),{emptyGroups:p,patternIdxToConfig:C,charCodeToPatternIdxToConfig:B,hasCustom:n,canBeOptimized:y}}Ve.analyzeTokenTypes=kEe;function REe(r,e){var t=[],i=VY(r);t=t.concat(i.errors);var n=XY(i.valid),s=n.valid;return t=t.concat(n.errors),t=t.concat(FEe(s)),t=t.concat(rj(s)),t=t.concat(ij(s,e)),t=t.concat(nj(s)),t}Ve.validatePatterns=REe;function FEe(r){var e=[],t=(0,xe.filter)(r,function(i){return(0,xe.isRegExp)(i[ko])});return e=e.concat(ZY(t)),e=e.concat($Y(t)),e=e.concat(ej(t)),e=e.concat(tj(t)),e=e.concat(_Y(t)),e}function VY(r){var e=(0,xe.filter)(r,function(n){return!(0,xe.has)(n,ko)}),t=(0,xe.map)(e,function(n){return{message:"Token Type: ->"+n.name+"<- missing static 'PATTERN' property",type:ir.LexerDefinitionErrorType.MISSING_PATTERN,tokenTypes:[n]}}),i=(0,xe.difference)(r,e);return{errors:t,valid:i}}Ve.findMissingPatterns=VY;function XY(r){var e=(0,xe.filter)(r,function(n){var s=n[ko];return!(0,xe.isRegExp)(s)&&!(0,xe.isFunction)(s)&&!(0,xe.has)(s,"exec")&&!(0,xe.isString)(s)}),t=(0,xe.map)(e,function(n){return{message:"Token Type: ->"+n.name+"<- static 'PATTERN' can only be a RegExp, a Function matching the {CustomPatternMatcherFunc} type or an Object matching the {ICustomPattern} interface.",type:ir.LexerDefinitionErrorType.INVALID_PATTERN,tokenTypes:[n]}}),i=(0,xe.difference)(r,e);return{errors:t,valid:i}}Ve.findInvalidPatterns=XY;var NEe=/[^\\][\$]/;function ZY(r){var e=function(n){JY(s,n);function s(){var o=n!==null&&n.apply(this,arguments)||this;return o.found=!1,o}return s.prototype.visitEndAnchor=function(o){this.found=!0},s}(WY.BaseRegExpVisitor),t=(0,xe.filter)(r,function(n){var s=n[ko];try{var o=(0,zY.getRegExpAst)(s),a=new e;return a.visit(o),a.found}catch{return NEe.test(s.source)}}),i=(0,xe.map)(t,function(n){return{message:`Unexpected RegExp Anchor Error: Token Type: ->`+n.name+`<- static 'PATTERN' cannot contain end of input anchor '$' See chevrotain.io/docs/guide/resolving_lexer_errors.html#ANCHORS for details.`,type:ir.LexerDefinitionErrorType.EOI_ANCHOR_FOUND,tokenTypes:[n]}});return i}Ve.findEndOfInputAnchor=ZY;function _Y(r){var e=(0,xe.filter)(r,function(i){var n=i[ko];return n.test("")}),t=(0,xe.map)(e,function(i){return{message:"Token Type: ->"+i.name+"<- static 'PATTERN' must not match an empty string",type:ir.LexerDefinitionErrorType.EMPTY_MATCH_PATTERN,tokenTypes:[i]}});return t}Ve.findEmptyMatchRegExps=_Y;var TEe=/[^\\[][\^]|^\^/;function $Y(r){var e=function(n){JY(s,n);function s(){var o=n!==null&&n.apply(this,arguments)||this;return o.found=!1,o}return s.prototype.visitStartAnchor=function(o){this.found=!0},s}(WY.BaseRegExpVisitor),t=(0,xe.filter)(r,function(n){var s=n[ko];try{var o=(0,zY.getRegExpAst)(s),a=new e;return a.visit(o),a.found}catch{return TEe.test(s.source)}}),i=(0,xe.map)(t,function(n){return{message:`Unexpected RegExp Anchor Error: Token Type: ->`+n.name+`<- static 'PATTERN' cannot contain start of input anchor '^' See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#ANCHORS for details.`,type:ir.LexerDefinitionErrorType.SOI_ANCHOR_FOUND,tokenTypes:[n]}});return i}Ve.findStartOfInputAnchor=$Y;function ej(r){var e=(0,xe.filter)(r,function(i){var n=i[ko];return n instanceof RegExp&&(n.multiline||n.global)}),t=(0,xe.map)(e,function(i){return{message:"Token Type: ->"+i.name+"<- static 'PATTERN' may NOT contain global('g') or multiline('m')",type:ir.LexerDefinitionErrorType.UNSUPPORTED_FLAGS_FOUND,tokenTypes:[i]}});return t}Ve.findUnsupportedFlags=ej;function tj(r){var e=[],t=(0,xe.map)(r,function(s){return(0,xe.reduce)(r,function(o,a){return s.PATTERN.source===a.PATTERN.source&&!(0,xe.contains)(e,a)&&a.PATTERN!==ir.Lexer.NA&&(e.push(a),o.push(a)),o},[])});t=(0,xe.compact)(t);var i=(0,xe.filter)(t,function(s){return s.length>1}),n=(0,xe.map)(i,function(s){var o=(0,xe.map)(s,function(l){return l.name}),a=(0,xe.first)(s).PATTERN;return{message:"The same RegExp pattern ->"+a+"<-"+("has been used in all of the following Token Types: "+o.join(", ")+" <-"),type:ir.LexerDefinitionErrorType.DUPLICATE_PATTERNS_FOUND,tokenTypes:s}});return n}Ve.findDuplicatePatterns=tj;function rj(r){var e=(0,xe.filter)(r,function(i){if(!(0,xe.has)(i,"GROUP"))return!1;var n=i.GROUP;return n!==ir.Lexer.SKIPPED&&n!==ir.Lexer.NA&&!(0,xe.isString)(n)}),t=(0,xe.map)(e,function(i){return{message:"Token Type: ->"+i.name+"<- static 'GROUP' can only be Lexer.SKIPPED/Lexer.NA/A String",type:ir.LexerDefinitionErrorType.INVALID_GROUP_TYPE_FOUND,tokenTypes:[i]}});return t}Ve.findInvalidGroupType=rj;function ij(r,e){var t=(0,xe.filter)(r,function(n){return n.PUSH_MODE!==void 0&&!(0,xe.contains)(e,n.PUSH_MODE)}),i=(0,xe.map)(t,function(n){var s="Token Type: ->"+n.name+"<- static 'PUSH_MODE' value cannot refer to a Lexer Mode ->"+n.PUSH_MODE+"<-which does not exist";return{message:s,type:ir.LexerDefinitionErrorType.PUSH_MODE_DOES_NOT_EXIST,tokenTypes:[n]}});return i}Ve.findModesThatDoNotExist=ij;function nj(r){var e=[],t=(0,xe.reduce)(r,function(i,n,s){var o=n.PATTERN;return o===ir.Lexer.NA||((0,xe.isString)(o)?i.push({str:o,idx:s,tokenType:n}):(0,xe.isRegExp)(o)&&OEe(o)&&i.push({str:o.source,idx:s,tokenType:n})),i},[]);return(0,xe.forEach)(r,function(i,n){(0,xe.forEach)(t,function(s){var o=s.str,a=s.idx,l=s.tokenType;if(n"+i.name+"<-")+`in the lexer's definition. See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#UNREACHABLE`;e.push({message:c,type:ir.LexerDefinitionErrorType.UNREACHABLE_PATTERN,tokenTypes:[i,l]})}})}),e}Ve.findUnreachablePatterns=nj;function LEe(r,e){if((0,xe.isRegExp)(e)){var t=e.exec(r);return t!==null&&t.index===0}else{if((0,xe.isFunction)(e))return e(r,0,[],{});if((0,xe.has)(e,"exec"))return e.exec(r,0,[],{});if(typeof e=="string")return e===r;throw Error("non exhaustive match")}}function OEe(r){var e=[".","\\","[","]","|","^","$","(",")","?","*","+","{"];return(0,xe.find)(e,function(t){return r.source.indexOf(t)!==-1})===void 0}function Nv(r){var e=r.ignoreCase?"i":"";return new RegExp("^(?:"+r.source+")",e)}Ve.addStartOfInput=Nv;function Tv(r){var e=r.ignoreCase?"iy":"y";return new RegExp(""+r.source,e)}Ve.addStickyFlag=Tv;function MEe(r,e,t){var i=[];return(0,xe.has)(r,Ve.DEFAULT_MODE)||i.push({message:"A MultiMode Lexer cannot be initialized without a <"+Ve.DEFAULT_MODE+`> property in its definition `,type:ir.LexerDefinitionErrorType.MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE}),(0,xe.has)(r,Ve.MODES)||i.push({message:"A MultiMode Lexer cannot be initialized without a <"+Ve.MODES+`> property in its definition `,type:ir.LexerDefinitionErrorType.MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY}),(0,xe.has)(r,Ve.MODES)&&(0,xe.has)(r,Ve.DEFAULT_MODE)&&!(0,xe.has)(r.modes,r.defaultMode)&&i.push({message:"A MultiMode Lexer cannot be initialized with a "+Ve.DEFAULT_MODE+": <"+r.defaultMode+`>which does not exist `,type:ir.LexerDefinitionErrorType.MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST}),(0,xe.has)(r,Ve.MODES)&&(0,xe.forEach)(r.modes,function(n,s){(0,xe.forEach)(n,function(o,a){(0,xe.isUndefined)(o)&&i.push({message:"A Lexer cannot be initialized using an undefined Token Type. Mode:"+("<"+s+"> at index: <"+a+`> `),type:ir.LexerDefinitionErrorType.LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED})})}),i}Ve.performRuntimeChecks=MEe;function KEe(r,e,t){var i=[],n=!1,s=(0,xe.compact)((0,xe.flatten)((0,xe.mapValues)(r.modes,function(l){return l}))),o=(0,xe.reject)(s,function(l){return l[ko]===ir.Lexer.NA}),a=Aj(t);return e&&(0,xe.forEach)(o,function(l){var c=oj(l,a);if(c!==!1){var u=aj(l,c),g={message:u,type:c.issue,tokenType:l};i.push(g)}else(0,xe.has)(l,"LINE_BREAKS")?l.LINE_BREAKS===!0&&(n=!0):(0,Zg.canMatchCharCode)(a,l.PATTERN)&&(n=!0)}),e&&!n&&i.push({message:`Warning: No LINE_BREAKS Found. This Lexer has been defined to track line and column information, But none of the Token Types can be identified as matching a line terminator. See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#LINE_BREAKS for details.`,type:ir.LexerDefinitionErrorType.NO_LINE_BREAKS_FLAGS}),i}Ve.performWarningRuntimeChecks=KEe;function UEe(r){var e={},t=(0,xe.keys)(r);return(0,xe.forEach)(t,function(i){var n=r[i];if((0,xe.isArray)(n))e[i]=[];else throw Error("non exhaustive match")}),e}Ve.cloneEmptyGroups=UEe;function Ov(r){var e=r.PATTERN;if((0,xe.isRegExp)(e))return!1;if((0,xe.isFunction)(e))return!0;if((0,xe.has)(e,"exec"))return!0;if((0,xe.isString)(e))return!1;throw Error("non exhaustive match")}Ve.isCustomPattern=Ov;function sj(r){return(0,xe.isString)(r)&&r.length===1?r.charCodeAt(0):!1}Ve.isShortPattern=sj;Ve.LineTerminatorOptimizedTester={test:function(r){for(var e=r.length,t=this.lastIndex;t Token Type `)+(" Root cause: "+e.errMsg+`. `)+" For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#IDENTIFY_TERMINATOR";if(e.issue===ir.LexerDefinitionErrorType.CUSTOM_LINE_BREAK)return`Warning: A Custom Token Pattern should specify the option. `+(" The problem is in the <"+r.name+`> Token Type `)+" For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#CUSTOM_LINE_BREAK";throw Error("non exhaustive match")}Ve.buildLineBreakIssueMessage=aj;function Aj(r){var e=(0,xe.map)(r,function(t){return(0,xe.isString)(t)&&t.length>0?t.charCodeAt(0):t});return e}function Fv(r,e,t){r[e]===void 0?r[e]=[t]:r[e].push(t)}Ve.minOptimizationVal=256;var ny=[];function Lv(r){return r255?255+~~(r/255):r}}});var _g=w(Nt=>{"use strict";Object.defineProperty(Nt,"__esModule",{value:!0});Nt.isTokenType=Nt.hasExtendingTokensTypesMapProperty=Nt.hasExtendingTokensTypesProperty=Nt.hasCategoriesProperty=Nt.hasShortKeyProperty=Nt.singleAssignCategoriesToksMap=Nt.assignCategoriesMapProp=Nt.assignCategoriesTokensProp=Nt.assignTokenDefaultProps=Nt.expandCategories=Nt.augmentTokenTypes=Nt.tokenIdxToClass=Nt.tokenShortNameIdx=Nt.tokenStructuredMatcherNoCategories=Nt.tokenStructuredMatcher=void 0;var Zr=Gt();function GEe(r,e){var t=r.tokenTypeIdx;return t===e.tokenTypeIdx?!0:e.isParent===!0&&e.categoryMatchesMap[t]===!0}Nt.tokenStructuredMatcher=GEe;function YEe(r,e){return r.tokenTypeIdx===e.tokenTypeIdx}Nt.tokenStructuredMatcherNoCategories=YEe;Nt.tokenShortNameIdx=1;Nt.tokenIdxToClass={};function jEe(r){var e=lj(r);cj(e),gj(e),uj(e),(0,Zr.forEach)(e,function(t){t.isParent=t.categoryMatches.length>0})}Nt.augmentTokenTypes=jEe;function lj(r){for(var e=(0,Zr.cloneArr)(r),t=r,i=!0;i;){t=(0,Zr.compact)((0,Zr.flatten)((0,Zr.map)(t,function(s){return s.CATEGORIES})));var n=(0,Zr.difference)(t,e);e=e.concat(n),(0,Zr.isEmpty)(n)?i=!1:t=n}return e}Nt.expandCategories=lj;function cj(r){(0,Zr.forEach)(r,function(e){fj(e)||(Nt.tokenIdxToClass[Nt.tokenShortNameIdx]=e,e.tokenTypeIdx=Nt.tokenShortNameIdx++),Mv(e)&&!(0,Zr.isArray)(e.CATEGORIES)&&(e.CATEGORIES=[e.CATEGORIES]),Mv(e)||(e.CATEGORIES=[]),hj(e)||(e.categoryMatches=[]),pj(e)||(e.categoryMatchesMap={})})}Nt.assignTokenDefaultProps=cj;function uj(r){(0,Zr.forEach)(r,function(e){e.categoryMatches=[],(0,Zr.forEach)(e.categoryMatchesMap,function(t,i){e.categoryMatches.push(Nt.tokenIdxToClass[i].tokenTypeIdx)})})}Nt.assignCategoriesTokensProp=uj;function gj(r){(0,Zr.forEach)(r,function(e){Kv([],e)})}Nt.assignCategoriesMapProp=gj;function Kv(r,e){(0,Zr.forEach)(r,function(t){e.categoryMatchesMap[t.tokenTypeIdx]=!0}),(0,Zr.forEach)(e.CATEGORIES,function(t){var i=r.concat(e);(0,Zr.contains)(i,t)||Kv(i,t)})}Nt.singleAssignCategoriesToksMap=Kv;function fj(r){return(0,Zr.has)(r,"tokenTypeIdx")}Nt.hasShortKeyProperty=fj;function Mv(r){return(0,Zr.has)(r,"CATEGORIES")}Nt.hasCategoriesProperty=Mv;function hj(r){return(0,Zr.has)(r,"categoryMatches")}Nt.hasExtendingTokensTypesProperty=hj;function pj(r){return(0,Zr.has)(r,"categoryMatchesMap")}Nt.hasExtendingTokensTypesMapProperty=pj;function qEe(r){return(0,Zr.has)(r,"tokenTypeIdx")}Nt.isTokenType=qEe});var Uv=w(sy=>{"use strict";Object.defineProperty(sy,"__esModule",{value:!0});sy.defaultLexerErrorProvider=void 0;sy.defaultLexerErrorProvider={buildUnableToPopLexerModeMessage:function(r){return"Unable to pop Lexer Mode after encountering Token ->"+r.image+"<- The Mode Stack is empty"},buildUnexpectedCharactersMessage:function(r,e,t,i,n){return"unexpected character: ->"+r.charAt(e)+"<- at offset: "+e+","+(" skipped "+t+" characters.")}}});var Bd=w(Cc=>{"use strict";Object.defineProperty(Cc,"__esModule",{value:!0});Cc.Lexer=Cc.LexerDefinitionErrorType=void 0;var _s=Rv(),nr=Gt(),JEe=_g(),WEe=Uv(),zEe=ty(),VEe;(function(r){r[r.MISSING_PATTERN=0]="MISSING_PATTERN",r[r.INVALID_PATTERN=1]="INVALID_PATTERN",r[r.EOI_ANCHOR_FOUND=2]="EOI_ANCHOR_FOUND",r[r.UNSUPPORTED_FLAGS_FOUND=3]="UNSUPPORTED_FLAGS_FOUND",r[r.DUPLICATE_PATTERNS_FOUND=4]="DUPLICATE_PATTERNS_FOUND",r[r.INVALID_GROUP_TYPE_FOUND=5]="INVALID_GROUP_TYPE_FOUND",r[r.PUSH_MODE_DOES_NOT_EXIST=6]="PUSH_MODE_DOES_NOT_EXIST",r[r.MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE=7]="MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE",r[r.MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY=8]="MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY",r[r.MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST=9]="MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST",r[r.LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED=10]="LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED",r[r.SOI_ANCHOR_FOUND=11]="SOI_ANCHOR_FOUND",r[r.EMPTY_MATCH_PATTERN=12]="EMPTY_MATCH_PATTERN",r[r.NO_LINE_BREAKS_FLAGS=13]="NO_LINE_BREAKS_FLAGS",r[r.UNREACHABLE_PATTERN=14]="UNREACHABLE_PATTERN",r[r.IDENTIFY_TERMINATOR=15]="IDENTIFY_TERMINATOR",r[r.CUSTOM_LINE_BREAK=16]="CUSTOM_LINE_BREAK"})(VEe=Cc.LexerDefinitionErrorType||(Cc.LexerDefinitionErrorType={}));var bd={deferDefinitionErrorsHandling:!1,positionTracking:"full",lineTerminatorsPattern:/\n|\r\n?/g,lineTerminatorCharacters:[` `,"\r"],ensureOptimizations:!1,safeMode:!1,errorMessageProvider:WEe.defaultLexerErrorProvider,traceInitPerf:!1,skipValidations:!1};Object.freeze(bd);var XEe=function(){function r(e,t){var i=this;if(t===void 0&&(t=bd),this.lexerDefinition=e,this.lexerDefinitionErrors=[],this.lexerDefinitionWarning=[],this.patternIdxToConfig={},this.charCodeToPatternIdxToConfig={},this.modes=[],this.emptyGroups={},this.config=void 0,this.trackStartLines=!0,this.trackEndLines=!0,this.hasCustom=!1,this.canModeBeOptimized={},typeof t=="boolean")throw Error(`The second argument to the Lexer constructor is now an ILexerConfig Object. a boolean 2nd argument is no longer supported`);this.config=(0,nr.merge)(bd,t);var n=this.config.traceInitPerf;n===!0?(this.traceInitMaxIdent=1/0,this.traceInitPerf=!0):typeof n=="number"&&(this.traceInitMaxIdent=n,this.traceInitPerf=!0),this.traceInitIndent=-1,this.TRACE_INIT("Lexer Constructor",function(){var s,o=!0;i.TRACE_INIT("Lexer Config handling",function(){if(i.config.lineTerminatorsPattern===bd.lineTerminatorsPattern)i.config.lineTerminatorsPattern=_s.LineTerminatorOptimizedTester;else if(i.config.lineTerminatorCharacters===bd.lineTerminatorCharacters)throw Error(`Error: Missing property on the Lexer config. For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#MISSING_LINE_TERM_CHARS`);if(t.safeMode&&t.ensureOptimizations)throw Error('"safeMode" and "ensureOptimizations" flags are mutually exclusive.');i.trackStartLines=/full|onlyStart/i.test(i.config.positionTracking),i.trackEndLines=/full/i.test(i.config.positionTracking),(0,nr.isArray)(e)?(s={modes:{}},s.modes[_s.DEFAULT_MODE]=(0,nr.cloneArr)(e),s[_s.DEFAULT_MODE]=_s.DEFAULT_MODE):(o=!1,s=(0,nr.cloneObj)(e))}),i.config.skipValidations===!1&&(i.TRACE_INIT("performRuntimeChecks",function(){i.lexerDefinitionErrors=i.lexerDefinitionErrors.concat((0,_s.performRuntimeChecks)(s,i.trackStartLines,i.config.lineTerminatorCharacters))}),i.TRACE_INIT("performWarningRuntimeChecks",function(){i.lexerDefinitionWarning=i.lexerDefinitionWarning.concat((0,_s.performWarningRuntimeChecks)(s,i.trackStartLines,i.config.lineTerminatorCharacters))})),s.modes=s.modes?s.modes:{},(0,nr.forEach)(s.modes,function(u,g){s.modes[g]=(0,nr.reject)(u,function(f){return(0,nr.isUndefined)(f)})});var a=(0,nr.keys)(s.modes);if((0,nr.forEach)(s.modes,function(u,g){i.TRACE_INIT("Mode: <"+g+"> processing",function(){if(i.modes.push(g),i.config.skipValidations===!1&&i.TRACE_INIT("validatePatterns",function(){i.lexerDefinitionErrors=i.lexerDefinitionErrors.concat((0,_s.validatePatterns)(u,a))}),(0,nr.isEmpty)(i.lexerDefinitionErrors)){(0,JEe.augmentTokenTypes)(u);var f;i.TRACE_INIT("analyzeTokenTypes",function(){f=(0,_s.analyzeTokenTypes)(u,{lineTerminatorCharacters:i.config.lineTerminatorCharacters,positionTracking:t.positionTracking,ensureOptimizations:t.ensureOptimizations,safeMode:t.safeMode,tracer:i.TRACE_INIT.bind(i)})}),i.patternIdxToConfig[g]=f.patternIdxToConfig,i.charCodeToPatternIdxToConfig[g]=f.charCodeToPatternIdxToConfig,i.emptyGroups=(0,nr.merge)(i.emptyGroups,f.emptyGroups),i.hasCustom=f.hasCustom||i.hasCustom,i.canModeBeOptimized[g]=f.canBeOptimized}})}),i.defaultMode=s.defaultMode,!(0,nr.isEmpty)(i.lexerDefinitionErrors)&&!i.config.deferDefinitionErrorsHandling){var l=(0,nr.map)(i.lexerDefinitionErrors,function(u){return u.message}),c=l.join(`----------------------- `);throw new Error(`Errors detected in definition of Lexer: `+c)}(0,nr.forEach)(i.lexerDefinitionWarning,function(u){(0,nr.PRINT_WARNING)(u.message)}),i.TRACE_INIT("Choosing sub-methods implementations",function(){if(_s.SUPPORT_STICKY?(i.chopInput=nr.IDENTITY,i.match=i.matchWithTest):(i.updateLastIndex=nr.NOOP,i.match=i.matchWithExec),o&&(i.handleModes=nr.NOOP),i.trackStartLines===!1&&(i.computeNewColumn=nr.IDENTITY),i.trackEndLines===!1&&(i.updateTokenEndLineColumnLocation=nr.NOOP),/full/i.test(i.config.positionTracking))i.createTokenInstance=i.createFullToken;else if(/onlyStart/i.test(i.config.positionTracking))i.createTokenInstance=i.createStartOnlyToken;else if(/onlyOffset/i.test(i.config.positionTracking))i.createTokenInstance=i.createOffsetOnlyToken;else throw Error('Invalid config option: "'+i.config.positionTracking+'"');i.hasCustom?(i.addToken=i.addTokenUsingPush,i.handlePayload=i.handlePayloadWithCustom):(i.addToken=i.addTokenUsingMemberAccess,i.handlePayload=i.handlePayloadNoCustom)}),i.TRACE_INIT("Failed Optimization Warnings",function(){var u=(0,nr.reduce)(i.canModeBeOptimized,function(g,f,h){return f===!1&&g.push(h),g},[]);if(t.ensureOptimizations&&!(0,nr.isEmpty)(u))throw Error("Lexer Modes: < "+u.join(", ")+` > cannot be optimized. Disable the "ensureOptimizations" lexer config flag to silently ignore this and run the lexer in an un-optimized mode. Or inspect the console log for details on how to resolve these issues.`)}),i.TRACE_INIT("clearRegExpParserCache",function(){(0,zEe.clearRegExpParserCache)()}),i.TRACE_INIT("toFastProperties",function(){(0,nr.toFastProperties)(i)})})}return r.prototype.tokenize=function(e,t){if(t===void 0&&(t=this.defaultMode),!(0,nr.isEmpty)(this.lexerDefinitionErrors)){var i=(0,nr.map)(this.lexerDefinitionErrors,function(o){return o.message}),n=i.join(`----------------------- `);throw new Error(`Unable to Tokenize because Errors detected in definition of Lexer: `+n)}var s=this.tokenizeInternal(e,t);return s},r.prototype.tokenizeInternal=function(e,t){var i=this,n,s,o,a,l,c,u,g,f,h,p,C,y,B,v,D,T=e,H=T.length,j=0,$=0,V=this.hasCustom?0:Math.floor(e.length/10),W=new Array(V),_=[],A=this.trackStartLines?1:void 0,Ae=this.trackStartLines?1:void 0,ge=(0,_s.cloneEmptyGroups)(this.emptyGroups),re=this.trackStartLines,O=this.config.lineTerminatorsPattern,F=0,ue=[],pe=[],ke=[],Fe=[];Object.freeze(Fe);var Ne=void 0;function oe(){return ue}function le(pr){var Ii=(0,_s.charCodeToOptimizedIndex)(pr),rs=pe[Ii];return rs===void 0?Fe:rs}var Be=function(pr){if(ke.length===1&&pr.tokenType.PUSH_MODE===void 0){var Ii=i.config.errorMessageProvider.buildUnableToPopLexerModeMessage(pr);_.push({offset:pr.startOffset,line:pr.startLine!==void 0?pr.startLine:void 0,column:pr.startColumn!==void 0?pr.startColumn:void 0,length:pr.image.length,message:Ii})}else{ke.pop();var rs=(0,nr.last)(ke);ue=i.patternIdxToConfig[rs],pe=i.charCodeToPatternIdxToConfig[rs],F=ue.length;var fa=i.canModeBeOptimized[rs]&&i.config.safeMode===!1;pe&&fa?Ne=le:Ne=oe}};function fe(pr){ke.push(pr),pe=this.charCodeToPatternIdxToConfig[pr],ue=this.patternIdxToConfig[pr],F=ue.length,F=ue.length;var Ii=this.canModeBeOptimized[pr]&&this.config.safeMode===!1;pe&&Ii?Ne=le:Ne=oe}fe.call(this,t);for(var ae;jc.length){c=a,u=g,ae=_e;break}}}break}}if(c!==null){if(f=c.length,h=ae.group,h!==void 0&&(p=ae.tokenTypeIdx,C=this.createTokenInstance(c,j,p,ae.tokenType,A,Ae,f),this.handlePayload(C,u),h===!1?$=this.addToken(W,$,C):ge[h].push(C)),e=this.chopInput(e,f),j=j+f,Ae=this.computeNewColumn(Ae,f),re===!0&&ae.canLineTerminator===!0){var It=0,Or=void 0,ii=void 0;O.lastIndex=0;do Or=O.test(c),Or===!0&&(ii=O.lastIndex-1,It++);while(Or===!0);It!==0&&(A=A+It,Ae=f-ii,this.updateTokenEndLineColumnLocation(C,h,ii,It,A,Ae,f))}this.handleModes(ae,Be,fe,C)}else{for(var gi=j,hr=A,fi=Ae,ni=!1;!ni&&j <"+e+">");var n=(0,nr.timer)(t),s=n.time,o=n.value,a=s>10?console.warn:console.log;return this.traceInitIndent time: "+s+"ms"),this.traceInitIndent--,o}else return t()},r.SKIPPED="This marks a skipped Token pattern, this means each token identified by it willbe consumed and then thrown into oblivion, this can be used to for example to completely ignore whitespace.",r.NA=/NOT_APPLICABLE/,r}();Cc.Lexer=XEe});var TA=w(Qi=>{"use strict";Object.defineProperty(Qi,"__esModule",{value:!0});Qi.tokenMatcher=Qi.createTokenInstance=Qi.EOF=Qi.createToken=Qi.hasTokenLabel=Qi.tokenName=Qi.tokenLabel=void 0;var $s=Gt(),ZEe=Bd(),Hv=_g();function _Ee(r){return bj(r)?r.LABEL:r.name}Qi.tokenLabel=_Ee;function $Ee(r){return r.name}Qi.tokenName=$Ee;function bj(r){return(0,$s.isString)(r.LABEL)&&r.LABEL!==""}Qi.hasTokenLabel=bj;var eIe="parent",dj="categories",Cj="label",mj="group",Ej="push_mode",Ij="pop_mode",yj="longer_alt",wj="line_breaks",Bj="start_chars_hint";function Qj(r){return tIe(r)}Qi.createToken=Qj;function tIe(r){var e=r.pattern,t={};if(t.name=r.name,(0,$s.isUndefined)(e)||(t.PATTERN=e),(0,$s.has)(r,eIe))throw`The parent property is no longer supported. See: https://github.com/chevrotain/chevrotain/issues/564#issuecomment-349062346 for details.`;return(0,$s.has)(r,dj)&&(t.CATEGORIES=r[dj]),(0,Hv.augmentTokenTypes)([t]),(0,$s.has)(r,Cj)&&(t.LABEL=r[Cj]),(0,$s.has)(r,mj)&&(t.GROUP=r[mj]),(0,$s.has)(r,Ij)&&(t.POP_MODE=r[Ij]),(0,$s.has)(r,Ej)&&(t.PUSH_MODE=r[Ej]),(0,$s.has)(r,yj)&&(t.LONGER_ALT=r[yj]),(0,$s.has)(r,wj)&&(t.LINE_BREAKS=r[wj]),(0,$s.has)(r,Bj)&&(t.START_CHARS_HINT=r[Bj]),t}Qi.EOF=Qj({name:"EOF",pattern:ZEe.Lexer.NA});(0,Hv.augmentTokenTypes)([Qi.EOF]);function rIe(r,e,t,i,n,s,o,a){return{image:e,startOffset:t,endOffset:i,startLine:n,endLine:s,startColumn:o,endColumn:a,tokenTypeIdx:r.tokenTypeIdx,tokenType:r}}Qi.createTokenInstance=rIe;function iIe(r,e){return(0,Hv.tokenStructuredMatcher)(r,e)}Qi.tokenMatcher=iIe});var mn=w(zt=>{"use strict";var Pa=zt&&zt.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(zt,"__esModule",{value:!0});zt.serializeProduction=zt.serializeGrammar=zt.Terminal=zt.Alternation=zt.RepetitionWithSeparator=zt.Repetition=zt.RepetitionMandatoryWithSeparator=zt.RepetitionMandatory=zt.Option=zt.Alternative=zt.Rule=zt.NonTerminal=zt.AbstractProduction=void 0;var Ar=Gt(),nIe=TA(),Ro=function(){function r(e){this._definition=e}return Object.defineProperty(r.prototype,"definition",{get:function(){return this._definition},set:function(e){this._definition=e},enumerable:!1,configurable:!0}),r.prototype.accept=function(e){e.visit(this),(0,Ar.forEach)(this.definition,function(t){t.accept(e)})},r}();zt.AbstractProduction=Ro;var Sj=function(r){Pa(e,r);function e(t){var i=r.call(this,[])||this;return i.idx=1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return Object.defineProperty(e.prototype,"definition",{get:function(){return this.referencedRule!==void 0?this.referencedRule.definition:[]},set:function(t){},enumerable:!1,configurable:!0}),e.prototype.accept=function(t){t.visit(this)},e}(Ro);zt.NonTerminal=Sj;var vj=function(r){Pa(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.orgText="",(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(Ro);zt.Rule=vj;var xj=function(r){Pa(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.ignoreAmbiguities=!1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(Ro);zt.Alternative=xj;var Pj=function(r){Pa(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.idx=1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(Ro);zt.Option=Pj;var Dj=function(r){Pa(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.idx=1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(Ro);zt.RepetitionMandatory=Dj;var kj=function(r){Pa(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.idx=1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(Ro);zt.RepetitionMandatoryWithSeparator=kj;var Rj=function(r){Pa(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.idx=1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(Ro);zt.Repetition=Rj;var Fj=function(r){Pa(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.idx=1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(Ro);zt.RepetitionWithSeparator=Fj;var Nj=function(r){Pa(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.idx=1,i.ignoreAmbiguities=!1,i.hasPredicates=!1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return Object.defineProperty(e.prototype,"definition",{get:function(){return this._definition},set:function(t){this._definition=t},enumerable:!1,configurable:!0}),e}(Ro);zt.Alternation=Nj;var oy=function(){function r(e){this.idx=1,(0,Ar.assign)(this,(0,Ar.pick)(e,function(t){return t!==void 0}))}return r.prototype.accept=function(e){e.visit(this)},r}();zt.Terminal=oy;function sIe(r){return(0,Ar.map)(r,Qd)}zt.serializeGrammar=sIe;function Qd(r){function e(s){return(0,Ar.map)(s,Qd)}if(r instanceof Sj){var t={type:"NonTerminal",name:r.nonTerminalName,idx:r.idx};return(0,Ar.isString)(r.label)&&(t.label=r.label),t}else{if(r instanceof xj)return{type:"Alternative",definition:e(r.definition)};if(r instanceof Pj)return{type:"Option",idx:r.idx,definition:e(r.definition)};if(r instanceof Dj)return{type:"RepetitionMandatory",idx:r.idx,definition:e(r.definition)};if(r instanceof kj)return{type:"RepetitionMandatoryWithSeparator",idx:r.idx,separator:Qd(new oy({terminalType:r.separator})),definition:e(r.definition)};if(r instanceof Fj)return{type:"RepetitionWithSeparator",idx:r.idx,separator:Qd(new oy({terminalType:r.separator})),definition:e(r.definition)};if(r instanceof Rj)return{type:"Repetition",idx:r.idx,definition:e(r.definition)};if(r instanceof Nj)return{type:"Alternation",idx:r.idx,definition:e(r.definition)};if(r instanceof oy){var i={type:"Terminal",name:r.terminalType.name,label:(0,nIe.tokenLabel)(r.terminalType),idx:r.idx};(0,Ar.isString)(r.label)&&(i.terminalLabel=r.label);var n=r.terminalType.PATTERN;return r.terminalType.PATTERN&&(i.pattern=(0,Ar.isRegExp)(n)?n.source:n),i}else{if(r instanceof vj)return{type:"Rule",name:r.name,orgText:r.orgText,definition:e(r.definition)};throw Error("non exhaustive match")}}}zt.serializeProduction=Qd});var Ay=w(ay=>{"use strict";Object.defineProperty(ay,"__esModule",{value:!0});ay.RestWalker=void 0;var Gv=Gt(),En=mn(),oIe=function(){function r(){}return r.prototype.walk=function(e,t){var i=this;t===void 0&&(t=[]),(0,Gv.forEach)(e.definition,function(n,s){var o=(0,Gv.drop)(e.definition,s+1);if(n instanceof En.NonTerminal)i.walkProdRef(n,o,t);else if(n instanceof En.Terminal)i.walkTerminal(n,o,t);else if(n instanceof En.Alternative)i.walkFlat(n,o,t);else if(n instanceof En.Option)i.walkOption(n,o,t);else if(n instanceof En.RepetitionMandatory)i.walkAtLeastOne(n,o,t);else if(n instanceof En.RepetitionMandatoryWithSeparator)i.walkAtLeastOneSep(n,o,t);else if(n instanceof En.RepetitionWithSeparator)i.walkManySep(n,o,t);else if(n instanceof En.Repetition)i.walkMany(n,o,t);else if(n instanceof En.Alternation)i.walkOr(n,o,t);else throw Error("non exhaustive match")})},r.prototype.walkTerminal=function(e,t,i){},r.prototype.walkProdRef=function(e,t,i){},r.prototype.walkFlat=function(e,t,i){var n=t.concat(i);this.walk(e,n)},r.prototype.walkOption=function(e,t,i){var n=t.concat(i);this.walk(e,n)},r.prototype.walkAtLeastOne=function(e,t,i){var n=[new En.Option({definition:e.definition})].concat(t,i);this.walk(e,n)},r.prototype.walkAtLeastOneSep=function(e,t,i){var n=Tj(e,t,i);this.walk(e,n)},r.prototype.walkMany=function(e,t,i){var n=[new En.Option({definition:e.definition})].concat(t,i);this.walk(e,n)},r.prototype.walkManySep=function(e,t,i){var n=Tj(e,t,i);this.walk(e,n)},r.prototype.walkOr=function(e,t,i){var n=this,s=t.concat(i);(0,Gv.forEach)(e.definition,function(o){var a=new En.Alternative({definition:[o]});n.walk(a,s)})},r}();ay.RestWalker=oIe;function Tj(r,e,t){var i=[new En.Option({definition:[new En.Terminal({terminalType:r.separator})].concat(r.definition)})],n=i.concat(e,t);return n}});var $g=w(ly=>{"use strict";Object.defineProperty(ly,"__esModule",{value:!0});ly.GAstVisitor=void 0;var Fo=mn(),aIe=function(){function r(){}return r.prototype.visit=function(e){var t=e;switch(t.constructor){case Fo.NonTerminal:return this.visitNonTerminal(t);case Fo.Alternative:return this.visitAlternative(t);case Fo.Option:return this.visitOption(t);case Fo.RepetitionMandatory:return this.visitRepetitionMandatory(t);case Fo.RepetitionMandatoryWithSeparator:return this.visitRepetitionMandatoryWithSeparator(t);case Fo.RepetitionWithSeparator:return this.visitRepetitionWithSeparator(t);case Fo.Repetition:return this.visitRepetition(t);case Fo.Alternation:return this.visitAlternation(t);case Fo.Terminal:return this.visitTerminal(t);case Fo.Rule:return this.visitRule(t);default:throw Error("non exhaustive match")}},r.prototype.visitNonTerminal=function(e){},r.prototype.visitAlternative=function(e){},r.prototype.visitOption=function(e){},r.prototype.visitRepetition=function(e){},r.prototype.visitRepetitionMandatory=function(e){},r.prototype.visitRepetitionMandatoryWithSeparator=function(e){},r.prototype.visitRepetitionWithSeparator=function(e){},r.prototype.visitAlternation=function(e){},r.prototype.visitTerminal=function(e){},r.prototype.visitRule=function(e){},r}();ly.GAstVisitor=aIe});var vd=w(Mi=>{"use strict";var AIe=Mi&&Mi.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(Mi,"__esModule",{value:!0});Mi.collectMethods=Mi.DslMethodsCollectorVisitor=Mi.getProductionDslName=Mi.isBranchingProd=Mi.isOptionalProd=Mi.isSequenceProd=void 0;var Sd=Gt(),br=mn(),lIe=$g();function cIe(r){return r instanceof br.Alternative||r instanceof br.Option||r instanceof br.Repetition||r instanceof br.RepetitionMandatory||r instanceof br.RepetitionMandatoryWithSeparator||r instanceof br.RepetitionWithSeparator||r instanceof br.Terminal||r instanceof br.Rule}Mi.isSequenceProd=cIe;function Yv(r,e){e===void 0&&(e=[]);var t=r instanceof br.Option||r instanceof br.Repetition||r instanceof br.RepetitionWithSeparator;return t?!0:r instanceof br.Alternation?(0,Sd.some)(r.definition,function(i){return Yv(i,e)}):r instanceof br.NonTerminal&&(0,Sd.contains)(e,r)?!1:r instanceof br.AbstractProduction?(r instanceof br.NonTerminal&&e.push(r),(0,Sd.every)(r.definition,function(i){return Yv(i,e)})):!1}Mi.isOptionalProd=Yv;function uIe(r){return r instanceof br.Alternation}Mi.isBranchingProd=uIe;function gIe(r){if(r instanceof br.NonTerminal)return"SUBRULE";if(r instanceof br.Option)return"OPTION";if(r instanceof br.Alternation)return"OR";if(r instanceof br.RepetitionMandatory)return"AT_LEAST_ONE";if(r instanceof br.RepetitionMandatoryWithSeparator)return"AT_LEAST_ONE_SEP";if(r instanceof br.RepetitionWithSeparator)return"MANY_SEP";if(r instanceof br.Repetition)return"MANY";if(r instanceof br.Terminal)return"CONSUME";throw Error("non exhaustive match")}Mi.getProductionDslName=gIe;var Lj=function(r){AIe(e,r);function e(){var t=r!==null&&r.apply(this,arguments)||this;return t.separator="-",t.dslMethods={option:[],alternation:[],repetition:[],repetitionWithSeparator:[],repetitionMandatory:[],repetitionMandatoryWithSeparator:[]},t}return e.prototype.reset=function(){this.dslMethods={option:[],alternation:[],repetition:[],repetitionWithSeparator:[],repetitionMandatory:[],repetitionMandatoryWithSeparator:[]}},e.prototype.visitTerminal=function(t){var i=t.terminalType.name+this.separator+"Terminal";(0,Sd.has)(this.dslMethods,i)||(this.dslMethods[i]=[]),this.dslMethods[i].push(t)},e.prototype.visitNonTerminal=function(t){var i=t.nonTerminalName+this.separator+"Terminal";(0,Sd.has)(this.dslMethods,i)||(this.dslMethods[i]=[]),this.dslMethods[i].push(t)},e.prototype.visitOption=function(t){this.dslMethods.option.push(t)},e.prototype.visitRepetitionWithSeparator=function(t){this.dslMethods.repetitionWithSeparator.push(t)},e.prototype.visitRepetitionMandatory=function(t){this.dslMethods.repetitionMandatory.push(t)},e.prototype.visitRepetitionMandatoryWithSeparator=function(t){this.dslMethods.repetitionMandatoryWithSeparator.push(t)},e.prototype.visitRepetition=function(t){this.dslMethods.repetition.push(t)},e.prototype.visitAlternation=function(t){this.dslMethods.alternation.push(t)},e}(lIe.GAstVisitor);Mi.DslMethodsCollectorVisitor=Lj;var cy=new Lj;function fIe(r){cy.reset(),r.accept(cy);var e=cy.dslMethods;return cy.reset(),e}Mi.collectMethods=fIe});var qv=w(No=>{"use strict";Object.defineProperty(No,"__esModule",{value:!0});No.firstForTerminal=No.firstForBranching=No.firstForSequence=No.first=void 0;var uy=Gt(),Oj=mn(),jv=vd();function gy(r){if(r instanceof Oj.NonTerminal)return gy(r.referencedRule);if(r instanceof Oj.Terminal)return Uj(r);if((0,jv.isSequenceProd)(r))return Mj(r);if((0,jv.isBranchingProd)(r))return Kj(r);throw Error("non exhaustive match")}No.first=gy;function Mj(r){for(var e=[],t=r.definition,i=0,n=t.length>i,s,o=!0;n&&o;)s=t[i],o=(0,jv.isOptionalProd)(s),e=e.concat(gy(s)),i=i+1,n=t.length>i;return(0,uy.uniq)(e)}No.firstForSequence=Mj;function Kj(r){var e=(0,uy.map)(r.definition,function(t){return gy(t)});return(0,uy.uniq)((0,uy.flatten)(e))}No.firstForBranching=Kj;function Uj(r){return[r.terminalType]}No.firstForTerminal=Uj});var Jv=w(fy=>{"use strict";Object.defineProperty(fy,"__esModule",{value:!0});fy.IN=void 0;fy.IN="_~IN~_"});var qj=w(fs=>{"use strict";var hIe=fs&&fs.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(fs,"__esModule",{value:!0});fs.buildInProdFollowPrefix=fs.buildBetweenProdsFollowPrefix=fs.computeAllProdsFollows=fs.ResyncFollowsWalker=void 0;var pIe=Ay(),dIe=qv(),Hj=Gt(),Gj=Jv(),CIe=mn(),Yj=function(r){hIe(e,r);function e(t){var i=r.call(this)||this;return i.topProd=t,i.follows={},i}return e.prototype.startWalking=function(){return this.walk(this.topProd),this.follows},e.prototype.walkTerminal=function(t,i,n){},e.prototype.walkProdRef=function(t,i,n){var s=jj(t.referencedRule,t.idx)+this.topProd.name,o=i.concat(n),a=new CIe.Alternative({definition:o}),l=(0,dIe.first)(a);this.follows[s]=l},e}(pIe.RestWalker);fs.ResyncFollowsWalker=Yj;function mIe(r){var e={};return(0,Hj.forEach)(r,function(t){var i=new Yj(t).startWalking();(0,Hj.assign)(e,i)}),e}fs.computeAllProdsFollows=mIe;function jj(r,e){return r.name+e+Gj.IN}fs.buildBetweenProdsFollowPrefix=jj;function EIe(r){var e=r.terminalType.name;return e+r.idx+Gj.IN}fs.buildInProdFollowPrefix=EIe});var xd=w(Da=>{"use strict";Object.defineProperty(Da,"__esModule",{value:!0});Da.defaultGrammarValidatorErrorProvider=Da.defaultGrammarResolverErrorProvider=Da.defaultParserErrorProvider=void 0;var ef=TA(),IIe=Gt(),eo=Gt(),Wv=mn(),Jj=vd();Da.defaultParserErrorProvider={buildMismatchTokenMessage:function(r){var e=r.expected,t=r.actual,i=r.previous,n=r.ruleName,s=(0,ef.hasTokenLabel)(e),o=s?"--> "+(0,ef.tokenLabel)(e)+" <--":"token of type --> "+e.name+" <--",a="Expecting "+o+" but found --> '"+t.image+"' <--";return a},buildNotAllInputParsedMessage:function(r){var e=r.firstRedundant,t=r.ruleName;return"Redundant input, expecting EOF but found: "+e.image},buildNoViableAltMessage:function(r){var e=r.expectedPathsPerAlt,t=r.actual,i=r.previous,n=r.customUserDescription,s=r.ruleName,o="Expecting: ",a=(0,eo.first)(t).image,l=` but found: '`+a+"'";if(n)return o+n+l;var c=(0,eo.reduce)(e,function(h,p){return h.concat(p)},[]),u=(0,eo.map)(c,function(h){return"["+(0,eo.map)(h,function(p){return(0,ef.tokenLabel)(p)}).join(", ")+"]"}),g=(0,eo.map)(u,function(h,p){return" "+(p+1)+". "+h}),f=`one of these possible Token sequences: `+g.join(` `);return o+f+l},buildEarlyExitMessage:function(r){var e=r.expectedIterationPaths,t=r.actual,i=r.customUserDescription,n=r.ruleName,s="Expecting: ",o=(0,eo.first)(t).image,a=` but found: '`+o+"'";if(i)return s+i+a;var l=(0,eo.map)(e,function(u){return"["+(0,eo.map)(u,function(g){return(0,ef.tokenLabel)(g)}).join(",")+"]"}),c=`expecting at least one iteration which starts with one of these possible Token sequences:: `+("<"+l.join(" ,")+">");return s+c+a}};Object.freeze(Da.defaultParserErrorProvider);Da.defaultGrammarResolverErrorProvider={buildRuleNotFoundError:function(r,e){var t="Invalid grammar, reference to a rule which is not defined: ->"+e.nonTerminalName+`<- inside top level rule: ->`+r.name+"<-";return t}};Da.defaultGrammarValidatorErrorProvider={buildDuplicateFoundError:function(r,e){function t(u){return u instanceof Wv.Terminal?u.terminalType.name:u instanceof Wv.NonTerminal?u.nonTerminalName:""}var i=r.name,n=(0,eo.first)(e),s=n.idx,o=(0,Jj.getProductionDslName)(n),a=t(n),l=s>0,c="->"+o+(l?s:"")+"<- "+(a?"with argument: ->"+a+"<-":"")+` appears more than once (`+e.length+" times) in the top level rule: ->"+i+`<-. For further details see: https://chevrotain.io/docs/FAQ.html#NUMERICAL_SUFFIXES `;return c=c.replace(/[ \t]+/g," "),c=c.replace(/\s\s+/g,` `),c},buildNamespaceConflictError:function(r){var e=`Namespace conflict found in grammar. `+("The grammar has both a Terminal(Token) and a Non-Terminal(Rule) named: <"+r.name+`>. `)+`To resolve this make sure each Terminal and Non-Terminal names are unique This is easy to accomplish by using the convention that Terminal names start with an uppercase letter and Non-Terminal names start with a lower case letter.`;return e},buildAlternationPrefixAmbiguityError:function(r){var e=(0,eo.map)(r.prefixPath,function(n){return(0,ef.tokenLabel)(n)}).join(", "),t=r.alternation.idx===0?"":r.alternation.idx,i="Ambiguous alternatives: <"+r.ambiguityIndices.join(" ,")+`> due to common lookahead prefix `+("in inside <"+r.topLevelRule.name+`> Rule, `)+("<"+e+`> may appears as a prefix path in all these alternatives. `)+`See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#COMMON_PREFIX For Further details.`;return i},buildAlternationAmbiguityError:function(r){var e=(0,eo.map)(r.prefixPath,function(n){return(0,ef.tokenLabel)(n)}).join(", "),t=r.alternation.idx===0?"":r.alternation.idx,i="Ambiguous Alternatives Detected: <"+r.ambiguityIndices.join(" ,")+"> in "+(" inside <"+r.topLevelRule.name+`> Rule, `)+("<"+e+`> may appears as a prefix path in all these alternatives. `);return i=i+`See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#AMBIGUOUS_ALTERNATIVES For Further details.`,i},buildEmptyRepetitionError:function(r){var e=(0,Jj.getProductionDslName)(r.repetition);r.repetition.idx!==0&&(e+=r.repetition.idx);var t="The repetition <"+e+"> within Rule <"+r.topLevelRule.name+`> can never consume any tokens. This could lead to an infinite loop.`;return t},buildTokenNameError:function(r){return"deprecated"},buildEmptyAlternationError:function(r){var e="Ambiguous empty alternative: <"+(r.emptyChoiceIdx+1)+">"+(" in inside <"+r.topLevelRule.name+`> Rule. `)+"Only the last alternative may be an empty alternative.";return e},buildTooManyAlternativesError:function(r){var e=`An Alternation cannot have more than 256 alternatives: `+(" inside <"+r.topLevelRule.name+`> Rule. has `+(r.alternation.definition.length+1)+" alternatives.");return e},buildLeftRecursionError:function(r){var e=r.topLevelRule.name,t=IIe.map(r.leftRecursionPath,function(s){return s.name}),i=e+" --> "+t.concat([e]).join(" --> "),n=`Left Recursion found in grammar. `+("rule: <"+e+`> can be invoked from itself (directly or indirectly) `)+(`without consuming any Tokens. The grammar path that causes this is: `+i+` `)+` To fix this refactor your grammar to remove the left recursion. see: https://en.wikipedia.org/wiki/LL_parser#Left_Factoring.`;return n},buildInvalidRuleNameError:function(r){return"deprecated"},buildDuplicateRuleNameError:function(r){var e;r.topLevelRule instanceof Wv.Rule?e=r.topLevelRule.name:e=r.topLevelRule;var t="Duplicate definition, rule: ->"+e+"<- is already defined in the grammar: ->"+r.grammarName+"<-";return t}}});var Vj=w(LA=>{"use strict";var yIe=LA&&LA.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(LA,"__esModule",{value:!0});LA.GastRefResolverVisitor=LA.resolveGrammar=void 0;var wIe=jn(),Wj=Gt(),BIe=$g();function bIe(r,e){var t=new zj(r,e);return t.resolveRefs(),t.errors}LA.resolveGrammar=bIe;var zj=function(r){yIe(e,r);function e(t,i){var n=r.call(this)||this;return n.nameToTopRule=t,n.errMsgProvider=i,n.errors=[],n}return e.prototype.resolveRefs=function(){var t=this;(0,Wj.forEach)((0,Wj.values)(this.nameToTopRule),function(i){t.currTopLevel=i,i.accept(t)})},e.prototype.visitNonTerminal=function(t){var i=this.nameToTopRule[t.nonTerminalName];if(i)t.referencedRule=i;else{var n=this.errMsgProvider.buildRuleNotFoundError(this.currTopLevel,t);this.errors.push({message:n,type:wIe.ParserDefinitionErrorType.UNRESOLVED_SUBRULE_REF,ruleName:this.currTopLevel.name,unresolvedRefName:t.nonTerminalName})}},e}(BIe.GAstVisitor);LA.GastRefResolverVisitor=zj});var Dd=w(Nr=>{"use strict";var mc=Nr&&Nr.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(Nr,"__esModule",{value:!0});Nr.nextPossibleTokensAfter=Nr.possiblePathsFrom=Nr.NextTerminalAfterAtLeastOneSepWalker=Nr.NextTerminalAfterAtLeastOneWalker=Nr.NextTerminalAfterManySepWalker=Nr.NextTerminalAfterManyWalker=Nr.AbstractNextTerminalAfterProductionWalker=Nr.NextAfterTokenWalker=Nr.AbstractNextPossibleTokensWalker=void 0;var Xj=Ay(),Kt=Gt(),QIe=qv(),kt=mn(),Zj=function(r){mc(e,r);function e(t,i){var n=r.call(this)||this;return n.topProd=t,n.path=i,n.possibleTokTypes=[],n.nextProductionName="",n.nextProductionOccurrence=0,n.found=!1,n.isAtEndOfPath=!1,n}return e.prototype.startWalking=function(){if(this.found=!1,this.path.ruleStack[0]!==this.topProd.name)throw Error("The path does not start with the walker's top Rule!");return this.ruleStack=(0,Kt.cloneArr)(this.path.ruleStack).reverse(),this.occurrenceStack=(0,Kt.cloneArr)(this.path.occurrenceStack).reverse(),this.ruleStack.pop(),this.occurrenceStack.pop(),this.updateExpectedNext(),this.walk(this.topProd),this.possibleTokTypes},e.prototype.walk=function(t,i){i===void 0&&(i=[]),this.found||r.prototype.walk.call(this,t,i)},e.prototype.walkProdRef=function(t,i,n){if(t.referencedRule.name===this.nextProductionName&&t.idx===this.nextProductionOccurrence){var s=i.concat(n);this.updateExpectedNext(),this.walk(t.referencedRule,s)}},e.prototype.updateExpectedNext=function(){(0,Kt.isEmpty)(this.ruleStack)?(this.nextProductionName="",this.nextProductionOccurrence=0,this.isAtEndOfPath=!0):(this.nextProductionName=this.ruleStack.pop(),this.nextProductionOccurrence=this.occurrenceStack.pop())},e}(Xj.RestWalker);Nr.AbstractNextPossibleTokensWalker=Zj;var SIe=function(r){mc(e,r);function e(t,i){var n=r.call(this,t,i)||this;return n.path=i,n.nextTerminalName="",n.nextTerminalOccurrence=0,n.nextTerminalName=n.path.lastTok.name,n.nextTerminalOccurrence=n.path.lastTokOccurrence,n}return e.prototype.walkTerminal=function(t,i,n){if(this.isAtEndOfPath&&t.terminalType.name===this.nextTerminalName&&t.idx===this.nextTerminalOccurrence&&!this.found){var s=i.concat(n),o=new kt.Alternative({definition:s});this.possibleTokTypes=(0,QIe.first)(o),this.found=!0}},e}(Zj);Nr.NextAfterTokenWalker=SIe;var Pd=function(r){mc(e,r);function e(t,i){var n=r.call(this)||this;return n.topRule=t,n.occurrence=i,n.result={token:void 0,occurrence:void 0,isEndOfRule:void 0},n}return e.prototype.startWalking=function(){return this.walk(this.topRule),this.result},e}(Xj.RestWalker);Nr.AbstractNextTerminalAfterProductionWalker=Pd;var vIe=function(r){mc(e,r);function e(){return r!==null&&r.apply(this,arguments)||this}return e.prototype.walkMany=function(t,i,n){if(t.idx===this.occurrence){var s=(0,Kt.first)(i.concat(n));this.result.isEndOfRule=s===void 0,s instanceof kt.Terminal&&(this.result.token=s.terminalType,this.result.occurrence=s.idx)}else r.prototype.walkMany.call(this,t,i,n)},e}(Pd);Nr.NextTerminalAfterManyWalker=vIe;var xIe=function(r){mc(e,r);function e(){return r!==null&&r.apply(this,arguments)||this}return e.prototype.walkManySep=function(t,i,n){if(t.idx===this.occurrence){var s=(0,Kt.first)(i.concat(n));this.result.isEndOfRule=s===void 0,s instanceof kt.Terminal&&(this.result.token=s.terminalType,this.result.occurrence=s.idx)}else r.prototype.walkManySep.call(this,t,i,n)},e}(Pd);Nr.NextTerminalAfterManySepWalker=xIe;var PIe=function(r){mc(e,r);function e(){return r!==null&&r.apply(this,arguments)||this}return e.prototype.walkAtLeastOne=function(t,i,n){if(t.idx===this.occurrence){var s=(0,Kt.first)(i.concat(n));this.result.isEndOfRule=s===void 0,s instanceof kt.Terminal&&(this.result.token=s.terminalType,this.result.occurrence=s.idx)}else r.prototype.walkAtLeastOne.call(this,t,i,n)},e}(Pd);Nr.NextTerminalAfterAtLeastOneWalker=PIe;var DIe=function(r){mc(e,r);function e(){return r!==null&&r.apply(this,arguments)||this}return e.prototype.walkAtLeastOneSep=function(t,i,n){if(t.idx===this.occurrence){var s=(0,Kt.first)(i.concat(n));this.result.isEndOfRule=s===void 0,s instanceof kt.Terminal&&(this.result.token=s.terminalType,this.result.occurrence=s.idx)}else r.prototype.walkAtLeastOneSep.call(this,t,i,n)},e}(Pd);Nr.NextTerminalAfterAtLeastOneSepWalker=DIe;function _j(r,e,t){t===void 0&&(t=[]),t=(0,Kt.cloneArr)(t);var i=[],n=0;function s(c){return c.concat((0,Kt.drop)(r,n+1))}function o(c){var u=_j(s(c),e,t);return i.concat(u)}for(;t.length=0;ge--){var re=B.definition[ge],O={idx:p,def:re.definition.concat((0,Kt.drop)(h)),ruleStack:C,occurrenceStack:y};g.push(O),g.push(o)}else if(B instanceof kt.Alternative)g.push({idx:p,def:B.definition.concat((0,Kt.drop)(h)),ruleStack:C,occurrenceStack:y});else if(B instanceof kt.Rule)g.push(RIe(B,p,C,y));else throw Error("non exhaustive match")}}return u}Nr.nextPossibleTokensAfter=kIe;function RIe(r,e,t,i){var n=(0,Kt.cloneArr)(t);n.push(r.name);var s=(0,Kt.cloneArr)(i);return s.push(1),{idx:e,def:r.definition,ruleStack:n,occurrenceStack:s}}});var kd=w(Zt=>{"use strict";var tq=Zt&&Zt.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(Zt,"__esModule",{value:!0});Zt.areTokenCategoriesNotUsed=Zt.isStrictPrefixOfPath=Zt.containsPath=Zt.getLookaheadPathsForOptionalProd=Zt.getLookaheadPathsForOr=Zt.lookAheadSequenceFromAlternatives=Zt.buildSingleAlternativeLookaheadFunction=Zt.buildAlternativesLookAheadFunc=Zt.buildLookaheadFuncForOptionalProd=Zt.buildLookaheadFuncForOr=Zt.getProdType=Zt.PROD_TYPE=void 0;var sr=Gt(),$j=Dd(),FIe=Ay(),hy=_g(),OA=mn(),NIe=$g(),oi;(function(r){r[r.OPTION=0]="OPTION",r[r.REPETITION=1]="REPETITION",r[r.REPETITION_MANDATORY=2]="REPETITION_MANDATORY",r[r.REPETITION_MANDATORY_WITH_SEPARATOR=3]="REPETITION_MANDATORY_WITH_SEPARATOR",r[r.REPETITION_WITH_SEPARATOR=4]="REPETITION_WITH_SEPARATOR",r[r.ALTERNATION=5]="ALTERNATION"})(oi=Zt.PROD_TYPE||(Zt.PROD_TYPE={}));function TIe(r){if(r instanceof OA.Option)return oi.OPTION;if(r instanceof OA.Repetition)return oi.REPETITION;if(r instanceof OA.RepetitionMandatory)return oi.REPETITION_MANDATORY;if(r instanceof OA.RepetitionMandatoryWithSeparator)return oi.REPETITION_MANDATORY_WITH_SEPARATOR;if(r instanceof OA.RepetitionWithSeparator)return oi.REPETITION_WITH_SEPARATOR;if(r instanceof OA.Alternation)return oi.ALTERNATION;throw Error("non exhaustive match")}Zt.getProdType=TIe;function LIe(r,e,t,i,n,s){var o=iq(r,e,t),a=Xv(o)?hy.tokenStructuredMatcherNoCategories:hy.tokenStructuredMatcher;return s(o,i,a,n)}Zt.buildLookaheadFuncForOr=LIe;function OIe(r,e,t,i,n,s){var o=nq(r,e,n,t),a=Xv(o)?hy.tokenStructuredMatcherNoCategories:hy.tokenStructuredMatcher;return s(o[0],a,i)}Zt.buildLookaheadFuncForOptionalProd=OIe;function MIe(r,e,t,i){var n=r.length,s=(0,sr.every)(r,function(l){return(0,sr.every)(l,function(c){return c.length===1})});if(e)return function(l){for(var c=(0,sr.map)(l,function(D){return D.GATE}),u=0;u{"use strict";var Zv=Vt&&Vt.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(Vt,"__esModule",{value:!0});Vt.checkPrefixAlternativesAmbiguities=Vt.validateSomeNonEmptyLookaheadPath=Vt.validateTooManyAlts=Vt.RepetionCollector=Vt.validateAmbiguousAlternationAlternatives=Vt.validateEmptyOrAlternative=Vt.getFirstNoneTerminal=Vt.validateNoLeftRecursion=Vt.validateRuleIsOverridden=Vt.validateRuleDoesNotAlreadyExist=Vt.OccurrenceValidationCollector=Vt.identifyProductionForDuplicates=Vt.validateGrammar=void 0;var er=Gt(),Qr=Gt(),To=jn(),_v=vd(),tf=kd(),YIe=Dd(),to=mn(),$v=$g();function jIe(r,e,t,i,n){var s=er.map(r,function(h){return qIe(h,i)}),o=er.map(r,function(h){return ex(h,h,i)}),a=[],l=[],c=[];(0,Qr.every)(o,Qr.isEmpty)&&(a=(0,Qr.map)(r,function(h){return cq(h,i)}),l=(0,Qr.map)(r,function(h){return uq(h,e,i)}),c=hq(r,e,i));var u=zIe(r,t,i),g=(0,Qr.map)(r,function(h){return fq(h,i)}),f=(0,Qr.map)(r,function(h){return lq(h,r,n,i)});return er.flatten(s.concat(c,o,a,l,u,g,f))}Vt.validateGrammar=jIe;function qIe(r,e){var t=new Aq;r.accept(t);var i=t.allProductions,n=er.groupBy(i,oq),s=er.pick(n,function(a){return a.length>1}),o=er.map(er.values(s),function(a){var l=er.first(a),c=e.buildDuplicateFoundError(r,a),u=(0,_v.getProductionDslName)(l),g={message:c,type:To.ParserDefinitionErrorType.DUPLICATE_PRODUCTIONS,ruleName:r.name,dslName:u,occurrence:l.idx},f=aq(l);return f&&(g.parameter=f),g});return o}function oq(r){return(0,_v.getProductionDslName)(r)+"_#_"+r.idx+"_#_"+aq(r)}Vt.identifyProductionForDuplicates=oq;function aq(r){return r instanceof to.Terminal?r.terminalType.name:r instanceof to.NonTerminal?r.nonTerminalName:""}var Aq=function(r){Zv(e,r);function e(){var t=r!==null&&r.apply(this,arguments)||this;return t.allProductions=[],t}return e.prototype.visitNonTerminal=function(t){this.allProductions.push(t)},e.prototype.visitOption=function(t){this.allProductions.push(t)},e.prototype.visitRepetitionWithSeparator=function(t){this.allProductions.push(t)},e.prototype.visitRepetitionMandatory=function(t){this.allProductions.push(t)},e.prototype.visitRepetitionMandatoryWithSeparator=function(t){this.allProductions.push(t)},e.prototype.visitRepetition=function(t){this.allProductions.push(t)},e.prototype.visitAlternation=function(t){this.allProductions.push(t)},e.prototype.visitTerminal=function(t){this.allProductions.push(t)},e}($v.GAstVisitor);Vt.OccurrenceValidationCollector=Aq;function lq(r,e,t,i){var n=[],s=(0,Qr.reduce)(e,function(a,l){return l.name===r.name?a+1:a},0);if(s>1){var o=i.buildDuplicateRuleNameError({topLevelRule:r,grammarName:t});n.push({message:o,type:To.ParserDefinitionErrorType.DUPLICATE_RULE_NAME,ruleName:r.name})}return n}Vt.validateRuleDoesNotAlreadyExist=lq;function JIe(r,e,t){var i=[],n;return er.contains(e,r)||(n="Invalid rule override, rule: ->"+r+"<- cannot be overridden in the grammar: ->"+t+"<-as it is not defined in any of the super grammars ",i.push({message:n,type:To.ParserDefinitionErrorType.INVALID_RULE_OVERRIDE,ruleName:r})),i}Vt.validateRuleIsOverridden=JIe;function ex(r,e,t,i){i===void 0&&(i=[]);var n=[],s=Rd(e.definition);if(er.isEmpty(s))return[];var o=r.name,a=er.contains(s,r);a&&n.push({message:t.buildLeftRecursionError({topLevelRule:r,leftRecursionPath:i}),type:To.ParserDefinitionErrorType.LEFT_RECURSION,ruleName:o});var l=er.difference(s,i.concat([r])),c=er.map(l,function(u){var g=er.cloneArr(i);return g.push(u),ex(r,u,t,g)});return n.concat(er.flatten(c))}Vt.validateNoLeftRecursion=ex;function Rd(r){var e=[];if(er.isEmpty(r))return e;var t=er.first(r);if(t instanceof to.NonTerminal)e.push(t.referencedRule);else if(t instanceof to.Alternative||t instanceof to.Option||t instanceof to.RepetitionMandatory||t instanceof to.RepetitionMandatoryWithSeparator||t instanceof to.RepetitionWithSeparator||t instanceof to.Repetition)e=e.concat(Rd(t.definition));else if(t instanceof to.Alternation)e=er.flatten(er.map(t.definition,function(o){return Rd(o.definition)}));else if(!(t instanceof to.Terminal))throw Error("non exhaustive match");var i=(0,_v.isOptionalProd)(t),n=r.length>1;if(i&&n){var s=er.drop(r);return e.concat(Rd(s))}else return e}Vt.getFirstNoneTerminal=Rd;var tx=function(r){Zv(e,r);function e(){var t=r!==null&&r.apply(this,arguments)||this;return t.alternations=[],t}return e.prototype.visitAlternation=function(t){this.alternations.push(t)},e}($v.GAstVisitor);function cq(r,e){var t=new tx;r.accept(t);var i=t.alternations,n=er.reduce(i,function(s,o){var a=er.dropRight(o.definition),l=er.map(a,function(c,u){var g=(0,YIe.nextPossibleTokensAfter)([c],[],null,1);return er.isEmpty(g)?{message:e.buildEmptyAlternationError({topLevelRule:r,alternation:o,emptyChoiceIdx:u}),type:To.ParserDefinitionErrorType.NONE_LAST_EMPTY_ALT,ruleName:r.name,occurrence:o.idx,alternative:u+1}:null});return s.concat(er.compact(l))},[]);return n}Vt.validateEmptyOrAlternative=cq;function uq(r,e,t){var i=new tx;r.accept(i);var n=i.alternations;n=(0,Qr.reject)(n,function(o){return o.ignoreAmbiguities===!0});var s=er.reduce(n,function(o,a){var l=a.idx,c=a.maxLookahead||e,u=(0,tf.getLookaheadPathsForOr)(l,r,c,a),g=WIe(u,a,r,t),f=pq(u,a,r,t);return o.concat(g,f)},[]);return s}Vt.validateAmbiguousAlternationAlternatives=uq;var gq=function(r){Zv(e,r);function e(){var t=r!==null&&r.apply(this,arguments)||this;return t.allProductions=[],t}return e.prototype.visitRepetitionWithSeparator=function(t){this.allProductions.push(t)},e.prototype.visitRepetitionMandatory=function(t){this.allProductions.push(t)},e.prototype.visitRepetitionMandatoryWithSeparator=function(t){this.allProductions.push(t)},e.prototype.visitRepetition=function(t){this.allProductions.push(t)},e}($v.GAstVisitor);Vt.RepetionCollector=gq;function fq(r,e){var t=new tx;r.accept(t);var i=t.alternations,n=er.reduce(i,function(s,o){return o.definition.length>255&&s.push({message:e.buildTooManyAlternativesError({topLevelRule:r,alternation:o}),type:To.ParserDefinitionErrorType.TOO_MANY_ALTS,ruleName:r.name,occurrence:o.idx}),s},[]);return n}Vt.validateTooManyAlts=fq;function hq(r,e,t){var i=[];return(0,Qr.forEach)(r,function(n){var s=new gq;n.accept(s);var o=s.allProductions;(0,Qr.forEach)(o,function(a){var l=(0,tf.getProdType)(a),c=a.maxLookahead||e,u=a.idx,g=(0,tf.getLookaheadPathsForOptionalProd)(u,n,l,c),f=g[0];if((0,Qr.isEmpty)((0,Qr.flatten)(f))){var h=t.buildEmptyRepetitionError({topLevelRule:n,repetition:a});i.push({message:h,type:To.ParserDefinitionErrorType.NO_NON_EMPTY_LOOKAHEAD,ruleName:n.name})}})}),i}Vt.validateSomeNonEmptyLookaheadPath=hq;function WIe(r,e,t,i){var n=[],s=(0,Qr.reduce)(r,function(a,l,c){return e.definition[c].ignoreAmbiguities===!0||(0,Qr.forEach)(l,function(u){var g=[c];(0,Qr.forEach)(r,function(f,h){c!==h&&(0,tf.containsPath)(f,u)&&e.definition[h].ignoreAmbiguities!==!0&&g.push(h)}),g.length>1&&!(0,tf.containsPath)(n,u)&&(n.push(u),a.push({alts:g,path:u}))}),a},[]),o=er.map(s,function(a){var l=(0,Qr.map)(a.alts,function(u){return u+1}),c=i.buildAlternationAmbiguityError({topLevelRule:t,alternation:e,ambiguityIndices:l,prefixPath:a.path});return{message:c,type:To.ParserDefinitionErrorType.AMBIGUOUS_ALTS,ruleName:t.name,occurrence:e.idx,alternatives:[a.alts]}});return o}function pq(r,e,t,i){var n=[],s=(0,Qr.reduce)(r,function(o,a,l){var c=(0,Qr.map)(a,function(u){return{idx:l,path:u}});return o.concat(c)},[]);return(0,Qr.forEach)(s,function(o){var a=e.definition[o.idx];if(a.ignoreAmbiguities!==!0){var l=o.idx,c=o.path,u=(0,Qr.findAll)(s,function(f){return e.definition[f.idx].ignoreAmbiguities!==!0&&f.idx{"use strict";Object.defineProperty(rf,"__esModule",{value:!0});rf.validateGrammar=rf.resolveGrammar=void 0;var ix=Gt(),VIe=Vj(),XIe=rx(),dq=xd();function ZIe(r){r=(0,ix.defaults)(r,{errMsgProvider:dq.defaultGrammarResolverErrorProvider});var e={};return(0,ix.forEach)(r.rules,function(t){e[t.name]=t}),(0,VIe.resolveGrammar)(e,r.errMsgProvider)}rf.resolveGrammar=ZIe;function _Ie(r){return r=(0,ix.defaults)(r,{errMsgProvider:dq.defaultGrammarValidatorErrorProvider}),(0,XIe.validateGrammar)(r.rules,r.maxLookahead,r.tokenTypes,r.errMsgProvider,r.grammarName)}rf.validateGrammar=_Ie});var nf=w(In=>{"use strict";var Fd=In&&In.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(In,"__esModule",{value:!0});In.EarlyExitException=In.NotAllInputParsedException=In.NoViableAltException=In.MismatchedTokenException=In.isRecognitionException=void 0;var $Ie=Gt(),mq="MismatchedTokenException",Eq="NoViableAltException",Iq="EarlyExitException",yq="NotAllInputParsedException",wq=[mq,Eq,Iq,yq];Object.freeze(wq);function eye(r){return(0,$Ie.contains)(wq,r.name)}In.isRecognitionException=eye;var py=function(r){Fd(e,r);function e(t,i){var n=this.constructor,s=r.call(this,t)||this;return s.token=i,s.resyncedTokens=[],Object.setPrototypeOf(s,n.prototype),Error.captureStackTrace&&Error.captureStackTrace(s,s.constructor),s}return e}(Error),tye=function(r){Fd(e,r);function e(t,i,n){var s=r.call(this,t,i)||this;return s.previousToken=n,s.name=mq,s}return e}(py);In.MismatchedTokenException=tye;var rye=function(r){Fd(e,r);function e(t,i,n){var s=r.call(this,t,i)||this;return s.previousToken=n,s.name=Eq,s}return e}(py);In.NoViableAltException=rye;var iye=function(r){Fd(e,r);function e(t,i){var n=r.call(this,t,i)||this;return n.name=yq,n}return e}(py);In.NotAllInputParsedException=iye;var nye=function(r){Fd(e,r);function e(t,i,n){var s=r.call(this,t,i)||this;return s.previousToken=n,s.name=Iq,s}return e}(py);In.EarlyExitException=nye});var sx=w(Ki=>{"use strict";Object.defineProperty(Ki,"__esModule",{value:!0});Ki.attemptInRepetitionRecovery=Ki.Recoverable=Ki.InRuleRecoveryException=Ki.IN_RULE_RECOVERY_EXCEPTION=Ki.EOF_FOLLOW_KEY=void 0;var dy=TA(),hs=Gt(),sye=nf(),oye=Jv(),aye=jn();Ki.EOF_FOLLOW_KEY={};Ki.IN_RULE_RECOVERY_EXCEPTION="InRuleRecoveryException";function nx(r){this.name=Ki.IN_RULE_RECOVERY_EXCEPTION,this.message=r}Ki.InRuleRecoveryException=nx;nx.prototype=Error.prototype;var Aye=function(){function r(){}return r.prototype.initRecoverable=function(e){this.firstAfterRepMap={},this.resyncFollows={},this.recoveryEnabled=(0,hs.has)(e,"recoveryEnabled")?e.recoveryEnabled:aye.DEFAULT_PARSER_CONFIG.recoveryEnabled,this.recoveryEnabled&&(this.attemptInRepetitionRecovery=Bq)},r.prototype.getTokenToInsert=function(e){var t=(0,dy.createTokenInstance)(e,"",NaN,NaN,NaN,NaN,NaN,NaN);return t.isInsertedInRecovery=!0,t},r.prototype.canTokenTypeBeInsertedInRecovery=function(e){return!0},r.prototype.tryInRepetitionRecovery=function(e,t,i,n){for(var s=this,o=this.findReSyncTokenType(),a=this.exportLexerState(),l=[],c=!1,u=this.LA(1),g=this.LA(1),f=function(){var h=s.LA(0),p=s.errorMessageProvider.buildMismatchTokenMessage({expected:n,actual:u,previous:h,ruleName:s.getCurrRuleFullName()}),C=new sye.MismatchedTokenException(p,u,s.LA(0));C.resyncedTokens=(0,hs.dropRight)(l),s.SAVE_ERROR(C)};!c;)if(this.tokenMatcher(g,n)){f();return}else if(i.call(this)){f(),e.apply(this,t);return}else this.tokenMatcher(g,o)?c=!0:(g=this.SKIP_TOKEN(),this.addToResyncTokens(g,l));this.importLexerState(a)},r.prototype.shouldInRepetitionRecoveryBeTried=function(e,t,i){return!(i===!1||e===void 0||t===void 0||this.tokenMatcher(this.LA(1),e)||this.isBackTracking()||this.canPerformInRuleRecovery(e,this.getFollowsForInRuleRecovery(e,t)))},r.prototype.getFollowsForInRuleRecovery=function(e,t){var i=this.getCurrentGrammarPath(e,t),n=this.getNextPossibleTokenTypes(i);return n},r.prototype.tryInRuleRecovery=function(e,t){if(this.canRecoverWithSingleTokenInsertion(e,t)){var i=this.getTokenToInsert(e);return i}if(this.canRecoverWithSingleTokenDeletion(e)){var n=this.SKIP_TOKEN();return this.consumeToken(),n}throw new nx("sad sad panda")},r.prototype.canPerformInRuleRecovery=function(e,t){return this.canRecoverWithSingleTokenInsertion(e,t)||this.canRecoverWithSingleTokenDeletion(e)},r.prototype.canRecoverWithSingleTokenInsertion=function(e,t){var i=this;if(!this.canTokenTypeBeInsertedInRecovery(e)||(0,hs.isEmpty)(t))return!1;var n=this.LA(1),s=(0,hs.find)(t,function(o){return i.tokenMatcher(n,o)})!==void 0;return s},r.prototype.canRecoverWithSingleTokenDeletion=function(e){var t=this.tokenMatcher(this.LA(2),e);return t},r.prototype.isInCurrentRuleReSyncSet=function(e){var t=this.getCurrFollowKey(),i=this.getFollowSetFromFollowKey(t);return(0,hs.contains)(i,e)},r.prototype.findReSyncTokenType=function(){for(var e=this.flattenFollowSet(),t=this.LA(1),i=2;;){var n=t.tokenType;if((0,hs.contains)(e,n))return n;t=this.LA(i),i++}},r.prototype.getCurrFollowKey=function(){if(this.RULE_STACK.length===1)return Ki.EOF_FOLLOW_KEY;var e=this.getLastExplicitRuleShortName(),t=this.getLastExplicitRuleOccurrenceIndex(),i=this.getPreviousExplicitRuleShortName();return{ruleName:this.shortRuleNameToFullName(e),idxInCallingRule:t,inRule:this.shortRuleNameToFullName(i)}},r.prototype.buildFullFollowKeyStack=function(){var e=this,t=this.RULE_STACK,i=this.RULE_OCCURRENCE_STACK;return(0,hs.map)(t,function(n,s){return s===0?Ki.EOF_FOLLOW_KEY:{ruleName:e.shortRuleNameToFullName(n),idxInCallingRule:i[s],inRule:e.shortRuleNameToFullName(t[s-1])}})},r.prototype.flattenFollowSet=function(){var e=this,t=(0,hs.map)(this.buildFullFollowKeyStack(),function(i){return e.getFollowSetFromFollowKey(i)});return(0,hs.flatten)(t)},r.prototype.getFollowSetFromFollowKey=function(e){if(e===Ki.EOF_FOLLOW_KEY)return[dy.EOF];var t=e.ruleName+e.idxInCallingRule+oye.IN+e.inRule;return this.resyncFollows[t]},r.prototype.addToResyncTokens=function(e,t){return this.tokenMatcher(e,dy.EOF)||t.push(e),t},r.prototype.reSyncTo=function(e){for(var t=[],i=this.LA(1);this.tokenMatcher(i,e)===!1;)i=this.SKIP_TOKEN(),this.addToResyncTokens(i,t);return(0,hs.dropRight)(t)},r.prototype.attemptInRepetitionRecovery=function(e,t,i,n,s,o,a){},r.prototype.getCurrentGrammarPath=function(e,t){var i=this.getHumanReadableRuleStack(),n=(0,hs.cloneArr)(this.RULE_OCCURRENCE_STACK),s={ruleStack:i,occurrenceStack:n,lastTok:e,lastTokOccurrence:t};return s},r.prototype.getHumanReadableRuleStack=function(){var e=this;return(0,hs.map)(this.RULE_STACK,function(t){return e.shortRuleNameToFullName(t)})},r}();Ki.Recoverable=Aye;function Bq(r,e,t,i,n,s,o){var a=this.getKeyForAutomaticLookahead(i,n),l=this.firstAfterRepMap[a];if(l===void 0){var c=this.getCurrRuleFullName(),u=this.getGAstProductions()[c],g=new s(u,n);l=g.startWalking(),this.firstAfterRepMap[a]=l}var f=l.token,h=l.occurrence,p=l.isEndOfRule;this.RULE_STACK.length===1&&p&&f===void 0&&(f=dy.EOF,h=1),this.shouldInRepetitionRecoveryBeTried(f,h,o)&&this.tryInRepetitionRecovery(r,e,t,f)}Ki.attemptInRepetitionRecovery=Bq});var Cy=w(Jt=>{"use strict";Object.defineProperty(Jt,"__esModule",{value:!0});Jt.getKeyForAutomaticLookahead=Jt.AT_LEAST_ONE_SEP_IDX=Jt.MANY_SEP_IDX=Jt.AT_LEAST_ONE_IDX=Jt.MANY_IDX=Jt.OPTION_IDX=Jt.OR_IDX=Jt.BITS_FOR_ALT_IDX=Jt.BITS_FOR_RULE_IDX=Jt.BITS_FOR_OCCURRENCE_IDX=Jt.BITS_FOR_METHOD_TYPE=void 0;Jt.BITS_FOR_METHOD_TYPE=4;Jt.BITS_FOR_OCCURRENCE_IDX=8;Jt.BITS_FOR_RULE_IDX=12;Jt.BITS_FOR_ALT_IDX=8;Jt.OR_IDX=1<{"use strict";Object.defineProperty(my,"__esModule",{value:!0});my.LooksAhead=void 0;var ka=kd(),ro=Gt(),bq=jn(),Ra=Cy(),Ec=vd(),cye=function(){function r(){}return r.prototype.initLooksAhead=function(e){this.dynamicTokensEnabled=(0,ro.has)(e,"dynamicTokensEnabled")?e.dynamicTokensEnabled:bq.DEFAULT_PARSER_CONFIG.dynamicTokensEnabled,this.maxLookahead=(0,ro.has)(e,"maxLookahead")?e.maxLookahead:bq.DEFAULT_PARSER_CONFIG.maxLookahead,this.lookAheadFuncsCache=(0,ro.isES2015MapSupported)()?new Map:[],(0,ro.isES2015MapSupported)()?(this.getLaFuncFromCache=this.getLaFuncFromMap,this.setLaFuncCache=this.setLaFuncCacheUsingMap):(this.getLaFuncFromCache=this.getLaFuncFromObj,this.setLaFuncCache=this.setLaFuncUsingObj)},r.prototype.preComputeLookaheadFunctions=function(e){var t=this;(0,ro.forEach)(e,function(i){t.TRACE_INIT(i.name+" Rule Lookahead",function(){var n=(0,Ec.collectMethods)(i),s=n.alternation,o=n.repetition,a=n.option,l=n.repetitionMandatory,c=n.repetitionMandatoryWithSeparator,u=n.repetitionWithSeparator;(0,ro.forEach)(s,function(g){var f=g.idx===0?"":g.idx;t.TRACE_INIT(""+(0,Ec.getProductionDslName)(g)+f,function(){var h=(0,ka.buildLookaheadFuncForOr)(g.idx,i,g.maxLookahead||t.maxLookahead,g.hasPredicates,t.dynamicTokensEnabled,t.lookAheadBuilderForAlternatives),p=(0,Ra.getKeyForAutomaticLookahead)(t.fullRuleNameToShort[i.name],Ra.OR_IDX,g.idx);t.setLaFuncCache(p,h)})}),(0,ro.forEach)(o,function(g){t.computeLookaheadFunc(i,g.idx,Ra.MANY_IDX,ka.PROD_TYPE.REPETITION,g.maxLookahead,(0,Ec.getProductionDslName)(g))}),(0,ro.forEach)(a,function(g){t.computeLookaheadFunc(i,g.idx,Ra.OPTION_IDX,ka.PROD_TYPE.OPTION,g.maxLookahead,(0,Ec.getProductionDslName)(g))}),(0,ro.forEach)(l,function(g){t.computeLookaheadFunc(i,g.idx,Ra.AT_LEAST_ONE_IDX,ka.PROD_TYPE.REPETITION_MANDATORY,g.maxLookahead,(0,Ec.getProductionDslName)(g))}),(0,ro.forEach)(c,function(g){t.computeLookaheadFunc(i,g.idx,Ra.AT_LEAST_ONE_SEP_IDX,ka.PROD_TYPE.REPETITION_MANDATORY_WITH_SEPARATOR,g.maxLookahead,(0,Ec.getProductionDslName)(g))}),(0,ro.forEach)(u,function(g){t.computeLookaheadFunc(i,g.idx,Ra.MANY_SEP_IDX,ka.PROD_TYPE.REPETITION_WITH_SEPARATOR,g.maxLookahead,(0,Ec.getProductionDslName)(g))})})})},r.prototype.computeLookaheadFunc=function(e,t,i,n,s,o){var a=this;this.TRACE_INIT(""+o+(t===0?"":t),function(){var l=(0,ka.buildLookaheadFuncForOptionalProd)(t,e,s||a.maxLookahead,a.dynamicTokensEnabled,n,a.lookAheadBuilderForOptional),c=(0,Ra.getKeyForAutomaticLookahead)(a.fullRuleNameToShort[e.name],i,t);a.setLaFuncCache(c,l)})},r.prototype.lookAheadBuilderForOptional=function(e,t,i){return(0,ka.buildSingleAlternativeLookaheadFunction)(e,t,i)},r.prototype.lookAheadBuilderForAlternatives=function(e,t,i,n){return(0,ka.buildAlternativesLookAheadFunc)(e,t,i,n)},r.prototype.getKeyForAutomaticLookahead=function(e,t){var i=this.getLastExplicitRuleShortName();return(0,Ra.getKeyForAutomaticLookahead)(i,e,t)},r.prototype.getLaFuncFromCache=function(e){},r.prototype.getLaFuncFromMap=function(e){return this.lookAheadFuncsCache.get(e)},r.prototype.getLaFuncFromObj=function(e){return this.lookAheadFuncsCache[e]},r.prototype.setLaFuncCache=function(e,t){},r.prototype.setLaFuncCacheUsingMap=function(e,t){this.lookAheadFuncsCache.set(e,t)},r.prototype.setLaFuncUsingObj=function(e,t){this.lookAheadFuncsCache[e]=t},r}();my.LooksAhead=cye});var Sq=w(Lo=>{"use strict";Object.defineProperty(Lo,"__esModule",{value:!0});Lo.addNoneTerminalToCst=Lo.addTerminalToCst=Lo.setNodeLocationFull=Lo.setNodeLocationOnlyOffset=void 0;function uye(r,e){isNaN(r.startOffset)===!0?(r.startOffset=e.startOffset,r.endOffset=e.endOffset):r.endOffset{"use strict";Object.defineProperty(MA,"__esModule",{value:!0});MA.defineNameProp=MA.functionName=MA.classNameFromInstance=void 0;var pye=Gt();function dye(r){return xq(r.constructor)}MA.classNameFromInstance=dye;var vq="name";function xq(r){var e=r.name;return e||"anonymous"}MA.functionName=xq;function Cye(r,e){var t=Object.getOwnPropertyDescriptor(r,vq);return(0,pye.isUndefined)(t)||t.configurable?(Object.defineProperty(r,vq,{enumerable:!1,configurable:!0,writable:!1,value:e}),!0):!1}MA.defineNameProp=Cye});var Fq=w(Si=>{"use strict";Object.defineProperty(Si,"__esModule",{value:!0});Si.validateRedundantMethods=Si.validateMissingCstMethods=Si.validateVisitor=Si.CstVisitorDefinitionError=Si.createBaseVisitorConstructorWithDefaults=Si.createBaseSemanticVisitorConstructor=Si.defaultVisit=void 0;var ps=Gt(),Nd=ox();function Pq(r,e){for(var t=(0,ps.keys)(r),i=t.length,n=0;n: `+(""+s.join(` `).replace(/\n/g,` `)))}}};return t.prototype=i,t.prototype.constructor=t,t._RULE_NAMES=e,t}Si.createBaseSemanticVisitorConstructor=mye;function Eye(r,e,t){var i=function(){};(0,Nd.defineNameProp)(i,r+"BaseSemanticsWithDefaults");var n=Object.create(t.prototype);return(0,ps.forEach)(e,function(s){n[s]=Pq}),i.prototype=n,i.prototype.constructor=i,i}Si.createBaseVisitorConstructorWithDefaults=Eye;var ax;(function(r){r[r.REDUNDANT_METHOD=0]="REDUNDANT_METHOD",r[r.MISSING_METHOD=1]="MISSING_METHOD"})(ax=Si.CstVisitorDefinitionError||(Si.CstVisitorDefinitionError={}));function Dq(r,e){var t=kq(r,e),i=Rq(r,e);return t.concat(i)}Si.validateVisitor=Dq;function kq(r,e){var t=(0,ps.map)(e,function(i){if(!(0,ps.isFunction)(r[i]))return{msg:"Missing visitor method: <"+i+"> on "+(0,Nd.functionName)(r.constructor)+" CST Visitor.",type:ax.MISSING_METHOD,methodName:i}});return(0,ps.compact)(t)}Si.validateMissingCstMethods=kq;var Iye=["constructor","visit","validateVisitor"];function Rq(r,e){var t=[];for(var i in r)(0,ps.isFunction)(r[i])&&!(0,ps.contains)(Iye,i)&&!(0,ps.contains)(e,i)&&t.push({msg:"Redundant visitor method: <"+i+"> on "+(0,Nd.functionName)(r.constructor)+` CST Visitor There is no Grammar Rule corresponding to this method's name. `,type:ax.REDUNDANT_METHOD,methodName:i});return t}Si.validateRedundantMethods=Rq});var Tq=w(Ey=>{"use strict";Object.defineProperty(Ey,"__esModule",{value:!0});Ey.TreeBuilder=void 0;var sf=Sq(),_r=Gt(),Nq=Fq(),yye=jn(),wye=function(){function r(){}return r.prototype.initTreeBuilder=function(e){if(this.CST_STACK=[],this.outputCst=e.outputCst,this.nodeLocationTracking=(0,_r.has)(e,"nodeLocationTracking")?e.nodeLocationTracking:yye.DEFAULT_PARSER_CONFIG.nodeLocationTracking,!this.outputCst)this.cstInvocationStateUpdate=_r.NOOP,this.cstFinallyStateUpdate=_r.NOOP,this.cstPostTerminal=_r.NOOP,this.cstPostNonTerminal=_r.NOOP,this.cstPostRule=_r.NOOP;else if(/full/i.test(this.nodeLocationTracking))this.recoveryEnabled?(this.setNodeLocationFromToken=sf.setNodeLocationFull,this.setNodeLocationFromNode=sf.setNodeLocationFull,this.cstPostRule=_r.NOOP,this.setInitialNodeLocation=this.setInitialNodeLocationFullRecovery):(this.setNodeLocationFromToken=_r.NOOP,this.setNodeLocationFromNode=_r.NOOP,this.cstPostRule=this.cstPostRuleFull,this.setInitialNodeLocation=this.setInitialNodeLocationFullRegular);else if(/onlyOffset/i.test(this.nodeLocationTracking))this.recoveryEnabled?(this.setNodeLocationFromToken=sf.setNodeLocationOnlyOffset,this.setNodeLocationFromNode=sf.setNodeLocationOnlyOffset,this.cstPostRule=_r.NOOP,this.setInitialNodeLocation=this.setInitialNodeLocationOnlyOffsetRecovery):(this.setNodeLocationFromToken=_r.NOOP,this.setNodeLocationFromNode=_r.NOOP,this.cstPostRule=this.cstPostRuleOnlyOffset,this.setInitialNodeLocation=this.setInitialNodeLocationOnlyOffsetRegular);else if(/none/i.test(this.nodeLocationTracking))this.setNodeLocationFromToken=_r.NOOP,this.setNodeLocationFromNode=_r.NOOP,this.cstPostRule=_r.NOOP,this.setInitialNodeLocation=_r.NOOP;else throw Error('Invalid config option: "'+e.nodeLocationTracking+'"')},r.prototype.setInitialNodeLocationOnlyOffsetRecovery=function(e){e.location={startOffset:NaN,endOffset:NaN}},r.prototype.setInitialNodeLocationOnlyOffsetRegular=function(e){e.location={startOffset:this.LA(1).startOffset,endOffset:NaN}},r.prototype.setInitialNodeLocationFullRecovery=function(e){e.location={startOffset:NaN,startLine:NaN,startColumn:NaN,endOffset:NaN,endLine:NaN,endColumn:NaN}},r.prototype.setInitialNodeLocationFullRegular=function(e){var t=this.LA(1);e.location={startOffset:t.startOffset,startLine:t.startLine,startColumn:t.startColumn,endOffset:NaN,endLine:NaN,endColumn:NaN}},r.prototype.cstInvocationStateUpdate=function(e,t){var i={name:e,children:{}};this.setInitialNodeLocation(i),this.CST_STACK.push(i)},r.prototype.cstFinallyStateUpdate=function(){this.CST_STACK.pop()},r.prototype.cstPostRuleFull=function(e){var t=this.LA(0),i=e.location;i.startOffset<=t.startOffset?(i.endOffset=t.endOffset,i.endLine=t.endLine,i.endColumn=t.endColumn):(i.startOffset=NaN,i.startLine=NaN,i.startColumn=NaN)},r.prototype.cstPostRuleOnlyOffset=function(e){var t=this.LA(0),i=e.location;i.startOffset<=t.startOffset?i.endOffset=t.endOffset:i.startOffset=NaN},r.prototype.cstPostTerminal=function(e,t){var i=this.CST_STACK[this.CST_STACK.length-1];(0,sf.addTerminalToCst)(i,t,e),this.setNodeLocationFromToken(i.location,t)},r.prototype.cstPostNonTerminal=function(e,t){var i=this.CST_STACK[this.CST_STACK.length-1];(0,sf.addNoneTerminalToCst)(i,t,e),this.setNodeLocationFromNode(i.location,e.location)},r.prototype.getBaseCstVisitorConstructor=function(){if((0,_r.isUndefined)(this.baseCstVisitorConstructor)){var e=(0,Nq.createBaseSemanticVisitorConstructor)(this.className,(0,_r.keys)(this.gastProductionsCache));return this.baseCstVisitorConstructor=e,e}return this.baseCstVisitorConstructor},r.prototype.getBaseCstVisitorConstructorWithDefaults=function(){if((0,_r.isUndefined)(this.baseCstVisitorWithDefaultsConstructor)){var e=(0,Nq.createBaseVisitorConstructorWithDefaults)(this.className,(0,_r.keys)(this.gastProductionsCache),this.getBaseCstVisitorConstructor());return this.baseCstVisitorWithDefaultsConstructor=e,e}return this.baseCstVisitorWithDefaultsConstructor},r.prototype.getLastExplicitRuleShortName=function(){var e=this.RULE_STACK;return e[e.length-1]},r.prototype.getPreviousExplicitRuleShortName=function(){var e=this.RULE_STACK;return e[e.length-2]},r.prototype.getLastExplicitRuleOccurrenceIndex=function(){var e=this.RULE_OCCURRENCE_STACK;return e[e.length-1]},r}();Ey.TreeBuilder=wye});var Oq=w(Iy=>{"use strict";Object.defineProperty(Iy,"__esModule",{value:!0});Iy.LexerAdapter=void 0;var Lq=jn(),Bye=function(){function r(){}return r.prototype.initLexerAdapter=function(){this.tokVector=[],this.tokVectorLength=0,this.currIdx=-1},Object.defineProperty(r.prototype,"input",{get:function(){return this.tokVector},set:function(e){if(this.selfAnalysisDone!==!0)throw Error("Missing invocation at the end of the Parser's constructor.");this.reset(),this.tokVector=e,this.tokVectorLength=e.length},enumerable:!1,configurable:!0}),r.prototype.SKIP_TOKEN=function(){return this.currIdx<=this.tokVector.length-2?(this.consumeToken(),this.LA(1)):Lq.END_OF_FILE},r.prototype.LA=function(e){var t=this.currIdx+e;return t<0||this.tokVectorLength<=t?Lq.END_OF_FILE:this.tokVector[t]},r.prototype.consumeToken=function(){this.currIdx++},r.prototype.exportLexerState=function(){return this.currIdx},r.prototype.importLexerState=function(e){this.currIdx=e},r.prototype.resetLexerState=function(){this.currIdx=-1},r.prototype.moveToTerminatedState=function(){this.currIdx=this.tokVector.length-1},r.prototype.getLexerPosition=function(){return this.exportLexerState()},r}();Iy.LexerAdapter=Bye});var Kq=w(yy=>{"use strict";Object.defineProperty(yy,"__esModule",{value:!0});yy.RecognizerApi=void 0;var Mq=Gt(),bye=nf(),Ax=jn(),Qye=xd(),Sye=rx(),vye=mn(),xye=function(){function r(){}return r.prototype.ACTION=function(e){return e.call(this)},r.prototype.consume=function(e,t,i){return this.consumeInternal(t,e,i)},r.prototype.subrule=function(e,t,i){return this.subruleInternal(t,e,i)},r.prototype.option=function(e,t){return this.optionInternal(t,e)},r.prototype.or=function(e,t){return this.orInternal(t,e)},r.prototype.many=function(e,t){return this.manyInternal(e,t)},r.prototype.atLeastOne=function(e,t){return this.atLeastOneInternal(e,t)},r.prototype.CONSUME=function(e,t){return this.consumeInternal(e,0,t)},r.prototype.CONSUME1=function(e,t){return this.consumeInternal(e,1,t)},r.prototype.CONSUME2=function(e,t){return this.consumeInternal(e,2,t)},r.prototype.CONSUME3=function(e,t){return this.consumeInternal(e,3,t)},r.prototype.CONSUME4=function(e,t){return this.consumeInternal(e,4,t)},r.prototype.CONSUME5=function(e,t){return this.consumeInternal(e,5,t)},r.prototype.CONSUME6=function(e,t){return this.consumeInternal(e,6,t)},r.prototype.CONSUME7=function(e,t){return this.consumeInternal(e,7,t)},r.prototype.CONSUME8=function(e,t){return this.consumeInternal(e,8,t)},r.prototype.CONSUME9=function(e,t){return this.consumeInternal(e,9,t)},r.prototype.SUBRULE=function(e,t){return this.subruleInternal(e,0,t)},r.prototype.SUBRULE1=function(e,t){return this.subruleInternal(e,1,t)},r.prototype.SUBRULE2=function(e,t){return this.subruleInternal(e,2,t)},r.prototype.SUBRULE3=function(e,t){return this.subruleInternal(e,3,t)},r.prototype.SUBRULE4=function(e,t){return this.subruleInternal(e,4,t)},r.prototype.SUBRULE5=function(e,t){return this.subruleInternal(e,5,t)},r.prototype.SUBRULE6=function(e,t){return this.subruleInternal(e,6,t)},r.prototype.SUBRULE7=function(e,t){return this.subruleInternal(e,7,t)},r.prototype.SUBRULE8=function(e,t){return this.subruleInternal(e,8,t)},r.prototype.SUBRULE9=function(e,t){return this.subruleInternal(e,9,t)},r.prototype.OPTION=function(e){return this.optionInternal(e,0)},r.prototype.OPTION1=function(e){return this.optionInternal(e,1)},r.prototype.OPTION2=function(e){return this.optionInternal(e,2)},r.prototype.OPTION3=function(e){return this.optionInternal(e,3)},r.prototype.OPTION4=function(e){return this.optionInternal(e,4)},r.prototype.OPTION5=function(e){return this.optionInternal(e,5)},r.prototype.OPTION6=function(e){return this.optionInternal(e,6)},r.prototype.OPTION7=function(e){return this.optionInternal(e,7)},r.prototype.OPTION8=function(e){return this.optionInternal(e,8)},r.prototype.OPTION9=function(e){return this.optionInternal(e,9)},r.prototype.OR=function(e){return this.orInternal(e,0)},r.prototype.OR1=function(e){return this.orInternal(e,1)},r.prototype.OR2=function(e){return this.orInternal(e,2)},r.prototype.OR3=function(e){return this.orInternal(e,3)},r.prototype.OR4=function(e){return this.orInternal(e,4)},r.prototype.OR5=function(e){return this.orInternal(e,5)},r.prototype.OR6=function(e){return this.orInternal(e,6)},r.prototype.OR7=function(e){return this.orInternal(e,7)},r.prototype.OR8=function(e){return this.orInternal(e,8)},r.prototype.OR9=function(e){return this.orInternal(e,9)},r.prototype.MANY=function(e){this.manyInternal(0,e)},r.prototype.MANY1=function(e){this.manyInternal(1,e)},r.prototype.MANY2=function(e){this.manyInternal(2,e)},r.prototype.MANY3=function(e){this.manyInternal(3,e)},r.prototype.MANY4=function(e){this.manyInternal(4,e)},r.prototype.MANY5=function(e){this.manyInternal(5,e)},r.prototype.MANY6=function(e){this.manyInternal(6,e)},r.prototype.MANY7=function(e){this.manyInternal(7,e)},r.prototype.MANY8=function(e){this.manyInternal(8,e)},r.prototype.MANY9=function(e){this.manyInternal(9,e)},r.prototype.MANY_SEP=function(e){this.manySepFirstInternal(0,e)},r.prototype.MANY_SEP1=function(e){this.manySepFirstInternal(1,e)},r.prototype.MANY_SEP2=function(e){this.manySepFirstInternal(2,e)},r.prototype.MANY_SEP3=function(e){this.manySepFirstInternal(3,e)},r.prototype.MANY_SEP4=function(e){this.manySepFirstInternal(4,e)},r.prototype.MANY_SEP5=function(e){this.manySepFirstInternal(5,e)},r.prototype.MANY_SEP6=function(e){this.manySepFirstInternal(6,e)},r.prototype.MANY_SEP7=function(e){this.manySepFirstInternal(7,e)},r.prototype.MANY_SEP8=function(e){this.manySepFirstInternal(8,e)},r.prototype.MANY_SEP9=function(e){this.manySepFirstInternal(9,e)},r.prototype.AT_LEAST_ONE=function(e){this.atLeastOneInternal(0,e)},r.prototype.AT_LEAST_ONE1=function(e){return this.atLeastOneInternal(1,e)},r.prototype.AT_LEAST_ONE2=function(e){this.atLeastOneInternal(2,e)},r.prototype.AT_LEAST_ONE3=function(e){this.atLeastOneInternal(3,e)},r.prototype.AT_LEAST_ONE4=function(e){this.atLeastOneInternal(4,e)},r.prototype.AT_LEAST_ONE5=function(e){this.atLeastOneInternal(5,e)},r.prototype.AT_LEAST_ONE6=function(e){this.atLeastOneInternal(6,e)},r.prototype.AT_LEAST_ONE7=function(e){this.atLeastOneInternal(7,e)},r.prototype.AT_LEAST_ONE8=function(e){this.atLeastOneInternal(8,e)},r.prototype.AT_LEAST_ONE9=function(e){this.atLeastOneInternal(9,e)},r.prototype.AT_LEAST_ONE_SEP=function(e){this.atLeastOneSepFirstInternal(0,e)},r.prototype.AT_LEAST_ONE_SEP1=function(e){this.atLeastOneSepFirstInternal(1,e)},r.prototype.AT_LEAST_ONE_SEP2=function(e){this.atLeastOneSepFirstInternal(2,e)},r.prototype.AT_LEAST_ONE_SEP3=function(e){this.atLeastOneSepFirstInternal(3,e)},r.prototype.AT_LEAST_ONE_SEP4=function(e){this.atLeastOneSepFirstInternal(4,e)},r.prototype.AT_LEAST_ONE_SEP5=function(e){this.atLeastOneSepFirstInternal(5,e)},r.prototype.AT_LEAST_ONE_SEP6=function(e){this.atLeastOneSepFirstInternal(6,e)},r.prototype.AT_LEAST_ONE_SEP7=function(e){this.atLeastOneSepFirstInternal(7,e)},r.prototype.AT_LEAST_ONE_SEP8=function(e){this.atLeastOneSepFirstInternal(8,e)},r.prototype.AT_LEAST_ONE_SEP9=function(e){this.atLeastOneSepFirstInternal(9,e)},r.prototype.RULE=function(e,t,i){if(i===void 0&&(i=Ax.DEFAULT_RULE_CONFIG),(0,Mq.contains)(this.definedRulesNames,e)){var n=Qye.defaultGrammarValidatorErrorProvider.buildDuplicateRuleNameError({topLevelRule:e,grammarName:this.className}),s={message:n,type:Ax.ParserDefinitionErrorType.DUPLICATE_RULE_NAME,ruleName:e};this.definitionErrors.push(s)}this.definedRulesNames.push(e);var o=this.defineRule(e,t,i);return this[e]=o,o},r.prototype.OVERRIDE_RULE=function(e,t,i){i===void 0&&(i=Ax.DEFAULT_RULE_CONFIG);var n=[];n=n.concat((0,Sye.validateRuleIsOverridden)(e,this.definedRulesNames,this.className)),this.definitionErrors=this.definitionErrors.concat(n);var s=this.defineRule(e,t,i);return this[e]=s,s},r.prototype.BACKTRACK=function(e,t){return function(){this.isBackTrackingStack.push(1);var i=this.saveRecogState();try{return e.apply(this,t),!0}catch(n){if((0,bye.isRecognitionException)(n))return!1;throw n}finally{this.reloadRecogState(i),this.isBackTrackingStack.pop()}}},r.prototype.getGAstProductions=function(){return this.gastProductionsCache},r.prototype.getSerializedGastProductions=function(){return(0,vye.serializeGrammar)((0,Mq.values)(this.gastProductionsCache))},r}();yy.RecognizerApi=xye});var Yq=w(By=>{"use strict";Object.defineProperty(By,"__esModule",{value:!0});By.RecognizerEngine=void 0;var Pr=Gt(),qn=Cy(),wy=nf(),Uq=kd(),of=Dd(),Hq=jn(),Pye=sx(),Gq=TA(),Td=_g(),Dye=ox(),kye=function(){function r(){}return r.prototype.initRecognizerEngine=function(e,t){if(this.className=(0,Dye.classNameFromInstance)(this),this.shortRuleNameToFull={},this.fullRuleNameToShort={},this.ruleShortNameIdx=256,this.tokenMatcher=Td.tokenStructuredMatcherNoCategories,this.definedRulesNames=[],this.tokensMap={},this.isBackTrackingStack=[],this.RULE_STACK=[],this.RULE_OCCURRENCE_STACK=[],this.gastProductionsCache={},(0,Pr.has)(t,"serializedGrammar"))throw Error(`The Parser's configuration can no longer contain a property. See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_6-0-0 For Further details.`);if((0,Pr.isArray)(e)){if((0,Pr.isEmpty)(e))throw Error(`A Token Vocabulary cannot be empty. Note that the first argument for the parser constructor is no longer a Token vector (since v4.0).`);if(typeof e[0].startOffset=="number")throw Error(`The Parser constructor no longer accepts a token vector as the first argument. See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_4-0-0 For Further details.`)}if((0,Pr.isArray)(e))this.tokensMap=(0,Pr.reduce)(e,function(o,a){return o[a.name]=a,o},{});else if((0,Pr.has)(e,"modes")&&(0,Pr.every)((0,Pr.flatten)((0,Pr.values)(e.modes)),Td.isTokenType)){var i=(0,Pr.flatten)((0,Pr.values)(e.modes)),n=(0,Pr.uniq)(i);this.tokensMap=(0,Pr.reduce)(n,function(o,a){return o[a.name]=a,o},{})}else if((0,Pr.isObject)(e))this.tokensMap=(0,Pr.cloneObj)(e);else throw new Error(" argument must be An Array of Token constructors, A dictionary of Token constructors or an IMultiModeLexerDefinition");this.tokensMap.EOF=Gq.EOF;var s=(0,Pr.every)((0,Pr.values)(e),function(o){return(0,Pr.isEmpty)(o.categoryMatches)});this.tokenMatcher=s?Td.tokenStructuredMatcherNoCategories:Td.tokenStructuredMatcher,(0,Td.augmentTokenTypes)((0,Pr.values)(this.tokensMap))},r.prototype.defineRule=function(e,t,i){if(this.selfAnalysisDone)throw Error("Grammar rule <"+e+`> may not be defined after the 'performSelfAnalysis' method has been called' Make sure that all grammar rule definitions are done before 'performSelfAnalysis' is called.`);var n=(0,Pr.has)(i,"resyncEnabled")?i.resyncEnabled:Hq.DEFAULT_RULE_CONFIG.resyncEnabled,s=(0,Pr.has)(i,"recoveryValueFunc")?i.recoveryValueFunc:Hq.DEFAULT_RULE_CONFIG.recoveryValueFunc,o=this.ruleShortNameIdx<t},r.prototype.orInternal=function(e,t){var i=this.getKeyForAutomaticLookahead(qn.OR_IDX,t),n=(0,Pr.isArray)(e)?e:e.DEF,s=this.getLaFuncFromCache(i),o=s.call(this,n);if(o!==void 0){var a=n[o];return a.ALT.call(this)}this.raiseNoAltException(t,e.ERR_MSG)},r.prototype.ruleFinallyStateUpdate=function(){if(this.RULE_STACK.pop(),this.RULE_OCCURRENCE_STACK.pop(),this.cstFinallyStateUpdate(),this.RULE_STACK.length===0&&this.isAtEndOfInput()===!1){var e=this.LA(1),t=this.errorMessageProvider.buildNotAllInputParsedMessage({firstRedundant:e,ruleName:this.getCurrRuleFullName()});this.SAVE_ERROR(new wy.NotAllInputParsedException(t,e))}},r.prototype.subruleInternal=function(e,t,i){var n;try{var s=i!==void 0?i.ARGS:void 0;return n=e.call(this,t,s),this.cstPostNonTerminal(n,i!==void 0&&i.LABEL!==void 0?i.LABEL:e.ruleName),n}catch(o){this.subruleInternalError(o,i,e.ruleName)}},r.prototype.subruleInternalError=function(e,t,i){throw(0,wy.isRecognitionException)(e)&&e.partialCstResult!==void 0&&(this.cstPostNonTerminal(e.partialCstResult,t!==void 0&&t.LABEL!==void 0?t.LABEL:i),delete e.partialCstResult),e},r.prototype.consumeInternal=function(e,t,i){var n;try{var s=this.LA(1);this.tokenMatcher(s,e)===!0?(this.consumeToken(),n=s):this.consumeInternalError(e,s,i)}catch(o){n=this.consumeInternalRecovery(e,t,o)}return this.cstPostTerminal(i!==void 0&&i.LABEL!==void 0?i.LABEL:e.name,n),n},r.prototype.consumeInternalError=function(e,t,i){var n,s=this.LA(0);throw i!==void 0&&i.ERR_MSG?n=i.ERR_MSG:n=this.errorMessageProvider.buildMismatchTokenMessage({expected:e,actual:t,previous:s,ruleName:this.getCurrRuleFullName()}),this.SAVE_ERROR(new wy.MismatchedTokenException(n,t,s))},r.prototype.consumeInternalRecovery=function(e,t,i){if(this.recoveryEnabled&&i.name==="MismatchedTokenException"&&!this.isBackTracking()){var n=this.getFollowsForInRuleRecovery(e,t);try{return this.tryInRuleRecovery(e,n)}catch(s){throw s.name===Pye.IN_RULE_RECOVERY_EXCEPTION?i:s}}else throw i},r.prototype.saveRecogState=function(){var e=this.errors,t=(0,Pr.cloneArr)(this.RULE_STACK);return{errors:e,lexerState:this.exportLexerState(),RULE_STACK:t,CST_STACK:this.CST_STACK}},r.prototype.reloadRecogState=function(e){this.errors=e.errors,this.importLexerState(e.lexerState),this.RULE_STACK=e.RULE_STACK},r.prototype.ruleInvocationStateUpdate=function(e,t,i){this.RULE_OCCURRENCE_STACK.push(i),this.RULE_STACK.push(e),this.cstInvocationStateUpdate(t,e)},r.prototype.isBackTracking=function(){return this.isBackTrackingStack.length!==0},r.prototype.getCurrRuleFullName=function(){var e=this.getLastExplicitRuleShortName();return this.shortRuleNameToFull[e]},r.prototype.shortRuleNameToFullName=function(e){return this.shortRuleNameToFull[e]},r.prototype.isAtEndOfInput=function(){return this.tokenMatcher(this.LA(1),Gq.EOF)},r.prototype.reset=function(){this.resetLexerState(),this.isBackTrackingStack=[],this.errors=[],this.RULE_STACK=[],this.CST_STACK=[],this.RULE_OCCURRENCE_STACK=[]},r}();By.RecognizerEngine=kye});var qq=w(by=>{"use strict";Object.defineProperty(by,"__esModule",{value:!0});by.ErrorHandler=void 0;var lx=nf(),cx=Gt(),jq=kd(),Rye=jn(),Fye=function(){function r(){}return r.prototype.initErrorHandler=function(e){this._errors=[],this.errorMessageProvider=(0,cx.has)(e,"errorMessageProvider")?e.errorMessageProvider:Rye.DEFAULT_PARSER_CONFIG.errorMessageProvider},r.prototype.SAVE_ERROR=function(e){if((0,lx.isRecognitionException)(e))return e.context={ruleStack:this.getHumanReadableRuleStack(),ruleOccurrenceStack:(0,cx.cloneArr)(this.RULE_OCCURRENCE_STACK)},this._errors.push(e),e;throw Error("Trying to save an Error which is not a RecognitionException")},Object.defineProperty(r.prototype,"errors",{get:function(){return(0,cx.cloneArr)(this._errors)},set:function(e){this._errors=e},enumerable:!1,configurable:!0}),r.prototype.raiseEarlyExitException=function(e,t,i){for(var n=this.getCurrRuleFullName(),s=this.getGAstProductions()[n],o=(0,jq.getLookaheadPathsForOptionalProd)(e,s,t,this.maxLookahead),a=o[0],l=[],c=1;c<=this.maxLookahead;c++)l.push(this.LA(c));var u=this.errorMessageProvider.buildEarlyExitMessage({expectedIterationPaths:a,actual:l,previous:this.LA(0),customUserDescription:i,ruleName:n});throw this.SAVE_ERROR(new lx.EarlyExitException(u,this.LA(1),this.LA(0)))},r.prototype.raiseNoAltException=function(e,t){for(var i=this.getCurrRuleFullName(),n=this.getGAstProductions()[i],s=(0,jq.getLookaheadPathsForOr)(e,n,this.maxLookahead),o=[],a=1;a<=this.maxLookahead;a++)o.push(this.LA(a));var l=this.LA(0),c=this.errorMessageProvider.buildNoViableAltMessage({expectedPathsPerAlt:s,actual:o,previous:l,customUserDescription:t,ruleName:this.getCurrRuleFullName()});throw this.SAVE_ERROR(new lx.NoViableAltException(c,this.LA(1),l))},r}();by.ErrorHandler=Fye});var zq=w(Qy=>{"use strict";Object.defineProperty(Qy,"__esModule",{value:!0});Qy.ContentAssist=void 0;var Jq=Dd(),Wq=Gt(),Nye=function(){function r(){}return r.prototype.initContentAssist=function(){},r.prototype.computeContentAssist=function(e,t){var i=this.gastProductionsCache[e];if((0,Wq.isUndefined)(i))throw Error("Rule ->"+e+"<- does not exist in this grammar.");return(0,Jq.nextPossibleTokensAfter)([i],t,this.tokenMatcher,this.maxLookahead)},r.prototype.getNextPossibleTokenTypes=function(e){var t=(0,Wq.first)(e.ruleStack),i=this.getGAstProductions(),n=i[t],s=new Jq.NextAfterTokenWalker(n,e).startWalking();return s},r}();Qy.ContentAssist=Nye});var rJ=w(xy=>{"use strict";Object.defineProperty(xy,"__esModule",{value:!0});xy.GastRecorder=void 0;var yn=Gt(),Oo=mn(),Tye=Bd(),_q=_g(),$q=TA(),Lye=jn(),Oye=Cy(),vy={description:"This Object indicates the Parser is during Recording Phase"};Object.freeze(vy);var Vq=!0,Xq=Math.pow(2,Oye.BITS_FOR_OCCURRENCE_IDX)-1,eJ=(0,$q.createToken)({name:"RECORDING_PHASE_TOKEN",pattern:Tye.Lexer.NA});(0,_q.augmentTokenTypes)([eJ]);var tJ=(0,$q.createTokenInstance)(eJ,`This IToken indicates the Parser is in Recording Phase See: https://chevrotain.io/docs/guide/internals.html#grammar-recording for details`,-1,-1,-1,-1,-1,-1);Object.freeze(tJ);var Mye={name:`This CSTNode indicates the Parser is in Recording Phase See: https://chevrotain.io/docs/guide/internals.html#grammar-recording for details`,children:{}},Kye=function(){function r(){}return r.prototype.initGastRecorder=function(e){this.recordingProdStack=[],this.RECORDING_PHASE=!1},r.prototype.enableRecording=function(){var e=this;this.RECORDING_PHASE=!0,this.TRACE_INIT("Enable Recording",function(){for(var t=function(n){var s=n>0?n:"";e["CONSUME"+s]=function(o,a){return this.consumeInternalRecord(o,n,a)},e["SUBRULE"+s]=function(o,a){return this.subruleInternalRecord(o,n,a)},e["OPTION"+s]=function(o){return this.optionInternalRecord(o,n)},e["OR"+s]=function(o){return this.orInternalRecord(o,n)},e["MANY"+s]=function(o){this.manyInternalRecord(n,o)},e["MANY_SEP"+s]=function(o){this.manySepFirstInternalRecord(n,o)},e["AT_LEAST_ONE"+s]=function(o){this.atLeastOneInternalRecord(n,o)},e["AT_LEAST_ONE_SEP"+s]=function(o){this.atLeastOneSepFirstInternalRecord(n,o)}},i=0;i<10;i++)t(i);e.consume=function(n,s,o){return this.consumeInternalRecord(s,n,o)},e.subrule=function(n,s,o){return this.subruleInternalRecord(s,n,o)},e.option=function(n,s){return this.optionInternalRecord(s,n)},e.or=function(n,s){return this.orInternalRecord(s,n)},e.many=function(n,s){this.manyInternalRecord(n,s)},e.atLeastOne=function(n,s){this.atLeastOneInternalRecord(n,s)},e.ACTION=e.ACTION_RECORD,e.BACKTRACK=e.BACKTRACK_RECORD,e.LA=e.LA_RECORD})},r.prototype.disableRecording=function(){var e=this;this.RECORDING_PHASE=!1,this.TRACE_INIT("Deleting Recording methods",function(){for(var t=0;t<10;t++){var i=t>0?t:"";delete e["CONSUME"+i],delete e["SUBRULE"+i],delete e["OPTION"+i],delete e["OR"+i],delete e["MANY"+i],delete e["MANY_SEP"+i],delete e["AT_LEAST_ONE"+i],delete e["AT_LEAST_ONE_SEP"+i]}delete e.consume,delete e.subrule,delete e.option,delete e.or,delete e.many,delete e.atLeastOne,delete e.ACTION,delete e.BACKTRACK,delete e.LA})},r.prototype.ACTION_RECORD=function(e){},r.prototype.BACKTRACK_RECORD=function(e,t){return function(){return!0}},r.prototype.LA_RECORD=function(e){return Lye.END_OF_FILE},r.prototype.topLevelRuleRecord=function(e,t){try{var i=new Oo.Rule({definition:[],name:e});return i.name=e,this.recordingProdStack.push(i),t.call(this),this.recordingProdStack.pop(),i}catch(n){if(n.KNOWN_RECORDER_ERROR!==!0)try{n.message=n.message+` This error was thrown during the "grammar recording phase" For more info see: https://chevrotain.io/docs/guide/internals.html#grammar-recording`}catch{throw n}throw n}},r.prototype.optionInternalRecord=function(e,t){return Ld.call(this,Oo.Option,e,t)},r.prototype.atLeastOneInternalRecord=function(e,t){Ld.call(this,Oo.RepetitionMandatory,t,e)},r.prototype.atLeastOneSepFirstInternalRecord=function(e,t){Ld.call(this,Oo.RepetitionMandatoryWithSeparator,t,e,Vq)},r.prototype.manyInternalRecord=function(e,t){Ld.call(this,Oo.Repetition,t,e)},r.prototype.manySepFirstInternalRecord=function(e,t){Ld.call(this,Oo.RepetitionWithSeparator,t,e,Vq)},r.prototype.orInternalRecord=function(e,t){return Uye.call(this,e,t)},r.prototype.subruleInternalRecord=function(e,t,i){if(Sy(t),!e||(0,yn.has)(e,"ruleName")===!1){var n=new Error(" argument is invalid"+(" expecting a Parser method reference but got: <"+JSON.stringify(e)+">")+(` inside top level rule: <`+this.recordingProdStack[0].name+">"));throw n.KNOWN_RECORDER_ERROR=!0,n}var s=(0,yn.peek)(this.recordingProdStack),o=e.ruleName,a=new Oo.NonTerminal({idx:t,nonTerminalName:o,label:i==null?void 0:i.LABEL,referencedRule:void 0});return s.definition.push(a),this.outputCst?Mye:vy},r.prototype.consumeInternalRecord=function(e,t,i){if(Sy(t),!(0,_q.hasShortKeyProperty)(e)){var n=new Error(" argument is invalid"+(" expecting a TokenType reference but got: <"+JSON.stringify(e)+">")+(` inside top level rule: <`+this.recordingProdStack[0].name+">"));throw n.KNOWN_RECORDER_ERROR=!0,n}var s=(0,yn.peek)(this.recordingProdStack),o=new Oo.Terminal({idx:t,terminalType:e,label:i==null?void 0:i.LABEL});return s.definition.push(o),tJ},r}();xy.GastRecorder=Kye;function Ld(r,e,t,i){i===void 0&&(i=!1),Sy(t);var n=(0,yn.peek)(this.recordingProdStack),s=(0,yn.isFunction)(e)?e:e.DEF,o=new r({definition:[],idx:t});return i&&(o.separator=e.SEP),(0,yn.has)(e,"MAX_LOOKAHEAD")&&(o.maxLookahead=e.MAX_LOOKAHEAD),this.recordingProdStack.push(o),s.call(this),n.definition.push(o),this.recordingProdStack.pop(),vy}function Uye(r,e){var t=this;Sy(e);var i=(0,yn.peek)(this.recordingProdStack),n=(0,yn.isArray)(r)===!1,s=n===!1?r:r.DEF,o=new Oo.Alternation({definition:[],idx:e,ignoreAmbiguities:n&&r.IGNORE_AMBIGUITIES===!0});(0,yn.has)(r,"MAX_LOOKAHEAD")&&(o.maxLookahead=r.MAX_LOOKAHEAD);var a=(0,yn.some)(s,function(l){return(0,yn.isFunction)(l.GATE)});return o.hasPredicates=a,i.definition.push(o),(0,yn.forEach)(s,function(l){var c=new Oo.Alternative({definition:[]});o.definition.push(c),(0,yn.has)(l,"IGNORE_AMBIGUITIES")?c.ignoreAmbiguities=l.IGNORE_AMBIGUITIES:(0,yn.has)(l,"GATE")&&(c.ignoreAmbiguities=!0),t.recordingProdStack.push(c),l.ALT.call(t),t.recordingProdStack.pop()}),vy}function Zq(r){return r===0?"":""+r}function Sy(r){if(r<0||r>Xq){var e=new Error("Invalid DSL Method idx value: <"+r+`> `+("Idx value must be a none negative value smaller than "+(Xq+1)));throw e.KNOWN_RECORDER_ERROR=!0,e}}});var nJ=w(Py=>{"use strict";Object.defineProperty(Py,"__esModule",{value:!0});Py.PerformanceTracer=void 0;var iJ=Gt(),Hye=jn(),Gye=function(){function r(){}return r.prototype.initPerformanceTracer=function(e){if((0,iJ.has)(e,"traceInitPerf")){var t=e.traceInitPerf,i=typeof t=="number";this.traceInitMaxIdent=i?t:1/0,this.traceInitPerf=i?t>0:t}else this.traceInitMaxIdent=0,this.traceInitPerf=Hye.DEFAULT_PARSER_CONFIG.traceInitPerf;this.traceInitIndent=-1},r.prototype.TRACE_INIT=function(e,t){if(this.traceInitPerf===!0){this.traceInitIndent++;var i=new Array(this.traceInitIndent+1).join(" ");this.traceInitIndent <"+e+">");var n=(0,iJ.timer)(t),s=n.time,o=n.value,a=s>10?console.warn:console.log;return this.traceInitIndent time: "+s+"ms"),this.traceInitIndent--,o}else return t()},r}();Py.PerformanceTracer=Gye});var sJ=w(Dy=>{"use strict";Object.defineProperty(Dy,"__esModule",{value:!0});Dy.applyMixins=void 0;function Yye(r,e){e.forEach(function(t){var i=t.prototype;Object.getOwnPropertyNames(i).forEach(function(n){if(n!=="constructor"){var s=Object.getOwnPropertyDescriptor(i,n);s&&(s.get||s.set)?Object.defineProperty(r.prototype,n,s):r.prototype[n]=t.prototype[n]}})})}Dy.applyMixins=Yye});var jn=w(dr=>{"use strict";var AJ=dr&&dr.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(dr,"__esModule",{value:!0});dr.EmbeddedActionsParser=dr.CstParser=dr.Parser=dr.EMPTY_ALT=dr.ParserDefinitionErrorType=dr.DEFAULT_RULE_CONFIG=dr.DEFAULT_PARSER_CONFIG=dr.END_OF_FILE=void 0;var en=Gt(),jye=qj(),oJ=TA(),lJ=xd(),aJ=Cq(),qye=sx(),Jye=Qq(),Wye=Tq(),zye=Oq(),Vye=Kq(),Xye=Yq(),Zye=qq(),_ye=zq(),$ye=rJ(),ewe=nJ(),twe=sJ();dr.END_OF_FILE=(0,oJ.createTokenInstance)(oJ.EOF,"",NaN,NaN,NaN,NaN,NaN,NaN);Object.freeze(dr.END_OF_FILE);dr.DEFAULT_PARSER_CONFIG=Object.freeze({recoveryEnabled:!1,maxLookahead:3,dynamicTokensEnabled:!1,outputCst:!0,errorMessageProvider:lJ.defaultParserErrorProvider,nodeLocationTracking:"none",traceInitPerf:!1,skipValidations:!1});dr.DEFAULT_RULE_CONFIG=Object.freeze({recoveryValueFunc:function(){},resyncEnabled:!0});var rwe;(function(r){r[r.INVALID_RULE_NAME=0]="INVALID_RULE_NAME",r[r.DUPLICATE_RULE_NAME=1]="DUPLICATE_RULE_NAME",r[r.INVALID_RULE_OVERRIDE=2]="INVALID_RULE_OVERRIDE",r[r.DUPLICATE_PRODUCTIONS=3]="DUPLICATE_PRODUCTIONS",r[r.UNRESOLVED_SUBRULE_REF=4]="UNRESOLVED_SUBRULE_REF",r[r.LEFT_RECURSION=5]="LEFT_RECURSION",r[r.NONE_LAST_EMPTY_ALT=6]="NONE_LAST_EMPTY_ALT",r[r.AMBIGUOUS_ALTS=7]="AMBIGUOUS_ALTS",r[r.CONFLICT_TOKENS_RULES_NAMESPACE=8]="CONFLICT_TOKENS_RULES_NAMESPACE",r[r.INVALID_TOKEN_NAME=9]="INVALID_TOKEN_NAME",r[r.NO_NON_EMPTY_LOOKAHEAD=10]="NO_NON_EMPTY_LOOKAHEAD",r[r.AMBIGUOUS_PREFIX_ALTS=11]="AMBIGUOUS_PREFIX_ALTS",r[r.TOO_MANY_ALTS=12]="TOO_MANY_ALTS"})(rwe=dr.ParserDefinitionErrorType||(dr.ParserDefinitionErrorType={}));function iwe(r){return r===void 0&&(r=void 0),function(){return r}}dr.EMPTY_ALT=iwe;var ky=function(){function r(e,t){this.definitionErrors=[],this.selfAnalysisDone=!1;var i=this;if(i.initErrorHandler(t),i.initLexerAdapter(),i.initLooksAhead(t),i.initRecognizerEngine(e,t),i.initRecoverable(t),i.initTreeBuilder(t),i.initContentAssist(),i.initGastRecorder(t),i.initPerformanceTracer(t),(0,en.has)(t,"ignoredIssues"))throw new Error(`The IParserConfig property has been deprecated. Please use the flag on the relevant DSL method instead. See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#IGNORING_AMBIGUITIES For further details.`);this.skipValidations=(0,en.has)(t,"skipValidations")?t.skipValidations:dr.DEFAULT_PARSER_CONFIG.skipValidations}return r.performSelfAnalysis=function(e){throw Error("The **static** `performSelfAnalysis` method has been deprecated. \nUse the **instance** method with the same name instead.")},r.prototype.performSelfAnalysis=function(){var e=this;this.TRACE_INIT("performSelfAnalysis",function(){var t;e.selfAnalysisDone=!0;var i=e.className;e.TRACE_INIT("toFastProps",function(){(0,en.toFastProperties)(e)}),e.TRACE_INIT("Grammar Recording",function(){try{e.enableRecording(),(0,en.forEach)(e.definedRulesNames,function(s){var o=e[s],a=o.originalGrammarAction,l=void 0;e.TRACE_INIT(s+" Rule",function(){l=e.topLevelRuleRecord(s,a)}),e.gastProductionsCache[s]=l})}finally{e.disableRecording()}});var n=[];if(e.TRACE_INIT("Grammar Resolving",function(){n=(0,aJ.resolveGrammar)({rules:(0,en.values)(e.gastProductionsCache)}),e.definitionErrors=e.definitionErrors.concat(n)}),e.TRACE_INIT("Grammar Validations",function(){if((0,en.isEmpty)(n)&&e.skipValidations===!1){var s=(0,aJ.validateGrammar)({rules:(0,en.values)(e.gastProductionsCache),maxLookahead:e.maxLookahead,tokenTypes:(0,en.values)(e.tokensMap),errMsgProvider:lJ.defaultGrammarValidatorErrorProvider,grammarName:i});e.definitionErrors=e.definitionErrors.concat(s)}}),(0,en.isEmpty)(e.definitionErrors)&&(e.recoveryEnabled&&e.TRACE_INIT("computeAllProdsFollows",function(){var s=(0,jye.computeAllProdsFollows)((0,en.values)(e.gastProductionsCache));e.resyncFollows=s}),e.TRACE_INIT("ComputeLookaheadFunctions",function(){e.preComputeLookaheadFunctions((0,en.values)(e.gastProductionsCache))})),!r.DEFER_DEFINITION_ERRORS_HANDLING&&!(0,en.isEmpty)(e.definitionErrors))throw t=(0,en.map)(e.definitionErrors,function(s){return s.message}),new Error(`Parser Definition Errors detected: `+t.join(` ------------------------------- `))})},r.DEFER_DEFINITION_ERRORS_HANDLING=!1,r}();dr.Parser=ky;(0,twe.applyMixins)(ky,[qye.Recoverable,Jye.LooksAhead,Wye.TreeBuilder,zye.LexerAdapter,Xye.RecognizerEngine,Vye.RecognizerApi,Zye.ErrorHandler,_ye.ContentAssist,$ye.GastRecorder,ewe.PerformanceTracer]);var nwe=function(r){AJ(e,r);function e(t,i){i===void 0&&(i=dr.DEFAULT_PARSER_CONFIG);var n=this,s=(0,en.cloneObj)(i);return s.outputCst=!0,n=r.call(this,t,s)||this,n}return e}(ky);dr.CstParser=nwe;var swe=function(r){AJ(e,r);function e(t,i){i===void 0&&(i=dr.DEFAULT_PARSER_CONFIG);var n=this,s=(0,en.cloneObj)(i);return s.outputCst=!1,n=r.call(this,t,s)||this,n}return e}(ky);dr.EmbeddedActionsParser=swe});var uJ=w(Ry=>{"use strict";Object.defineProperty(Ry,"__esModule",{value:!0});Ry.createSyntaxDiagramsCode=void 0;var cJ=Dv();function owe(r,e){var t=e===void 0?{}:e,i=t.resourceBase,n=i===void 0?"https://unpkg.com/chevrotain@"+cJ.VERSION+"/diagrams/":i,s=t.css,o=s===void 0?"https://unpkg.com/chevrotain@"+cJ.VERSION+"/diagrams/diagrams.css":s,a=` `,l=` `,c=`