diff --git a/.github/pull-request-template.md b/.github/pull-request-template.md index dadcb45..1e2d668 100644 --- a/.github/pull-request-template.md +++ b/.github/pull-request-template.md @@ -6,6 +6,4 @@ QA should verify that the following things hold true (see `qa/playbook.ts`): -- [ ] Autocomplete works on `Shape` - [ ] Autocomplete works on `Derive` -- [ ] Autocomplete works for keys on `Alias` diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 1ed5b1f..3af6182 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -10,13 +10,13 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Setup node.js - uses: actions/setup-node@v1 + - name: Setup Node.js + uses: actions/setup-node@v2 with: - node-version: '16' + node-version: 'lts/*' - name: Install dependencies - run: yarn install + run: yarn install --frozen-lockfile - name: Run linter run: yarn lint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c653d85..c970e03 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,15 +9,13 @@ jobs: name: Release runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 + - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v2 with: node-version: 'lts/*' + - name: Install dependencies run: yarn install --frozen-lockfile diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 986e5ca..666b3f7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,13 +10,13 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Setup node.js - uses: actions/setup-node@v1 + - name: Setup Node.js + uses: actions/setup-node@v2 with: - node-version: '16' + node-version: 'lts/*' - name: Install dependencies - run: yarn install + run: yarn install --frozen-lockfile - name: Run tests run: yarn test diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..06a9a36 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +The MIT License + +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. diff --git a/README.md b/README.md index 475dda1..410114c 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,20 @@ [![npm version](https://badge.fury.io/js/@shoooe%2Fderive.svg)](https://badge.fury.io/js/@shoooe%2Fderive) -Utility type to generate a type starting from another. +Utility type to generate a type from another with a special care for the DX. -You can see this tool as an hardcode version of `Pick`. +You can see this tool as an _hardcode_ version of `Pick`. Features: - 😎 Type safe - 🌱 Minimal & lightweight - βŒ¨οΈβ€‹ Autocompletion for fields +- πŸ’₯ Automatically expands arrays and nullable/optional types - πŸ‘€ Preview expanded types in intellisense - πŸ’« Supports recursive & mutually recursive types - ❓ Optional fields support - πŸ’‹ Inspired by GraphQL -- πŸ›  Supports [GraphQL Code Generator](https://github.com/dotansimha/graphql-code-generator) types ## Installation @@ -53,29 +53,27 @@ We can derive a subset of its properties via: type Result = Derive< User, { - // `Auto`Β = infer original type (`number`) - id: Auto; - - // `Auto`Β = infer original type (`string`) - name: Auto; + id: true; + name: true; // Automatically expands nullable & optional types, which means that `null` // and `undefined` will be added automatically to the resulting type if // they existed in the target type. bestFriend: { - name: Auto; + name: true; }; // Automatically expands arrays as well friends: { - name: Auto; + name: true; + // Supports mutually recursive types favoriteBook: { - isdn: Auto; - title: Auto; - synopsis: Auto; + isdn: true; + title: true; + synopsis: true; author: { - name: Auto; + name: true; }; }; }; @@ -110,60 +108,9 @@ type Result = { }; ``` -### Reusing shapes - -You can extract and reuse shapes: - -```typescript -type CustomShape = Shape< - User, - { - id: Auto; - name: Auto; - bestFriend: { - name: Auto; - }; - } ->; -``` - -Once defined you can use them directly to generate the type: - -```typescript -type Result = Derive; -``` - -Otherwise you can use them inside other shapes: - -```typescript -type Result = Derive< - User, - { - id: Auto; - name: Auto; - bestFriend: CustomShape; - } ->; -``` - -### Aliases - -You can alias a field from another type and infer nullability and optionality using `Alias` like this: +## Alternatives -```typescript -type Result = Derive< - User, - { - alias: Alias; - } ->; -``` - -which will result in: - -```typescript -type Result = { alias?: string | null }; -``` +- [ts-essentials](https://github.com/ts-essentials/ts-essentials): comprehensive library with a different style for `DeepPick` (it doesn't do automatic expansion) ## Credits diff --git a/index.ts b/index.ts index a656ce7..1a909ed 100644 --- a/index.ts +++ b/index.ts @@ -1,4 +1 @@ -export type { Derive } from './src/Derive'; -export type { Auto } from './src/Auto'; -export type { Shape } from './src/Shape'; -export type { Alias } from './src/Alias'; +export type { Derive } from './src/derive'; diff --git a/package.json b/package.json index 9cbcdd9..6f8a1aa 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,8 @@ "keywords": [ "safe", "derive", + "pick", + "deep", "types", "typesafe", "typescript", diff --git a/qa/playbook.ts b/qa/playbook.ts index 5b957f3..0852365 100644 --- a/qa/playbook.ts +++ b/qa/playbook.ts @@ -1,7 +1,4 @@ -import { Alias } from '../src/Alias'; -import { Auto } from '../src/Auto'; -import { Derive } from '../src/Derive'; -import { Shape } from '../src/Shape'; +import { Derive } from '../src/derive'; type User = { id: string; @@ -9,39 +6,12 @@ type User = { bestFriend: User | null; }; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -type ShapeAutocomplete = Shape< - User, - { - id: Auto; - // CHECK: start typing "name" and notice the autocomplete - // CHECK: start typing "bestFriend" and then "name" and notice the autocomplete for the nested type - } ->; - // eslint-disable-next-line @typescript-eslint/no-unused-vars type DeriveAutocomplete = Derive< User, { - id: Auto; + id: true; // CHECK: start typing "name" and notice the autocomplete // CHECK: start typing "bestFriend" and then "name" and notice the autocomplete for the nested type } >; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -type AliasAutocomplete = Shape< - User, - { - id: Auto; - alias: Alias; // CHECK: remove 'id' and notice the autocomplete for keys - nestedAlias: Alias< - User, - 'bestFriend', - { - id: Auto; - // CHECK: start typing "name" and notice the autocomplete - } - >; - } ->; diff --git a/src/Alias.ts b/src/Alias.ts deleted file mode 100644 index 620ccc7..0000000 --- a/src/Alias.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { AutocompleteHelper } from './AutocompleteHelper'; -import { RecordLike } from './RecordLike'; -import { ValidShape } from './ValidShape'; - -/** - * Marker type for an alias. - */ -export class Alias< - Target extends RecordLike, - Key extends keyof Target, - Shape extends AutocompleteHelper & - ValidShape, -> { - private __target!: Target; - private __key!: Key; - private __shape!: Shape; -} diff --git a/src/Auto.ts b/src/Auto.ts deleted file mode 100644 index f488768..0000000 --- a/src/Auto.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Type used to mark that a field type should be inferred from some target - * type. - **/ -export class Auto { - private __auto!: void; -} diff --git a/src/EmptyRecord.ts b/src/EmptyRecord.ts deleted file mode 100644 index c156343..0000000 --- a/src/EmptyRecord.ts +++ /dev/null @@ -1 +0,0 @@ -export type EmptyRecord = Record; diff --git a/src/Shape.ts b/src/Shape.ts deleted file mode 100644 index 528d83e..0000000 --- a/src/Shape.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { AutocompleteHelper } from './AutocompleteHelper'; -import { ValidShape } from './ValidShape'; - -/** - * Type used to define a shape that can then be reused as a `Derive` shape - * or inside other shapes. - * - * @example - * type BookShape = Shape - * } - * }>; - */ -export type Shape< - Target, - Shape extends AutocompleteHelper & ValidShape, -> = Shape; diff --git a/src/AutocompleteHelper.ts b/src/autocomplete-helper.ts similarity index 72% rename from src/AutocompleteHelper.ts rename to src/autocomplete-helper.ts index e1eff11..888a579 100644 --- a/src/AutocompleteHelper.ts +++ b/src/autocomplete-helper.ts @@ -1,8 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Alias } from './Alias'; -import { Auto } from './Auto'; -import { CoreTypeOf } from './CoreTypeOf'; -import { RecordLike } from './RecordLike'; +import { CoreTypeOf } from './core-type-of'; +import { RecordLike } from './record-like'; /** * Represents the possible shape values for a given target type fields. @@ -18,4 +16,4 @@ export type AutocompleteHelper = CoreTypeOf extends RecordLike CoreTypeOf[Key] >; } - : Auto | Alias; + : true; diff --git a/src/CoreTypeOf.ts b/src/core-type-of.ts similarity index 79% rename from src/CoreTypeOf.ts rename to src/core-type-of.ts index 671be7d..3ae763e 100644 --- a/src/CoreTypeOf.ts +++ b/src/core-type-of.ts @@ -1,4 +1,4 @@ -import { ElementTypeOf } from './ElementTypeOf'; +import { ElementTypeOf } from './element-type-of'; /** * Gets the core type of another type by stripping array notations, `null` and diff --git a/src/Derive.ts b/src/derive.ts similarity index 56% rename from src/Derive.ts rename to src/derive.ts index 9b6301f..2a8ec5c 100644 --- a/src/Derive.ts +++ b/src/derive.ts @@ -1,10 +1,8 @@ -import { Alias } from './Alias'; -import { Auto } from './Auto'; -import { AutocompleteHelper } from './AutocompleteHelper'; -import { ForceIntellisenseExpansion } from './ForceIntellisenseExpansion'; -import { RecordLike } from './RecordLike'; -import { ValidShape } from './ValidShape'; -import { OptionalKeysOf } from './OptionalKeysOf'; +import { AutocompleteHelper } from './autocomplete-helper'; +import { ForceIntellisenseExpansion } from './force-intellisense-expansion'; +import { RecordLike } from './record-like'; +import { ValidShape } from './valid-shape'; +import { OptionalKeysOf } from './optional-keys-of'; /** * Utility to derive a type from another type. @@ -13,10 +11,10 @@ import { OptionalKeysOf } from './OptionalKeysOf'; * type Result = Derive< * User, * { - * id: Auto; - * name: Auto; + * id: true; + * name: true; * bestFriend: { - * id: Auto; + * id: true; * }; * } * >; @@ -24,31 +22,18 @@ import { OptionalKeysOf } from './OptionalKeysOf'; export type Derive< Target, Shape extends AutocompleteHelper & ValidShape, -> = ForceIntellisenseExpansion>>; - -/** - * Only resolves aliases. - * - * @package - */ -type ResolveAliases = Shape extends Alias< - infer AliasTarget, - infer AliasKey, - infer AliasShape -> - ? ResolveAutos - : Shape; +> = ForceIntellisenseExpansion>; /** * Resolves a shape. * * @package */ -type ResolveAutos = Target extends null | undefined - ? ResolveAutos, Shape> | Extract +type ResolveShape = Target extends null | undefined + ? ResolveShape, Shape> | Extract : Target extends Array - ? Array> - : Shape extends Auto + ? Array> + : Shape extends true ? Target : Shape extends RecordLike ? Target extends RecordLike @@ -73,8 +58,8 @@ type ResolveRequiredFields< Shape extends RecordLike, > = { [Key in ResultRequiredKeys]: Key extends keyof BaseType - ? ResolveAutos> - : ResolveAliases; + ? ResolveShape + : Shape[Key]; }; type ResolveOptionalFields< @@ -82,8 +67,8 @@ type ResolveOptionalFields< Shape extends RecordLike, > = { [Key in ResultOptionalKeys]?: Key extends keyof BaseType - ? ResolveAutos> - : ResolveAliases; + ? ResolveShape + : Shape[Key]; }; /** @@ -99,15 +84,6 @@ type ResultOptionalKeys< { [Key in keyof Shape]: Key extends OptionalKeysOf ? Key - : Shape[Key] extends Alias< - infer AliasTarget, - infer AliasKey, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - infer _AliasShape - > - ? AliasKey extends OptionalKeysOf - ? Key - : never : never; }[keyof Shape], undefined diff --git a/src/ElementTypeOf.ts b/src/element-type-of.ts similarity index 100% rename from src/ElementTypeOf.ts rename to src/element-type-of.ts diff --git a/src/Exactly.ts b/src/exactly.ts similarity index 100% rename from src/Exactly.ts rename to src/exactly.ts diff --git a/src/ForceIntellisenseExpansion.ts b/src/force-intellisense-expansion.ts similarity index 90% rename from src/ForceIntellisenseExpansion.ts rename to src/force-intellisense-expansion.ts index 1ff421e..e632e8a 100644 --- a/src/ForceIntellisenseExpansion.ts +++ b/src/force-intellisense-expansion.ts @@ -1,4 +1,4 @@ -import { RecordLike } from './RecordLike'; +import { RecordLike } from './record-like'; /** * This is a no-op type that forces intellisense to evaluate the type diff --git a/src/OptionalKeysOf.ts b/src/optional-keys-of.ts similarity index 100% rename from src/OptionalKeysOf.ts rename to src/optional-keys-of.ts diff --git a/src/RecordLike.ts b/src/record-like.ts similarity index 100% rename from src/RecordLike.ts rename to src/record-like.ts diff --git a/src/RequiredKeysOf.ts b/src/required-keys-of.ts similarity index 85% rename from src/RequiredKeysOf.ts rename to src/required-keys-of.ts index ab73626..3df12ed 100644 --- a/src/RequiredKeysOf.ts +++ b/src/required-keys-of.ts @@ -1,4 +1,4 @@ -import { OptionalKeysOf } from './OptionalKeysOf'; +import { OptionalKeysOf } from './optional-keys-of'; /** * Returns the union of keys from `Type` that are required. diff --git a/src/ValidShape.ts b/src/valid-shape.ts similarity index 57% rename from src/ValidShape.ts rename to src/valid-shape.ts index 924f162..c6bd398 100644 --- a/src/ValidShape.ts +++ b/src/valid-shape.ts @@ -1,9 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Alias } from './Alias'; -import { Auto } from './Auto'; -import { CoreTypeOf } from './CoreTypeOf'; -import { Exactly } from './Exactly'; -import { RecordLike } from './RecordLike'; +import { CoreTypeOf } from './core-type-of'; +import { Exactly } from './exactly'; +import { RecordLike } from './record-like'; /** * Usually used in `extends` clause to check that a shape is a valid shape @@ -22,16 +20,8 @@ export type ValidShape = CoreTypeOf extends RecordLike CoreTypeOf[Key], Shape[Key] >; - } & { - // For everything else we only accept Alias. - [Key in Exclude>]: Alias< - any, - any, - any - >; }, Shape > - : // If the target is an object and the shape is not then it can only be an alias. - Alias - : Auto | Alias; + : never + : true; diff --git a/test/Alias.test.ts b/test/Alias.test.ts deleted file mode 100644 index a3951b6..0000000 --- a/test/Alias.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { assertEqualTypes } from '../utils/assertEqualTypes'; -import { assertNonEqualTypes } from '../utils/assertNonEqualTypes'; -import { describe } from '../utils/describe'; -import { Alias } from '../src/Alias'; -import { Auto } from '../src/Auto'; -import { assertCompilationError } from '../utils/assertCompilationError'; -import { it } from '../utils/it'; - -type User = { - id: number; - name: string | null; - bestFriend: User | null; -}; - -describe('Alias', [ - it('supports scalars', [ - assertNonEqualTypes, Alias>(), - assertEqualTypes, Alias>(), - ]), - - it('defaults to `Auto` for scalar types', [ - // assertEqualTypes, Alias>(), - ]), - - it("doesn't compile when you specify a key that doesn't exist", [ - assertCompilationError< - Alias< - User, - // @ts-expect-error: error - 'missing', - Auto - > - >(), - ]), - - it("doesn't compile when you specify `Auto` for a record like type", [ - assertCompilationError< - Alias< - User, - 'bestFriend', - // @ts-expect-error: error - Auto - > - >(), - ]), - - it("doesn't compile when you specify a nested shape for a scalar field", [ - assertCompilationError< - Alias< - User, - 'id', - // @ts-expect-error: error - { - id: Auto; - } - > - >(), - ]), -]); diff --git a/test/Auto.test.ts b/test/Auto.test.ts deleted file mode 100644 index f3ff7c4..0000000 --- a/test/Auto.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { assertEqualTypes } from '../utils/assertEqualTypes'; -import { describe } from '../utils/describe'; -import { Auto } from '../src/Auto'; -import { it } from '../utils/it'; - -describe('Auto', [it('is equal to itself', [assertEqualTypes()])]); diff --git a/test/Derive.test.ts b/test/Derive.test.ts deleted file mode 100644 index 2be4da7..0000000 --- a/test/Derive.test.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { assertEqualTypes } from '../utils/assertEqualTypes'; -import { describe } from '../utils/describe'; -import { it } from '../utils/it'; -import { Derive } from '../src/Derive'; -import { Auto } from '../src/Auto'; -import { Alias } from '../src/Alias'; -import { assertCompilationError } from '../utils/assertCompilationError'; - -// Test data (with recursive & mutually recursive types) -type User = { - bestFriend: User | undefined; - favoriteBook: Book | null; - friends: User[]; - id: number; - manager: User; - name: string; - parents: User[] | null; - note?: string; - editorNote?: string | null; -}; - -type Book = { - author: User; - isdn: number; - reviewer: User | undefined; - synopsis: string | null; - title: string | null | undefined; -}; - -describe('Derive', [ - it('simply resolves the original type with scalars and `Auto`', [ - assertEqualTypes, number>(), - assertEqualTypes, number | null>(), - assertEqualTypes, number | undefined>(), - assertEqualTypes< - Derive, - number | null | undefined - >(), - ]), - - it("doesn't allow `Auto` for object like types", [ - assertCompilationError< - Derive< - { nested: { id: string } }, - // @ts-expect-error: error - { nested: Auto } - > - >(), - ]), - - it("doesn't allow nested shapes for scalar types", [ - assertCompilationError< - Derive< - { id: string }, - // @ts-expect-error: error - { - id: { prop: Auto }; - } - > - >(), - ]), - - it('supports scalars', [ - assertEqualTypes< - Derive, - { id: number; name: string } - >(), - assertEqualTypes< - Derive, - { title: string | null | undefined } - >(), - assertEqualTypes< - Derive, - { synopsis: string | null } - >(), - assertEqualTypes< - Derive, - { note?: string | undefined } - >(), - assertEqualTypes< - Derive, - { editorNote?: string | null } - >(), - ]), - - it('supports records', [ - assertEqualTypes< - Derive, - { manager: { id: number } } - >(), - assertEqualTypes< - Derive, - { bestFriend: { id: number } | undefined } - >(), - ]), - - it('supports arrays', [ - assertEqualTypes< - Derive, - { friends: { id: number }[] } - >(), - assertEqualTypes< - Derive, - { parents: { id: number }[] | null } - >(), - ]), - - it('supports recursive types', [ - assertEqualTypes< - Derive, - { parents: { manager: { id: number } }[] | null } - >(), - ]), - - it('supports mutually recursive types', [ - assertEqualTypes< - Derive, - { favoriteBook: { author: { id: number } } | null } - >(), - ]), - - it('supports aliases', [ - assertEqualTypes>, number>(), - assertEqualTypes< - Derive }>, - { isdn: number; someAlias: number } - >(), - ]), - it('supports aliases that override existing fields', [ - assertEqualTypes< - Derive }>, - { isdn: number; author: number } - >(), - ]), - it('infers optionality for fields with aliases', [ - assertEqualTypes< - Derive }>, - { isdn: number; someAlias?: string | undefined } - >(), - ]), - it('supports nested aliases', [ - assertEqualTypes< - Derive< - Book, - { - isdn: Auto; - someAlias: Alias< - Book, - 'author', - { id: Auto; note: Auto; someOtherAlias: Alias } - >; - } - >, - { - isdn: number; - someAlias: { - id: number; - note?: string | undefined; - someOtherAlias: string; - }; - } - >(), - ]), -]); diff --git a/test/Shape.test.ts b/test/Shape.test.ts deleted file mode 100644 index 349c88b..0000000 --- a/test/Shape.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { describe } from '../utils/describe'; -import { it } from '../utils/it'; -import { assertEqualTypes } from '../utils/assertEqualTypes'; -import { Shape } from '../src/Shape'; -import { Auto } from '../src/Auto'; -import { assertCompilationError } from '../utils/assertCompilationError'; -import { EmptyRecord } from '../src/EmptyRecord'; - -describe('Shape', [ - it('allows scalars as target types', [ - assertEqualTypes, Auto>(), - ]), - it('allows empty records for record types', [ - assertEqualTypes, EmptyRecord>(), - ]), - it("doesn't allow using `Auto` for an object like field", [ - assertCompilationError< - Shape< - { nested: { id: string } }, - // @ts-expect-error: error - { nested: Auto } - > - >(), - ]), - it("doesn't allow using other types other than the ones allowed", [ - assertCompilationError< - Shape< - { id: string }, - // @ts-expect-error: error - { id: string } - > - >(), - assertCompilationError< - Shape< - { id: string }, - // @ts-expect-error: error - { extra: string } - > - >(), - ]), - it("doesn't allow optional `Auto`s", [ - assertCompilationError< - Shape< - { id: string }, - // @ts-expect-error: error - { id?: Auto } - > - >(), - ]), - it("doesn't allow record-like shapes for scalars", [ - assertCompilationError< - Shape< - { id: string }, - // @ts-expect-error: error - { id: { prop: number } } - > - >(), - ]), -]); diff --git a/test/AutocompleteHelper.test.ts b/tests/autocomplete-helper.ts similarity index 66% rename from test/AutocompleteHelper.test.ts rename to tests/autocomplete-helper.ts index b16548c..c9c3631 100644 --- a/test/AutocompleteHelper.test.ts +++ b/tests/autocomplete-helper.ts @@ -1,8 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Alias } from '../src/Alias'; -import { Auto } from '../src/Auto'; -import { AutocompleteHelper } from '../src/AutocompleteHelper'; -import { assertEqualTypes } from '../utils/assertEqualTypes'; +import { AutocompleteHelper } from '../src/autocomplete-helper'; +import { assertEqualTypes } from '../utils/assert-equal-types'; import { describe } from '../utils/describe'; import { it } from '../utils/it'; @@ -10,36 +8,36 @@ describe('AutocompleteHelper', [ it('supports scalar types', [ assertEqualTypes< AutocompleteHelper<{ id: number }>, - { id?: Auto | Alias } + { id?: true } >(), ]), it('ignores null and undefined', [ assertEqualTypes< AutocompleteHelper<{ id: number | undefined }>, - { id?: Auto | Alias } + { id?: true } >(), assertEqualTypes< AutocompleteHelper<{ id: number | null }>, - { id?: Auto | Alias } + { id?: true } >(), assertEqualTypes< AutocompleteHelper<{ id: number | null | undefined }>, - { id?: Auto | Alias } + { id?: true } >(), ]), it('ignores optionality', [ assertEqualTypes< AutocompleteHelper<{ id?: number }>, - { id?: Auto | Alias } + { id?: true } >(), ]), it('supports nested records', [ assertEqualTypes< AutocompleteHelper<{ friend: { name: string } }>, - { friend?: { name?: Auto | Alias } } + { friend?: { name?: true } } >(), ]), @@ -48,33 +46,33 @@ describe('AutocompleteHelper', [ AutocompleteHelper<{ friend: { name: string } | null; }>, - { friend?: { name?: Auto | Alias } } + { friend?: { name?: true } } >(), assertEqualTypes< AutocompleteHelper<{ friend: { name: string } | undefined; }>, - { friend?: { name?: Auto | Alias } } + { friend?: { name?: true } } >(), assertEqualTypes< AutocompleteHelper<{ friend: { name: string } | null | undefined; }>, - { friend?: { name?: Auto | Alias } } + { friend?: { name?: true } } >(), ]), it('supports scalar arrays', [ assertEqualTypes< AutocompleteHelper<{ id: number[] }>, - { id?: Auto | Alias } + { id?: true } >(), ]), it('supports unions', [ assertEqualTypes< AutocompleteHelper<{ id: number | string }>, - { id?: Auto | Alias } + { id?: true } >(), ]), @@ -82,8 +80,8 @@ describe('AutocompleteHelper', [ assertEqualTypes< AutocompleteHelper<{ id: number; friend: { name: string }[] }>, { - id?: Auto | Alias; - friend?: { name?: Auto | Alias }; + id?: true; + friend?: { name?: true }; } >(), ]), diff --git a/test/CoreTypeOf.test.ts b/tests/core-type-of.ts similarity index 89% rename from test/CoreTypeOf.test.ts rename to tests/core-type-of.ts index 0b6c463..4ffaac5 100644 --- a/test/CoreTypeOf.test.ts +++ b/tests/core-type-of.ts @@ -1,7 +1,7 @@ -import { assertEqualTypes } from '../utils/assertEqualTypes'; +import { assertEqualTypes } from '../utils/assert-equal-types'; import { it } from '../utils/it'; import { describe } from '../utils/describe'; -import { CoreTypeOf } from '../src/CoreTypeOf'; +import { CoreTypeOf } from '../src/core-type-of'; describe('CoreTypeOf', [ it('returns a simple scalar type', [ diff --git a/tests/derive.ts b/tests/derive.ts new file mode 100644 index 0000000..e6c14fd --- /dev/null +++ b/tests/derive.ts @@ -0,0 +1,96 @@ +import { assertEqualTypes } from '../utils/assert-equal-types'; +import { describe } from '../utils/describe'; +import { it } from '../utils/it'; +import { Derive } from '../src/derive'; + +// Test data (with recursive & mutually recursive types) +type User = { + bestFriend: User | undefined; + favoriteBook: Book | null; + friends: User[]; + id: number; + manager: User; + name: string; + parents: User[] | null; + note?: string; + editorNote?: string | null; +}; + +type Book = { + author: User; + isdn: number; + reviewer: User | undefined; + synopsis: string | null; + title: string | null | undefined; +}; + +describe('Derive', [ + it('resolves scalars', [ + assertEqualTypes, number>(), + assertEqualTypes, number | null>(), + assertEqualTypes, number | undefined>(), + assertEqualTypes< + Derive, + number | null | undefined + >(), + ]), + + it('supports scalars', [ + assertEqualTypes< + Derive, + { id: number; name: string } + >(), + assertEqualTypes< + Derive, + { title: string | null | undefined } + >(), + assertEqualTypes< + Derive, + { synopsis: string | null } + >(), + assertEqualTypes< + Derive, + { note?: string | undefined } + >(), + assertEqualTypes< + Derive, + { editorNote?: string | null } + >(), + ]), + + it('supports records', [ + assertEqualTypes< + Derive, + { manager: { id: number } } + >(), + assertEqualTypes< + Derive, + { bestFriend: { id: number } | undefined } + >(), + ]), + + it('supports arrays', [ + assertEqualTypes< + Derive, + { friends: { id: number }[] } + >(), + assertEqualTypes< + Derive, + { parents: { id: number }[] | null } + >(), + ]), + + it('supports recursive types', [ + assertEqualTypes< + Derive, + { parents: { manager: { id: number } }[] | null } + >(), + ]), + + it('supports mutually recursive types', [ + assertEqualTypes< + Derive, + { favoriteBook: { author: { id: number } } | null } + >(), + ]), +]); diff --git a/test/ElementTypeOf.test.ts b/tests/element-type-of.ts similarity index 83% rename from test/ElementTypeOf.test.ts rename to tests/element-type-of.ts index 6c6f20d..37fdb01 100644 --- a/test/ElementTypeOf.test.ts +++ b/tests/element-type-of.ts @@ -1,5 +1,5 @@ -import { assertEqualTypes } from '../utils/assertEqualTypes'; -import { ElementTypeOf } from '../src/ElementTypeOf'; +import { assertEqualTypes } from '../utils/assert-equal-types'; +import { ElementTypeOf } from '../src/element-type-of'; import { describe } from '../utils/describe'; import { it } from '../utils/it'; diff --git a/test/Exactly.test.ts b/tests/exactly.ts similarity index 91% rename from test/Exactly.test.ts rename to tests/exactly.ts index 5f8775f..4ab4675 100644 --- a/test/Exactly.test.ts +++ b/tests/exactly.ts @@ -1,5 +1,5 @@ -import { Exactly } from '../src/Exactly'; -import { assertEqualTypes } from '../utils/assertEqualTypes'; +import { Exactly } from '../src/exactly'; +import { assertEqualTypes } from '../utils/assert-equal-types'; import { describe } from '../utils/describe'; import { it } from '../utils/it'; diff --git a/test/ForceIntellisenseExpansion.test.ts b/tests/force-intellisense-expansion.ts similarity index 82% rename from test/ForceIntellisenseExpansion.test.ts rename to tests/force-intellisense-expansion.ts index a6bd78c..0cb0f66 100644 --- a/test/ForceIntellisenseExpansion.test.ts +++ b/tests/force-intellisense-expansion.ts @@ -1,7 +1,7 @@ -import { assertEqualTypes } from '../utils/assertEqualTypes'; +import { assertEqualTypes } from '../utils/assert-equal-types'; import { describe } from '../utils/describe'; import { it } from '../utils/it'; -import { ForceIntellisenseExpansion } from '../src/ForceIntellisenseExpansion'; +import { ForceIntellisenseExpansion } from '../src/force-intellisense-expansion'; describe('ForceIntellisenseExpansion', [ it('should perform no modifications to the type', [ diff --git a/test/OptionalKeysOf.test.ts b/tests/optional-keys-of.ts similarity index 77% rename from test/OptionalKeysOf.test.ts rename to tests/optional-keys-of.ts index 4a11c9d..53e98c4 100644 --- a/test/OptionalKeysOf.test.ts +++ b/tests/optional-keys-of.ts @@ -1,5 +1,5 @@ -import { assertEqualTypes } from '../utils/assertEqualTypes'; -import { OptionalKeysOf } from '../src/OptionalKeysOf'; +import { assertEqualTypes } from '../utils/assert-equal-types'; +import { OptionalKeysOf } from '../src/optional-keys-of'; import { describe } from '../utils/describe'; import { it } from '../utils/it'; diff --git a/test/RequiredKeysOf.test.ts b/tests/required-keys-of.ts similarity index 77% rename from test/RequiredKeysOf.test.ts rename to tests/required-keys-of.ts index ccff579..f54b393 100644 --- a/test/RequiredKeysOf.test.ts +++ b/tests/required-keys-of.ts @@ -1,6 +1,6 @@ -import { assertEqualTypes } from '../utils/assertEqualTypes'; +import { assertEqualTypes } from '../utils/assert-equal-types'; import { it } from '../utils/it'; -import { RequiredKeysOf } from '../src/RequiredKeysOf'; +import { RequiredKeysOf } from '../src/required-keys-of'; import { describe } from '../utils/describe'; type Mouse = { diff --git a/tsconfig.test.json b/tsconfig.test.json index 377d89e..080f4d3 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", - "include": ["src/**/*", "test/**/*", "index.ts"] + "include": ["src/**/*", "tests/**/*", "index.ts"] } diff --git a/utils/assertCompilationError.ts b/utils/assert-compilation-error.ts similarity index 86% rename from utils/assertCompilationError.ts rename to utils/assert-compilation-error.ts index 8c1e660..08d0461 100644 --- a/utils/assertCompilationError.ts +++ b/utils/assert-compilation-error.ts @@ -3,10 +3,6 @@ * compile. * * @note You'll need to use `@ts-expect-error` for this to work - * - * @example - * - * assertCompilationError */ // eslint-disable-next-line @typescript-eslint/no-unused-vars export declare function assertCompilationError<_Type>(): true; diff --git a/utils/assertEqualTypes.ts b/utils/assert-equal-types.ts similarity index 100% rename from utils/assertEqualTypes.ts rename to utils/assert-equal-types.ts diff --git a/utils/assertNonEqualTypes.ts b/utils/assert-non-equal-types.ts similarity index 100% rename from utils/assertNonEqualTypes.ts rename to utils/assert-non-equal-types.ts