@servicenow/sdk-build-core 3.0.2 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts +25 -0
- package/dist/app.js +8 -0
- package/dist/app.js.map +1 -0
- package/dist/compiler.d.ts +60 -0
- package/dist/compiler.js +320 -0
- package/dist/compiler.js.map +1 -0
- package/dist/compression.d.ts +7 -0
- package/dist/compression.js +79 -0
- package/dist/compression.js.map +1 -0
- package/dist/crypto.d.ts +1 -0
- package/dist/crypto.js +9 -0
- package/dist/crypto.js.map +1 -0
- package/dist/diagnostic.d.ts +41 -0
- package/dist/diagnostic.js +130 -0
- package/dist/diagnostic.js.map +1 -0
- package/dist/{plugins/Diagnostic.d.ts → fluent-diagnostic.d.ts} +3 -2
- package/dist/fluent-diagnostic.js +23 -0
- package/dist/fluent-diagnostic.js.map +1 -0
- package/dist/fluent-directive.d.ts +8 -0
- package/dist/fluent-directive.js +54 -0
- package/dist/fluent-directive.js.map +1 -0
- package/dist/fluent-file.d.ts +5 -0
- package/dist/fluent-file.js +15 -0
- package/dist/fluent-file.js.map +1 -0
- package/dist/formatter.d.ts +11 -0
- package/dist/formatter.js +77 -0
- package/dist/formatter.js.map +1 -0
- package/dist/fs.d.ts +174 -0
- package/dist/fs.js +313 -0
- package/dist/fs.js.map +1 -0
- package/dist/guid.d.ts +2 -0
- package/dist/{GUID.js → guid.js} +3 -6
- package/dist/guid.js.map +1 -0
- package/dist/index.d.ts +19 -5
- package/dist/index.js +19 -5
- package/dist/index.js.map +1 -1
- package/dist/json.d.ts +5 -0
- package/dist/json.js +43 -0
- package/dist/json.js.map +1 -0
- package/dist/keys-registry.d.ts +64 -0
- package/dist/keys-registry.js +339 -0
- package/dist/keys-registry.js.map +1 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.js +17 -0
- package/dist/logger.js.map +1 -0
- package/dist/now-config.d.ts +348 -0
- package/dist/now-config.js +283 -0
- package/dist/now-config.js.map +1 -0
- package/dist/path.d.ts +3 -0
- package/dist/path.js +12 -0
- package/dist/path.js.map +1 -0
- package/dist/plugins/cache.d.ts +20 -0
- package/dist/plugins/cache.js +46 -0
- package/dist/plugins/cache.js.map +1 -0
- package/dist/plugins/context.d.ts +85 -0
- package/dist/plugins/{Context.js → context.js} +1 -1
- package/dist/plugins/context.js.map +1 -0
- package/dist/plugins/database.d.ts +27 -0
- package/dist/plugins/database.js +102 -0
- package/dist/plugins/database.js.map +1 -0
- package/dist/plugins/file.d.ts +10 -0
- package/dist/plugins/{behaviors/Arranger.js → file.js} +1 -1
- package/dist/plugins/file.js.map +1 -0
- package/dist/plugins/index.d.ts +9 -5
- package/dist/plugins/index.js +9 -6
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/plugin.d.ts +478 -0
- package/dist/plugins/plugin.js +533 -0
- package/dist/plugins/plugin.js.map +1 -0
- package/dist/plugins/product.d.ts +15 -0
- package/dist/plugins/product.js +38 -0
- package/dist/plugins/product.js.map +1 -0
- package/dist/plugins/project.d.ts +25 -0
- package/dist/plugins/{behaviors/Generator.js → project.js} +1 -1
- package/dist/plugins/project.js.map +1 -0
- package/dist/plugins/shape.d.ts +424 -0
- package/dist/plugins/shape.js +1181 -0
- package/dist/plugins/shape.js.map +1 -0
- package/dist/plugins/time.d.ts +12 -0
- package/dist/plugins/time.js +84 -0
- package/dist/plugins/time.js.map +1 -0
- package/dist/plugins/usage.d.ts +11 -0
- package/dist/plugins/usage.js +26 -0
- package/dist/plugins/usage.js.map +1 -0
- package/dist/prettier/config-loader.d.ts +13 -0
- package/dist/prettier/config-loader.js +105 -0
- package/dist/prettier/config-loader.js.map +1 -0
- package/dist/telemetry/index.d.ts +25 -0
- package/dist/telemetry/index.js +18 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/typescript.d.ts +293 -0
- package/dist/typescript.js +454 -0
- package/dist/typescript.js.map +1 -0
- package/dist/util/get-file-type.d.ts +2 -0
- package/dist/util/get-file-type.js +13 -0
- package/dist/util/get-file-type.js.map +1 -0
- package/dist/util/index.d.ts +2 -6
- package/dist/util/index.js +2 -6
- package/dist/util/index.js.map +1 -1
- package/dist/util/{Scope.js → is-sn-scope.js} +1 -1
- package/dist/util/is-sn-scope.js.map +1 -0
- package/dist/xml.d.ts +24 -0
- package/dist/xml.js +71 -0
- package/dist/xml.js.map +1 -0
- package/now.config.schema.json +336 -0
- package/package.json +22 -12
- package/src/app.ts +33 -0
- package/src/compiler.ts +384 -0
- package/src/compression.ts +93 -0
- package/src/crypto.ts +5 -0
- package/src/diagnostic.ts +108 -0
- package/src/{plugins/Diagnostic.ts → fluent-diagnostic.ts} +3 -10
- package/src/fluent-directive.ts +63 -0
- package/src/fluent-file.ts +13 -0
- package/src/formatter.ts +58 -0
- package/src/fs.ts +438 -0
- package/src/{GUID.ts → guid.ts} +2 -6
- package/src/index.ts +19 -5
- package/src/json.ts +20 -0
- package/src/keys-registry.ts +384 -0
- package/src/logger.ts +20 -0
- package/src/now-config.ts +337 -0
- package/src/path.ts +9 -0
- package/src/plugins/cache.ts +45 -0
- package/src/plugins/context.ts +93 -0
- package/src/plugins/database.ts +121 -0
- package/src/plugins/file.ts +19 -0
- package/src/plugins/index.ts +9 -5
- package/src/plugins/plugin.ts +995 -0
- package/src/plugins/product.ts +44 -0
- package/src/plugins/project.ts +39 -0
- package/src/plugins/shape.ts +1532 -0
- package/src/plugins/time.ts +108 -0
- package/src/plugins/usage.ts +26 -0
- package/src/prettier/config-loader.ts +130 -0
- package/src/telemetry/index.ts +27 -0
- package/src/typescript.ts +502 -0
- package/src/util/get-file-type.ts +11 -0
- package/src/util/index.ts +2 -6
- package/src/xml.ts +86 -0
- package/dist/GUID.d.ts +0 -2
- package/dist/GUID.js.map +0 -1
- package/dist/IncludePaths.d.ts +0 -25
- package/dist/IncludePaths.js +0 -97
- package/dist/IncludePaths.js.map +0 -1
- package/dist/Keys.d.ts +0 -32
- package/dist/Keys.js +0 -245
- package/dist/Keys.js.map +0 -1
- package/dist/TypeScript.d.ts +0 -5
- package/dist/TypeScript.js +0 -58
- package/dist/TypeScript.js.map +0 -1
- package/dist/XML.d.ts +0 -32
- package/dist/XML.js +0 -77
- package/dist/XML.js.map +0 -1
- package/dist/plugins/Context.d.ts +0 -190
- package/dist/plugins/Context.js.map +0 -1
- package/dist/plugins/Diagnostic.js +0 -28
- package/dist/plugins/Diagnostic.js.map +0 -1
- package/dist/plugins/Plugin.d.ts +0 -175
- package/dist/plugins/Plugin.js +0 -15
- package/dist/plugins/Plugin.js.map +0 -1
- package/dist/plugins/behaviors/Arranger.d.ts +0 -26
- package/dist/plugins/behaviors/Arranger.js.map +0 -1
- package/dist/plugins/behaviors/Composer.d.ts +0 -102
- package/dist/plugins/behaviors/Composer.js +0 -15
- package/dist/plugins/behaviors/Composer.js.map +0 -1
- package/dist/plugins/behaviors/Diagnostics.d.ts +0 -7
- package/dist/plugins/behaviors/Diagnostics.js +0 -3
- package/dist/plugins/behaviors/Diagnostics.js.map +0 -1
- package/dist/plugins/behaviors/Generator.d.ts +0 -21
- package/dist/plugins/behaviors/Generator.js.map +0 -1
- package/dist/plugins/behaviors/OwnedTables.d.ts +0 -6
- package/dist/plugins/behaviors/OwnedTables.js +0 -3
- package/dist/plugins/behaviors/OwnedTables.js.map +0 -1
- package/dist/plugins/behaviors/PostProcessor.d.ts +0 -5
- package/dist/plugins/behaviors/PostProcessor.js +0 -3
- package/dist/plugins/behaviors/PostProcessor.js.map +0 -1
- package/dist/plugins/behaviors/Serializer.d.ts +0 -30
- package/dist/plugins/behaviors/Serializer.js +0 -3
- package/dist/plugins/behaviors/Serializer.js.map +0 -1
- package/dist/plugins/behaviors/Transformer.d.ts +0 -23
- package/dist/plugins/behaviors/Transformer.js +0 -3
- package/dist/plugins/behaviors/Transformer.js.map +0 -1
- package/dist/plugins/behaviors/extractors/Data.d.ts +0 -119
- package/dist/plugins/behaviors/extractors/Data.js +0 -244
- package/dist/plugins/behaviors/extractors/Data.js.map +0 -1
- package/dist/plugins/behaviors/extractors/Extractors.d.ts +0 -63
- package/dist/plugins/behaviors/extractors/Extractors.js +0 -3
- package/dist/plugins/behaviors/extractors/Extractors.js.map +0 -1
- package/dist/plugins/behaviors/extractors/index.d.ts +0 -2
- package/dist/plugins/behaviors/extractors/index.js +0 -19
- package/dist/plugins/behaviors/extractors/index.js.map +0 -1
- package/dist/plugins/behaviors/index.d.ts +0 -9
- package/dist/plugins/behaviors/index.js +0 -26
- package/dist/plugins/behaviors/index.js.map +0 -1
- package/dist/plugins/util/CallExpression.d.ts +0 -5
- package/dist/plugins/util/CallExpression.js +0 -88
- package/dist/plugins/util/CallExpression.js.map +0 -1
- package/dist/plugins/util/CodeTransformation.d.ts +0 -95
- package/dist/plugins/util/CodeTransformation.js +0 -624
- package/dist/plugins/util/CodeTransformation.js.map +0 -1
- package/dist/plugins/util/ObjectLiteral.d.ts +0 -9
- package/dist/plugins/util/ObjectLiteral.js +0 -37
- package/dist/plugins/util/ObjectLiteral.js.map +0 -1
- package/dist/plugins/util/index.d.ts +0 -3
- package/dist/plugins/util/index.js +0 -20
- package/dist/plugins/util/index.js.map +0 -1
- package/dist/util/Debug.d.ts +0 -4
- package/dist/util/Debug.js +0 -20
- package/dist/util/Debug.js.map +0 -1
- package/dist/util/Directive.d.ts +0 -16
- package/dist/util/Directive.js +0 -107
- package/dist/util/Directive.js.map +0 -1
- package/dist/util/RuntimeTableSchema.d.ts +0 -5
- package/dist/util/RuntimeTableSchema.js +0 -58
- package/dist/util/RuntimeTableSchema.js.map +0 -1
- package/dist/util/Scope.js.map +0 -1
- package/dist/util/Util.d.ts +0 -1
- package/dist/util/Util.js +0 -12
- package/dist/util/Util.js.map +0 -1
- package/dist/util/XMLUploadParser.d.ts +0 -22
- package/dist/util/XMLUploadParser.js +0 -67
- package/dist/util/XMLUploadParser.js.map +0 -1
- package/src/IncludePaths.ts +0 -122
- package/src/Keys.ts +0 -274
- package/src/TypeScript.ts +0 -65
- package/src/XML.ts +0 -92
- package/src/plugins/Context.ts +0 -239
- package/src/plugins/Plugin.ts +0 -278
- package/src/plugins/behaviors/Arranger.ts +0 -42
- package/src/plugins/behaviors/Composer.ts +0 -125
- package/src/plugins/behaviors/Diagnostics.ts +0 -12
- package/src/plugins/behaviors/Generator.ts +0 -31
- package/src/plugins/behaviors/OwnedTables.ts +0 -5
- package/src/plugins/behaviors/PostProcessor.ts +0 -6
- package/src/plugins/behaviors/Serializer.ts +0 -40
- package/src/plugins/behaviors/Transformer.ts +0 -32
- package/src/plugins/behaviors/extractors/Data.ts +0 -332
- package/src/plugins/behaviors/extractors/Extractors.ts +0 -73
- package/src/plugins/behaviors/extractors/index.ts +0 -2
- package/src/plugins/behaviors/index.ts +0 -9
- package/src/plugins/util/CallExpression.ts +0 -110
- package/src/plugins/util/CodeTransformation.ts +0 -731
- package/src/plugins/util/ObjectLiteral.ts +0 -37
- package/src/plugins/util/index.ts +0 -3
- package/src/util/Debug.ts +0 -24
- package/src/util/Directive.ts +0 -123
- package/src/util/RuntimeTableSchema.ts +0 -44
- package/src/util/Util.ts +0 -7
- package/src/util/XMLUploadParser.ts +0 -90
- /package/dist/util/{Scope.d.ts → is-sn-scope.d.ts} +0 -0
- /package/src/util/{Scope.ts → is-sn-scope.ts} +0 -0
|
@@ -0,0 +1,1532 @@
|
|
|
1
|
+
import { type Source, Product } from './product'
|
|
2
|
+
import { path } from '../path'
|
|
3
|
+
import { ts } from '../typescript'
|
|
4
|
+
import type { Plugin } from './plugin'
|
|
5
|
+
import type { InstallCategory } from './file'
|
|
6
|
+
import { JSON5 } from '../json'
|
|
7
|
+
import * as _ from 'lodash'
|
|
8
|
+
|
|
9
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypeScript is annoying about function arguments. There's not any better options here.
|
|
10
|
+
export type ShapeClass<P extends Shape = Shape> = (abstract new (...args: any[]) => P) & { prototype: P }
|
|
11
|
+
|
|
12
|
+
export abstract class Shape<T = unknown> extends Product {
|
|
13
|
+
constructor({ source }: { source: Source }) {
|
|
14
|
+
super(source)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Instantiate an appropriate shape from the given value. If the value is already
|
|
19
|
+
* a shape, it will be returned as-is.
|
|
20
|
+
*/
|
|
21
|
+
static from(source: Source, value: unknown): Shape {
|
|
22
|
+
if (value instanceof Shape) {
|
|
23
|
+
return value
|
|
24
|
+
} else if (typeof value === 'string') {
|
|
25
|
+
return new StringLiteralShape({ source, literalText: value })
|
|
26
|
+
} else if (typeof value === 'number') {
|
|
27
|
+
return new NumberShape({ source, value })
|
|
28
|
+
} else if (typeof value === 'boolean') {
|
|
29
|
+
return new BooleanShape({ source, value })
|
|
30
|
+
} else if (value === undefined || value === null) {
|
|
31
|
+
return new UndefinedShape({ source })
|
|
32
|
+
} else if (Array.isArray(value)) {
|
|
33
|
+
return new ArrayShape({ source, elements: value })
|
|
34
|
+
} else if (typeof value === 'object') {
|
|
35
|
+
return new ObjectShape({ source, properties: value })
|
|
36
|
+
} else {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Tried to create shape from invalid value (type: ${typeof value}): ${JSON5.stringify(value)}`
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Instantiate a NoOpShape which has no value and will not generate or update
|
|
45
|
+
* any code. Useful for shapes that are not supported for bidirectional sync.
|
|
46
|
+
*/
|
|
47
|
+
static noOp(source: Source): NoOpShape {
|
|
48
|
+
return new NoOpShape(source)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Gets the value of the shape.
|
|
53
|
+
*/
|
|
54
|
+
getValue(): T {
|
|
55
|
+
return Symbol(this.getKind()) as T
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
equals(value: unknown): boolean {
|
|
59
|
+
value = value instanceof Shape ? value.getValue() : value
|
|
60
|
+
return value === this.getValue()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
pipe<T>(pipeFn: (shape: this) => T): T {
|
|
64
|
+
return pipeFn(this)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
isResolvable(): this is ResolvableShape {
|
|
68
|
+
return this.is(ResolvableShape)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
isIdentifier(): this is IdentifierShape {
|
|
72
|
+
return this.is(IdentifierShape)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
isString(): this is StringShape {
|
|
76
|
+
return this.is(StringShape)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
isStringLiteral(): this is StringLiteralShape {
|
|
80
|
+
return this.is(StringLiteralShape)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
isNumber(): this is NumberShape {
|
|
84
|
+
return this.is(NumberShape)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
isBoolean(): this is BooleanShape {
|
|
88
|
+
return this.is(BooleanShape)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
isNoOp(): this is NoOpShape {
|
|
92
|
+
return this.is(NoOpShape)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
isUnresolved(): this is UnresolvedShape {
|
|
96
|
+
return this.is(UnresolvedShape)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
isUndefined(): this is UndefinedShape {
|
|
100
|
+
return this.is(UndefinedShape)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
isDefined(): this is this {
|
|
104
|
+
return !this.isUndefined() && !this.isUnresolved() && !this.isNoOp()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
isArray(): this is ArrayShape {
|
|
108
|
+
return this.is(ArrayShape)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
isObject(): this is ObjectShape {
|
|
112
|
+
return this.is(ObjectShape)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
isRecord(): this is Record {
|
|
116
|
+
return this.is(Record)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
isRecordId(): this is RecordId {
|
|
120
|
+
return this.is(RecordId)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
is<const T extends [Shape, Shape, ...Shape[]]>(
|
|
124
|
+
shapeClasses: { [I in keyof T]: ShapeClass<T[I]> },
|
|
125
|
+
failureMessage?: string
|
|
126
|
+
): this is T[number]
|
|
127
|
+
is<const S extends Shape>(shapeClass: ShapeClass<S>, failureMessage?: string): this is S
|
|
128
|
+
is(shapeClass: ShapeClass | ShapeClass[]): boolean {
|
|
129
|
+
for (const c of Array.isArray(shapeClass) ? shapeClass : [shapeClass]) {
|
|
130
|
+
if (this instanceof c) {
|
|
131
|
+
return true
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
ifResolvable(): ResolvableShape | undefined {
|
|
139
|
+
return this.if(ResolvableShape)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
ifIdentifier(): IdentifierShape | undefined {
|
|
143
|
+
return this.if(IdentifierShape)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
ifString(): StringShape | undefined {
|
|
147
|
+
return this.if(StringShape)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
ifStringLiteral(): StringLiteralShape | undefined {
|
|
151
|
+
return this.if(StringLiteralShape)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
ifNumber(): NumberShape | undefined {
|
|
155
|
+
return this.if(NumberShape)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
ifBoolean(): BooleanShape | undefined {
|
|
159
|
+
return this.if(BooleanShape)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
ifUnresolved(): UnresolvedShape | undefined {
|
|
163
|
+
return this.if(UnresolvedShape)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
ifUndefined(): UndefinedShape | undefined {
|
|
167
|
+
return this.if(UndefinedShape)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
ifDefined(): this | undefined {
|
|
171
|
+
return this.isDefined() ? this : undefined
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
ifArray(): ArrayShape | undefined {
|
|
175
|
+
return this.if(ArrayShape)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
ifObject(): ObjectShape | undefined {
|
|
179
|
+
return this.if(ObjectShape)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
ifRecord(): Record | undefined {
|
|
183
|
+
return this.if(Record)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
ifRecordId(): RecordId | undefined {
|
|
187
|
+
return this.if(RecordId)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if<const T extends [Shape, Shape, ...Shape[]]>(
|
|
191
|
+
shapeClasses: { [I in keyof T]: ShapeClass<T[I]> },
|
|
192
|
+
failureMessage?: string
|
|
193
|
+
): T[number] | undefined
|
|
194
|
+
if<const S extends Shape>(shapeClass: ShapeClass<S>, failureMessage?: string): S | undefined
|
|
195
|
+
if(shapeClass: ShapeClass | ShapeClass[]): Shape | undefined {
|
|
196
|
+
return this.is(shapeClass as any) ? this : undefined
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
asResolvable(failureMessage?: string): ResolvableShape {
|
|
200
|
+
return this.as(ResolvableShape, failureMessage)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
asIdentifier(failureMessage?: string): IdentifierShape {
|
|
204
|
+
return this.as(IdentifierShape, failureMessage)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
asString(failureMessage?: string): StringShape {
|
|
208
|
+
return this.as(StringShape, failureMessage)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
asStringLiteral(failureMessage?: string): StringLiteralShape {
|
|
212
|
+
return this.as(StringLiteralShape, failureMessage)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
asNumber(failureMessage?: string): NumberShape {
|
|
216
|
+
return this.as(NumberShape, failureMessage)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
asBoolean(failureMessage?: string): BooleanShape {
|
|
220
|
+
return this.as(BooleanShape, failureMessage)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
asUnresolved(failureMessage?: string): UnresolvedShape {
|
|
224
|
+
return this.as(UnresolvedShape, failureMessage)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
asUndefined(failureMessage?: string): UndefinedShape {
|
|
228
|
+
return this.as(UndefinedShape, failureMessage)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
asArray(failureMessage?: string): ArrayShape {
|
|
232
|
+
return this.as(ArrayShape, failureMessage)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
asObject(failureMessage?: string): ObjectShape {
|
|
236
|
+
return this.as(ObjectShape, failureMessage)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
asRecord(failureMessage?: string): Record {
|
|
240
|
+
return this.as(Record, failureMessage)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
asRecordId(failureMessage?: string): RecordId {
|
|
244
|
+
return this.as(RecordId, failureMessage)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
as<const T extends [Shape, Shape, ...Shape[]]>(
|
|
248
|
+
shapeClasses: { [I in keyof T]: ShapeClass<T[I]> },
|
|
249
|
+
failureMessage?: string
|
|
250
|
+
): T[number]
|
|
251
|
+
as<const S extends Shape>(shapeClass: ShapeClass<S>, failureMessage?: string): S
|
|
252
|
+
as(shapeClass: ShapeClass | ShapeClass[], failureMessage?: string): Shape {
|
|
253
|
+
for (const c of Array.isArray(shapeClass) ? shapeClass : [shapeClass]) {
|
|
254
|
+
if (this.is(c)) {
|
|
255
|
+
return this
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
throw new Error(
|
|
260
|
+
failureMessage ??
|
|
261
|
+
`Failed to cast ${this.getKind()} to ${Array.isArray(shapeClass) ? `any of the requested shapes: ${shapeClass.map((c) => c.name).join(', ')}` : shapeClass.name}`
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
override toString(): StringShape {
|
|
266
|
+
if (this.isString()) {
|
|
267
|
+
return this
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return Shape.from(this, String(this.getValue())).asString()
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
toNumber(): NumberShape {
|
|
274
|
+
if (this.isNumber()) {
|
|
275
|
+
return this
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const value = this.getValue()
|
|
279
|
+
const coerced = Number(value)
|
|
280
|
+
|
|
281
|
+
//check for empty string since Number('') returns 0
|
|
282
|
+
if (isNaN(coerced) || (typeof value === 'string' && !value.trim())) {
|
|
283
|
+
throw new Error(`Cannot coerce value to number: ${value}`)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return new NumberShape({ source: this.getSource(), value: coerced })
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
toBoolean(): BooleanShape {
|
|
290
|
+
if (this.isBoolean()) {
|
|
291
|
+
return this
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const value = this.getValue()
|
|
295
|
+
if (typeof value !== 'boolean') {
|
|
296
|
+
throw new Error(`Cannot coerce value to boolean: ${value}`)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return new BooleanShape({ source: this, value })
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
toRecordId(): RecordId {
|
|
303
|
+
if (this.isRecordId()) {
|
|
304
|
+
return this
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
throw new Error(`Coercion from ${this.getKind()} to record ID is not supported`)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
getKind(): string {
|
|
311
|
+
return this.constructor.name
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
getDescription(): string {
|
|
315
|
+
return this.getKind()
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
getCode(): string {
|
|
319
|
+
const value = this.getValue()
|
|
320
|
+
if (typeof value === 'symbol') {
|
|
321
|
+
throw new Error(
|
|
322
|
+
`Shape's value is a symbol which is not supported by the default code generation behavior: ${this.getDescription()}`
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return JSON5.stringify(this.getValue(), { space: 1, quote: "'" })
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export class NoOpShape extends Shape<never> {
|
|
331
|
+
constructor(source: Source) {
|
|
332
|
+
super({ source })
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
override getValue(): never {
|
|
336
|
+
throw new Error('Cannot get a value from a no-op shape')
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export class UnresolvedShape extends Shape<symbol> {
|
|
341
|
+
public static readonly CODE = 'Now.UNRESOLVED'
|
|
342
|
+
public static readonly VALUE: unique symbol = Symbol(UnresolvedShape.CODE)
|
|
343
|
+
|
|
344
|
+
override getValue() {
|
|
345
|
+
return UnresolvedShape.VALUE
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
override getCode(): string {
|
|
349
|
+
return UnresolvedShape.CODE
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export class UndefinedShape extends Shape<undefined> {
|
|
354
|
+
constructor({ source }: { source: Source }) {
|
|
355
|
+
super({ source })
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
override getValue(): undefined {
|
|
359
|
+
return undefined
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
override equals(other: unknown): boolean {
|
|
363
|
+
return Shape.from(this, other).isUndefined()
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
override toString(): StringShape {
|
|
367
|
+
return Shape.from(this, '').asString()
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export class DeletedShape extends UndefinedShape {}
|
|
372
|
+
|
|
373
|
+
export abstract class ResolvableShape extends Shape {
|
|
374
|
+
abstract resolve(deep?: boolean): Shape
|
|
375
|
+
|
|
376
|
+
override equals(other: unknown): boolean {
|
|
377
|
+
return this.resolve(true).equals(other)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
override getValue(): unknown {
|
|
381
|
+
return this.resolve(true).getValue()
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
override toString(): StringShape {
|
|
385
|
+
return this.resolve(true).toString()
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
override toNumber(): NumberShape {
|
|
389
|
+
return this.resolve(true).toNumber()
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
override toBoolean(): BooleanShape {
|
|
393
|
+
return this.resolve(true).toBoolean()
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
override toRecordId(): RecordId {
|
|
397
|
+
return this.resolve(true).toRecordId()
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export class IdentifierShape extends ResolvableShape {
|
|
402
|
+
private readonly name: string
|
|
403
|
+
private readonly value: Shape
|
|
404
|
+
|
|
405
|
+
constructor({
|
|
406
|
+
source,
|
|
407
|
+
name,
|
|
408
|
+
value = new UnresolvedShape({ source }),
|
|
409
|
+
}: {
|
|
410
|
+
source: Source
|
|
411
|
+
name: string
|
|
412
|
+
value?: unknown
|
|
413
|
+
}) {
|
|
414
|
+
super({ source })
|
|
415
|
+
this.name = name
|
|
416
|
+
this.value = Shape.from(source, value)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
getName(): string {
|
|
420
|
+
return this.name
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
override resolve(deep = true): Shape {
|
|
424
|
+
return deep && this.value.isResolvable() ? this.value.resolve(true) : this.value
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
override getCode(): string {
|
|
428
|
+
return this.getName()
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
override equals(other: unknown): boolean {
|
|
432
|
+
return other instanceof IdentifierShape ? this.name === other.name && super.equals(other) : super.equals(other)
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
type PropertyAccessElements = readonly [IdentifierShape, IdentifierShape, ...IdentifierShape[]]
|
|
437
|
+
|
|
438
|
+
export class PropertyAccessShape extends ResolvableShape {
|
|
439
|
+
private readonly elements: PropertyAccessElements
|
|
440
|
+
|
|
441
|
+
constructor({ source, elements }: { source: Source; elements: readonly [unknown, unknown, ...unknown[]] }) {
|
|
442
|
+
super({ source })
|
|
443
|
+
this.elements = elements.map((e) =>
|
|
444
|
+
typeof e === 'string' ? new IdentifierShape({ source, name: e }) : Shape.from(source, e).asIdentifier()
|
|
445
|
+
) as unknown as PropertyAccessElements // Annoying cast needed because `map()` doesn't respect tuples
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
getElements(): PropertyAccessElements {
|
|
449
|
+
return this.elements
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
getElement(index: number): IdentifierShape {
|
|
453
|
+
const element = this.elements[index]
|
|
454
|
+
if (!element) {
|
|
455
|
+
throw new Error(`Property access shape does not have an element at index ${index}: ${this.getCode()}`)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return element
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
getFirstElement(): IdentifierShape {
|
|
462
|
+
return this.getElement(0)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
getLastElement(): IdentifierShape {
|
|
466
|
+
return this.getElement(this.elements.length - 1)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
override resolve(deep = true) {
|
|
470
|
+
return this.getLastElement().resolve(deep)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
override getCode(): string {
|
|
474
|
+
return this.elements.map((e) => e.getCode()).join('.')
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
type StringContentType = 'plain' | 'cdata'
|
|
479
|
+
|
|
480
|
+
export abstract class StringShape extends Shape<string> {
|
|
481
|
+
private contentType: StringContentType = 'plain'
|
|
482
|
+
|
|
483
|
+
startsWith(substring: string): boolean {
|
|
484
|
+
return this.getValue().startsWith(substring)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
endsWith(substring: string): boolean {
|
|
488
|
+
return this.getValue().endsWith(substring)
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
includes(substring: string): boolean {
|
|
492
|
+
return this.getValue().includes(substring)
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
split(separator: string): string[] {
|
|
496
|
+
return this.getValue().split(separator)
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
isEmpty(): boolean {
|
|
500
|
+
return this.getValue().length === 0
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
ifNotEmpty(): StringShape | undefined {
|
|
504
|
+
return this.isEmpty() ? undefined : this
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
replace(regex: string | RegExp, replacement: string): StringShape {
|
|
508
|
+
return Shape.from(this, this.getValue().replace(regex, replacement)).asString()
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
replaceAll(regex: string | RegExp, replacement: string): StringShape {
|
|
512
|
+
return Shape.from(this, this.getValue().replaceAll(regex, replacement)).asString()
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
parseJson(): ObjectShape | ArrayShape {
|
|
516
|
+
const value = this.getValue()
|
|
517
|
+
const result = JSON5.parse<unknown>(value)
|
|
518
|
+
if (!result || (!Array.isArray(result) && typeof result !== 'object')) {
|
|
519
|
+
throw new Error(`Failed to parse string shape as JSON: ${value}`)
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return Shape.from(this, result).as([
|
|
523
|
+
// I have absolutely no idea why these casts are needed in this file and
|
|
524
|
+
// nowhere else. Seems like a probable TypeScript bug.
|
|
525
|
+
ObjectShape as ShapeClass<ObjectShape>,
|
|
526
|
+
ArrayShape as ShapeClass<ArrayShape>,
|
|
527
|
+
])
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
withContentType(contentType: StringContentType): this {
|
|
531
|
+
this.contentType = contentType
|
|
532
|
+
return this
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
getContentType(): StringContentType {
|
|
536
|
+
return this.contentType
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
override toBoolean(): BooleanShape {
|
|
540
|
+
const value = this.getValue()
|
|
541
|
+
const trimmed = value.trim().toLowerCase()
|
|
542
|
+
|
|
543
|
+
if (trimmed === '') {
|
|
544
|
+
return new BooleanShape({ source: this, value: false })
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (!/^(true|false)$/.test(trimmed)) {
|
|
548
|
+
throw new Error(`Cannot coerce string "${value}" to boolean`)
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return new BooleanShape({ source: this, value: trimmed === 'true' })
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
static escapeSingleQuotes(str: string): string {
|
|
555
|
+
return str.replaceAll("'", "\\'")
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
static escapeBackticks(str: string): string {
|
|
559
|
+
return str.replace(/[`\\]|\${/g, (char) => {
|
|
560
|
+
switch (char) {
|
|
561
|
+
case '`':
|
|
562
|
+
return '\\`'
|
|
563
|
+
case '\\':
|
|
564
|
+
return '\\' + char
|
|
565
|
+
case '${':
|
|
566
|
+
return '\\${'
|
|
567
|
+
default:
|
|
568
|
+
return char
|
|
569
|
+
}
|
|
570
|
+
})
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
static escapeCdataTags(str: string): string {
|
|
574
|
+
return str.replace(/]]>/g, ']]]]><![CDATA[>')
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
export class StringLiteralShape extends StringShape {
|
|
579
|
+
private readonly literalText: string
|
|
580
|
+
|
|
581
|
+
constructor({ source, literalText }: { source: Source; literalText: string | Shape }) {
|
|
582
|
+
super({ source })
|
|
583
|
+
this.literalText = typeof literalText === 'string' ? literalText : literalText.toString().getValue()
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
override getValue(): string {
|
|
587
|
+
return this.literalText
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
override getCode(): string {
|
|
591
|
+
return this.includes('\n')
|
|
592
|
+
? `\`${StringShape.escapeBackticks(this.literalText)}\``
|
|
593
|
+
: `'${StringShape.escapeSingleQuotes(this.literalText)}'`
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
type TemplateSubstitutionValue = Shape | string | boolean | number
|
|
598
|
+
export type TemplateSubstitutions =
|
|
599
|
+
| ((expression: Shape) => TemplateSubstitutionValue)
|
|
600
|
+
| globalThis.Record<number, ((expression: Shape) => TemplateSubstitutionValue) | TemplateSubstitutionValue>
|
|
601
|
+
|
|
602
|
+
export class TemplateSpanShape extends StringShape {
|
|
603
|
+
private readonly literalText: string
|
|
604
|
+
private readonly expression: Shape
|
|
605
|
+
|
|
606
|
+
constructor({
|
|
607
|
+
source,
|
|
608
|
+
literalText,
|
|
609
|
+
expression,
|
|
610
|
+
}: {
|
|
611
|
+
source: Source
|
|
612
|
+
literalText: string | Shape
|
|
613
|
+
expression: Shape
|
|
614
|
+
}) {
|
|
615
|
+
super({ source })
|
|
616
|
+
this.literalText = Shape.from(source, literalText).asString().getValue()
|
|
617
|
+
this.expression = expression
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
getLiteralText(): string {
|
|
621
|
+
return this.literalText
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
getExpression(): Shape {
|
|
625
|
+
return this.expression
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
override getValue(
|
|
629
|
+
substitution?: ((expression: Shape) => TemplateSubstitutionValue) | TemplateSubstitutionValue
|
|
630
|
+
): string {
|
|
631
|
+
return `${substitution instanceof Shape ? substitution.getValue() : typeof substitution === 'function' ? substitution(this.expression) : (substitution ?? this.expression.toString().getValue())}${this.literalText}`
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
override getCode(): string {
|
|
635
|
+
return `$\{${this.expression.getCode()}}${StringShape.escapeBackticks(this.literalText)}`
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
export class TemplateExpressionShape extends StringShape {
|
|
640
|
+
private readonly literalText: string
|
|
641
|
+
private readonly spans: TemplateSpanShape[]
|
|
642
|
+
|
|
643
|
+
constructor({
|
|
644
|
+
source,
|
|
645
|
+
literalText,
|
|
646
|
+
spans,
|
|
647
|
+
}: {
|
|
648
|
+
source: Source
|
|
649
|
+
literalText: string | Shape
|
|
650
|
+
spans?: TemplateSpanShape[]
|
|
651
|
+
}) {
|
|
652
|
+
super({ source })
|
|
653
|
+
this.literalText = Shape.from(source, literalText).asString().getValue()
|
|
654
|
+
this.spans = spans ?? []
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
getLiteralText(): string {
|
|
658
|
+
return this.literalText
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
hasSubstitution(): boolean {
|
|
662
|
+
return this.spans.length > 0
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
getSpans(): TemplateSpanShape[] {
|
|
666
|
+
return this.spans
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
override getValue(substitutions?: TemplateSubstitutions): string {
|
|
670
|
+
return `${this.literalText}${this.spans.map((s, i) => s.getValue(typeof substitutions === 'function' ? substitutions : substitutions?.[i])).join('')}`
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
override getCode(): string {
|
|
674
|
+
return `\`${StringShape.escapeBackticks(this.literalText)}${this.spans.map((s) => s.getCode()).join('')}\``
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
export class TaggedTemplateExpressionShape extends StringShape {
|
|
679
|
+
private readonly tag: string
|
|
680
|
+
private readonly template: TemplateExpressionShape
|
|
681
|
+
|
|
682
|
+
constructor({ source, tag, template }: { source: Source; tag: string | Shape; template: Shape }) {
|
|
683
|
+
super({ source })
|
|
684
|
+
this.tag = Shape.from(source, tag).asString().getValue()
|
|
685
|
+
this.template = template.as(TemplateExpressionShape, 'Template must be a template expression shape')
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
getTag(): string {
|
|
689
|
+
return this.tag
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
getTemplate(): TemplateExpressionShape {
|
|
693
|
+
return this.template
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
override getValue(substitutions?: TemplateSubstitutions): string {
|
|
697
|
+
return this.template.getValue(substitutions)
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
override getCode(): string {
|
|
701
|
+
return `${this.tag || ''}${this.template.getCode()}`
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
export class NumberShape extends Shape<number> {
|
|
706
|
+
private readonly value: number
|
|
707
|
+
|
|
708
|
+
constructor({ source, value }: { source: Source; value: number }) {
|
|
709
|
+
super({ source })
|
|
710
|
+
this.value = value
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
override getValue(): number {
|
|
714
|
+
return this.value
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
export class BooleanShape extends Shape<boolean> {
|
|
719
|
+
private readonly value: boolean
|
|
720
|
+
|
|
721
|
+
constructor({ source, value }: { source: Source; value: boolean }) {
|
|
722
|
+
super({ source })
|
|
723
|
+
this.value = value
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
override getValue(): boolean {
|
|
727
|
+
return this.value
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
export class ArrayShape extends Shape<unknown[]> {
|
|
732
|
+
private readonly elements: Shape[]
|
|
733
|
+
|
|
734
|
+
constructor({ source, elements }: { source: Source; elements: unknown[] }) {
|
|
735
|
+
super({ source })
|
|
736
|
+
this.elements = elements.map((e) => Shape.from(source, e))
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
override getValue(): unknown[] {
|
|
740
|
+
return this.elements.map((e) => e.getValue())
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
getElements(resolve = true): Shape[] {
|
|
744
|
+
return resolve ? this.elements.map((s) => (s.isResolvable() ? s.resolve(true) : s)) : this.elements
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
getElement(index: number, resolve = true): Shape {
|
|
748
|
+
return this.getElements(resolve)[index] ?? new UndefinedShape({ source: this })
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
hasElement(index: number): boolean {
|
|
752
|
+
return index < this.elements.length
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
map<T>(mapFunction: (element: Shape) => T, resolve = true): T[] {
|
|
756
|
+
return this.getElements(resolve).map(mapFunction)
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
override equals(other: unknown): boolean {
|
|
760
|
+
const otherShape = Shape.from(this, other)
|
|
761
|
+
if (!otherShape.isArray() || this.elements.length !== otherShape.elements.length) {
|
|
762
|
+
return false
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
for (const [i, element] of this.elements.entries()) {
|
|
766
|
+
if (!element.equals(otherShape.getElement(i))) {
|
|
767
|
+
return false
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
return true
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
static override from(source: Source, value: unknown): ArrayShape {
|
|
775
|
+
if (value instanceof ArrayShape) {
|
|
776
|
+
return value
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
value = value instanceof Shape ? value.getValue() : value
|
|
780
|
+
const coerced = Array.isArray(value) ? value : [value]
|
|
781
|
+
return new ArrayShape({ source, elements: coerced })
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
override getCode(): string {
|
|
785
|
+
return `[${this.map((e) => e.getCode(), false).join(', ')}]`
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const MERGE = Symbol('MERGE')
|
|
790
|
+
const coerceTo = (coerceToType: 'string' | 'number' | 'boolean' | 'cdata', value: Shape): unknown => {
|
|
791
|
+
switch (coerceToType) {
|
|
792
|
+
case 'string':
|
|
793
|
+
return value.toString()
|
|
794
|
+
case 'number':
|
|
795
|
+
return value.toNumber()
|
|
796
|
+
case 'cdata':
|
|
797
|
+
return value.toString().withContentType('cdata')
|
|
798
|
+
default:
|
|
799
|
+
return value.toBoolean()
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
class Transform {
|
|
804
|
+
constructor(
|
|
805
|
+
private readonly shape: ObjectShape,
|
|
806
|
+
private readonly resolve = true,
|
|
807
|
+
private readonly _from: [string, ...string[]] | undefined = undefined,
|
|
808
|
+
private readonly _map: ((...from: Shape[]) => unknown) | undefined = undefined,
|
|
809
|
+
private readonly _def: unknown = undefined,
|
|
810
|
+
private readonly _coerce: 'string' | 'number' | 'boolean' | 'cdata' | undefined = undefined
|
|
811
|
+
) {}
|
|
812
|
+
|
|
813
|
+
_(to: string | typeof MERGE) {
|
|
814
|
+
const args = (this._from ?? (to === MERGE ? ([] as string[]) : [to])).map((p) =>
|
|
815
|
+
this.shape.get(p, this.resolve)
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
const val = (() => {
|
|
819
|
+
if (this._map) {
|
|
820
|
+
return this._map(...args)
|
|
821
|
+
} else if (this._from) {
|
|
822
|
+
return this._from.length === 1
|
|
823
|
+
? this.shape.get(this._from[0], this.resolve)
|
|
824
|
+
: this.shape.pick(this._from, { resolve: this.resolve })
|
|
825
|
+
} else if (to !== MERGE) {
|
|
826
|
+
return this.shape.get(to, this.resolve)
|
|
827
|
+
} else {
|
|
828
|
+
return this.shape.properties({ resolve: this.resolve })
|
|
829
|
+
}
|
|
830
|
+
})()
|
|
831
|
+
|
|
832
|
+
if (!this._coerce) {
|
|
833
|
+
return {
|
|
834
|
+
def: this._def,
|
|
835
|
+
val,
|
|
836
|
+
}
|
|
837
|
+
} else {
|
|
838
|
+
const valShape = Shape.from(this.shape, val)
|
|
839
|
+
return {
|
|
840
|
+
def: this._def,
|
|
841
|
+
val: valShape.ifUndefined() ?? coerceTo(this._coerce, valShape),
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
from(...properties: [string, ...string[]]) {
|
|
847
|
+
return new Transform(this.shape, this.resolve, properties, this._map, this._def, this._coerce)
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
map(mapFunction: (...from: Shape[]) => unknown) {
|
|
851
|
+
return new Transform(this.shape, this.resolve, this._from, mapFunction, this._def, this._coerce)
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
val(value: unknown) {
|
|
855
|
+
return new Transform(this.shape, this.resolve, this._from, () => value, this._def, this._coerce)
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
def(value: unknown) {
|
|
859
|
+
return new Transform(this.shape, this.resolve, this._from, this._map, value, this._coerce)
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
toCdata() {
|
|
863
|
+
return new Transform(this.shape, this.resolve, this._from, this._map, this._def, 'cdata')
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
toString() {
|
|
867
|
+
return new Transform(this.shape, this.resolve, this._from, this._map, this._def, 'string')
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
toNumber() {
|
|
871
|
+
return new Transform(this.shape, this.resolve, this._from, this._map, this._def, 'number')
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
toBoolean() {
|
|
875
|
+
return new Transform(this.shape, this.resolve, this._from, this._map, this._def, 'boolean')
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function assignWithoutOverwriting(target: NonNullable<object>, source: unknown) {
|
|
880
|
+
if (!source) {
|
|
881
|
+
return
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
for (const [k, v] of Object.entries(source)) {
|
|
885
|
+
if (!(k in target)) {
|
|
886
|
+
target[k] = v
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
type ObjectPropertyOptions = {
|
|
892
|
+
nonDefaultsOnly?: boolean
|
|
893
|
+
resolve?: boolean
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
export class ObjectShape extends Shape<globalThis.Record<string, unknown>> {
|
|
897
|
+
protected readonly setProperties: globalThis.Record<string, Shape>
|
|
898
|
+
private defaultProperties: globalThis.Record<string, Shape>
|
|
899
|
+
private aliasedKeys: globalThis.Record<string, string[]>
|
|
900
|
+
|
|
901
|
+
constructor({ source, properties }: { source: Source; properties: NonNullable<object> }) {
|
|
902
|
+
super({ source })
|
|
903
|
+
if (properties instanceof ObjectShape) {
|
|
904
|
+
this.defaultProperties = properties.defaultProperties
|
|
905
|
+
this.setProperties = properties.setProperties
|
|
906
|
+
this.aliasedKeys = properties.aliasedKeys
|
|
907
|
+
} else {
|
|
908
|
+
this.defaultProperties = {}
|
|
909
|
+
this.aliasedKeys = {}
|
|
910
|
+
this.setProperties = Object.fromEntries(
|
|
911
|
+
Object.entries(properties).map(([k, v]) => [k, Shape.from(source, v)])
|
|
912
|
+
)
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
withAliasedKeys(aliasedKeys: globalThis.Record<string, string[]>): this {
|
|
917
|
+
this.aliasedKeys = aliasedKeys
|
|
918
|
+
return this
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
getAliasedKeys(): globalThis.Record<string, string[]> {
|
|
922
|
+
return this.aliasedKeys
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
findAliasUsed(key: string): string | undefined {
|
|
926
|
+
const aliases = this.getAliases(key)
|
|
927
|
+
const properties = this.properties({ resolve: false })
|
|
928
|
+
return aliases.find((alias) => properties[alias])
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
getAliases(key: string): string[] {
|
|
932
|
+
return this.aliasedKeys[key] ?? []
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
properties({
|
|
936
|
+
nonDefaultsOnly = false,
|
|
937
|
+
resolve = true,
|
|
938
|
+
}: ObjectPropertyOptions = {}): globalThis.Record<string, Shape> {
|
|
939
|
+
if (nonDefaultsOnly) {
|
|
940
|
+
const entries = Object.entries(this.setProperties).filter(
|
|
941
|
+
([k, v]) => v.isDefined() && !v.equals(this.getDefault(k))
|
|
942
|
+
)
|
|
943
|
+
|
|
944
|
+
return Object.fromEntries(
|
|
945
|
+
resolve ? entries.map(([k, v]) => [k, v.isResolvable() ? v.resolve() : v]) : entries
|
|
946
|
+
)
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// This logic may seem odd, but it's written this way to preserve the ordering of
|
|
950
|
+
// the set properties instead of using the ordering of the defaults.
|
|
951
|
+
const entries = [
|
|
952
|
+
...Object.entries(this.setProperties),
|
|
953
|
+
...Object.entries(this.defaultProperties).filter(
|
|
954
|
+
([k]) => !this.setProperties[k] || this.setProperties[k].isUndefined()
|
|
955
|
+
),
|
|
956
|
+
]
|
|
957
|
+
|
|
958
|
+
return Object.fromEntries(resolve ? entries.map(([k, v]) => [k, v.isResolvable() ? v.resolve() : v]) : entries)
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
override getValue(nonDefaultsOnly = false): globalThis.Record<string, unknown> {
|
|
962
|
+
return Object.fromEntries(this.entries({ nonDefaultsOnly }).map(([k, v]) => [k, v.getValue()]))
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
get(propertyOrPath: string | [string, ...string[]], resolve = true): Shape {
|
|
966
|
+
const [property, next, ...rest] = typeof propertyOrPath === 'string' ? [propertyOrPath] : propertyOrPath
|
|
967
|
+
const propertyAliases = [property, ...this.getAliases(property)]
|
|
968
|
+
const properties = this.properties({ resolve })
|
|
969
|
+
const match = propertyAliases.find((p) => properties[p])
|
|
970
|
+
const value = match ? properties[match] : undefined
|
|
971
|
+
return (
|
|
972
|
+
(next ? value?.ifObject()?.get([next, ...rest], resolve) : value) ??
|
|
973
|
+
new UndefinedShape({ source: this.getSource() })
|
|
974
|
+
)
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
getDefault(property: string): Shape {
|
|
978
|
+
return this.defaultProperties[property] ?? new UndefinedShape({ source: this.getSource() })
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
keys(nonDefaultsOnly = false): string[] {
|
|
982
|
+
return Object.keys(this.properties({ nonDefaultsOnly }))
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
values(options: ObjectPropertyOptions = {}): Shape[] {
|
|
986
|
+
return Object.values(this.properties(options))
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
entries(options: ObjectPropertyOptions = {}): [string, Shape][] {
|
|
990
|
+
return Object.entries(this.properties(options))
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
has(property: string): boolean {
|
|
994
|
+
return property in this.setProperties
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
omit(
|
|
998
|
+
properties: string | [string, ...string[]],
|
|
999
|
+
options: ObjectPropertyOptions = {}
|
|
1000
|
+
): globalThis.Record<string, Shape> {
|
|
1001
|
+
properties = typeof properties === 'string' ? [properties] : properties
|
|
1002
|
+
const regex = new RegExp(`^(${properties.map((p) => p.replaceAll('$', '\\$')).join('|')})$`)
|
|
1003
|
+
return Object.fromEntries(
|
|
1004
|
+
this.entries(options)
|
|
1005
|
+
.filter(([k]) => !regex.test(k))
|
|
1006
|
+
.map(([k, v]) => [k, v])
|
|
1007
|
+
)
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
pick(
|
|
1011
|
+
properties: string | [string, ...string[]],
|
|
1012
|
+
options: ObjectPropertyOptions = {}
|
|
1013
|
+
): globalThis.Record<string, Shape> {
|
|
1014
|
+
properties = typeof properties === 'string' ? [properties] : properties
|
|
1015
|
+
const regex = new RegExp(`^(${properties.join('|')})$`)
|
|
1016
|
+
return Object.fromEntries(
|
|
1017
|
+
this.entries(options)
|
|
1018
|
+
.filter(([k]) => regex.test(k))
|
|
1019
|
+
.map(([k, v]) => [k, v])
|
|
1020
|
+
)
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
private setDefaults(defaults: globalThis.Record<string, unknown>): this {
|
|
1024
|
+
this.defaultProperties = Object.fromEntries(
|
|
1025
|
+
Object.entries(defaults).map(([k, v]) => [k, Shape.from(this.getSource(), v)])
|
|
1026
|
+
)
|
|
1027
|
+
|
|
1028
|
+
for (const [k, v] of Object.entries(this.setProperties)) {
|
|
1029
|
+
if (v.isObject() && this.defaultProperties[k]?.isObject()) {
|
|
1030
|
+
v.setDefaults(this.defaultProperties[k].getValue())
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
return this
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
transform(
|
|
1038
|
+
schema: (utils: { $: Transform; merge: typeof MERGE }) => {
|
|
1039
|
+
[P in string | typeof MERGE]?: Transform
|
|
1040
|
+
},
|
|
1041
|
+
resolve = true
|
|
1042
|
+
) {
|
|
1043
|
+
const properties = {}
|
|
1044
|
+
const defaults = {}
|
|
1045
|
+
|
|
1046
|
+
const definedSchema = schema({ merge: MERGE, $: new Transform(this, resolve) })
|
|
1047
|
+
for (const to of [...Object.keys(definedSchema), MERGE] as const) {
|
|
1048
|
+
if (definedSchema[to]) {
|
|
1049
|
+
const result = definedSchema[to]._(to)
|
|
1050
|
+
if (to === MERGE) {
|
|
1051
|
+
const { val, def } = result
|
|
1052
|
+
if (val && (typeof val !== 'object' || Array.isArray(val))) {
|
|
1053
|
+
throw new Error('Cannot merge non-object value')
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
if (def && (typeof def !== 'object' || Array.isArray(def))) {
|
|
1057
|
+
throw new Error('Cannot merge non-object default value')
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
if (val instanceof ObjectShape) {
|
|
1061
|
+
assignWithoutOverwriting(properties, val.setProperties)
|
|
1062
|
+
assignWithoutOverwriting(defaults, val.defaultProperties)
|
|
1063
|
+
} else {
|
|
1064
|
+
assignWithoutOverwriting(properties, val)
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
if (def instanceof ObjectShape) {
|
|
1068
|
+
assignWithoutOverwriting(defaults, def.setProperties)
|
|
1069
|
+
} else if (def) {
|
|
1070
|
+
assignWithoutOverwriting(defaults, def)
|
|
1071
|
+
}
|
|
1072
|
+
} else {
|
|
1073
|
+
properties[to] = result.val
|
|
1074
|
+
if (result.def !== undefined) {
|
|
1075
|
+
defaults[to] = result.def
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
return new ObjectShape({
|
|
1082
|
+
source: this.getSource(),
|
|
1083
|
+
properties,
|
|
1084
|
+
}).setDefaults(defaults)
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
merge(other: NonNullable<object>): ObjectShape {
|
|
1088
|
+
const mergedProperties = { ...this.setProperties }
|
|
1089
|
+
const mergedDefaults = { ...this.defaultProperties }
|
|
1090
|
+
|
|
1091
|
+
for (const [a, b] of [
|
|
1092
|
+
[mergedProperties, other instanceof ObjectShape ? other.setProperties : other],
|
|
1093
|
+
[mergedDefaults, other instanceof ObjectShape ? other.defaultProperties : {}],
|
|
1094
|
+
] as const) {
|
|
1095
|
+
for (const [key, v] of Object.entries(b)) {
|
|
1096
|
+
const value = Shape.from(this, v)
|
|
1097
|
+
if (!(key in a) && value.isUndefined()) {
|
|
1098
|
+
// Don't add properties that are undefined
|
|
1099
|
+
} else if (a[key]?.equals(value)) {
|
|
1100
|
+
// Don't overwrite properties that are equal
|
|
1101
|
+
} else if (a[key]?.isObject() && value.isObject()) {
|
|
1102
|
+
a[key].merge(value) // Merge objects recursively
|
|
1103
|
+
} else {
|
|
1104
|
+
a[key] = value
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
return new ObjectShape({
|
|
1110
|
+
source: this.getSource(),
|
|
1111
|
+
properties: mergedProperties,
|
|
1112
|
+
})
|
|
1113
|
+
.setDefaults(mergedDefaults)
|
|
1114
|
+
.setCreator(this.getCreator())
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
override equals(other: unknown): boolean {
|
|
1118
|
+
const otherShape = Shape.from(this, other)
|
|
1119
|
+
if (!otherShape.isObject() || this.keys().length !== otherShape.keys().length) {
|
|
1120
|
+
return false
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
for (const [k, v] of this.entries()) {
|
|
1124
|
+
if (!v.equals(otherShape.get(k))) {
|
|
1125
|
+
return false
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
return true
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
override getCode(): string {
|
|
1133
|
+
return `{\n ${this.entries({ nonDefaultsOnly: true, resolve: false })
|
|
1134
|
+
.map(([k, v]) => `${ObjectShape.quotePropertyNameIfNeeded(k)}: ${v.getCode()}`)
|
|
1135
|
+
.join(',\n ')}\n}`
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
static quotePropertyNameIfNeeded(name: string): string {
|
|
1139
|
+
return /[^\w$]/.test(name) ? `'${name}'` : name
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
export class CallExpressionShape extends Shape {
|
|
1144
|
+
private readonly callee: string
|
|
1145
|
+
private readonly args: ArrayShape
|
|
1146
|
+
|
|
1147
|
+
constructor({ source, callee, args }: { source: Source; callee: string; args: unknown[] }) {
|
|
1148
|
+
super({ source })
|
|
1149
|
+
this.callee = callee
|
|
1150
|
+
this.args = new ArrayShape({ source, elements: args })
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
getCallee(): string {
|
|
1154
|
+
return this.callee
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
getArguments(resolve = true): Shape[] {
|
|
1158
|
+
return this.args.getElements(resolve)
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
getArgument(index: number, resolve = true): Shape {
|
|
1162
|
+
return this.args.getElement(index, resolve)
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
hasArgument(index: number): boolean {
|
|
1166
|
+
return this.args.hasElement(index)
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
override getDescription(): string {
|
|
1170
|
+
return `${this.getKind()} (${this.callee})`
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
override getCode(): string {
|
|
1174
|
+
return `${this.callee}(${this.args.map((a) => a.getCode(), false).join(', ')})`
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
export class SourceFileShape extends StringShape {
|
|
1179
|
+
private readonly path: string
|
|
1180
|
+
private readonly content: string
|
|
1181
|
+
|
|
1182
|
+
constructor({ file }: { file: ts.SourceFile }) {
|
|
1183
|
+
super({ source: file })
|
|
1184
|
+
this.path = file.getFilePath()
|
|
1185
|
+
this.content = file.getFullText()
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
getPath(): string {
|
|
1189
|
+
return this.path
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
getBaseName(): string {
|
|
1193
|
+
return path.basename(this.path)
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
getExtension(): string {
|
|
1197
|
+
return path.extname(this.path)
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
getContent(): string {
|
|
1201
|
+
return this.content
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
override getSource(): ts.SourceFile {
|
|
1205
|
+
return this.getOriginalNode().asKindOrThrow(ts.SyntaxKind.SourceFile)
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
override getValue(): string {
|
|
1209
|
+
return this.getContent()
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
override getCode(): string {
|
|
1213
|
+
return this.getContent()
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
export class ElementAccessExpressionShape extends Shape {
|
|
1218
|
+
private readonly callee: string
|
|
1219
|
+
private readonly arg: StringShape | NumberShape
|
|
1220
|
+
|
|
1221
|
+
constructor({ source, callee, arg }: { source: Source; callee: string; arg: unknown }) {
|
|
1222
|
+
super({ source })
|
|
1223
|
+
this.callee = callee
|
|
1224
|
+
this.arg = Shape.from(source, arg)
|
|
1225
|
+
.pipe((e) => (e.isResolvable() ? e.resolve() : e))
|
|
1226
|
+
.as(
|
|
1227
|
+
[
|
|
1228
|
+
// I have absolutely no idea why these casts are needed in this file and
|
|
1229
|
+
// nowhere else. Seems like a probable TypeScript bug.
|
|
1230
|
+
StringShape as ShapeClass<StringShape>,
|
|
1231
|
+
NumberShape as ShapeClass<NumberShape>,
|
|
1232
|
+
],
|
|
1233
|
+
`Invalid argument to element access expression: ${arg instanceof Shape ? arg.getDescription() : JSON.stringify(arg)}`
|
|
1234
|
+
)
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
getCallee(): string {
|
|
1238
|
+
return this.callee
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
getArg(): StringShape | NumberShape {
|
|
1242
|
+
return this.arg
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
export class VariableStatementShape extends Shape {
|
|
1247
|
+
private readonly variableName: IdentifierShape
|
|
1248
|
+
private readonly initializer: Shape
|
|
1249
|
+
private readonly _isExported: boolean
|
|
1250
|
+
|
|
1251
|
+
constructor({
|
|
1252
|
+
source,
|
|
1253
|
+
variableName,
|
|
1254
|
+
initializer,
|
|
1255
|
+
isExported = false,
|
|
1256
|
+
}: {
|
|
1257
|
+
source: Source
|
|
1258
|
+
variableName: IdentifierShape | string
|
|
1259
|
+
initializer: unknown
|
|
1260
|
+
isExported?: boolean
|
|
1261
|
+
}) {
|
|
1262
|
+
super({ source })
|
|
1263
|
+
this.variableName =
|
|
1264
|
+
variableName instanceof Shape ? variableName : new IdentifierShape({ source, name: variableName })
|
|
1265
|
+
this.initializer = Shape.from(source, initializer)
|
|
1266
|
+
this._isExported = isExported
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
getVariableName(): IdentifierShape {
|
|
1270
|
+
return this.variableName
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
getInitializer(): Shape {
|
|
1274
|
+
return this.initializer
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
isExported(): boolean {
|
|
1278
|
+
return this._isExported
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
override getCode(): string {
|
|
1282
|
+
return `${this.isExported() ? 'export ' : ''}const ${this.getVariableName().getName()} = ${this.getInitializer().getCode()}`
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
function getDefaultCategoryFromTable(table: string): InstallCategory {
|
|
1287
|
+
return table === 'sys_app' ? 'scope' : 'update'
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
function getInstallCategoryFromInstallMethod(installMethod: string): InstallCategory {
|
|
1291
|
+
return installMethod === 'demo' ? 'unload.demo' : 'unload'
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
export function getInstallCategory(source: Source, table: string): InstallCategory {
|
|
1295
|
+
let installCategory: InstallCategory = getDefaultCategoryFromTable(table)
|
|
1296
|
+
if (source instanceof CallExpressionShape) {
|
|
1297
|
+
const installMethod = source.getArgument(0).ifObject()?.get(['$meta', 'installMethod']).ifString()?.getValue()
|
|
1298
|
+
if (installMethod) {
|
|
1299
|
+
installCategory = getInstallCategoryFromInstallMethod(installMethod)
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
return installCategory
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
export type Action = 'INSERT_OR_UPDATE' | 'UPDATE' | 'DELETE' | 'delete_multiple'
|
|
1306
|
+
|
|
1307
|
+
export class Record extends ObjectShape {
|
|
1308
|
+
private readonly id: RecordId
|
|
1309
|
+
private readonly related: Record[] = []
|
|
1310
|
+
private readonly action: Action
|
|
1311
|
+
private readonly installCategory: InstallCategory
|
|
1312
|
+
|
|
1313
|
+
constructor({
|
|
1314
|
+
source,
|
|
1315
|
+
id,
|
|
1316
|
+
properties,
|
|
1317
|
+
action = 'INSERT_OR_UPDATE',
|
|
1318
|
+
installCategory = getDefaultCategoryFromTable(id.getTable()),
|
|
1319
|
+
}: {
|
|
1320
|
+
source: Source
|
|
1321
|
+
id: RecordId
|
|
1322
|
+
properties: NonNullable<object>
|
|
1323
|
+
action?: Action
|
|
1324
|
+
installCategory?: InstallCategory
|
|
1325
|
+
}) {
|
|
1326
|
+
super({ source, properties })
|
|
1327
|
+
this.action = action
|
|
1328
|
+
this.id = id
|
|
1329
|
+
this.installCategory = installCategory
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
static isDeleteAction(action: Action): boolean {
|
|
1333
|
+
return action === 'DELETE' || action === 'delete_multiple'
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
getId(): RecordId {
|
|
1337
|
+
return this.id
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
getInstallCategory(): InstallCategory {
|
|
1341
|
+
return this.installCategory
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
getTable(): string {
|
|
1345
|
+
return this.id.getTable()
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
getAction(): Action {
|
|
1349
|
+
return this.action
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
isDeleted(): boolean {
|
|
1353
|
+
return Record.isDeleteAction(this.getAction())
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
getUpdateName(): string {
|
|
1357
|
+
return this.id.getUpdateName()
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
with(...related: Record[]): this {
|
|
1361
|
+
this.related.length = 0
|
|
1362
|
+
this.related.push(...related)
|
|
1363
|
+
return this
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
flat(): Record[] {
|
|
1367
|
+
return [this, ...this.related.flatMap((r) => r.flat())]
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
getCount(): number {
|
|
1371
|
+
return 1 + this.related.reduce((count, r) => count + r.getCount(), 0)
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
override merge(other: NonNullable<object>): Record {
|
|
1375
|
+
return new Record({
|
|
1376
|
+
source: this.getSource(),
|
|
1377
|
+
id: this.getId(),
|
|
1378
|
+
action: other instanceof Record ? other.getAction() : this.getAction(),
|
|
1379
|
+
installCategory: this.getInstallCategory(),
|
|
1380
|
+
properties: super.merge(other),
|
|
1381
|
+
}).setCreator(this.getCreator())
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
override equals(other: unknown): boolean {
|
|
1385
|
+
return this.getId().equals(other)
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/**
|
|
1389
|
+
* Useful when you need to know if this record is equal to another not
|
|
1390
|
+
* just in terms of its identity but also its data.
|
|
1391
|
+
*/
|
|
1392
|
+
strictEquals(other: unknown): boolean {
|
|
1393
|
+
if (!this.equals(other)) {
|
|
1394
|
+
return false
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
if (!(other instanceof Record)) {
|
|
1398
|
+
return false
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
return this.getAction() === other.getAction() && super.equals(other)
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
override setCreator(creator: Plugin | undefined, overwrite = false): this {
|
|
1405
|
+
this.related.forEach((r) => r.setCreator(creator, overwrite))
|
|
1406
|
+
return super.setCreator(creator, overwrite)
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
override toRecordId(): RecordId {
|
|
1410
|
+
return this.id
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
override toString(): StringShape {
|
|
1414
|
+
return this.id
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
export type CoalesceKeys = globalThis.Record<string, string | RecordId>
|
|
1419
|
+
export type ResolvedCoalesceKeys = globalThis.Record<string, string>
|
|
1420
|
+
export class RecordId extends StringShape {
|
|
1421
|
+
private readonly table: string
|
|
1422
|
+
private readonly guid: string | (() => string)
|
|
1423
|
+
private readonly keys: CoalesceKeys | undefined
|
|
1424
|
+
private readonly nowIdKey: string | number | undefined
|
|
1425
|
+
|
|
1426
|
+
constructor({
|
|
1427
|
+
source,
|
|
1428
|
+
table,
|
|
1429
|
+
guid,
|
|
1430
|
+
keys: rawKeys,
|
|
1431
|
+
nowIdKey,
|
|
1432
|
+
}: {
|
|
1433
|
+
source: Source
|
|
1434
|
+
table: string
|
|
1435
|
+
guid: string | (() => string)
|
|
1436
|
+
keys?: CoalesceKeys | ObjectShape | undefined
|
|
1437
|
+
nowIdKey?: string | number | undefined
|
|
1438
|
+
}) {
|
|
1439
|
+
const keys =
|
|
1440
|
+
rawKeys instanceof ObjectShape
|
|
1441
|
+
? Object.fromEntries(rawKeys.entries().map(([k, v]) => [k, v.ifString()?.getValue() ?? v.toRecordId()]))
|
|
1442
|
+
: rawKeys
|
|
1443
|
+
|
|
1444
|
+
if (keys && Object.keys(keys).length <= 0) {
|
|
1445
|
+
throw new Error('Failed to create RecordId shape. Coalesce keys must contain at least one entry.')
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
super({ source })
|
|
1449
|
+
this.table = table
|
|
1450
|
+
this.guid = guid
|
|
1451
|
+
this.keys = keys
|
|
1452
|
+
this.nowIdKey = nowIdKey
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
getTable(): string {
|
|
1456
|
+
return this.table
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
getGuidProducer(): string | (() => string) {
|
|
1460
|
+
return this.guid
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
getNowIdKey(): string | number | undefined {
|
|
1464
|
+
return this.nowIdKey
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
getKeys(): CoalesceKeys | undefined {
|
|
1468
|
+
return this.keys
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
/**
|
|
1472
|
+
* Similar to `getKeys`, but resolves any nested record IDs to their GUID values
|
|
1473
|
+
*/
|
|
1474
|
+
resolveKeys(): ResolvedCoalesceKeys | undefined {
|
|
1475
|
+
if (!this.keys) {
|
|
1476
|
+
return undefined
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
return Object.fromEntries(
|
|
1480
|
+
Object.entries(this.keys).map(([k, v]) => [k, typeof v === 'string' ? v : v.getValue()])
|
|
1481
|
+
)
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
getKey(key: string): string | RecordId | undefined {
|
|
1485
|
+
return this.getKeys()?.[key]
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
getPrimaryKey(): string {
|
|
1489
|
+
const keyValues = Object.values(this.keys ?? {})
|
|
1490
|
+
const [primaryKeyValue] = keyValues
|
|
1491
|
+
if (keyValues.length !== 1 || !primaryKeyValue || typeof primaryKeyValue !== 'string') {
|
|
1492
|
+
throw new Error("Tried to get primary key of record ID that doesn't have one")
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
return primaryKeyValue
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
hasPrimaryKey(): boolean {
|
|
1499
|
+
return !!this.keys && Object.keys(this.keys).length === 1
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
getUpdateName(): string {
|
|
1503
|
+
// TODO: Provide a way to override this for non-standard coalesce strategies, or
|
|
1504
|
+
// just hard-code the very short list of special cases from UpdateName.java
|
|
1505
|
+
return `${this.table}_${this.getValue()}`
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
override getValue(): string {
|
|
1509
|
+
return typeof this.guid === 'string' ? this.guid : this.guid()
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
override equals(other: unknown): boolean {
|
|
1513
|
+
const otherShape = Shape.from(this, other)
|
|
1514
|
+
if (otherShape.isRecord()) {
|
|
1515
|
+
return this.equals(otherShape.getId())
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
if (otherShape.isRecordId()) {
|
|
1519
|
+
return (
|
|
1520
|
+
this.getTable() === otherShape.getTable() &&
|
|
1521
|
+
(this.getValue() === otherShape.getValue() ||
|
|
1522
|
+
(this.keys !== undefined && _.isEqual(this.keys, otherShape.keys)))
|
|
1523
|
+
)
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
return (this.hasPrimaryKey() && otherShape.equals(this.getPrimaryKey())) || super.equals(other)
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
override getCode(): string {
|
|
1530
|
+
return `'${this.hasPrimaryKey() ? this.getPrimaryKey() : this.getValue()}'`
|
|
1531
|
+
}
|
|
1532
|
+
}
|