Repository: sindresorhus/type-fest Branch: main Commit: 0329b2b2dc56 Files: 449 Total size: 1.2 MB Directory structure: gitextract__kzp05im/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── 1 new type.yml │ │ ├── 2 bug report.yml │ │ └── 3 enhancement.md │ ├── contributing.md │ ├── dependabot.yml │ ├── funding.yml │ ├── pull_request_template.md │ ├── security.md │ └── workflows/ │ ├── claude-code-review.yml │ ├── claude.yml │ ├── main.yml │ └── ts-canary.yml ├── .gitignore ├── .npmrc ├── CLAUDE.md ├── index.d.ts ├── license-cc0 ├── license-mit ├── lint-processors/ │ ├── fixtures/ │ │ └── eslint.config.js │ ├── jsdoc-codeblocks.js │ └── jsdoc-codeblocks.test.js ├── lint-rules/ │ ├── import-path.js │ ├── import-path.test.js │ ├── require-export.js │ ├── require-export.test.js │ ├── require-exported-types.js │ ├── require-exported-types.test.js │ ├── source-files-extension.js │ ├── source-files-extension.test.js │ ├── test-utils.js │ ├── validate-jsdoc-codeblocks.js │ └── validate-jsdoc-codeblocks.test.js ├── media/ │ ├── logo.sketch │ └── readme.md ├── package.json ├── readme.md ├── source/ │ ├── all-extend.d.ts │ ├── all-union-fields.d.ts │ ├── and-all.d.ts │ ├── and.d.ts │ ├── array-element.d.ts │ ├── array-indices.d.ts │ ├── array-length.d.ts │ ├── array-reverse.d.ts │ ├── array-slice.d.ts │ ├── array-splice.d.ts │ ├── array-tail.d.ts │ ├── array-values.d.ts │ ├── arrayable.d.ts │ ├── async-return-type.d.ts │ ├── asyncify.d.ts │ ├── basic.d.ts │ ├── camel-case.d.ts │ ├── camel-cased-properties-deep.d.ts │ ├── camel-cased-properties.d.ts │ ├── characters.d.ts │ ├── conditional-except.d.ts │ ├── conditional-keys.d.ts │ ├── conditional-pick-deep.d.ts │ ├── conditional-pick.d.ts │ ├── conditional-simplify-deep.d.ts │ ├── conditional-simplify.d.ts │ ├── delimiter-case.d.ts │ ├── delimiter-cased-properties-deep.d.ts │ ├── delimiter-cased-properties.d.ts │ ├── distributed-omit.d.ts │ ├── distributed-pick.d.ts │ ├── empty-object.d.ts │ ├── entries.d.ts │ ├── entry.d.ts │ ├── exact.d.ts │ ├── except.d.ts │ ├── exclude-exactly.d.ts │ ├── exclude-rest-element.d.ts │ ├── exclude-strict.d.ts │ ├── exclusify-union.d.ts │ ├── extends-strict.d.ts │ ├── extract-rest-element.d.ts │ ├── extract-strict.d.ts │ ├── find-global-type.d.ts │ ├── fixed-length-array.d.ts │ ├── get.d.ts │ ├── global-this.d.ts │ ├── globals/ │ │ ├── index.d.ts │ │ └── observable-like.d.ts │ ├── greater-than-or-equal.d.ts │ ├── greater-than.d.ts │ ├── has-optional-keys.d.ts │ ├── has-readonly-keys.d.ts │ ├── has-required-keys.d.ts │ ├── has-writable-keys.d.ts │ ├── if-any.d.ts │ ├── if-empty-object.d.ts │ ├── if-never.d.ts │ ├── if-null.d.ts │ ├── if-unknown.d.ts │ ├── if.d.ts │ ├── includes.d.ts │ ├── int-closed-range.d.ts │ ├── int-range.d.ts │ ├── internal/ │ │ ├── array.d.ts │ │ ├── characters.d.ts │ │ ├── enforce-optional.d.ts │ │ ├── index.d.ts │ │ ├── keys.d.ts │ │ ├── numeric.d.ts │ │ ├── object.d.ts │ │ ├── string.d.ts │ │ ├── tuple.d.ts │ │ └── type.d.ts │ ├── invariant-of.d.ts │ ├── is-any.d.ts │ ├── is-equal.d.ts │ ├── is-float.d.ts │ ├── is-integer.d.ts │ ├── is-literal.d.ts │ ├── is-lowercase.d.ts │ ├── is-never.d.ts │ ├── is-null.d.ts │ ├── is-nullable.d.ts │ ├── is-optional-key-of.d.ts │ ├── is-optional.d.ts │ ├── is-readonly-key-of.d.ts │ ├── is-required-key-of.d.ts │ ├── is-tuple.d.ts │ ├── is-undefined.d.ts │ ├── is-union.d.ts │ ├── is-unknown.d.ts │ ├── is-uppercase.d.ts │ ├── is-writable-key-of.d.ts │ ├── iterable-element.d.ts │ ├── join.d.ts │ ├── json-value.d.ts │ ├── jsonifiable.d.ts │ ├── jsonify.d.ts │ ├── kebab-case.d.ts │ ├── kebab-cased-properties-deep.d.ts │ ├── kebab-cased-properties.d.ts │ ├── key-as-string.d.ts │ ├── keys-of-union.d.ts │ ├── last-array-element.d.ts │ ├── less-than-or-equal.d.ts │ ├── less-than.d.ts │ ├── literal-to-primitive-deep.d.ts │ ├── literal-to-primitive.d.ts │ ├── literal-union.d.ts │ ├── merge-deep.d.ts │ ├── merge-exclusive.d.ts │ ├── merge.d.ts │ ├── multidimensional-array.d.ts │ ├── multidimensional-readonly-array.d.ts │ ├── non-empty-object.d.ts │ ├── non-empty-string.d.ts │ ├── non-empty-tuple.d.ts │ ├── numeric.d.ts │ ├── object-merge.d.ts │ ├── omit-deep.d.ts │ ├── omit-index-signature.d.ts │ ├── opaque.d.ts │ ├── optional-keys-of.d.ts │ ├── optional.d.ts │ ├── or-all.d.ts │ ├── or.d.ts │ ├── override-properties.d.ts │ ├── package-json.d.ts │ ├── partial-deep.d.ts │ ├── partial-on-undefined-deep.d.ts │ ├── pascal-case.d.ts │ ├── pascal-cased-properties-deep.d.ts │ ├── pascal-cased-properties.d.ts │ ├── paths.d.ts │ ├── pick-deep.d.ts │ ├── pick-index-signature.d.ts │ ├── primitive.d.ts │ ├── promisable.d.ts │ ├── readonly-deep.d.ts │ ├── readonly-keys-of.d.ts │ ├── readonly-tuple.d.ts │ ├── remove-prefix.d.ts │ ├── replace.d.ts │ ├── require-all-or-none.d.ts │ ├── require-at-least-one.d.ts │ ├── require-exactly-one.d.ts │ ├── require-one-or-none.d.ts │ ├── required-deep.d.ts │ ├── required-keys-of.d.ts │ ├── schema.d.ts │ ├── screaming-snake-case.d.ts │ ├── set-field-type.d.ts │ ├── set-non-nullable-deep.d.ts │ ├── set-non-nullable.d.ts │ ├── set-optional.d.ts │ ├── set-parameter-type.d.ts │ ├── set-readonly.d.ts │ ├── set-required-deep.d.ts │ ├── set-required.d.ts │ ├── set-return-type.d.ts │ ├── shared-union-fields-deep.d.ts │ ├── shared-union-fields.d.ts │ ├── simplify-deep.d.ts │ ├── simplify.d.ts │ ├── single-key-object.d.ts │ ├── snake-case.d.ts │ ├── snake-cased-properties-deep.d.ts │ ├── snake-cased-properties.d.ts │ ├── some-extend.d.ts │ ├── split-on-rest-element.d.ts │ ├── split.d.ts │ ├── spread.d.ts │ ├── string-repeat.d.ts │ ├── string-slice.d.ts │ ├── stringified.d.ts │ ├── structured-cloneable.d.ts │ ├── subtract.d.ts │ ├── sum.d.ts │ ├── tagged-union.d.ts │ ├── tagged.d.ts │ ├── trim.d.ts │ ├── tsconfig-json.d.ts │ ├── tuple-of.d.ts │ ├── tuple-to-object.d.ts │ ├── tuple-to-union.d.ts │ ├── typed-array.d.ts │ ├── undefined-on-partial-deep.d.ts │ ├── union-member.d.ts │ ├── union-to-intersection.d.ts │ ├── union-to-tuple.d.ts │ ├── unknown-array.d.ts │ ├── unknown-map.d.ts │ ├── unknown-record.d.ts │ ├── unknown-set.d.ts │ ├── unwrap-partial.d.ts │ ├── value-of.d.ts │ ├── words.d.ts │ ├── writable-deep.d.ts │ ├── writable-keys-of.d.ts │ ├── writable.d.ts │ └── xor.d.ts ├── test-d/ │ ├── abstract-class.ts │ ├── all-extend.ts │ ├── all-union-fields.ts │ ├── and-all.ts │ ├── and.ts │ ├── array-element.ts │ ├── array-indices.ts │ ├── array-length.ts │ ├── array-reverse.ts │ ├── array-slice.ts │ ├── array-splice.ts │ ├── array-tail.ts │ ├── array-values.ts │ ├── arrayable.ts │ ├── async-return-type.ts │ ├── asyncify.ts │ ├── camel-case.ts │ ├── camel-cased-properties-deep.ts │ ├── camel-cased-properties.ts │ ├── class.ts │ ├── conditional-except.ts │ ├── conditional-keys.ts │ ├── conditional-pick-deep.ts │ ├── conditional-pick.ts │ ├── conditional-simplify-deep.ts │ ├── conditional-simplify.ts │ ├── delimiter-case.ts │ ├── delimiter-cased-properties-deep.ts │ ├── delimiter-cased-properties.ts │ ├── distributed-omit.ts │ ├── distributed-pick.ts │ ├── empty-object.ts │ ├── entries.ts │ ├── exact.ts │ ├── except.ts │ ├── exclude-exactly.ts │ ├── exclude-rest-element.ts │ ├── exclude-strict.ts │ ├── exclusify-union.ts │ ├── extends-strict.ts │ ├── extract-rest-element.ts │ ├── extract-strict.ts │ ├── find-global-type.ts │ ├── fixed-length-array.ts │ ├── get.ts │ ├── global-this.ts │ ├── greater-than-or-equal.ts │ ├── greater-than.ts │ ├── has-optional-keys.ts │ ├── has-readonly-keys.ts │ ├── has-required-keys.ts │ ├── has-writable-keys.ts │ ├── if-any.ts │ ├── if-never.ts │ ├── if-unknown.ts │ ├── if.ts │ ├── includes.ts │ ├── int-closed-range.ts │ ├── int-range.ts │ ├── internal/ │ │ ├── apply-default-options.ts │ │ ├── collapse-literals-in-union.ts │ │ ├── collapse-rest-element.ts │ │ ├── enforce-optional.ts │ │ ├── has-multiple-call-signatures.ts │ │ ├── homomorphic-pick.ts │ │ ├── if-not-any-or-never.ts │ │ ├── is-array-readonly.ts │ │ ├── is-not-false.ts │ │ ├── is-number-like.ts │ │ ├── is-numeric.ts │ │ ├── is-whitespace.ts │ │ ├── normalized-keys.ts │ │ ├── not.ts │ │ ├── number-absolute.ts │ │ ├── object-value.ts │ │ ├── readonly-keys-of-union.ts │ │ ├── require-none.ts │ │ ├── tuple-max.ts │ │ ├── tuple-min.ts │ │ ├── union-max.ts │ │ ├── union-min.ts │ │ └── value-of-union.ts │ ├── invariant-of.ts │ ├── is-any.ts │ ├── is-equal.ts │ ├── is-float.ts │ ├── is-integer.ts │ ├── is-literal.ts │ ├── is-lowercase.ts │ ├── is-never.ts │ ├── is-null.ts │ ├── is-nullable.ts │ ├── is-optional-key-of.ts │ ├── is-optional.ts │ ├── is-readonly-key-of.ts │ ├── is-required-key-of.ts │ ├── is-tuple.ts │ ├── is-undefined.ts │ ├── is-union.ts │ ├── is-unknown.ts │ ├── is-uppercase.ts │ ├── is-writable-key-of.ts │ ├── iterable-element.ts │ ├── join.ts │ ├── jsonifiable.ts │ ├── jsonify.ts │ ├── kebab-case.ts │ ├── kebab-cased-properties-deep.ts │ ├── kebab-cased-properties.ts │ ├── key-as-string.ts │ ├── keys-of-union.ts │ ├── last-array-element.ts │ ├── less-than-or-equal.ts │ ├── less-than.ts │ ├── literal-to-primitive-deep.ts │ ├── literal-to-primitive.ts │ ├── merge-deep.ts │ ├── merge-exclusive.ts │ ├── merge.ts │ ├── multidimensional-array.ts │ ├── multidimensional-readonly-array.ts │ ├── non-empty-object.ts │ ├── non-empty-string.ts │ ├── non-empty-tuple.ts │ ├── numeric.ts │ ├── object-merge.ts │ ├── observable-like.ts │ ├── omit-deep.ts │ ├── omit-index-signature.ts │ ├── opaque.ts │ ├── optional-keys-of.ts │ ├── optional.ts │ ├── or-all.ts │ ├── or.ts │ ├── override-properties.ts │ ├── package-json.ts │ ├── partial-deep.ts │ ├── partial-on-undefined-deep.ts │ ├── pascal-case.ts │ ├── pascal-cased-properties-deep.ts │ ├── pascal-cased-properties.ts │ ├── paths.ts │ ├── pick-deep.ts │ ├── pick-index-signature.ts │ ├── promisable.ts │ ├── readonly-deep.ts │ ├── readonly-keys-of.ts │ ├── readonly-tuple.ts │ ├── remove-prefix.ts │ ├── replace.ts │ ├── require-all-or-none.ts │ ├── require-at-least-one.ts │ ├── require-exactly-one.ts │ ├── require-one-or-none.ts │ ├── required-deep.ts │ ├── required-keys-of.ts │ ├── schema.ts │ ├── screaming-snake-case.ts │ ├── set-field-type.ts │ ├── set-non-nullable-deep.ts │ ├── set-non-nullable.ts │ ├── set-optional.ts │ ├── set-parameter-type.ts │ ├── set-readonly.ts │ ├── set-required-deep.ts │ ├── set-required.ts │ ├── set-return-type.ts │ ├── shared-union-fields-deep.ts │ ├── shared-union-fields.ts │ ├── simplify-deep.ts │ ├── simplify.ts │ ├── single-key-object.ts │ ├── snake-case.ts │ ├── snake-cased-properties-deep.ts │ ├── snake-cased-properties.ts │ ├── some-extend.ts │ ├── split-on-rest-element.ts │ ├── split.ts │ ├── spread.ts │ ├── string-repeat.ts │ ├── string-slice.ts │ ├── stringified.ts │ ├── structured-cloneable.ts │ ├── subtract.ts │ ├── sum.ts │ ├── tagged-union.ts │ ├── trim.ts │ ├── ts41.ts │ ├── tsconfig-json.ts │ ├── tuple-of.ts │ ├── tuple-to-object.ts │ ├── tuple-to-union.ts │ ├── undefined-on-partial-deep.ts │ ├── union-member.ts │ ├── union-to-intersection.ts │ ├── union-to-tuple.ts │ ├── unknown-array.ts │ ├── unknown-map.ts │ ├── unknown-record.ts │ ├── unknown-set.ts │ ├── unwrap-partial.ts │ ├── value-of.ts │ ├── words.ts │ ├── writable-deep.ts │ ├── writable-keys-of.ts │ ├── writable.ts │ └── xor.ts ├── tsconfig.json └── xo.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] indent_style = tab end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.yml] indent_style = space indent_size = 2 ================================================ FILE: .gitattributes ================================================ * text=auto eol=lf ================================================ FILE: .github/ISSUE_TEMPLATE/1 new type.yml ================================================ name: 💡 Suggest new type description: '​‌‍⁠ ' # Magic whitespace to hide this required field labels: 'type addition' body: - type: textarea id: description validations: required: true attributes: label: Type description + examples - type: textarea id: type attributes: label: Type source description: If you already have the type source, enter it here as a starting point for a the discussion - id: requirements type: checkboxes attributes: label: Search existing types and issues first options: - label: I tried my best to look for it required: true ================================================ FILE: .github/ISSUE_TEMPLATE/2 bug report.yml ================================================ name: 🐛 Report bug description: '​‌‍⁠ ' # Magic whitespace to hide this required field labels: bug body: - type: textarea id: description validations: required: true attributes: label: Bug description - type: input id: repro validations: required: true attributes: label: Repro description: | Open [this playground](https://www.typescriptlang.org/play/?#code/JYWwDg9gTgLgBDAnmApnA3gUQB4GMVgwC+cAZlBCHAORKoC0pKAzjNQNwCwAUD3WgDEIEOAF4MPAJABXAHbBc0WQC44rKMFkBzLt0lQAhpoBGEAO6rTEADYoDs3UV19kaAHIQASkdmmzYuBx8QgAeIQgAGhpDE3NqAD5dOGS4AHpUuAA9AH4eIA), write a piece of code that fails your expectations, click "Share", and paste the URL here ================================================ FILE: .github/ISSUE_TEMPLATE/3 enhancement.md ================================================ --- name: ♻️ Propose change or improvement to existing types about: '​‌‍⁠ ' # Magic whitespace to hide this required field labels: 'enhancement' --- ================================================ FILE: .github/contributing.md ================================================ # Contributing guidelines ## Submitting a new type - One type addition per pull request, unless they are connected. - **Please help review the other open pull requests.** - If there are no open pull requests, provide some feedback on some of the open issues. - [Example of a type contribution.](https://github.com/sindresorhus/type-fest/commit/5374588a88ee643893784f66367bc26b8e6509ec) - Create a new file in the `test-d` directory and write at least one type test. - See the other tests for inspiration. - If it makes sense, also write a negative test using [`expectNotAssignable()`](https://github.com/SamVerschueren/tsd#expectnotassignabletexpression-any) or, to test other diagnostics, [`expectError()`](https://github.com/SamVerschueren/tsd#expecterrort--anyexpression-t). - Don't use one-character type names like `T` and `U`. Use descriptive names. See the existing types for inspiration. - Follow the existing code style, even in documentation code examples. - Don't prefix each line in documentation comments with `*`. - Write a good documentation comment that includes: - Write a short and clear description of what the type does. - The first line should match the description in the readme. - Write about some real-world use-cases where it can be useful. (It can be hard sometimes for users to see where they would use something) - Example code block with a realistic example. - At the bottom, explain how the type works. Some types can be quite advanced and hard to understand. We can use this opportunity to teach users. - If there has been any discussion somewhere about this type, include a link to it. For example, a discussion on the TypeScript issue tracker. - Add relevant `@category` tags. See other types for examples. - If you add any internal helper types, they should still be properly documented and tested. - Add the type to the readme. - Make sure the file in the `source` directory uses a `.d.ts` extension and not `.ts`. - **Use AI (like ChatGPT) to catch type bugs, improve docs, spot typos, validate examples, and suggest more tests.** Include all relevant code (type, tests, helpers, etc.) in the prompt, and also provide a couple of existing types as examples of how it's done. Try this prompt: “Review this TypeScript type for correctness, edge cases, naming, docs, and test coverage. Suggest improvements and realistic succinct examples.” - Run `$ npm test` before submitting and make sure it passes. - Name the pull request ```Add `TypeName` type```. ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: 'github-actions' directory: '/' schedule: interval: 'weekly' groups: github-actions: patterns: - '*' - package-ecosystem: 'npm' directory: '/' schedule: interval: 'monthly' versioning-strategy: 'increase-if-necessary' groups: development-dependencies: dependency-type: 'development' ================================================ FILE: .github/funding.yml ================================================ github: [sindresorhus, som-sm, Emiyaaaaa, voxpelli] ================================================ FILE: .github/pull_request_template.md ================================================ ================================================ FILE: .github/security.md ================================================ # Security Policy To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. ================================================ FILE: .github/workflows/claude-code-review.yml ================================================ name: Claude Code Review on: pull_request: types: [opened, synchronize] jobs: claude-review: runs-on: ubuntu-latest # Only run if the PR is from the same repository (not a fork), because forks are not supported yet: https://github.com/anthropics/claude-code-action/issues/339 if: github.event.pull_request.head.repo.full_name == github.repository permissions: contents: read pull-requests: write issues: read id-token: write steps: - name: Checkout repository uses: actions/checkout@v5 with: fetch-depth: 1 - name: Run Claude Code Review id: claude-review uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} prompt: | REPO: ${{ github.repository }} PR NUMBER: ${{ github.event.pull_request.number }} Please review this pull request and provide feedback on: - Code quality and best practices - Potential bugs or issues - Performance considerations - Security concerns - Test coverage - Be succinct - Only comment about things that needs to be fixed/improved - Review thoroughly Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback. Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR. # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md # or https://docs.claude.com/en/docs/claude-code/sdk#command-line for available options claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"' ================================================ FILE: .github/workflows/claude.yml ================================================ name: Claude Code on: issue_comment: types: [created] pull_request_review_comment: types: [created] issues: types: [opened, assigned] pull_request_review: types: [submitted] jobs: claude: if: | (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) runs-on: ubuntu-latest permissions: contents: read pull-requests: read issues: read id-token: write actions: read # Required for Claude to read CI results on PRs steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 1 - name: Run Claude Code id: claude uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} additional_permissions: | actions: read ================================================ FILE: .github/workflows/main.yml ================================================ name: CI on: push: branches: - main tags: - '*' pull_request: branches: - main jobs: test: name: Node.js ${{ matrix.node-version }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: node-version: - 24 - 22 - 20 steps: - uses: actions/checkout@v5 - uses: actions/setup-node@v5 with: node-version: ${{ matrix.node-version }} - run: npm install - run: npm test types: name: TypeScript ${{ matrix.typescript-version }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: typescript-version: - 'latest' - '~5.9.0' steps: - uses: actions/checkout@v5 - uses: actions/setup-node@v5 with: node-version: 24 - run: npm install - run: npm install typescript@${{ matrix.typescript-version }} - run: NODE_OPTIONS="--max-old-space-size=6144" npx tsc ================================================ FILE: .github/workflows/ts-canary.yml ================================================ name: TypeScript Canary on: schedule: # Every Thursday at 21.15 - cron: '15 21 * * 4' workflow_dispatch: jobs: types: name: TypeScript ${{ matrix.typescript-version }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: typescript-version: - next - latest steps: - uses: actions/checkout@v5 - uses: actions/setup-node@v5 with: node-version: lts/* - run: npm install - run: npm install typescript@${{ matrix.typescript-version }} - name: show installed typescript version run: npm list typescript --depth=0 - run: NODE_OPTIONS="--max-old-space-size=6144" npx tsc ================================================ FILE: .gitignore ================================================ node_modules yarn.lock ================================================ FILE: .npmrc ================================================ package-lock=false ================================================ FILE: CLAUDE.md ================================================ # type-fest TypeScript utility types library. Pure type-level programming—no runtime code. **Read `.github/contributing.md` for complete guidelines.** ## Commands ```bash npm test # MUST pass before committing (runs all below) npm run test:tsc # TypeScript compiler npm run test:tsd # Type tests (tsd) npm run test:xo # Linter ``` ## File Structure ``` source/type-name.d.ts → Type definition (.d.ts REQUIRED) test-d/type-name.ts → Tests (tsd) index.d.ts → Export (MUST add, lint enforces) readme.md → API docs (add to category) source/internal/ → Shared helpers (not exported) ``` ## Code Patterns ```ts // ✅ CORRECT import type {IsNever} from './is-never.d.ts'; // Include .d.ts export type MyType = ... // Descriptive names (not T/U) export {}; // REQUIRED at end // ❌ WRONG import type {IsNever} from './is-never'; // Missing .d.ts export type MyType = ...; // Single-letter /** Creates a tuple type of specified length with elements of specified type. Use-cases: - Define fixed-length arrays with specific types @example ``` import type {TupleOf} from 'type-fest'; type RGB = TupleOf<3, number>; //=> [number, number, number] ``` @category Array */ export type TupleOf = ...; export {}; ``` Type params: `Value`, `Target`, `Item`, `Length`, `Element`, `Key` (not `T`, `U`, `V`, `K`) Docs: No `*` prefix. First line → readme. Include use-cases, examples, `@category`. ## Testing **ALWAYS test edge cases** (`any`, `never`, `unknown`). ```ts expectType('' as MyType<'foo'>); // Basic expectType('' as MyType); // any/never/unknown expectNotAssignable>({wrong: true}); // Negative expectError>(); // Should error ``` Study: `source/tuple-of.d.ts`, `is-equal.d.ts`, `literal-union.d.ts`, `internal/*.d.ts` ## Workflow 1. Research - Study `source/` similar types + online research 2. Define - `source/type-name.d.ts` with docs 3. Test - `test-d/type-name.ts` with edge cases 4. Export - Add to `index.d.ts` + readme.md 5. Verify - `npm test` must pass 6. Commit - ``Add `TypeName` type`` or `` `TypeName`: Fix description`` ## Critical Rules 1. Import paths MUST include `.d.ts` extension 2. Files MUST end with `export {};` 3. Types MUST be exported from `index.d.ts` 4. Type params MUST use descriptive names (not `T`/`U`) 5. MUST test `any`, `never`, `unknown` edge cases 6. First doc line MUST be concise (goes in readme) 7. Use realistic examples with `//=>` comments 8. Include `@category` tags 9. Fix lint issues, don't disable rules ## Type Programming - Conditionals: `T extends U ? X : Y` - Recursion: `type Loop = ... Loop<...> ...` - Extract: `infer Item` - Distribute: `T extends any ? ... : never` - Count via tuple `['length']` - Union distribution is tricky—test it ## Philosophy - Correctness > cleverness - Real problems only - Docs teach how, not what - Edge cases mandatory - One concept per PR - Descriptive names > brevity - No unrelated changes ## Troubleshooting - Tests fail? Check `.d.ts` imports, `export {};`, edge cases (`any`, `never`, `unknown`) - Lint errors? Add to `index.d.ts`, fix issues (don't disable rules) ================================================ FILE: index.d.ts ================================================ // Basic export type * from './source/primitive.d.ts'; export type * from './source/typed-array.d.ts'; export type * from './source/basic.d.ts'; export type * from './source/json-value.d.ts'; export type * from './source/characters.d.ts'; // Utilities export type {KeysOfUnion} from './source/keys-of-union.d.ts'; export type {DistributedOmit} from './source/distributed-omit.d.ts'; export type {DistributedPick} from './source/distributed-pick.d.ts'; export type {EmptyObject, IsEmptyObject} from './source/empty-object.d.ts'; export type {IfEmptyObject} from './source/if-empty-object.d.ts'; export type {NonEmptyObject} from './source/non-empty-object.d.ts'; export type {NonEmptyString} from './source/non-empty-string.d.ts'; export type {UnknownRecord} from './source/unknown-record.d.ts'; export type {UnknownArray} from './source/unknown-array.d.ts'; export type {UnknownSet} from './source/unknown-set.d.ts'; export type {UnknownMap} from './source/unknown-map.d.ts'; export type {Except, ExceptOptions} from './source/except.d.ts'; export type {TaggedUnion} from './source/tagged-union.d.ts'; export type {Writable} from './source/writable.d.ts'; export type {WritableDeep} from './source/writable-deep.d.ts'; export type {Merge} from './source/merge.d.ts'; export type {ObjectMerge} from './source/object-merge.d.ts'; export type {MergeDeep, MergeDeepOptions} from './source/merge-deep.d.ts'; export type {MergeExclusive} from './source/merge-exclusive.d.ts'; export type {RequireAtLeastOne} from './source/require-at-least-one.d.ts'; export type {RequireExactlyOne} from './source/require-exactly-one.d.ts'; export type {RequireAllOrNone} from './source/require-all-or-none.d.ts'; export type {RequireOneOrNone} from './source/require-one-or-none.d.ts'; export type {SingleKeyObject} from './source/single-key-object.d.ts'; export type {OmitIndexSignature} from './source/omit-index-signature.d.ts'; export type {PickIndexSignature} from './source/pick-index-signature.d.ts'; export type {PartialDeep, PartialDeepOptions} from './source/partial-deep.d.ts'; export type {UnwrapPartial} from './source/unwrap-partial.d.ts'; export type {RequiredDeep} from './source/required-deep.d.ts'; export type {PickDeep} from './source/pick-deep.d.ts'; export type {OmitDeep} from './source/omit-deep.d.ts'; export type {PartialOnUndefinedDeep, PartialOnUndefinedDeepOptions} from './source/partial-on-undefined-deep.d.ts'; export type {UndefinedOnPartialDeep} from './source/undefined-on-partial-deep.d.ts'; export type {ReadonlyDeep} from './source/readonly-deep.d.ts'; export type {LiteralUnion} from './source/literal-union.d.ts'; export type {Promisable} from './source/promisable.d.ts'; export type {Arrayable} from './source/arrayable.d.ts'; export type {Optional} from './source/optional.d.ts'; export type {Opaque, UnwrapOpaque, Tagged, GetTagMetadata, UnwrapTagged} from './source/tagged.d.ts'; export type {InvariantOf} from './source/invariant-of.d.ts'; export type {SetOptional} from './source/set-optional.d.ts'; export type {SetReadonly} from './source/set-readonly.d.ts'; export type {SetRequired} from './source/set-required.d.ts'; export type {SetRequiredDeep} from './source/set-required-deep.d.ts'; export type {SetNonNullable} from './source/set-non-nullable.d.ts'; export type {SetNonNullableDeep} from './source/set-non-nullable-deep.d.ts'; export type {ValueOf} from './source/value-of.d.ts'; export type {AsyncReturnType} from './source/async-return-type.d.ts'; export type {ConditionalExcept} from './source/conditional-except.d.ts'; export type {ConditionalKeys} from './source/conditional-keys.d.ts'; export type {ConditionalPick} from './source/conditional-pick.d.ts'; export type {ConditionalPickDeep, ConditionalPickDeepOptions} from './source/conditional-pick-deep.d.ts'; export type {UnionToIntersection} from './source/union-to-intersection.d.ts'; export type {Stringified} from './source/stringified.d.ts'; export type {StringSlice} from './source/string-slice.d.ts'; export type {FixedLengthArray} from './source/fixed-length-array.d.ts'; export type {MultidimensionalArray} from './source/multidimensional-array.d.ts'; export type {MultidimensionalReadonlyArray} from './source/multidimensional-readonly-array.d.ts'; export type {IterableElement} from './source/iterable-element.d.ts'; export type {Entry} from './source/entry.d.ts'; export type {Entries} from './source/entries.d.ts'; export type {SetReturnType} from './source/set-return-type.d.ts'; export type {SetParameterType} from './source/set-parameter-type.d.ts'; export type {Asyncify} from './source/asyncify.d.ts'; export type {Simplify} from './source/simplify.d.ts'; export type {SimplifyDeep} from './source/simplify-deep.d.ts'; export type {Jsonify} from './source/jsonify.d.ts'; export type {Jsonifiable} from './source/jsonifiable.d.ts'; export type {StructuredCloneable} from './source/structured-cloneable.d.ts'; export type {Schema, SchemaOptions} from './source/schema.d.ts'; export type {LiteralToPrimitive} from './source/literal-to-primitive.d.ts'; export type {LiteralToPrimitiveDeep} from './source/literal-to-primitive-deep.d.ts'; export type { PositiveInfinity, NegativeInfinity, Finite, Integer, Float, NegativeFloat, Negative, NonNegative, NegativeInteger, NonNegativeInteger, IsNegative, } from './source/numeric.d.ts'; export type {GreaterThan} from './source/greater-than.d.ts'; export type {GreaterThanOrEqual} from './source/greater-than-or-equal.d.ts'; export type {LessThan} from './source/less-than.d.ts'; export type {LessThanOrEqual} from './source/less-than-or-equal.d.ts'; export type {Sum} from './source/sum.d.ts'; export type {Subtract} from './source/subtract.d.ts'; export type {KeyAsString} from './source/key-as-string.d.ts'; export type {Exact} from './source/exact.d.ts'; export type {ReadonlyTuple} from './source/readonly-tuple.d.ts'; export type {OverrideProperties} from './source/override-properties.d.ts'; export type {OptionalKeysOf} from './source/optional-keys-of.d.ts'; export type {IsOptionalKeyOf} from './source/is-optional-key-of.d.ts'; export type {HasOptionalKeys} from './source/has-optional-keys.d.ts'; export type {RequiredKeysOf} from './source/required-keys-of.d.ts'; export type {IsRequiredKeyOf} from './source/is-required-key-of.d.ts'; export type {HasRequiredKeys} from './source/has-required-keys.d.ts'; export type {ReadonlyKeysOf} from './source/readonly-keys-of.d.ts'; export type {IsReadonlyKeyOf} from './source/is-readonly-key-of.d.ts'; export type {HasReadonlyKeys} from './source/has-readonly-keys.d.ts'; export type {WritableKeysOf} from './source/writable-keys-of.d.ts'; export type {IsWritableKeyOf} from './source/is-writable-key-of.d.ts'; export type {HasWritableKeys} from './source/has-writable-keys.d.ts'; export type {Spread} from './source/spread.d.ts'; export type {SplitOnRestElement} from './source/split-on-rest-element.d.ts'; export type {ExtractRestElement} from './source/extract-rest-element.d.ts'; export type {ExcludeRestElement} from './source/exclude-rest-element.d.ts'; export type {IsInteger} from './source/is-integer.d.ts'; export type {IsFloat} from './source/is-float.d.ts'; export type {TupleToObject} from './source/tuple-to-object.d.ts'; export type {TupleToUnion} from './source/tuple-to-union.d.ts'; export type {UnionToTuple} from './source/union-to-tuple.d.ts'; export type {IntRange} from './source/int-range.d.ts'; export type {IntClosedRange} from './source/int-closed-range.d.ts'; export type {IsEqual} from './source/is-equal.d.ts'; export type { IsLiteral, IsStringLiteral, IsNumericLiteral, IsBooleanLiteral, IsSymbolLiteral, } from './source/is-literal.d.ts'; export type {IsAny} from './source/is-any.d.ts'; export type {IfAny} from './source/if-any.d.ts'; export type {IsNever} from './source/is-never.d.ts'; export type {IfNever} from './source/if-never.d.ts'; export type {IsUnknown} from './source/is-unknown.d.ts'; export type {IfUnknown} from './source/if-unknown.d.ts'; export type {IsTuple, IsTupleOptions} from './source/is-tuple.d.ts'; export type {ArrayIndices} from './source/array-indices.d.ts'; export type {ArrayValues} from './source/array-values.d.ts'; export type {ArraySlice} from './source/array-slice.d.ts'; export type {ArraySplice} from './source/array-splice.d.ts'; export type {ArrayTail} from './source/array-tail.d.ts'; export type {ArrayElement} from './source/array-element.d.ts'; export type {ArrayLength} from './source/array-length.d.ts'; export type {SetFieldType, SetFieldTypeOptions} from './source/set-field-type.d.ts'; export type {Paths, PathsOptions} from './source/paths.d.ts'; export type {AllUnionFields} from './source/all-union-fields.d.ts'; export type {SharedUnionFields} from './source/shared-union-fields.d.ts'; export type {SharedUnionFieldsDeep, SharedUnionFieldsDeepOptions} from './source/shared-union-fields-deep.d.ts'; export type {IsNull} from './source/is-null.d.ts'; export type {IfNull} from './source/if-null.d.ts'; export type {IsUndefined} from './source/is-undefined.d.ts'; export type {And} from './source/and.d.ts'; export type {AndAll} from './source/and-all.d.ts'; export type {Or} from './source/or.d.ts'; export type {OrAll} from './source/or-all.d.ts'; export type {Xor} from './source/xor.d.ts'; export type {AllExtend, AllExtendOptions} from './source/all-extend.d.ts'; export type {SomeExtend, SomeExtendOptions} from './source/some-extend.d.ts'; export type {NonEmptyTuple} from './source/non-empty-tuple.d.ts'; export type {FindGlobalInstanceType, FindGlobalType} from './source/find-global-type.d.ts'; export type {If} from './source/if.d.ts'; export type {IsUnion} from './source/is-union.d.ts'; export type {IsLowercase} from './source/is-lowercase.d.ts'; export type {IsUppercase} from './source/is-uppercase.d.ts'; export type {IsOptional} from './source/is-optional.d.ts'; export type {IsNullable} from './source/is-nullable.d.ts'; export type {TupleOf} from './source/tuple-of.d.ts'; export type {ExclusifyUnion} from './source/exclusify-union.d.ts'; export type {ArrayReverse} from './source/array-reverse.d.ts'; export type {UnionMember} from './source/union-member.d.ts'; // Template literal types export type {CamelCase, CamelCaseOptions} from './source/camel-case.d.ts'; export type {CamelCasedProperties} from './source/camel-cased-properties.d.ts'; export type {CamelCasedPropertiesDeep} from './source/camel-cased-properties-deep.d.ts'; export type {KebabCase} from './source/kebab-case.d.ts'; export type {KebabCasedProperties} from './source/kebab-cased-properties.d.ts'; export type {KebabCasedPropertiesDeep} from './source/kebab-cased-properties-deep.d.ts'; export type {PascalCase} from './source/pascal-case.d.ts'; export type {PascalCasedProperties} from './source/pascal-cased-properties.d.ts'; export type {PascalCasedPropertiesDeep} from './source/pascal-cased-properties-deep.d.ts'; export type {SnakeCase} from './source/snake-case.d.ts'; export type {SnakeCasedProperties} from './source/snake-cased-properties.d.ts'; export type {SnakeCasedPropertiesDeep} from './source/snake-cased-properties-deep.d.ts'; export type {ScreamingSnakeCase} from './source/screaming-snake-case.d.ts'; export type {DelimiterCase} from './source/delimiter-case.d.ts'; export type {DelimiterCasedProperties} from './source/delimiter-cased-properties.d.ts'; export type {DelimiterCasedPropertiesDeep} from './source/delimiter-cased-properties-deep.d.ts'; export type {Join} from './source/join.d.ts'; export type {Split, SplitOptions} from './source/split.d.ts'; export type {Words, WordsOptions} from './source/words.d.ts'; export type {Trim} from './source/trim.d.ts'; export type {Replace, ReplaceOptions} from './source/replace.d.ts'; export type {StringRepeat} from './source/string-repeat.d.ts'; export type {Includes} from './source/includes.d.ts'; export type {Get, GetOptions} from './source/get.d.ts'; export type {LastArrayElement} from './source/last-array-element.d.ts'; export type {ConditionalSimplify} from './source/conditional-simplify.d.ts'; export type {ConditionalSimplifyDeep} from './source/conditional-simplify-deep.d.ts'; export type {RemovePrefix, RemovePrefixOptions} from './source/remove-prefix.d.ts'; // Miscellaneous export type {GlobalThis} from './source/global-this.d.ts'; export type {PackageJson} from './source/package-json.d.ts'; export type {TsConfigJson} from './source/tsconfig-json.d.ts'; // Improved built-in export type {ExtendsStrict} from './source/extends-strict.d.ts'; export type {ExtractStrict} from './source/extract-strict.d.ts'; export type {ExcludeStrict} from './source/exclude-strict.d.ts'; export type {ExcludeExactly} from './source/exclude-exactly.d.ts'; export {}; ================================================ FILE: license-cc0 ================================================ Creative Commons Legal Code CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. ================================================ FILE: license-mit ================================================ MIT License Copyright (c) Sindre Sorhus (https://sindresorhus.com) 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: lint-processors/fixtures/eslint.config.js ================================================ import tseslint from 'typescript-eslint'; import {defineConfig} from 'eslint/config'; import {jsdocCodeblocksProcessor} from '../jsdoc-codeblocks.js'; const errorEndingAtFirstColumnRule = { create(context) { return { 'TSTypeAliasDeclaration Literal'(node) { if (node.value !== 'error_ending_at_first_column') { return; } context.report({ loc: { start: { line: node.loc.start.line, column: 0, }, end: { line: node.loc.start.line + 1, column: 0, }, }, message: 'Error ending at first column', }); }, }; }, }; const config = defineConfig( tseslint.configs.recommended, tseslint.configs.stylistic, { rules: { '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/consistent-type-definitions': [ 'error', 'type', ], 'test/error-ending-at-first-column': 'error', }, }, { plugins: { test: { processors: { 'jsdoc-codeblocks': jsdocCodeblocksProcessor, }, rules: { 'error-ending-at-first-column': errorEndingAtFirstColumnRule, }, }, }, }, { files: ['**/*.d.ts'], processor: 'test/jsdoc-codeblocks', }, ); export default config; ================================================ FILE: lint-processors/jsdoc-codeblocks.js ================================================ // @ts-check import tsParser from '@typescript-eslint/parser'; /** @import {Linter} from 'eslint'; */ const CODEBLOCK_REGEX = /(?(?^[ \t]*)```(?:ts|typescript)?\n)(?[\s\S]*?)\n\s*```/gm; /** @typedef {{lineOffset: number, characterOffset: number, indent: string, unindentedText: string}} CodeblockData @type {Map} */ const codeblockDataPerFile = new Map(); /** @param {string} text @param {number} index @param {string} indent @returns {number} */ function indentsUptoIndex(text, index, indent) { let i = 0; let indents = 0; for (const line of text.split('\n')) { if (i > index) { break; } if (line === '') { i += 1; // +1 for the newline continue; } i += line.length + 1; // +1 for the newline i -= indent.length; // Because `text` is unindented but `index` corresponds to dedented text indents += indent.length; } return indents; } export const jsdocCodeblocksProcessor = { supportsAutofix: true, /** @param {string} text @param {string} filename @returns {(string | Linter.ProcessorFile)[]} */ preprocess(text, filename) { const ast = tsParser.parse(text); const jsdocComments = ast.comments.filter( comment => comment.type === 'Block' && comment.value.startsWith('*'), ); /** @type {(string | Linter.ProcessorFile)[]} */ const files = [text]; // First entry is for the entire file /** @type {CodeblockData[]} */ const allCodeblocksData = []; // Loop over all JSDoc comments in the file for (const comment of jsdocComments) { // Loop over all codeblocks in the JSDoc comment for (const match of comment.value.matchAll(CODEBLOCK_REGEX)) { const {code, openingFence, indent} = match.groups ?? {}; // Skip empty code blocks if (!code || !openingFence || indent === undefined) { continue; } const codeLines = code.split('\n'); const indentSize = indent.length; // Skip comments that are not consistently indented if (!codeLines.every(line => line === '' || line.startsWith(indent))) { continue; } const dedentedCode = codeLines .map(line => line.slice(indentSize)) .join('\n'); files.push({ text: dedentedCode, filename: `${files.length}.ts`, // Final filename example: `/path/to/type-fest/source/and.d.ts/1_1.ts` }); const linesBeforeMatch = comment.value.slice(0, match.index).split('\n').length - 1; allCodeblocksData.push({ lineOffset: comment.loc.start.line + linesBeforeMatch, characterOffset: comment.range[0] + match.index + openingFence.length + 2, // +2 because `comment.value` doesn't include the starting `/*` indent, unindentedText: code, }); } } codeblockDataPerFile.set(filename, allCodeblocksData); return files; }, /** @param {import('eslint').Linter.LintMessage[][]} messages @param {string} filename @returns {import('eslint').Linter.LintMessage[]} */ postprocess(messages, filename) { const codeblocks = codeblockDataPerFile.get(filename) || []; codeblockDataPerFile.delete(filename); const normalizedMessages = [...(messages[0] ?? [])]; // First entry contains errors for the entire file, and it doesn't need any adjustments for (const [index, codeblockMessages] of messages.slice(1).entries()) { const codeblockData = codeblocks[index]; if (!codeblockData) { // This should ideally never happen continue; } const {lineOffset, characterOffset, indent, unindentedText} = codeblockData; for (const message of codeblockMessages) { message.line += lineOffset; message.column += indent.length; if (typeof message.endColumn === 'number' && message.endColumn > 1) { // An `endColumn` of `1` indicates the error actually ended on the previous line since it's exclusive. // So, adding `indent.length` in this case would incorrectly move the error marker into the indentation. // Therefore, the indentation length is only added when `endColumn` is greater than `1`. message.endColumn += indent.length; } if (typeof message.endLine === 'number') { message.endLine += lineOffset; } if (message.fix) { message.fix.text = message.fix.text.split('\n').join(`\n${indent}`); const indentsBeforeFixStart = indentsUptoIndex(unindentedText, message.fix.range[0], indent); const indentsBeforeFixEnd = indentsUptoIndex(unindentedText, message.fix.range[1] - 1, indent); // -1 because range end is exclusive message.fix.range = [ message.fix.range[0] + characterOffset + indentsBeforeFixStart, message.fix.range[1] + characterOffset + indentsBeforeFixEnd, ]; } for (const {fix} of (message.suggestions ?? [])) { fix.text = fix.text.split('\n').join(`\n${indent}`); const indentsBeforeFixStart = indentsUptoIndex(unindentedText, fix.range[0], indent); const indentsBeforeFixEnd = indentsUptoIndex(unindentedText, fix.range[1] - 1, indent); // -1 because range end is exclusive fix.range = [ fix.range[0] + characterOffset + indentsBeforeFixStart, fix.range[1] + characterOffset + indentsBeforeFixEnd, ]; } normalizedMessages.push(message); } } return normalizedMessages; }, }; ================================================ FILE: lint-processors/jsdoc-codeblocks.test.js ================================================ import {describe, test} from 'node:test'; import fs from 'node:fs/promises'; import path from 'node:path'; import {ESLint} from 'eslint'; import {code1, code2, dedenter, errorAt, exportType, exportTypeAndOption, fence, jsdoc} from '../lint-rules/test-utils.js'; const root = path.join(import.meta.dirname, 'fixtures'); try { await fs.access(path.join(root, 'eslint.config.js')); } catch { throw new Error('\'eslint.config.js\' is missing in \'lint-processors/fixtures\' directory.'); } const valid = [ { name: 'No JSDoc', code: exportTypeAndOption(''), }, { name: 'JSDoc without code block', code: exportType(jsdoc('No codeblock here')), }, { name: 'Valid code block', code: exportTypeAndOption(jsdoc(fence(code1))), }, { name: 'With text before and after', code: exportTypeAndOption(jsdoc('Some description.', fence(code1), '@category Test')), }, { name: 'With line breaks before and after', code: exportTypeAndOption( jsdoc('Some description.\n', 'Note: Some note.\n', fence(code1, 'ts'), '\n@category Test'), ), }, { name: 'With @example tag', code: exportTypeAndOption(jsdoc('@example', fence(code1))), }, { name: 'With ts language specifier', code: exportTypeAndOption(jsdoc(fence(code1, 'ts'))), }, { name: 'With typescript language specifier', code: exportTypeAndOption(jsdoc(fence(code1, 'typescript'))), }, { name: 'Multiple code blocks', code: exportTypeAndOption( jsdoc('@example', fence(code1, 'ts'), '\nSome text in between.\n', '@example', fence(code2)), ), }, { name: 'Multiple exports and multiple properties', code: exportTypeAndOption(jsdoc(fence(code1)), jsdoc(fence(code2))), }, { name: 'Indented code blocks', code: exportTypeAndOption(jsdoc( 'Note:', dedenter` 1. First point \`\`\`ts import type {Subtract} from 'type-fest'; type A = Subtract<1, 2>; \`\`\` 2. Second point \`\`\`ts import type {Sum} from 'type-fest'; type A = Sum<1, 2>; \`\`\` `, )), }, { name: 'Ignore codeblocks with inconsistent indentation', code: exportTypeAndOption(jsdoc( 'Some description.', dedenter` Note: @example \`\`\`ts const foo: string = '1'; const bar: number = 1; \`\`\` `, )), }, ]; const invalid = [ { name: 'With text before and after', code: dedenter` /** Some description. \`\`\`ts const foo: Array = []; \`\`\` @category Test */ export type T0 = string; export type TOptions = { /** Some description. \`\`\`ts const foo: Array = []; \`\`\` @category Test */ p0: string; }; `, output: dedenter` /** Some description. \`\`\`ts const foo: string[] = []; \`\`\` @category Test */ export type T0 = string; export type TOptions = { /** Some description. \`\`\`ts const foo: string[] = []; \`\`\` @category Test */ p0: string; }; `, errors: [ errorAt({ ruleId: '@typescript-eslint/array-type', line: 4, textBeforeStart: 'const foo: ', target: 'Array', }), errorAt({ ruleId: '@typescript-eslint/array-type', line: 14, textBeforeStart: '\tconst foo: ', target: 'Array', }), ], }, { name: 'With line breaks before and after', code: dedenter` /** Some description. Note: Some note. \`\`\`ts const foo: number = 1; \`\`\` @category Test */ export type T0 = string; export type TOptions = { /** Some description. Note: Some note. \`\`\`ts const foo: number = 1; \`\`\` @category Test */ p0: string; }; `, output: dedenter` /** Some description. Note: Some note. \`\`\`ts const foo = 1; \`\`\` @category Test */ export type T0 = string; export type TOptions = { /** Some description. Note: Some note. \`\`\`ts const foo = 1; \`\`\` @category Test */ p0: string; }; `, errors: [ errorAt({ ruleId: '@typescript-eslint/no-inferrable-types', line: 7, textBeforeStart: 'const ', target: 'foo: number = 1', }), errorAt({ ruleId: '@typescript-eslint/no-inferrable-types', line: 21, textBeforeStart: '\tconst ', target: 'foo: number = 1', }), ], }, { name: 'With @example tag', code: dedenter` /** @example \`\`\`ts interface Foo { a: string; b: number; } \`\`\` */ export type T0 = string; export type TOptions = { /** @example \`\`\`ts interface Foo { a: string; b: number; } \`\`\` */ p0: string; }; `, output: dedenter` /** @example \`\`\`ts type Foo = { a: string; b: number; } \`\`\` */ export type T0 = string; export type TOptions = { /** @example \`\`\`ts type Foo = { a: string; b: number; } \`\`\` */ p0: string; }; `, errors: [ errorAt({ ruleId: '@typescript-eslint/consistent-type-definitions', line: 4, textBeforeStart: 'interface ', target: 'Foo', }), errorAt({ ruleId: '@typescript-eslint/consistent-type-definitions', line: 16, textBeforeStart: '\tinterface ', target: 'Foo', }), ], }, { name: 'With language specifiers', code: dedenter` /** \`\`\`ts const foo: Array = []; \`\`\` */ export type T0 = string; export type TOptions = { /** \`\`\`ts const foo: Array = []; \`\`\` */ p0: string; }; `, output: dedenter` /** \`\`\`ts const foo: string[] = []; \`\`\` */ export type T0 = string; export type TOptions = { /** \`\`\`ts const foo: string[] = []; \`\`\` */ p0: string; }; `, errors: [ errorAt({ ruleId: '@typescript-eslint/array-type', line: 3, textBeforeStart: 'const foo: ', target: 'Array', }), errorAt({ ruleId: '@typescript-eslint/array-type', line: 11, textBeforeStart: '\tconst foo: ', target: 'Array', }), ], }, { name: 'With typescript language specifiers', code: dedenter` /** \`\`\`typescript const foo: Array = []; \`\`\` */ export type T0 = string; export type TOptions = { /** \`\`\`typescript const foo: Array = []; \`\`\` */ p0: string; }; `, output: dedenter` /** \`\`\`typescript const foo: string[] = []; \`\`\` */ export type T0 = string; export type TOptions = { /** \`\`\`typescript const foo: string[] = []; \`\`\` */ p0: string; }; `, errors: [ errorAt({ ruleId: '@typescript-eslint/array-type', line: 3, textBeforeStart: 'const foo: ', target: 'Array', }), errorAt({ ruleId: '@typescript-eslint/array-type', line: 11, textBeforeStart: '\tconst foo: ', target: 'Array', }), ], }, { name: 'Multiple code blocks', code: dedenter` /** @example \`\`\`ts const foo: Array = []; \`\`\` Some text in between. @example \`\`\`ts const bar: number = 1; \`\`\` */ export type T0 = string; export type TOptions = { /** @example \`\`\`ts const foo: Array = []; \`\`\` Some text in between. @example \`\`\`ts const bar: number = 1; \`\`\` */ p0: string; }; `, output: dedenter` /** @example \`\`\`ts const foo: string[] = []; \`\`\` Some text in between. @example \`\`\`ts const bar = 1; \`\`\` */ export type T0 = string; export type TOptions = { /** @example \`\`\`ts const foo: string[] = []; \`\`\` Some text in between. @example \`\`\`ts const bar = 1; \`\`\` */ p0: string; }; `, errors: [ errorAt({ ruleId: '@typescript-eslint/array-type', line: 4, textBeforeStart: 'const foo: ', target: 'Array', }), errorAt({ ruleId: '@typescript-eslint/no-inferrable-types', line: 11, textBeforeStart: 'const ', target: 'bar: number = 1', }), errorAt({ ruleId: '@typescript-eslint/array-type', line: 20, textBeforeStart: '\tconst foo: ', target: 'Array', }), errorAt({ ruleId: '@typescript-eslint/no-inferrable-types', line: 27, textBeforeStart: '\tconst ', target: 'bar: number = 1', }), ], }, { name: 'Multiple exports and multiple properties', code: dedenter` /** \`\`\`ts function foo(example: {(): number}): number { return example(); } \`\`\` */ export type T0 = string; /** \`\`\`ts type Foo = { [key: string]: unknown; }; \`\`\` */ export type T1 = string; export type T0Options = { /** \`\`\`ts function foo(example: {(): number}): number { return example(); } \`\`\` */ p0: string; /** \`\`\`ts type Foo = { [key: string]: unknown; }; \`\`\` */ p1: string; }; export type T1Options = { /** \`\`\`ts const foo: Map = new Map(); \`\`\` */ p0: string; }; `, output: dedenter` /** \`\`\`ts function foo(example: () => number): number { return example(); } \`\`\` */ export type T0 = string; /** \`\`\`ts type Foo = Record; \`\`\` */ export type T1 = string; export type T0Options = { /** \`\`\`ts function foo(example: () => number): number { return example(); } \`\`\` */ p0: string; /** \`\`\`ts type Foo = Record; \`\`\` */ p1: string; }; export type T1Options = { /** \`\`\`ts const foo = new Map(); \`\`\` */ p0: string; }; `, errors: [ errorAt({ ruleId: '@typescript-eslint/prefer-function-type', line: 3, textBeforeStart: 'function foo(example: {', target: '(): number', }), errorAt({ ruleId: '@typescript-eslint/consistent-indexed-object-style', line: 12, textBeforeStart: 'type Foo = ', endLine: 14, textBeforeEnd: '}', }), errorAt({ ruleId: '@typescript-eslint/prefer-function-type', line: 22, textBeforeStart: '\tfunction foo(example: {', target: '(): number', }), errorAt({ ruleId: '@typescript-eslint/consistent-indexed-object-style', line: 31, textBeforeStart: '\ttype Foo = ', endLine: 33, textBeforeEnd: '\t}', }), errorAt({ ruleId: '@typescript-eslint/consistent-generic-constructors', line: 42, textBeforeStart: '\tconst ', target: 'foo: Map = new Map()', }), ], }, { name: 'Indented code blocks', code: dedenter` /** Note: 1. First point \`\`\`ts type Foo = { [x: string]: unknown; }; \`\`\` 2. Second point \`\`\`ts type Bar = { (arg: string): number; }; \`\`\` */ export type T0 = string; export type TOptions = { /** Note: 1. First point \`\`\`ts type Foo = { [x: string]: unknown; }; \`\`\` 2. Second point \`\`\`ts type Bar = { (arg: string): number; }; \`\`\` */ p0: string; }; `, output: dedenter` /** Note: 1. First point \`\`\`ts type Foo = Record; \`\`\` 2. Second point \`\`\`ts type Bar = (arg: string) => number; \`\`\` */ export type T0 = string; export type TOptions = { /** Note: 1. First point \`\`\`ts type Foo = Record; \`\`\` 2. Second point \`\`\`ts type Bar = (arg: string) => number; \`\`\` */ p0: string; }; `, errors: [ errorAt({ ruleId: '@typescript-eslint/consistent-indexed-object-style', line: 5, textBeforeStart: '\ttype Foo = ', endLine: 7, textBeforeEnd: '\t}', }), errorAt({ ruleId: '@typescript-eslint/prefer-function-type', line: 12, textBeforeStart: '\t\t', target: '(arg: string): number;', }), errorAt({ ruleId: '@typescript-eslint/consistent-indexed-object-style', line: 23, textBeforeStart: '\t\ttype Foo = ', endLine: 25, textBeforeEnd: '\t\t}', }), errorAt({ ruleId: '@typescript-eslint/prefer-function-type', line: 30, textBeforeStart: '\t\t\t', target: '(arg: string): number;', }), ], }, { name: 'Error and fix starting at the first character of the codeblock', code: dedenter` /** Some description. \`\`\`ts var foo = 1; foo = 2; \`\`\` */ export type T0 = string; export type TOptions = { /** Some description. \`\`\`ts var foo = 1; foo = 2; \`\`\` */ p0: string; }; `, output: dedenter` /** Some description. \`\`\`ts let foo = 1; foo = 2; \`\`\` */ export type T0 = string; export type TOptions = { /** Some description. \`\`\`ts let foo = 1; foo = 2; \`\`\` */ p0: string; }; `, errors: [ errorAt({ ruleId: 'no-var', line: 4, textBeforeStart: '', target: 'var foo = 1;', }), errorAt({ ruleId: 'no-var', line: 14, textBeforeStart: '\t', target: 'var foo = 1;', }), ], }, { name: 'Error and fix in the middle of the codeblock', code: dedenter` /** \`\`\`ts const foo: string[] = []; const bar: Array = []; const baz: boolean[] = []; \`\`\` Some text after. */ export type T0 = string; export type TOptions = { /** \`\`\`ts const foo: string[] = []; const bar: Array = []; const baz: boolean[] = []; \`\`\` Some text after. */ p0: string; }; `, output: dedenter` /** \`\`\`ts const foo: string[] = []; const bar: number[] = []; const baz: boolean[] = []; \`\`\` Some text after. */ export type T0 = string; export type TOptions = { /** \`\`\`ts const foo: string[] = []; const bar: number[] = []; const baz: boolean[] = []; \`\`\` Some text after. */ p0: string; }; `, errors: [ errorAt({ ruleId: '@typescript-eslint/array-type', line: 5, textBeforeStart: 'const bar: ', target: 'Array', }), errorAt({ ruleId: '@typescript-eslint/array-type', line: 18, textBeforeStart: '\tconst bar: ', target: 'Array', }), ], }, { name: 'Error and fix ending at the last character of the codeblock', code: dedenter` /** Some description. @example \`\`\`ts type Foo = {a: string} type Bar = {[K: string]: Foo} \`\`\` */ export type T0 = string; export type TOptions = { /** Some description. @example \`\`\`ts type Foo = {a: string} type Bar = {[K: string]: Foo} \`\`\` */ p0: string; }; `, output: dedenter` /** Some description. @example \`\`\`ts type Foo = {a: string} type Bar = Record \`\`\` */ export type T0 = string; export type TOptions = { /** Some description. @example \`\`\`ts type Foo = {a: string} type Bar = Record \`\`\` */ p0: string; }; `, errors: [ errorAt({ ruleId: '@typescript-eslint/consistent-indexed-object-style', line: 7, textBeforeStart: 'type Bar = ', target: '{[K: string]: Foo}', }), errorAt({ ruleId: '@typescript-eslint/consistent-indexed-object-style', line: 19, textBeforeStart: '\ttype Bar = ', target: '{[K: string]: Foo}', }), ], }, { name: 'Error spanning multiple lines', code: dedenter` /** \`\`\`ts import type {PickIndexSignature} from 'type-fest'; type Foo = { [key: string]: unknown; }; type Test = PickIndexSignature; \`\`\` */ export type T0 = string; export type TOptions = { /** \`\`\`ts import type {PickIndexSignature} from 'type-fest'; type Foo = { [key: string]: unknown; }; type Test = PickIndexSignature; \`\`\` */ p0: string; }; `, output: dedenter` /** \`\`\`ts import type {PickIndexSignature} from 'type-fest'; type Foo = Record; type Test = PickIndexSignature; \`\`\` */ export type T0 = string; export type TOptions = { /** \`\`\`ts import type {PickIndexSignature} from 'type-fest'; type Foo = Record; type Test = PickIndexSignature; \`\`\` */ p0: string; }; `, errors: [ errorAt({ ruleId: '@typescript-eslint/consistent-indexed-object-style', line: 5, textBeforeStart: 'type Foo = ', endLine: 7, textBeforeEnd: '}', }), errorAt({ ruleId: '@typescript-eslint/consistent-indexed-object-style', line: 19, textBeforeStart: '\ttype Foo = ', endLine: 21, textBeforeEnd: '\t}', }), ], }, { name: 'Multiline fix', code: dedenter` /** Some description. Some more description. @example \`\`\`ts type Test = {( foo: string, bar: number ): void}; \`\`\` @category Test */ export type T0 = string; export type TOptions = { /** Some description. Some more description. @example \`\`\`ts type Test = {( foo: string, bar: number ): void}; \`\`\` @category Test */ p0: string; }; `, output: dedenter` /** Some description. Some more description. @example \`\`\`ts type Test = ( foo: string, bar: number ) => void; \`\`\` @category Test */ export type T0 = string; export type TOptions = { /** Some description. Some more description. @example \`\`\`ts type Test = ( foo: string, bar: number ) => void; \`\`\` @category Test */ p0: string; }; `, errors: [ errorAt({ ruleId: '@typescript-eslint/prefer-function-type', line: 7, textBeforeStart: 'type Test = {', endLine: 10, textBeforeEnd: '): void', }), errorAt({ ruleId: '@typescript-eslint/prefer-function-type', line: 23, textBeforeStart: '\ttype Test = {', endLine: 26, textBeforeEnd: '\t): void', }), ], }, { name: 'Multiple errors', code: dedenter` /** @example \`\`\`typescript const foo: number = 1 const bar: Map = new Map() interface Baz { (x: string): unknown } \`\`\` */ export type T0 = string; export type TOptions = { /** @example \`\`\`typescript const foo: number = 1 const bar: Map = new Map() interface Baz { (x: string): unknown } \`\`\` */ p0: string; }; `, output: dedenter` /** @example \`\`\`typescript const foo = 1 const bar = new Map() type Baz = (x: string) => unknown \`\`\` */ export type T0 = string; export type TOptions = { /** @example \`\`\`typescript const foo = 1 const bar = new Map() type Baz = (x: string) => unknown \`\`\` */ p0: string; }; `, errors: [ errorAt({ ruleId: '@typescript-eslint/no-inferrable-types', line: 4, textBeforeStart: 'const ', target: 'foo: number = 1', }), errorAt({ ruleId: '@typescript-eslint/consistent-generic-constructors', line: 6, textBeforeStart: 'const ', target: 'bar: Map = new Map()', }), errorAt({ ruleId: '@typescript-eslint/consistent-type-definitions', line: 8, textBeforeStart: 'interface ', target: 'Baz', }), errorAt({ ruleId: '@typescript-eslint/prefer-function-type', line: 9, textBeforeStart: '\t', target: '(x: string): unknown', }), errorAt({ ruleId: '@typescript-eslint/no-inferrable-types', line: 19, textBeforeStart: '\tconst ', target: 'foo: number = 1', }), errorAt({ ruleId: '@typescript-eslint/consistent-generic-constructors', line: 21, textBeforeStart: '\tconst ', target: 'bar: Map = new Map()', }), errorAt({ ruleId: '@typescript-eslint/consistent-type-definitions', line: 23, textBeforeStart: '\tinterface ', target: 'Baz', }), errorAt({ ruleId: '@typescript-eslint/prefer-function-type', line: 24, textBeforeStart: '\t\t', target: '(x: string): unknown', }), ], }, { name: 'Overlapping errors', code: dedenter` /** \`\`\`ts const foo: Array> = []; \`\`\` */ export type T0 = string; export type TOptions = { /** \`\`\`ts const foo: Array> = []; \`\`\` */ p0: string; }; `, output: dedenter` /** \`\`\`ts const foo: string[][] = []; \`\`\` */ export type T0 = string; export type TOptions = { /** \`\`\`ts const foo: string[][] = []; \`\`\` */ p0: string; }; `, errors: [ errorAt({ ruleId: '@typescript-eslint/array-type', line: 3, textBeforeStart: 'const foo: ', target: 'Array>', }), errorAt({ ruleId: '@typescript-eslint/array-type', line: 3, textBeforeStart: 'const foo: Array<', target: 'Array', }), errorAt({ ruleId: '@typescript-eslint/array-type', line: 11, textBeforeStart: '\tconst foo: ', target: 'Array>', }), errorAt({ ruleId: '@typescript-eslint/array-type', line: 11, textBeforeStart: '\tconst foo: Array<', target: 'Array', }), ], }, { name: 'Error reporting location different from fix location', code: dedenter` /** \`\`\`ts type Foo = { (): void; }; \`\`\` */ export type T0 = string; export type TOptions = { /** \`\`\`ts type Foo = { (): void; }; \`\`\` */ p0: string; }; `, output: dedenter` /** \`\`\`ts type Foo = () => void; \`\`\` */ export type T0 = string; export type TOptions = { /** \`\`\`ts type Foo = () => void; \`\`\` */ p0: string; }; `, errors: [ errorAt({ ruleId: '@typescript-eslint/prefer-function-type', line: 4, textBeforeStart: '\t', target: '(): void;', }), errorAt({ ruleId: '@typescript-eslint/prefer-function-type', line: 14, textBeforeStart: '\t\t', target: '(): void;', }), ], }, { name: 'Non fixable error', code: dedenter` /** Some description. @example \`\`\` type Foo = {}; \`\`\` */ export type T0 = string; export type TOptions = { /** Some description. @example \`\`\` type Foo = {}; \`\`\` */ p0: string; }; `, output: undefined, errors: [ errorAt({ ruleId: '@typescript-eslint/no-empty-object-type', line: 5, textBeforeStart: 'type Foo = ', target: '{}', }), errorAt({ ruleId: '@typescript-eslint/no-empty-object-type', line: 15, textBeforeStart: '\ttype Foo = ', target: '{}', }), ], }, { name: 'Error outside JSDoc', code: dedenter` /** Some description. \`\`\` type Foo = string; \`\`\` */ export type T0 = Array; type Foo = String; `, output: dedenter` /** Some description. \`\`\` type Foo = string; \`\`\` */ export type T0 = string[]; type Foo = string; `, errors: [ errorAt({ ruleId: '@typescript-eslint/array-type', line: 7, textBeforeStart: 'export type T0 = ', target: 'Array', }), errorAt({ ruleId: '@typescript-eslint/no-wrapper-object-types', line: 9, textBeforeStart: 'type Foo = ', target: 'String', }), ], }, { name: 'Error ending at first column', code: dedenter` /** Some description. Note: @example \`\`\`ts type Foo = 'error_ending_at_first_column' type Bar = number \`\`\` */ export type T0 = string; export type TOptions = { /** Some description. Note: @example \`\`\`ts type Foo = 'error_ending_at_first_column' type Bar = number \`\`\` */ p0: string; }; `, output: undefined, errors: [ errorAt({ line: 7, textBeforeStart: '\t', endLine: 8, textBeforeEnd: '', }), errorAt({ line: 20, textBeforeStart: '\t\t', endLine: 21, textBeforeEnd: '', }), ], }, ]; describe('jsdoc-codeblocks processor', {concurrency: true}, () => { const eslint = new ESLint({cwd: root}); const eslintFixed = new ESLint({cwd: root, fix: true}); const testCases = [ ...valid.map(testCase => ({...testCase, type: 'valid'})), ...invalid.map(testCase => ({...testCase, type: 'invalid'})), ]; for (const {type, name, code, output, errors = []} of testCases) { test(`${type} - ${name}`, async t => { const fileName = `test-${type}-${name.replaceAll(/\s+/g, '-')}.d.ts`; const filePath = path.join(root, fileName); await fs.writeFile(filePath, code); t.after(async () => { await fs.unlink(filePath); }); const results = await eslint.lintFiles([fileName]); t.assert.strictEqual(results[0].messages.length, errors.length); if (type === 'invalid') { // Manual loop because `assert.partialDeepStrictEqual` isn't available in Node 20 for (const [index, expected] of errors.entries()) { const actual = results[0].messages[index]; const actualSubset = Object.fromEntries(Object.keys(expected).map(key => [key, actual[key]])); t.assert.deepStrictEqual(actualSubset, expected); } const resultsFixed = await eslintFixed.lintFiles([fileName]); t.assert.strictEqual(resultsFixed[0].output, output); } }); } }); ================================================ FILE: lint-rules/import-path.js ================================================ import path from 'node:path'; export const importPathRule = /** @type {const} */ ({ meta: { type: 'problem', docs: { description: 'Enforces import paths to end with a \'.d.ts\' extension.', }, fixable: 'code', messages: { incorrectImportPath: 'Import path \'{{importPath}}\' must end with a \'.d.ts\' extension. Use \'{{fixedImportPath}}\' instead.', }, schema: [], }, defaultOptions: [], create(context) { return { 'ImportDeclaration, ExportNamedDeclaration, ExportAllDeclaration'(node) { // Exit if not a re-export if (!node.source) { return; } const importPath = node.source.value; // Skip if not relative path if (!(importPath.startsWith('./') || importPath.startsWith('../'))) { return; } const filename = path.basename(importPath); const firstDotIndex = filename.indexOf('.'); const extension = firstDotIndex === -1 ? '' : filename.slice(firstDotIndex); // Skip if the import path already ends with `.d.ts` if (extension === '.d.ts') { return; } const importPathWithoutExtension = extension.length > 0 ? importPath.slice(0, -extension.length) : importPath; const fixedImportPath = `${importPathWithoutExtension}.d.ts`; context.report({ node: node.source, messageId: 'incorrectImportPath', fix(fixer) { return fixer.replaceText(node.source, `'${fixedImportPath}'`); }, data: {importPath, fixedImportPath}, }); }, }; }, }); ================================================ FILE: lint-rules/import-path.test.js ================================================ import {createRuleTester} from './test-utils.js'; import {importPathRule} from './import-path.js'; const ruleTester = createRuleTester(); const invalidImport = (code, output) => ({ code, errors: [{messageId: 'incorrectImportPath'}], output, }); ruleTester.run('import-path', importPathRule, { valid: [ // Already has .d.ts extension { code: 'import type {Foo} from "./foo.d.ts";', }, { code: 'import type {Bar} from "../bar.d.ts";', }, { code: 'import {Baz} from "./types/baz.d.ts";', }, // Non-relative imports are ignored { code: 'import {something} from "external-package";', }, { code: 'import type {Type} from "@types/node";', }, // Export named declarations with .d.ts extension { code: 'export type {Foo} from "./foo.d.ts";', }, { code: 'export {Bar} from "../bar.d.ts";', }, { code: 'export type {Baz as Qux} from "./types/baz.d.ts";', }, // Export all declarations with .d.ts extension { code: 'export * from "./foo.d.ts";', }, { code: 'export * from "../bar.d.ts";', }, { code: 'export * as Types from "./types.d.ts";', }, // Non re-exports are ignored { code: 'export {localVar};', }, { code: 'export type {LocalType};', }, { code: 'export type Foo = string;', }, // Non-relative exports are ignored { code: 'export {something} from "external-package";', }, { code: 'export * from "@types/node";', }, ], invalid: [ // Missing extension invalidImport( 'import type {Foo} from "./foo";', 'import type {Foo} from \'./foo.d.ts\';', ), // Wrong extension .ts invalidImport( 'import type {Bar} from "../bar.ts";', 'import type {Bar} from \'../bar.d.ts\';', ), // Wrong extension .js invalidImport( 'import type {Baz} from "./types.js";', 'import type {Baz} from \'./types.d.ts\';', ), // Deep path invalidImport( 'import type {Deep} from "../../deep/path.tsx";', 'import type {Deep} from \'../../deep/path.d.ts\';', ), // Export named declarations - missing extension invalidImport( 'export type {Foo} from "./foo";', 'export type {Foo} from \'./foo.d.ts\';', ), invalidImport( 'export {Bar} from "../bar";', 'export {Bar} from \'../bar.d.ts\';', ), // Export named declarations - wrong extension invalidImport( 'export type {Baz} from "./types.ts";', 'export type {Baz} from \'./types.d.ts\';', ), invalidImport( 'export {Qux} from "./qux.js";', 'export {Qux} from \'./qux.d.ts\';', ), // Export all declarations - missing extension invalidImport( 'export * from "./foo";', 'export * from \'./foo.d.ts\';', ), invalidImport( 'export * from "../bar";', 'export * from \'../bar.d.ts\';', ), // Export all declarations - wrong extension invalidImport( 'export * from "./types.ts";', 'export * from \'./types.d.ts\';', ), invalidImport( 'export * as AllTypes from "../../all.js";', 'export * as AllTypes from \'../../all.d.ts\';', ), // Wrong extension .d.d.ts invalidImport( 'import type {Foo} from "./foo.d.d.ts";', 'import type {Foo} from \'./foo.d.ts\';', ), ], }); ================================================ FILE: lint-rules/require-export.js ================================================ export const requireExportRule = /** @type {const} */ ({ meta: { type: 'problem', docs: { description: 'Ensure .d.ts files always have `export {}` to avoid being treated as global', category: 'Best Practices', recommended: true, }, messages: { noEmptyExport: 'File must have `export {}` to ensure it is treated as a module.', }, fixable: 'code', }, defaultOptions: [], create(context) { const filename = context.filename.replaceAll('\\', '/'); // Only check source .d.ts files if (!filename.includes('/source/') || !filename.endsWith('.d.ts')) { return {}; } let hasEmptyExport = false; return { ExportNamedDeclaration(node) { // Check if this is specifically `export {}` if (!node.declaration && node.specifiers.length === 0 && !node.source) { hasEmptyExport = true; } }, 'Program:exit'(node) { if (!hasEmptyExport) { context.report({ node, messageId: 'noEmptyExport', fix(fixer) { // Add export {} at the end of the file return fixer.insertTextAfter(node, '\nexport {};\n'); }, }); } }, }; }, }); ================================================ FILE: lint-rules/require-export.test.js ================================================ import {createRuleTester} from './test-utils.js'; import {requireExportRule} from './require-export.js'; const ruleTester = createRuleTester(); const missingEmptyExport = (code, filename, output) => ({ code, filename, errors: [{messageId: 'noEmptyExport'}], output, }); ruleTester.run('require-export', requireExportRule, { valid: [ // Has export {} { code: 'export {};', filename: '/source/foo.d.ts', }, // Has export {} with other exports { code: 'export type Foo = string;\nexport {};', filename: '/source/bar.d.ts', }, // Non-.d.ts files don't need export {} { code: 'const x = 1;', filename: '/source/foo.ts', }, { code: 'export const y = 2;', filename: '/source/bar.js', }, // Files outside source directory are ignored { code: 'type Test = string;', filename: '/test/foo.d.ts', }, { code: 'interface ITest {}', filename: '/lib/bar.d.ts', }, ], invalid: [ // Missing export {} in empty file missingEmptyExport('', '/source/foo.d.ts', '\nexport {};\n'), // Missing export {} with type declarations missingEmptyExport( 'export type Foo = string;', '/source/bar.d.ts', 'export type Foo = string;\nexport {};\n', ), // Missing export {} with interface missingEmptyExport( 'export interface IFoo {\n\tx: string;\n}', '/source/baz.d.ts', 'export interface IFoo {\n\tx: string;\n}\nexport {};\n', ), // Missing export {} with multiple exports missingEmptyExport( 'export type A = string;\nexport type B = number;', '/source/multi.d.ts', 'export type A = string;\nexport type B = number;\nexport {};\n', ), ], }); ================================================ FILE: lint-rules/require-exported-types.js ================================================ import path from 'node:path'; import fs from 'node:fs'; export const requireExportedTypesRule = /** @type {const} */ ({ meta: { type: 'suggestion', docs: { description: 'Enforce that exported types are also exported from index.d.ts', category: 'Best Practices', recommended: true, }, messages: { missingExport: 'Type `{{typeName}}` is exported from this file but not from index.d.ts. ' + 'Add it to index.d.ts or use `// eslint-disable-next-line type-fest/require-exported-types` to ignore.', noTypeInfo: 'Rule requires TypeScript type information. Configure `parserServices` in your ESLint config.', }, schema: [ { type: 'object', properties: { indexFile: { type: 'string', description: 'Path to the index file (default: "index.d.ts")', }, }, additionalProperties: false, }, ], }, defaultOptions: [], create(context) { // Only run on TypeScript declaration files in source directory // Convert to forward slashes for consistent path checking across platforms const filename = context.filename.replaceAll('\\', '/'); if (!filename.includes('/source/') || !filename.endsWith('.d.ts')) { return {}; } // Skip internal files if (filename.includes('/internal/')) { return {}; } const options = context.options?.[0] ?? {}; const indexFileName = options.indexFile ?? 'index.d.ts'; // Get TypeScript type-aware services const {parserServices} = context.sourceCode; // Type information is required for this rule if (!parserServices?.program || !parserServices?.esTreeNodeToTSNodeMap) { // Report once per file that type information is required return { 'Program'(node) { context.report({ node, messageId: 'noTypeInfo', }); }, }; } const {program} = parserServices; const checker = program.getTypeChecker(); // Find project root let projectRoot = path.dirname(filename); while (projectRoot !== path.dirname(projectRoot)) { if (fs.existsSync(path.join(projectRoot, 'package.json'))) { break; } projectRoot = path.dirname(projectRoot); } const indexPath = path.join(projectRoot, indexFileName); // Get index exports (no caching to detect changes immediately) const indexExports = new Set(); // Get the source file for index.d.ts const indexSourceFile = program.getSourceFile(indexPath); if (indexSourceFile) { const indexSymbol = checker.getSymbolAtLocation(indexSourceFile); if (indexSymbol) { const exports = checker.getExportsOfModule(indexSymbol); for (const exportSymbol of exports) { indexExports.add(exportSymbol.name); } } } // State to track processed nodes const processed = new Set(); // Helper function to check exported type/interface const checkExportedType = node => { const typeName = node.id.name; // Skip types starting with underscore (internal/private convention) if (typeName.startsWith('_')) { return; } // Skip if inside declare namespace const ancestors = context.sourceCode.getAncestors(node); const isInsideDeclareNamespace = ancestors.some(ancestor => ancestor.type === 'TSModuleDeclaration' && ancestor.declare, ); if (isInsideDeclareNamespace) { return; } // Skip if already processed if (processed.has(typeName)) { return; } processed.add(typeName); // Report if not exported from index if (!indexExports.has(typeName)) { context.report({ node, messageId: 'missingExport', data: {typeName}, }); } }; return { // Handle: export type Foo = ... 'ExportNamedDeclaration > TSTypeAliasDeclaration': checkExportedType, // Handle: export interface Foo { ... } 'ExportNamedDeclaration > TSInterfaceDeclaration': checkExportedType, // Clean up cache periodically }; }, }); ================================================ FILE: lint-rules/require-exported-types.test.js ================================================ import {createTypeAwareRuleTester} from './test-utils.js'; import {requireExportedTypesRule} from './require-exported-types.js'; const packageContent = JSON.stringify({ name: 'require-exported-types-fixture', }, null, '\t'); const indexContent = 'export {type ExportedType} from \'./source/exported-type\';\n'; const exportedTypeContent = 'export type ExportedType = {\n\tvalue: string;\n};\n'; const internalTypeContent = 'export type InternalType = {\n\tvalue: boolean;\n};\n'; const nestedInternalTypeContent = 'export interface NestedInternalType {\n\tvalue: string;\n}\n'; const privateTypeContent = 'export type _HiddenType = string;\n'; const missingTypeContent = 'export type MissingType = {\n\tvalue: number;\n};\n'; const unexportedMultipleContent = 'export type FirstUnexportedType = string;\nexport interface SecondUnexportedInterface {\n\tvalue: boolean;\n}\n'; const implementationContent = 'export const value = 1;\n'; const testDeclarationContent = 'export type Test = number;\n'; const libDeclarationContent = 'export interface LibTest {\n\tvalue: string;\n}\n'; const {ruleTester, fixturePath} = createTypeAwareRuleTester({ 'package.json': `${packageContent}\n`, 'index.d.ts': indexContent, 'source/exported-type.d.ts': undefined, 'source/internal/internal-type.d.ts': undefined, 'source/internal/nested/deep.d.ts': undefined, 'source/private-type.d.ts': undefined, 'source/missing-type.d.ts': undefined, 'source/unexported-multiple.d.ts': undefined, 'source/implementation.ts': undefined, 'test/test.d.ts': undefined, 'lib/test.d.ts': undefined, }); // Type-aware rule tests rely on the generated fixture project above. const missingExportError = typeName => ({ messageId: 'missingExport', data: {typeName}, }); ruleTester.run('require-exported-types', requireExportedTypesRule, { valid: [ { code: exportedTypeContent, filename: fixturePath('source/exported-type.d.ts'), }, { code: privateTypeContent, filename: fixturePath('source/private-type.d.ts'), }, { code: internalTypeContent, filename: fixturePath('source/internal/internal-type.d.ts'), }, { code: nestedInternalTypeContent, filename: fixturePath('source/internal/nested/deep.d.ts'), }, { code: implementationContent, filename: fixturePath('source/implementation.ts'), }, { code: testDeclarationContent, filename: fixturePath('test/test.d.ts'), }, { code: libDeclarationContent, filename: fixturePath('lib/test.d.ts'), }, ], invalid: [ { code: missingTypeContent, filename: fixturePath('source/missing-type.d.ts'), errors: [missingExportError('MissingType')], }, { code: unexportedMultipleContent, filename: fixturePath('source/unexported-multiple.d.ts'), errors: [ missingExportError('FirstUnexportedType'), missingExportError('SecondUnexportedInterface'), ], }, ], }); ================================================ FILE: lint-rules/source-files-extension.js ================================================ import path from 'node:path'; export const sourceFilesExtensionRule = /** @type {const} */ ({ meta: { type: 'problem', docs: { description: 'Enforces source files to end with a \'.d.ts\' extension.', }, fixable: 'code', messages: { incorrectFilename: 'Filename \'{{filename}}\' must end with a \'.d.ts\' extension. Use \'{{fixedFilename}}\' instead.', }, schema: [], }, defaultOptions: [], create(context) { const filename = path.basename(context.filename); const firstDotIndex = filename.indexOf('.'); const extension = firstDotIndex === -1 ? '' : filename.slice(firstDotIndex); if (extension === '.d.ts') { return {}; } return { Program(node) { const filenameWithoutExtension = extension.length > 0 ? filename.slice(0, -extension.length) : filename; const fixedFilename = `${filenameWithoutExtension}.d.ts`; context.report({ loc: {column: 0, line: 1}, // Report error on start of the file node, messageId: 'incorrectFilename', data: {filename, fixedFilename}, }); }, }; }, }); ================================================ FILE: lint-rules/source-files-extension.test.js ================================================ import {createRuleTester} from './test-utils.js'; import {sourceFilesExtensionRule} from './source-files-extension.js'; const ruleTester = createRuleTester(); const invalidSourceFile = (filename, code = '') => ({ code, filename, errors: [{messageId: 'incorrectFilename'}], }); ruleTester.run('source-files-extension', sourceFilesExtensionRule, { valid: [ // Correct .d.ts extension { code: '', filename: '/source/foo.d.ts', }, { code: '', filename: '/source/types/bar.d.ts', }, { code: '', filename: '/source/deeply/nested/file.d.ts', }, ], invalid: [ // Wrong .ts extension invalidSourceFile('/source/foo.ts'), // Wrong .js extension invalidSourceFile('/source/foo.js'), // No extension invalidSourceFile('/source/bar'), // Wrong .tsx extension invalidSourceFile('/source/component.tsx'), // Wrong .mjs extension invalidSourceFile('/source/module.mjs'), ], }); ================================================ FILE: lint-rules/test-utils.js ================================================ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import {RuleTester} from 'eslint'; import tsParser from '@typescript-eslint/parser'; import dedent from 'dedent'; export const createRuleTester = (overrides = {}) => { const { languageOptions: overrideLanguageOptions = {}, ...restOverrides } = overrides; const { parserOptions: overrideParserOptions = {}, ...otherLanguageOptions } = overrideLanguageOptions; return new RuleTester({ ...restOverrides, languageOptions: { ecmaVersion: 'latest', sourceType: 'module', parser: tsParser, ...otherLanguageOptions, parserOptions: { ...overrideParserOptions, }, }, }); }; const defaultTypeAwareTsconfig = { compilerOptions: { declaration: true, emitDeclarationOnly: true, module: 'ESNext', moduleResolution: 'Bundler', skipLibCheck: true, strict: true, target: 'ES2022', }, include: [ '*.d.ts', 'source/**/*.*', 'lib/**/*.d.ts', 'test/**/*.d.ts', ], }; export const createTypeAwareRuleTester = (fixtureFiles, options = {}) => { const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'type-fest-type-aware-')); const writeFixture = (relativePath, content) => { const absolutePath = path.join(fixtureRoot, relativePath); fs.mkdirSync(path.dirname(absolutePath), {recursive: true}); fs.writeFileSync(absolutePath, content ?? ''); }; for (const [relativePath, content] of Object.entries(fixtureFiles)) { writeFixture(relativePath, content); } const hasRuleTesterOption = Object.hasOwn(options, 'ruleTester'); const hasTsconfigOption = Object.hasOwn(options, 'tsconfig'); const ruleTesterOverrides = hasRuleTesterOption || hasTsconfigOption ? options.ruleTester ?? {} : options; const tsconfig = hasRuleTesterOption || hasTsconfigOption ? options.tsconfig ?? defaultTypeAwareTsconfig : defaultTypeAwareTsconfig; if (!('tsconfig.json' in fixtureFiles)) { writeFixture('tsconfig.json', `${JSON.stringify(tsconfig, null, '\t')}\n`); } const overrideLanguageOptions = ruleTesterOverrides.languageOptions ?? {}; const overrideParserOptions = overrideLanguageOptions.parserOptions ?? {}; const overrideProjectService = overrideParserOptions.projectService ?? {}; const ruleTester = createRuleTester({ ...ruleTesterOverrides, languageOptions: { ...overrideLanguageOptions, parserOptions: { ...overrideParserOptions, projectService: { allowDefaultProject: ['*.ts*'], ...overrideProjectService, }, tsconfigRootDir: fixtureRoot, }, }, }); const fixturePath = relativePath => path.join(fixtureRoot, relativePath); return { ruleTester, fixtureRoot, fixturePath, writeFixture, }; }; export const dedenter = dedent.withOptions({alignValues: true}); /** Returns the specified code in a fenced code block, with an optional language tag. @example ``` fence('type A = string;'); // Returns: // ``` // type A = string; // ``` fence(`import {RemovePrefix} from 'type-fest'; type A = RemovePrefix<'onChange', 'on'>; //=> 'Change'`, 'ts'); // Returns: // ```ts // import {RemovePrefix} from 'type-fest'; // type A = RemovePrefix<'onChange', 'on'>; // //=> 'Change' // ``` ``` */ export const fence = (code, lang = '') => dedenter` \`\`\`${lang} ${code} \`\`\` `; /** Returns the specified lines as a JSDoc comment, placing each specified line on a new line. @example ``` jsdoc('Some description.', 'Note: Some note.'); // Returns: // /** // Some description. // Note: Some note. // *​/ jsdoc('@example', '```\ntype A = string;\n```', '@category Test'); // Returns; // /** // @example // ``` // type A = string; // ``` // @category Test // *​/ ``` */ export const jsdoc = (...lines) => dedenter` /** ${lines.join('\n')} */ `; /** Returns an exported type for each provided prefix, with each prefix placed directly above its corresponding type declaration. @example ``` exportType( '// Some comment', 'type Test = string;', '/**\nSome description.\nNote: Some note.\n*​/' ); // Returns: // // Some comment // export type T0 = string; // // type Test = string; // export type T1 = string; // // /** // Some description. // Note: Some note. // *​/ // export type T2 = string; */ export const exportType = (...prefixes) => prefixes .map((doc, i) => dedenter` ${doc} export type T${i} = string; `) .join('\n\n'); /** Returns an exported "Options" object type containing a property for each specified prefix, with each prefix placed directly above its corresponding property declaration. @example ``` exportOption( '// Some comment', 'type Test = string;', '/**\nSome description.\nNote: Some note.\n*​/' ); // Returns: // export type TOptions = { // // Some comment // p0: string; // test: string; // p1: string; // /** // Some description. // Note: Some note. // *​/ // p2: string; // }; ``` */ export const exportOption = (...prefixes) => dedenter` export type TOptions = { ${prefixes .map((doc, i) => dedenter` ${doc} p${i}: string; `) .join('\n\n')} }; `; /** Returns an exported type for each provided prefix, and an exported "Options" object type containing a property for each specified prefix, with each prefix placed directly above its corresponding declaration. @example ``` exportTypeAndOption('// Some comment', '/**\nSome JSDoc\n*​/'); // Returns: // // Some comment // type T0 = string; // /** // Some JSDoc // *​/ // type T1 = string; // type TOptions = { // // Some comment // p0: string; // /** // Some JSDoc // *​/ // p1: string; // }; ``` */ export const exportTypeAndOption = (...prefixes) => dedenter` ${exportType(...prefixes)} ${exportOption(...prefixes)} `; /** @typedef {{ line: number; textBeforeStart: string; ruleId?: string; messageId?: string; } & ({ target: string } | { endLine: number; textBeforeEnd: string })} ErrorAtProps @param {ErrorAtProps} props @returns {{line: number, column: number, endLine: number, endColumn: number, ruleId?: string, messageId?: string}} */ export const errorAt = props => { const {line, textBeforeStart, ruleId, messageId} = props; const column = textBeforeStart.length + 1; const endColumn = 'textBeforeEnd' in props ? props.textBeforeEnd.length + 1 : column + props.target.length; const endLine = 'endLine' in props ? props.endLine : line; return { ...(ruleId && {ruleId}), ...(messageId && {messageId}), line, // 1-based, inclusive column, // 1-based, inclusive endLine, // 1-based, inclusive endColumn, // 1-based, exclusive }; }; /// Code samples export const code1 = dedenter` import type {Sum} from 'type-fest'; type A = Sum<1, 2>; //=> 3 `; export const code2 = dedenter` import type {LiteralToPrimitiveDeep} from 'type-fest'; const config = {appName: 'MyApp', version: '1.0.0'} as const; declare function updateConfig(newConfig: LiteralToPrimitiveDeep): void; updateConfig({appName: 'MyUpdatedApp', version: '2.0.0'}); `; ================================================ FILE: lint-rules/validate-jsdoc-codeblocks.js ================================================ import path from 'node:path'; import ts from 'typescript'; import {createFSBackedSystem, createVirtualTypeScriptEnvironment} from '@typescript/vfs'; const CODEBLOCK_REGEX = /(?```(?:ts|typescript)?\n)(?[\s\S]*?)```/g; const FILENAME = 'example-codeblock.ts'; const TWOSLASH_COMMENT = '//=>'; const compilerOptions = { lib: ['lib.es2023.d.ts', 'lib.dom.d.ts', 'lib.dom.iterable.d.ts'], target: ts.ScriptTarget.ESNext, module: ts.ModuleKind.Node20, moduleResolution: ts.ModuleResolutionKind.Node16, strict: true, noImplicitReturns: true, noImplicitOverride: true, noUnusedLocals: false, // This is intentionally disabled noUnusedParameters: true, noFallthroughCasesInSwitch: true, noUncheckedIndexedAccess: true, noPropertyAccessFromIndexSignature: true, noUncheckedSideEffectImports: true, useDefineForClassFields: true, exactOptionalPropertyTypes: true, }; const virtualFsMap = new Map(); virtualFsMap.set(FILENAME, '// Can\'t be empty'); const rootDir = path.join(import.meta.dirname, '..'); const system = createFSBackedSystem(virtualFsMap, rootDir, ts); const defaultEnv = createVirtualTypeScriptEnvironment(system, [FILENAME], ts, compilerOptions); function parseCompilerOptions(code) { const options = {}; const lines = code.split('\n'); for (const line of lines) { if (!line.trim()) { // Skip empty lines continue; } const match = line.match(/^\s*\/\/ @(\w+): (.*)$/); if (!match) { // Stop parsing at the first non-matching line return options; } const [, key, value] = match; const trimmedValue = value.trim(); try { options[key] = JSON.parse(trimmedValue); } catch { options[key] = trimmedValue; } } return options; } function getJSDocNode(sourceCode, node) { let previousToken = sourceCode.getTokenBefore(node, {includeComments: true}); // Skip over any line comments immediately before the node while (previousToken && previousToken.type === 'Line') { previousToken = sourceCode.getTokenBefore(previousToken, {includeComments: true}); } if (previousToken && previousToken.type === 'Block' && previousToken.value.startsWith('*')) { return previousToken; } return undefined; } export const validateJSDocCodeblocksRule = /** @type {const} */ ({ meta: { type: 'suggestion', docs: { description: 'Ensures JSDoc example codeblocks don\'t have errors', }, fixable: 'code', messages: { invalidCodeblock: '{{errorMessage}}', incorrectTwoslashType: 'Expected twoslash comment to be: {{expectedComment}}, but found: {{actualComment}}', incorrectTwoslashFormat: 'Expected twoslash comment to be: {{expectedComment}}, but found: {{actualComment}}', }, schema: [{ type: 'object', properties: { verbosityLevels: { type: 'array', uniqueItems: true, items: { minimum: 0, type: 'number', }, }, }, }], /** @type {unknown[]} */ defaultOptions: [{ verbosityLevels: [], }], }, create(context) { const filename = context.filename.replaceAll('\\', '/'); // Skip internal files if (filename.includes('/internal/')) { return {}; } try { defaultEnv.updateFile(context.filename, context.sourceCode.getText()); } catch { // Ignore } return { TSTypeAliasDeclaration(node) { const {parent} = node; // Skip if type is not exported or starts with an underscore (private/internal) if (parent.type !== 'ExportNamedDeclaration' || node.id.name.startsWith('_')) { return; } const previousNodes = []; const jsdocForExport = getJSDocNode(context.sourceCode, parent); if (jsdocForExport) { previousNodes.push(jsdocForExport); } // Handle JSDoc blocks for options if (node.id.name.endsWith('Options') && node.typeAnnotation.type === 'TSTypeLiteral') { for (const member of node.typeAnnotation.members) { const jsdocForMember = getJSDocNode(context.sourceCode, member); if (jsdocForMember) { previousNodes.push(jsdocForMember); } } } for (const previousNode of previousNodes) { const comment = previousNode.value; for (const match of comment.matchAll(CODEBLOCK_REGEX)) { const {code, openingFence} = match.groups ?? {}; // Skip empty code blocks if (!code || !openingFence) { continue; } const matchOffset = match.index + openingFence.length + 2; // Add `2` because `comment` doesn't include the starting `/*` const codeStartIndex = previousNode.range[0] + matchOffset; const overrides = parseCompilerOptions(code); let env = defaultEnv; if (Object.keys(overrides).length > 0) { const {options, errors} = ts.convertCompilerOptionsFromJson(overrides, rootDir); if (errors.length === 0) { // Create a new environment with overridden options env = createVirtualTypeScriptEnvironment(system, [FILENAME], ts, {...compilerOptions, ...options}); } } env.updateFile(FILENAME, code); const syntacticDiagnostics = env.languageService.getSyntacticDiagnostics(FILENAME); const semanticDiagnostics = env.languageService.getSemanticDiagnostics(FILENAME); const diagnostics = syntacticDiagnostics.length > 0 ? syntacticDiagnostics : semanticDiagnostics; // Show semantic errors only if there are no syntactic errors for (const diagnostic of diagnostics) { // If diagnostic location is not available, report on the entire code block const diagnosticStart = codeStartIndex + (diagnostic.start ?? 0); const diagnosticEnd = diagnosticStart + (diagnostic.length ?? code.length); context.report({ loc: { start: context.sourceCode.getLocFromIndex(diagnosticStart), end: context.sourceCode.getLocFromIndex(diagnosticEnd), }, messageId: 'invalidCodeblock', data: { errorMessage: ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'), }, }); } if (diagnostics.length === 0) { validateTwoslashTypes(context, env, code, codeStartIndex); } } } }, }; }, }); function getLeftmostQuickInfo(env, line, lineOffset, verbosityLevel) { for (let i = 0; i < line.length; i++) { const quickInfo = env.languageService.getQuickInfoAtPosition(FILENAME, lineOffset + i, undefined, verbosityLevel); if (quickInfo?.displayParts) { return quickInfo; } } } function extractTypeFromQuickInfo(quickInfo) { const {displayParts} = quickInfo; // For interfaces and enums, return everything after the keyword const keywordIndex = displayParts.findIndex( part => part.kind === 'keyword' && ['interface', 'enum'].includes(part.text), ); if (keywordIndex !== -1) { return displayParts.slice(keywordIndex + 1).map(part => part.text).join('').trim(); } let depth = 0; const separatorIndex = displayParts.findIndex(part => { if (part.kind === 'punctuation') { if (['(', '{', '<'].includes(part.text)) { depth++; } else if ([')', '}', '>'].includes(part.text)) { depth--; } else if (part.text === ':' && depth === 0) { return true; } } else if (part.kind === 'operator' && part.text === '=' && depth === 0) { return true; } return false; }); // If `separatorIndex` is `-1` (not found), return the entire thing return displayParts.slice(separatorIndex + 1).map(part => part.text).join('').trim(); } function normalizeType(type, onlySortNumbers = false) { const sourceFile = ts.createSourceFile( 'twoslash-type.ts', `declare const test: ${type};`, ts.ScriptTarget.Latest, ); const typeNode = sourceFile.statements[0].declarationList.declarations[0].type; const print = node => ts.createPrinter().printNode(ts.EmitHint.Unspecified, node, sourceFile); const isNumeric = v => v.trim() !== '' && Number.isFinite(Number(v)); const visit = node => { node = ts.visitEachChild(node, visit, undefined); if (ts.isUnionTypeNode(node)) { let types = node.types.map(t => [print(t), t]); if (onlySortNumbers) { // Sort only numeric members while keeping non-numeric members at their original positions const sortedNumericTypes = types.filter(([a]) => isNumeric(a)).sort(([a], [b]) => Number(a) - Number(b)); let numericIndex = 0; types = types.map(t => isNumeric(t[0]) ? sortedNumericTypes[numericIndex++][1] : t[1]); } else { types = types .sort(([a], [b]) => a < b ? -1 : (a > b ? 1 : 0)) .map(t => t[1]); } return ts.factory.updateUnionTypeNode( node, ts.factory.createNodeArray(types), ); } // Prefer single-line formatting for tuple types if (ts.isTupleTypeNode(node)) { const updated = ts.factory.createTupleTypeNode(node.elements); ts.setEmitFlags(updated, ts.EmitFlags.SingleLine); return updated; } // Replace double-quoted string literals with single-quoted ones if (ts.isStringLiteral(node)) { const updated = ts.factory.createStringLiteral(node.text, true); // Preserve non-ASCII characters like emojis. ts.setEmitFlags(updated, ts.EmitFlags.NoAsciiEscaping); return updated; } return node; }; return print(visit(typeNode)).replaceAll(/^( +)/gm, indentation => { // Replace spaces used for indentation with tabs const spacesPerTab = 4; const tabCount = Math.floor(indentation.length / spacesPerTab); const remainingSpaces = indentation.length % spacesPerTab; return '\t'.repeat(tabCount) + ' '.repeat(remainingSpaces); }); } function getCommentForType(type) { let comment = type; if (type.length < 80) { comment = type .replaceAll(/\r?\n\s*/g, ' ') // Collapse into single line .replaceAll(/{\s+/g, '{') // Remove spaces after `{` .replaceAll(/\s+}/g, '}') // Remove spaces before `}` .replaceAll(/;(?=})/g, ''); // Remove semicolons before `}` } return `${TWOSLASH_COMMENT} ${comment.replaceAll('\n', '\n// ')}`; } function reportTypeMismatch({context, messageId, start, end, data, fix}) { context.report({ loc: { start: context.sourceCode.getLocFromIndex(start), end: context.sourceCode.getLocFromIndex(end), }, messageId, data, fix(fixer) { return fixer.replaceTextRange([start, end], fix); }, }); } function validateTwoslashTypes(context, env, code, codeStartIndex) { const sourceFile = env.languageService.getProgram().getSourceFile(FILENAME); const lines = code.split('\n'); const specifiedVerbosityLevels = context.options[0].verbosityLevels; const verbosityLevels = [0, ...specifiedVerbosityLevels, Infinity]; // Keep `Infinity` last since suggestion logic relies on the order for (const [index, line] of lines.entries()) { const dedentedLine = line.trimStart(); if (!dedentedLine.startsWith(TWOSLASH_COMMENT)) { continue; } const previousLineIndex = index - 1; if (previousLineIndex < 0) { continue; } let rawActualType = dedentedLine.slice(TWOSLASH_COMMENT.length); let actualComment = dedentedLine; let actualCommentEndLine = index; for (let i = index + 1; i < lines.length; i++) { const dedentedNextLine = lines[i].trimStart(); if (!dedentedNextLine.startsWith('//') || dedentedNextLine.startsWith(TWOSLASH_COMMENT)) { break; } actualComment += '\n' + dedentedNextLine; rawActualType += '\n' + dedentedNextLine.slice(2); // Remove the `//` from start actualCommentEndLine = i; } const previousLine = lines[previousLineIndex]; const previousLineOffset = sourceFile.getPositionOfLineAndCharacter(previousLineIndex, 0); const actualCommentIndex = line.indexOf(TWOSLASH_COMMENT); const actualCommentStartOffset = sourceFile.getPositionOfLineAndCharacter(index, actualCommentIndex); const actualCommentEndOffset = sourceFile.getPositionOfLineAndCharacter(actualCommentEndLine, lines[actualCommentEndLine].length); const start = codeStartIndex + actualCommentStartOffset; const end = codeStartIndex + actualCommentEndOffset; const indent = line.slice(0, actualCommentIndex); const quickInfos = verbosityLevels .map(verbosity => getLeftmostQuickInfo(env, previousLine, previousLineOffset, verbosity)) .filter(qi => qi?.displayParts); if (quickInfos.length > 0) { const expectedTypes = quickInfos.map(qi => normalizeType(extractTypeFromQuickInfo(qi))); const actualType = normalizeType(rawActualType); if (expectedTypes.includes(actualType)) { // If the types match, check for formatting errors and unordered numbers in unions const expectedComment = getCommentForType(normalizeType(rawActualType, true)); if (actualComment !== expectedComment) { reportTypeMismatch({ messageId: 'incorrectTwoslashFormat', context, start, end, data: {expectedComment, actualComment}, fix: expectedComment.replaceAll('\n', `\n${indent}`), }); } } else { // For suggestion, use infinite verbosity, and it should be the last one const expectedComment = getCommentForType(expectedTypes.at(-1)); reportTypeMismatch({ messageId: 'incorrectTwoslashType', context, start, end, data: {expectedComment, actualComment}, fix: expectedComment.replaceAll('\n', `\n${indent}`), }); } } } } ================================================ FILE: lint-rules/validate-jsdoc-codeblocks.test.js ================================================ /* eslint-disable max-lines */ import {code1, code2, createRuleTester, dedenter, errorAt as errorAt_, exportType, exportTypeAndOption, fence, jsdoc} from './test-utils.js'; import {validateJSDocCodeblocksRule} from './validate-jsdoc-codeblocks.js'; const ruleTester = createRuleTester(); const codeWithErrors = dedenter` import type {RemovePrefix} from 'type-fest'; type A = RemovePrefix<'on-change', string, {strict: "yes"}>; `; const invalidCodeblockErrorAt = props => errorAt_({...props, messageId: 'invalidCodeblock'}); const incorrectTwoslashTypeErrorAt = props => errorAt_({...props, messageId: 'incorrectTwoslashType'}); const incorrectTwoslashFormatErrorAt = props => errorAt_({...props, messageId: 'incorrectTwoslashFormat'}); ruleTester.run('validate-jsdoc-codeblocks', validateJSDocCodeblocksRule, { valid: [ // Not exported dedenter` ${jsdoc(fence(codeWithErrors))} type NotExported = string; `, dedenter` type NotExportedOptions = { ${jsdoc(fence(codeWithErrors))} p1: string; } `, // Internal (leading underscore) dedenter` ${jsdoc(fence(codeWithErrors))} export type _Internal = string; `, dedenter` export type _InternalOptions = { ${jsdoc(fence(codeWithErrors))} p1: string; } `, // Without `Options` suffix dedenter` export type NoSuffix = { ${jsdoc(fence(codeWithErrors))} p1: string; } `, // No JSDoc exportTypeAndOption(''), exportType('type Some = number;'), exportTypeAndOption('// Not block comment'), exportTypeAndOption('/* Block comment, but not JSDoc */'), // No codeblock in JSDoc exportType(jsdoc('No codeblock here')), // With text before and after exportTypeAndOption(jsdoc('Some description.', fence(code1), '@category Test')), // With line breaks before and after exportTypeAndOption( jsdoc('Some description.\n', 'Note: Some note.\n', fence(code1, 'ts'), '\n@category Test'), ), // With `@example` tag exportTypeAndOption(jsdoc('@example', fence(code1))), // With language specifiers exportTypeAndOption(jsdoc(fence(code1, 'ts'))), exportTypeAndOption(jsdoc(fence(code1, 'typescript'))), // Multiple code blocks exportTypeAndOption( jsdoc('@example', fence(code1, 'ts'), '\nSome text in between.\n', '@example', fence(code2)), ), // Multiple exports and multiple properties exportTypeAndOption(jsdoc(fence(code1)), jsdoc(fence(code2))), // With @ts-expect-error exportTypeAndOption(jsdoc(fence(dedenter` import type {ExtractStrict} from 'type-fest'; // @ts-expect-error type A = ExtractStrict<'foo' | 'bar', 'baz'>; `))), // Indented code blocks exportTypeAndOption(jsdoc( 'Note:', dedenter` 1. First point \`\`\`ts import type {Subtract} from 'type-fest'; type A = Subtract<1, 2>; \`\`\` 2. Second point \`\`\`ts import type {Sum} from 'type-fest'; type A = Sum<1, 2>; \`\`\` `, )), // Compiler options overrides exportTypeAndOption(jsdoc(fence(dedenter` // @exactOptionalPropertyTypes: false const foo: {a?: number} = {a: undefined}; `))), // Incorrect compiler options are ignored exportTypeAndOption(jsdoc(fence(dedenter` // @noUnusedLocals: 'invalid-value' const foo = {a: 1}; `))), // Line comment between JSDoc and type/option exportTypeAndOption(dedenter` ${jsdoc(fence(code1))} // Some line comment between JSDoc and export `), ], invalid: [ // With text before and after { code: dedenter` /** Some description. \`\`\`ts type A = Subtract<1, 2>; \`\`\` @category Test */ export type T0 = string; export type TOptions = { /** Some description. \`\`\`ts type A = Subtract<1, 2>; \`\`\` @category Test */ p0: string; }; `, errors: [ invalidCodeblockErrorAt({line: 4, textBeforeStart: 'type A = ', target: 'Subtract'}), invalidCodeblockErrorAt({line: 14, textBeforeStart: '\ttype A = ', target: 'Subtract'}), ], }, // With line breaks before and after { code: dedenter` /** Some description. Note: Some note. \`\`\`ts type A = Subtract<1, 2>; \`\`\` @category Test */ export type T0 = string; export type TOptions = { /** Some description. Note: Some note. \`\`\`ts type A = Subtract<1, 2>; \`\`\` @category Test */ p0: string; }; `, errors: [ invalidCodeblockErrorAt({line: 7, textBeforeStart: 'type A = ', target: 'Subtract'}), invalidCodeblockErrorAt({line: 21, textBeforeStart: '\ttype A = ', target: 'Subtract'}), ], }, // With `@example` tag { code: dedenter` /** @example \`\`\`ts type A = Subtract<1, 2>; \`\`\` */ export type T0 = string; export type TOptions = { /** @example \`\`\`ts type A = Subtract<1, 2>; \`\`\` */ p0: string; }; `, errors: [ invalidCodeblockErrorAt({line: 4, textBeforeStart: 'type A = ', target: 'Subtract'}), invalidCodeblockErrorAt({line: 13, textBeforeStart: '\ttype A = ', target: 'Subtract'}), ], }, // With language specifiers { code: dedenter` /** \`\`\`ts type A = Subtract<1, 2>; \`\`\` */ export type T0 = string; export type TOptions = { /** \`\`\`ts type A = Subtract<1, 2>; \`\`\` */ p0: string; }; `, errors: [ invalidCodeblockErrorAt({line: 3, textBeforeStart: 'type A = ', target: 'Subtract'}), invalidCodeblockErrorAt({line: 11, textBeforeStart: '\ttype A = ', target: 'Subtract'}), ], }, { code: dedenter` /** \`\`\`typescript type A = Subtract<1, 2>; \`\`\` */ export type T0 = string; export type TOptions = { /** \`\`\`typescript type A = Subtract<1, 2>; \`\`\` */ p0: string; }; `, errors: [ invalidCodeblockErrorAt({line: 3, textBeforeStart: 'type A = ', target: 'Subtract'}), invalidCodeblockErrorAt({line: 11, textBeforeStart: '\ttype A = ', target: 'Subtract'}), ], }, // Multiple code blocks { code: dedenter` /** @example \`\`\`ts type A = Subtract<1, 2>; \`\`\` Some text in between. @example \`\`\`ts import type {ExcludeStrict} from 'type-fest'; type A = ExcludeStrict; \`\`\` */ export type T0 = string; export type TOptions = { /** @example \`\`\`ts type A = Subtract<1, 2>; \`\`\` Some text in between. @example \`\`\`ts import type {ExcludeStrict} from 'type-fest'; type A = ExcludeStrict; \`\`\` */ p0: string; }; `, errors: [ invalidCodeblockErrorAt({line: 4, textBeforeStart: 'type A = ', target: 'Subtract'}), invalidCodeblockErrorAt({line: 13, textBeforeStart: 'type A = ExcludeStrict; \`\`\` */ export type T0 = string; /** \`\`\`ts import type {ExcludeStrict} from 'type-fest'; type A = ExcludeStrict; \`\`\` */ export type T1 = string; export type T0Options = { /** \`\`\`ts type A = Subtract<1, 2>; \`\`\` */ p0: string; /** \`\`\`ts import type {ExcludeStrict} from 'type-fest'; type A = ExcludeStrict; \`\`\` */ p1: string; }; export type T1Options = { /** \`\`\`ts import type {Sum} from 'type-fest'; Sum<1, 2>; //=> 3 \`\`\` */ p0: string; }; `, errors: [ invalidCodeblockErrorAt({line: 3, textBeforeStart: 'type A = ', target: 'Subtract'}), invalidCodeblockErrorAt({line: 12, textBeforeStart: 'type A = ExcludeStrict; \`\`\` 2. Second point \`\`\`ts type A = Sum<1, 2>; \`\`\` */ export type T0 = string; export type TOptions = { /** Note: 1. First point \`\`\`ts type A = Subtract<1, 2>; \`\`\` 2. Second point \`\`\`ts type A = Sum<1, 2>; \`\`\` */ p0: string; }; `, errors: [ invalidCodeblockErrorAt({line: 5, textBeforeStart: '\ttype A = ', target: 'Subtract'}), invalidCodeblockErrorAt({line: 9, textBeforeStart: '\ttype A = ', target: 'Sum'}), invalidCodeblockErrorAt({line: 19, textBeforeStart: '\t\ttype A = ', target: 'Subtract'}), invalidCodeblockErrorAt({line: 23, textBeforeStart: '\t\ttype A = ', target: 'Sum'}), ], }, // Missing import { code: dedenter` /** Description \`\`\` type A = Sum<1, 2>; //=> 3 type B = Sum<-1, 2>; //=> 1 \`\`\` */ export type Sum = string; `, errors: [ invalidCodeblockErrorAt({line: 4, textBeforeStart: 'type A = ', target: 'Sum'}), invalidCodeblockErrorAt({line: 7, textBeforeStart: 'type B = ', target: 'Sum'}), ], }, // Floating examples { code: dedenter` /** \`\`\`ts import type {IsUppercase} from 'type-fest'; IsUppercase<'ABC'>; //=> true IsUppercase<'Abc'>; //=> false \`\`\` @category Utilities */ export type IsUppercase = boolean; `, errors: [ invalidCodeblockErrorAt({line: 5, textBeforeStart: '', target: 'IsUppercase'}), invalidCodeblockErrorAt({line: 8, textBeforeStart: '', target: 'IsUppercase'}), ], }, // Hypthetical references { code: dedenter` /** Some description Some note \`\`\` import type {Except} from 'type-fest'; type PostPayload = Except; \`\`\` */ export type Except = string; `, errors: [ invalidCodeblockErrorAt({line: 7, textBeforeStart: 'type PostPayload = Except<', target: 'UserData'}), ], }, // Duplicate identifiers { code: dedenter` export type IsTupleOptions = { /** @example \`\`\` import type {IsTuple} from 'type-fest'; type Example = IsTuple<[number, ...number[]], {fixedLengthOnly: true}>; //=> false type Example = IsTuple<[number, ...number[]], {fixedLengthOnly: false}>; //=> true \`\`\` @default true */ fixedLengthOnly: boolean; }; `, errors: [ invalidCodeblockErrorAt({line: 7, textBeforeStart: '\ttype ', target: 'Example'}), invalidCodeblockErrorAt({line: 10, textBeforeStart: '\ttype ', target: 'Example'}), ], }, // Multi line error { code: dedenter` /** @example \`\`\` declare function updateConfig(newConfig: {name?: string; version?: number}): void; updateConfig({ name: undefined, version: undefined, }); \`\`\` @category Utilities */ export type MultiLine = string; `, errors: [ invalidCodeblockErrorAt({line: 6, textBeforeStart: 'updateConfig(', endLine: 9, textBeforeEnd: '}'}), ], }, // Precise one character error { code: dedenter` /** \`\`\` import type {ExcludeStrict} from 'type-fest'; type A = ExcludeStrict<'a' | 'b', 'A'>; \`\`\` */ export type ExcludeStrict = string; `, errors: [ invalidCodeblockErrorAt({ line: 5, textBeforeStart: 'type A = ExcludeStrict<\'a\' | \'b\', ', target: '\'A\'', }), ], }, // `exactOptionalPropertyTypes` is enabled { code: dedenter` /** \`\`\` const test: {foo?: string} = {foo: undefined}; \`\`\` */ export type Test = string; `, errors: [ invalidCodeblockErrorAt({line: 3, textBeforeStart: 'const ', target: 'test'}), ], }, // Overlapping errors { code: dedenter` /** \`\`\`typescript import type {ExcludeStrict, Sum} from 'type-fest'; type A = Sum<1, '2'>; \`\`\` */ export type Test = string; `, errors: [ invalidCodeblockErrorAt({line: 5, textBeforeStart: 'type A = ', target: 'Sum<1, \'2\'>'}), invalidCodeblockErrorAt({line: 5, textBeforeStart: 'type A = Sum<1, ', target: '\'2\''}), ], }, // Compiler options overrides { code: dedenter` /** \`\`\`ts // @noUnusedLocals: true const foo = {a: 1}; \`\`\` */ export type T0 = string; `, errors: [ invalidCodeblockErrorAt({line: 4, textBeforeStart: 'const ', target: 'foo'}), ], }, ], }); // Type mismatch tests ruleTester.run('validate-jsdoc-codeblocks', validateJSDocCodeblocksRule, { valid: [ exportTypeAndOption(jsdoc(fence(dedenter` type Foo = string; //=> string `))), // No twoslash comment at all exportTypeAndOption(jsdoc(fence(dedenter` const foo = 'bar'; `))), // Twoslash comment at very first line exportTypeAndOption(jsdoc(fence(dedenter` //=> 'bar' const foo = 'bar'; `))), // Object type collapsed into single line exportTypeAndOption(jsdoc(fence(dedenter` const foo = {a: 1, b: {c: 'c'}}; //=> {a: number; b: {c: string}} `))), // Multiline type exportTypeAndOption(jsdoc(fence(dedenter` import type {Simplify} from 'type-fest'; type Foo = {readonly a: number; readonly b?: number}; type Bar = {c?: string; d: {readonly e: boolean}; e: string}; type Baz = Simplify; //=> { // readonly a: number; // readonly b?: number; // c?: string; // d: { // readonly e: boolean; // }; // e: string; // } `))), // Quick info at 0th index exportTypeAndOption(jsdoc(fence(dedenter` let foo = 1; foo++; //=> number `))), // Quick info at some middle index exportTypeAndOption(jsdoc(fence(dedenter` const foo = 1 as string | number; //=> string | number `))), // Quick info at last index exportTypeAndOption(jsdoc(fence(dedenter` const foo = {n: 1} const bar = foo .n //=> number `))), // Double-quotes properly replaced exportTypeAndOption(jsdoc(fence(dedenter` import type {Simplify} from 'type-fest'; type Foo = {a: 'abc'; b: 123; c: 'def'}; type Bar = {x: {y: 'y'; z: 'z'}}; type Baz = Simplify; //=> {a: 'abc'; b: 123; c: 'def'; x: {y: 'y'; z: 'z'}} `))), exportTypeAndOption(jsdoc(fence(dedenter` type Foo = 'a"b"c'; //=> 'a"b"c' type Bar = "d'e'f"; //=> 'd\'e\'f' `))), // Space indentation properly replaced exportTypeAndOption(jsdoc(fence(dedenter` import type {Simplify} from 'type-fest'; type Foo = { a: { ab: boolean; ac: { acd: string | number; }; }; e: [ { fgh: false; ijk: { lmno: 'yes' | 'no'; }; }, string, [ 'foo', 'bar', ], ]; }; type Bar = Simplify; //=> { // a: { // ab: boolean; // ac: { // acd: string | number; // }; // }; // e: [{ // fgh: false; // ijk: { // lmno: 'yes' | 'no'; // }; // }, string, ['foo', 'bar']]; // } `))), // Compiler options overrides exportTypeAndOption(jsdoc(fence(dedenter` // @exactOptionalPropertyTypes: false import type {AllExtend} from 'type-fest'; type A = AllExtend<[1?, 2?, 3?], number>; //=> boolean `))), // Multiple `//=>` exportTypeAndOption(jsdoc(fence(dedenter` const foo = {a: true, b: false, c: {d: true}} as const; //=> { // readonly a: true; // readonly b: false; // readonly c: { // readonly d: true; // }; // } const bar = ['a', 'b', 'c'] as const; //=> readonly ['a', 'b', 'c'] const baz = new Set(bar); //=> Set<'a' | 'b' | 'c'> `))), // Indented code blocks exportTypeAndOption(jsdoc( 'Note:', dedenter` 1. First point \`\`\`ts import type {Subtract} from 'type-fest'; type A = Subtract<1, 2>; //=> -1 \`\`\` 2. Second point \`\`\`ts import type {Sum} from 'type-fest'; type A = Sum<1, 2>; //=> 3 \`\`\` `, )), // Order of non-numbers in unions doesn't matter exportTypeAndOption(jsdoc(fence(dedenter` type Test = 'a' | 'b' | {x: ['c' | 'd' | 'e']; y: {z: 'f' | 'g' | 'h'}}; type Valid = Test //=> 'b' | 'a' | {x: ['d' | 'c' | 'e']; y: {z: 'g' | 'h' | 'f'}} type Valid2 = Test //=> 'a' | {x: ['e' | 'd' | 'c']; y: {z: 'h' | 'g' | 'f'}} | 'b' type Valid3 = Test //=> {x: ['e' | 'c' | 'd']; y: {z: 'h' | 'f' | 'g'}} | 'b' | 'a' `))), // Numbers are sorted in unions exportTypeAndOption(jsdoc(fence(dedenter` import type {IntClosedRange} from 'type-fest'; type ZeroToNine = IntClosedRange<0, 9>; //=> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 `))), // Numbers in nested unions are sorted exportTypeAndOption(jsdoc(fence(dedenter` type Test = {w: 0 | 10 | 5; x: [2 | 16 | 4]; y: {z: 3 | 27 | 9}}; //=> {w: 0 | 5 | 10; x: [2 | 4 | 16]; y: {z: 3 | 9 | 27}} `))), // Numbers in unions inside unions are sorted, non-numbers can be in any order exportTypeAndOption(jsdoc(fence(dedenter` type Test = {a: 'foo' | 27 | 1 | {b: 2 | 1 | 8 | 4} | 'baz' | 9 | 3 | 'bar'}; type Valid = Test; //=> {a: 1 | 3 | 9 | 27 | {b: 1 | 2 | 4 | 8} | 'bar' | 'foo' | 'baz'} type Valid2 = Test; //=> {a: {b: 1 | 2 | 4 | 8} | 1 | 'foo' | 3 | 'baz' | 'bar' | 9 | 27} type Valid3 = Test; //=> {a: 'baz' | 'foo' | 1 | 3 | 9 | 'bar' | 27 | {b: 1 | 2 | 4 | 8}} `))), // Only numbers are sorted in unions, non-numbers can be in any order exportTypeAndOption(jsdoc(fence(dedenter` import type {ArrayElement} from 'type-fest'; type Tuple1 = ArrayElement<[null, string, boolean, 1, 3, 0, -2, 4, 2, -1]>; //=> string | -2 | -1 | boolean | 0 | null | 1 | 2 | 3 | 4 type Tuple2 = ArrayElement<[null, 1, 3, string, 0, -2, 4, 2, boolean, -1]>; //=> -2 | boolean | -1 | 0 | 1 | 2 | 3 | 4 | null | string `))), // Tuples are in single line exportTypeAndOption(jsdoc(fence(dedenter` import type {TupleOf} from 'type-fest'; type RGB = TupleOf<3, number>; //=> [number, number, number] type TicTacToeBoard = TupleOf<3, TupleOf<3, 'X' | 'O' | null>>; //=> [['X' | 'O' | null, 'X' | 'O' | null, 'X' | 'O' | null], ['X' | 'O' | null, 'X' | 'O' | null, 'X' | 'O' | null], ['X' | 'O' | null, 'X' | 'O' | null, 'X' | 'O' | null]] `))), // Emojis are preserved exportTypeAndOption(jsdoc(fence(dedenter` type Pets = '🦄' | '🐶' | '🐇'; //=> '🦄' | '🐶' | '🐇' `))), // `0` and `Infinity` verbosity levels exportTypeAndOption(jsdoc(fence(dedenter` type Test = {a: Pick<{b: Pick<{c: Pick<{d: 1}, 'd'>}, 'c'>}, 'b'>}; type LevelZero = Test; //=> {a: {b: {c: {d: 1}}}} type LevelThree = Test; //=> { // a: Pick<{ // b: Pick<{ // c: Pick<{ // d: 1; // }, 'd'>; // }, 'c'>; // }, 'b'>; // } `))), // Custom verbosity level // `0` and `Infinity` verbosity levels are still allowed { code: exportTypeAndOption(jsdoc(fence(dedenter` type Test = {a: Pick<{b: Pick<{c: Pick<{d: 1}, 'd'>}, 'c'>}, 'b'>}; type LevelZero = Test; //=> {a: {b: {c: {d: 1}}}} type LevelOne = Test; //=> {a: {b: Pick<{c: Pick<{d: 1}, 'd'>}, 'c'>}} type LevelTwo = Test; //=> {a: {b: {c: Pick<{d: 1}, 'd'>}}} type LevelThree = Test; //=> { // a: Pick<{ // b: Pick<{ // c: Pick<{ // d: 1; // }, 'd'>; // }, 'c'>; // }, 'b'>; // } `))), options: [{verbosityLevels: [1, 2]}], }, // === Different types of quick info === // Function exportTypeAndOption(jsdoc(fence(dedenter` declare function foo(a: string): {b: string; c: number}; foo('a'); //=> {b: string; c: number} `))), // Variable exportTypeAndOption(jsdoc(fence(dedenter` const foo = 'foo'; //=> 'foo' let bar = {a: 1}; //=> {a: number} var baz = true; //=> boolean `))), // Type Alias exportTypeAndOption(jsdoc(fence(dedenter` type Foo = {a: number}; //=> {a: number} `))), // Interface exportTypeAndOption(jsdoc(fence(dedenter` interface Foo { foo: string; } //=> Foo `))), // Generic interface exportTypeAndOption(jsdoc(fence(dedenter` interface Foo { foo: T; } //=> Foo `))), // Parameter exportTypeAndOption(jsdoc(fence(dedenter` function foo(n: number) { n++; //=> number } `))), // Property exportTypeAndOption(jsdoc(fence(dedenter` const foo = {n: 1}; foo .n++; //=> number `))), // Method exportTypeAndOption(jsdoc(fence(dedenter` class Foo { m() { return 'foo'; } } const f = new Foo() .m(); //=> string `))), // Constructor exportTypeAndOption(jsdoc(fence(dedenter` class Foo { constructor() { //=> Foo console.log('Foo'); } } `))), // Enum exportTypeAndOption(jsdoc(fence(dedenter` enum Foo {} //=> Foo `))), // Const enum exportTypeAndOption(jsdoc(fence(dedenter` const enum Foo { A = 1 } //=> Foo `))), // Enum Member exportTypeAndOption(jsdoc(fence(dedenter` enum Foo { A } void Foo .A; //=> 0 `))), ], invalid: [ // === Twoslash format errors === // No space after `//=>` { code: dedenter` /** \`\`\`ts type Foo = string; //=>string \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashFormatErrorAt({line: 4, textBeforeStart: '', target: '//=>string'}), ], output: dedenter` /** \`\`\`ts type Foo = string; //=> string \`\`\` */ export type T0 = string; `, }, // More than one space after `//=>` { code: dedenter` /** \`\`\`ts type Foo = string; //=> string \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashFormatErrorAt({line: 4, textBeforeStart: '', target: '//=> string'}), ], output: dedenter` /** \`\`\`ts type Foo = string; //=> string \`\`\` */ export type T0 = string; `, }, // Incorrect spacing { code: dedenter` /** \`\`\`ts type Foo = 'a' | 'b' | { c: 'd' } | 'e'; //=> 'a'|'b' | { c : 'd' }| 'e' \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashFormatErrorAt({line: 4, textBeforeStart: '', target: '//=> \'a\'|\'b\' | { c : \'d\' }| \'e\' '}), ], output: dedenter` /** \`\`\`ts type Foo = 'a' | 'b' | { c: 'd' } | 'e'; //=> 'a' | 'b' | {c: 'd'} | 'e' \`\`\` */ export type T0 = string; `, }, // No space in subsequent lines { code: dedenter` /** \`\`\`ts const foo = {a: true, b: true, c: false, d: false, e: true} as const; //=> { // readonly a: true; // readonly b: true; // readonly c: false; // readonly d: false; // readonly e: true; //} \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashFormatErrorAt({line: 4, textBeforeStart: '', endLine: 10, textBeforeEnd: '//}'}), ], output: dedenter` /** \`\`\`ts const foo = {a: true, b: true, c: false, d: false, e: true} as const; //=> { // readonly a: true; // readonly b: true; // readonly c: false; // readonly d: false; // readonly e: true; // } \`\`\` */ export type T0 = string; `, }, // Incorrect double quotes { code: dedenter` /** \`\`\`ts type Foo = ["a", {b: "c", d: {e: "f"}}, "g" | "h"]; //=> ["a", {b: "c"; d: {e: "f"}}, "g" | "h"] \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashFormatErrorAt({line: 4, textBeforeStart: '', target: '//=> ["a", {b: "c"; d: {e: "f"}}, "g" | "h"]'}), ], output: dedenter` /** \`\`\`ts type Foo = ["a", {b: "c", d: {e: "f"}}, "g" | "h"]; //=> ['a', {b: 'c'; d: {e: 'f'}}, 'g' | 'h'] \`\`\` */ export type T0 = string; `, }, // Incorrect multiline tuples { code: dedenter` /** \`\`\`ts import type {TupleOf} from 'type-fest'; type RGB = TupleOf<3, number>; //=> [ // number, // number, // number, // ] \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashFormatErrorAt({line: 6, textBeforeStart: '', endLine: 10, textBeforeEnd: '// ]'}), ], output: dedenter` /** \`\`\`ts import type {TupleOf} from 'type-fest'; type RGB = TupleOf<3, number>; //=> [number, number, number] \`\`\` */ export type T0 = string; `, }, // Multi line to single line { code: dedenter` /** \`\`\`ts const foo = [{a: 1}] as const; //=> readonly [{ // readonly a: 1; // }] \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashFormatErrorAt({line: 4, textBeforeStart: '', endLine: 6, textBeforeEnd: '// }]'}), ], output: dedenter` /** \`\`\`ts const foo = [{a: 1}] as const; //=> readonly [{readonly a: 1}] \`\`\` */ export type T0 = string; `, }, // Single line to multi line { code: dedenter` /** \`\`\`ts const foo = {a: true, b: true, c: false, d: false, e: true} as const; //=> {readonly a: true; readonly b: true; readonly c: false; readonly d: false; readonly e: true} \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashFormatErrorAt({line: 4, textBeforeStart: '', target: '//=> {readonly a: true; readonly b: true; readonly c: false; readonly d: false; readonly e: true}'}), ], output: dedenter` /** \`\`\`ts const foo = {a: true, b: true, c: false, d: false, e: true} as const; //=> { // readonly a: true; // readonly b: true; // readonly c: false; // readonly d: false; // readonly e: true; // } \`\`\` */ export type T0 = string; `, }, // Incorrect order of numbers in unions { code: dedenter` /** \`\`\`ts type T1 = 5 | 4 | 3 | 2 | 1; //=> 5 | 4 | 1 | 2 | 3 type T2 = 1 | 2 | 3 | 'a'; //=> 2 | 'a' | 3 | 1 \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashFormatErrorAt({line: 4, textBeforeStart: '', target: '//=> 5 | 4 | 1 | 2 | 3'}), incorrectTwoslashFormatErrorAt({line: 7, textBeforeStart: '', target: '//=> 2 | \'a\' | 3 | 1'}), ], output: dedenter` /** \`\`\`ts type T1 = 5 | 4 | 3 | 2 | 1; //=> 1 | 2 | 3 | 4 | 5 type T2 = 1 | 2 | 3 | 'a'; //=> 1 | 'a' | 2 | 3 \`\`\` */ export type T0 = string; `, }, // Indented code blocks { code: dedenter` /** Note: 1. First point \`\`\`ts const foo = {a: true, b: false, c: {d: true}}; //=> {a: boolean; b: boolean; // c: {d: boolean}; // } \`\`\` 2. Second point \`\`\`ts const bar = ['a', 'b', 'c'] as const; //=> readonly [ 'a', 'b', 'c' ] const baz = new Set(bar); //=>Set< 'a'|'b'|'c' > \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashFormatErrorAt({line: 6, textBeforeStart: '\t', endLine: 8, textBeforeEnd: '\t// }'}), incorrectTwoslashFormatErrorAt({line: 13, textBeforeStart: '\t', target: '//=> readonly [ \'a\', \'b\', \'c\' ]'}), incorrectTwoslashFormatErrorAt({line: 15, textBeforeStart: '\t', target: '//=>Set< \'a\'|\'b\'|\'c\' >'}), ], output: dedenter` /** Note: 1. First point \`\`\`ts const foo = {a: true, b: false, c: {d: true}}; //=> {a: boolean; b: boolean; c: {d: boolean}} \`\`\` 2. Second point \`\`\`ts const bar = ['a', 'b', 'c'] as const; //=> readonly ['a', 'b', 'c'] const baz = new Set(bar); //=> Set<'a' | 'b' | 'c'> \`\`\` */ export type T0 = string; `, }, // Multiple `//=>` { code: dedenter` /** \`\`\`ts const foo = {a: 'a', b: 'b'} as const; //=> { // readonly a: 'a'; // readonly b: 'b'; // } const bar = ['a', 'b', 'c'] as const; //=> readonly [ // 'a', // 'b', // 'c' // ] const baz = new Set(bar); //=> Set< // | 'a' // | 'b' // | 'c' // > \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashFormatErrorAt({line: 4, textBeforeStart: '', endLine: 7, textBeforeEnd: '// }'}), incorrectTwoslashFormatErrorAt({line: 9, textBeforeStart: '', endLine: 13, textBeforeEnd: '// ]'}), incorrectTwoslashFormatErrorAt({line: 15, textBeforeStart: '', endLine: 19, textBeforeEnd: '// >'}), ], output: dedenter` /** \`\`\`ts const foo = {a: 'a', b: 'b'} as const; //=> {readonly a: 'a'; readonly b: 'b'} const bar = ['a', 'b', 'c'] as const; //=> readonly ['a', 'b', 'c'] const baz = new Set(bar); //=> Set<'a' | 'b' | 'c'> \`\`\` */ export type T0 = string; `, }, // Preserves the specified verbosity level during formatting fixes { code: dedenter` /** \`\`\`ts type Test = {a: Pick<{b: Pick<{c: Pick<{d: 'abracadabra'}, 'd'>}, 'c'>}, 'b'>}; type LevelOne = Test; //=> {a: {b: Pick<{c: Pick<{d: 'abracadabra'}, 'd'>}, 'c'>}} type LevelTwo = Test; //=> {a: {b: {c: Pick<{d: "abracadabra"}, "d">}}} \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashFormatErrorAt({line: 6, textBeforeStart: '', target: '//=> {a: {b: Pick<{c: Pick<{d: \'abracadabra\'}, \'d\'>}, \'c\'>}}'}), incorrectTwoslashFormatErrorAt({line: 9, textBeforeStart: '', target: '//=> {a: {b: {c: Pick<{d: "abracadabra"}, "d">}}}'}), ], options: [{verbosityLevels: [1, 2]}], output: dedenter` /** \`\`\`ts type Test = {a: Pick<{b: Pick<{c: Pick<{d: 'abracadabra'}, 'd'>}, 'c'>}, 'b'>}; type LevelOne = Test; //=> { // a: { // b: Pick<{ // c: Pick<{ // d: 'abracadabra'; // }, 'd'>; // }, 'c'>; // }; // } type LevelTwo = Test; //=> {a: {b: {c: Pick<{d: 'abracadabra'}, 'd'>}}} \`\`\` */ export type T0 = string; `, }, // === Twoslash type errors === // Incorrect type { code: dedenter` /** \`\`\`ts const foo = 'bar'; //=> 'baz' \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashTypeErrorAt({line: 4, textBeforeStart: '', target: '//=> \'baz\''}), ], output: dedenter` /** \`\`\`ts const foo = 'bar'; //=> 'bar' \`\`\` */ export type T0 = string; `, }, // Empty `//=>` { code: dedenter` /** \`\`\`ts type Foo = string; //=> \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashTypeErrorAt({line: 4, textBeforeStart: '', target: '//=>'}), ], output: dedenter` /** \`\`\`ts type Foo = string; //=> string \`\`\` */ export type T0 = string; `, }, // Broken type { code: dedenter` /** \`\`\`ts const foo = {a: 1, b: 2}; //=> {a const bar = {a: 1, b: 2}; //=> {a: number // b: \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashTypeErrorAt({line: 4, textBeforeStart: '', target: '//=> {a'}), incorrectTwoslashTypeErrorAt({line: 7, textBeforeStart: '', endLine: 8, textBeforeEnd: '// b:'}), ], output: dedenter` /** \`\`\`ts const foo = {a: 1, b: 2}; //=> {a: number; b: number} const bar = {a: 1, b: 2}; //=> {a: number; b: number} \`\`\` */ export type T0 = string; `, }, // Multiline replace { code: dedenter` /** \`\`\`ts const foo = {foo: true, bar: {baz: true, qux: [true, false]}} as const; //=> { // foo: true; // readonly bar: { // readonly baz: false; // readonly qux: [true, false]; // }; // } \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashTypeErrorAt({line: 4, textBeforeStart: '', endLine: 10, textBeforeEnd: '// }'}), ], output: dedenter` /** \`\`\`ts const foo = {foo: true, bar: {baz: true, qux: [true, false]}} as const; //=> { // readonly foo: true; // readonly bar: { // readonly baz: true; // readonly qux: readonly [true, false]; // }; // } \`\`\` */ export type T0 = string; `, }, // Multiline add missing lines { code: dedenter` /** \`\`\`ts const foo = {foo: true, bar: {baz: true, qux: [true, false]}} as const; //=> { // readonly bar: { // readonly qux: readonly [true, false]; // }; // } \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashTypeErrorAt({line: 4, textBeforeStart: '', endLine: 8, textBeforeEnd: '// }'}), ], output: dedenter` /** \`\`\`ts const foo = {foo: true, bar: {baz: true, qux: [true, false]}} as const; //=> { // readonly foo: true; // readonly bar: { // readonly baz: true; // readonly qux: readonly [true, false]; // }; // } \`\`\` */ export type T0 = string; `, }, // Multiline remove extra lines { code: dedenter` /** \`\`\`ts const foo = {bar: {qux: [true, false]}, quux: [null, undefined]} as const; //=> { // readonly foo: true; // readonly bar: { // readonly baz: true; // readonly qux: readonly [true, false]; // }; // readonly quux: readonly [null, undefined]; // } \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashTypeErrorAt({line: 4, textBeforeStart: '', endLine: 11, textBeforeEnd: '// }'}), ], output: dedenter` /** \`\`\`ts const foo = {bar: {qux: [true, false]}, quux: [null, undefined]} as const; //=> { // readonly bar: { // readonly qux: readonly [true, false]; // }; // readonly quux: readonly [null, undefined]; // } \`\`\` */ export type T0 = string; `, }, // Compiler options overrides { code: dedenter` /** \`\`\`ts // @exactOptionalPropertyTypes: false type Prettify = { [P in keyof T]: T[P]; }; type T1 = Prettify<{a?: string; b?: number}>; //=> { // a?: string; // b?: number; // } \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashTypeErrorAt({line: 9, textBeforeStart: '', endLine: 12, textBeforeEnd: '// }'}), ], output: dedenter` /** \`\`\`ts // @exactOptionalPropertyTypes: false type Prettify = { [P in keyof T]: T[P]; }; type T1 = Prettify<{a?: string; b?: number}>; //=> {a?: string | undefined; b?: number | undefined} \`\`\` */ export type T0 = string; `, }, // Incorrect type and improper formatting { code: dedenter` /** \`\`\`ts const foo = {a: 'a', b: 'b'} as const; //=> { // a: 'a'; // b: 'b'; // } const bar = 'bar'; //=>"baz" \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashTypeErrorAt({line: 4, textBeforeStart: '', endLine: 7, textBeforeEnd: '// }'}), incorrectTwoslashTypeErrorAt({line: 10, textBeforeStart: '', target: '//=>"baz"'}), ], output: dedenter` /** \`\`\`ts const foo = {a: 'a', b: 'b'} as const; //=> {readonly a: 'a'; readonly b: 'b'} const bar = 'bar'; //=> 'bar' \`\`\` */ export type T0 = string; `, }, // Indented code blocks { code: dedenter` /** Note: 1. First point \`\`\`ts const foo = {a: true, b: false, c: {d: true}} as const; //=> { // a?: false; // c?: { // d?: false; // }; // } \`\`\` 2. Second point \`\`\`ts const bar = ['a', 'b', 'c'] as const; //=> ['a', 'c'] const baz = new Set(bar); //=> Set \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashTypeErrorAt({line: 6, textBeforeStart: '\t', endLine: 11, textBeforeEnd: '\t// }'}), incorrectTwoslashTypeErrorAt({line: 16, textBeforeStart: '\t', target: '//=> [\'a\', \'c\']'}), incorrectTwoslashTypeErrorAt({line: 18, textBeforeStart: '\t', target: '//=> Set'}), ], output: dedenter` /** Note: 1. First point \`\`\`ts const foo = {a: true, b: false, c: {d: true}} as const; //=> { // readonly a: true; // readonly b: false; // readonly c: { // readonly d: true; // }; // } \`\`\` 2. Second point \`\`\`ts const bar = ['a', 'b', 'c'] as const; //=> readonly ['a', 'b', 'c'] const baz = new Set(bar); //=> Set<'a' | 'b' | 'c'> \`\`\` */ export type T0 = string; `, }, // Multiple `//=>` { code: dedenter` /** \`\`\`ts const foo = {a: true, b: false, c: {d: true}} as const; //=> const bar = ['a', 'b', 'c'] as const; //=> const baz = new Set(bar); //=> \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashTypeErrorAt({line: 4, textBeforeStart: '', target: '//=>'}), incorrectTwoslashTypeErrorAt({line: 6, textBeforeStart: '', target: '//=>'}), incorrectTwoslashTypeErrorAt({line: 8, textBeforeStart: '', target: '//=>'}), ], output: dedenter` /** \`\`\`ts const foo = {a: true, b: false, c: {d: true}} as const; //=> { // readonly a: true; // readonly b: false; // readonly c: { // readonly d: true; // }; // } const bar = ['a', 'b', 'c'] as const; //=> readonly ['a', 'b', 'c'] const baz = new Set(bar); //=> Set<'a' | 'b' | 'c'> \`\`\` */ export type T0 = string; `, }, // Fixer suggests types at `Infinity` verbosity level { code: dedenter` /** \`\`\`ts type Test = {foo: Pick<{bar: Pick<{baz: string}, 'baz'>}, 'bar'>}; //=> \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashTypeErrorAt({line: 4, textBeforeStart: '', target: '//=>'}), ], output: dedenter` /** \`\`\`ts type Test = {foo: Pick<{bar: Pick<{baz: string}, 'baz'>}, 'bar'>}; //=> {foo: {bar: {baz: string}}} \`\`\` */ export type T0 = string; `, }, // Only `0` and `Infinity` verbosity levels are allowed by default { code: dedenter` /** \`\`\`ts type Test = {foo: Pick<{bar: Pick<{baz: string}, 'baz'>}, 'bar'>}; //=> {foo: {bar: Pick<{baz: string}, 'baz'>}} \`\`\` */ export type T0 = string; `, errors: [ incorrectTwoslashTypeErrorAt({line: 4, textBeforeStart: '', target: '//=> {foo: {bar: Pick<{baz: string}, \'baz\'>}}'}), ], output: dedenter` /** \`\`\`ts type Test = {foo: Pick<{bar: Pick<{baz: string}, 'baz'>}, 'bar'>}; //=> {foo: {bar: {baz: string}}} \`\`\` */ export type T0 = string; `, }, ], }); ================================================ FILE: media/readme.md ================================================ # Media ## Attribution ### Fireworks vector graphic [Free Vectors via Vecteezy!](https://www.vecteezy.com) ================================================ FILE: package.json ================================================ { "name": "type-fest", "version": "5.5.0", "description": "A collection of essential TypeScript types", "license": "(MIT OR CC0-1.0)", "repository": "sindresorhus/type-fest", "funding": "https://github.com/sponsors/sindresorhus", "author": { "name": "Sindre Sorhus", "email": "sindresorhus@gmail.com", "url": "https://sindresorhus.com" }, "type": "module", "exports": { ".": { "types": "./index.d.ts" }, "./globals": { "types": "./source/globals/index.d.ts" } }, "types": "./index.d.ts", "sideEffects": false, "engines": { "node": ">=20" }, "scripts": { "test:tsc": "node --max-old-space-size=6144 ./node_modules/.bin/tsc", "test:tsd": "node --max-old-space-size=6144 ./node_modules/.bin/tsd", "test:xo": "node --max-old-space-size=6144 ./node_modules/.bin/xo --ignores=lint-processors/fixtures/**/*.d.ts", "test:linter": "node --test", "test": "run-p test:*" }, "files": [ "index.d.ts", "source", "license-mit", "license-cc0" ], "keywords": [ "typescript", "ts", "types", "utility", "util", "utilities", "omit", "merge", "json", "generics" ], "dependencies": { "tagged-tag": "^1.0.0" }, "devDependencies": { "@sindresorhus/tsconfig": "^8.0.1", "@typescript-eslint/parser": "^8.44.0", "eslint": "^9.35.0", "@typescript/vfs": "^1.6.1", "dedent": "^1.7.0", "expect-type": "^1.2.2", "npm-run-all2": "^8.0.4", "tsd": "^0.33.0", "typescript": "^5.9.2", "typescript-eslint": "^8.47.0", "xo": "^1.2.2" }, "tsd": { "compilerOptions": { "noUnusedLocals": false } } } ================================================ FILE: readme.md ================================================

[![](https://img.shields.io/badge/unicorn-approved-ff69b4.svg)](https://giphy.com/gifs/illustration-rainbow-unicorn-26AHG5KGFxSkUWw1i) [![npm dependents](https://badgen.net/npm/dependents/type-fest)](https://www.npmjs.com/package/type-fest?activeTab=dependents) [![npm downloads](https://badgen.net/npm/dt/type-fest)](https://www.npmjs.com/package/type-fest) Many of the types here should have been built-in. You can help by suggesting some of them to the [TypeScript project](https://github.com/Microsoft/TypeScript/blob/main/CONTRIBUTING.md). Either add this package as a dependency or copy-paste the needed types. No credit required. 👌 PR welcome for additional commonly needed types and docs improvements. Read the [contributing guidelines](.github/contributing.md) first. **Help wanted with reviewing [proposals](https://github.com/sindresorhus/type-fest/issues) and [pull requests](https://github.com/sindresorhus/type-fest/pulls).** ## Install ```sh npm install type-fest ``` *Requires TypeScript >=5.9, [ESM](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c), and [`{strict: true}`](https://www.typescriptlang.org/tsconfig#strict) in your tsconfig.* > [!NOTE] > This readme shows the current development version. For docs about the latest version, see the [npm page](https://www.npmjs.com/package/type-fest). *You may also like my [`ts-extras`](https://github.com/sindresorhus/ts-extras) package which provides runtime functions for some of these types.* ## Usage ```ts import type {Except} from 'type-fest'; type Foo = { unicorn: string; rainbow: boolean; }; type FooWithoutRainbow = Except; //=> {unicorn: string} ``` ## API Click the type names for complete docs. ### Basic - [`Primitive`](source/primitive.d.ts) - Matches any [primitive value](https://developer.mozilla.org/en-US/docs/Glossary/Primitive). - [`Class`](source/basic.d.ts) - Matches a [`class`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). - [`Constructor`](source/basic.d.ts) - Matches a [`class` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). - [`AbstractClass`](source/basic.d.ts) - Matches an [`abstract class`](https://www.typescriptlang.org/docs/handbook/2/classes.html#abstract-classes-and-members). - [`AbstractConstructor`](source/basic.d.ts) - Matches an [`abstract class`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-2.html#abstract-construct-signatures) constructor. - [`TypedArray`](source/typed-array.d.ts) - Matches any [typed array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray), like `Uint8Array` or `Float64Array`. - [`ObservableLike`](source/globals/observable-like.d.ts) - Matches a value that is like an [Observable](https://github.com/tc39/proposal-observable). - [`LowercaseLetter`](source/characters.d.ts) - Matches any lowercase letter in the basic Latin alphabet (a-z). - [`UppercaseLetter`](source/characters.d.ts) - Matches any uppercase letter in the basic Latin alphabet (A-Z). - [`DigitCharacter`](source/characters.d.ts) - Matches any digit as a string ('0'-'9'). - [`Alphanumeric`](source/characters.d.ts) - Matches any lowercase letter (a-z), uppercase letter (A-Z), or digit ('0'-'9') in the basic Latin alphabet. ### Utilities - [`EmptyObject`](source/empty-object.d.ts) - Represents a strictly empty plain object, the `{}` value. - [`NonEmptyObject`](source/non-empty-object.d.ts) - Represents an object with at least 1 non-optional key. - [`UnknownRecord`](source/unknown-record.d.ts) - Represents an object with `unknown` value. You probably want this instead of `{}`. - [`UnknownArray`](source/unknown-array.d.ts) - Represents an array with `unknown` value. - [`UnknownMap`](source/unknown-map.d.ts) - Represents a map with `unknown` key and value. - [`UnknownSet`](source/unknown-set.d.ts) - Represents a set with `unknown` value. - [`Except`](source/except.d.ts) - Create a type from an object type without certain keys. This is a stricter version of [`Omit`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys). - [`Writable`](source/writable.d.ts) - Create a type that strips `readonly` from the given type. Inverse of `Readonly`. - [`WritableDeep`](source/writable-deep.d.ts) - Create a deeply mutable version of an `object`/`ReadonlyMap`/`ReadonlySet`/`ReadonlyArray` type. The inverse of `ReadonlyDeep`. Use `Writable` if you only need one level deep. - [`Merge`](source/merge.d.ts) - Merge two types into a new type. Keys of the second type overrides keys of the first type. - [`ObjectMerge`](source/object-merge.d.ts) - Merge two object types into a new object type, where keys from the second override keys from the first. - [`MergeDeep`](source/merge-deep.d.ts) - Merge two objects or two arrays/tuples recursively into a new type. - [`MergeExclusive`](source/merge-exclusive.d.ts) - Create a type that has mutually exclusive keys. - [`OverrideProperties`](source/override-properties.d.ts) - Override only existing properties of the given type. Similar to `Merge`, but enforces that the original type has the properties you want to override. - [`RequireAtLeastOne`](source/require-at-least-one.d.ts) - Create a type that requires at least one of the given keys. - [`RequireExactlyOne`](source/require-exactly-one.d.ts) - Create a type that requires exactly a single key of the given keys and disallows more. - [`RequireAllOrNone`](source/require-all-or-none.d.ts) - Create a type that requires all of the given keys or none of the given keys. - [`RequireOneOrNone`](source/require-one-or-none.d.ts) - Create a type that requires exactly a single key of the given keys and disallows more, or none of the given keys. - [`SingleKeyObject`](source/single-key-object.d.ts) - Create a type that only accepts an object with a single key. - [`RequiredDeep`](source/required-deep.d.ts) - Create a deeply required version of another type. Use [`Required`](https://www.typescriptlang.org/docs/handbook/utility-types.html#requiredtype) if you only need one level deep. - [`PickDeep`](source/pick-deep.d.ts) - Pick properties from a deeply-nested object. Use [`Pick`](https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys) if you only need one level deep. - [`OmitDeep`](source/omit-deep.d.ts) - Omit properties from a deeply-nested object. Use [`Omit`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys) if you only need one level deep. - [`OmitIndexSignature`](source/omit-index-signature.d.ts) - Omit any index signatures from the given object type, leaving only explicitly defined properties. - [`PickIndexSignature`](source/pick-index-signature.d.ts) - Pick only index signatures from the given object type, leaving out all explicitly defined properties. - [`PartialDeep`](source/partial-deep.d.ts) - Create a deeply optional version of another type. Use [`Partial`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype) if you only need one level deep. - [`PartialOnUndefinedDeep`](source/partial-on-undefined-deep.d.ts) - Create a deep version of another type where all keys accepting `undefined` type are set to optional. - [`UndefinedOnPartialDeep`](source/undefined-on-partial-deep.d.ts) - Create a deep version of another type where all optional keys are set to also accept `undefined`. - [`UnwrapPartial`](source/unwrap-partial.d.ts) - Revert the `Partial` modifier on an object type. - [`ReadonlyDeep`](source/readonly-deep.d.ts) - Create a deeply immutable version of an `object`/`Map`/`Set`/`Array` type. Use [`Readonly`](https://www.typescriptlang.org/docs/handbook/utility-types.html#readonlytype) if you only need one level deep. - [`LiteralUnion`](source/literal-union.d.ts) - Create a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union. Workaround for [Microsoft/TypeScript#29729](https://github.com/Microsoft/TypeScript/issues/29729). - [`Tagged`](source/tagged.d.ts) - Create a [tagged type](https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d) that can support [multiple tags](https://github.com/sindresorhus/type-fest/issues/665) and [per-tag metadata](https://medium.com/@ethanresnick/advanced-typescript-tagged-types-improved-with-type-level-metadata-5072fc125fcf). (This replaces the previous [`Opaque`](source/tagged.d.ts) type, which is now deprecated.) - [`UnwrapTagged`](source/tagged.d.ts) - Get the untagged portion of a tagged type created with `Tagged`. (This replaces the previous [`UnwrapOpaque`](source/tagged.d.ts) type, which is now deprecated.) - [`InvariantOf`](source/invariant-of.d.ts) - Create an [invariant type](https://basarat.gitbook.io/typescript/type-system/type-compatibility#footnote-invariance), which is a type that does not accept supertypes and subtypes. - [`SetOptional`](source/set-optional.d.ts) - Create a type that makes the given keys optional. - [`SetReadonly`](source/set-readonly.d.ts) - Create a type that makes the given keys readonly. - [`SetRequired`](source/set-required.d.ts) - Create a type that makes the given keys required. - [`SetRequiredDeep`](source/set-required-deep.d.ts) - Like `SetRequired` except it selects the keys deeply. - [`SetNonNullable`](source/set-non-nullable.d.ts) - Create a type that makes the given keys non-nullable. - [`SetNonNullableDeep`](source/set-non-nullable-deep.d.ts) - Create a type that makes the specified keys non-nullable (removes `null` and `undefined`), supports deeply nested key paths, and leaves all other keys unchanged. - [`ValueOf`](source/value-of.d.ts) - Create a union of the given object's values, and optionally specify which keys to get the values from. - [`ConditionalKeys`](source/conditional-keys.d.ts) - Extract keys from a shape where values extend the given `Condition` type. - [`ConditionalPick`](source/conditional-pick.d.ts) - Like `Pick` except it selects properties from a shape where the values extend the given `Condition` type. - [`ConditionalPickDeep`](source/conditional-pick-deep.d.ts) - Like `ConditionalPick` except that it selects the properties deeply. - [`ConditionalExcept`](source/conditional-except.d.ts) - Like `Omit` except it removes properties from a shape where the values extend the given `Condition` type. - [`UnionToIntersection`](source/union-to-intersection.d.ts) - Convert a union type to an intersection type. - [`LiteralToPrimitive`](source/literal-to-primitive.d.ts) - Convert a [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) to the [primitive type](source/primitive.d.ts) it belongs to. - [`LiteralToPrimitiveDeep`](source/literal-to-primitive-deep.d.ts) - Like `LiteralToPrimitive` except it converts literal types inside an object or array deeply. - [`Stringified`](source/stringified.d.ts) - Create a type with the keys of the given type changed to `string` type. - [`IterableElement`](source/iterable-element.d.ts) - Get the element type of an `Iterable`/`AsyncIterable`. For example, `Array`, `Set`, `Map`, generator, stream, etc. - [`Entry`](source/entry.d.ts) - Create a type that represents the type of an entry of a collection. - [`Entries`](source/entries.d.ts) - Create a type that represents the type of the entries of a collection. - [`SetReturnType`](source/set-return-type.d.ts) - Create a function type with a return type of your choice and the same parameters as the given function type. - [`SetParameterType`](source/set-parameter-type.d.ts) - Create a function that replaces some parameters with the given parameters. - [`Simplify`](source/simplify.d.ts) - Useful to flatten the type output to improve type hints shown in editors. And also to transform an interface into a type to aide with assignability. - [`SimplifyDeep`](source/simplify-deep.d.ts) - Deeply simplifies an object type. - [`Get`](source/get.d.ts) - Get a deeply-nested property from an object using a key path, like [Lodash's `.get()`](https://lodash.com/docs/latest#get) function. - [`KeyAsString`](source/key-as-string.d.ts) - Get keys of the given type as strings. - [`Schema`](source/schema.d.ts) - Create a deep version of another object type where property values are recursively replaced into a given value type. - [`Exact`](source/exact.d.ts) - Create a type that does not allow extra properties. - [`KeysOfUnion`](source/keys-of-union.d.ts) - Create a union of all keys from a given type, even those exclusive to specific union members. - [`OptionalKeysOf`](source/optional-keys-of.d.ts) - Extract all optional keys from the given type. - [`HasOptionalKeys`](source/has-optional-keys.d.ts) - Create a `true`/`false` type depending on whether the given type has any optional fields. - [`RequiredKeysOf`](source/required-keys-of.d.ts) - Extract all required keys from the given type. - [`HasRequiredKeys`](source/has-required-keys.d.ts) - Create a `true`/`false` type depending on whether the given type has any required fields. - [`ReadonlyKeysOf`](source/readonly-keys-of.d.ts) - Extract all readonly keys from the given type. - [`HasReadonlyKeys`](source/has-readonly-keys.d.ts) - Create a `true`/`false` type depending on whether the given type has any readonly fields. - [`WritableKeysOf`](source/writable-keys-of.d.ts) - Extract all writable (non-readonly) keys from the given type. - [`HasWritableKeys`](source/has-writable-keys.d.ts) - Create a `true`/`false` type depending on whether the given type has any writable fields. - [`Spread`](source/spread.d.ts) - Mimic the type inferred by TypeScript when merging two objects or two arrays/tuples using the spread syntax. - [`IsEqual`](source/is-equal.d.ts) - Returns a boolean for whether the two given types are equal. - [`TaggedUnion`](source/tagged-union.d.ts) - Create a union of types that share a common discriminant property. - [`IntRange`](source/int-range.d.ts) - Generate a union of numbers (includes the start and excludes the end). - [`IntClosedRange`](source/int-closed-range.d.ts) - Generate a union of numbers (includes the start and the end). - [`ArrayIndices`](source/array-indices.d.ts) - Provides valid indices for a constant array or tuple. - [`ArrayValues`](source/array-values.d.ts) - Provides all values for a constant array or tuple. - [`ArraySplice`](source/array-splice.d.ts) - Create a new array type by adding or removing elements at a specified index range in the original array. - [`ArrayTail`](source/array-tail.d.ts) - Extract the type of an array or tuple minus the first element. - [`SetFieldType`](source/set-field-type.d.ts) - Create a type that changes the type of the given keys. - [`Paths`](source/paths.d.ts) - Generate a union of all possible paths to properties in the given object. - [`SharedUnionFields`](source/shared-union-fields.d.ts) - Create a type with shared fields from a union of object types. - [`SharedUnionFieldsDeep`](source/shared-union-fields-deep.d.ts) - Create a type with shared fields from a union of object types, deeply traversing nested structures. - [`AllUnionFields`](source/all-union-fields.d.ts) - Create a type with all fields from a union of object types. - [`DistributedOmit`](source/distributed-omit.d.ts) - Omits keys from a type, distributing the operation over a union. - [`DistributedPick`](source/distributed-pick.d.ts) - Picks keys from a type, distributing the operation over a union. - [`And`](source/and.d.ts) - Returns a boolean for whether two given types are both `true`. - [`Or`](source/or.d.ts) - Returns a boolean for whether either of two given types is `true`. - [`Xor`](source/xor.d.ts) - Returns a boolean for whether only one of two given types is `true`. - [`AndAll`](source/and-all.d.ts) - Returns a boolean for whether all of the given elements are `true`. - [`OrAll`](source/or-all.d.ts) - Returns a boolean for whether any of the given elements is `true`. - [`AllExtend`](source/all-extend.d.ts) - Returns a boolean for whether every element in an array type extends another type. - [`SomeExtend`](source/some-extend.d.ts) - Returns a boolean for whether some element in an array type extends another type. - [`NonEmptyTuple`](source/non-empty-tuple.d.ts) - Matches any non-empty tuple. - [`NonEmptyString`](source/non-empty-string.d.ts) - Matches any non-empty string. - [`FindGlobalType`](source/find-global-type.d.ts) - Tries to find the type of a global with the given name. - [`FindGlobalInstanceType`](source/find-global-type.d.ts) - Tries to find one or more types from their globally-defined constructors. - [`ConditionalSimplify`](source/conditional-simplify.d.ts) - Simplifies a type while including and/or excluding certain types from being simplified. - [`ConditionalSimplifyDeep`](source/conditional-simplify-deep.d.ts) - Recursively simplifies a type while including and/or excluding certain types from being simplified. - [`ExclusifyUnion`](source/exclusify-union.d.ts) - Ensure mutual exclusivity in object unions by adding other members’ keys as `?: never`. - [`Optional`](source/optional.d.ts) - Create a type that represents either the value or `undefined`, while stripping `null` from the type. - [`UnionMember`](source/union-member.d.ts) - Returns an arbitrary member of a union type. ### Type Guard - [`If`](source/if.d.ts) - An if-else-like type that resolves depending on whether the given `boolean` type is `true` or `false`. - [`IsLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). - [`IsStringLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `string` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). - [`IsNumericLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `number` or `bigint` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). - [`IsBooleanLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `true` or `false` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). - [`IsSymbolLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `symbol` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). - [`IsAny`](source/is-any.d.ts) - Returns a boolean for whether the given type is `any`. - [`IsNever`](source/is-never.d.ts) - Returns a boolean for whether the given type is `never`. - [`IsUnknown`](source/is-unknown.d.ts) - Returns a boolean for whether the given type is `unknown`. - [`IsEmptyObject`](source/empty-object.d.ts) - Returns a boolean for whether the type is strictly equal to an empty plain object, the `{}` value. - [`IsNull`](source/is-null.d.ts) - Returns a boolean for whether the given type is `null`. - [`IsUndefined`](source/is-undefined.d.ts) - Returns a boolean for whether the given type is `undefined`. - [`IsTuple`](source/is-tuple.d.ts) - Returns a boolean for whether the given array is a tuple. - [`IsUnion`](source/is-union.d.ts) - Returns a boolean for whether the given type is a union. - [`IsLowercase`](source/is-lowercase.d.ts) - Returns a boolean for whether the given string literal is lowercase. - [`IsUppercase`](source/is-uppercase.d.ts) - Returns a boolean for whether the given string literal is uppercase. - [`IsOptional`](source/is-optional.d.ts) - Returns a boolean for whether the given type includes `undefined`. - [`IsNullable`](source/is-nullable.d.ts) - Returns a boolean for whether the given type includes `null`. - [`IsOptionalKeyOf`](source/is-optional-key-of.d.ts) - Returns a boolean for whether the given key is an optional key of type. - [`IsRequiredKeyOf`](source/is-required-key-of.d.ts) - Returns a boolean for whether the given key is a required key of type. - [`IsReadonlyKeyOf`](source/is-readonly-key-of.d.ts) - Returns a boolean for whether the given key is a readonly key of type. - [`IsWritableKeyOf`](source/is-writable-key-of.d.ts) - Returns a boolean for whether the given key is a writable key of type. ### JSON - [`Jsonify`](source/jsonify.d.ts) - Transform a type to one that is assignable to the `JsonValue` type. - [`Jsonifiable`](source/jsonifiable.d.ts) - Matches a value that can be losslessly converted to JSON. - [`JsonPrimitive`](source/json-value.d.ts) - Matches a JSON primitive. - [`JsonObject`](source/json-value.d.ts) - Matches a JSON object. - [`JsonArray`](source/json-value.d.ts) - Matches a JSON array. - [`JsonValue`](source/json-value.d.ts) - Matches any valid JSON value. ### Structured clone - [`StructuredCloneable`](source/structured-cloneable.d.ts) - Matches a value that can be losslessly cloned using `structuredClone`. ### Async - [`Promisable`](source/promisable.d.ts) - Create a type that represents either the value or the value wrapped in `PromiseLike`. - [`AsyncReturnType`](source/async-return-type.d.ts) - Unwrap the return type of a function that returns a `Promise`. - [`Asyncify`](source/asyncify.d.ts) - Create an async version of the given function type. ### String - [`Trim`](source/trim.d.ts) - Remove leading and trailing spaces from a string. - [`Split`](source/split.d.ts) - Represents an array of strings split using a given character or character set. - [`Words`](source/words.d.ts) - Represents an array of strings split using a heuristic for detecting words. - [`Replace`](source/replace.d.ts) - Represents a string with some or all matches replaced by a replacement. - [`StringSlice`](source/string-slice.d.ts) - Returns a string slice of a given range, just like `String#slice()`. - [`StringRepeat`](source/string-repeat.d.ts) - Returns a new string which contains the specified number of copies of a given string, just like `String#repeat()`. - [`RemovePrefix`](source/remove-prefix.d.ts) - Remove the specified prefix from the start of a string. ### Array - [`Arrayable`](source/arrayable.d.ts) - Create a type that represents either the value or an array of the value. - [`Includes`](source/includes.d.ts) - Returns a boolean for whether the given array includes the given item. - [`Join`](source/join.d.ts) - Join an array of strings and/or numbers using the given string as a delimiter. - [`ArraySlice`](source/array-slice.d.ts) - Returns an array slice of a given range, just like `Array#slice()`. - [`ArrayElement`](source/array-element.d.ts) - Extracts the element type of an array or tuple. - [`LastArrayElement`](source/last-array-element.d.ts) - Extract the type of the last element of an array. - [`FixedLengthArray`](source/fixed-length-array.d.ts) - Create a type that represents an array of the given type and length. The `Array` prototype methods that manipulate its length are excluded from the resulting type. - [`MultidimensionalArray`](source/multidimensional-array.d.ts) - Create a type that represents a multidimensional array of the given type and dimensions. - [`MultidimensionalReadonlyArray`](source/multidimensional-readonly-array.d.ts) - Create a type that represents a multidimensional readonly array of the given type and dimensions. - [`ReadonlyTuple`](source/readonly-tuple.d.ts) - Create a type that represents a read-only tuple of the given type and length. - [`TupleToUnion`](source/tuple-to-union.d.ts) - Convert a tuple/array into a union type of its elements. - [`UnionToTuple`](source/union-to-tuple.d.ts) - Convert a union type into an unordered tuple type of its elements. - [`TupleToObject`](source/tuple-to-object.d.ts) - Transforms a tuple into an object, mapping each tuple index to its corresponding type as a key-value pair. - [`TupleOf`](source/tuple-of.d.ts) - Create a tuple type of the specified length with elements of the specified type. - [`SplitOnRestElement`](source/split-on-rest-element.d.ts) - Splits an array into three parts, where the first contains all elements before the rest element, the second is the [`rest`](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) element itself, and the third contains all elements after the rest element. - [`ExtractRestElement`](source/extract-rest-element.d.ts) - Extract the [`rest`](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) element type from an array. - [`ExcludeRestElement`](source/exclude-rest-element.d.ts) - Create a tuple with the [`rest`](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) element removed. - [`ArrayReverse`](source/array-reverse.d.ts) - Reverse the order of elements in a tuple type. - [`ArrayLength`](source/array-length.d.ts) - Return the length of an array. Equivalent to `T['length']` where `T` extends any array. ### Numeric - [`PositiveInfinity`](source/numeric.d.ts) - Matches the hidden `Infinity` type. - [`NegativeInfinity`](source/numeric.d.ts) - Matches the hidden `-Infinity` type. - [`Finite`](source/numeric.d.ts) - A finite `number`. - [`Integer`](source/numeric.d.ts) - A `number` that is an integer. - [`Float`](source/numeric.d.ts) - A `number` that is not an integer. - [`NegativeFloat`](source/numeric.d.ts) - A negative (`-∞ < x < 0`) `number` that is not an integer. - [`Negative`](source/numeric.d.ts) - A negative `number`/`bigint` (`-∞ < x < 0`) - [`NonNegative`](source/numeric.d.ts) - A non-negative `number`/`bigint` (`0 <= x < ∞`). - [`NegativeInteger`](source/numeric.d.ts) - A negative (`-∞ < x < 0`) `number` that is an integer. - [`NonNegativeInteger`](source/numeric.d.ts) - A non-negative (`0 <= x < ∞`) `number` that is an integer. - [`IsNegative`](source/numeric.d.ts) - Returns a boolean for whether the given number is a negative number. - [`IsFloat`](source/is-float.d.ts) - Returns a boolean for whether the given number is a float, like `1.5` or `-1.5`. - [`IsInteger`](source/is-integer.d.ts) - Returns a boolean for whether the given number is an integer, like `-5`, `1.0` or `100`. - [`GreaterThan`](source/greater-than.d.ts) - Returns a boolean for whether a given number is greater than another number. - [`GreaterThanOrEqual`](source/greater-than-or-equal.d.ts) - Returns a boolean for whether a given number is greater than or equal to another number. - [`LessThan`](source/less-than.d.ts) - Returns a boolean for whether a given number is less than another number. - [`LessThanOrEqual`](source/less-than-or-equal.d.ts) - Returns a boolean for whether a given number is less than or equal to another number. - [`Sum`](source/sum.d.ts) - Returns the sum of two numbers. - [`Subtract`](source/subtract.d.ts) - Returns the difference between two numbers. ### Change case - [`CamelCase`](source/camel-case.d.ts) - Convert a string literal to camel-case (`fooBar`). - [`CamelCasedProperties`](source/camel-cased-properties.d.ts) - Convert object properties to camel-case (`fooBar`). - [`CamelCasedPropertiesDeep`](source/camel-cased-properties-deep.d.ts) - Convert object properties to camel-case recursively (`fooBar`). - [`KebabCase`](source/kebab-case.d.ts) - Convert a string literal to kebab-case (`foo-bar`). - [`KebabCasedProperties`](source/kebab-cased-properties.d.ts) - Convert object properties to kebab-case (`foo-bar`). - [`KebabCasedPropertiesDeep`](source/kebab-cased-properties-deep.d.ts) - Convert object properties to kebab-case recursively (`foo-bar`). - [`PascalCase`](source/pascal-case.d.ts) - Convert a string literal to pascal-case (`FooBar`). - [`PascalCasedProperties`](source/pascal-cased-properties.d.ts) - Convert object properties to pascal-case (`FooBar`). - [`PascalCasedPropertiesDeep`](source/pascal-cased-properties-deep.d.ts) - Convert object properties to pascal-case recursively (`FooBar`). - [`SnakeCase`](source/snake-case.d.ts) - Convert a string literal to snake-case (`foo_bar`). - [`SnakeCasedProperties`](source/snake-cased-properties.d.ts) - Convert object properties to snake-case (`foo_bar`). - [`SnakeCasedPropertiesDeep`](source/snake-cased-properties-deep.d.ts) - Convert object properties to snake-case recursively (`foo_bar`). - [`ScreamingSnakeCase`](source/screaming-snake-case.d.ts) - Convert a string literal to screaming-snake-case (`FOO_BAR`). - [`DelimiterCase`](source/delimiter-case.d.ts) - Convert a string literal to a custom string delimiter casing. - [`DelimiterCasedProperties`](source/delimiter-cased-properties.d.ts) - Convert object properties to a custom string delimiter casing. - [`DelimiterCasedPropertiesDeep`](source/delimiter-cased-properties-deep.d.ts) - Convert object properties to a custom string delimiter casing recursively. ### Miscellaneous - [`GlobalThis`](source/global-this.d.ts) - Declare locally scoped properties on `globalThis`. - [`PackageJson`](source/package-json.d.ts) - Type for [npm's `package.json` file](https://docs.npmjs.com/creating-a-package-json-file). It also includes support for [TypeScript Declaration Files](https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html). - [`TsConfigJson`](source/tsconfig-json.d.ts) - Type for [TypeScript's `tsconfig.json` file](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html). ### Improved built-in - [`ExtendsStrict`](source/extends-strict.d.ts) - A stricter, non-distributive version of `extends` for checking whether one type is assignable to another. - [`ExtractStrict`](source/extract-strict.d.ts) - A stricter version of `Extract` that ensures every member of `U` can successfully extract something from `T`. - [`ExcludeStrict`](source/exclude-strict.d.ts) - A stricter version of `Exclude` that ensures every member of `U` can successfully exclude something from `T`. - [`ExcludeExactly`](source/exclude-exactly.d.ts) - A stricter version of `Exclude` that excludes types only when they are exactly identical. ## Declined types *If we decline a type addition, we will make sure to document the better solution here.* - [`Diff` and `Spread`](https://github.com/sindresorhus/type-fest/pull/7) - The pull request author didn't provide any real-world use-cases and the PR went stale. If you think this type is useful, provide some real-world use-cases and we might reconsider. - [`Dictionary`](https://github.com/sindresorhus/type-fest/issues/33) - You only save a few characters (`Dictionary` vs `Record`) from [`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type), which is more flexible and well-known. Also, you shouldn't use an object as a dictionary. We have `Map` in JavaScript now. - [`ExtractProperties` and `ExtractMethods`](https://github.com/sindresorhus/type-fest/pull/4) - The types violate the single responsibility principle. Instead, refine your types into more granular type hierarchies. - [`Url2Json`](https://github.com/sindresorhus/type-fest/pull/262) - Inferring search parameters from a URL string is a cute idea, but not very useful in practice, since search parameters are usually dynamic and defined separately. - [`Nullish`](https://github.com/sindresorhus/type-fest/pull/318) - The type only saves a couple of characters, not everyone knows what "nullish" means, and I'm also trying to [get away from `null`](https://github.com/sindresorhus/meta/discussions/7). - [`TitleCase`](https://github.com/sindresorhus/type-fest/pull/303) - It's not solving a common need and is a better fit for a separate package. - [`ExtendOr` and `ExtendAnd`](https://github.com/sindresorhus/type-fest/pull/247) - The benefits don't outweigh having to learn what they mean. - [`PackageJsonExtras`](https://github.com/sindresorhus/type-fest/issues/371) - There are too many possible configurations that can be put into `package.json`. If you would like to extend `PackageJson` to support an additional configuration in your project, please see the *Extending existing types* section below. ## Alternative type names *If you know one of our types by a different name, add it here for discovery.* - `Prettify`- See [`Simplify`](source/simplify.d.ts) - `Expand`- See [`Simplify`](source/simplify.d.ts) - `PartialBy` - See [`SetOptional`](source/set-optional.d.ts) - `RecordDeep`- See [`Schema`](source/schema.d.ts) - `Mutable`- See [`Writable`](source/writable.d.ts) - `RequireOnlyOne`, `OneOf` - See [`RequireExactlyOne`](source/require-exactly-one.d.ts) - `AtMostOne` - See [`RequireOneOrNone`](source/require-one-or-none.d.ts) - `AllKeys` - See [`KeysOfUnion`](source/keys-of-union.d.ts) - `Branded` - See [`Tagged`](source/tagged.d.ts) - `Opaque` - See [`Tagged`](source/tagged.d.ts) - `SetElement` - See [`IterableElement`](source/iterable-element.d.ts) - `SetEntry` - See [`IterableElement`](source/iterable-element.d.ts) - `SetValues` - See [`IterableElement`](source/iterable-element.d.ts) - `PickByTypes` - See [`ConditionalPick`](source/conditional-pick.d.ts) - `HomomorphicOmit` - See [`Except`](source/except.d.ts) - `IfAny`, `IfNever`, `If*` - See [`If`](source/if.d.ts) - `Maybe`, `Option` - See [`Optional`](source/optional.d.ts) - `MaybePromise` - See [`Promisable`](source/promisable.d.ts) - `ReadonlyTuple` - See [`TupleOf`](source/tuple-of.d.ts) - `LastOfUnion` - See [`UnionMember`](source/union-member.d.ts) - `FirstOfUnion` - See [`UnionMember`](source/union-member.d.ts) ## Tips ### Extending existing types - [`PackageJson`](source/package-json.d.ts) - There are a lot of tools that place extra configurations inside the `package.json` file. You can extend `PackageJson` to support these additional configurations.
Example [Playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBDAnmApnA3gBQIYGMDW2A5igFIDOEAdnNuXAEJ0o4HFmVUC+cAZlBBBwA5ElQBaXinIxhAbgCwAKFCRYCZGnQAZYFRgooPfoJHSANntmKlysWlaESFanAC8jZo-YuaAMgwLKwBhal5gIgB+AC44XX1DADpQqnCiLhsgA) ```ts import type {PackageJson as BasePackageJson} from 'type-fest'; import type {Linter} from 'eslint'; type PackageJson = BasePackageJson & {eslintConfig?: Linter.Config}; ```
### Related - [typed-query-selector](https://github.com/g-plane/typed-query-selector) - Enhances `document.querySelector` and `document.querySelectorAll` with a template literal type that matches element types returned from an HTML element query selector. - [`Linter.Config`](https://github.com/eslint/eslint/blob/main/lib/types/index.d.ts) - Definitions for the [ESLint configuration schema](https://eslint.org/docs/user-guide/configuring/language-options). ### Built-in types There are many advanced types most users don't know about. - [`Awaited`](https://www.typescriptlang.org/docs/handbook/utility-types.html#awaitedtype) - Extract the type of a value that a `Promise` resolves to.
Example [Playground](https://www.typescriptlang.org/play/?#code/JYOwLgpgTgZghgYwgAgKoGdrIN4FgBQyyAkMACYBcyIArgLYBG0A3AUcSHHRFemFKADmrQiTiCe1ekygiiAXwJtkCADZx06NJigBBAA7AAytABuwJDmXENATxAJkMCGAQALDNAAUNHQElKKUZoAEoqAAUoAHs6YEwAHk8oAD4rUWJiAHpM5AAxF3dkMDcUXywyODA4J2i6IpLkCqqGDQgAOmssnIAVBsQwGjhVZGA6fVUIbnBK4CiQZFjBNzBkVSiogGtV4A2UYriKTuyVOb5kKAh0fVOUAF5kOAB3OGAV51c3LwAiTLhDTLKUEyABJsICAvIQnISF0TiAzk1qvcLlcbm0AFboOZeKFHHIXAZQeaI6EZAk0Ik4EaBACMABpqFxJF8AFJRNzzAAiUQgXwZ4kkAGYAAzIeSkxSiSXKMC2fQofIfCBkJLIe66Z6vZXxABKLgpIG6cogiR0BmMZgsEAA2l93u4kl8ALrJZIiZR2BxOGgOMCzeZuOAgMgTJKcypwLx-C1QcxIKhJc0mWNWhngwK0YJQEJpdj8Wy5mEIU4rQFURXuZWq+5PF4raPJuPte0eHQ+fxkXHpWG6GCQKBOApuITIQGNCMM2xRGgqIPIeWwKJQOqmOACadafr+rToGiFDSj-RNEfFUo6EbgaDwJB0vGz9wnhqImpRb2Es8QBlLhZwDYjuBkGQrz+kMyC6OEfjnBAACONCXGAm5aCAEDKsqHTpPIs4fMgXjQNE2aFhkxx4d+gbBqoQjWJKChKKIxbwqWZqGI2VpqtQECPNo0BJpaSA4tCZEhhAYYRu23HMbxn7IDSUJAA) ```ts interface User { id: number; name: string; age: number; } class UserApiService { async fetchUser(userId: number): Promise { // Fetch the user data from the database. // The actual implementation might look like this: // const response = await fetch('/api/user/${userId}'); // const data = response.json(); // return data; return { id: 1, name: 'John Doe', age: 30 }; } } type FetchedUser = Awaited>; async function handleUserData(apiService: UserApiService, userId: number) { try { const user: FetchedUser = await apiService.fetchUser(userId); // After fetching user data, you can perform various actions such as updating the user interface, // caching the data for future use, or making additional API requests as needed. } catch (error) { // Error handling } } const userApiService = new UserApiService(); handleUserData(userApiService, 1); ``` - [`Partial`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype) - Make all properties in `T` optional.
Example [Playground](https://www.typescriptlang.org/play/#code/JYOwLgpgTgZghgYwgAgHIHsAmEDC6QzADmyA3gLABQyycADnanALYQBcyAzmFKEQNxUaddFDAcQAV2YAjaIMoBfKlQQAbOJ05osEAIIMAQpOBrsUMkOR1eANziRkCfISKSoD4Pg4ZseAsTIALyW1DS0DEysHADkvvoMMQA0VsKi4sgAzAAMuVaKClY2wPaOknSYDrguADwA0sgQAB6QIJjaANYQAJ7oMDp+LsQAfAAUXd0cdUnI9mo+uv6uANp1ALoAlKHhyGAAFsCcAHTOAW4eYF4gyxNrwbNwago0ypRWp66jH8QcAApwYmAjxq8SWIy2FDCNDA3ToKFBQyIdR69wmfQG1TOhShyBgomQX3w3GQE2Q6IA8jIAFYQBBgI4TTiEs5bTQYsFInrLTbbHZOIlgZDlSqQABqj0kKBC3yINx6a2xfOQwH6o2FVXFaklwSCIUkbQghBAEEwENSfNOlykEGefNe5uhB2O6sgS3GPRmLogmslG1tLxUOKgEDA7hAuydtteryAA) ```ts interface NodeConfig { appName: string; port: number; } class NodeAppBuilder { private configuration: NodeConfig = { appName: 'NodeApp', port: 3000 }; private updateConfig(key: Key, value: NodeConfig[Key]) { this.configuration[key] = value; } config(config: Partial) { type NodeConfigKey = keyof NodeConfig; for (const key of Object.keys(config) as NodeConfigKey[]) { const updateValue = config[key]; if (updateValue === undefined) { continue; } this.updateConfig(key, updateValue); } return this; } } // `Partial`` allows us to provide only a part of the // NodeConfig interface. new NodeAppBuilder().config({appName: 'ToDoApp'}); ``` `Partial` can be reverted with [`UnwrapPartial`](source/unwrap-partial.d.ts).
- [`Required`](https://www.typescriptlang.org/docs/handbook/utility-types.html#requiredtype) - Make all properties in `T` required.
Example [Playground](https://typescript-play.js.org/?target=6#code/AQ4SwOwFwUwJwGYEMDGNgGED21VQGJZwC2wA3gFCjXAzFJgA2A-AFzADOUckA5gNxUaIYjA4ckvGG07c+g6gF8KQkAgCuEFFDA5O6gEbEwUbLm2ESwABQIixACJIoSdgCUYAR3Vg4MACYAPGYuFvYAfACU5Ko0APRxwADKMBD+wFAAFuh2Vv7OSBlYGdmc8ABu8LHKsRyGxqY4oQT21pTCIHQMjOwA5DAAHgACxAAOjDAAdChYxL0ANLHUouKSMH0AEmAAhJhY6ozpAJ77GTCMjMCiV0ToSAb7UJPPC9WRgrEJwAAqR6MwSRQPFGUFocDgRHYxnEfGAowh-zgUCOwF6KwkUl6tXqJhCeEsxDaS1AXSYfUGI3GUxmc0WSneQA) ```ts interface ContactForm { email?: string; message?: string; } function submitContactForm(formData: Required) { // Send the form data to the server. } submitContactForm({ email: 'ex@mple.com', message: 'Hi! Could you tell me more about…', }); // TypeScript error: missing property 'message' submitContactForm({ email: 'ex@mple.com', }); ```
- [`Readonly`](https://www.typescriptlang.org/docs/handbook/utility-types.html#readonlytype) - Make all properties in `T` readonly.
Example [Playground](https://typescript-play.js.org/?target=6#code/AQ4UwOwVwW2AZA9gc3mAbmANsA3gKFCOAHkAzMgGkOJABEwAjKZa2kAUQCcvEu32AMQCGAF2FYBIAL4BufDRABLCKLBcywgMZgEKZOoDCiCGSXI8i4hGEwwALmABnUVxXJ57YFgzZHSVF8sT1BpBSItLGEnJz1kAy5LLy0TM2RHACUwYQATEywATwAeAITjU3MAPnkrCJMXLigtUT4AClxgGztKbyDgaX99I1TzAEokr1BRAAslJwA6FIqLAF48TtswHp9MHDla9hJGACswZvmyLjAwAC8wVpm5xZHkUZDaMKIwqyWXYCW0oN4sNlsA1h0ug5gAByACyBQAggAHJHQ7ZBIFoXbzBjMCz7OoQP5YIaJNYQMAAdziCVaALGNSIAHomcAACoFJFgADKWjcSNEwG4vC4ji0wggEEQguiTnMEGALWAV1yAFp8gVgEjeFyuKICvMrCTgVxnst5jtsGC4ljsPNhXxGaAWcAAOq6YRXYDCRg+RWIcA5JSC+kWdCepQ+v3RYCU3RInzRMCGwlpC19NYBW1Ye08R1AA) ```ts enum LogLevel { Off, Debug, Error, Fatal }; interface LoggerConfig { name: string; level: LogLevel; } class Logger { config: Readonly; constructor({name, level}: LoggerConfig) { this.config = {name, level}; Object.freeze(this.config); } } const config: LoggerConfig = { name: 'MyApp', level: LogLevel.Debug }; const logger = new Logger(config); // TypeScript Error: cannot assign to read-only property. logger.config.level = LogLevel.Error; // We are able to edit config variable as we please. config.level = LogLevel.Error; ```
- [`Pick`](https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys) - From `T`, pick a set of properties whose keys are in the union `K`.
Example [Playground](https://typescript-play.js.org/?target=6#code/AQ4SwOwFwUwJwGYEMDGNgEE5TCgNugN4BQoZwOUBAXMAM5RyQDmA3KeSFABYCuAtgCMISMHloMmENh04oA9tBjQJjFuzIBfYrOAB6PcADCcGElh1gEGAHcKATwAO6ebyjB5CTNlwFwSxFR0BX5HeToYABNgBDh5fm8cfBg6AHIKG3ldA2BHOOcfFNpUygJ0pAhokr4hETFUgDpswywkggAFUwA3MFtgAF5gQgowKhhVKTYKGuFRcXo1aVZgbTIoJ3RW3xhOmB6+wfbcAGsAHi3kgBpgEtGy4AAfG54BWfqAPnZm4AAlZUj4MAkMA8GAGB4vEgfMlLLw6CwPBA8PYRmMgZVgAC6CgmI4cIommQELwICh8RBgKZKvALh1ur0bHQABR5PYMui0Wk7em2ADaAF0AJS0AASABUALIAGQAogR+Mp3CROCAFBBwVC2ikBpj5CgBIqGjizLA5TAFdAmalImAuqlBRoVQh5HBgEy1eDWfs7J5cjzGYKhroVfpDEhHM4MV6GRR5NN0JrtnRg6BVirTFBeHAKYmYY6QNpdB73LmCJZBlSAXAubtvczeSmQMNSuMbmKNgBlHFgPEUNwusBIPAAQlS1xetTmxT0SDoESgdD0C4aACtHMwxytLrohawgA) ```ts interface Article { title: string; thumbnail: string; content: string; } // Creates new type out of the `Article` interface composed // from the Articles' two properties: `title` and `thumbnail`. // `ArticlePreview = {title: string; thumbnail: string}` type ArticlePreview = Pick; // Render a list of articles using only title and description. function renderArticlePreviews(previews: ArticlePreview[]): HTMLElement { const articles = document.createElement('div'); for (const preview of previews) { // Append preview to the articles. } return articles; } const articles = renderArticlePreviews([ { title: 'TypeScript tutorial!', thumbnail: '/assets/ts.jpg' } ]); ```
- [`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type) - Construct a type with a set of properties `K` of type `T`.
Example [Playground](https://typescript-play.js.org/?target=6#code/AQ4ejYAUHsGcCWAXBMB2dgwGbAKYC2ADgDYwCeeemCaWArgE7ADGMxAhmuQHQBQoYEnJE8wALKEARnkaxEKdMAC8wAOS0kstGuAAfdQBM8ANzxlRjXQbVaWACwC0JPB0NqA3HwGgIwAJJoWozYHCxixnAsjAhStADmwESMMJYo1Fi4HMCIaPEu+MRklHj8gpqyoeHAAKJFFFTAAN4+giDYCIxwSAByHAR4AFw5SDF5Xm2gJBzdfQPD3WPxE5PAlBxdAPLYNQAelgh4aOHDaPQEMowrIAC+3oJ+AMKMrlrAXFhSAFZ4LEhC9g4-0BmA4JBISXgiCkBQABpILrJ5MhUGhYcATGD6Bk4Hh-jNgABrPDkOBlXyQAAq9ngYmJpOAAHcEOCRjAXqwYODfoo6DhakUSph+Uh7GI4P0xER4Cj0OSQGwMP8tP1hgAlX7swwAHgRl2RvIANALSA08ABtAC6AD4VM1Wm0Kow0MMrYaHYJjGYLLJXZb3at1HYnC43Go-QHQDcvA6-JsmEJXARgCDgMYWAhjIYhDAU+YiMAAFIwex0ZmilMITCGF79TLAGRsAgJYAAZRwSEZGzEABFTOZUrJ5Yn+jwnWgeER6HB7AAKJrADpdXqS4ZqYultTG6azVfqHswPBbtauLY7fayQ7HIbAAAMwBuAEoYw9IBq2Ixs9h2eFMOQYPQObALQKJgggABeYhghCIpikkKRpOQRIknAsZUiIeCttECBEP8NSMCkjDDAARMGziuIYxHwYOjDCMBmDNnAuTxA6irdCOBB1Lh5Dqpqn66tISIykawBnOCtqqC0gbjqc9DgpGkxegOliyfJDrRkAA) ```ts // Positions of employees in our company. type MemberPosition = 'intern' | 'developer' | 'tech-lead'; // Interface describing properties of a single employee. interface Employee { firstName: string; lastName: string; yearsOfExperience: number; } // Create an object that has all possible `MemberPosition` values set as keys. // Those keys will store a collection of Employees of the same position. const team: Record = { intern: [], developer: [], 'tech-lead': [], }; // Our team has decided to help John with his dream of becoming Software Developer. team.intern.push({ firstName: 'John', lastName: 'Doe', yearsOfExperience: 0 }); // `Record` forces you to initialize all of the property keys. // TypeScript Error: "tech-lead" property is missing const teamEmpty: Record = { intern: null, developer: null, }; ```
- [`Exclude`](https://www.typescriptlang.org/docs/handbook/utility-types.html#excludetype-excludedunion) - Exclude from `T` those types that are assignable to `U`.
Example [Playground](https://typescript-play.js.org/?target=6#code/JYOwLgpgTgZghgYwgAgMrQG7QMIHsQzADmyA3gFDLIAOuUYAXMiAK4A2byAPsgM5hRQJHqwC2AI2gBucgF9y5MAE9qKAEoQAjiwj8AEnBAATNtGQBeZAAooWphu26wAGmS3e93bRC8IASgsAPmRDJRlyAHoI5ABRAA8ENhYjFFYOZGVVZBgoXFFkAAM0zh5+QRBhZhYJaAKAOkjogEkQZAQ4X2QAdwALCFbaemRgXmQtFjhOMFwq9K6ULuB0lk6U+HYwZAxJnQaYFhAEMGB8ZCIIMAAFOjAANR2IK0HGWISklIAedCgsKDwCYgAbQA5M9gQBdVzFQJ+JhiSRQMiUYYwayZCC4VHPCzmSzAspCYEBWxgFhQAZwKC+FpgJ43VwARgADH4ZFQSWSBjcZPJyPtDsdTvxKWBvr8rD1DCZoJ5HPopaYoK4EPhCEQmGKcKriLCtrhgEYkVQVT5Nr4fmZLLZtMBbFZgT0wGBqES6ghbHBIJqoBKFdBWQpjfh+DQbhY2tqiHVsbjLMVkAB+ZAAZiZaeQTHOVxu9ySjxNaujNwDVHNvzqbBGkBAdPoAfkQA) ```ts interface ServerConfig { port: null | string | number; } type RequestHandler = (request: Request, response: Response) => void; // Exclude `null` type from `null | string | number`. // In case the port is equal to `null`, we will use default value. function getPortValue(port: Exclude): number { if (typeof port === 'string') { return parseInt(port, 10); } return port; } function startServer(handler: RequestHandler, config: ServerConfig): void { const server = require('http').createServer(handler); const port = config.port === null ? 3000 : getPortValue(config.port); server.listen(port); } ```
- [`Extract`](https://www.typescriptlang.org/docs/handbook/utility-types.html#extracttype-union) - Extract from `T` those types that are assignable to `U`.
Example [Playground](https://typescript-play.js.org/?target=6#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXzSwEdkQBJYACgEoAueVZAWwCMQYBuAKDDwGcM8MgBF4AXngBlAJ6scESgHIRi6ty5ZUGdoihgEABXZ888AN5d48ANoiAuvUat23K6ihMQ9ATE0BzV3goPy8GZjZOLgBfLi4Aejj4AEEICBwAdz54MAALKFQQ+BxEeAAHY1NgKAwoIKy0grr4DByEUpgccpgMaXgAaxBerCzi+B9-ZulygDouFHRsU1z8kKMYE1RhaqgAHkt4AHkWACt4EAAPbVRgLLWNgBp9gGlBs8uQa6yAUUuYPQwdgNpKM7nh7mMML4CgA+R5WABqUAgpDeVxuhxO1he0jsXGh8EoOBO9COx3BQPo2PBADckaR6IjkSA6PBqTgsMBzPsicdrEC7OJWXSQNwYvFEgAVTS9JLXODpeDpKBZFg4GCoWa8VACIJykAKiQWKy2YQOAioYikCg0OEMDyhRSy4DyxS24KhAAMjyi6gS8AAwjh5OD0iBFHAkJoEOksC1mnkMJq8gUQKDNttKPlnfrwYp3J5XfBHXqoKpfYkAOI4ansTxaeDADmoRSCCBYAbxhC6TDx6rwYHIRX5bScjA4bLJwoDmDwDkfbA9JMrVMVdM1TN69LgkTgwgkchUahqIA) ```ts declare function uniqueId(): number; const ID = Symbol('ID'); interface Person { [ID]: number; name: string; age: number; } // Allows changing the person data as long as the property key is of string type. function changePersonData< Obj extends Person, Key extends Extract, Value extends Obj[Key] > (obj: Obj, key: Key, value: Value): void { obj[key] = value; } // Tiny Andrew was born. const andrew = { [ID]: uniqueId(), name: 'Andrew', age: 0, }; // Cool, we're fine with that. changePersonData(andrew, 'name', 'Pony'); // Government didn't like the fact that you wanted to change your identity. changePersonData(andrew, ID, uniqueId()); ```
- [`NonNullable`](https://www.typescriptlang.org/docs/handbook/utility-types.html#nonnullabletype) - Exclude `null` and `undefined` from `T`.
Example Works with strictNullChecks set to true. [Playground](https://typescript-play.js.org/?target=6#code/C4TwDgpgBACg9gJ2AOQK4FsBGEFQLxQDOwCAlgHYDmUAPlORtrnQwDasDcAUFwPQBU-WAEMkUOADMowqAGNWwwoSgATCBIqlgpOOSjAAFsOBRSy1IQgr9cKJlSlW1mZYQA3HFH68u8xcoBlHA8EACEHJ08Aby4oKDBUTFZSWXjEFEYcAEIALihkXTR2YSSIAB54JDQsHAA+blj4xOTUsHSACkMzPKD3HHDHNQQAGjSkPMqMmoQASh7g-oihqBi4uNIpdraxPAI2VhmVxrX9AzMAOm2ppnwoAA4ABifuE4BfKAhWSyOTuK7CS7pao3AhXF5rV48E4ICDAVAIPT-cGQyG+XTEIgLMJLTx7CAAdygvRCA0iCHaMwarhJOIQjUBSHaACJHk8mYdeLwxtdcVAAOSsh58+lXdr7Dlcq7A3n3J4PEUdADMcspUE53OluAIUGVTx46oAKuAIAFZGQwCYAKIIBCILjUxaDHAMnla+iodjcIA) ```ts type PortNumber = string | number | null; /** Part of a class definition that is used to build a server */ class ServerBuilder { portNumber!: NonNullable; port(this: ServerBuilder, port: PortNumber): ServerBuilder { if (port == null) { this.portNumber = 8000; } else { this.portNumber = port; } return this; } } const serverBuilder = new ServerBuilder(); serverBuilder .port('8000') // portNumber = '8000' .port(null) // portNumber = 8000 .port(3000); // portNumber = 3000 // TypeScript error serverBuilder.portNumber = null; ```
- [`Parameters`](https://www.typescriptlang.org/docs/handbook/utility-types.html#parameterstype) - Obtain the parameters of a function type in a tuple.
Example [Playground](https://typescript-play.js.org/?target=6#code/GYVwdgxgLglg9mABAZwBYmMANgUwBQxgAOIUAXIgIZgCeA2gLoCUFAbnDACaIDeAUIkQB6IYgCypSlBxUATrMo1ECsJzgBbLEoipqAc0J7EMKMgDkiHLnU4wp46pwAPHMgB0fAL58+oSLARECEosLAA5ABUYG2QAHgAxJGdpVWREPDdMylk9ZApqemZEAF4APipacrw-CApEgBogkKwAYThwckQwEHUAIxxZJl4BYVEImiIZKF0oZRwiWVdbeygJmThgOYgcGFYcbhqApCJsyhtpWXcR1cnEePBoeDAABVPzgbTixFeFd8uEsClADcIxGiygIFkSEOT3SmTc2VydQeRx+ZxwF2QQ34gkEwDgsnSuFmMBKiAADEDjIhYk1Qm0OlSYABqZnYka4xA1DJZHJYkGc7yCbyeRA+CAIZCzNAYbA4CIAdxg2zJwVCkWirjwMswuEaACYmCCgA) ```ts function shuffle(input: any[]): void { // Mutate array randomly changing its' elements indexes. } function callNTimes any> (func: Fn, callCount: number) { // Type that represents the type of the received function parameters. type FunctionParameters = Parameters; return function (...arguments_: FunctionParameters) { for (let i = 0; i < callCount; i++) { func(...arguments_); } } } const shuffleTwice = callNTimes(shuffle, 2); ```
- [`ConstructorParameters`](https://www.typescriptlang.org/docs/handbook/utility-types.html#constructorparameterstype) - Obtain the parameters of a constructor function type in a tuple.
Example [Playground](https://typescript-play.js.org/?target=6#code/MYGwhgzhAECCBOAXAlqApgWQPYBM0mgG8AoaaFRENALmgkXmQDsBzAblOmCycTV4D8teo1YdO3JiICuwRFngAKClWENmLAJRFOZRAAtkEAHQq00ALzlklNBzIBfYk+KhIMAJJTEYJsDQAwmDA+mgAPAAq0GgAHnxMODCKTGgA7tCKxllg8CwQtL4AngDaALraFgB80EWa1SRkAA6MAG5gfNAB4FABPDJyCrQR9tDNyG0dwMGhtBhgjWEiGgA00F70vv4RhY3hEZXVVinpc42KmuJkkv3y8Bly8EPaDWTkhiZd7r3e8LK3llwGCMXGQWGhEOsfH5zJlsrl8p0+gw-goAAo5MAAW3BaHgEEilU0tEhmzQ212BJ0ry4SOg+kg+gBBiMximIGA0nAfAQLGk2N4EAAEgzYcYcnkLsRdDTvNEYkYUKwSdCme9WdM0MYwYhFPSIPpJdTkAAzDKxBUaZX+aAAQgsVmkCTQxuYaBw2ng4Ok8CYcotSu8pMur09iG9vuObxZnx6SN+AyUWTF8MN0CcZE4Ywm5jZHK5aB5fP4iCFIqT4oRRTKRLo6lYVNeAHpG50wOzOe1zHr9NLQ+HoABybsD4HOKXXRA1JCoKhBELmI5pNaB6Fz0KKBAodDYPAgSUTmqYsAALx4m5nC6nW9nGq14KtaEUA9gR9PvuNCjQ9BgACNvcwNBtAcLiAA) ```ts class ArticleModel { title: string; content?: string; constructor(title: string) { this.title = title; } } class InstanceCache any)> { private ClassConstructor: T; private cache: Map> = new Map(); constructor (ctr: T) { this.ClassConstructor = ctr; } getInstance (...arguments_: ConstructorParameters): InstanceType { const hash = this.calculateArgumentsHash(...arguments_); const existingInstance = this.cache.get(hash); if (existingInstance !== undefined) { return existingInstance; } return new this.ClassConstructor(...arguments_); } private calculateArgumentsHash(...arguments_: any[]): string { // Calculate hash. return 'hash'; } } const articleCache = new InstanceCache(ArticleModel); const amazonArticle = articleCache.getInstance('Amazon forests burning!'); ```
- [`ReturnType`](https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype) - Obtain the return type of a function type.
Example [Playground](https://typescript-play.js.org/?target=6#code/MYGwhgzhAECSAmICmBlJAnAbgS2E6A3gFDTTwD2AcuQC4AW2AdgOYAUAlAFzSbnbyEAvkWFFQkGJSQB3GMVI1sNZNwg10TZgG4S0YOUY0kh1es07d+xmvQBXYDXLpWi5UlMaWAGj0GjJ6BtNdkJdBQYIADpXZGgAXmgYpB1ScOwoq38aeN9DYxoU6GFRKzVoJjUwRjwAYXJbPPRuAFkwAAcAHgAxBodsAx9GWwBbACMMAD4cxhloVraOCyYjdAAzMDxoOut1e0d0UNIZ6WhWSPOwdGYIbiqATwBtAF0uaHudUQB6ACpv6ABpJBINqJdAbADW0Do5BOw3u5R2VTwMHIq2gAANtjZ0bkbHsnFCwJh8ONjHp0EgwEZ4JFoN9PkRVr1FAZoMwkDRYIjqkgOrosepoEgAB7+eAwAV2BxOLy6ACCVxgIrFEoMeOl6AACpcwMMORgIB1JRMiBNWKVdhruJKfOdIpdrtwFddXlzKjyACp3Nq842HaDIbL6BrZBIVGhIpB1EMYSLsmjmtWW-YhAA+qegAAYLKQLQj3ZsEsdccmnGcLor2Dn8xGedHGpEIBzEzspfsfMHDNAANTQACMVaIljV5GQkRA5DYmIpVKQAgAJARO9le33BDXIyi0YuLW2nJFGLqkOvxFB0YPdBSaLZ0IwNzyPkO8-xkGgsLh8Al427a3hWAhXwwHA8EHT5PmgAB1bAQBAANJ24adKWpft72RaBUTgRBUCAj89HAM8xCTaBjggABRQx0DuHJv25P9dCkWRZVIAAiBjoFImpmjlFBgA0NpsjadByDacgIDAEAIAAQmYpjoGYgAZSBsmGPw6DtZiiFA8CoJguDmAQmoZ2QvtUKQLdoAYmBTwgdEiCAA) ```ts /** Provides every element of the iterable `iter` into the `callback` function and stores the results in an array. */ function mapIter< Elem, Func extends (elem: Elem) => any, Ret extends ReturnType >(iter: Iterable, callback: Func): Ret[] { const mapped: Ret[] = []; for (const elem of iter) { mapped.push(callback(elem)); } return mapped; } const setObject: Set = new Set(); const mapObject: Map = new Map(); mapIter(setObject, (value: string) => value.indexOf('Foo')); // number[] mapIter(mapObject, ([key, value]: [number, string]) => { return key % 2 === 0 ? value : 'Odd'; }); // string[] ```
- [`InstanceType`](https://www.typescriptlang.org/docs/handbook/utility-types.html#instancetypetype) - Obtain the instance type of a constructor function type.
Example [Playground](https://typescript-play.js.org/?target=6#code/MYGwhgzhAECSAmICmBlJAnAbgS2E6A3gFDTTwD2AcuQC4AW2AdgOYAUAlAFzSbnbyEAvkWFFQkGJSQB3GMVI1sNZNwg10TZgG4S0YOUY0kh1es07d+xmvQBXYDXLpWi5UlMaWAGj0GjJ6BtNdkJdBQYIADpXZGgAXmgYpB1ScOwoq38aeN9DYxoU6GFRKzVoJjUwRjwAYXJbPPRuAFkwAAcAHgAxBodsAx9GWwBbACMMAD4cxhloVraOCyYjdAAzMDxoOut1e0d0UNIZ6WhWSPOwdGYIbiqATwBtAF0uaHudUQB6ACpv6ABpJBINqJdAbADW0Do5BOw3u5R2VTwMHIq2gAANtjZ0bkbHsnFCwJh8ONjHp0EgwEZ4JFoN9PkRVr1FAZoMwkDRYIjqkgOrosepoEgAB7+eAwAV2BxOLy6ACCVxgIrFEoMeOl6AACpcwMMORgIB1JRMiBNWKVdhruJKfOdIpdrtwFddXlzKjyACp3Nq842HaDIbL6BrZBIVGhIpB1EMYSLsmjmtWW-YhAA+qegAAYLKQLQj3ZsEsdccmnGcLor2Dn8xGedHGpEIBzEzspfsfMHDNAANTQACMVaIljV5GQkRA5DYmIpVKQAgAJARO9le33BDXIyi0YuLW2nJFGLqkOvxFB0YPdBSaLZ0IwNzyPkO8-xkGgsLh8Al427a3hWAhXwwHA8EHT5PmgAB1bAQBAANJ24adKWpft72RaBUTgRBUCAj89HAM8xCTaBjggABRQx0DuHJv25P9dCkWRZVIAAiBjoFImpmjlFBgA0NpsjadByDacgIDAEAIAAQmYpjoGYgAZSBsmGPw6DtZiiFA8CoJguDmAQmoZ2QvtUKQLdoAYmBTwgdEiCAA) ```ts class IdleService { doNothing (): void {} } class News { title: string; content: string; constructor(title: string, content: string) { this.title = title; this.content = content; } } const instanceCounter: Map = new Map(); interface Constructor { new(...arguments_: any[]): any; } // Keep track how many instances of `Constr` constructor have been created. function getInstance< Constr extends Constructor, Arguments extends ConstructorParameters >(constructor: Constr, ...arguments_: Arguments): InstanceType { let count = instanceCounter.get(constructor) || 0; const instance = new constructor(...arguments_); instanceCounter.set(constructor, count + 1); console.log(`Created ${count + 1} instances of ${Constr.name} class`); return instance; } const idleService = getInstance(IdleService); // Will log: `Created 1 instances of IdleService class` const newsEntry = getInstance(News, 'New ECMAScript proposals!', 'Last month...'); // Will log: `Created 1 instances of News class` ```
- [`Omit`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys) - Constructs a type by picking all properties from T and then removing K.
Example [Playground](https://typescript-play.js.org/?target=6#code/JYOwLgpgTgZghgYwgAgIImAWzgG2QbwChlks4BzCAVShwC5kBnMKUcgbmKYAcIFgIjBs1YgOXMpSFMWbANoBdTiW5woFddwAW0kfKWEAvoUIB6U8gDCUCHEiNkICAHdkYAJ69kz4GC3JcPG4oAHteKDABBxCYNAxsPFBIWEQUCAAPJG4wZABySUFcgJAAEzMLXNV1ck0dIuCw6EjBADpy5AB1FAQ4EGQAV0YUP2AHDy8wEOQbUugmBLwtEIA3OcmQnEjuZBgQqE7gAGtgZAhwKHdkHFGwNvGUdDIcAGUliIBJEF3kAF5kAHlML4ADyPBIAGjyBUYRQAPnkqho4NoYQA+TiEGD9EAISIhPozErQMG4AASK2gn2+AApek9pCSXm8wFSQooAJQMUkAFQAsgAZACiOAgmDOOSIJAQ+OYyGl4DgoDmf2QJRCCH6YvALQQNjsEGFovF1NyJWAy1y7OUyHMyE+yRAuFImG4Iq1YDswHxbRINjA-SgfXlHqVUE4xiAA) ```ts interface Animal { imageUrl: string; species: string; images: string[]; paragraphs: string[]; } // Creates new type with all properties of the `Animal` interface // except 'images' and 'paragraphs' properties. We can use this // type to render small hover tooltip for a wiki entry list. type AnimalShortInfo = Omit; function renderAnimalHoverInfo (animals: AnimalShortInfo[]): HTMLElement { const container = document.createElement('div'); // Internal implementation. return container; } ```
- [`Uppercase`](https://www.typescriptlang.org/docs/handbook/utility-types.html#uppercasestringtype) - Transforms every character in a string into uppercase.
Example ```ts type T = Uppercase<'hello'>; // 'HELLO' type T2 = Uppercase<'foo' | 'bar'>; // 'FOO' | 'BAR' type T3 = Uppercase<`aB${S}`>; type T4 = T3<'xYz'>; // 'ABXYZ' type T5 = Uppercase; // string type T6 = Uppercase; // any type T7 = Uppercase; // never type T8 = Uppercase<42>; // Error, type 'number' does not satisfy the constraint 'string' ```
- [`Lowercase`](https://www.typescriptlang.org/docs/handbook/utility-types.html#lowercasestringtype) - Transforms every character in a string into lowercase.
Example ```ts type T = Lowercase<'HELLO'>; // 'hello' type T2 = Lowercase<'FOO' | 'BAR'>; // 'foo' | 'bar' type T3 = Lowercase<`aB${S}`>; type T4 = T3<'xYz'>; // 'abxyz' type T5 = Lowercase; // string type T6 = Lowercase; // any type T7 = Lowercase; // never type T8 = Lowercase<42>; // Error, type 'number' does not satisfy the constraint 'string' ```
- [`Capitalize`](https://www.typescriptlang.org/docs/handbook/utility-types.html#capitalizestringtype) - Transforms the first character in a string into uppercase.
Example ```ts type T = Capitalize<'hello'>; // 'Hello' type T2 = Capitalize<'foo' | 'bar'>; // 'Foo' | 'Bar' type T3 = Capitalize<`aB${S}`>; type T4 = T3<'xYz'>; // 'ABxYz' type T5 = Capitalize; // string type T6 = Capitalize; // any type T7 = Capitalize; // never type T8 = Capitalize<42>; // Error, type 'number' does not satisfy the constraint 'string' ```
- [`Uncapitalize`](https://www.typescriptlang.org/docs/handbook/utility-types.html#uncapitalizestringtype) - Transforms the first character in a string into lowercase.
Example ```ts type T = Uncapitalize<'Hello'>; // 'hello' type T2 = Uncapitalize<'Foo' | 'Bar'>; // 'foo' | 'bar' type T3 = Uncapitalize<`AB${S}`>; type T4 = T3<'xYz'>; // 'aBxYz' type T5 = Uncapitalize; // string type T6 = Uncapitalize; // any type T7 = Uncapitalize; // never type T8 = Uncapitalize<42>; // Error, type 'number' does not satisfy the constraint 'string' ```
You can find some examples in the [TypeScript docs](https://www.typescriptlang.org/docs/handbook/utility-types.html). ## Maintainers - [Sindre Sorhus](https://github.com/sindresorhus) - [Haozheng Li](https://github.com/Emiyaaaaa) - [Som Shekhar Mukherjee](https://github.com/som-sm) - [Jarek Radosz](https://github.com/CvX) - [Dimitri Benin](https://github.com/BendingBender) - [Pelle Wessman](https://github.com/voxpelli) - [Sébastien Mischler](https://github.com/skarab42) ## License - [MIT](license-mit) - [CC0-1.0](license-cc0) SPDX-License-Identifier: (MIT OR CC0-1.0) ================================================ FILE: source/all-extend.d.ts ================================================ import type {CollapseRestElement} from './internal/array.d.ts'; import type {ApplyDefaultOptions} from './internal/object.d.ts'; import type {IfNotAnyOrNever, Not} from './internal/type.d.ts'; import type {IsAny} from './is-any.d.ts'; import type {IsNever} from './is-never.d.ts'; import type {Or} from './or.d.ts'; import type {UnknownArray} from './unknown-array.d.ts'; /** @see {@link AllExtend} */ export type AllExtendOptions = { /** Consider `never` elements to match the target type only if the target type itself is `never` (or `any`). - When set to `true` (default), `never` is _not_ treated as a bottom type, instead, it is treated as a type that matches only itself (or `any`). - When set to `false`, `never` is treated as a bottom type, and behaves as it normally would. @default true @example ``` import type {AllExtend} from 'type-fest'; type A = AllExtend<[1, 2, never], number, {strictNever: true}>; //=> false type B = AllExtend<[1, 2, never], number, {strictNever: false}>; //=> true type C = AllExtend<[never, never], never, {strictNever: true}>; //=> true type D = AllExtend<[never, never], never, {strictNever: false}>; //=> true type E = AllExtend<['a', 'b', never], any, {strictNever: true}>; //=> true type F = AllExtend<['a', 'b', never], any, {strictNever: false}>; //=> true type G = AllExtend<[never, 1], never, {strictNever: true}>; //=> false type H = AllExtend<[never, 1], never, {strictNever: false}>; //=> false ``` */ strictNever?: boolean; }; type DefaultAllExtendOptions = { strictNever: true; }; /** Returns a boolean for whether every element in an array type extends another type. @example ``` import type {AllExtend} from 'type-fest'; type A = AllExtend<[1, 2, 3], number>; //=> true type B = AllExtend<[1, 2, '3'], number>; //=> false type C = AllExtend<[number, number | string], number>; //=> boolean type D = AllExtend<[true, boolean, true], true>; //=> boolean ``` Note: Behaviour of optional elements depend on the `exactOptionalPropertyTypes` compiler option. When the option is disabled, the target type must include `undefined` for a successful match. ``` // @exactOptionalPropertyTypes: true import type {AllExtend} from 'type-fest'; type A = AllExtend<[1?, 2?, 3?], number>; //=> true ``` ``` // @exactOptionalPropertyTypes: false import type {AllExtend} from 'type-fest'; type A = AllExtend<[1?, 2?, 3?], number>; //=> boolean type B = AllExtend<[1?, 2?, 3?], number | undefined>; //=> true ``` @see {@link AllExtendOptions} @category Utilities @category Array */ export type AllExtend = _AllExtend, Type, ApplyDefaultOptions>; type _AllExtend> = IfNotAnyOrNever extends true ? Or, IsAny>, Not> extends true // If target `Type` is also `never`, or is `any`, or `strictNever` is disabled, recurse further. ? _AllExtend : false : First extends Type ? _AllExtend : false : true, false, false>; export {}; ================================================ FILE: source/all-union-fields.d.ts ================================================ import type {NonRecursiveType, ReadonlyKeysOfUnion, ValueOfUnion} from './internal/index.d.ts'; import type {KeysOfUnion} from './keys-of-union.d.ts'; import type {SharedUnionFields} from './shared-union-fields.d.ts'; import type {Simplify} from './simplify.d.ts'; import type {UnknownArray} from './unknown-array.d.ts'; /** Create a type with all fields from a union of object types. Use-cases: - You want a safe object type where each key exists in the union object. @example ``` import type {AllUnionFields} from 'type-fest'; type Cat = { name: string; type: 'cat'; catType: string; }; type Dog = { name: string; type: 'dog'; dogType: string; }; function displayPetInfo(petInfo: Cat | Dog) { // typeof petInfo => // { // name: string; // type: 'cat'; // catType: string; // } | { // name: string; // type: 'dog'; // dogType: string; // } console.log('name:', petInfo.name); console.log('type:', petInfo.type); // TypeScript complains about `catType` and `dogType` not existing on type `Cat | Dog`. // @ts-expect-error console.log('animal type:', petInfo.catType ?? petInfo.dogType); } function displayPetInfoWithAllUnionFields(petInfo: AllUnionFields) { // typeof petInfo => // { // name: string; // type: 'cat' | 'dog'; // catType?: string; // dogType?: string; // } console.log('name:', petInfo.name); console.log('type:', petInfo.type); // No TypeScript error. console.log('animal type:', petInfo.catType ?? petInfo.dogType); } ``` @see {@link SharedUnionFields} @category Object @category Union */ export type AllUnionFields = Extract | ReadonlySet | UnknownArray> extends infer SkippedMembers ? Exclude extends infer RelevantMembers ? | SkippedMembers | Simplify< // Include fields that are common in all union members SharedUnionFields & // Include readonly fields present in any union member { readonly [P in ReadonlyKeysOfUnion]?: ValueOfUnion> } & // Include remaining fields that are neither common nor readonly { [P in Exclude, ReadonlyKeysOfUnion | keyof RelevantMembers>]?: ValueOfUnion } > : never : never; export {}; ================================================ FILE: source/and-all.d.ts ================================================ import type {AllExtend} from './all-extend.d.ts'; /** Returns a boolean for whether all of the given elements are `true`. Use-cases: - Check if all conditions in a list of booleans are met. @example ``` import type {AndAll} from 'type-fest'; type TTT = AndAll<[true, true, true]>; //=> true type TTF = AndAll<[true, true, false]>; //=> false type TFT = AndAll<[true, false, true]>; //=> false ``` Note: When `boolean` is passed as an element, it is distributed into separate cases, and the final result is a union of those cases. For example, `AndAll<[true, boolean]>` expands to `AndAll<[true, true]> | AndAll<[true, false]>`, which simplifies to `true | false` (i.e., `boolean`). @example ``` import type {AndAll} from 'type-fest'; type A = AndAll<[true, boolean]>; //=> boolean type B = AndAll<[false, boolean]>; //=> false ``` Note: If any of the elements is `never`, the result becomes `false`. @example ``` import type {AndAll} from 'type-fest'; type A = AndAll<[true, true, never]>; //=> false type B = AndAll<[false, never, never]>; //=> false type C = AndAll<[never, never, never]>; //=> false type D = AndAll<[boolean, true, never]>; //=> false ``` Note: If `any` is passed as an element, it is treated as `boolean` and the result is computed accordingly. @example ``` import type {AndAll} from 'type-fest'; type A = AndAll<[false, any]>; //=> false type B = AndAll<[true, any]>; //=> boolean ``` Note: `AndAll<[]>` evaluates to `true` due to the concept of [vacuous truth](https://en.wikipedia.org/wiki/Logical_conjunction#:~:text=In%20keeping%20with%20the%20concept%20of%20vacuous%20truth%2C%20when%20conjunction%20is%20defined%20as%20an%20operator%20or%20function%20of%20arbitrary%20arity%2C%20the%20empty%20conjunction%20(AND%2Ding%20over%20an%20empty%20set%20of%20operands)%20is%20often%20defined%20as%20having%20the%20result%20true.), i.e., there are no `false` elements in an empty tuple. @see {@link And} @see {@link OrAll} */ export type AndAll = AllExtend; export {}; ================================================ FILE: source/and.d.ts ================================================ import type {AndAll} from './and-all.d.ts'; /** Returns a boolean for whether two given types are both true. Use-case: Constructing complex conditional types where multiple conditions must be satisfied. @example ``` import type {And} from 'type-fest'; type TT = And; //=> true type TF = And; //=> false type FT = And; //=> false type FF = And; //=> false ``` Note: When `boolean` is passed as an argument, it is distributed into separate cases, and the final result is a union of those cases. For example, `And` expands to `And | And`, which simplifies to `true | false` (i.e., `boolean`). @example ``` import type {And} from 'type-fest'; type A = And; //=> boolean type B = And; //=> boolean type C = And; //=> false type D = And; //=> false type E = And; //=> boolean ``` Note: If either of the types is `never`, the result becomes `false`. @example ``` import type {And} from 'type-fest'; type A = And; //=> false type B = And; //=> false type C = And; //=> false type D = And; //=> false type E = And; //=> false type F = And; //=> false type G = And; //=> false ``` @see {@link AndAll} @see {@link Or} @see {@link Xor} */ export type And = AndAll<[A, B]>; export {}; ================================================ FILE: source/array-element.d.ts ================================================ import type {UnknownArray} from './unknown-array.d.ts'; /** Extracts the element type of an array or tuple. Use-cases: - When you need type-safe element extraction that returns `never` for non-arrays. - When extracting element types from generic array parameters in function signatures. - For better readability and explicit intent over using `T[number]` directly. Note: Returns `never` if the type is not an array. @example ``` import type {ArrayElement} from 'type-fest'; // Arrays type StringArray = ArrayElement; //=> string // Tuples type Tuple = ArrayElement<[1, 2, 3]>; //=> 1 | 2 | 3 // Type-safe type NotArray = ArrayElement<{a: string}>; //=> never // Practical example declare function getRandomElement(array: T): ArrayElement; getRandomElement(['foo', 'bar', 'baz'] as const); //=> 'foo' | 'bar' | 'baz' ``` @see {@link ArrayValues} - For directly extracting values from a constant array type. @see {@link IterableElement} - For iterables like `Set`, `Map`, and generators (not suitable for all use cases due to different inference behavior). @category Array */ export type ArrayElement = T extends UnknownArray ? T[number] : never; export {}; ================================================ FILE: source/array-indices.d.ts ================================================ /** Provides valid indices for a constant array or tuple. Use-case: This type is useful when working with constant arrays or tuples and you want to enforce type-safety for accessing elements by their indices. @example ``` import type {ArrayIndices, ArrayValues} from 'type-fest'; const weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] as const; type Weekday = ArrayIndices; type WeekdayName = ArrayValues; const getWeekdayName = (day: Weekday): WeekdayName => weekdays[day]; ``` @see {@link ArrayValues} @category Array */ export type ArrayIndices = Exclude['length'], Element['length']>; export {}; ================================================ FILE: source/array-length.d.ts ================================================ /** Return the length of an array. Equivalent to `T['length']` where `T` extends any array. Tuples resolve to numeric literals, while non-tuples resolve to the `number` type. @example ``` import type {ArrayLength} from 'type-fest'; type TupleLength = ArrayLength<[1, 2, 3]>; //=> 3 type TupleWithOptionalMembersLength = ArrayLength<[1, 2, number?]>; //=> 2 | 3 type NonTupleArrayLength = ArrayLength; //=> number type TupleWithRestElementLength = ArrayLength<[1, 2, ...string[]]>; //=> number // Distinguish between arrays with fixed and non-fixed lengths type IsFixedLengthArray = number extends ArrayLength ? false : true; type A = IsFixedLengthArray; //=> false type B = IsFixedLengthArray<[1, 2, 3]>; //=> true ``` @category Array */ export type ArrayLength = T['length']; export {}; ================================================ FILE: source/array-reverse.d.ts ================================================ import type {If} from './if.d.ts'; import type {IsArrayReadonly} from './internal/array.d.ts'; import type {IfNotAnyOrNever, IsExactOptionalPropertyTypesEnabled} from './internal/type.d.ts'; import type {IsOptionalKeyOf} from './is-optional-key-of.d.ts'; import type {UnknownArray} from './unknown-array.d.ts'; /** Reverse the order of elements in a tuple type. @example ```ts import type {ArrayReverse} from 'type-fest'; type A = ArrayReverse<[string, number, boolean]>; //=> [boolean, number, string] type B = ArrayReverse; //=> readonly [...boolean[], number, string] type C = ArrayReverse<['foo', 'bar'] | readonly [1, 2, 3]>; //=> ['bar', 'foo'] | readonly [3, 2, 1] type D = ArrayReverse; //=> string[] type E = ArrayReverse<[]>; //=> [] ``` Note: If the tuple contains optional elements, the result will be a union of tuples, refer to the examples below: @example ```ts import type {ArrayReverse} from 'type-fest'; type A = ArrayReverse<[string, number, boolean?]>; //=> [number, string] | [boolean, number, string] type B = ArrayReverse<[string, number?, boolean?]>; //=> [string] | [number, string] | [boolean, number, string] type C = ArrayReverse<[string?, number?, boolean?]>; //=> [] | [string] | [number, string] | [boolean, number, string] type D = ArrayReverse<[string, number?, ...boolean[]]>; //=> [string] | [...boolean[], number, string] type E = ArrayReverse<[string?, number?, ...boolean[]]>; //=> [] | [string] | [...boolean[], number, string] ``` @category Array */ export type ArrayReverse = IfNotAnyOrNever extends infer Result ? If, Readonly, Result> : never // Should never happen : never>; // Should never happen type _ArrayReverse< TArray extends UnknownArray, BeforeRestAcc extends UnknownArray = [], AfterRestAcc extends UnknownArray = [], Result extends UnknownArray = never, > = keyof TArray & `${number}` extends never // Enters this branch, if `TArray` is empty (e.g., `[]`), // or `TArray` contains no non-rest elements preceding the rest element (e.g., `[...string[]]` or `[...string[], string]`). ? TArray extends readonly [...infer Rest, infer Last] ? _ArrayReverse // Accumulate elements that are present after the rest element in reverse order. : Result | [...AfterRestAcc, ...TArray, ...BeforeRestAcc] // Add the rest element between the accumulated elements. : TArray extends readonly [(infer First)?, ...infer Rest] ? IsOptionalKeyOf extends true ? _ArrayReverse< Rest, [First | (If), ...BeforeRestAcc], // Add `| undefined` for optional elements, if `exactOptionalPropertyTypes` is disabled. AfterRestAcc, Result | BeforeRestAcc > : _ArrayReverse : never; // Should never happen, since `readonly [(infer First)?, ...infer Rest]` is a top-type for arrays. export {}; ================================================ FILE: source/array-slice.d.ts ================================================ import type {Sum} from './sum.d.ts'; import type {LessThanOrEqual} from './less-than-or-equal.d.ts'; import type {GreaterThanOrEqual} from './greater-than-or-equal.d.ts'; import type {GreaterThan} from './greater-than.d.ts'; import type {IsNegative} from './numeric.d.ts'; import type {Not, TupleMin} from './internal/index.d.ts'; import type {IsEqual} from './is-equal.d.ts'; import type {And} from './and.d.ts'; import type {ArraySplice} from './array-splice.d.ts'; import type {IsNever} from './is-never.d.ts'; /** Returns an array slice of a given range, just like `Array#slice()`. @example ``` import type {ArraySlice} from 'type-fest'; type T0 = ArraySlice<[0, 1, 2, 3, 4]>; //=> [0, 1, 2, 3, 4] type T1 = ArraySlice<[0, 1, 2, 3, 4], 0, -1>; //=> [0, 1, 2, 3] type T2 = ArraySlice<[0, 1, 2, 3, 4], 1, -2>; //=> [1, 2] type T3 = ArraySlice<[0, 1, 2, 3, 4], -2, 4>; //=> [3] type T4 = ArraySlice<[0, 1, 2, 3, 4], -2, -1>; //=> [3] type T5 = ArraySlice<[0, 1, 2, 3, 4], 0, -999>; //=> [] function arraySlice< const Array_ extends readonly unknown[], Start extends number = 0, End extends number = Array_['length'], >(array: Array_, start?: Start, end?: End) { return array.slice(start, end) as ArraySlice; } const slice = arraySlice([1, '2', {a: 3}, [4, 5]], 0, -1); type Slice = typeof slice; //=> [1, '2', {readonly a: 3}] const value = slice[2].a; //=> 3 // @ts-expect-error -- TS2493: Tuple type '[1, "2", {readonly a: 3}]' of length '3' has no element at index '3'. const invalidIndexAccess = slice[3]; ``` @category Array */ export type ArraySlice< Array_ extends readonly unknown[], Start extends number = never, End extends number = never, > = Array_ extends unknown // To distributive type ? IsNever extends true ? IsNever extends true ? _ArraySlice : End extends unknown // To distribute `End` ? _ArraySlice : never // Never happens : IsNever extends true ? Start extends unknown // To distribute `Start` ? _ArraySlice : never // Never happens : Start extends unknown // To distribute `Start` ? End extends unknown // To distribute `End` ? _ArraySlice : never // Never happens : never // Never happens : never; // Never happens type _ArraySlice< Array_ extends readonly unknown[], Start extends number = 0, End extends number = Array_['length'], > = And, IsEqual> extends true ? Array_ : number extends Array_['length'] ? VariableLengthArraySliceHelper : ArraySliceHelper extends true ? 0 : Start, IsEqual extends true ? Array_['length'] : End>; type VariableLengthArraySliceHelper< Array_ extends readonly unknown[], Start extends number, End extends number, > = And>, IsEqual> extends true ? ArraySplice : And< And>, Not>>, IsEqual, true> > extends true ? ArraySliceByPositiveIndex : []; type ArraySliceHelper< Array_ extends readonly unknown[], Start extends number = 0, End extends number = Array_['length'], TraversedElement extends Array = [], Result extends Array = [], ArrayLength extends number = Array_['length'], PositiveS extends number = IsNegative extends true ? Sum extends infer AddResult extends number ? number extends AddResult // (ArrayLength + Start) < 0 ? 0 : GreaterThan extends true ? AddResult : 0 : never : Start, PositiveE extends number = IsNegative extends true ? Sum : End, > = true extends [IsNegative, LessThanOrEqual, GreaterThanOrEqual][number] ? [] : ArraySliceByPositiveIndex, TupleMin<[PositiveE, ArrayLength]>>; type ArraySliceByPositiveIndex< Array_ extends readonly unknown[], Start extends number, End extends number, Result extends Array = [], > = Start extends End ? Result : ArraySliceByPositiveIndex, End, [...Result, Array_[Start]]>; export {}; ================================================ FILE: source/array-splice.d.ts ================================================ import type {StaticPartOfArray, VariablePartOfArray} from './internal/index.d.ts'; import type {GreaterThanOrEqual} from './greater-than-or-equal.d.ts'; import type {Subtract} from './subtract.d.ts'; import type {UnknownArray} from './unknown-array.d.ts'; import type {TupleOf} from './tuple-of.d.ts'; /** The implementation of `SplitArrayByIndex` for fixed length arrays. */ type SplitFixedArrayByIndex = SplitIndex extends 0 ? [[], T] : T extends readonly [...TupleOf, ...infer V] ? T extends readonly [...infer U, ...V] ? [U, V] : [never, never] : [never, never]; /** The implementation of `SplitArrayByIndex` for variable length arrays. */ type SplitVariableArrayByIndex['length']>, T2 = T1 extends number ? TupleOf extends true ? T1 : number, VariablePartOfArray[number]> : [], > = SplitIndex extends 0 ? [[], T] : GreaterThanOrEqual['length'], SplitIndex> extends true ? [ SplitFixedArrayByIndex, SplitIndex>[0], [ ...SplitFixedArrayByIndex, SplitIndex>[1], ...VariablePartOfArray, ], ] : [ [ ...StaticPartOfArray, ...(T2 extends UnknownArray ? T2 : []), ], VariablePartOfArray, ]; /** Split the given array `T` by the given `SplitIndex`. @example ``` type A = SplitArrayByIndex<[1, 2, 3, 4], 2>; //=> [[1, 2], [3, 4]]; type B = SplitArrayByIndex<[1, 2, 3, 4], 0>; //=> [[], [1, 2, 3, 4]]; ``` */ type SplitArrayByIndex = SplitIndex extends 0 ? [[], T] : number extends T['length'] ? SplitVariableArrayByIndex : SplitFixedArrayByIndex; /** Create a new array type by adding or removing elements at a specified index range in the original array. Use-case: Replace or insert items in an array type. Like [`Array#splice()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) but for types. @example ``` import type {ArraySplice} from 'type-fest'; type SomeMonths0 = ['January', 'April', 'June']; type Months0 = ArraySplice; //=> ['January', 'Feb', 'March', 'April', 'June'] type SomeMonths1 = ['January', 'April', 'June']; type Months1 = ArraySplice; //=> ['January', 'June'] type SomeMonths2 = ['January', 'Foo', 'April']; type Months2 = ArraySplice; //=> ['January', 'Feb', 'March', 'April'] ``` @category Array */ export type ArraySplice< T extends UnknownArray, Start extends number, DeleteCount extends number, Items extends UnknownArray = [], > = SplitArrayByIndex extends [infer U extends UnknownArray, infer V extends UnknownArray] ? SplitArrayByIndex extends [infer _Deleted extends UnknownArray, infer X extends UnknownArray] ? [...U, ...Items, ...X] : never // Should never happen : never; // Should never happen export {}; ================================================ FILE: source/array-tail.d.ts ================================================ import type {If} from './if.d.ts'; import type {IfNotAnyOrNever, IsArrayReadonly} from './internal/index.d.ts'; import type {UnknownArray} from './unknown-array.d.ts'; /** Extract the type of an array or tuple minus the first element. @example ``` import type {ArrayTail} from 'type-fest'; type A = ArrayTail<[1, 2, 3]>; //=> [2, 3] type B = ArrayTail; //=> readonly [2, 3] type C = ArrayTail<[1, 2, 3?, ...string[]]>; //=> [2, 3?, ...string[]] type D = ArrayTail; //=> readonly [] type E = ArrayTail<[]>; //=> [] type F = ArrayTail; //=> string[] type G = ArrayTail; //=> readonly [...string[], 1, 2] ``` @example ``` import type {ArrayTail} from 'type-fest'; type Curry = Func extends (...agruments_: infer Arguments) => infer Return ? Arguments extends readonly [] ? Return : (agrument: Arguments[0]) => Curry<(...agruments_: ArrayTail) => Return> : never; declare function curry(fn: Func): Curry; declare function searchBooks(genre: string, minRating: number, available: boolean): string[]; const availableTopSciFi = curry(searchBooks)('sci-fi')(4.5)(true); //=> string[] ``` @category Array */ export type ArrayTail = IfNotAnyOrNever extends infer Result ? If, Readonly, Result> : never // Should never happen : never >; type _ArrayTail = TArray extends readonly [unknown?, ...infer Tail] ? keyof TArray & `${number}` extends never ? TArray extends readonly [] ? [] : TArray // Happens when `TArray` is a non-tuple array (e.g., `string[]`) or has a leading rest element (e.g., `[...string[], number]`) : Tail : []; export {}; ================================================ FILE: source/array-values.d.ts ================================================ /** Provides all values for a constant array or tuple. Use-case: This type is useful when working with constant arrays or tuples and you want to enforce type-safety with their values. @example ``` import type {ArrayValues, ArrayIndices} from 'type-fest'; const weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] as const; type WeekdayName = ArrayValues; type Weekday = ArrayIndices; const getWeekdayName = (day: Weekday): WeekdayName => weekdays[day]; ``` @see {@link ArrayIndices} @category Array */ export type ArrayValues = T[number]; export {}; ================================================ FILE: source/arrayable.d.ts ================================================ /** Create a type that represents either the value or an array of the value. @see {@link Promisable} @example ``` import type {Arrayable} from 'type-fest'; function bundle(input: string, output: Arrayable) { const outputList = Array.isArray(output) ? output : [output]; // … for (const output of outputList) { console.log(`write ${input} to: ${output}`); } } bundle('src/index.js', 'dist/index.js'); bundle('src/index.js', ['dist/index.cjs', 'dist/index.mjs']); ``` @category Array */ export type Arrayable = T // TODO: Use `readonly T[]` when this issue is resolved: https://github.com/microsoft/TypeScript/issues/17002 | T[]; export {}; ================================================ FILE: source/async-return-type.d.ts ================================================ type AsyncFunction = (...arguments_: any[]) => PromiseLike; /** Unwrap the return type of a function that returns a `Promise`. There has been [discussion](https://github.com/microsoft/TypeScript/pull/35998) about implementing this type in TypeScript. @example ```ts import type {AsyncReturnType} from 'type-fest'; declare function asyncFunction(): Promise<{foo: string}>; // This type resolves to the unwrapped return type of `asyncFunction`. type Value = AsyncReturnType; //=> {foo: string} declare function doSomething(value: Value): void; const value = await asyncFunction(); doSomething(value); ``` @category Async */ export type AsyncReturnType = Awaited>; export {}; ================================================ FILE: source/asyncify.d.ts ================================================ import type {SetReturnType} from './set-return-type.d.ts'; /** Create an async version of the given function type, by boxing the return type in `Promise` while keeping the same parameter types. Use-case: You have two functions, one synchronous and one asynchronous that do the same thing. Instead of having to duplicate the type definition, you can use `Asyncify` to reuse the synchronous type. @example ``` import type {Asyncify} from 'type-fest'; // Synchronous function type Config = {featureFlags: Record}; declare function loadConfigSync(path: string): Config; type LoadConfigAsync = Asyncify; //=> (path: string) => Promise ``` @category Async */ export type Asyncify any> = SetReturnType>>>; export {}; ================================================ FILE: source/basic.d.ts ================================================ /** Matches a [`class`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). @category Class */ export type Class = { prototype: Pick; new(...arguments_: Arguments): T; }; /** Matches a [`class` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). @category Class */ export type Constructor = new(...arguments_: Arguments) => T; /** Matches an [`abstract class`](https://www.typescriptlang.org/docs/handbook/2/classes.html#abstract-classes-and-members). @category Class @privateRemarks We cannot use a `type` here because TypeScript throws: 'abstract' modifier cannot appear on a type member. (1070) */ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions export interface AbstractClass extends AbstractConstructor { prototype: Pick; } /** Matches an [`abstract class`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-2.html#abstract-construct-signatures) constructor. @category Class */ export type AbstractConstructor = abstract new(...arguments_: Arguments) => T; export {}; ================================================ FILE: source/camel-case.d.ts ================================================ import type {ApplyDefaultOptions} from './internal/index.d.ts'; import type {Words, WordsOptions} from './words.d.ts'; /** CamelCase options. @see {@link CamelCase} */ export type CamelCaseOptions = WordsOptions & { /** Whether to preserved consecutive uppercase letter. @default false */ preserveConsecutiveUppercase?: boolean; }; export type _DefaultCamelCaseOptions = { splitOnNumbers: true; preserveConsecutiveUppercase: false; }; /** Convert an array of words to camel-case. */ type CamelCaseFromArray< Words extends string[], Options extends Required, OutputString extends string = '', > = Words extends [ infer FirstWord extends string, ...infer RemainingWords extends string[], ] ? Options['preserveConsecutiveUppercase'] extends true ? `${Capitalize}${CamelCaseFromArray}` : `${Capitalize>}${CamelCaseFromArray}` : OutputString; /** Convert a string literal to camel-case. This can be useful when, for example, converting some kebab-cased command-line flags or a snake-cased database result. By default, consecutive uppercase letter are preserved. See {@link CamelCaseOptions.preserveConsecutiveUppercase preserveConsecutiveUppercase} option to change this behaviour. @example ``` import type {CamelCase} from 'type-fest'; // Simple const someVariable: CamelCase<'foo-bar'> = 'fooBar'; const preserveConsecutiveUppercase: CamelCase<'foo-BAR-baz', {preserveConsecutiveUppercase: true}> = 'fooBARBaz'; // Advanced type CamelCasedProperties = { [K in keyof T as CamelCase]: T[K] }; type RawOptions = { 'dry-run': boolean; 'full_family_name': string; foo: number; BAR: string; QUZ_QUX: number; 'OTHER-FIELD': boolean; }; const dbResult: CamelCasedProperties = { dryRun: true, fullFamilyName: 'bar.js', foo: 123, bar: 'foo', quzQux: 6, otherField: false, }; ``` @category Change case @category Template literal */ export type CamelCase = Type extends string ? string extends Type ? Type : Uncapitalize ? Lowercase : Type, Options>, ApplyDefaultOptions >> : Type; export {}; ================================================ FILE: source/camel-cased-properties-deep.d.ts ================================================ import type {CamelCase, CamelCaseOptions, _DefaultCamelCaseOptions} from './camel-case.d.ts'; import type {ApplyDefaultOptions, NonRecursiveType} from './internal/index.d.ts'; import type {UnknownArray} from './unknown-array.d.ts'; /** Convert object properties to camel case recursively. This can be useful when, for example, converting some API types from a different style. @see {@link CamelCasedProperties} @see {@link CamelCase} @example ``` import type {CamelCasedPropertiesDeep} from 'type-fest'; type User = { UserId: number; UserName: string; }; type UserWithFriends = { UserInfo: User; UserFriends: User[]; }; const result: CamelCasedPropertiesDeep = { userInfo: { userId: 1, userName: 'Tom', }, userFriends: [ { userId: 2, userName: 'Jerry', }, { userId: 3, userName: 'Spike', }, ], }; const preserveConsecutiveUppercase: CamelCasedPropertiesDeep<{fooBAR: {fooBARBiz: [{fooBARBaz: string}]}}, {preserveConsecutiveUppercase: true}> = { fooBAR: { fooBARBiz: [{ fooBARBaz: 'string', }], }, }; ``` @category Change case @category Template literal @category Object */ export type CamelCasedPropertiesDeep< Value, Options extends CamelCaseOptions = {}, > = _CamelCasedPropertiesDeep>; type _CamelCasedPropertiesDeep< Value, Options extends Required, > = Value extends NonRecursiveType ? Value : Value extends UnknownArray ? CamelCasedPropertiesArrayDeep : Value extends Set ? Set<_CamelCasedPropertiesDeep> : Value extends object ? { [K in keyof Value as CamelCase]: _CamelCasedPropertiesDeep; } : Value; // This is a copy of DelimiterCasedPropertiesArrayDeep (see: delimiter-cased-properties-deep.d.ts). // These types should be kept in sync. type CamelCasedPropertiesArrayDeep< Value extends UnknownArray, Options extends Required, > = Value extends [] ? [] // Trailing spread array : Value extends [infer U, ...infer V] ? [_CamelCasedPropertiesDeep, ..._CamelCasedPropertiesDeep] : Value extends readonly [infer U, ...infer V] ? readonly [_CamelCasedPropertiesDeep, ..._CamelCasedPropertiesDeep] : // Leading spread array Value extends readonly [...infer U, infer V] ? [..._CamelCasedPropertiesDeep, _CamelCasedPropertiesDeep] : // Array Value extends Array ? Array<_CamelCasedPropertiesDeep> : Value extends ReadonlyArray ? ReadonlyArray<_CamelCasedPropertiesDeep> : never; export {}; ================================================ FILE: source/camel-cased-properties.d.ts ================================================ import type {CamelCase, CamelCaseOptions, _DefaultCamelCaseOptions} from './camel-case.d.ts'; import type {ApplyDefaultOptions} from './internal/index.d.ts'; /** Convert object properties to camel case but not recursively. This can be useful when, for example, converting some API types from a different style. @see {@link CamelCasedPropertiesDeep} @see {@link CamelCase} @example ``` import type {CamelCasedProperties} from 'type-fest'; type User = { UserId: number; UserName: string; }; const result: CamelCasedProperties = { userId: 1, userName: 'Tom', }; const preserveConsecutiveUppercase: CamelCasedProperties<{fooBAR: string}, {preserveConsecutiveUppercase: true}> = { fooBAR: 'string', }; ``` @category Change case @category Template literal @category Object */ export type CamelCasedProperties = Value extends Function ? Value : Value extends Array ? Value : { [K in keyof Value as CamelCase> ]: Value[K]; }; export {}; ================================================ FILE: source/characters.d.ts ================================================ /** Matches any uppercase letter in the basic Latin alphabet (A-Z). @example ``` import type {UppercaseLetter} from 'type-fest'; const a: UppercaseLetter = 'A'; // Valid // @ts-expect-error const b: UppercaseLetter = 'a'; // Invalid // @ts-expect-error const c: UppercaseLetter = 'AB'; // Invalid ``` @category Type */ export type UppercaseLetter = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z'; /** Matches any lowercase letter in the basic Latin alphabet (a-z). @example ``` import type {LowercaseLetter} from 'type-fest'; const a: LowercaseLetter = 'a'; // Valid // @ts-expect-error const b: LowercaseLetter = 'A'; // Invalid ``` @category Type */ export type LowercaseLetter = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z'; /** Matches any digit as a string ('0'-'9'). @example ``` import type {DigitCharacter} from 'type-fest'; const a: DigitCharacter = '0'; // Valid // @ts-expect-error const b: DigitCharacter = 0; // Invalid ``` @category Type */ export type DigitCharacter = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'; /** Matches any lowercase letter (a-z), uppercase letter (A-Z), or digit ('0'-'9') in the basic Latin alphabet. @example ``` import type {Alphanumeric} from 'type-fest'; const a: Alphanumeric = 'A'; // Valid // @ts-expect-error const b: Alphanumeric = '#'; // Invalid ``` @category Type */ export type Alphanumeric = LowercaseLetter | UppercaseLetter | DigitCharacter; export {}; ================================================ FILE: source/conditional-except.d.ts ================================================ import type {Except} from './except.d.ts'; import type {ConditionalKeys} from './conditional-keys.d.ts'; /** Exclude keys from a shape that matches the given `Condition`. This is useful when you want to create a new type with a specific set of keys from a shape. For example, you might want to exclude all the primitive properties from a class and form a new shape containing everything but the primitive properties. @example ``` import type {Primitive, ConditionalExcept} from 'type-fest'; class Awesome { constructor(public name: string, public successes: number, public failures: bigint) {} run() { // do something } } type ExceptPrimitivesFromAwesome = ConditionalExcept; //=> {run: () => void} ``` @example ``` import type {ConditionalExcept} from 'type-fest'; type Example = { a: string; b: string | number; c: () => void; d: {}; }; type NonStringKeysOnly = ConditionalExcept; //=> {b: string | number; c: () => void; d: {}} ``` @category Object */ export type ConditionalExcept = Except< Base, ConditionalKeys >; export {}; ================================================ FILE: source/conditional-keys.d.ts ================================================ import type {ExtendsStrict} from './extends-strict.d.ts'; import type {IfNotAnyOrNever} from './internal/type.d.ts'; import type {TupleToObject} from './tuple-to-object.d.ts'; import type {UnknownArray} from './unknown-array.d.ts'; /** Extract the keys from a type where the value type of the key extends the given `Condition`. Internally this is used for the `ConditionalPick` and `ConditionalExcept` types. @example ``` import type {ConditionalKeys} from 'type-fest'; type Example = { a: string; b: string | number; c?: string; d: {}; }; type StringKeysOnly = ConditionalKeys; //=> 'a' ``` Note: To extract optional keys, make sure your `Condition` is a union of `undefined` (for example, `string | undefined`) as demonstrated below. @example ``` import type {ConditionalKeys} from 'type-fest'; type StringKeysAndUndefined = ConditionalKeys<{a?: string}, string | undefined>; //=> 'a' type NoMatchingKeys = ConditionalKeys<{a?: string}, string>; //=> never ``` You can also extract array indices whose value match the specified condition, as shown below: ``` import type {ConditionalKeys} from 'type-fest'; type StringValueIndices = ConditionalKeys<[string, number, string], string>; //=> '0' | '2' type NumberValueIndices = ConditionalKeys<[string, number?, string?], number | undefined>; //=> '1' ``` @category Object */ export type ConditionalKeys = (Base extends UnknownArray ? TupleToObject : Base) extends infer _Base // Remove non-numeric keys from arrays ? IfNotAnyOrNever<_Base, _ConditionalKeys<_Base, Condition>, keyof _Base> : never; type _ConditionalKeys = keyof { [ Key in (keyof Base & {}) as // `& {}` prevents homomorphism ExtendsStrict extends true ? Key : never ]: never }; export {}; ================================================ FILE: source/conditional-pick-deep.d.ts ================================================ import type {IsEqual} from './is-equal.d.ts'; import type {ConditionalExcept} from './conditional-except.d.ts'; import type {ConditionalSimplifyDeep} from './conditional-simplify-deep.d.ts'; import type {UnknownRecord} from './unknown-record.d.ts'; import type {EmptyObject} from './empty-object.d.ts'; import type {ApplyDefaultOptions, IsPlainObject} from './internal/index.d.ts'; /** Used to mark properties that should be excluded. */ declare const conditionalPickDeepSymbol: unique symbol; /** Assert the condition according to the {@link ConditionalPickDeepOptions.condition|condition} option. */ type AssertCondition = Options['condition'] extends 'equality' ? IsEqual : Type extends Condition ? true : false; /** ConditionalPickDeep options. @see {@link ConditionalPickDeep} */ export type ConditionalPickDeepOptions = { /** The condition assertion mode. @default 'extends' */ condition?: 'extends' | 'equality'; }; type DefaultConditionalPickDeepOptions = { condition: 'extends'; }; /** Pick keys recursively from the shape that matches the given condition. @see {@link ConditionalPick} @example ``` import type {ConditionalPickDeep} from 'type-fest'; type Example = { a: string; b: string | boolean; c: { d: string; e: { f?: string; g?: boolean; h: string | boolean; i: boolean | bigint; }; j: boolean; }; }; type StringPick = ConditionalPickDeep; //=> {a: string; c: {d: string}} type StringPickOptional = ConditionalPickDeep; //=> {a: string; c: {d: string; e: {f?: string}}} type StringPickOptionalOnly = ConditionalPickDeep; //=> {c: {e: {f?: string}}} type BooleanPick = ConditionalPickDeep; //=> {c: {e: {g?: boolean}; j: boolean}} type NumberPick = ConditionalPickDeep; //=> never type StringOrBooleanPick = ConditionalPickDeep; //=> { // a: string; // b: string | boolean; // c: { // d: string; // e: { // h: string | boolean; // }; // j: boolean; // }; // } type StringOrBooleanPickOnly = ConditionalPickDeep; //=> {b: string | boolean; c: {e: {h: string | boolean}}} ``` @category Object */ export type ConditionalPickDeep< Type, Condition, Options extends ConditionalPickDeepOptions = {}, > = _NeverIfEmpty<_ConditionalPickDeep< Type, Condition, ApplyDefaultOptions >>; type _NeverIfEmpty = Type extends EmptyObject ? never : Type; type _ConditionalPickDeep< Type, Condition, Options extends Required, > = ConditionalSimplifyDeep extends true ? Type[Key] : IsPlainObject extends true ? _ConditionalPickDeep : typeof conditionalPickDeepSymbol; }, (typeof conditionalPickDeepSymbol | undefined) | EmptyObject>, never, UnknownRecord>; export {}; ================================================ FILE: source/conditional-pick.d.ts ================================================ import type {ConditionalKeys} from './conditional-keys.d.ts'; import type {IsNever} from './is-never.d.ts'; /** Pick keys from the shape that matches the given `Condition`. This is useful when you want to create a new type from a specific subset of an existing type. For example, you might want to pick all the primitive properties from a class and form a new automatically derived type. @example ``` import type {Primitive, ConditionalPick} from 'type-fest'; class Awesome { constructor(public name: string, public successes: number, public failures: bigint) {} run() { // do something } } type PickPrimitivesFromAwesome = ConditionalPick; //=> {name: string; successes: number; failures: bigint} ``` @example ``` import type {ConditionalPick} from 'type-fest'; type Example = { a: string; b: string | number; c: () => void; d: {}; }; type StringKeysOnly = ConditionalPick; //=> {a: string} ``` @category Object */ export type ConditionalPick = ConditionalKeys extends infer Keys ? IsNever extends true ? never : Pick : never; export {}; ================================================ FILE: source/conditional-simplify-deep.d.ts ================================================ /** Recursively simplifies a type while including and/or excluding certain types from being simplified. @example ``` import type {ConditionalSimplifyDeep} from 'type-fest'; type TypeA = { foo: { a: string; }; }; type TypeB = { foo: { b: string; }; }; type SimplifyDeepTypeAB = ConditionalSimplifyDeep; //=> {foo: {a: string; b: string}} ``` @example ``` import type {ConditionalSimplifyDeep} from 'type-fest'; type SomeComplexType1 = { a1: string; b1: number; c1: boolean; }; type SomeComplexType2 = { a2: string; b2: number; c2: boolean; }; type TypeA = { foo: { a: string; complexType: SomeComplexType1; }; }; type TypeB = { foo: { b: string; complexType: SomeComplexType2; }; }; type SimplifyDeepTypeAB = ConditionalSimplifyDeep; //=> { // foo: { // a: string; // complexType: SomeComplexType1 & SomeComplexType2; // b: string; // }; // } ``` @see {@link SimplifyDeep} @category Object */ export type ConditionalSimplifyDeep = Type extends ExcludeType ? Type : Type extends IncludeType ? {[TypeKey in keyof Type]: ConditionalSimplifyDeep} : Type; export {}; ================================================ FILE: source/conditional-simplify.d.ts ================================================ /** Simplifies a type while including and/or excluding certain types from being simplified. Useful to improve type hints shown in editors. And also to transform an `interface` into a `type` to aid with assignability. @example ``` import type {ConditionalSimplify} from 'type-fest'; type TypeA = { a: string; }; type TypeB = { b: string; }; type TypeAB = TypeA & TypeB; //=> TypeA & TypeB type SimplifyTypeAB = ConditionalSimplify; //=> {a: string; b: string} ``` @example ``` import type {ConditionalSimplify} from 'type-fest'; type Simplify = ConditionalSimplify | Map | unknown[], object>; type A = Simplify & Set>; //=> Set & Set type B = Simplify & Map>; //=> Map & Map type C = Simplify<{a: number} & {b: string}>; //=> {a: number; b: string} ``` @see {@link ConditionalSimplifyDeep} @category Object */ export type ConditionalSimplify = Type extends ExcludeType ? Type : Type extends IncludeType ? {[TypeKey in keyof Type]: Type[TypeKey]} : Type; export {}; ================================================ FILE: source/delimiter-case.d.ts ================================================ import type {ApplyDefaultOptions, AsciiPunctuation, StartsWith} from './internal/index.d.ts'; import type {IsStringLiteral} from './is-literal.d.ts'; import type {Merge} from './merge.d.ts'; import type {RemovePrefix} from './remove-prefix.d.ts'; import type {_DefaultWordsOptions, Words, WordsOptions} from './words.d.ts'; export type _DefaultDelimiterCaseOptions = Merge<_DefaultWordsOptions, {splitOnNumbers: false}>; /** Convert an array of words to delimiter case starting with a delimiter with input capitalization. */ type DelimiterCaseFromArray< Words extends string[], Delimiter extends string, OutputString extends string = '', > = Words extends [ infer FirstWord extends string, ...infer RemainingWords extends string[], ] ? DelimiterCaseFromArray extends true ? '' : Delimiter }${FirstWord}`> : OutputString; /** Convert a string literal to a custom string delimiter casing. This can be useful when, for example, converting a camel-cased object property to an oddly cased one. @see {@link KebabCase} @see {@link SnakeCase} @example ``` import type {DelimiterCase} from 'type-fest'; // Simple const someVariable: DelimiterCase<'fooBar', '#'> = 'foo#bar'; const someVariableNoSplitOnNumbers: DelimiterCase<'p2pNetwork', '#', {splitOnNumbers: false}> = 'p2p#network'; // Advanced type OddlyCasedProperties = { [K in keyof T as DelimiterCase]: T[K] }; type SomeOptions = { dryRun: boolean; includeFile: string; foo: number; }; const rawCliOptions: OddlyCasedProperties = { 'dry#run': true, 'include#file': 'bar.js', foo: 123, }; ``` @category Change case @category Template literal */ export type DelimiterCase< Value, Delimiter extends string, Options extends WordsOptions = {}, > = Value extends string ? IsStringLiteral extends false ? Value : Lowercase>, Delimiter >, string, {strict: false}>> : Value; export {}; ================================================ FILE: source/delimiter-cased-properties-deep.d.ts ================================================ import type {_DefaultDelimiterCaseOptions, DelimiterCase} from './delimiter-case.d.ts'; import type {ApplyDefaultOptions, NonRecursiveType} from './internal/index.d.ts'; import type {UnknownArray} from './unknown-array.d.ts'; import type {WordsOptions} from './words.d.ts'; /** Convert object properties to delimiter case recursively. This can be useful when, for example, converting some API types from a different style. @see {@link DelimiterCase} @see {@link DelimiterCasedProperties} @example ``` import type {DelimiterCasedPropertiesDeep} from 'type-fest'; type User = { userId: number; userName: string; }; type UserWithFriends = { userInfo: User; userFriends: User[]; }; const result: DelimiterCasedPropertiesDeep = { 'user-info': { 'user-id': 1, 'user-name': 'Tom', }, 'user-friends': [ { 'user-id': 2, 'user-name': 'Jerry', }, { 'user-id': 3, 'user-name': 'Spike', }, ], }; const splitOnNumbers: DelimiterCasedPropertiesDeep<{line1: {line2: [{line3: string}]}}, '-', {splitOnNumbers: true}> = { 'line-1': { 'line-2': [ { 'line-3': 'string', }, ], }, }; ``` @category Change case @category Template literal @category Object */ export type DelimiterCasedPropertiesDeep< Value, Delimiter extends string, Options extends WordsOptions = {}, > = _DelimiterCasedPropertiesDeep>; type _DelimiterCasedPropertiesDeep< Value, Delimiter extends string, Options extends Required, > = Value extends NonRecursiveType ? Value : Value extends UnknownArray ? DelimiterCasedPropertiesArrayDeep : Value extends Set ? Set<_DelimiterCasedPropertiesDeep> : Value extends object ? { [K in keyof Value as DelimiterCase]: _DelimiterCasedPropertiesDeep } : Value; // This is a copy of CamelCasedPropertiesArrayDeep (see: camel-cased-properties-deep.d.ts). // These types should be kept in sync. type DelimiterCasedPropertiesArrayDeep< Value extends UnknownArray, Delimiter extends string, Options extends Required, > = Value extends [] ? [] // Trailing spread array : Value extends [infer U, ...infer V] ? [_DelimiterCasedPropertiesDeep, ..._DelimiterCasedPropertiesDeep] : Value extends readonly [infer U, ...infer V] ? readonly [_DelimiterCasedPropertiesDeep, ..._DelimiterCasedPropertiesDeep] // Leading spread array : Value extends [...infer U, infer V] ? [..._DelimiterCasedPropertiesDeep, _DelimiterCasedPropertiesDeep] : Value extends readonly [...infer U, infer V] ? readonly [..._DelimiterCasedPropertiesDeep, _DelimiterCasedPropertiesDeep] // Array : Value extends Array ? Array<_DelimiterCasedPropertiesDeep> : Value extends ReadonlyArray ? ReadonlyArray<_DelimiterCasedPropertiesDeep> : never; export {}; ================================================ FILE: source/delimiter-cased-properties.d.ts ================================================ import type {_DefaultDelimiterCaseOptions, DelimiterCase} from './delimiter-case.d.ts'; import type {ApplyDefaultOptions} from './internal/index.d.ts'; import type {WordsOptions} from './words.d.ts'; /** Convert object properties to delimiter case but not recursively. This can be useful when, for example, converting some API types from a different style. @see {@link DelimiterCase} @see {@link DelimiterCasedPropertiesDeep} @example ``` import type {DelimiterCasedProperties} from 'type-fest'; type User = { userId: number; userName: string; }; const result: DelimiterCasedProperties = { 'user-id': 1, 'user-name': 'Tom', }; const splitOnNumbers: DelimiterCasedProperties<{line1: string}, '-', {splitOnNumbers: true}> = { 'line-1': 'string', }; ``` @category Change case @category Template literal @category Object */ export type DelimiterCasedProperties< Value, Delimiter extends string, Options extends WordsOptions = {}, > = Value extends Function ? Value : Value extends Array ? Value : {[K in keyof Value as DelimiterCase> ]: Value[K]}; export {}; ================================================ FILE: source/distributed-omit.d.ts ================================================ import type {KeysOfUnion} from './keys-of-union.d.ts'; /** Omits keys from a type, distributing the operation over a union. TypeScript's `Omit` doesn't distribute over unions, leading to the erasure of unique properties from union members when omitting keys. This creates a type that only retains properties common to all union members, making it impossible to access member-specific properties after the Omit. Essentially, using `Omit` on a union type merges the types into a less specific one, hindering type narrowing and property access based on discriminants. This type solves that. Example: ``` type A = { discriminant: 'A'; foo: string; a: number; }; type B = { discriminant: 'B'; foo: string; b: string; }; type Union = A | B; type OmittedUnion = Omit; //=> {discriminant: 'A' | 'B'} declare const omittedUnion: OmittedUnion; if (omittedUnion.discriminant === 'A') { // We would like to narrow `omittedUnion`'s type // to `A` here, but we can't because `Omit` // doesn't distribute over unions. // @ts-expect-error const aValue = omittedUnion.a; // Error: `a` is not a property of `{discriminant: 'A' | 'B'}` } ``` While `Except` solves this problem, it restricts the keys you can omit to the ones that are present in **ALL** union members, where `DistributedOmit` allows you to omit keys that are present in **ANY** union member. @example ``` import type {DistributedOmit} from 'type-fest'; type A = { discriminant: 'A'; foo: string; a: number; }; type B = { discriminant: 'B'; foo: string; bar: string; b: string; }; type C = { discriminant: 'C'; bar: string; c: boolean; }; // Notice that `foo` exists in `A` and `B`, but not in `C`, and // `bar` exists in `B` and `C`, but not in `A`. type Union = A | B | C; type OmittedUnion = DistributedOmit; declare const omittedUnion: OmittedUnion; if (omittedUnion.discriminant === 'A') { const aValue = omittedUnion.a; // OK // @ts-expect-error const fooValue = omittedUnion.foo; // Error: `foo` is not a property of `{discriminant: 'A'; a: string}` // @ts-expect-error const barValue = omittedUnion.bar; // Error: `bar` is not a property of `{discriminant: 'A'; a: string}` } ``` @category Object */ export type DistributedOmit> = ObjectType extends unknown ? Omit : never; export {}; ================================================ FILE: source/distributed-pick.d.ts ================================================ import type {KeysOfUnion} from './keys-of-union.d.ts'; /** Pick keys from a type, distributing the operation over a union. TypeScript's `Pick` doesn't distribute over unions, leading to the erasure of unique properties from union members when picking keys. This creates a type that only retains properties common to all union members, making it impossible to access member-specific properties after the Pick. Essentially, using `Pick` on a union type merges the types into a less specific one, hindering type narrowing and property access based on discriminants. This type solves that. Example: ``` type A = { discriminant: 'A'; foo: { bar: string; }; }; type B = { discriminant: 'B'; foo: { baz: string; }; }; type Union = A | B; type PickedUnion = Pick; //=> {discriminant: 'A' | 'B'; foo: {bar: string} | {baz: string}} declare const pickedUnion: PickedUnion; if (pickedUnion.discriminant === 'A') { // We would like to narrow `pickedUnion`'s type // to `A` here, but we can't because `Pick` // doesn't distribute over unions. // @ts-expect-error const barValue = pickedUnion.foo.bar; // Error: Property 'bar' does not exist on type '{bar: string} | {baz: string}'. } ``` @example ``` import type {DistributedPick} from 'type-fest'; type A = { discriminant: 'A'; foo: { bar: string; }; extraneous: boolean; }; type B = { discriminant: 'B'; foo: { baz: string; }; extraneous: boolean; }; // Notice that `foo.bar` exists in `A` but not in `B`. type Union = A | B; type PickedUnion = DistributedPick; declare const pickedUnion: PickedUnion; if (pickedUnion.discriminant === 'A') { const barValue = pickedUnion.foo.bar; // OK // @ts-expect-error const extraneousValue = pickedUnion.extraneous; // Error: Property `extraneous` does not exist on type `Pick`. // @ts-expect-error const bazValue = pickedUnion.foo.baz; // Error: `bar` is not a property of `{discriminant: 'A'; a: string}`. } ``` @category Object */ export type DistributedPick> = ObjectType extends unknown ? Pick> : never; export {}; ================================================ FILE: source/empty-object.d.ts ================================================ declare const emptyObjectSymbol: unique symbol; /** Represents a strictly empty plain object, the `{}` value. When you annotate something as the type `{}`, it can be anything except `null` and `undefined`. This means that you cannot use `{}` to represent an empty plain object ([read more](https://stackoverflow.com/questions/47339869/typescript-empty-object-and-any-difference/52193484#52193484)). @example ``` import type {EmptyObject} from 'type-fest'; // The following illustrates the problem with `{}`. const foo1: {} = {}; // Pass const foo2: {} = []; // Pass const foo3: {} = 42; // Pass const foo4: {} = {a: 1}; // Pass // With `EmptyObject` only the first case is valid. const bar1: EmptyObject = {}; // Pass // @ts-expect-error const bar2: EmptyObject = []; // Fail // @ts-expect-error const bar3: EmptyObject = 42; // Fail // @ts-expect-error const bar4: EmptyObject = {a: 1}; // Fail ``` Unfortunately, `Record`, `Record` and `Record` do not work. See {@link https://github.com/sindresorhus/type-fest/issues/395 #395}. @category Object */ export type EmptyObject = {[emptyObjectSymbol]?: never}; /** Returns a `boolean` for whether the type is strictly equal to an empty plain object, the `{}` value. @example ``` import type {IsEmptyObject} from 'type-fest'; type Pass = IsEmptyObject<{}>; //=> true type Fail1 = IsEmptyObject<[]>; //=> false type Fail2 = IsEmptyObject; //=> false ``` @see {@link EmptyObject} @category Object */ export type IsEmptyObject = T extends EmptyObject ? true : false; export {}; ================================================ FILE: source/entries.d.ts ================================================ import type {_ArrayEntry, _MapEntry, _ObjectEntry, _SetEntry} from './entry.d.ts'; type ArrayEntries = Array<_ArrayEntry>; type MapEntries = Array<_MapEntry>; type ObjectEntries = Array<_ObjectEntry>; type SetEntries> = Array<_SetEntry>; /** Many collections have an `entries` method which returns an array of a given object's own enumerable string-keyed property [key, value] pairs. The `Entries` type will return the type of that collection's entries. For example the {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries|`Object`}, {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries|`Map`}, {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/entries|`Array`}, and {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries|`Set`} collections all have this method. Note that `WeakMap` and `WeakSet` do not have this method since their entries are not enumerable. @see `Entry` if you want to just access the type of a single entry. @example ``` import type {Entries} from 'type-fest'; type Example = { someKey: number; }; const manipulatesEntries = (examples: Entries) => examples.map(example => [ // Does some arbitrary processing on the key (with type information available) example[0].toUpperCase(), // Does some arbitrary processing on the value (with type information available) example[1].toFixed(0), ]); const example: Example = {someKey: 1}; const entries = Object.entries(example) as Entries; const output = manipulatesEntries(entries); // Objects const objectExample = {a: 1}; const objectEntries: Entries = [['a', 1]]; // Arrays const arrayExample = ['a', 1]; const arrayEntries: Entries = [[0, 'a'], [1, 1]]; // Maps const mapExample = new Map([['a', 1]]); const mapEntries: Entries = [['a', 1]]; // Sets const setExample = new Set(['a', 1]); const setEntries: Entries = [['a', 'a'], [1, 1]]; ``` @category Object @category Map @category Set @category Array */ export type Entries = BaseType extends Map ? MapEntries : BaseType extends Set ? SetEntries : BaseType extends readonly unknown[] ? ArrayEntries : BaseType extends object ? ObjectEntries : never; export {}; ================================================ FILE: source/entry.d.ts ================================================ type MapKey = BaseType extends Map ? KeyType : never; type MapValue = BaseType extends Map ? ValueType : never; export type _ArrayEntry = [number, BaseType[number]]; export type _MapEntry = [MapKey, MapValue]; export type _ObjectEntry = [keyof BaseType, BaseType[keyof BaseType]]; export type _SetEntry = BaseType extends Set ? [ItemType, ItemType] : never; /** Many collections have an `entries` method which returns an array of a given object's own enumerable string-keyed property [key, value] pairs. The `Entry` type will return the type of that collection's entry. For example the {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries|`Object`}, {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries|`Map`}, {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/entries|`Array`}, and {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries|`Set`} collections all have this method. Note that `WeakMap` and `WeakSet` do not have this method since their entries are not enumerable. @see `Entries` if you want to just access the type of the array of entries (which is the return of the `.entries()` method). @example ``` import type {Entry} from 'type-fest'; type Example = { someKey: number; }; const manipulatesEntry = (example: Entry) => [ // Does some arbitrary processing on the key (with type information available) example[0].toUpperCase(), // Does some arbitrary processing on the value (with type information available) example[1].toFixed(0), ]; const example: Example = {someKey: 1}; const entry = Object.entries(example)[0] as Entry; const output = manipulatesEntry(entry); // Objects const objectExample = {a: 1}; const objectEntry: Entry = ['a', 1]; // Arrays const arrayExample = ['a', 1]; const arrayEntryString: Entry = [0, 'a']; const arrayEntryNumber: Entry = [1, 1]; // Maps const mapExample = new Map([['a', 1]]); const mapEntry: Entry = ['a', 1]; // Sets const setExample = new Set(['a', 1]); const setEntryString: Entry = ['a', 'a']; const setEntryNumber: Entry = [1, 1]; ``` @category Object @category Map @category Array @category Set */ export type Entry = BaseType extends Map ? _MapEntry : BaseType extends Set ? _SetEntry : BaseType extends readonly unknown[] ? _ArrayEntry : BaseType extends object ? _ObjectEntry : never; export {}; ================================================ FILE: source/exact.d.ts ================================================ import type {ObjectValue} from './internal/index.d.ts'; import type {ArrayElement} from './array-element.d.ts'; import type {IsEqual} from './is-equal.d.ts'; import type {KeysOfUnion} from './keys-of-union.d.ts'; import type {IsUnknown} from './is-unknown.d.ts'; import type {Primitive} from './primitive.d.ts'; /** Create a type from `ParameterType` and `InputType` and change keys exclusive to `InputType` to `never`. - Generate a list of keys that exists in `InputType` but not in `ParameterType`. - Mark these excess keys as `never`. */ type ExactObject = {[Key in keyof ParameterType]: Exact>} & Record>, never>; /** Create a type that does not allow extra properties, meaning it only allows properties that are explicitly declared. This is useful for function type-guarding to reject arguments with excess properties. Due to the nature of TypeScript, it does not complain if excess properties are provided unless the provided value is an object literal. *Please upvote [this issue](https://github.com/microsoft/TypeScript/issues/12936) if you want to have this type as a built-in in TypeScript.* @example ``` type OnlyAcceptName = {name: string}; declare function onlyAcceptName(arguments_: OnlyAcceptName): void; // TypeScript complains about excess properties when an object literal is provided. // @ts-expect-error onlyAcceptName({name: 'name', id: 1}); // `id` is excess // TypeScript does not complain about excess properties when the provided value is a variable (not an object literal). const invalidInput = {name: 'name', id: 1}; onlyAcceptName(invalidInput); // No errors ``` Having `Exact` allows TypeScript to reject excess properties. @example ``` import type {Exact} from 'type-fest'; type OnlyAcceptName = {name: string}; declare function onlyAcceptNameImproved>(arguments_: T): void; const invalidInput = {name: 'name', id: 1}; // @ts-expect-error onlyAcceptNameImproved(invalidInput); // Compilation error ``` [Read more](https://stackoverflow.com/questions/49580725/is-it-possible-to-restrict-typescript-object-to-contain-only-properties-defined) @category Utilities */ export type Exact = // Before distributing, check if the two types are equal and if so, return the parameter type immediately IsEqual extends true ? ParameterType // If the parameter is a primitive, return it as is immediately to avoid it being converted to a complex type : ParameterType extends Primitive ? ParameterType // If the parameter is an unknown, return it as is immediately to avoid it being converted to a complex type : IsUnknown extends true ? unknown // If the parameter is a Function, return it as is because this type is not capable of handling function, leave it to TypeScript : ParameterType extends Function ? ParameterType // Convert union of array to array of union: A[] & B[] => (A & B)[] : ParameterType extends unknown[] ? Array, ArrayElement>> // In TypeScript, Array is a subtype of ReadonlyArray, so always test Array before ReadonlyArray. : ParameterType extends readonly unknown[] ? ReadonlyArray, ArrayElement>> : ExactObject; export {}; ================================================ FILE: source/except.d.ts ================================================ import type {ApplyDefaultOptions} from './internal/index.d.ts'; import type {IsEqual} from './is-equal.d.ts'; /** Filter out keys from an object. Returns `never` if `Exclude` is strictly equal to `Key`. Returns `never` if `Key` extends `Exclude`. Returns `Key` otherwise. @example ``` type Filtered = Filter<'foo', 'foo'>; //=> never ``` @example ``` type Filtered = Filter<'bar', string>; //=> never ``` @example ``` type Filtered = Filter<'bar', 'foo'>; //=> 'bar' ``` @see {Except} */ type Filter = IsEqual extends true ? never : (KeyType extends ExcludeType ? never : KeyType); export type ExceptOptions = { /** Disallow assigning non-specified properties. Note that any omitted properties in the resulting type will be present in autocomplete as `undefined`. @default false */ requireExactProps?: boolean; }; type DefaultExceptOptions = { requireExactProps: false; }; /** Create a type from an object type without certain keys. We recommend setting the `requireExactProps` option to `true`. This type is a stricter version of [`Omit`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-5.html#the-omit-helper-type). The `Omit` type does not restrict the omitted keys to be keys present on the given type, while `Except` does. The benefits of a stricter type are avoiding typos and allowing the compiler to pick up on rename refactors automatically. This type was proposed to the TypeScript team, which declined it, saying they prefer that libraries implement stricter versions of the built-in types ([microsoft/TypeScript#30825](https://github.com/microsoft/TypeScript/issues/30825#issuecomment-523668235)). @example ``` import type {Except} from 'type-fest'; type Foo = { a: number; b: string; }; type FooWithoutA = Except; //=> {b: string} // @ts-expect-error const fooWithoutA: FooWithoutA = {a: 1, b: '2'}; // errors: 'a' does not exist in type '{ b: string; }' type FooWithoutB = Except; //=> {a: number} & Partial> // @ts-expect-error const fooWithoutB: FooWithoutB = {a: 1, b: '2'}; // errors at 'b': Type 'string' is not assignable to type 'undefined'. // The `Omit` utility type doesn't work when omitting specific keys from objects containing index signatures. // Consider the following example: type UserData = { [metadata: string]: string; email: string; name: string; role: 'admin' | 'user'; }; // `Omit` clearly doesn't behave as expected in this case: type PostPayload = Omit; //=> {[x: string]: string; [x: number]: string} // In situations like this, `Except` works better. // It simply removes the `email` key while preserving all the other keys. type PostPayloadFixed = Except; //=> {[x: string]: string; name: string; role: 'admin' | 'user'} ``` @category Object */ export type Except = _Except>; type _Except> = { [KeyType in keyof ObjectType as Filter]: ObjectType[KeyType]; } & (Options['requireExactProps'] extends true ? Partial> : {}); export {}; ================================================ FILE: source/exclude-exactly.d.ts ================================================ import type {IsNever} from './is-never.d.ts'; import type {IsAny} from './is-any.d.ts'; import type {If} from './if.d.ts'; import type {IsEqual} from './is-equal.d.ts'; import type {IfNotAnyOrNever} from './internal/type.d.ts'; /** A stricter version of `Exclude` that excludes types only when they are exactly identical. @example ``` import type {ExcludeExactly} from 'type-fest'; type TestExclude1 = Exclude<'a' | 'b' | 'c' | 1 | 2 | 3, string>; //=> 1 | 2 | 3 type TestExcludeExactly1 = ExcludeExactly<'a' | 'b' | 'c' | 1 | 2 | 3, string>; //=> 'a' | 'b' | 'c' | 1 | 2 | 3 type TestExclude2 = Exclude<'a' | 'b' | 'c' | 1 | 2 | 3, any>; //=> never type TestExcludeExactly2 = ExcludeExactly<'a' | 'b' | 'c' | 1 | 2 | 3, any>; //=> 'a' | 'b' | 'c' | 1 | 2 | 3 type TestExclude3 = Exclude<{a: string} | {a: string; b: string}, {a: string}>; //=> never type TestExcludeExactly3 = ExcludeExactly<{a: string} | {a: string; b: string}, {a: string}>; //=> {a: string; b: string} ``` @category Improved Built-in */ export type ExcludeExactly = IfNotAnyOrNever< Union, _ExcludeExactly, // If `Union` is `any`, then if `Delete` is `any`, return `never`, else return `Union`. If, never, Union>, // If `Union` is `never`, then if `Delete` is `never`, return `never`, else return `Union`. If, never, Union> >; type _ExcludeExactly = IfNotAnyOrNever, true, never> : never] extends [never] ? Union : never : never, // If `Delete` is `any` or `never`, then return `Union`, // because `Union` cannot be `any` or `never` here. Union, Union >; export {}; ================================================ FILE: source/exclude-rest-element.d.ts ================================================ import type {SplitOnRestElement} from './split-on-rest-element.d.ts'; import type {IsArrayReadonly} from './internal/array.d.ts'; import type {UnknownArray} from './unknown-array.d.ts'; import type {IfNotAnyOrNever} from './internal/type.d.ts'; /** Create a tuple with the [`rest`](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) element removed. @example ``` import type {ExcludeRestElement} from 'type-fest'; type T1 = ExcludeRestElement<[number, ...string[], string, 'foo']>; //=> [number, string, 'foo'] type T2 = ExcludeRestElement<[...boolean[], string]>; //=> [string] type T3 = ExcludeRestElement<[...Array<'foo'>, true]>; //=> [true] type T4 = ExcludeRestElement<[number, string]>; //=> [number, string] ``` @see {@link ExtractRestElement} @see {@link SplitOnRestElement} @category Array */ export type ExcludeRestElement = IfNotAnyOrNever extends infer Result ? Result extends readonly UnknownArray[] ? IsArrayReadonly extends true ? Readonly<[...Result[0], ...Result[2]]> : [...Result[0], ...Result[2]] : never : never >; export {}; ================================================ FILE: source/exclude-strict.d.ts ================================================ /** A stricter version of {@link Exclude} that ensures every member of `U` can successfully exclude something from `T`. For example, `ExcludeStrict` will error because `bigint` cannot exclude anything from `string | number | boolean`. @example ``` // Valid Examples import type {ExcludeStrict} from 'type-fest'; type Example1 = ExcludeStrict<{status: 'success'; data: string[]} | {status: 'error'; error: string}, {status: 'success'}>; //=> {status: 'error'; error: string} type Example2 = ExcludeStrict<'xs' | 's' | 'm' | 'l' | 'xl', 'xs' | 's'>; //=> 'm' | 'l' | 'xl' type Example3 = ExcludeStrict<{x: number; y: number} | [number, number], unknown[]>; //=> {x: number; y: number} ``` @example ``` // Invalid Examples import type {ExcludeStrict} from 'type-fest'; // `'xxl'` cannot exclude anything from `'xs' | 's' | 'm' | 'l' | 'xl'` // @ts-expect-error type Example1 = ExcludeStrict<'xs' | 's' | 'm' | 'l' | 'xl', 'xl' | 'xxl'>; // ~~~~~~~~~~~~ // Error: Type "'xl' | 'xxl'" does not satisfy the constraint 'never'. // `unknown[]` cannot exclude anything from `{x: number; y: number} | {x: string; y: string}` // @ts-expect-error type Example2 = ExcludeStrict<{x: number; y: number} | {x: string; y: string}, unknown[]>; // ~~~~~~~~~ // Error: Type 'unknown[]' does not satisfy the constraint 'never'. ``` @category Improved Built-in */ export type ExcludeStrict< T, U extends [U] extends [ // Ensure every member of `U` excludes something from `T` U extends unknown ? ([T] extends [Exclude] ? never : U) : never, ] ? unknown : never, > = Exclude; export {}; ================================================ FILE: source/exclusify-union.d.ts ================================================ import type {If} from './if.d.ts'; import type {IfNotAnyOrNever, MapsSetsOrArrays, NonRecursiveType} from './internal/type.d.ts'; import type {IsUnknown} from './is-unknown.d.ts'; import type {KeysOfUnion} from './keys-of-union.d.ts'; import type {Simplify} from './simplify.d.ts'; /** Ensure mutual exclusivity in object unions by adding other members’ keys as `?: never`. Use-cases: - You want each union member to be exclusive, preventing overlapping object shapes. - You want to safely access any property defined across the union without additional type guards. @example ``` import type {ExclusifyUnion} from 'type-fest'; type FileConfig = { filePath: string; }; type InlineConfig = { content: string; }; declare function loadConfig1(options: FileConfig | InlineConfig): void; // Someone could mistakenly provide both `filePath` and `content`. loadConfig1({filePath: './config.json', content: '{ "name": "app" }'}); // No errors // Use `ExclusifyUnion` to prevent that mistake. type Config = ExclusifyUnion; //=> { // filePath: string; // content?: never; // } | { // content: string; // filePath?: never; // } declare function loadConfig2(options: Config): void; // @ts-expect-error loadConfig2({filePath: './config.json', content: '{ "name": "app" }'}); // Error: Argument of type '{ filePath: string; content: string; }' is not assignable to parameter of type '{ filePath: string; content?: never; } | { content: string; filePath?: never; }'. loadConfig2({filePath: './config.json'}); // Ok loadConfig2({content: '{ "name": "app" }'}); // Ok ``` @example ``` import type {ExclusifyUnion} from 'type-fest'; type CardPayment = { amount: number; cardNumber: string; }; type PaypalPayment = { amount: number; paypalId: string; }; function processPayment1(payment: CardPayment | PaypalPayment) { // @ts-expect-error const details = payment.cardNumber ?? payment.paypalId; // Cannot access `cardNumber` or `paypalId` directly } type Payment = ExclusifyUnion; //=> { // amount: number; // cardNumber: string; // paypalId?: never; // } | { // amount: number; // paypalId: string; // cardNumber?: never; // } function processPayment2(payment: Payment) { const details = payment.cardNumber ?? payment.paypalId; // Ok //=> string } ``` @example ``` import type {ExclusifyUnion} from 'type-fest'; type A = ExclusifyUnion<{a: string} | {b: number}>; //=> {a: string; b?: never} | {b: number; a?: never} type B = ExclusifyUnion<{a: string} | {b: number} | {c: boolean}>; //=> { // a: string; // b?: never; // c?: never; // } | { // b: number; // a?: never; // c?: never; // } | { // c: boolean; // a?: never; // b?: never; // } type C = ExclusifyUnion<{a: string; b: number} | {b: string; c: number}>; //=> { // a: string; // b: number; // c?: never; // } | { // b: string; // c: number; // a?: never; // } type D = ExclusifyUnion<{a?: 1; readonly b: 2} | {d: 4}>; //=> {a?: 1; readonly b: 2; d?: never} | {d: 4; a?: never; b?: never} ``` @category Object @category Union */ export type ExclusifyUnion = IfNotAnyOrNever, Union, Extract extends infer SkippedMembers ? SkippedMembers | _ExclusifyUnion> : never > >; type _ExclusifyUnion = Union extends unknown // For distributing `Union` ? Simplify< Union & Partial< Record< Exclude, keyof Union>, never > > > : never; // Should never happen export {}; ================================================ FILE: source/extends-strict.d.ts ================================================ import type {IsNever} from './is-never.d.ts'; import type {IsAny} from './is-any.d.ts'; /** A stricter, non-distributive version of `extends` for checking whether one type is assignable to another. Unlike the built-in `extends` keyword, `ExtendsStrict`: 1. Prevents distribution over union types by wrapping both types in tuples. For example, `ExtendsStrict` returns `false`, whereas `string | number extends number` would result in `boolean`. 2. Treats `never` as a special case: `never` doesn't extend every other type, it only extends itself (or `any`). For example, `ExtendsStrict` returns `false` whereas `never extends number` would result in `true`. @example ``` import type {ExtendsStrict} from 'type-fest'; type T1 = ExtendsStrict; //=> false type T2 = ExtendsStrict; //=> false type T3 = ExtendsStrict; //=> true type T4 = ExtendsStrict; //=> true type T5 = ExtendsStrict; //=> true ``` @category Improved Built-in */ export type ExtendsStrict = IsAny extends true ? true : IsNever extends true ? IsNever : [Left] extends [Right] ? true : false; export {}; ================================================ FILE: source/extract-rest-element.d.ts ================================================ import type {SplitOnRestElement} from './split-on-rest-element.d.ts'; import type {UnknownArray} from './unknown-array.d.ts'; /** Extract the [`rest`](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) element type from an array. @example ``` import type {ExtractRestElement} from 'type-fest'; type T1 = ExtractRestElement<[number, ...string[], string, 'foo']>; //=> string type T2 = ExtractRestElement<[...boolean[], string]>; //=> boolean type T3 = ExtractRestElement<[...Array<'foo'>, true]>; //=> 'foo' type T4 = ExtractRestElement<[number, string]>; //=> never ``` @see {@link ExcludeRestElement} @see {@link SplitOnRestElement} @category Array */ export type ExtractRestElement = SplitOnRestElement[1][number]; export {}; ================================================ FILE: source/extract-strict.d.ts ================================================ /** A stricter version of {@link Extract} that ensures every member of `U` can successfully extract something from `T`. For example, `ExtractStrict` will error because `bigint` cannot extract anything from `string | number | boolean`. @example ``` // Valid Examples import type {ExtractStrict} from 'type-fest'; type Example1 = ExtractStrict<{status: 'success'; data: string[]} | {status: 'error'; error: string}, {status: 'success'}>; //=> {status: 'success'; data: string[]} type Example2 = ExtractStrict<'xs' | 's' | 'm' | 'l' | 'xl', 'xs' | 's'>; //=> 'xs' | 's' type Example3 = ExtractStrict<{x: number; y: number} | [number, number], unknown[]>; //=> [number, number] ``` @example ``` // Invalid Examples import type {ExtractStrict} from 'type-fest'; // `'xxl'` cannot extract anything from `'xs' | 's' | 'm' | 'l' | 'xl'` // @ts-expect-error type Example1 = ExtractStrict<'xs' | 's' | 'm' | 'l' | 'xl', 'xl' | 'xxl'>; // ~~~~~~~~~~~~ // Error: Type "'xl' | 'xxl'" does not satisfy the constraint 'never'. // `unknown[]` cannot extract anything from `{x: number; y: number} | {x: string; y: string}` // @ts-expect-error type Example2 = ExtractStrict<{x: number; y: number} | {x: string; y: string}, unknown[]>; // ~~~~~~~~~ // Error: Type 'unknown[]' does not satisfy the constraint 'never'. ``` @category Improved Built-in */ export type ExtractStrict< T, U extends [U] extends [ // Ensure every member of `U` extracts something from `T` U extends unknown ? (Extract extends never ? never : U) : never, ] ? unknown : never, > = Extract; export {}; ================================================ FILE: source/find-global-type.d.ts ================================================ /** Tries to find the type of a global with the given name. Limitations: Due to peculiarities with the behavior of `globalThis`, "globally defined" only includes `var` declarations in `declare global` blocks, not `let` or `const` declarations. @example ``` import type {FindGlobalType} from 'type-fest'; declare global { const foo: number; // let and const don't work var bar: string; // var works } type FooType = FindGlobalType<'foo'>; //=> never (let/const don't work) type BarType = FindGlobalType<'bar'>; //=> string type OtherType = FindGlobalType<'other'>; //=> never (no global named 'other') ``` @category Utilities */ export type FindGlobalType = typeof globalThis extends Record ? T : never; /** Tries to find one or more types from their globally-defined constructors. Use-case: Conditionally referencing DOM types only when the DOM library present. *Limitations:* Due to peculiarities with the behavior of `globalThis`, "globally defined" has a narrow definition in this case. Declaring a class in a `declare global` block won't work, instead you must declare its type using an interface and declare its constructor as a `var` (*not* `let`/`const`) inside the `declare global` block. @example ``` import type {FindGlobalInstanceType} from 'type-fest'; class Point { constructor(public x: number, public y: number) {} } type PointLike = Point | FindGlobalInstanceType<'DOMPoint'>; ``` @example ``` import type {FindGlobalInstanceType} from 'type-fest'; declare global { // Class syntax won't add the key to `globalThis` class Foo {} // interface + constructor style works interface Bar { bar: string; } var Bar: new () => Bar; // Not let or const } type FindFoo = FindGlobalInstanceType<'Foo'>; // Doesn't work type FindBar = FindGlobalInstanceType<'Bar'>; // Works ``` @category Utilities */ export type FindGlobalInstanceType = Name extends string ? typeof globalThis extends Record infer T> ? T : never : never; export {}; ================================================ FILE: source/fixed-length-array.d.ts ================================================ import type {Except} from './except.d.ts'; import type {TupleOf} from './tuple-of.d.ts'; /** Methods to exclude. */ type ArrayLengthMutationKeys = 'splice' | 'push' | 'pop' | 'shift' | 'unshift'; /** Create a type that represents an array of the given type and length. The `Array` prototype methods that manipulate its length are excluded from the resulting type. The problem with the built-in tuple type is that it allows mutating methods like `push`, `pop` etc, which can cause issues, like in the following example: @example ``` const color: [number, number, number] = [255, 128, 64]; function toHex([r, g, b]: readonly [number, number, number]) { return `#${r.toString(16)}${g.toString(16)}${b.toString(16)}`; } color.pop(); // Allowed console.log(toHex(color)); // Compiles fine, but fails at runtime since index `2` no longer contains a `number`. ``` `ArrayLengthMutationKeys` solves this problem by excluding methods like `push`, `pop` etc from the resulting type. @example ``` import type {FixedLengthArray} from 'type-fest'; const color: FixedLengthArray = [255, 128, 64]; // @ts-expect-error color.pop(); // Error: Property 'pop' does not exist on type 'FixedLengthArray'. ``` Use-cases: - Declaring fixed-length tuples or arrays with a large number of items. - Creating an array of coordinates with a static length, for example, length of 3 for a 3D vector. @example ``` import type {FixedLengthArray} from 'type-fest'; let color: FixedLengthArray = [255, 128, 64]; const red = color[0]; //=> number const green = color[1]; //=> number const blue = color[2]; //=> number // @ts-expect-error const alpha = color[3]; // Error: Property '3' does not exist on type 'FixedLengthArray'. // You can write to valid indices. color[0] = 128; color[1] = 64; color[2] = 32; // But you cannot write to out-of-bounds indices. // @ts-expect-error color[3] = 0.5; // Error: Property '3' does not exist on type 'FixedLengthArray'. // @ts-expect-error color.push(0.5); // Error: Property 'push' does not exist on type 'FixedLengthArray'. // @ts-expect-error color = [0, 128, 255, 0.5]; // Error: Type '[number, number, number, number]' is not assignable to type 'FixedLengthArray'. Types of property 'length' are incompatible. // @ts-expect-error color.length = 4; // Error: Cannot assign to 'length' because it is a read-only property. function toHex([r, g, b]: readonly [number, number, number]) { return `#${r.toString(16)}${g.toString(16)}${b.toString(16)}`; } console.log(toHex(color)); // `FixedLengthArray` is assignable to `readonly [number, number, number]`. ``` @category Array */ export type FixedLengthArray = Except, ArrayLengthMutationKeys | number | 'length'> & {readonly length: Length} & (number extends Length ? {[n: number]: Element} : {}); // Add `number` index signature only for non-tuple arrays. export {}; ================================================ FILE: source/get.d.ts ================================================ import type {ApplyDefaultOptions, ToString} from './internal/index.d.ts'; import type {_LiteralStringUnion} from './literal-union.d.ts'; import type {Paths} from './paths.d.ts'; import type {Split} from './split.d.ts'; import type {KeyAsString} from './key-as-string.d.ts'; import type {DigitCharacter} from './characters.d.ts'; export type GetOptions = { /** Include `undefined` in the return type when accessing properties. Setting this to `false` is not recommended. @default true */ strict?: boolean; }; type DefaultGetOptions = { strict: true; }; /** Like the `Get` type but receives an array of strings as a path parameter. */ type GetWithPath> = Keys extends readonly [] ? BaseType : Keys extends readonly [infer Head, ...infer Tail] ? GetWithPath< PropertyOf, Options>, Extract, Options > : never; /** Adds `undefined` to `Type` if `strict` is enabled. */ type Strictify> = Options['strict'] extends false ? Type : (Type | undefined); /** If `Options['strict']` is `true`, includes `undefined` in the returned type when accessing properties on `Record`. Known limitations: - Does not include `undefined` in the type on object types with an index signature (for example, `{a: string; [key: string]: string}`). */ type StrictPropertyOf> = Record extends BaseType ? string extends keyof BaseType ? Strictify // Record : BaseType[Key] // Record<'a' | 'b', any> (Records with a string union as keys have required properties) : BaseType[Key]; /** Splits a dot-prop style path into a tuple comprised of the properties in the path. Handles square-bracket notation. @example ``` type A = ToPath<'foo.bar.baz'>; //=> ['foo', 'bar', 'baz'] type B = ToPath<'foo[0].bar.baz'>; //=> ['foo', '0', 'bar', 'baz'] ``` */ type ToPath = Split, '.', {strictLiteralChecks: false}>; /** Replaces square-bracketed dot notation with dots, for example, `foo[0].bar` -> `foo.0.bar`. */ type FixPathSquareBrackets = Path extends `[${infer Head}]${infer Tail}` ? Tail extends `[${string}` ? `${Head}.${FixPathSquareBrackets}` : `${Head}${FixPathSquareBrackets}` : Path extends `${infer Head}[${infer Middle}]${infer Tail}` ? `${Head}.${FixPathSquareBrackets<`[${Middle}]${Tail}`>}` : Path; /** Returns true if `LongString` is made up out of `Substring` repeated 0 or more times. @example ``` type A = ConsistsOnlyOf<'aaa', 'a'>; //=> true type B = ConsistsOnlyOf<'ababab', 'ab'>; //=> true type C = ConsistsOnlyOf<'aBa', 'a'>; //=> false type D = ConsistsOnlyOf<'', 'a'>; //=> true ``` */ type ConsistsOnlyOf = LongString extends '' ? true : LongString extends `${Substring}${infer Tail}` ? ConsistsOnlyOf : false; /** Convert a type which may have number keys to one with string keys, making it possible to index using strings retrieved from template types. @example ``` type WithNumbers = {foo: string; 0: boolean}; type WithStrings = WithStringKeys; type WithNumbersKeys = keyof WithNumbers; //=> 'foo' | 0 type WithStringsKeys = keyof WithStrings; //=> 'foo' | '0' ``` */ type WithStringKeys = { [Key in KeyAsString]: UncheckedIndex }; /** Perform a `T[U]` operation if `T` supports indexing. */ type UncheckedIndex = [T] extends [Record] ? T[U] : never; /** Get a property of an object or array. Works when indexing arrays using number-literal-strings, for example, `PropertyOf = number`, and when indexing objects with number keys. Note: - Returns `unknown` if `Key` is not a property of `BaseType`, since TypeScript uses structural typing, and it cannot be guaranteed that extra properties unknown to the type system will exist at runtime. - Returns `undefined` from nullish values, to match the behaviour of most deep-key libraries like `lodash`, `dot-prop`, etc. */ type PropertyOf> = BaseType extends null | undefined ? undefined : Key extends keyof BaseType ? StrictPropertyOf // Handle arrays and tuples : BaseType extends readonly unknown[] ? Key extends `${number}` // For arrays with unknown length (regular arrays) ? number extends BaseType['length'] ? Strictify // For tuples: check if the index is valid : Key extends keyof BaseType ? Strictify // Out-of-bounds access for tuples : unknown // Non-numeric string key for arrays/tuples : unknown // Handle array-like objects : BaseType extends { [n: number]: infer Item; length: number; // Note: This is needed to avoid being too lax with records types using number keys like `{0: string; 1: boolean}`. } ? ( ConsistsOnlyOf extends true ? Strictify : unknown ) : Key extends keyof WithStringKeys ? StrictPropertyOf, Key, Options> : unknown; // This works by first splitting the path based on `.` and `[...]` characters into a tuple of string keys. Then it recursively uses the head key to get the next property of the current object, until there are no keys left. Number keys extract the item type from arrays, or are converted to strings to extract types from tuples and dictionaries with number keys. /** Get a deeply-nested property from an object using a key path, like Lodash's `.get()` function. Use-case: Retrieve a property from deep inside an API response or some other complex object. @example ``` import type {Get} from 'type-fest'; declare function get(object: BaseType, path: Path): Get; type ApiResponse = { hits: { hits: Array<{ _id: string; _source: { name: Array<{ given: string[]; family: string; }>; birthDate: string; }; }>; }; }; const getName = (apiResponse: ApiResponse) => get(apiResponse, 'hits.hits[0]._source.name'); //=> (apiResponse: ApiResponse) => { // given: string[]; // family: string; // }[] | undefined // Path also supports a readonly array of strings const getNameWithPathArray = (apiResponse: ApiResponse) => get(apiResponse, ['hits', 'hits', '0', '_source', 'name']); //=> (apiResponse: ApiResponse) => { // given: string[]; // family: string; // }[] | undefined // Non-strict mode: type A = Get; //=> string type B = Get, 'foo', {strict: true}>; //=> string | undefined ``` @category Object @category Array @category Template literal */ export type Get< BaseType, Path extends | readonly string[] | _LiteralStringUnion | Paths>>, Options extends GetOptions = {}, > = GetWithPath< BaseType, Path extends string ? ToPath : Path, ApplyDefaultOptions >; export {}; ================================================ FILE: source/global-this.d.ts ================================================ /** Declare locally scoped properties on `globalThis`. When defining a global variable in a declaration file is inappropriate, it can be helpful to define a `type` or `interface` (say `ExtraGlobals`) with the global variable and then cast `globalThis` via code like `globalThis as unknown as ExtraGlobals`. Instead of casting through `unknown`, you can update your `type` or `interface` to extend `GlobalThis` and then directly cast `globalThis`. @example ``` import type {GlobalThis} from 'type-fest'; type ExtraGlobals = GlobalThis & { readonly GLOBAL_TOKEN: string; }; const globalToken = (globalThis as ExtraGlobals).GLOBAL_TOKEN; //=> string ``` @category Type */ export type GlobalThis = typeof globalThis; export {}; ================================================ FILE: source/globals/index.d.ts ================================================ export type * from './observable-like.d.ts'; export {}; ================================================ FILE: source/globals/observable-like.d.ts ================================================ declare global { // eslint-disable-next-line @typescript-eslint/consistent-type-definitions -- It has to be an `interface` so that it can be merged. interface SymbolConstructor { readonly observable: symbol; } } /** @remarks The TC39 observable proposal defines a `closed` property, but some implementations (such as xstream) do not as of 10/08/2021. As well, some guidance on making an `Observable` to not include `closed` property. @see https://github.com/tc39/proposal-observable/blob/master/src/Observable.js#L129-L130 @see https://github.com/staltz/xstream/blob/6c22580c1d84d69773ee4b0905df44ad464955b3/src/index.ts#L79-L85 @see https://github.com/benlesh/symbol-observable#making-an-object-observable @category Observable */ // eslint-disable-next-line type-fest/require-exported-types export type Unsubscribable = { unsubscribe(): void; }; /** @category Observable */ type OnNext = (value: ValueType) => void; /** @category Observable */ type OnError = (error: unknown) => void; /** @category Observable */ type OnComplete = () => void; /** @category Observable */ // eslint-disable-next-line type-fest/require-exported-types export type Observer = { next: OnNext; error: OnError; complete: OnComplete; }; /** Matches a value that is like an [Observable](https://github.com/tc39/proposal-observable). You must import it as a sub-import: @example ``` import type {ObservableLike} from 'type-fest/globals'; ``` @remarks The TC39 Observable proposal defines 2 forms of `subscribe()`: 1. Three callback arguments: `subscribe(observer: OnNext, onError?: OnError, onComplete?: OnComplete): Unsubscribable;` 2. A single `observer` argument: (as defined below) But `Observable` implementations have evolved to preferring case 2 and some implementations choose not to implement case 1. Therefore, an `ObservableLike` cannot be trusted to implement the first case. (xstream and hand built observerables often do not implement case 1) @see https://github.com/tc39/proposal-observable#observable @see https://github.com/tc39/proposal-observable/blob/master/src/Observable.js#L246-L259 @see https://benlesh.com/posts/learning-observable-by-building-observable/ @category Observable */ // eslint-disable-next-line type-fest/require-exported-types export type ObservableLike = { subscribe(observer?: Partial>): Unsubscribable; [Symbol.observable](): ObservableLike; }; export {}; ================================================ FILE: source/greater-than-or-equal.d.ts ================================================ import type {GreaterThan} from './greater-than.d.ts'; /** Returns a boolean for whether a given number is greater than or equal to another number. @example ``` import type {GreaterThanOrEqual} from 'type-fest'; type A = GreaterThanOrEqual<1, -5>; //=> true type B = GreaterThanOrEqual<1, 1>; //=> true type C = GreaterThanOrEqual<1, 5>; //=> false ``` Note: If either argument is the non-literal `number` type, the result is `boolean`. @example ``` import type {GreaterThanOrEqual} from 'type-fest'; type A = GreaterThanOrEqual; //=> boolean type B = GreaterThanOrEqual<1, number>; //=> boolean type C = GreaterThanOrEqual; //=> boolean ``` @example ``` import type {GreaterThanOrEqual} from 'type-fest'; // Use `GreaterThanOrEqual` to constrain a function parameter to non-negative numbers. declare function setNonNegative(value: GreaterThanOrEqual extends true ? N : never): void; setNonNegative(0); // ✅ Allowed setNonNegative(1); // ✅ Allowed // @ts-expect-error setNonNegative(-1); // @ts-expect-error setNonNegative(-2); ``` */ export type GreaterThanOrEqual = number extends A | B ? boolean : A extends number // For distributing `A` ? B extends number // For distributing `B` ? A extends B ? true : GreaterThan : never // Should never happen : never; // Should never happen export {}; ================================================ FILE: source/greater-than.d.ts ================================================ import type {NumberAbsolute, PositiveNumericStringGt} from './internal/index.d.ts'; import type {IsEqual} from './is-equal.d.ts'; import type {PositiveInfinity, NegativeInfinity, IsNegative} from './numeric.d.ts'; import type {And} from './and.d.ts'; import type {Or} from './or.d.ts'; /** Returns a boolean for whether a given number is greater than another number. @example ``` import type {GreaterThan} from 'type-fest'; type A = GreaterThan<1, -5>; //=> true type B = GreaterThan<1, 1>; //=> false type C = GreaterThan<1, 5>; //=> false ``` Note: If either argument is the non-literal `number` type, the result is `boolean`. @example ``` import type {GreaterThan} from 'type-fest'; type A = GreaterThan; //=> boolean type B = GreaterThan<1, number>; //=> boolean type C = GreaterThan; //=> boolean ``` @example ``` import type {GreaterThan} from 'type-fest'; // Use `GreaterThan` to constrain a function parameter to positive numbers. declare function setPositive(value: GreaterThan extends true ? N : never): void; setPositive(1); // ✅ Allowed setPositive(2); // ✅ Allowed // @ts-expect-error setPositive(0); // @ts-expect-error setPositive(-1); ``` */ export type GreaterThan = A extends number // For distributing `A` ? B extends number // For distributing `B` ? number extends A | B ? boolean : [ IsEqual, IsEqual, IsEqual, IsEqual, ] extends infer R extends [boolean, boolean, boolean, boolean] ? Or< And, IsEqual>, And, IsEqual> > extends true ? true : Or< And, IsEqual>, And, IsEqual> > extends true ? false : true extends R[number] ? false : [IsNegative, IsNegative] extends infer R extends [boolean, boolean] ? [true, false] extends R ? false : [false, true] extends R ? true : [false, false] extends R ? PositiveNumericStringGt<`${A}`, `${B}`> : PositiveNumericStringGt<`${NumberAbsolute}`, `${NumberAbsolute}`> : never : never : never // Should never happen : never; // Should never happen export {}; ================================================ FILE: source/has-optional-keys.d.ts ================================================ import type {OptionalKeysOf} from './optional-keys-of.d.ts'; /** Creates a type that represents `true` or `false` depending on whether the given type has any optional fields. This is useful when you want to create an API whose behavior depends on the presence or absence of optional fields. @example ``` import type {HasOptionalKeys, OptionalKeysOf} from 'type-fest'; type UpdateService = { removeField: HasOptionalKeys extends true ? (field: OptionalKeysOf) => Promise : never; }; ``` @category Utilities */ export type HasOptionalKeys = OptionalKeysOf extends never ? false : true; export {}; ================================================ FILE: source/has-readonly-keys.d.ts ================================================ import type {ReadonlyKeysOf} from './readonly-keys-of.d.ts'; /** Creates a type that represents `true` or `false` depending on whether the given type has any readonly fields. This is useful when you want to create an API whose behavior depends on the presence or absence of readonly fields. @example ``` import type {HasReadonlyKeys, ReadonlyKeysOf} from 'type-fest'; type UpdateService = { removeField: HasReadonlyKeys extends true ? (field: ReadonlyKeysOf) => Promise : never; }; ``` @category Utilities */ export type HasReadonlyKeys = ReadonlyKeysOf extends never ? false : true; export {}; ================================================ FILE: source/has-required-keys.d.ts ================================================ import type {RequiredKeysOf} from './required-keys-of.d.ts'; /** Creates a type that represents `true` or `false` depending on whether the given type has any required fields. This is useful when you want to create an API whose behavior depends on the presence or absence of required fields. @example ``` import type {HasRequiredKeys} from 'type-fest'; type GeneratorOptions