-
-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathxorFactory.js
159 lines (140 loc) · 4.77 KB
/
xorFactory.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
const ts = require('typescript')
const xorParamCount = 200
const countOfUniqueLetters = 20
/**
* Contains ['A', 'B', ..., <countOfUniqueLetters'th_letter_used_in_Array_constructor>]
*/
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)