@cap-js/cds-typer 0.22.0 → 0.23.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 +9 -1
- package/cds-plugin.js +4 -4
- package/lib/cli.js +19 -8
- package/lib/compile.js +8 -10
- package/lib/components/enum.js +12 -12
- package/lib/components/identifier.js +9 -1
- package/lib/components/inline.js +15 -13
- package/lib/components/reference.js +1 -1
- package/lib/components/wrappers.js +8 -8
- package/lib/csn.js +38 -25
- package/lib/file.js +34 -30
- package/lib/logging.js +12 -70
- package/lib/resolution/builtin.js +64 -0
- package/lib/resolution/entity.js +155 -0
- package/lib/{components → resolution}/resolver.js +54 -163
- package/lib/typedefs.d.ts +21 -0
- package/lib/util.js +6 -6
- package/lib/visitor.js +116 -124
- package/package.json +2 -1
|
@@ -3,122 +3,25 @@
|
|
|
3
3
|
const util = require('../util')
|
|
4
4
|
// eslint-disable-next-line no-unused-vars
|
|
5
5
|
const { Buffer, SourceFile, Path, Library } = require('../file')
|
|
6
|
-
const { deepRequire, createToManyAssociation, createToOneAssociation, createArrayOf, createCompositionOfMany, createCompositionOfOne } = require('
|
|
7
|
-
const { StructuredInlineDeclarationResolver } = require('
|
|
8
|
-
const { isInlineEnumType, propertyToInlineEnumName } = require('
|
|
9
|
-
const { isReferenceType } = require('
|
|
10
|
-
const { isEntity } = require('../csn')
|
|
11
|
-
const { baseDefinitions } = require('
|
|
6
|
+
const { deepRequire, createToManyAssociation, createToOneAssociation, createArrayOf, createCompositionOfMany, createCompositionOfOne } = require('../components/wrappers')
|
|
7
|
+
const { StructuredInlineDeclarationResolver } = require('../components/inline')
|
|
8
|
+
const { isInlineEnumType, propertyToInlineEnumName } = require('../components/enum')
|
|
9
|
+
const { isReferenceType } = require('../components/reference')
|
|
10
|
+
const { isEntity, getMaxCardinality } = require('../csn')
|
|
11
|
+
const { baseDefinitions } = require('../components/basedefs')
|
|
12
|
+
const { BuiltinResolver } = require('./builtin')
|
|
13
|
+
const { LOG } = require('../logging')
|
|
14
|
+
const { last } = require('../components/identifier')
|
|
12
15
|
|
|
13
16
|
/** @typedef {import('../visitor').Visitor} Visitor */
|
|
14
17
|
/** @typedef {import('../typedefs').resolver.CSN} CSN */
|
|
15
18
|
/** @typedef {import('../typedefs').resolver.TypeResolveInfo} TypeResolveInfo */
|
|
16
|
-
|
|
17
|
-
class BuiltinResolver {
|
|
18
|
-
/**
|
|
19
|
-
* Builtin types defined by CDS.
|
|
20
|
-
*/
|
|
21
|
-
#builtins = {
|
|
22
|
-
UUID: 'string',
|
|
23
|
-
String: 'string',
|
|
24
|
-
Binary: 'string',
|
|
25
|
-
LargeString: 'string',
|
|
26
|
-
LargeBinary: 'Buffer | string | {value: import("stream").Readable, $mediaContentType: string, $mediaContentDispositionFilename?: string, $mediaContentDispositionType?: string}',
|
|
27
|
-
Vector: 'string',
|
|
28
|
-
Integer: 'number',
|
|
29
|
-
UInt8: 'number',
|
|
30
|
-
Int16: 'number',
|
|
31
|
-
Int32: 'number',
|
|
32
|
-
Int64: 'number',
|
|
33
|
-
Integer64: 'number',
|
|
34
|
-
Decimal: 'number',
|
|
35
|
-
DecimalFloat: 'number',
|
|
36
|
-
Float: 'number',
|
|
37
|
-
Double: 'number',
|
|
38
|
-
Boolean: 'boolean',
|
|
39
|
-
// note: the date-related types are strings on purpose, which reflects their runtime behaviour
|
|
40
|
-
Date: '__.CdsDate', // yyyy-mm-dd
|
|
41
|
-
DateTime: '__.CdsDateTime', // yyyy-mm-dd + time + TZ (precision: seconds)
|
|
42
|
-
Time: '__.CdsTime', // hh:mm:ss
|
|
43
|
-
Timestamp: '__.CdsTimestamp', // yyy-mm-dd + time + TZ (ms precision)
|
|
44
|
-
//
|
|
45
|
-
Composition: 'Array',
|
|
46
|
-
Association: 'Array'
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
constructor ({ IEEE754Compatible } = {}) {
|
|
50
|
-
if (IEEE754Compatible) {
|
|
51
|
-
this.#builtins.Decimal = '(number | string)'
|
|
52
|
-
this.#builtins.DecimalFloat = '(number | string)'
|
|
53
|
-
this.#builtins.Float = '(number | string)'
|
|
54
|
-
this.#builtins.Double = '(number | string)'
|
|
55
|
-
}
|
|
56
|
-
this.#builtins = Object.freeze(this.#builtins)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* @param {string | string[]} t - name or parts of the type name split on dots
|
|
61
|
-
* @returns {string | undefined | false} if t refers to a builtin, the name of the corresponding TS type is returned.
|
|
62
|
-
* If t _looks like_ a builtin (`cds.X`), undefined is returned.
|
|
63
|
-
* If t is obviously not a builtin, false is returned.
|
|
64
|
-
*/
|
|
65
|
-
resolveBuiltin (t) {
|
|
66
|
-
if (!Array.isArray(t) && typeof t !== 'string') return false
|
|
67
|
-
const path = Array.isArray(t) ? t : t.split('.')
|
|
68
|
-
return path.length === 2 && path[0] === 'cds'
|
|
69
|
-
? this.#builtins[path[1]]
|
|
70
|
-
: false
|
|
71
|
-
}
|
|
72
|
-
}
|
|
19
|
+
/** @typedef {import('../typedefs').visitor.Inflection} TypeResolveInfo */
|
|
73
20
|
|
|
74
21
|
class Resolver {
|
|
75
|
-
|
|
76
|
-
#caches = {
|
|
77
|
-
/**
|
|
78
|
-
* @type {{ [qualifier: string]: string }}
|
|
79
|
-
*/
|
|
80
|
-
namespaces: {},
|
|
81
|
-
/**
|
|
82
|
-
* @type {{ [qualifier: string]: string[] }}
|
|
83
|
-
*/
|
|
84
|
-
propertyAccesses: {}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* @param {string} qualifier
|
|
89
|
-
* @returns {string?}
|
|
90
|
-
*/
|
|
91
|
-
#getCachedNamespace (qualifier) {
|
|
92
|
-
return this.#caches.namespaces[qualifier]
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* @param {string} qualifier
|
|
97
|
-
* @param {string} namespace
|
|
98
|
-
*/
|
|
99
|
-
#cacheNamespace (qualifier, namespace) {
|
|
100
|
-
this.#caches.namespaces[qualifier] = namespace
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* @param {string} qualifier
|
|
105
|
-
* @returns {string[]?}
|
|
106
|
-
*/
|
|
107
|
-
#getCachedPropertyAccess (qualifier) {
|
|
108
|
-
return this.#caches.propertyAccesses[qualifier]
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* @param {string} qualifier
|
|
113
|
-
* @param {string[]} propertyAccess
|
|
114
|
-
*/
|
|
115
|
-
#cachePropertyAccess (qualifier, propertyAccess) {
|
|
116
|
-
this.#caches.propertyAccesses[qualifier] = propertyAccess
|
|
117
|
-
}
|
|
118
|
-
|
|
119
22
|
get csn() { return this.visitor.csn.inferred }
|
|
120
|
-
|
|
121
|
-
/** @param {Visitor} visitor */
|
|
23
|
+
|
|
24
|
+
/** @param {Visitor} visitor - the visitor */
|
|
122
25
|
constructor(visitor) {
|
|
123
26
|
/** @type {Visitor} */
|
|
124
27
|
this.visitor = visitor
|
|
@@ -134,8 +37,24 @@ class Resolver {
|
|
|
134
37
|
* needed for inline declarations
|
|
135
38
|
*/
|
|
136
39
|
this.structuredInlineResolver = new StructuredInlineDeclarationResolver(this.visitor)
|
|
137
|
-
}
|
|
138
|
-
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} fq - fully qualified name of the entity
|
|
44
|
+
* @returns {boolean} true, iff the entity exists in the CSN (excluding builtins, see {@link isPartOfModel})
|
|
45
|
+
*/
|
|
46
|
+
existsInCsn(fq) {
|
|
47
|
+
return Boolean(this.csn.definitions[fq])
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {string} fq - fully qualified name of the entity or builtin
|
|
52
|
+
* @returns {boolean} true, iff the entity exists in the CSN or is identified as a builtin
|
|
53
|
+
*/
|
|
54
|
+
isPartOfModel(fq) {
|
|
55
|
+
return this.existsInCsn(fq) || Boolean(this.builtinResolver.resolveBuiltin(fq))
|
|
56
|
+
}
|
|
57
|
+
|
|
139
58
|
/**
|
|
140
59
|
* Returns all libraries that have been referenced at least once.
|
|
141
60
|
* @returns {Library[]}
|
|
@@ -144,27 +63,19 @@ class Resolver {
|
|
|
144
63
|
return this.libraries.filter(l => l.referenced)
|
|
145
64
|
}
|
|
146
65
|
|
|
147
|
-
/**
|
|
148
|
-
* TODO: this should probably be a class where we can also cache the properties
|
|
149
|
-
* and only retrieve them on demand
|
|
150
|
-
* @typedef {object} Untangled
|
|
151
|
-
* @property {string[]} scope in case the entity is wrapped in another entity `a.b.C.D.E.f.g` -> `[C,D]`
|
|
152
|
-
* @property {string} name name of the leaf entity `a.b.C.D.E.f.g` -> `E`
|
|
153
|
-
* @property {string[]} property the property access path `a.b.C.D.E.f.g` -> `[f,g]`
|
|
154
|
-
* @property {Path} namespace the cds namespace of the entity `a.b.C.D.E.f.g` -> `a.b`
|
|
155
|
-
*/
|
|
156
|
-
|
|
157
66
|
/**
|
|
158
67
|
* Conveniently combines resolveNamespace and trimNamespace
|
|
159
68
|
* to end up with both the resolved Path of the namespace,
|
|
160
69
|
* and the clean name of the class.
|
|
161
70
|
* @param {string} fq - the fully qualified name of an entity.
|
|
162
|
-
* @returns {Untangled} untangled qualifier
|
|
71
|
+
* @returns {import('../typedefs').resolver.Untangled} untangled qualifier
|
|
163
72
|
*/
|
|
164
73
|
untangle(fq) {
|
|
165
74
|
const builtin = this.builtinResolver.resolveBuiltin(fq)
|
|
166
75
|
if (builtin) return { namespace: new Path([]), name: builtin, property: [], scope: [] }
|
|
167
76
|
|
|
77
|
+
// FIXME: if fq points to a service definition, ns will be the same as nameAndProperty
|
|
78
|
+
// this currently isn't a problem as we only use the its name, but should be addressed at some point
|
|
168
79
|
const ns = this.resolveNamespace(fq)
|
|
169
80
|
const nameAndProperty = this.trimNamespace(fq)
|
|
170
81
|
const property = this.findPropertyAccess(fq)
|
|
@@ -172,7 +83,7 @@ class Resolver {
|
|
|
172
83
|
? nameAndProperty.slice(0, -(property.join('').length + property.length)) // +1 for each dot
|
|
173
84
|
: nameAndProperty
|
|
174
85
|
).split('.')//.at(-1) // nested entities would return Foo.Bar, so we only take the last part to get the actual entity name
|
|
175
|
-
return {
|
|
86
|
+
return {
|
|
176
87
|
namespace: new Path(ns.split('.')),
|
|
177
88
|
scope: nameParts.slice(0, -1),
|
|
178
89
|
name: nameParts.at(-1),
|
|
@@ -191,7 +102,6 @@ class Resolver {
|
|
|
191
102
|
* @returns {string} the entity name without leading namespace.
|
|
192
103
|
*/
|
|
193
104
|
trimNamespace(p) {
|
|
194
|
-
//if (this.#getCachedNamespace(p)) return this.#getCachedNamespace(p)
|
|
195
105
|
const parts = p.split('.')
|
|
196
106
|
if (parts.length <= 1) return p
|
|
197
107
|
|
|
@@ -218,13 +128,13 @@ class Resolver {
|
|
|
218
128
|
* entity Entity {
|
|
219
129
|
* x: Composition of { y: Composition of z: { a: Integer }}
|
|
220
130
|
* }
|
|
221
|
-
*
|
|
131
|
+
*
|
|
222
132
|
* // somewhere else
|
|
223
133
|
* entity Foo {
|
|
224
134
|
* x: namespace.Entity.x.y.z;
|
|
225
135
|
* }
|
|
226
136
|
* ```
|
|
227
|
-
* @example
|
|
137
|
+
* @example
|
|
228
138
|
* ```js
|
|
229
139
|
* findPropertyAccess('namespace') // []
|
|
230
140
|
* findPropertyAccess('namespace.Entity') // []
|
|
@@ -233,7 +143,6 @@ class Resolver {
|
|
|
233
143
|
* ```
|
|
234
144
|
*/
|
|
235
145
|
findPropertyAccess(p) {
|
|
236
|
-
if (this.#getCachedPropertyAccess(p)) return this.#getCachedPropertyAccess(p)
|
|
237
146
|
const parts = p.split('.')
|
|
238
147
|
if (parts.length <= 1) return []
|
|
239
148
|
|
|
@@ -247,13 +156,12 @@ class Resolver {
|
|
|
247
156
|
qualifier += `.${parts.shift()}`
|
|
248
157
|
}
|
|
249
158
|
// skip forward to the last entity from left (Entity3), assuming that there is no name conflict between entities and properties
|
|
250
|
-
// i.e.: if there is a property "Entity2" in the entity Entity1, this will instead [Entity2, Entity3, property1, property2] as property access
|
|
159
|
+
// i.e.: if there is a property "Entity2" in the entity Entity1, this will instead [Entity2, Entity3, property1, property2] as property access
|
|
251
160
|
while (!isPropertyOf(parts[0], defs[qualifier]) && isEntity(defs[qualifier + `.${parts[0]}`])) {
|
|
252
161
|
qualifier += `.${parts.shift()}`
|
|
253
162
|
}
|
|
254
163
|
// assuming Entity3 _does_ own a property "property1", return [property1, property2]
|
|
255
164
|
const propertyAccess = isPropertyOf(parts[0], defs[qualifier]) ? parts : []
|
|
256
|
-
this.#cachePropertyAccess(p, propertyAccess)
|
|
257
165
|
return propertyAccess
|
|
258
166
|
}
|
|
259
167
|
|
|
@@ -274,7 +182,7 @@ class Resolver {
|
|
|
274
182
|
// TODO: handle builtins here as well?
|
|
275
183
|
// guard: types don't get inflected
|
|
276
184
|
if (typeInfo.csn?.kind === 'type') {
|
|
277
|
-
return {
|
|
185
|
+
return {
|
|
278
186
|
singular: typeInfo.plainName,
|
|
279
187
|
plural: createArrayOf(typeInfo.plainName),
|
|
280
188
|
typeName: typeInfo.plainName,
|
|
@@ -318,7 +226,7 @@ class Resolver {
|
|
|
318
226
|
}
|
|
319
227
|
}
|
|
320
228
|
if (!singular || !plural) {
|
|
321
|
-
|
|
229
|
+
LOG.error(`Singular ('${singular}') or plural ('${plural}') for '${typeName}' is empty.`)
|
|
322
230
|
}
|
|
323
231
|
|
|
324
232
|
return { typeName, singular, plural }
|
|
@@ -344,7 +252,7 @@ class Resolver {
|
|
|
344
252
|
*/
|
|
345
253
|
resolveAndRequire(element, file) {
|
|
346
254
|
const typeInfo = this.resolveType(element, file)
|
|
347
|
-
const cardinality =
|
|
255
|
+
const cardinality = getMaxCardinality(element)
|
|
348
256
|
|
|
349
257
|
let typeName = typeInfo.plainName ?? typeInfo.type
|
|
350
258
|
|
|
@@ -372,14 +280,14 @@ class Resolver {
|
|
|
372
280
|
// But we can't just fix it in inflection(...), as that would break several other things
|
|
373
281
|
// So we bandaid-fix it back here, as it is the least intrusive place -- but this should get fixed asap!
|
|
374
282
|
if (target.type) {
|
|
375
|
-
const untangled = this.
|
|
283
|
+
const untangled = this.visitor.entityRepository.getByFq(target.type)
|
|
376
284
|
const scope = untangled.scope.join('.')
|
|
377
285
|
if (scope && !singular.startsWith(scope)) {
|
|
378
286
|
singular = `${scope}.${singular}`
|
|
379
287
|
}
|
|
380
288
|
}
|
|
381
289
|
|
|
382
|
-
typeName = cardinality > 1
|
|
290
|
+
typeName = cardinality > 1
|
|
383
291
|
? toMany(plural)
|
|
384
292
|
: toOne(this.visitor.isSelfReference(target) ? 'this' : singular)
|
|
385
293
|
file.addImport(baseDefinitions.path)
|
|
@@ -426,13 +334,13 @@ class Resolver {
|
|
|
426
334
|
// handle typeof (unless it has already been handled above)
|
|
427
335
|
const target = element.target?.name ?? element.type?.ref?.join('.') ?? element.type
|
|
428
336
|
if (target && !typeInfo.isDeepRequire) {
|
|
429
|
-
const {
|
|
430
|
-
if (propertyAccess
|
|
337
|
+
const { propertyAccess } = this.visitor.entityRepository.getByFq(target) ?? {}
|
|
338
|
+
if (propertyAccess?.length) {
|
|
431
339
|
const element = target.slice(0, -propertyAccess.join('.').length - 1)
|
|
432
340
|
const access = this.visitor.inlineDeclarationResolver.getTypeLookup(propertyAccess)
|
|
433
341
|
// singular, as we have to access the property of the entity
|
|
434
342
|
typeName = deepRequire(util.singular4(element)) + access
|
|
435
|
-
typeInfo.isDeepRequire = true
|
|
343
|
+
typeInfo.isDeepRequire = true
|
|
436
344
|
}
|
|
437
345
|
}
|
|
438
346
|
|
|
@@ -447,18 +355,6 @@ class Resolver {
|
|
|
447
355
|
return { typeName, typeInfo }
|
|
448
356
|
}
|
|
449
357
|
|
|
450
|
-
/**
|
|
451
|
-
* Attempts to retrieve the max cardinality of a CSN for an entity.
|
|
452
|
-
* @param {EntityCSN} element - csn of entity to retrieve cardinality for
|
|
453
|
-
* @returns {number} max cardinality of the element.
|
|
454
|
-
* If no cardinality is attached to the element, cardinality is 1.
|
|
455
|
-
* If it is set to '*', result is Infinity.
|
|
456
|
-
*/
|
|
457
|
-
getMaxCardinality(element) {
|
|
458
|
-
const cardinality = element?.cardinality?.max ?? 1
|
|
459
|
-
return cardinality === '*' ? Infinity : parseInt(cardinality)
|
|
460
|
-
}
|
|
461
|
-
|
|
462
358
|
/**
|
|
463
359
|
* Resolves the fully qualified name of an entity to its parent entity.
|
|
464
360
|
* resolveParent(a.b.c.D) -> CSN {a.b.c}
|
|
@@ -480,8 +376,6 @@ class Resolver {
|
|
|
480
376
|
*/
|
|
481
377
|
resolveNamespace(pathParts) {
|
|
482
378
|
if (typeof pathParts === 'string') pathParts = pathParts.split('.')
|
|
483
|
-
const fq = pathParts.join('.')
|
|
484
|
-
if (this.#getCachedNamespace(fq)) return this.#getCachedNamespace(fq)
|
|
485
379
|
let result
|
|
486
380
|
while (result === undefined) {
|
|
487
381
|
const path = pathParts.join('.')
|
|
@@ -494,7 +388,6 @@ class Resolver {
|
|
|
494
388
|
pathParts = pathParts.slice(0, -1)
|
|
495
389
|
}
|
|
496
390
|
}
|
|
497
|
-
this.#cacheNamespace(fq, result)
|
|
498
391
|
return result
|
|
499
392
|
}
|
|
500
393
|
|
|
@@ -510,15 +403,15 @@ class Resolver {
|
|
|
510
403
|
// with an already resolved type. In that case, just return the type we have.
|
|
511
404
|
if (element && Object.hasOwn(element, 'isBuiltin')) return element
|
|
512
405
|
|
|
513
|
-
const cardinality =
|
|
406
|
+
const cardinality = getMaxCardinality(element)
|
|
514
407
|
|
|
515
408
|
const result = {
|
|
516
409
|
isBuiltin: false, // will be rectified in the corresponding handlers, if needed
|
|
517
410
|
isInlineDeclaration: false,
|
|
518
411
|
isForeignKeyReference: false,
|
|
519
412
|
isArray: false,
|
|
520
|
-
isNotNull: element?.isRefNotNull !== undefined
|
|
521
|
-
? element?.isRefNotNull
|
|
413
|
+
isNotNull: element?.isRefNotNull !== undefined
|
|
414
|
+
? element?.isRefNotNull
|
|
522
415
|
: element?.key || element?.notNull || cardinality > 1,
|
|
523
416
|
}
|
|
524
417
|
|
|
@@ -550,7 +443,7 @@ class Resolver {
|
|
|
550
443
|
// FIXME: this is the case where users have arrays of enums as action parameter type.
|
|
551
444
|
// Instead of building the proper type (e.g. `'A' | 'B' | ...`, we are instead building
|
|
552
445
|
// the encasing type (e.g. `string` here)
|
|
553
|
-
// We should instead aim for a proper type, i.e.
|
|
446
|
+
// We should instead aim for a proper type, i.e.
|
|
554
447
|
// this.#resolveInlineDeclarationType(element.enum, result, file)
|
|
555
448
|
// or
|
|
556
449
|
// stringifyEnumType(csnToEnumPairs(element))
|
|
@@ -558,7 +451,7 @@ class Resolver {
|
|
|
558
451
|
}
|
|
559
452
|
} else {
|
|
560
453
|
this.resolvePotentialReferenceType(element.type, result, file)
|
|
561
|
-
}
|
|
454
|
+
}
|
|
562
455
|
|
|
563
456
|
// objects and arrays
|
|
564
457
|
if (element?.items) {
|
|
@@ -574,7 +467,7 @@ class Resolver {
|
|
|
574
467
|
}
|
|
575
468
|
|
|
576
469
|
if (result.isBuiltin === false && result.isInlineDeclaration === false && !result.plainName) {
|
|
577
|
-
|
|
470
|
+
LOG.warn(`Plain name is empty for ${element?.type ?? '<empty>'}. This will probably cause issues.`)
|
|
578
471
|
}
|
|
579
472
|
return result
|
|
580
473
|
}
|
|
@@ -601,7 +494,7 @@ class Resolver {
|
|
|
601
494
|
|
|
602
495
|
/**
|
|
603
496
|
* Attempts to resolve a type that could reference another type.
|
|
604
|
-
* @param {?} val
|
|
497
|
+
* @param {?} val - the value
|
|
605
498
|
* @param {TypeResolveInfo} into - see resolveType()
|
|
606
499
|
* @param {SourceFile} file - only needed as we may call #resolveInlineDeclarationType from here. Will be expelled at some point.
|
|
607
500
|
*/
|
|
@@ -669,7 +562,7 @@ class Resolver {
|
|
|
669
562
|
// class Book { title: _cds_hana.cds.hana.VARCHAR } // <- how it would be without discarding the namespace
|
|
670
563
|
// class Book { title: _cds_hana.VARCHAR } // <- how we want it to look
|
|
671
564
|
// ```
|
|
672
|
-
const plain = t
|
|
565
|
+
const plain = last(t)
|
|
673
566
|
lib.referenced = true
|
|
674
567
|
result.type = plain
|
|
675
568
|
result.isBuiltin = false
|
|
@@ -685,6 +578,4 @@ class Resolver {
|
|
|
685
578
|
}
|
|
686
579
|
}
|
|
687
580
|
|
|
688
|
-
module.exports = {
|
|
689
|
-
Resolver
|
|
690
|
-
}
|
|
581
|
+
module.exports = { Resolver }
|
package/lib/typedefs.d.ts
CHANGED
|
@@ -31,6 +31,18 @@ export module resolver {
|
|
|
31
31
|
imports: Path[]
|
|
32
32
|
inner: TypeResolveInfo
|
|
33
33
|
}
|
|
34
|
+
|
|
35
|
+
// TODO: this will be completely replaced by EntityInfo
|
|
36
|
+
export type Untangled = {
|
|
37
|
+
// scope in case the entity is wrapped in another entity `a.b.C.D.E.f.g` -> `[C,D]`
|
|
38
|
+
scope: string[],
|
|
39
|
+
// name name of the leaf entity `a.b.C.D.E.f.g` -> `E`
|
|
40
|
+
name: string,
|
|
41
|
+
// property the property access path `a.b.C.D.E.f.g` -> `[f,g]`
|
|
42
|
+
property: string[],
|
|
43
|
+
// namespace the cds namespace of the entity `a.b.C.D.E.f.g` -> `a.b`
|
|
44
|
+
namespace: Path
|
|
45
|
+
}
|
|
34
46
|
}
|
|
35
47
|
|
|
36
48
|
export module util {
|
|
@@ -59,7 +71,12 @@ export module visitor {
|
|
|
59
71
|
}
|
|
60
72
|
|
|
61
73
|
export type VisitorOptions = {
|
|
74
|
+
/** `propertiesOptional = true` -> all properties are generated as optional ?:. (standard CAP behaviour, where properties be unavailable) */
|
|
62
75
|
propertiesOptional: boolean,
|
|
76
|
+
/**
|
|
77
|
+
* `inlineDeclarations = 'structured'` -> @see {@link inline.StructuredInlineDeclarationResolver}
|
|
78
|
+
* `inlineDeclarations = 'flat'` -> @see {@link inline.FlatInlineDeclarationResolver}
|
|
79
|
+
*/
|
|
63
80
|
inlineDeclarations: 'flat' | 'structured',
|
|
64
81
|
}
|
|
65
82
|
|
|
@@ -68,6 +85,10 @@ export module visitor {
|
|
|
68
85
|
singular: string,
|
|
69
86
|
plural: string
|
|
70
87
|
}
|
|
88
|
+
|
|
89
|
+
export type Context = {
|
|
90
|
+
entity: string
|
|
91
|
+
}
|
|
71
92
|
}
|
|
72
93
|
|
|
73
94
|
export module file {
|
package/lib/util.js
CHANGED
|
@@ -88,7 +88,7 @@ const singular4 = (dn, stripped = false) => {
|
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
90
|
* Attempts to derive the plural form of an English noun.
|
|
91
|
-
* If '@plural' is passed as annotation, that is preferred.
|
|
91
|
+
* If '@plural' is passed as annotation, that is preferred.
|
|
92
92
|
* @param {Annotations} dn - annotations
|
|
93
93
|
* @param {boolean} stripped - if true, leading namespace will be stripped
|
|
94
94
|
*/
|
|
@@ -110,7 +110,7 @@ const plural4 = (dn, stripped) => {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
* Performs a deep merge of the passed objects into the first object.
|
|
113
|
+
* Performs a deep merge of the passed objects into the first object.
|
|
114
114
|
* See Object.assign(target, source).
|
|
115
115
|
* @param {object} target - object to assign into.
|
|
116
116
|
* @param {object} source - object to assign from.
|
|
@@ -124,13 +124,13 @@ const deepMerge = (target, source) => {
|
|
|
124
124
|
|
|
125
125
|
/**
|
|
126
126
|
* Parses command line arguments into named and positional parameters.
|
|
127
|
-
* Named parameters are expected to start with a double dash (--).
|
|
127
|
+
* Named parameters are expected to start with a double dash (--).
|
|
128
128
|
* If the next argument `B` after a named parameter `A` is not a named parameter itself,
|
|
129
129
|
* `B` is used as value for `A`.
|
|
130
130
|
* If `A` and `B` are both named parameters, `A` is just treated as a flag (and may receive a default value).
|
|
131
131
|
* Only named parameters that occur in validFlags are allowed. Specifying named flags that are not listed there
|
|
132
132
|
* will cause an error.
|
|
133
|
-
* Named parameters that are either not specified or do not have a value assigned to them may draw a default value
|
|
133
|
+
* Named parameters that are either not specified or do not have a value assigned to them may draw a default value
|
|
134
134
|
* from their definition in validFlags.
|
|
135
135
|
* @param {string[]} argv - list of command line arguments
|
|
136
136
|
* @param {{[key: string]: CommandlineFlag}} validFlags - allowed flags. May specify default values.
|
|
@@ -157,9 +157,9 @@ const parseCommandlineArgs = (argv, validFlags) => {
|
|
|
157
157
|
named[arg] = validFlags[arg].default
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
const allowed = validFlags[arg]
|
|
160
|
+
const { allowed, allowedHint } = validFlags[arg]
|
|
161
161
|
if (allowed && !allowed.includes(named[arg])) {
|
|
162
|
-
throw new Error(`invalid value '${named[arg]}' for flag ${arg}. Must be one of ${allowed.join(', ')}`)
|
|
162
|
+
throw new Error(`invalid value '${named[arg]}' for flag ${arg}. Must be one of ${(allowedHint ?? allowed.join(', '))}`)
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
} else {
|