@cap-js/cds-typer 0.16.0 → 0.17.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/CHANGELOG.md +5 -1
- package/lib/components/enum.js +3 -7
- package/lib/components/reference.js +23 -0
- package/lib/components/resolver.js +3 -2
- package/lib/csn.js +3 -1
- package/lib/file.js +65 -2
- package/lib/visitor.js +72 -102
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,11 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
6
6
|
|
|
7
|
-
## Version 0.
|
|
7
|
+
## Version 0.18.0 - TBD
|
|
8
|
+
|
|
9
|
+
## Version 0.17.0 - 2024-03-05
|
|
10
|
+
### Fixed
|
|
11
|
+
- Fixed a bug where refering to an externally defined enum via the `typeof` syntax would crash the type generation
|
|
8
12
|
|
|
9
13
|
## Version 0.16.0 - 2024-02-01
|
|
10
14
|
### Changed
|
package/lib/components/enum.js
CHANGED
|
@@ -29,13 +29,9 @@ const { normalise } = require('./identifier')
|
|
|
29
29
|
function printEnum(buffer, name, kvs, options = {}) {
|
|
30
30
|
const opts = {...{export: true}, ...options}
|
|
31
31
|
buffer.add('// enum')
|
|
32
|
-
buffer.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
buffer.add(`${normalise(k)}: ${v},`)
|
|
36
|
-
}
|
|
37
|
-
buffer.outdent()
|
|
38
|
-
buffer.add('} as const;')
|
|
32
|
+
buffer.addIndentedBlock(`${opts.export ? 'export ' : ''}const ${name} = {`, () =>
|
|
33
|
+
kvs.forEach(([k, v]) => buffer.add(`${normalise(k)}: ${v},`))
|
|
34
|
+
, '} as const;')
|
|
39
35
|
buffer.add(`${opts.export ? 'export ' : ''}type ${name} = ${stringifyEnumType(kvs)}`)
|
|
40
36
|
buffer.add('')
|
|
41
37
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if an element references another type.
|
|
3
|
+
* This happens for foreign key relationships
|
|
4
|
+
* and for the typeof syntax.
|
|
5
|
+
*
|
|
6
|
+
* ```cds
|
|
7
|
+
* entity E {
|
|
8
|
+
* x: Integer
|
|
9
|
+
* }
|
|
10
|
+
*
|
|
11
|
+
* entity F {
|
|
12
|
+
* y: E.x // <- ref
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @param {{type: any}} element
|
|
17
|
+
* @returns boolean
|
|
18
|
+
*/
|
|
19
|
+
const isReferenceType = (element) => element.type && Object.hasOwn(element.type, 'ref')
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
isReferenceType
|
|
23
|
+
}
|
|
@@ -6,6 +6,7 @@ const { Buffer, SourceFile, Path, Library, baseDefinitions } = require('../file'
|
|
|
6
6
|
const { deepRequire, createToManyAssociation, createToOneAssociation, createArrayOf, createCompositionOfMany, createCompositionOfOne } = require('./wrappers')
|
|
7
7
|
const { StructuredInlineDeclarationResolver } = require('./inline')
|
|
8
8
|
const { isInlineEnumType, propertyToInlineEnumName } = require('./enum')
|
|
9
|
+
const { isReferenceType } = require('./reference')
|
|
9
10
|
|
|
10
11
|
/** @typedef {{ cardinality?: { max?: '*' | number } }} EntityCSN */
|
|
11
12
|
/** @typedef {{ definitions?: Object<string, EntityCSN> }} CSN */
|
|
@@ -55,7 +56,7 @@ const Builtins = {
|
|
|
55
56
|
Float: 'number',
|
|
56
57
|
Double: 'number',
|
|
57
58
|
Boolean: 'boolean',
|
|
58
|
-
// note: the date-related types
|
|
59
|
+
// note: the date-related types are strings on purpose, which reflects their runtime behaviour
|
|
59
60
|
Date: 'string', // yyyy-mm-dd
|
|
60
61
|
DateTime: 'string', // yyyy-mm-dd + time + TZ (precision: seconds
|
|
61
62
|
Time: 'string',
|
|
@@ -370,7 +371,7 @@ class Resolver {
|
|
|
370
371
|
result.type = '{}'
|
|
371
372
|
result.isInlineDeclaration = true
|
|
372
373
|
} else {
|
|
373
|
-
if (isInlineEnumType(element, this.csn)) {
|
|
374
|
+
if (!isReferenceType(element) && isInlineEnumType(element, this.csn)) {
|
|
374
375
|
// element.parent is only set if the enum is attached to an entity's property.
|
|
375
376
|
// If it is missing then we are dealing with an inline parameter type of an action.
|
|
376
377
|
// Edge case: element.parent is set, but no .name property is attached. This happens
|
package/lib/csn.js
CHANGED
|
@@ -234,10 +234,12 @@ function amendCSN(csn) {
|
|
|
234
234
|
*/
|
|
235
235
|
const isView = entity => entity.query && !entity.projection
|
|
236
236
|
|
|
237
|
+
const isDraftEnabled = entity => entity['@odata.draft.enabled'] === true
|
|
238
|
+
|
|
237
239
|
/**
|
|
238
240
|
* @see isView
|
|
239
241
|
* Unresolved entities have to be looked up from inferred csn.
|
|
240
242
|
*/
|
|
241
243
|
const isUnresolved = entity => entity._unresolved === true
|
|
242
244
|
|
|
243
|
-
module.exports = { amendCSN, isView, isUnresolved, propagateForeignKeys }
|
|
245
|
+
module.exports = { amendCSN, isView, isDraftEnabled, isUnresolved, propagateForeignKeys }
|
package/lib/file.js
CHANGED
|
@@ -93,10 +93,13 @@ class Library extends File {
|
|
|
93
93
|
* Source file containing several buffers.
|
|
94
94
|
*/
|
|
95
95
|
class SourceFile extends File {
|
|
96
|
+
/**
|
|
97
|
+
* @param {string | Path} path
|
|
98
|
+
*/
|
|
96
99
|
constructor(path) {
|
|
97
100
|
super()
|
|
98
101
|
/** @type {Path} */
|
|
99
|
-
this.path = new Path(path.split('.'))
|
|
102
|
+
this.path = path instanceof Path ? path : new Path(path.split('.'))
|
|
100
103
|
/** @type {Object} */
|
|
101
104
|
this.imports = {}
|
|
102
105
|
/** @type {Buffer} */
|
|
@@ -480,6 +483,33 @@ class Buffer {
|
|
|
480
483
|
add(part) {
|
|
481
484
|
this.parts.push(this.currentIndent + part)
|
|
482
485
|
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Adds an element to the buffer with one level of indent.
|
|
489
|
+
* @param {string | (() => void)} part either a string or a function. If it is a string, it is added to the buffer.
|
|
490
|
+
* If not, it is expected to be a function that manipulates the buffer as a side effect.
|
|
491
|
+
*/
|
|
492
|
+
addIndented(part) {
|
|
493
|
+
this.indent()
|
|
494
|
+
if (typeof part === 'function') {
|
|
495
|
+
part()
|
|
496
|
+
} else if (Array.isArray(part)) {
|
|
497
|
+
part.forEach(p => this.add(p))
|
|
498
|
+
}
|
|
499
|
+
this.outdent()
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Adds an element to a buffer with one level of indent and and opener and closer surrounding it.
|
|
504
|
+
* @param {string} opener the string to put before the indent
|
|
505
|
+
* @param {string} content the content to indent (see {@link addIndented})
|
|
506
|
+
* @param {string} closer the string to put after the indent
|
|
507
|
+
*/
|
|
508
|
+
addIndentedBlock(opener, content, closer) {
|
|
509
|
+
this.add(opener)
|
|
510
|
+
this.addIndented(content)
|
|
511
|
+
this.add(closer)
|
|
512
|
+
}
|
|
483
513
|
}
|
|
484
514
|
|
|
485
515
|
/**
|
|
@@ -548,6 +578,38 @@ class Path {
|
|
|
548
578
|
}
|
|
549
579
|
}
|
|
550
580
|
|
|
581
|
+
// TODO: having the repository pattern in place we can separate (some of) the printing logic from the visitor.
|
|
582
|
+
// Most of it hinges primarily on resolving specific files. We can now pass the repository and the resolver to a printer.
|
|
583
|
+
class FileRepository {
|
|
584
|
+
#files = {}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* @param {string} name
|
|
588
|
+
* @param {SourceFile} file
|
|
589
|
+
*/
|
|
590
|
+
add(name, file) {
|
|
591
|
+
this.#files[name] = file
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Determines the file corresponding to the namespace.
|
|
596
|
+
* If no such file exists yet, it is created first.
|
|
597
|
+
* @param {string | Path} path the name of the namespace (foo.bar.baz)
|
|
598
|
+
* @returns {SourceFile} the file corresponding to that namespace name
|
|
599
|
+
*/
|
|
600
|
+
getNamespaceFile(path) {
|
|
601
|
+
const key = path instanceof Path ? path.asNamespace() : path
|
|
602
|
+
return (this.#files[key] ??= new SourceFile(path))
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* @returns {SourceFile[]}
|
|
607
|
+
*/
|
|
608
|
+
getFiles() {
|
|
609
|
+
return Object.values(this.#files)
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
551
613
|
/**
|
|
552
614
|
* Base definitions used throughout the typing process,
|
|
553
615
|
* such as Associations and Compositions.
|
|
@@ -583,7 +645,7 @@ export type EntitySet<T> = T[] & {
|
|
|
583
645
|
|
|
584
646
|
export type DeepRequired<T> = {
|
|
585
647
|
[K in keyof T]: DeepRequired<T[K]>
|
|
586
|
-
} & Required<T>;
|
|
648
|
+
} & Exclude<Required<T>, null>;
|
|
587
649
|
`)
|
|
588
650
|
|
|
589
651
|
/**
|
|
@@ -618,6 +680,7 @@ module.exports = {
|
|
|
618
680
|
Library,
|
|
619
681
|
Buffer,
|
|
620
682
|
File,
|
|
683
|
+
FileRepository,
|
|
621
684
|
SourceFile,
|
|
622
685
|
Path,
|
|
623
686
|
writeout,
|
package/lib/visitor.js
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
const util = require('./util')
|
|
4
4
|
|
|
5
|
-
const { amendCSN, isView, isUnresolved, propagateForeignKeys } = require('./csn')
|
|
5
|
+
const { amendCSN, isView, isUnresolved, propagateForeignKeys, isDraftEnabled } = require('./csn')
|
|
6
6
|
// eslint-disable-next-line no-unused-vars
|
|
7
|
-
const { SourceFile, baseDefinitions, Buffer } = require('./file')
|
|
7
|
+
const { SourceFile, FileRepository, baseDefinitions, Buffer } = require('./file')
|
|
8
8
|
const { FlatInlineDeclarationResolver, StructuredInlineDeclarationResolver } = require('./components/inline')
|
|
9
9
|
const { Resolver } = require('./components/resolver')
|
|
10
10
|
const { Logger } = require('./logging')
|
|
11
11
|
const { docify } = require('./components/wrappers')
|
|
12
12
|
const { csnToEnumPairs, propertyToInlineEnumName, isInlineEnumType, stringifyEnumType } = require('./components/enum')
|
|
13
|
+
const { isReferenceType } = require('./components/reference')
|
|
13
14
|
|
|
14
15
|
/** @typedef {import('./file').File} File */
|
|
15
16
|
/** @typedef {{ entity: String }} Context */
|
|
@@ -54,7 +55,7 @@ class Visitor {
|
|
|
54
55
|
* @returns {File[]} a full list of files to be written
|
|
55
56
|
*/
|
|
56
57
|
getWriteoutFiles() {
|
|
57
|
-
return
|
|
58
|
+
return this.fileRepository.getFiles().concat(this.resolver.getUsedLibraries())
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
/**
|
|
@@ -74,9 +75,9 @@ class Visitor {
|
|
|
74
75
|
/** @type {Resolver} */
|
|
75
76
|
this.resolver = new Resolver(this)
|
|
76
77
|
|
|
77
|
-
/** @type {
|
|
78
|
-
this.
|
|
79
|
-
this.
|
|
78
|
+
/** @type {FileRepository} */
|
|
79
|
+
this.fileRepository = new FileRepository()
|
|
80
|
+
this.fileRepository.add(baseDefinitions.path.asNamespace(), baseDefinitions)
|
|
80
81
|
this.inlineDeclarationResolver =
|
|
81
82
|
this.options.inlineDeclarations === 'structured'
|
|
82
83
|
? new StructuredInlineDeclarationResolver(this)
|
|
@@ -85,16 +86,6 @@ class Visitor {
|
|
|
85
86
|
this.visitDefinitions()
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
/**
|
|
89
|
-
* Determines the file corresponding to the namespace.
|
|
90
|
-
* If no such file exists yet, it is created first.
|
|
91
|
-
* @param {string} path the name of the namespace (foo.bar.baz)
|
|
92
|
-
* @returns {SourceFile} the file corresponding to that namespace name
|
|
93
|
-
*/
|
|
94
|
-
getNamespaceFile(path) {
|
|
95
|
-
return (this.files[path] ??= new SourceFile(path))
|
|
96
|
-
}
|
|
97
|
-
|
|
98
89
|
/**
|
|
99
90
|
* Visits all definitions within the CSN definitions.
|
|
100
91
|
*/
|
|
@@ -163,7 +154,7 @@ class Visitor {
|
|
|
163
154
|
#aspectify(name, entity, buffer, cleanName = undefined) {
|
|
164
155
|
const clean = cleanName ?? this.resolver.trimNamespace(name)
|
|
165
156
|
const ns = this.resolver.resolveNamespace(name.split('.'))
|
|
166
|
-
const file = this.getNamespaceFile(ns)
|
|
157
|
+
const file = this.fileRepository.getNamespaceFile(ns)
|
|
167
158
|
|
|
168
159
|
const identSingular = (name) => name
|
|
169
160
|
const identAspect = (name) => `_${name}Aspect`
|
|
@@ -171,66 +162,54 @@ class Visitor {
|
|
|
171
162
|
this.contexts.push({ entity: name })
|
|
172
163
|
|
|
173
164
|
// CLASS ASPECT
|
|
174
|
-
buffer.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
this.visitElement(foreignKey, kelement, file, buffer)
|
|
165
|
+
buffer.addIndentedBlock(`export function ${identAspect(clean)}<TBase extends new (...args: any[]) => object>(Base: TBase) {`, function () {
|
|
166
|
+
buffer.addIndentedBlock(`return class ${clean} extends Base {`, function () {
|
|
167
|
+
const enums = []
|
|
168
|
+
const exclusions = new Set(entity.projection?.excluding ?? [])
|
|
169
|
+
const elements = Object.entries(entity.elements ?? {}).filter(([ename]) => !exclusions.has(ename))
|
|
170
|
+
for (const [ename, element] of elements) {
|
|
171
|
+
this.visitElement(ename, element, file, buffer)
|
|
172
|
+
|
|
173
|
+
// make foreign keys explicit
|
|
174
|
+
if ('target' in element) {
|
|
175
|
+
// lookup in cds.definitions can fail for inline structs.
|
|
176
|
+
// We don't really have to care for this case, as keys from such structs are _not_ propagated to
|
|
177
|
+
// the containing entity.
|
|
178
|
+
for (const [kname, kelement] of this.#keys(element.target)) {
|
|
179
|
+
if (this.resolver.getMaxCardinality(element) === 1) { // FIXME: kelement?
|
|
180
|
+
const foreignKey = `${ename}_${kname}`
|
|
181
|
+
if (Object.hasOwn(entity.elements, foreignKey)) {
|
|
182
|
+
this.logger.error(`Attempting to generate a foreign key reference called '${foreignKey}' in type definition for entity ${name}. But a property of that name is already defined explicitly. Consider renaming that property.`)
|
|
183
|
+
} else {
|
|
184
|
+
kelement.isRefNotNull = !!element.notNull || !!element.key
|
|
185
|
+
this.visitElement(foreignKey, kelement, file, buffer)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
198
188
|
}
|
|
199
189
|
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
190
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
buffer.indent()
|
|
210
|
-
for (const e of enums) {
|
|
211
|
-
buffer.add(`static ${e.name} = ${propertyToInlineEnumName(clean, e.name)}`)
|
|
212
|
-
file.addInlineEnum(clean, name, e.name, csnToEnumPairs(e, {unwrapVals: true}))
|
|
213
|
-
}
|
|
214
|
-
buffer.add('static actions: {')
|
|
215
|
-
buffer.indent()
|
|
216
|
-
for (const [aname, action] of Object.entries(entity.actions ?? {})) {
|
|
217
|
-
buffer.add(
|
|
218
|
-
SourceFile.stringifyLambda({
|
|
219
|
-
name: aname,
|
|
220
|
-
parameters: this.#stringifyFunctionParams(action.params, file),
|
|
221
|
-
returns: action.returns ? this.resolver.resolveAndRequire(action.returns, file).typeName : 'any'
|
|
222
|
-
//initialiser: `undefined as unknown as typeof ${clean}.${aname}`,
|
|
223
|
-
})
|
|
224
|
-
)
|
|
225
|
-
}
|
|
226
|
-
buffer.outdent()
|
|
227
|
-
buffer.outdent()
|
|
228
|
-
buffer.add('}') // end of actions
|
|
191
|
+
// store inline enums for later handling, as they have to go into one common "static elements" wrapper
|
|
192
|
+
if (isInlineEnumType(element, this.csn.xtended)) {
|
|
193
|
+
enums.push(element)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
229
196
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
197
|
+
buffer.addIndented(function() {
|
|
198
|
+
for (const e of enums) {
|
|
199
|
+
buffer.add(`static ${e.name} = ${propertyToInlineEnumName(clean, e.name)}`)
|
|
200
|
+
file.addInlineEnum(clean, name, e.name, csnToEnumPairs(e, {unwrapVals: true}))
|
|
201
|
+
}
|
|
202
|
+
const actions = Object.entries(entity.actions ?? {})
|
|
203
|
+
buffer.addIndentedBlock('static actions: {',
|
|
204
|
+
actions.map(([aname, action]) => SourceFile.stringifyLambda({
|
|
205
|
+
name: aname,
|
|
206
|
+
parameters: this.#stringifyFunctionParams(action.params, file),
|
|
207
|
+
returns: action.returns ? this.resolver.resolveAndRequire(action.returns, file).typeName : 'any'
|
|
208
|
+
}))
|
|
209
|
+
, '}') // end of actions
|
|
210
|
+
}.bind(this))
|
|
211
|
+
}.bind(this), '};') // end of generated class
|
|
212
|
+
}.bind(this), '}') // end of aspect
|
|
234
213
|
|
|
235
214
|
// CLASS WITH ADDED ASPECTS
|
|
236
215
|
file.addImport(baseDefinitions.path)
|
|
@@ -255,12 +234,8 @@ class Visitor {
|
|
|
255
234
|
this.contexts.pop()
|
|
256
235
|
}
|
|
257
236
|
|
|
258
|
-
#isDraftEnabled(entity) {
|
|
259
|
-
return entity['@odata.draft.enabled'] === true
|
|
260
|
-
}
|
|
261
|
-
|
|
262
237
|
#staticClassContents(clean, entity) {
|
|
263
|
-
return
|
|
238
|
+
return isDraftEnabled(entity) ? [`static drafts: typeof ${clean}`] : []
|
|
264
239
|
}
|
|
265
240
|
|
|
266
241
|
#printEntity(name, entity) {
|
|
@@ -268,7 +243,7 @@ class Visitor {
|
|
|
268
243
|
const overrideNameProperty = (clazz, content) => `Object.defineProperty(${clazz}, 'name', { value: '${content}' })`
|
|
269
244
|
const clean = this.resolver.trimNamespace(name)
|
|
270
245
|
const ns = this.resolver.resolveNamespace(name.split('.'))
|
|
271
|
-
const file = this.getNamespaceFile(ns)
|
|
246
|
+
const file = this.fileRepository.getNamespaceFile(ns)
|
|
272
247
|
// entities are expected to be in plural anyway, so we would favour the regular name.
|
|
273
248
|
// If the user decides to pass a @plural annotation, that gets precedence over the regular name.
|
|
274
249
|
let plural = this.resolver.trimNamespace(util.getPluralAnnotation(entity) ? util.plural4(entity, false) : name)
|
|
@@ -354,7 +329,7 @@ class Visitor {
|
|
|
354
329
|
// FIXME: mostly duplicate of printAction -> reuse
|
|
355
330
|
this.logger.debug(`Printing function ${name}:\n${JSON.stringify(func, null, 2)}`)
|
|
356
331
|
const ns = this.resolver.resolveNamespace(name.split('.'))
|
|
357
|
-
const file = this.getNamespaceFile(ns)
|
|
332
|
+
const file = this.fileRepository.getNamespaceFile(ns)
|
|
358
333
|
const params = this.#stringifyFunctionParams(func.params, file)
|
|
359
334
|
const returns = this.resolver.visitor.inlineDeclarationResolver.getPropertyDatatype(
|
|
360
335
|
this.resolver.resolveAndRequire(func.returns, file)
|
|
@@ -365,7 +340,7 @@ class Visitor {
|
|
|
365
340
|
#printAction(name, action) {
|
|
366
341
|
this.logger.debug(`Printing action ${name}:\n${JSON.stringify(action, null, 2)}`)
|
|
367
342
|
const ns = this.resolver.resolveNamespace(name.split('.'))
|
|
368
|
-
const file = this.getNamespaceFile(ns)
|
|
343
|
+
const file = this.fileRepository.getNamespaceFile(ns)
|
|
369
344
|
const params = this.#stringifyFunctionParams(action.params, file)
|
|
370
345
|
const returns = this.resolver.visitor.inlineDeclarationResolver.getPropertyDatatype(
|
|
371
346
|
this.resolver.resolveAndRequire(action.returns, file)
|
|
@@ -375,10 +350,9 @@ class Visitor {
|
|
|
375
350
|
|
|
376
351
|
#printType(name, type) {
|
|
377
352
|
this.logger.debug(`Printing type ${name}:\n${JSON.stringify(type, null, 2)}`)
|
|
378
|
-
const clean = this.resolver.
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
if ('enum' in type) {
|
|
353
|
+
const [ns, clean] = this.resolver.untangle(name)
|
|
354
|
+
const file = this.fileRepository.getNamespaceFile(ns)
|
|
355
|
+
if ('enum' in type && !isReferenceType(type)) { // skip references to enums
|
|
382
356
|
file.addEnum(name, clean, csnToEnumPairs(type))
|
|
383
357
|
} else {
|
|
384
358
|
// alias
|
|
@@ -389,9 +363,8 @@ class Visitor {
|
|
|
389
363
|
|
|
390
364
|
#printAspect(name, aspect) {
|
|
391
365
|
this.logger.debug(`Printing aspect ${name}`)
|
|
392
|
-
const clean = this.resolver.
|
|
393
|
-
const
|
|
394
|
-
const file = this.getNamespaceFile(ns)
|
|
366
|
+
const [ns, clean] = this.resolver.untangle(name)
|
|
367
|
+
const file = this.fileRepository.getNamespaceFile(ns)
|
|
395
368
|
// aspects are technically classes and can therefore be added to the list of defined classes.
|
|
396
369
|
// Still, when using them as mixins for a class, they need to already be defined.
|
|
397
370
|
// So we separate them into another buffer which is printed before the classes.
|
|
@@ -402,28 +375,25 @@ class Visitor {
|
|
|
402
375
|
|
|
403
376
|
#printEvent(name, event) {
|
|
404
377
|
this.logger.debug(`Printing event ${name}`)
|
|
405
|
-
const clean = this.resolver.
|
|
406
|
-
const
|
|
407
|
-
const file = this.getNamespaceFile(ns)
|
|
378
|
+
const [ns, clean] = this.resolver.untangle(name)
|
|
379
|
+
const file = this.fileRepository.getNamespaceFile(ns)
|
|
408
380
|
file.addEvent(clean, name)
|
|
409
381
|
const buffer = file.events.buffer
|
|
410
382
|
buffer.add('// event')
|
|
411
|
-
buffer.
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
this
|
|
419
|
-
buffer.outdent()
|
|
420
|
-
buffer.add('}')
|
|
383
|
+
buffer.addIndentedBlock(`export class ${clean} {`, function() {
|
|
384
|
+
const propOpt = this.options.propertiesOptional
|
|
385
|
+
this.options.propertiesOptional = false
|
|
386
|
+
for (const [ename, element] of Object.entries(event.elements ?? {})) {
|
|
387
|
+
this.visitElement(ename, element, file, buffer)
|
|
388
|
+
}
|
|
389
|
+
this.options.propertiesOptional = propOpt
|
|
390
|
+
}.bind(this), '}')
|
|
421
391
|
}
|
|
422
392
|
|
|
423
393
|
#printService(name, service) {
|
|
424
394
|
this.logger.debug(`Printing service ${name}:\n${JSON.stringify(service, null, 2)}`)
|
|
425
395
|
const ns = this.resolver.resolveNamespace(name)
|
|
426
|
-
const file = this.getNamespaceFile(ns)
|
|
396
|
+
const file = this.fileRepository.getNamespaceFile(ns)
|
|
427
397
|
// service.name is clean of namespace
|
|
428
398
|
file.services.buffer.add(`export default { name: '${service.name}' }`)
|
|
429
399
|
file.addService(service.name)
|