diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5d12634 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.npmignore b/.npmignore index 73489dc..c9ac48e 100644 --- a/.npmignore +++ b/.npmignore @@ -5,3 +5,4 @@ tsconfig.json .gitignore .npmrc .travis.yml +.editorconfig diff --git a/README.md b/README.md index 253c13d..41284a8 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ # ts-xor -The npm package `ts-xor` introduces the new mapped type `XOR` that helps you compose your own custom TypeScript types containing mutually exclusive keys. +The tiny npm package `ts-xor` introduces the new mapped type `XOR` that helps you compose your own custom TypeScript types containing mutually exclusive keys for zero runtime overhead. ## Description @@ -60,9 +60,11 @@ then the derived type is shown quite differently in VS Code: ### How it works -Notice in the example above, that when using XOR each "variant" of the resulting type contains all keys of one source type plus all keys of the other. At the same time those keys of the second type are defined as _optional_ while additionally they are also typed as _undefined_. +Notice in the example above, that when using `XOR`, each union branch of the resulting type contains all keys of one source type plus all keys of the other. At the same time, in each variant, those keys of the other type are defined as _optional_ while additionally they are also typed as _undefined_. -This trick will not only forbid defining keys of both source types at the same time (since the type of each key is explicitly `undefined`), but also _allow_ us to not need to define all keys all of the time since each set of keys is optional on each variant. +This trick will not only forbid having keys of both source types defined at the same time (since the type of each key is explicitly `undefined`), but also _allow_ us to not need to define all keys all of the time since each set of keys is optional on each variant. + +>_Fun fact: The actual TypeScript code for `XOR` [is generated programmatically](https://github.com/maninak/ts-xor/pull/27) using the TypeScript Compiler API._ 🦾 ## Installation @@ -74,8 +76,6 @@ npm install -D ts-xor ## Usage -### A simple scenario - ```typescript import type { XOR } from 'ts-xor' @@ -90,7 +90,32 @@ test = { a: '', b: '' } // error test = {} // error ``` -### A realistic scenario +### XORing more than two types + +If you want to create a type as the product of the logical XOR operation between multiple types (more than two and even up to 200), then just pass them as additional comma-separated generic params. + +```typescript +let test: XOR +``` + +`ts-xor` can easily handle up to 200 generic params. 💯 + +### Pattern 1: Typing a fetcher returning data XOR error + +Using `XOR` we can type a function that returns either the data requested from an API or a response object like so: + +```ts +type FetchResult

= XOR< + { data: P }, + { error: FetchError

}, +> +``` + +Now TypeScript has all the necessary information to infer if the `FetchResult` contains a `data` or `error` key _at compile time_ which results in very clean, yet strictly typed, handling code. + +![data or error intellisense demo](./assets/dataOrError-intellisense.gif) + +### Pattern 2: Typing an API's response shape Let's assume that we have the following spec for a weather forecast API's response: @@ -100,8 +125,6 @@ Let's assume that we have the following spec for a weather forecast API's respon 4. The rain, snow members _always_ contain either a member `1h` or a member `3h` with a number value, but _never_ both keys at the same time. ```typescript -import type { XOR } from 'ts-xor' - type ForecastAccuracy = XOR<{ '1h': number }, { '3h': number }> interface WeatherForecastBase { @@ -134,26 +157,6 @@ const test: WeatherForecast = { } ``` -### XORing more than two types - -If you want to create a type as the product of the logical XOR operation between multiple types (more than two), then nest the generic params. - -```typescript -import type { XOR } from 'ts-xor' - -interface A { a: string } -interface B { b: string } -interface C { c: string } - -let test: XOR> - -test = { a: '' } // OK -test = { b: '' } // OK -test = { c: '' } // OK -test = { a: '', c: '' } // error -test = {} // error -``` - ## Tests and coverage The library `ts-xor` is fully covered with smoke, acceptance and mutation tests against the typescript compiler itself. The tests can be found inside the [`test`](https://github.com/maninak/ts-xor/tree/master/test) folder. diff --git a/assets/dataOrError-intellisense.gif b/assets/dataOrError-intellisense.gif new file mode 100644 index 0000000..31e8512 Binary files /dev/null and b/assets/dataOrError-intellisense.gif differ diff --git a/package.json b/package.json index aa61604..f862f48 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,10 @@ }, "sideEffects": false, "scripts": { + "codegen": "node ./src/xorFactory.js > ./src/types/xor.ts", + "prebuild": "npm run codegen", "build": "tsup src/index.ts --format esm,cjs --dts --sourcemap --clean", + "pretest": "npm run codegen", "test": "npm run test:smoke && npm run test:unit && npm run test:package", "test:smoke": "tsc -p . --noEmit", "test:unit": "sh scripts/run-tests.sh", diff --git a/src/index.ts b/src/index.ts index 30a7c10..e73ee8b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1 @@ -export * from './types/xor.js' +export type * from './types/xor.js' diff --git a/src/types/evalIfNotUnknown.ts b/src/types/evalIfNotUnknown.ts new file mode 100644 index 0000000..3f2f868 --- /dev/null +++ b/src/types/evalIfNotUnknown.ts @@ -0,0 +1,4 @@ + /** + * Skip evaluating `U` if `T` is `unknown`. + */ + export type EvalIfNotUnknown = unknown extends T ? never : U; diff --git a/src/types/xor.ts b/src/types/xor.ts index 3e32b8b..32f6bab 100644 --- a/src/types/xor.ts +++ b/src/types/xor.ts @@ -1,16 +1,20 @@ -import { Prettify } from './prettify.js' -import { Without } from './without.js' + +import type { EvalIfNotUnknown } from './evalIfNotUnknown.js' +import type { Prettify } from './prettify.js' +import type { Without } from './without.js' /** - * Restrict using either exclusively the keys of T or exclusively the keys of U. - * - * No unique keys of T can be used simultaneously with any unique keys of U. - * - * Example: - * `const myVar: XOR` - * + * Restrict using either exclusively the keys of `T` or exclusively the keys of `U`. + * + * No unique keys of `T` can be used simultaneously with any unique keys of `U`. + * + * @example + * ```ts + * const myVar: XOR<{ data: object }, { error: object }> + * ``` + * + * Supports from 2 up to 200 generic parameters. + * * More: https://github.com/maninak/ts-xor/tree/master#description - */ -export type XOR = (T | U) extends object - ? (Prettify & U>) | (Prettify & T>) - : T | U + * */ +export type XOR = Prettify<(Without & A) | (Without & B) | EvalIfNotUnknown & C> | EvalIfNotUnknown & D> | EvalIfNotUnknown & E> | EvalIfNotUnknown & F> | EvalIfNotUnknown & G> | EvalIfNotUnknown & H> | EvalIfNotUnknown & I> | EvalIfNotUnknown & J> | EvalIfNotUnknown & K> | EvalIfNotUnknown & L> | EvalIfNotUnknown & M> | EvalIfNotUnknown & N> | EvalIfNotUnknown & O> | EvalIfNotUnknown & P> | EvalIfNotUnknown & Q> | EvalIfNotUnknown & R> | EvalIfNotUnknown & S> | EvalIfNotUnknown & T> | EvalIfNotUnknown & AA> | EvalIfNotUnknown & AB> | EvalIfNotUnknown & AC> | EvalIfNotUnknown & AD> | EvalIfNotUnknown & AE> | EvalIfNotUnknown & AF> | EvalIfNotUnknown & AG> | EvalIfNotUnknown & AH> | EvalIfNotUnknown & AI> | EvalIfNotUnknown & AJ> | EvalIfNotUnknown & AK> | EvalIfNotUnknown & AL> | EvalIfNotUnknown & AM> | EvalIfNotUnknown & AN> | EvalIfNotUnknown & AO> | EvalIfNotUnknown & AP> | EvalIfNotUnknown & AQ> | EvalIfNotUnknown & AR> | EvalIfNotUnknown & AS> | EvalIfNotUnknown & AT> | EvalIfNotUnknown & BA> | EvalIfNotUnknown & BB> | EvalIfNotUnknown & BC> | EvalIfNotUnknown & BD> | EvalIfNotUnknown & BE> | EvalIfNotUnknown & BF> | EvalIfNotUnknown & BG> | EvalIfNotUnknown & BH> | EvalIfNotUnknown & BI> | EvalIfNotUnknown & BJ> | EvalIfNotUnknown & BK> | EvalIfNotUnknown & BL> | EvalIfNotUnknown & BM> | EvalIfNotUnknown & BN> | EvalIfNotUnknown & BO> | EvalIfNotUnknown & BP> | EvalIfNotUnknown & BQ> | EvalIfNotUnknown & BR> | EvalIfNotUnknown & BS> | EvalIfNotUnknown & BT> | EvalIfNotUnknown & CA> | EvalIfNotUnknown & CB> | EvalIfNotUnknown & CC> | EvalIfNotUnknown & CD> | EvalIfNotUnknown & CE> | EvalIfNotUnknown & CF> | EvalIfNotUnknown & CG> | EvalIfNotUnknown & CH> | EvalIfNotUnknown & CI> | EvalIfNotUnknown & CJ> | EvalIfNotUnknown & CK> | EvalIfNotUnknown & CL> | EvalIfNotUnknown & CM> | EvalIfNotUnknown & CN> | EvalIfNotUnknown & CO> | EvalIfNotUnknown & CP> | EvalIfNotUnknown & CQ> | EvalIfNotUnknown & CR> | EvalIfNotUnknown & CS> | EvalIfNotUnknown & CT> | EvalIfNotUnknown & DA> | EvalIfNotUnknown & DB> | EvalIfNotUnknown & DC> | EvalIfNotUnknown & DD> | EvalIfNotUnknown & DE> | EvalIfNotUnknown & DF> | EvalIfNotUnknown & DG> | EvalIfNotUnknown & DH> | EvalIfNotUnknown & DI> | EvalIfNotUnknown & DJ> | EvalIfNotUnknown & DK> | EvalIfNotUnknown & DL> | EvalIfNotUnknown & DM> | EvalIfNotUnknown & DN> | EvalIfNotUnknown & DO> | EvalIfNotUnknown & DP> | EvalIfNotUnknown & DQ> | EvalIfNotUnknown & DR> | EvalIfNotUnknown & DS> | EvalIfNotUnknown & DT> | EvalIfNotUnknown & EA> | EvalIfNotUnknown & EB> | EvalIfNotUnknown & EC> | EvalIfNotUnknown & ED> | EvalIfNotUnknown & EE> | EvalIfNotUnknown & EF> | EvalIfNotUnknown & EG> | EvalIfNotUnknown & EH> | EvalIfNotUnknown & EI> | EvalIfNotUnknown & EJ> | EvalIfNotUnknown & EK> | EvalIfNotUnknown & EL> | EvalIfNotUnknown & EM> | EvalIfNotUnknown & EN> | EvalIfNotUnknown & EO> | EvalIfNotUnknown & EP> | EvalIfNotUnknown & EQ> | EvalIfNotUnknown & ER> | EvalIfNotUnknown & ES> | EvalIfNotUnknown & ET> | EvalIfNotUnknown & FA> | EvalIfNotUnknown & FB> | EvalIfNotUnknown & FC> | EvalIfNotUnknown & FD> | EvalIfNotUnknown & FE> | EvalIfNotUnknown & FF> | EvalIfNotUnknown & FG> | EvalIfNotUnknown & FH> | EvalIfNotUnknown & FI> | EvalIfNotUnknown & FJ> | EvalIfNotUnknown & FK> | EvalIfNotUnknown & FL> | EvalIfNotUnknown & FM> | EvalIfNotUnknown & FN> | EvalIfNotUnknown & FO> | EvalIfNotUnknown & FP> | EvalIfNotUnknown & FQ> | EvalIfNotUnknown & FR> | EvalIfNotUnknown & FS> | EvalIfNotUnknown & FT> | EvalIfNotUnknown & GA> | EvalIfNotUnknown & GB> | EvalIfNotUnknown & GC> | EvalIfNotUnknown & GD> | EvalIfNotUnknown & GE> | EvalIfNotUnknown & GF> | EvalIfNotUnknown & GG> | EvalIfNotUnknown & GH> | EvalIfNotUnknown & GI> | EvalIfNotUnknown & GJ> | EvalIfNotUnknown & GK> | EvalIfNotUnknown & GL> | EvalIfNotUnknown & GM> | EvalIfNotUnknown & GN> | EvalIfNotUnknown & GO> | EvalIfNotUnknown & GP> | EvalIfNotUnknown & GQ> | EvalIfNotUnknown & GR> | EvalIfNotUnknown & GS> | EvalIfNotUnknown & GT> | EvalIfNotUnknown & HA> | EvalIfNotUnknown & HB> | EvalIfNotUnknown & HC> | EvalIfNotUnknown & HD> | EvalIfNotUnknown & HE> | EvalIfNotUnknown & HF> | EvalIfNotUnknown & HG> | EvalIfNotUnknown & HH> | EvalIfNotUnknown & HI> | EvalIfNotUnknown & HJ> | EvalIfNotUnknown & HK> | EvalIfNotUnknown & HL> | EvalIfNotUnknown & HM> | EvalIfNotUnknown & HN> | EvalIfNotUnknown & HO> | EvalIfNotUnknown & HP> | EvalIfNotUnknown & HQ> | EvalIfNotUnknown & HR> | EvalIfNotUnknown & HS> | EvalIfNotUnknown & HT> | EvalIfNotUnknown & IA> | EvalIfNotUnknown & IB> | EvalIfNotUnknown & IC> | EvalIfNotUnknown & ID> | EvalIfNotUnknown & IE> | EvalIfNotUnknown & IF> | EvalIfNotUnknown & IG> | EvalIfNotUnknown & IH> | EvalIfNotUnknown & II> | EvalIfNotUnknown & IJ> | EvalIfNotUnknown & IK> | EvalIfNotUnknown & IL> | EvalIfNotUnknown & IM> | EvalIfNotUnknown & IN> | EvalIfNotUnknown & IO> | EvalIfNotUnknown & IP> | EvalIfNotUnknown & IQ> | EvalIfNotUnknown & IR> | EvalIfNotUnknown & IS> | EvalIfNotUnknown & IT>> diff --git a/src/xorFactory.js b/src/xorFactory.js new file mode 100644 index 0000000..93fdd3a --- /dev/null +++ b/src/xorFactory.js @@ -0,0 +1,159 @@ +const ts = require('typescript') + +const xorParamCount = 200 +const countOfUniqueLetters = 20 +/** + * Contains ['A', 'B', ..., ] + */ +const uniqueLetters = [...Array(countOfUniqueLetters).keys()] + .map(i => String.fromCharCode(i + 65)) +const allParamNames = getUniqueSymbolPermutationsGivenPool(uniqueLetters, xorParamCount) +const [,, ...paramNamesExcludingANorB] = allParamNames + +function createXor() { + const modifiers = [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)] + const name = ts.factory.createIdentifier('XOR') + const typeParams = createXorParams() + const type = createXorType() + + return ts.factory.createTypeAliasDeclaration(modifiers, name, typeParams, type) +} + +function createXorParams() { + const xorParams = [ + ts.factory.createTypeParameterDeclaration(undefined, ts.factory.createIdentifier('A')), + ts.factory.createTypeParameterDeclaration(undefined, ts.factory.createIdentifier('B')), + ...paramNamesExcludingANorB.map((letter) => ts.factory.createTypeParameterDeclaration( + undefined, + ts.factory.createIdentifier(letter), + undefined, + ts.factory.createTypeReferenceNode('unknown') + )) + ] + + return xorParams +} + +function createXorType() { + const unionOfWithouts = ts.factory.createUnionTypeNode([ + createWithoutLettersIntersectingLetter( + allParamNames.filter((letterToExclude) => letterToExclude !== 'A'), + 'A', + ), + createWithoutLettersIntersectingLetter( + allParamNames.filter((letterToExclude) => letterToExclude !== 'B'), + 'B', + ), + ...paramNamesExcludingANorB.map( + (letter) => ts.factory.createTypeReferenceNode( + 'EvalIfNotUnknown', + [ + ts.factory.createTypeReferenceNode(letter), + createWithoutLettersIntersectingLetter( + allParamNames.filter((letterToExclude) => letterToExclude !== letter), + letter, + ), + ] + ) + ) + ]) + + const type = ts.factory.createTypeReferenceNode('Prettify', [unionOfWithouts]) + + return type +} + +/** + * @param {string[]} lettersExcludingLetter + * @param {string} excludedLetter + */ +function createWithoutLettersIntersectingLetter(lettersExcludingLetter, excludedLetter) { + const withoutLettersIntersectingLetter = ts.factory.createIntersectionTypeNode([ + createWithout(lettersExcludingLetter, excludedLetter), + ts.factory.createTypeReferenceNode(excludedLetter) + ]) + + return withoutLettersIntersectingLetter +} + +/** + * @param {string[]} lettersExcludingLetter + * @param {string} excludedLetter + */ +function createWithout(lettersExcludingLetter, excludedLetter) { + const type = ts.factory.createTypeReferenceNode('Without', [ + ts.factory.createIntersectionTypeNode( + lettersExcludingLetter.map((letter) => ts.factory.createTypeReferenceNode(letter)) + ), + ts.factory.createTypeReferenceNode(excludedLetter) + ]) + + return type +} + +/** + * Takes a `symbolPool` and uses them solo and then matches them in pairs until + * the provided count of unique symbols is reached. + * If all possible pairs with the available symbols are already created and the + * `countPermsToGenerate` is still not reached, then triplets will start to be generated, + * then quadruplets, etc. + * + * @example + * ```ts + * getUniqueSymbolPermutationsGivenPool(['A', 'B'], 8) + * // ['A', 'B', 'AA', 'AB', 'BA', 'BB', 'AAA', 'AAB'] + * ``` + * + * @param {string[]} symbolPool + * @param {number} countPermsToGenerate + */ +function getUniqueSymbolPermutationsGivenPool(symbolPool, countPermsToGenerate) { + const generateItem = (index) => { + if (index < 0) { + return '' + } + const remainder = index % 20 + return generateItem(Math.floor(index / 20) - 1) + symbolPool[remainder] + } + + const result = Array.from({ length: countPermsToGenerate }, (_, i) => generateItem(i)) + + return result +} + +const tempFile = ts.createSourceFile( + 'temp.ts', + '', + ts.ScriptTarget.ESNext, + false, ts.ScriptKind.TS, +) +const printer = ts.createPrinter({ + newLine: ts.NewLineKind.LineFeed, + omitTrailingSemicolon: true, +}) + +const xorTsFileContents = ` +import type { EvalIfNotUnknown } from './evalIfNotUnknown.js' +import type { Prettify } from './prettify.js' +import type { Without } from './without.js' + +${ + printer.printNode( + ts.EmitHint.Unspecified, + ts.factory.createJSDocComment( + `Restrict using either exclusively the keys of \`T\` or \ +exclusively the keys of \`U\`.\n\n\ +No unique keys of \`T\` can be used simultaneously with \ +any unique keys of \`U\`.\n\n@example\n\ +\`\`\`ts\nconst myVar: XOR<{ data: object }, { error: object }>\n\`\`\`\n\n\ +Supports from 2 up to ${xorParamCount} generic parameters.\n\n\ +More: https://github.com/maninak/ts-xor/tree/master#description\n` + ), + tempFile, + ) +} +${ + printer.printNode(ts.EmitHint.Unspecified, createXor(), tempFile) +}` + +console.log(xorTsFileContents) diff --git a/test/control-std-union-without-xor/setup.ts b/test/control-std-union-without-xor/setup.ts index 67dac26..6f923b8 100644 --- a/test/control-std-union-without-xor/setup.ts +++ b/test/control-std-union-without-xor/setup.ts @@ -1,9 +1,4 @@ -interface A { - a: string -} - -interface B { - b: string -} +interface A { a: string } +interface B { b: string } export type A_OR_B = A | B diff --git a/test/four-xored-types/has-keys-of-A-and-C.spec.ts b/test/four-xored-types/has-keys-of-A-and-C.spec.ts new file mode 100644 index 0000000..7ef0b30 --- /dev/null +++ b/test/four-xored-types/has-keys-of-A-and-C.spec.ts @@ -0,0 +1,6 @@ +import { XOR_A_B_C_D, XOR_A_B_C_D_Nested } from './setup' + +// @ts-expect-error +const test: XOR_A_B_C_D = { a: '', c: '' } +// @ts-expect-error +const testNested: XOR_A_B_C_D_Nested = { a: '', c: '' } diff --git a/test/four-xored-types/has-keys-of-A-and-D.spec.ts b/test/four-xored-types/has-keys-of-A-and-D.spec.ts new file mode 100644 index 0000000..5443e55 --- /dev/null +++ b/test/four-xored-types/has-keys-of-A-and-D.spec.ts @@ -0,0 +1,6 @@ +import { XOR_A_B_C_D, XOR_A_B_C_D_Nested } from './setup' + +// @ts-expect-error +const test: XOR_A_B_C_D = { a: '', d: '' } +// @ts-expect-error +const testNested: XOR_A_B_C_D_Nested = { a: '', d: '' } diff --git a/test/four-xored-types/has-keys-of-A.spec.ts b/test/four-xored-types/has-keys-of-A.spec.ts new file mode 100644 index 0000000..164032f --- /dev/null +++ b/test/four-xored-types/has-keys-of-A.spec.ts @@ -0,0 +1,4 @@ +import { XOR_A_B_C_D, XOR_A_B_C_D_Nested } from './setup' + +const test: XOR_A_B_C_D = { a: '' } // OK +const testNested: XOR_A_B_C_D_Nested = { a: '' } // OK diff --git a/test/four-xored-types/has-keys-of-B.spec.ts b/test/four-xored-types/has-keys-of-B.spec.ts new file mode 100644 index 0000000..970e58f --- /dev/null +++ b/test/four-xored-types/has-keys-of-B.spec.ts @@ -0,0 +1,4 @@ +import { XOR_A_B_C_D, XOR_A_B_C_D_Nested } from './setup' + +const test: XOR_A_B_C_D = { b: '' } // OK +const testNested: XOR_A_B_C_D_Nested = { b: '' } // OK diff --git a/test/four-xored-types/has-keys-of-C-and-D.spec.ts b/test/four-xored-types/has-keys-of-C-and-D.spec.ts new file mode 100644 index 0000000..adbf77f --- /dev/null +++ b/test/four-xored-types/has-keys-of-C-and-D.spec.ts @@ -0,0 +1,6 @@ +import { XOR_A_B_C_D, XOR_A_B_C_D_Nested } from './setup' + +// @ts-expect-error +const test: XOR_A_B_C_D = { c: '', d: '' } +// @ts-expect-error +const testNested: XOR_A_B_C_D_Nested = { c: '', d: '' } diff --git a/test/four-xored-types/has-keys-of-C.spec.ts b/test/four-xored-types/has-keys-of-C.spec.ts new file mode 100644 index 0000000..510e4bb --- /dev/null +++ b/test/four-xored-types/has-keys-of-C.spec.ts @@ -0,0 +1,4 @@ +import { XOR_A_B_C_D, XOR_A_B_C_D_Nested } from './setup' + +const test: XOR_A_B_C_D = { c: '' } // OK +const testNested: XOR_A_B_C_D_Nested = { c: '' } // OK diff --git a/test/four-xored-types/has-keys-of-D.spec.ts b/test/four-xored-types/has-keys-of-D.spec.ts new file mode 100644 index 0000000..f414eee --- /dev/null +++ b/test/four-xored-types/has-keys-of-D.spec.ts @@ -0,0 +1,4 @@ +import { XOR_A_B_C_D, XOR_A_B_C_D_Nested } from './setup' + +const test: XOR_A_B_C_D = { d: '' } // OK +const testNested: XOR_A_B_C_D_Nested = { d: '' } // OK diff --git a/test/four-xored-types/has-no-keys.spec.ts b/test/four-xored-types/has-no-keys.spec.ts new file mode 100644 index 0000000..45f4df9 --- /dev/null +++ b/test/four-xored-types/has-no-keys.spec.ts @@ -0,0 +1,6 @@ +import { XOR_A_B_C_D, XOR_A_B_C_D_Nested } from './setup' + +// @ts-expect-error +const test: XOR_A_B_C_D = {} +// @ts-expect-error +const testNested: XOR_A_B_C_D_Nested = {} diff --git a/test/four-xored-types/setup.ts b/test/four-xored-types/setup.ts new file mode 100644 index 0000000..23f2162 --- /dev/null +++ b/test/four-xored-types/setup.ts @@ -0,0 +1,9 @@ +import type { XOR } from '../../src' + +interface A { a: string } +interface B { b: string } +interface C { c: string } +interface D { d: string } + +export type XOR_A_B_C_D = XOR +export type XOR_A_B_C_D_Nested = XOR>> diff --git a/test/multiple-xored-types/has-keys-of-A-and-C.spec.ts b/test/multiple-xored-types/has-keys-of-A-and-C.spec.ts deleted file mode 100644 index ea1192c..0000000 --- a/test/multiple-xored-types/has-keys-of-A-and-C.spec.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { A_XOR_B_XOR_C } from './setup' - -// @ts-expect-error -const test: A_XOR_B_XOR_C = { a: '', c: '' } diff --git a/test/multiple-xored-types/has-keys-of-A.spec.ts b/test/multiple-xored-types/has-keys-of-A.spec.ts deleted file mode 100644 index 2737d23..0000000 --- a/test/multiple-xored-types/has-keys-of-A.spec.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { A_XOR_B_XOR_C } from './setup' - -const test: A_XOR_B_XOR_C = { a: '' } // OK diff --git a/test/multiple-xored-types/has-keys-of-B.spec.ts b/test/multiple-xored-types/has-keys-of-B.spec.ts deleted file mode 100644 index 09a0c9d..0000000 --- a/test/multiple-xored-types/has-keys-of-B.spec.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { A_XOR_B_XOR_C } from './setup' - -const test: A_XOR_B_XOR_C = { b: '' } // OK diff --git a/test/multiple-xored-types/has-keys-of-C.spec.ts b/test/multiple-xored-types/has-keys-of-C.spec.ts deleted file mode 100644 index 6b27f6e..0000000 --- a/test/multiple-xored-types/has-keys-of-C.spec.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { A_XOR_B_XOR_C } from './setup' - -const test: A_XOR_B_XOR_C = { c: '' } // OK diff --git a/test/multiple-xored-types/has-no-keys.spec.ts b/test/multiple-xored-types/has-no-keys.spec.ts deleted file mode 100644 index 8455662..0000000 --- a/test/multiple-xored-types/has-no-keys.spec.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { A_XOR_B_XOR_C } from './setup' - -// @ts-expect-error -const test: A_XOR_B_XOR_C = {} diff --git a/test/multiple-xored-types/setup.ts b/test/multiple-xored-types/setup.ts deleted file mode 100644 index ca695f2..0000000 --- a/test/multiple-xored-types/setup.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { XOR } from './../../src' - -interface A { - a: string -} - -interface B { - b: string -} - -interface C { - c: string -} - -export type A_XOR_B_XOR_C = XOR> diff --git a/test/shared-and-xored-members/setup.ts b/test/shared-and-xored-members/setup.ts index d21b7e2..d5caf7d 100644 --- a/test/shared-and-xored-members/setup.ts +++ b/test/shared-and-xored-members/setup.ts @@ -1,4 +1,4 @@ -import { XOR } from './../../src' +import type { XOR } from './../../src' type ForecastAccuracy = XOR<{ '1h': number }, { '3h': number }> diff --git a/test/single-member-objects/setup.ts b/test/single-member-objects/setup.ts index 9d54d57..0ff82d7 100644 --- a/test/single-member-objects/setup.ts +++ b/test/single-member-objects/setup.ts @@ -1,11 +1,6 @@ -import { XOR } from './../../src' +import type { XOR } from './../../src' -interface A { - a: string -} - -interface B { - b: string -} +interface A { a: string } +interface B { b: string } export type A_XOR_B = XOR diff --git a/test/two-hundred-xored-types/has-keys-of-first-and-keys-of-last.spec.ts b/test/two-hundred-xored-types/has-keys-of-first-and-keys-of-last.spec.ts new file mode 100644 index 0000000..3c1b545 --- /dev/null +++ b/test/two-hundred-xored-types/has-keys-of-first-and-keys-of-last.spec.ts @@ -0,0 +1,4 @@ +import { XOR_200 } from './setup'; + +// @ts-expect-error +const test: XOR_200 = { a: 0, zdt: 0 } diff --git a/test/two-hundred-xored-types/has-keys-of-first.spec.ts b/test/two-hundred-xored-types/has-keys-of-first.spec.ts new file mode 100644 index 0000000..069b5d3 --- /dev/null +++ b/test/two-hundred-xored-types/has-keys-of-first.spec.ts @@ -0,0 +1,3 @@ +import { XOR_200 } from './setup'; + +const test: XOR_200 = { a: 0 } // OK diff --git a/test/two-hundred-xored-types/has-keys-of-last-two.spec.ts b/test/two-hundred-xored-types/has-keys-of-last-two.spec.ts new file mode 100644 index 0000000..7686dba --- /dev/null +++ b/test/two-hundred-xored-types/has-keys-of-last-two.spec.ts @@ -0,0 +1,4 @@ +import { XOR_200 } from './setup'; + +// @ts-expect-error +const test: XOR_200 = { zds: 0, zdt: 0 } diff --git a/test/two-hundred-xored-types/has-keys-of-last.spec.ts b/test/two-hundred-xored-types/has-keys-of-last.spec.ts new file mode 100644 index 0000000..f073fc6 --- /dev/null +++ b/test/two-hundred-xored-types/has-keys-of-last.spec.ts @@ -0,0 +1,3 @@ +import { XOR_200 } from './setup'; + +const test: XOR_200 = { zdt: 0 } // OK diff --git a/test/two-hundred-xored-types/has-two-hundred-one-params.spec.ts b/test/two-hundred-xored-types/has-two-hundred-one-params.spec.ts new file mode 100644 index 0000000..a858457 --- /dev/null +++ b/test/two-hundred-xored-types/has-two-hundred-one-params.spec.ts @@ -0,0 +1,26 @@ +import type { XOR } from '../../src' + +// @ts-expect-error +export type XOR_200 = XOR< + {EXTRANEOUS: 0}, + {a: 0}, {b: 0}, {c: 0}, {d: 0}, {e: 0}, {f: 0}, {g: 0}, {h: 0}, {i: 0}, {j: 0}, + {k: 0}, {l: 0}, {m: 0}, {n: 0}, {o: 0}, {p: 0}, {q: 0}, {r: 0}, {s: 0}, {t: 0}, + {aa: 0}, {ab: 0}, {ac: 0}, {ad: 0}, {ae: 0}, {af: 0}, {ag: 0}, {ah: 0}, {ai: 0}, {aj: 0}, + {ak: 0}, {al: 0}, {am: 0}, {an: 0}, {ao: 0}, {ap: 0}, {aq: 0}, {ar: 0}, {as: 0}, {at: 0}, + {ba: 0}, {bb: 0}, {bc: 0}, {bd: 0}, {be: 0}, {bf: 0}, {bg: 0}, {bh: 0}, {bi: 0}, {bj: 0}, + {bk: 0}, {bl: 0}, {bm: 0}, {bn: 0}, {bo: 0}, {bp: 0}, {bq: 0}, {br: 0}, {bs: 0}, {bt: 0}, + {ca: 0}, {cb: 0}, {cc: 0}, {cd: 0}, {ce: 0}, {cf: 0}, {cg: 0}, {ch: 0}, {ci: 0}, {cj: 0}, + {ck: 0}, {cl: 0}, {cm: 0}, {cn: 0}, {co: 0}, {cp: 0}, {cq: 0}, {cr: 0}, {cs: 0}, {ct: 0}, + {da: 0}, {db: 0}, {dc: 0}, {dd: 0}, {de: 0}, {df: 0}, {dg: 0}, {dh: 0}, {di: 0}, {dj: 0}, + {dk: 0}, {dl: 0}, {dm: 0}, {dn: 0}, {do: 0}, {dp: 0}, {dq: 0}, {dr: 0}, {ds: 0}, {dt: 0}, + {za: 0}, {zb: 0}, {zc: 0}, {zd: 0}, {ze: 0}, {zf: 0}, {zg: 0}, {zh: 0}, {zi: 0}, {zj: 0}, + {zk: 0}, {zl: 0}, {zm: 0}, {zn: 0}, {zo: 0}, {zp: 0}, {zq: 0}, {zr: 0}, {zs: 0}, {zt: 0}, + {zaa: 0}, {zab: 0}, {zac: 0}, {zad: 0}, {zae: 0}, {zaf: 0}, {zag: 0}, {zah: 0}, {zai: 0}, {zaj: 0}, + {zak: 0}, {zal: 0}, {zam: 0}, {zan: 0}, {zao: 0}, {zap: 0}, {zaq: 0}, {zar: 0}, {zas: 0}, {zat: 0}, + {zba: 0}, {zbb: 0}, {zbc: 0}, {zbd: 0}, {zbe: 0}, {zbf: 0}, {zbg: 0}, {zbh: 0}, {zbi: 0}, {zbj: 0}, + {zbk: 0}, {zbl: 0}, {zbm: 0}, {zbn: 0}, {zbo: 0}, {zbp: 0}, {zbq: 0}, {zbr: 0}, {zbs: 0}, {zbt: 0}, + {zca: 0}, {zcb: 0}, {zcc: 0}, {zcd: 0}, {zce: 0}, {zcf: 0}, {zcg: 0}, {zch: 0}, {zci: 0}, {zcj: 0}, + {zck: 0}, {zcl: 0}, {zcm: 0}, {zcn: 0}, {zco: 0}, {zcp: 0}, {zcq: 0}, {zcr: 0}, {zcs: 0}, {zct: 0}, + {zda: 0}, {zdb: 0}, {zdc: 0}, {zdd: 0}, {zde: 0}, {zdf: 0}, {zdg: 0}, {zdh: 0}, {zdi: 0}, {zdj: 0}, + {zdk: 0}, {zdl: 0}, {zdm: 0}, {zdn: 0}, {zdo: 0}, {zdp: 0}, {zdq: 0}, {zdr: 0}, {zds: 0}, {zdt: 0} +> diff --git a/test/two-hundred-xored-types/setup.ts b/test/two-hundred-xored-types/setup.ts new file mode 100644 index 0000000..fe18bfb --- /dev/null +++ b/test/two-hundred-xored-types/setup.ts @@ -0,0 +1,24 @@ +import type { XOR } from '../../src' + +export type XOR_200 = XOR< + {a: 0}, {b: 0}, {c: 0}, {d: 0}, {e: 0}, {f: 0}, {g: 0}, {h: 0}, {i: 0}, {j: 0}, + {k: 0}, {l: 0}, {m: 0}, {n: 0}, {o: 0}, {p: 0}, {q: 0}, {r: 0}, {s: 0}, {t: 0}, + {aa: 0}, {ab: 0}, {ac: 0}, {ad: 0}, {ae: 0}, {af: 0}, {ag: 0}, {ah: 0}, {ai: 0}, {aj: 0}, + {ak: 0}, {al: 0}, {am: 0}, {an: 0}, {ao: 0}, {ap: 0}, {aq: 0}, {ar: 0}, {as: 0}, {at: 0}, + {ba: 0}, {bb: 0}, {bc: 0}, {bd: 0}, {be: 0}, {bf: 0}, {bg: 0}, {bh: 0}, {bi: 0}, {bj: 0}, + {bk: 0}, {bl: 0}, {bm: 0}, {bn: 0}, {bo: 0}, {bp: 0}, {bq: 0}, {br: 0}, {bs: 0}, {bt: 0}, + {ca: 0}, {cb: 0}, {cc: 0}, {cd: 0}, {ce: 0}, {cf: 0}, {cg: 0}, {ch: 0}, {ci: 0}, {cj: 0}, + {ck: 0}, {cl: 0}, {cm: 0}, {cn: 0}, {co: 0}, {cp: 0}, {cq: 0}, {cr: 0}, {cs: 0}, {ct: 0}, + {da: 0}, {db: 0}, {dc: 0}, {dd: 0}, {de: 0}, {df: 0}, {dg: 0}, {dh: 0}, {di: 0}, {dj: 0}, + {dk: 0}, {dl: 0}, {dm: 0}, {dn: 0}, {do: 0}, {dp: 0}, {dq: 0}, {dr: 0}, {ds: 0}, {dt: 0}, + {za: 0}, {zb: 0}, {zc: 0}, {zd: 0}, {ze: 0}, {zf: 0}, {zg: 0}, {zh: 0}, {zi: 0}, {zj: 0}, + {zk: 0}, {zl: 0}, {zm: 0}, {zn: 0}, {zo: 0}, {zp: 0}, {zq: 0}, {zr: 0}, {zs: 0}, {zt: 0}, + {zaa: 0}, {zab: 0}, {zac: 0}, {zad: 0}, {zae: 0}, {zaf: 0}, {zag: 0}, {zah: 0}, {zai: 0}, {zaj: 0}, + {zak: 0}, {zal: 0}, {zam: 0}, {zan: 0}, {zao: 0}, {zap: 0}, {zaq: 0}, {zar: 0}, {zas: 0}, {zat: 0}, + {zba: 0}, {zbb: 0}, {zbc: 0}, {zbd: 0}, {zbe: 0}, {zbf: 0}, {zbg: 0}, {zbh: 0}, {zbi: 0}, {zbj: 0}, + {zbk: 0}, {zbl: 0}, {zbm: 0}, {zbn: 0}, {zbo: 0}, {zbp: 0}, {zbq: 0}, {zbr: 0}, {zbs: 0}, {zbt: 0}, + {zca: 0}, {zcb: 0}, {zcc: 0}, {zcd: 0}, {zce: 0}, {zcf: 0}, {zcg: 0}, {zch: 0}, {zci: 0}, {zcj: 0}, + {zck: 0}, {zcl: 0}, {zcm: 0}, {zcn: 0}, {zco: 0}, {zcp: 0}, {zcq: 0}, {zcr: 0}, {zcs: 0}, {zct: 0}, + {zda: 0}, {zdb: 0}, {zdc: 0}, {zdd: 0}, {zde: 0}, {zdf: 0}, {zdg: 0}, {zdh: 0}, {zdi: 0}, {zdj: 0}, + {zdk: 0}, {zdl: 0}, {zdm: 0}, {zdn: 0}, {zdo: 0}, {zdp: 0}, {zdq: 0}, {zdr: 0}, {zds: 0}, {zdt: 0} +>