@servicenow/sdk-build-core 2.0.1

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.
Files changed (130) hide show
  1. package/dist/BuildOptions.d.ts +50 -0
  2. package/dist/BuildOptions.js +46 -0
  3. package/dist/BuildOptions.js.map +1 -0
  4. package/dist/GUID.d.ts +2 -0
  5. package/dist/GUID.js +48 -0
  6. package/dist/GUID.js.map +1 -0
  7. package/dist/Keys.d.ts +29 -0
  8. package/dist/Keys.js +258 -0
  9. package/dist/Keys.js.map +1 -0
  10. package/dist/TypeScript.d.ts +5 -0
  11. package/dist/TypeScript.js +81 -0
  12. package/dist/TypeScript.js.map +1 -0
  13. package/dist/XML.d.ts +25 -0
  14. package/dist/XML.js +72 -0
  15. package/dist/XML.js.map +1 -0
  16. package/dist/index.d.ts +8 -0
  17. package/dist/index.js +38 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/plugins/Context.d.ts +198 -0
  20. package/dist/plugins/Context.js +3 -0
  21. package/dist/plugins/Context.js.map +1 -0
  22. package/dist/plugins/Diagnostic.d.ts +10 -0
  23. package/dist/plugins/Diagnostic.js +52 -0
  24. package/dist/plugins/Diagnostic.js.map +1 -0
  25. package/dist/plugins/Plugin.d.ts +175 -0
  26. package/dist/plugins/Plugin.js +15 -0
  27. package/dist/plugins/Plugin.js.map +1 -0
  28. package/dist/plugins/behaviors/Arranger.d.ts +26 -0
  29. package/dist/plugins/behaviors/Arranger.js +3 -0
  30. package/dist/plugins/behaviors/Arranger.js.map +1 -0
  31. package/dist/plugins/behaviors/Composer.d.ts +101 -0
  32. package/dist/plugins/behaviors/Composer.js +15 -0
  33. package/dist/plugins/behaviors/Composer.js.map +1 -0
  34. package/dist/plugins/behaviors/Diagnostics.d.ts +8 -0
  35. package/dist/plugins/behaviors/Diagnostics.js +3 -0
  36. package/dist/plugins/behaviors/Diagnostics.js.map +1 -0
  37. package/dist/plugins/behaviors/Generator.d.ts +21 -0
  38. package/dist/plugins/behaviors/Generator.js +3 -0
  39. package/dist/plugins/behaviors/Generator.js.map +1 -0
  40. package/dist/plugins/behaviors/OwnedTables.d.ts +6 -0
  41. package/dist/plugins/behaviors/OwnedTables.js +3 -0
  42. package/dist/plugins/behaviors/OwnedTables.js.map +1 -0
  43. package/dist/plugins/behaviors/PostProcessor.d.ts +5 -0
  44. package/dist/plugins/behaviors/PostProcessor.js +3 -0
  45. package/dist/plugins/behaviors/PostProcessor.js.map +1 -0
  46. package/dist/plugins/behaviors/Serializer.d.ts +29 -0
  47. package/dist/plugins/behaviors/Serializer.js +3 -0
  48. package/dist/plugins/behaviors/Serializer.js.map +1 -0
  49. package/dist/plugins/behaviors/Transformer.d.ts +23 -0
  50. package/dist/plugins/behaviors/Transformer.js +3 -0
  51. package/dist/plugins/behaviors/Transformer.js.map +1 -0
  52. package/dist/plugins/behaviors/extractors/Data.d.ts +107 -0
  53. package/dist/plugins/behaviors/extractors/Data.js +191 -0
  54. package/dist/plugins/behaviors/extractors/Data.js.map +1 -0
  55. package/dist/plugins/behaviors/extractors/Extractors.d.ts +41 -0
  56. package/dist/plugins/behaviors/extractors/Extractors.js +3 -0
  57. package/dist/plugins/behaviors/extractors/Extractors.js.map +1 -0
  58. package/dist/plugins/behaviors/extractors/index.d.ts +2 -0
  59. package/dist/plugins/behaviors/extractors/index.js +19 -0
  60. package/dist/plugins/behaviors/extractors/index.js.map +1 -0
  61. package/dist/plugins/behaviors/index.d.ts +9 -0
  62. package/dist/plugins/behaviors/index.js +26 -0
  63. package/dist/plugins/behaviors/index.js.map +1 -0
  64. package/dist/plugins/index.d.ts +5 -0
  65. package/dist/plugins/index.js +23 -0
  66. package/dist/plugins/index.js.map +1 -0
  67. package/dist/plugins/util/CallExpression.d.ts +6 -0
  68. package/dist/plugins/util/CallExpression.js +93 -0
  69. package/dist/plugins/util/CallExpression.js.map +1 -0
  70. package/dist/plugins/util/CodeTransformation.d.ts +74 -0
  71. package/dist/plugins/util/CodeTransformation.js +421 -0
  72. package/dist/plugins/util/CodeTransformation.js.map +1 -0
  73. package/dist/plugins/util/ConfigurationFunction.d.ts +106 -0
  74. package/dist/plugins/util/ConfigurationFunction.js +377 -0
  75. package/dist/plugins/util/ConfigurationFunction.js.map +1 -0
  76. package/dist/plugins/util/ObjectLiteral.d.ts +9 -0
  77. package/dist/plugins/util/ObjectLiteral.js +60 -0
  78. package/dist/plugins/util/ObjectLiteral.js.map +1 -0
  79. package/dist/plugins/util/index.d.ts +4 -0
  80. package/dist/plugins/util/index.js +21 -0
  81. package/dist/plugins/util/index.js.map +1 -0
  82. package/dist/util/Debug.d.ts +8 -0
  83. package/dist/util/Debug.js +39 -0
  84. package/dist/util/Debug.js.map +1 -0
  85. package/dist/util/Util.d.ts +4 -0
  86. package/dist/util/Util.js +41 -0
  87. package/dist/util/Util.js.map +1 -0
  88. package/dist/util/XMLJsonBuilder.d.ts +18 -0
  89. package/dist/util/XMLJsonBuilder.js +59 -0
  90. package/dist/util/XMLJsonBuilder.js.map +1 -0
  91. package/dist/util/XMLUploadParser.d.ts +22 -0
  92. package/dist/util/XMLUploadParser.js +67 -0
  93. package/dist/util/XMLUploadParser.js.map +1 -0
  94. package/dist/util/index.d.ts +4 -0
  95. package/dist/util/index.js +21 -0
  96. package/dist/util/index.js.map +1 -0
  97. package/license +9 -0
  98. package/package.json +42 -0
  99. package/src/BuildOptions.ts +27 -0
  100. package/src/GUID.ts +26 -0
  101. package/src/Keys.ts +287 -0
  102. package/src/TypeScript.ts +65 -0
  103. package/src/XML.ts +85 -0
  104. package/src/index.ts +8 -0
  105. package/src/plugins/Context.ts +249 -0
  106. package/src/plugins/Diagnostic.ts +31 -0
  107. package/src/plugins/Plugin.ts +246 -0
  108. package/src/plugins/behaviors/Arranger.ts +42 -0
  109. package/src/plugins/behaviors/Composer.ts +124 -0
  110. package/src/plugins/behaviors/Diagnostics.ts +13 -0
  111. package/src/plugins/behaviors/Generator.ts +31 -0
  112. package/src/plugins/behaviors/OwnedTables.ts +5 -0
  113. package/src/plugins/behaviors/PostProcessor.ts +6 -0
  114. package/src/plugins/behaviors/Serializer.ts +39 -0
  115. package/src/plugins/behaviors/Transformer.ts +32 -0
  116. package/src/plugins/behaviors/extractors/Data.ts +247 -0
  117. package/src/plugins/behaviors/extractors/Extractors.ts +57 -0
  118. package/src/plugins/behaviors/extractors/index.ts +2 -0
  119. package/src/plugins/behaviors/index.ts +9 -0
  120. package/src/plugins/index.ts +5 -0
  121. package/src/plugins/util/CallExpression.ts +83 -0
  122. package/src/plugins/util/CodeTransformation.ts +500 -0
  123. package/src/plugins/util/ConfigurationFunction.ts +477 -0
  124. package/src/plugins/util/ObjectLiteral.ts +37 -0
  125. package/src/plugins/util/index.ts +4 -0
  126. package/src/util/Debug.ts +46 -0
  127. package/src/util/Util.ts +21 -0
  128. package/src/util/XMLJsonBuilder.ts +64 -0
  129. package/src/util/XMLUploadParser.ts +90 -0
  130. package/src/util/index.ts +4 -0
@@ -0,0 +1,83 @@
1
+ import * as ts from 'ts-morph'
2
+ import { Context } from '../Context'
3
+ import { EntityData, ExtractionResult, ObjectData } from '../behaviors'
4
+ import { FluentDiagnostic } from '../Diagnostic'
5
+
6
+ export const CURRENT_CALL_EXPRESSION = 'current_call_expression'
7
+
8
+ export function getCallExpressionName(node: ts.CallExpression) {
9
+ const expression = node.getExpression()
10
+ if (ts.Node.isIdentifier(expression)) {
11
+ return expression.getText()
12
+ } else if (ts.Node.isPropertyAccessExpression(expression)) {
13
+ return expression.getName()
14
+ } else {
15
+ throw `CallExpression does not have an identifier: ${expression.getText()}`
16
+ }
17
+ }
18
+
19
+ export function extractCallExpression<
20
+ const A extends unknown[],
21
+ const E extends A extends [infer T extends Record<string, unknown>] ? T : never,
22
+ const K extends string,
23
+ >(
24
+ fn: (...args: A) => E,
25
+ kind: K,
26
+ node: ts.CallExpression,
27
+ context: Context,
28
+ guid: (entity: E) => string,
29
+ validate: (entity: unknown) => entity is E = (_): _ is E => true
30
+ ): ExtractionResult<EntityData<E>> {
31
+ const name = getCallExpressionName(node)
32
+ if (name !== fn.name) {
33
+ return { handled: false }
34
+ }
35
+
36
+ //Needed to perform specific parsing and handling of nodes based on call expression
37
+ const oldNodeValue = context.store[CURRENT_CALL_EXPRESSION]
38
+ context.store[CURRENT_CALL_EXPRESSION] = fn.name
39
+ const diagnostics: FluentDiagnostic[] = []
40
+ const args = node.getArguments().map((a) => {
41
+ const result = context.extractAst(a)
42
+ if (!result.handled) {
43
+ diagnostics.push(new FluentDiagnostic(a, `Unsupported argument in ${fn.name} call`))
44
+ return undefined
45
+ }
46
+
47
+ diagnostics.push(...result.diagnostics)
48
+ if (!(0 in result.data) || result.data.length !== 1) {
49
+ diagnostics.push(new FluentDiagnostic(a, `Unsupported argument in ${fn.name} call`))
50
+ return undefined
51
+ }
52
+
53
+ return result.data[0]
54
+ })
55
+ context.store[CURRENT_CALL_EXPRESSION] = oldNodeValue
56
+
57
+ if (!(0 in args) || args.length !== 1) {
58
+ return {
59
+ handled: true,
60
+ data: [],
61
+ diagnostics: [
62
+ ...diagnostics,
63
+ new FluentDiagnostic(node, `Expected ${fn.name} call to have exactly one argument`),
64
+ ],
65
+ }
66
+ }
67
+
68
+ const entity = args[0] as ObjectData<E>
69
+ const entityValue = entity.getValue()
70
+ if (!validate(entityValue)) {
71
+ return {
72
+ handled: true,
73
+ data: [],
74
+ diagnostics: [...diagnostics, new FluentDiagnostic(node, `Invalid ${fn.name} call argument`)],
75
+ }
76
+ }
77
+
78
+ return {
79
+ handled: true,
80
+ diagnostics,
81
+ data: [new EntityData(kind, guid(entityValue), entity, node)],
82
+ }
83
+ }
@@ -0,0 +1,500 @@
1
+ import { NOW_FILE_EXTENSION, SupportedNode } from '@servicenow/sdk-project'
2
+ import { format, resolve } from 'path'
3
+ import { Context } from '../Context'
4
+ import { getCallExpressionName } from './CallExpression'
5
+ import { isArray, isEmpty, isObject, isPlainObject } from 'lodash'
6
+ import * as ts from 'ts-morph'
7
+ import * as z from 'zod'
8
+ import * as path from 'path'
9
+ import { Document } from '../behaviors'
10
+ import { getPropertyAssignment } from './ObjectLiteral'
11
+ import { isGUID } from '../../GUID'
12
+
13
+ export function stringify(val: unknown) {
14
+ return JSON.stringify(val)
15
+ }
16
+
17
+ export type PartialElements<A extends unknown[]> = {
18
+ [I in keyof A]: Partial<A[I]>
19
+ }
20
+
21
+ /**
22
+ * Formats a source file name with the .now.ts extension.
23
+ */
24
+ export function formatSourceFileName(name: string) {
25
+ return format({
26
+ name,
27
+ ext: NOW_FILE_EXTENSION,
28
+ })
29
+ }
30
+
31
+ export function getSysUpdateName(document: Document & { xml?: string }, table: string) {
32
+ if (document.xml) {
33
+ return path.basename(document.xml, path.extname(document.xml))
34
+ }
35
+ return `${table}_${document.guid}`
36
+ }
37
+
38
+ export function getOrCreateEntitySourceFile(context: Context, sysUpdateName: string) {
39
+ const fullPath = resolve(context.app.rootDir, context.app.config.generatedDir, formatSourceFileName(sysUpdateName))
40
+ const sourceFile = context.compiler.getSourceFile(fullPath)
41
+ if (sourceFile) {
42
+ return sourceFile
43
+ }
44
+
45
+ context.logger.info(`Generating new source file: ${fullPath}`)
46
+ return context.compiler.createSourceFile(fullPath)
47
+ }
48
+
49
+ export function addDefaultImportIfAbsent(sourceFile: ts.SourceFile, moduleSpecifier: string, module: string) {
50
+ const importDeclaration = sourceFile.getImportDeclaration((d) => d.getModuleSpecifierValue() === moduleSpecifier)
51
+ if (!importDeclaration) {
52
+ sourceFile.addImportDeclaration({
53
+ moduleSpecifier,
54
+ defaultImport: module,
55
+ })
56
+ return
57
+ }
58
+
59
+ const defaultImport = importDeclaration.getDefaultImport()?.getText()
60
+ if (defaultImport && defaultImport === module) {
61
+ return
62
+ }
63
+ importDeclaration.setDefaultImport(module)
64
+ }
65
+
66
+ export function addNamedImportIfAbsent(
67
+ context: Context,
68
+ sourceFile: ts.SourceFile,
69
+ moduleSpecifier: string,
70
+ module: { name: string } | string
71
+ ): ts.ImportSpecifier {
72
+ let importDeclaration = sourceFile.getImportDeclaration(
73
+ (d) => d.getModuleSpecifierValue() === moduleSpecifier && !d.getNamespaceImport() // If it's a namespace import we cannot add named imports, so we have to create a new one
74
+ )
75
+
76
+ const importName = typeof module === 'string' ? module : module.name
77
+ if (!importDeclaration) {
78
+ importDeclaration = sourceFile.addImportDeclaration({
79
+ moduleSpecifier,
80
+ namedImports: [importName],
81
+ })
82
+ }
83
+
84
+ const namedImports = importDeclaration.getNamedImports().find((n) => n.getName() === importName)
85
+ if (namedImports) {
86
+ return namedImports
87
+ }
88
+
89
+ context.logger.info(
90
+ `Adding named import '${importName}' from module '${moduleSpecifier}' to source file: ${path.normalize(sourceFile.getFilePath())}`
91
+ )
92
+
93
+ return importDeclaration.addNamedImport(importName)
94
+ }
95
+
96
+ /**
97
+ * Generate an exported variable assignment initialized by a function call with
98
+ * the provided arguments.
99
+ */
100
+ export function generateCallExpressionDefaultExport<const A extends unknown[]>(
101
+ context: Context,
102
+ sourceFile: ts.SourceFile,
103
+ moduleSpecifier: string,
104
+ fn: (...args: A) => unknown,
105
+ ...args: PartialElements<A>
106
+ ) {
107
+ addNamedImportIfAbsent(context, sourceFile, moduleSpecifier, fn)
108
+ const exportAssignment = sourceFile.addExportAssignment({
109
+ isExportEquals: false,
110
+ expression: `${fn.name}()`,
111
+ })
112
+
113
+ const callExpression = exportAssignment.getExpressionIfKindOrThrow(ts.SyntaxKind.CallExpression)
114
+ transformFunctionArguments(callExpression, fn, ...args)
115
+
116
+ return exportAssignment
117
+ }
118
+
119
+ export function generateCallExpressionExport<const A extends unknown[]>(
120
+ context: Context,
121
+ sourceFile: ts.SourceFile,
122
+ moduleSpecifier: string,
123
+ exportName: string,
124
+ fn: (...args: A) => unknown,
125
+ ...args: PartialElements<A>
126
+ ) {
127
+ addNamedImportIfAbsent(context, sourceFile, moduleSpecifier, fn)
128
+ const variableDeclaration = sourceFile
129
+ .addVariableStatement({
130
+ isExported: true,
131
+ declarationKind: ts.VariableDeclarationKind.Const,
132
+ declarations: [
133
+ {
134
+ name: exportName,
135
+ initializer: `${fn.name}()`, // Generate empty function call since we will add arguments later
136
+ },
137
+ ],
138
+ })
139
+ .getDeclarations()[0]!
140
+
141
+ // Transform function call to add arguments
142
+ const callExpression = variableDeclaration.getInitializerIfKindOrThrow(ts.SyntaxKind.CallExpression)
143
+ transformFunctionArguments(callExpression, fn, ...args)
144
+
145
+ return variableDeclaration
146
+ }
147
+
148
+ export function generateCallExpression<const A extends unknown[]>(
149
+ context: Context,
150
+ sourceFile: ts.SourceFile,
151
+ moduleSpecifier: string,
152
+ fn: (...args: A) => unknown,
153
+ ...args: PartialElements<A>
154
+ ) {
155
+ addNamedImportIfAbsent(context, sourceFile, moduleSpecifier, fn)
156
+ sourceFile.addStatements((writer) => writer.newLine())
157
+ const [statement] = sourceFile.addStatements(`${fn.name}()`)
158
+
159
+ const expression = statement!.getChildAtIndexIfKindOrThrow(0, ts.SyntaxKind.CallExpression)
160
+
161
+ transformFunctionArguments(expression, fn, ...args)
162
+
163
+ return expression
164
+ }
165
+
166
+ /**
167
+ * Generate an exported variable assignment initialized by a function call with the
168
+ * provided arguments. The exported variable name will be automatically determined
169
+ * based on the function's name and the document's ID.
170
+ */
171
+ export function generateCallExpressionExportForDocument<const A extends unknown[]>(
172
+ context: Context,
173
+ info: {
174
+ sourceFile: ts.SourceFile
175
+ moduleSpecifier: string
176
+ },
177
+ fn: (...args: A) => unknown,
178
+ ...args: PartialElements<A>
179
+ ) {
180
+ const { sourceFile, moduleSpecifier } = info
181
+ return generateCallExpressionDefaultExport(context, sourceFile, moduleSpecifier, fn, ...args)
182
+ }
183
+
184
+ export function getOrCreatePropertyAssignment(
185
+ obj: ts.ObjectLiteralExpression,
186
+ name: string,
187
+ initializer: string = 'undefined'
188
+ ) {
189
+ return (
190
+ obj.getProperty(name)?.asKind(ts.SyntaxKind.PropertyAssignment) ??
191
+ obj.addPropertyAssignment({ name, initializer })
192
+ )
193
+ }
194
+
195
+ export function createOrUpdateScriptProperty(obj: ts.ObjectLiteralExpression, name: string, value: string) {
196
+ if (!value) {
197
+ return
198
+ }
199
+
200
+ const property = obj.getProperty(name)?.asKind(ts.SyntaxKind.PropertyAssignment)
201
+ if (property) {
202
+ property.remove()
203
+ }
204
+ value = value.replaceAll('`', '\\`').replaceAll('${', '\\${').trim()
205
+
206
+ return createOrUpdateStringLiteralProperty(obj, name, value, '`')
207
+ }
208
+
209
+ export function createOrUpdateStringLiteralProperty(
210
+ obj: ts.ObjectLiteralExpression,
211
+ name: string,
212
+ value: string | undefined,
213
+ valueQuoteStyle: "'" | '"' | '`' = "'"
214
+ ) {
215
+ if (value === undefined) {
216
+ return
217
+ }
218
+
219
+ let property = obj.getProperty(name)?.asKind(ts.SyntaxKind.PropertyAssignment)
220
+
221
+ if (!property) {
222
+ property = obj.addPropertyAssignment({ name, initializer: `${valueQuoteStyle}${value}${valueQuoteStyle}` })
223
+ } else {
224
+ const valueNode = property.getChildAtIndexIfKind(2, ts.SyntaxKind.StringLiteral)
225
+ if (valueNode) {
226
+ valueNode.setLiteralValue(value || 'undefined')
227
+ } else {
228
+ const valueNode = property.getChildAtIndexIfKind(2, ts.SyntaxKind.NoSubstitutionTemplateLiteral)
229
+ if (valueNode) {
230
+ valueNode.setLiteralValue(value || 'undefined')
231
+ }
232
+ }
233
+ }
234
+
235
+ return property
236
+ }
237
+
238
+ export function isEscapedPropertyAssignment(args: ts.ObjectLiteralExpression, name: string) {
239
+ const prop = args.getProperty(name)
240
+ if (!prop) {
241
+ return false
242
+ }
243
+
244
+ const asExpression = prop.getChildAtIndexIfKind(2, ts.SyntaxKind.AsExpression)
245
+ if (asExpression && asExpression.getFullText().indexOf('any') !== -1) {
246
+ return true
247
+ }
248
+
249
+ return false
250
+ }
251
+
252
+ function isResolvablePropertyAssignment(args: ts.ObjectLiteralExpression, propertyName: string) {
253
+ const prop = args.getProperty(propertyName)
254
+ if (!prop) {
255
+ return false
256
+ }
257
+
258
+ const identifierExp = prop.getChildAtIndexIfKind(2, ts.SyntaxKind.Identifier)
259
+ if (!identifierExp) {
260
+ return false
261
+ }
262
+
263
+ // Variable definition may be from other file
264
+ identifierExp.getSourceFile().fixMissingImports()
265
+
266
+ const definitions = identifierExp.getDefinitionNodes()
267
+ if (definitions && definitions.length > 0) {
268
+ return true
269
+ }
270
+ return false
271
+ }
272
+
273
+ /** utility function to get the $id value in a object literal node */
274
+ export const getNodeId = (node: ts.ObjectLiteralExpression) => {
275
+ const prop = node.getPropertyOrThrow('$id').asKindOrThrow(ts.SyntaxKind.PropertyAssignment)
276
+ if (prop.getInitializerIfKind(ts.SyntaxKind.ElementAccessExpression)) {
277
+ const expression = prop.getInitializerIfKind(ts.SyntaxKind.ElementAccessExpression)?.getArgumentExpression()
278
+ return (
279
+ expression?.asKind(ts.SyntaxKind.StringLiteral)?.getLiteralValue() ??
280
+ expression?.asKind(ts.SyntaxKind.NumericLiteral)?.getLiteralValue().toString()
281
+ )
282
+ } else if (prop.getInitializerIfKind(ts.SyntaxKind.StringLiteral)) {
283
+ return prop.getInitializerIfKind(ts.SyntaxKind.StringLiteral)?.getLiteralValue()
284
+ }
285
+ return `${prop.getInitializerIfKind(ts.SyntaxKind.NumericLiteral)?.getLiteralValue()}`
286
+ }
287
+
288
+ /** utility iterator for iterating array structures in rest api*/
289
+ export class ArrayIterator {
290
+ arrayExpression: ts.ArrayLiteralExpression
291
+ elements: ts.Expression<ts.ts.Expression>[]
292
+ idx: number
293
+
294
+ constructor(private prop: ts.PropertyAssignment) {
295
+ this.arrayExpression = this.prop.getInitializerIfKindOrThrow(ts.SyntaxKind.ArrayLiteralExpression)!
296
+ this.elements = this.arrayExpression.getElements()
297
+ this.idx = 0
298
+ }
299
+
300
+ hasNext() {
301
+ return this.idx < this.elements.length
302
+ }
303
+
304
+ next() {
305
+ return this.elements[this.idx++]!
306
+ }
307
+
308
+ getExpression() {
309
+ return this.arrayExpression
310
+ }
311
+ }
312
+
313
+ export function writeArrayPropertyAsReference(
314
+ arg: ts.ObjectLiteralExpression,
315
+ name: string,
316
+ def: string,
317
+ value: string[]
318
+ ) {
319
+ if (!value || !isArray(value)) {
320
+ return
321
+ }
322
+
323
+ const propertyAssignment = getOrCreatePropertyAssignment(arg, name, def)
324
+
325
+ const newValues = value.map((val) => {
326
+ propertyAssignment.setInitializer((writer) => {
327
+ writer.write(val)
328
+ })
329
+
330
+ return isResolvablePropertyAssignment(arg, name) ? val : `'${val}' as any`
331
+ })
332
+
333
+ propertyAssignment.setInitializer((writer) => {
334
+ writer.write(`[${newValues}]`)
335
+ })
336
+ }
337
+
338
+ export const recordSchema = z.object({
339
+ $id: z.union([z.string(), z.number()]),
340
+ data: z.record(z.any()),
341
+ table: z.string(),
342
+ })
343
+
344
+ export function writePropertyAsReference(arg: ts.ObjectLiteralExpression, name: string, def: string, value: unknown) {
345
+ if (!value || isArray(value)) {
346
+ return
347
+ }
348
+
349
+ // This value is already a reference that we shouldn't attempt to resolve
350
+ if (recordSchema.safeParse(value).success) {
351
+ return
352
+ }
353
+
354
+ const propertyAssignment = getOrCreatePropertyAssignment(arg, name, def)
355
+
356
+ if (isEmpty(value)) {
357
+ propertyAssignment.setInitializer((writer) => {
358
+ writer.write(stringify(value))
359
+ })
360
+ }
361
+
362
+ if (isPlainObject(value)) {
363
+ const nestedArgs = propertyAssignment.getInitializerIfKindOrThrow(ts.SyntaxKind.ObjectLiteralExpression)
364
+ for (const property in value) {
365
+ if (isArray(value[property])) {
366
+ writeArrayPropertyAsReference(nestedArgs, property, '[]', value[property])
367
+ } else {
368
+ writePropertyAsReference(nestedArgs, property, '{}', value[property])
369
+ }
370
+ }
371
+
372
+ return
373
+ }
374
+
375
+ const escapedPreTransform = isEscapedPropertyAssignment(arg, name)
376
+
377
+ if (isGUID(value as string)) {
378
+ propertyAssignment.setInitializer((writer) => {
379
+ writer.write(stringify(value))
380
+ })
381
+ return
382
+ }
383
+
384
+ propertyAssignment.setInitializer((writer) => {
385
+ writer.write(value as string)
386
+ })
387
+
388
+ if (isResolvablePropertyAssignment(arg, name)) {
389
+ return
390
+ }
391
+
392
+ value = `${stringify(value)} as any`
393
+ if (!escapedPreTransform) {
394
+ // Generate comment
395
+ value = `${value} /*Generated*/` // comment like this '//comment' breaks the code in some occasion
396
+ }
397
+
398
+ propertyAssignment.setInitializer((writer) => {
399
+ writer.write(value as string)
400
+ })
401
+ }
402
+
403
+ export function writeCustomProperty(arg: ts.ObjectLiteralExpression, name: string, def: string, value: string) {
404
+ if (!value) {
405
+ return
406
+ }
407
+
408
+ const propertyAssignment = getOrCreatePropertyAssignment(arg, name, def)
409
+ propertyAssignment.setInitializer((writer) => {
410
+ writer.write(value)
411
+ })
412
+ }
413
+
414
+ export function mergeDataIntoObjectLiteral(node: ts.ObjectLiteralExpression, data: object) {
415
+ for (const [key, value] of Object.entries(data)) {
416
+ const existingAssignment = getPropertyAssignment(node, key)
417
+ const array = isArray(value)
418
+ if (existingAssignment) {
419
+ if (value === '') {
420
+ removeNode(existingAssignment)
421
+ } else if (array && value.length === 0) {
422
+ removeNode(existingAssignment)
423
+ }
424
+ }
425
+
426
+ if (isObject(value) && !Object.values(value).some((value) => value !== undefined)) {
427
+ // Don't write an empty object literal
428
+ continue
429
+ }
430
+
431
+ if (value !== '' && value !== undefined && !(array && value.length === 0)) {
432
+ const propertyAssignment = getOrCreatePropertyAssignment(node, key, array ? '[]' : '{}')
433
+ setPropertyAssignmentValue(propertyAssignment, value)
434
+ }
435
+ }
436
+ }
437
+
438
+ export function setPropertyAssignmentValue(
439
+ property: ts.PropertyAssignment,
440
+ value: string | ts.PropertyAssignmentStructure | ts.ImportSpecifierStructure | object
441
+ ) {
442
+ if (typeof value === 'object') {
443
+ if (ts.Structure.isPropertyAssignment(value)) {
444
+ property.setInitializer(value.initializer)
445
+ } else if (ts.Structure.isImportSpecifier(value)) {
446
+ property.setInitializer(value.name)
447
+ } else if (isArray(value)) {
448
+ property.setInitializer(stringify(value))
449
+ } else {
450
+ mergeDataIntoObjectLiteral(
451
+ property.getInitializerIfKindOrThrow(ts.SyntaxKind.ObjectLiteralExpression),
452
+ value
453
+ )
454
+ }
455
+ } else {
456
+ property.setInitializer(stringify(value))
457
+ }
458
+ }
459
+
460
+ export function transformFunctionArguments<const A extends unknown[]>(
461
+ node: ts.CallExpression,
462
+ fn: (...args: A) => unknown,
463
+ ...args: PartialElements<A>
464
+ ) {
465
+ if (getCallExpressionName(node) !== fn.name) {
466
+ return false
467
+ }
468
+
469
+ for (let i = 0; i < args.length; i++) {
470
+ const argValue = args[i]
471
+ const argNode = node.getArguments()[i] ?? node.insertArgument(i, '{}')
472
+
473
+ if (argValue && typeof argValue === 'object') {
474
+ mergeDataIntoObjectLiteral(argNode.asKindOrThrow(ts.SyntaxKind.ObjectLiteralExpression), argValue)
475
+ } else {
476
+ argNode.replaceWithText(stringify(argValue))
477
+ }
478
+ }
479
+
480
+ return true
481
+ }
482
+
483
+ export function removeNode(node: SupportedNode) {
484
+ if ('remove' in node) {
485
+ return node.remove()
486
+ } else if (ts.Node.isCallExpression(node)) {
487
+ const parent = node.getParent()
488
+ if (ts.Node.isExpressionStatement(parent) || ts.Node.isVariableDeclaration(parent)) {
489
+ return removeNode(parent)
490
+ }
491
+ } else if (ts.Node.isStatement(node)) {
492
+ removeNode(node)
493
+ }
494
+
495
+ // TODO: Nodes that aren't complete statements can't be arbitrarily removed because it
496
+ // will almost always result in broken code. Replacing with 'undefined' works pretty
497
+ // much anywhere, but isn't very elegant. We should revisit this and perhaps make the
498
+ // behavior configurable.
499
+ node.replaceWithText('undefined')
500
+ }