@cap-js/cds-typer 0.14.0 → 0.16.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 +22 -2
- package/lib/cli.js +2 -2
- package/lib/components/enum.js +27 -7
- package/lib/components/identifier.js +15 -0
- package/lib/components/inline.js +4 -4
- package/lib/components/resolver.js +28 -7
- package/lib/csn.js +1 -1
- package/lib/file.js +6 -5
- package/lib/util.js +1 -0
- package/lib/visitor.js +55 -17
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,14 +4,34 @@ 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.17.0 - TBD
|
|
8
|
+
|
|
9
|
+
## Version 0.16.0 - 2024-02-01
|
|
10
|
+
### Changed
|
|
11
|
+
- Changed default log level from `NONE` to `ERROR`. See the doc to manually pass in another log level for cds-typer runs
|
|
12
|
+
- Name collisions between automatically generated foreign key fields (`.…_ID`, `.…_code`, etc.) with explicitly named fields will now raise an error
|
|
13
|
+
- Generate CDS types that are actually structured types as if they were entities. This allows the correct representation of mixing aspects and types in CDS inheritance, and also fixes issues with inline enums in such types
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Externally defined enums can now be used as parameter types in actions
|
|
17
|
+
|
|
18
|
+
## Version 0.15.0 - 2023-12-21
|
|
19
|
+
### Added
|
|
20
|
+
- Support for [scoped entities](https://cap.cloud.sap/docs/cds/cdl#scoped-names)
|
|
21
|
+
- Support for [delimited identifiers](https://cap.cloud.sap/docs/cds/cdl#delimited-identifiers)
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- Inline enums are now available during runtime as well
|
|
25
|
+
- Inline enums can now be used as action parameter types as well. These enums will not have a runtime representation, but will only assert type safety!
|
|
26
|
+
- Arrays of inline enum values can now be used as action parameters too. But they will only be represented by their enclosing type for now, i.e. `string`, `number`, etc.
|
|
27
|
+
- Foreign keys of projection entities are now propagated as well
|
|
8
28
|
|
|
9
29
|
## Version 0.14.0 - 2023-12-13
|
|
10
30
|
### Added
|
|
11
31
|
- Entities that are database views now also receive typings
|
|
12
32
|
|
|
13
33
|
## Version 0.13.0 - 2023-12-06
|
|
14
|
-
###
|
|
34
|
+
### Changed
|
|
15
35
|
- Enums are now generated ecplicitly in the respective _index.js_ files and don't have to extract their values from the model at runtime anymore
|
|
16
36
|
|
|
17
37
|
### Added
|
package/lib/cli.js
CHANGED
|
@@ -23,7 +23,7 @@ const flags = {
|
|
|
23
23
|
logLevel: {
|
|
24
24
|
desc: `Minimum log level that is printed.`,
|
|
25
25
|
allowed: Object.keys(Levels),
|
|
26
|
-
default:
|
|
26
|
+
default: Levels.ERROR,
|
|
27
27
|
},
|
|
28
28
|
jsConfigPath: {
|
|
29
29
|
desc: `Path to where the jsconfig.json should be written.${EOL}If specified, ${toolName} will create a jsconfig.json file and${EOL}set it up to restrict property usage in types entities to${EOL}existing properties only.`,
|
|
@@ -90,7 +90,7 @@ const main = async (args) => {
|
|
|
90
90
|
compileFromFile(args.positional, {
|
|
91
91
|
// temporary fix until rootDir is faded out
|
|
92
92
|
outputDirectory: [args.named.outputDirectory, args.named.rootDir].find(d => d !== './') ?? './',
|
|
93
|
-
logLevel: Levels[args.named.logLevel],
|
|
93
|
+
logLevel: Levels[args.named.logLevel] ?? args.named.logLevel,
|
|
94
94
|
jsConfigPath: args.named.jsConfigPath,
|
|
95
95
|
inlineDeclarations: args.named.inlineDeclarations,
|
|
96
96
|
propertiesOptional: args.named.propertiesOptional === 'true'
|
package/lib/components/enum.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { normalise } = require('./identifier')
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Prints an enum to a buffer. To be precise, it prints
|
|
3
5
|
* a constant object and a type which together form an artificial enum.
|
|
@@ -29,19 +31,35 @@ function printEnum(buffer, name, kvs, options = {}) {
|
|
|
29
31
|
buffer.add('// enum')
|
|
30
32
|
buffer.add(`${opts.export ? 'export ' : ''}const ${name} = {`)
|
|
31
33
|
buffer.indent()
|
|
32
|
-
const vals = new Set()
|
|
33
34
|
for (const [k, v] of kvs) {
|
|
34
|
-
buffer.add(`${k}: ${v},`)
|
|
35
|
-
vals.add(v?.val ?? v) // in case of wrapped vals we need to unwrap here for the type
|
|
35
|
+
buffer.add(`${normalise(k)}: ${v},`)
|
|
36
36
|
}
|
|
37
37
|
buffer.outdent()
|
|
38
38
|
buffer.add('} as const;')
|
|
39
|
-
buffer.add(`${opts.export ? 'export ' : ''}type ${name} = ${
|
|
39
|
+
buffer.add(`${opts.export ? 'export ' : ''}type ${name} = ${stringifyEnumType(kvs)}`)
|
|
40
40
|
buffer.add('')
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Stringifies a list of enum key-value pairs into the righthand side of a TS type.
|
|
45
|
+
* @param {[string, string][]} kvs list of key-value pairs
|
|
46
|
+
* @returns {string} a stringified type
|
|
47
|
+
* @example
|
|
48
|
+
* ```js
|
|
49
|
+
* ['A', 'B', 'A'] // -> '"A" | "B"'
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
const stringifyEnumType = kvs => [...uniqueValues(kvs)].join(' | ')
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Extracts all unique values from a list of enum key-value pairs.
|
|
56
|
+
* If the value is an object, then the `.val` property is used.
|
|
57
|
+
* @param {[string, any | {val: any}][]} kvs
|
|
58
|
+
*/
|
|
59
|
+
const uniqueValues = kvs => new Set(kvs.map(([,v]) => v?.val ?? v)) // in case of wrapped vals we need to unwrap here for the type
|
|
60
|
+
|
|
43
61
|
// in case of strings, wrap in quotes and fallback to key to make sure values are attached for every key
|
|
44
|
-
const
|
|
62
|
+
const enumVal = (key, value, enumType) => enumType === 'cds.String' ? JSON.stringify(`${value ?? key}`) : value
|
|
45
63
|
|
|
46
64
|
/**
|
|
47
65
|
* Converts a CSN type describing an enum into a list of kv-pairs.
|
|
@@ -104,7 +122,8 @@ const isInlineEnumType = (element, csn) => element.enum && !(element.type in csn
|
|
|
104
122
|
* @param {string} name
|
|
105
123
|
* @param {[string, string][]} kvs a list of key-value pairs. Values that are falsey are replaced by
|
|
106
124
|
*/
|
|
107
|
-
|
|
125
|
+
// ??= for inline enums. If there is some static property of that name, we don't want to override it (for example: ".actions"
|
|
126
|
+
const stringifyEnumImplementation = (name, kvs) => `module.exports.${name} ??= { ${kvs.map(([k,v]) => `${normalise(k)}: ${v}`).join(', ')} }`
|
|
108
127
|
|
|
109
128
|
|
|
110
129
|
module.exports = {
|
|
@@ -112,5 +131,6 @@ module.exports = {
|
|
|
112
131
|
csnToEnumPairs,
|
|
113
132
|
propertyToInlineEnumName,
|
|
114
133
|
isInlineEnumType,
|
|
115
|
-
stringifyEnumImplementation
|
|
134
|
+
stringifyEnumImplementation,
|
|
135
|
+
stringifyEnumType
|
|
116
136
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const isValidIdent = /^[_$a-zA-Z][$\w]*$/
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalises an identifier to a valid JavaScript identifier.
|
|
5
|
+
* I.e. either the identifier itself or a quoted string.
|
|
6
|
+
* @param {string} ident the identifier to normalise
|
|
7
|
+
* @returns {string} the normalised identifier
|
|
8
|
+
*/
|
|
9
|
+
const normalise = ident => ident && !isValidIdent.test(ident)
|
|
10
|
+
? `"${ident}"`
|
|
11
|
+
: ident
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
normalise
|
|
15
|
+
}
|
package/lib/components/inline.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { SourceFile, Buffer } = require('../file')
|
|
2
|
+
const { normalise } = require('./identifier')
|
|
2
3
|
const { docify } = require('./wrappers')
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -8,7 +9,6 @@ const { docify } = require('./wrappers')
|
|
|
8
9
|
* their resolution mechanism.
|
|
9
10
|
*/
|
|
10
11
|
class InlineDeclarationResolver {
|
|
11
|
-
|
|
12
12
|
/**
|
|
13
13
|
* @param {string} name
|
|
14
14
|
* @param {import('./resolver').TypeResolveInfo} type
|
|
@@ -155,7 +155,7 @@ class FlatInlineDeclarationResolver extends InlineDeclarationResolver {
|
|
|
155
155
|
flatten(prefix, type) {
|
|
156
156
|
return type.typeInfo.structuredType
|
|
157
157
|
? Object.entries(type.typeInfo.structuredType).map(([k,v]) => this.flatten(`${this.prefix(prefix)}${k}`, v))
|
|
158
|
-
: [`${prefix}${this.getPropertyTypeSeparator()} ${this.getPropertyDatatype(type)}`]
|
|
158
|
+
: [`${normalise(prefix)}${this.getPropertyTypeSeparator()} ${this.getPropertyDatatype(type)}`]
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
printInlineType(name, type, buffer) {
|
|
@@ -198,7 +198,7 @@ class StructuredInlineDeclarationResolver extends InlineDeclarationResolver {
|
|
|
198
198
|
this.printDepth++
|
|
199
199
|
const lineEnding = this.printDepth > 1 ? ',' : statementEnd
|
|
200
200
|
if (type.typeInfo.structuredType) {
|
|
201
|
-
const prefix = name ? `${name}${this.getPropertyTypeSeparator()}`: ''
|
|
201
|
+
const prefix = name ? `${normalise(name)}${this.getPropertyTypeSeparator()}`: ''
|
|
202
202
|
buffer.add(`${prefix} {`)
|
|
203
203
|
buffer.indent()
|
|
204
204
|
for (const [n, t] of Object.entries(type.typeInfo.structuredType)) {
|
|
@@ -207,7 +207,7 @@ class StructuredInlineDeclarationResolver extends InlineDeclarationResolver {
|
|
|
207
207
|
buffer.outdent()
|
|
208
208
|
buffer.add(`}${this.getPropertyDatatype(type, '')}${lineEnding}`)
|
|
209
209
|
} else {
|
|
210
|
-
buffer.add(`${name}${this.getPropertyTypeSeparator()} ${this.getPropertyDatatype(type)}${lineEnding}`)
|
|
210
|
+
buffer.add(`${normalise(name)}${this.getPropertyTypeSeparator()} ${this.getPropertyDatatype(type)}${lineEnding}`)
|
|
211
211
|
}
|
|
212
212
|
this.printDepth--
|
|
213
213
|
return buffer
|
|
@@ -371,13 +371,34 @@ class Resolver {
|
|
|
371
371
|
result.isInlineDeclaration = true
|
|
372
372
|
} else {
|
|
373
373
|
if (isInlineEnumType(element, this.csn)) {
|
|
374
|
-
//
|
|
375
|
-
//
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
374
|
+
// element.parent is only set if the enum is attached to an entity's property.
|
|
375
|
+
// If it is missing then we are dealing with an inline parameter type of an action.
|
|
376
|
+
// Edge case: element.parent is set, but no .name property is attached. This happens
|
|
377
|
+
// for inline enums inside types:
|
|
378
|
+
// ```cds
|
|
379
|
+
// type T {
|
|
380
|
+
// x : String enum { ... }; // no element.name for x
|
|
381
|
+
// }
|
|
382
|
+
// ```
|
|
383
|
+
// In that case, we currently resolve to the more general type (cds.String, here)
|
|
384
|
+
if (element.parent?.name) {
|
|
385
|
+
result.isInlineDeclaration = true
|
|
386
|
+
// we use the singular as the initial declaration of these enums takes place
|
|
387
|
+
// while defining the singular class. Which therefore uses the singular over the plural name.
|
|
388
|
+
const cleanEntityName = util.singular4(element.parent, true)
|
|
389
|
+
const enumName = propertyToInlineEnumName(cleanEntityName, element.name)
|
|
390
|
+
result.type = enumName
|
|
391
|
+
result.plainName = enumName
|
|
392
|
+
} else {
|
|
393
|
+
// FIXME: this is the case where users have arrays of enums as action parameter type.
|
|
394
|
+
// Instead of building the proper type (e.g. `'A' | 'B' | ...`, we are instead building
|
|
395
|
+
// the encasing type (e.g. `string` here)
|
|
396
|
+
// We should instead aim for a proper type, i.e.
|
|
397
|
+
// this.#resolveInlineDeclarationType(element.enum, result, file)
|
|
398
|
+
// or
|
|
399
|
+
// stringifyEnumType(csnToEnumPairs(element))
|
|
400
|
+
this.#resolveTypeName(element.type, result)
|
|
401
|
+
}
|
|
381
402
|
} else {
|
|
382
403
|
this.resolvePotentialReferenceType(element.type, result, file)
|
|
383
404
|
}
|
package/lib/csn.js
CHANGED
|
@@ -240,4 +240,4 @@ const isView = entity => entity.query && !entity.projection
|
|
|
240
240
|
*/
|
|
241
241
|
const isUnresolved = entity => entity._unresolved === true
|
|
242
242
|
|
|
243
|
-
module.exports = { amendCSN, isView, isUnresolved }
|
|
243
|
+
module.exports = { amendCSN, isView, isUnresolved, propagateForeignKeys }
|
package/lib/file.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('fs').promises
|
|
4
4
|
const { readFileSync } = require('fs')
|
|
5
5
|
const { printEnum, propertyToInlineEnumName, stringifyEnumImplementation } = require('./components/enum')
|
|
6
|
+
const { normalise } = require('./components/identifier')
|
|
6
7
|
const path = require('path')
|
|
7
8
|
|
|
8
9
|
const AUTO_GEN_NOTE = "// This is an automatically generated file. Please do not change its contents manually!"
|
|
@@ -151,9 +152,9 @@ class SourceFile extends File {
|
|
|
151
152
|
* ```
|
|
152
153
|
*/
|
|
153
154
|
static stringifyLambda({name, parameters=[], returns='any', initialiser, isStatic=false}) {
|
|
154
|
-
const parameterTypes = parameters.map(([n, t]) => `${n}: ${t}`).join(', ')
|
|
155
|
+
const parameterTypes = parameters.map(([n, t]) => `${normalise(n)}: ${t}`).join(', ')
|
|
155
156
|
const callableSignature = `(${parameterTypes}): ${returns}`
|
|
156
|
-
let prefix = name ? `${name}: `: ''
|
|
157
|
+
let prefix = name ? `${normalise(name)}: `: ''
|
|
157
158
|
if (prefix && isStatic) {
|
|
158
159
|
prefix = `static ${prefix}`
|
|
159
160
|
}
|
|
@@ -268,7 +269,7 @@ class SourceFile extends File {
|
|
|
268
269
|
*/
|
|
269
270
|
addInlineEnum(entityCleanName, entityFqName, propertyName, kvs) {
|
|
270
271
|
this.enums.data.push({
|
|
271
|
-
name:
|
|
272
|
+
name: `${entityCleanName}.${propertyName}`,
|
|
272
273
|
property: propertyName,
|
|
273
274
|
kvs,
|
|
274
275
|
fq: `${entityCleanName}.${propertyName}`
|
|
@@ -375,11 +376,11 @@ class SourceFile extends File {
|
|
|
375
376
|
this.types.join(),
|
|
376
377
|
this.enums.buffer.join(),
|
|
377
378
|
this.inlineEnums.buffer.join(), // needs to be before classes
|
|
378
|
-
namespaces.join(),
|
|
379
379
|
this.aspects.join(), // needs to be before classes
|
|
380
380
|
this.classes.join(),
|
|
381
381
|
this.events.buffer.join(),
|
|
382
|
-
this.actions.buffer.join()
|
|
382
|
+
this.actions.buffer.join(),
|
|
383
|
+
namespaces.join() // needs to be after classes for possible declaration merging
|
|
383
384
|
].filter(Boolean).join('\n')
|
|
384
385
|
}
|
|
385
386
|
|
package/lib/util.js
CHANGED
|
@@ -51,6 +51,7 @@ const getPluralAnnotation = (csn) => csn[annotations.plural.find(a => Object.has
|
|
|
51
51
|
* unlocalize("{i18n>Foo}") -> "Foo"
|
|
52
52
|
* @param {string} name the entity name (singular or plural).
|
|
53
53
|
* @returns {string} the name without localisation syntax or untouched.
|
|
54
|
+
* @deprecated we have dropped this feature altogether, users specify custom names via @singular/@plural now
|
|
54
55
|
*/
|
|
55
56
|
const unlocalize = (name) => {
|
|
56
57
|
const match = name.match(/\{i18n>(.*)\}/)
|
package/lib/visitor.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
const util = require('./util')
|
|
4
4
|
|
|
5
|
-
const { amendCSN, isView, isUnresolved } = require('./csn')
|
|
5
|
+
const { amendCSN, isView, isUnresolved, propagateForeignKeys } = require('./csn')
|
|
6
6
|
// eslint-disable-next-line no-unused-vars
|
|
7
7
|
const { SourceFile, 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
|
-
const { csnToEnumPairs, propertyToInlineEnumName, isInlineEnumType } = require('./components/enum')
|
|
12
|
+
const { csnToEnumPairs, propertyToInlineEnumName, isInlineEnumType, stringifyEnumType } = require('./components/enum')
|
|
13
13
|
|
|
14
14
|
/** @typedef {import('./file').File} File */
|
|
15
15
|
/** @typedef {{ entity: String }} Context */
|
|
@@ -63,6 +63,7 @@ class Visitor {
|
|
|
63
63
|
*/
|
|
64
64
|
constructor(csn, options = {}, logger = new Logger()) {
|
|
65
65
|
amendCSN(csn.xtended)
|
|
66
|
+
propagateForeignKeys(csn.inferred)
|
|
66
67
|
this.options = { ...defaults, ...options }
|
|
67
68
|
this.logger = logger
|
|
68
69
|
this.csn = csn
|
|
@@ -130,6 +131,24 @@ class Visitor {
|
|
|
130
131
|
}
|
|
131
132
|
}
|
|
132
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Retrieves all the keys from an entity.
|
|
136
|
+
* That is: all keys that are present in both inferred, as well as xtended flavour.
|
|
137
|
+
* @returns {[string, object][]} array of key name and key element pairs
|
|
138
|
+
*/
|
|
139
|
+
#keys(name) {
|
|
140
|
+
// FIXME: this is actually pretty bad, as not only have to propagate keys through
|
|
141
|
+
// both flavours of CSN (see constructor), but we are now also collecting them from
|
|
142
|
+
// both flavours and deduplicating them.
|
|
143
|
+
// xtended contains keys that have been inherited from parents
|
|
144
|
+
// inferred contains keys from queried entities (thing `entity Foo as select from Bar`, where Bar has keys)
|
|
145
|
+
// So we currently need them both.
|
|
146
|
+
return Object.entries({
|
|
147
|
+
...this.csn.inferred.definitions[name]?.keys ?? {},
|
|
148
|
+
...this.csn.xtended.definitions[name]?.keys ?? {}
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
133
152
|
/**
|
|
134
153
|
* Transforms an entity or CDS aspect into a JS aspect (aka mixin).
|
|
135
154
|
* That is, for an element A we get:
|
|
@@ -168,10 +187,15 @@ class Visitor {
|
|
|
168
187
|
// lookup in cds.definitions can fail for inline structs.
|
|
169
188
|
// We don't really have to care for this case, as keys from such structs are _not_ propagated to
|
|
170
189
|
// the containing entity.
|
|
171
|
-
for (const [kname, kelement] of
|
|
190
|
+
for (const [kname, kelement] of this.#keys(element.target)) {
|
|
172
191
|
if (this.resolver.getMaxCardinality(element) === 1) {
|
|
173
|
-
|
|
174
|
-
|
|
192
|
+
const foreignKey = `${ename}_${kname}`
|
|
193
|
+
if (Object.hasOwn(entity.elements, foreignKey)) {
|
|
194
|
+
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.`)
|
|
195
|
+
} else {
|
|
196
|
+
kelement.isRefNotNull = !!element.notNull || !!element.key
|
|
197
|
+
this.visitElement(foreignKey, kelement, file, buffer)
|
|
198
|
+
}
|
|
175
199
|
}
|
|
176
200
|
}
|
|
177
201
|
}
|
|
@@ -247,11 +271,13 @@ class Visitor {
|
|
|
247
271
|
const file = this.getNamespaceFile(ns)
|
|
248
272
|
// entities are expected to be in plural anyway, so we would favour the regular name.
|
|
249
273
|
// If the user decides to pass a @plural annotation, that gets precedence over the regular name.
|
|
250
|
-
let plural = util.
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
274
|
+
let plural = this.resolver.trimNamespace(util.getPluralAnnotation(entity) ? util.plural4(entity, false) : name)
|
|
275
|
+
const singular = this.resolver.trimNamespace(util.singular4(entity, true))
|
|
276
|
+
// trimNamespace does not properly detect scoped entities, like A.B where both A and B are
|
|
277
|
+
// entities. So to see if we would run into a naming collision, we forcefully take the last
|
|
278
|
+
// part of the name, so "A.B" and "A.Bs" just become "B" and "Bs" to be compared.
|
|
279
|
+
// FIXME: put this in a util function
|
|
280
|
+
if (singular.split('.').at(-1) === plural.split('.').at(-1)) {
|
|
255
281
|
plural += '_'
|
|
256
282
|
this.logger.warning(
|
|
257
283
|
`Derived singular and plural forms for '${singular}' are the same. This usually happens when your CDS entities are named following singular flexion. Consider naming your entities in plural or providing '@singular:'/ '@plural:' annotations to have a clear distinction between the two. Plural form will be renamed to '${plural}' to avoid compilation errors within the output.`
|
|
@@ -259,7 +285,7 @@ class Visitor {
|
|
|
259
285
|
}
|
|
260
286
|
if (singular in this.csn.xtended.definitions) {
|
|
261
287
|
this.logger.error(
|
|
262
|
-
`Derived singular '${singular}' for your entity '${name}', already exists. The resulting types will be erronous.
|
|
288
|
+
`Derived singular '${singular}' for your entity '${name}', already exists. The resulting types will be erronous. Consider using '@singular:'/ '@plural:' annotations in your model or move the offending declarations into different namespaces to resolve this collision.`
|
|
263
289
|
)
|
|
264
290
|
}
|
|
265
291
|
file.addClass(singular, name)
|
|
@@ -281,7 +307,7 @@ class Visitor {
|
|
|
281
307
|
docify(entity.doc).forEach((d) => buffer.add(d))
|
|
282
308
|
}
|
|
283
309
|
|
|
284
|
-
this.#aspectify(name, entity,
|
|
310
|
+
this.#aspectify(name, entity, buffer, singular)
|
|
285
311
|
|
|
286
312
|
// PLURAL
|
|
287
313
|
if (plural.includes('.')) {
|
|
@@ -311,11 +337,19 @@ class Visitor {
|
|
|
311
337
|
.filter(([, type]) => type?.type !== '$self' && !(type.items?.type === '$self'))
|
|
312
338
|
.map(([name, type]) => [
|
|
313
339
|
name,
|
|
314
|
-
this
|
|
340
|
+
this.#stringifyFunctionParamType(type, file)
|
|
315
341
|
])
|
|
316
342
|
: []
|
|
317
343
|
}
|
|
318
344
|
|
|
345
|
+
#stringifyFunctionParamType(type, file) {
|
|
346
|
+
// if type.type is not 'cds.String', 'cds.Integer', ..., then we are actually looking
|
|
347
|
+
// at a named enum type. In that case also resolve that type name
|
|
348
|
+
return type.enum && type.type.startsWith('cds.')
|
|
349
|
+
? stringifyEnumType(csnToEnumPairs(type))
|
|
350
|
+
: this.inlineDeclarationResolver.getPropertyDatatype(this.resolver.resolveAndRequire(type, file))
|
|
351
|
+
}
|
|
352
|
+
|
|
319
353
|
#printFunction(name, func) {
|
|
320
354
|
// FIXME: mostly duplicate of printAction -> reuse
|
|
321
355
|
this.logger.debug(`Printing function ${name}:\n${JSON.stringify(func, null, 2)}`)
|
|
@@ -412,12 +446,16 @@ class Visitor {
|
|
|
412
446
|
case 'function':
|
|
413
447
|
this.#printAction(name, entity)
|
|
414
448
|
break
|
|
415
|
-
case 'type':
|
|
416
|
-
this.#printType(name, entity)
|
|
417
|
-
break
|
|
418
449
|
case 'aspect':
|
|
419
450
|
this.#printAspect(name, entity)
|
|
420
451
|
break
|
|
452
|
+
case 'type': {
|
|
453
|
+
// types like inline definitions can be used very similarly to entities.
|
|
454
|
+
// They can be extended, contain inline enums, etc., so we treat them as entities.
|
|
455
|
+
const handler = entity.elements ? this.#printEntity : this.#printType
|
|
456
|
+
handler.bind(this)(name, entity)
|
|
457
|
+
break
|
|
458
|
+
}
|
|
421
459
|
case 'event':
|
|
422
460
|
this.#printEvent(name, entity)
|
|
423
461
|
break
|
|
@@ -425,7 +463,7 @@ class Visitor {
|
|
|
425
463
|
this.#printService(name, entity)
|
|
426
464
|
break
|
|
427
465
|
default:
|
|
428
|
-
this.logger.
|
|
466
|
+
this.logger.debug(`Unhandled entity kind '${entity.kind}'.`)
|
|
429
467
|
}
|
|
430
468
|
}
|
|
431
469
|
|