@cap-js/cds-typer 0.17.0 → 0.18.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 +8 -1
- package/lib/components/resolver.js +96 -23
- package/lib/csn.js +37 -3
- package/lib/util.js +2 -2
- package/lib/visitor.js +46 -28
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,14 @@ 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.19.0 - TBD
|
|
8
|
+
|
|
9
|
+
## Version 0.18.0 - 2024-03-12
|
|
10
|
+
### Added
|
|
11
|
+
- Improved support for projections, including projections on inline definitions, and on views, as well as support for explicit exclusion and selection of properties
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- [breaking] CDS `type` definitions will not be inflected. Whatever inflection you define them in will be assumed treated as a singular form and will not receive a plural form anymore
|
|
8
15
|
|
|
9
16
|
## Version 0.17.0 - 2024-03-05
|
|
10
17
|
### Fixed
|
|
@@ -67,6 +67,34 @@ const Builtins = {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
class Resolver {
|
|
70
|
+
|
|
71
|
+
#caches = {
|
|
72
|
+
/**
|
|
73
|
+
* @type {{ [qualifier: string]: string }}
|
|
74
|
+
*/
|
|
75
|
+
namespaces: {},
|
|
76
|
+
/**
|
|
77
|
+
* @type {{ [qualifier: string]: string }}
|
|
78
|
+
*/
|
|
79
|
+
propertyAccesses: {}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {string} qualifier
|
|
84
|
+
* @returns {string?}
|
|
85
|
+
*/
|
|
86
|
+
#getCachedNamespace (qualifier) {
|
|
87
|
+
return this.#caches.namespaces[qualifier]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {string} qualifier
|
|
92
|
+
* @returns {string?}
|
|
93
|
+
*/
|
|
94
|
+
#getCachedPropertyAccess (qualifier) {
|
|
95
|
+
return this.#caches.propertyAccesses[qualifier]
|
|
96
|
+
}
|
|
97
|
+
|
|
70
98
|
get csn() { return this.visitor.csn.inferred }
|
|
71
99
|
|
|
72
100
|
/** @param {Visitor} visitor */
|
|
@@ -91,18 +119,18 @@ class Resolver {
|
|
|
91
119
|
* to end up with both the resolved Path of the namespace,
|
|
92
120
|
* and the clean name of the class.
|
|
93
121
|
* @param {string} fq the fully qualified name of an entity.
|
|
94
|
-
* @returns {[Path, string]} a tuple, [0] holding the path to the namespace, [1] holding the clean name of the entity
|
|
122
|
+
* @returns {[Path, string, string[]]} a tuple, [0] holding the path to the namespace, [1] holding the clean name of the entity, [2] holding chained property accesses
|
|
95
123
|
*/
|
|
96
124
|
untangle(fq) {
|
|
97
125
|
const ns = this.resolveNamespace(fq.split('.'))
|
|
98
|
-
const name = this.trimNamespace(fq)
|
|
99
|
-
return [new Path(ns.split('.')), name]
|
|
126
|
+
const name = this.trimNamespace(fq).split('.').at(-1) // nested entities would return Foo.Bar, so we only take the last part to get the actual entity name
|
|
127
|
+
return [new Path(ns.split('.')), name, this.findPropertyAccess(name)]
|
|
100
128
|
}
|
|
101
129
|
|
|
102
130
|
/**
|
|
103
131
|
* Convenience method to shave off the namespace of a fully qualified path.
|
|
104
132
|
* More specifically, only the parts (reading from right to left) that are of
|
|
105
|
-
* kind "entity" are retained.
|
|
133
|
+
* kind "entity" or something similar are retained.
|
|
106
134
|
* a.b.c.Foo -> Foo
|
|
107
135
|
* Bar -> Bar
|
|
108
136
|
* sap.cap.Book.text -> Book.text (assuming Book and text are both of kind "entity")
|
|
@@ -110,19 +138,15 @@ class Resolver {
|
|
|
110
138
|
* @returns {string} the entity name without leading namespace.
|
|
111
139
|
*/
|
|
112
140
|
trimNamespace(p) {
|
|
113
|
-
|
|
114
|
-
// start on right side, go up while we have an entity at hand
|
|
115
|
-
// we cant start on left side, as that clashes with undefined entities like "sap"
|
|
141
|
+
if (this.#getCachedNamespace(p)) return this.#getCachedNamespace(p)
|
|
116
142
|
const parts = p.split('.')
|
|
117
|
-
if (parts.length <= 1)
|
|
118
|
-
return p
|
|
119
|
-
}
|
|
143
|
+
if (parts.length <= 1) return p
|
|
120
144
|
|
|
145
|
+
// start on right side, go up while we have an entity at hand
|
|
146
|
+
// we cant start on left side, as that clashes with undefined entities like "sap"
|
|
147
|
+
const defs = this.csn.definitions
|
|
121
148
|
let qualifier = parts.join('.')
|
|
122
|
-
while (
|
|
123
|
-
this.csn.definitions[qualifier] &&
|
|
124
|
-
['entity', 'type', 'aspect', 'event'].includes(this.csn.definitions[qualifier].kind)
|
|
125
|
-
) {
|
|
149
|
+
while (defs[qualifier] && ['entity', 'type', 'aspect', 'event'].includes(defs[qualifier].kind)) {
|
|
126
150
|
parts.pop()
|
|
127
151
|
qualifier = parts.join('.')
|
|
128
152
|
}
|
|
@@ -130,6 +154,51 @@ class Resolver {
|
|
|
130
154
|
return qualifier ? p.substring(qualifier.length + 1) : p
|
|
131
155
|
}
|
|
132
156
|
|
|
157
|
+
/**
|
|
158
|
+
* From a fully qualified path, finds the parts that are property accesses.
|
|
159
|
+
* This are specifically used in CDS' `typeof` syntax, where a property can
|
|
160
|
+
* refer to another entity's property type.
|
|
161
|
+
* @example
|
|
162
|
+
* ```
|
|
163
|
+
* namespace namespace;
|
|
164
|
+
* entity Entity {
|
|
165
|
+
* x: Composition of { y: Composition of z: { a: Integer }}
|
|
166
|
+
* }
|
|
167
|
+
*
|
|
168
|
+
* // somewhere else
|
|
169
|
+
* entity Foo {
|
|
170
|
+
* x: namespace.Entity.x.y.z;
|
|
171
|
+
* }
|
|
172
|
+
* ```
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```js
|
|
176
|
+
* findPropertyAccess('namespace') // []
|
|
177
|
+
* findPropertyAccess('namespace.Entity') // []
|
|
178
|
+
* findPropertyAccess('namespace.Entity.x') // ['x']
|
|
179
|
+
* findPropertyAccess('namespace.Entity.x.y.z') // ['x', 'y', 'z']
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
findPropertyAccess(p) {
|
|
183
|
+
if (this.#getCachedPropertyAccess(p)) return this.#getCachedPropertyAccess(p)
|
|
184
|
+
const parts = p.split('.')
|
|
185
|
+
if (parts.length <= 1) return []
|
|
186
|
+
|
|
187
|
+
// start on right side, go up while we have an entity at hand
|
|
188
|
+
// we cant start on left side, as that clashes with undefined entities like "sap"
|
|
189
|
+
// sadly we have to use the extended flavour here, as inferred csn contains artificial entities for
|
|
190
|
+
// this kind of property access
|
|
191
|
+
const defs = this.visitor.csn.xtended.definitions
|
|
192
|
+
const properties = []
|
|
193
|
+
let qualifier = parts.join('.')
|
|
194
|
+
while (!defs[qualifier] && parts.length) {
|
|
195
|
+
properties.unshift(parts.pop())
|
|
196
|
+
qualifier = parts.join('.')
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return properties
|
|
200
|
+
}
|
|
201
|
+
|
|
133
202
|
/**
|
|
134
203
|
* Generates singular and plural inflection for the passed type.
|
|
135
204
|
* Several cases are covered here:
|
|
@@ -149,7 +218,7 @@ class Resolver {
|
|
|
149
218
|
if (typeInfo.csn?.kind === 'type') {
|
|
150
219
|
return {
|
|
151
220
|
singular: typeInfo.plainName,
|
|
152
|
-
plural: typeInfo.plainName,
|
|
221
|
+
plural: createArrayOf(typeInfo.plainName),
|
|
153
222
|
typeName: typeInfo.plainName,
|
|
154
223
|
}
|
|
155
224
|
}
|
|
@@ -175,14 +244,13 @@ class Resolver {
|
|
|
175
244
|
plural = createArrayOf(typeName)
|
|
176
245
|
} else {
|
|
177
246
|
// TODO: make sure the resolution still works. Currently, we only cut off the namespace!
|
|
178
|
-
|
|
179
|
-
|
|
247
|
+
plural = util.getPluralAnnotation(typeInfo.csn) ?? typeInfo.plainName
|
|
248
|
+
singular = util.getSingularAnnotation(typeInfo.csn) ?? util.singular4(typeInfo.csn, true) // util.singular4(typeInfo.csn, true) // can not use `plural` to honor possible @singular annotation
|
|
180
249
|
|
|
181
250
|
// don't slice off namespace if it isn't part of the inflected name.
|
|
182
251
|
// This happens when the user adds an annotation and singular4 therefore
|
|
183
|
-
// already returns an identifier without namespace
|
|
252
|
+
// already returns an identifier without namespace. Plural has ns already sliced off.
|
|
184
253
|
if (namespace && singular.startsWith(namespace)) {
|
|
185
|
-
// TODO: not totally sure why plural doesn't have to be sliced
|
|
186
254
|
singular = singular.slice(namespace.length + 1)
|
|
187
255
|
}
|
|
188
256
|
|
|
@@ -192,7 +260,7 @@ class Resolver {
|
|
|
192
260
|
}
|
|
193
261
|
}
|
|
194
262
|
if (!singular || !plural) {
|
|
195
|
-
this.logger.error(`Singular ('${singular}') or plural ('${plural}') for '${typeName}' is empty.`)
|
|
263
|
+
this.visitor.logger.error(`Singular ('${singular}') or plural ('${plural}') for '${typeName}' is empty.`)
|
|
196
264
|
}
|
|
197
265
|
|
|
198
266
|
return { typeName, singular, plural }
|
|
@@ -278,6 +346,13 @@ class Resolver {
|
|
|
278
346
|
typeInfo.inflection = this.inflect(typeInfo)
|
|
279
347
|
}
|
|
280
348
|
|
|
349
|
+
const [,,propertyAccess] = this.untangle(typeName)
|
|
350
|
+
if (propertyAccess.length) {
|
|
351
|
+
const element = typeName.slice(0, -propertyAccess.join('.').length - 1)
|
|
352
|
+
const access = this.visitor.inlineDeclarationResolver.getTypeLookup(propertyAccess)
|
|
353
|
+
typeName = deepRequire(element) + access
|
|
354
|
+
}
|
|
355
|
+
|
|
281
356
|
// add fallback inflection. Mainly needed for array-of with builtin types.
|
|
282
357
|
// (array-of relies on inflection being present, which is not the case in builtin)
|
|
283
358
|
typeInfo.inflection ??= {
|
|
@@ -349,9 +424,7 @@ class Resolver {
|
|
|
349
424
|
resolveType(element, file) {
|
|
350
425
|
// while resolving inline declarations, it can happen that we land here
|
|
351
426
|
// with an already resolved type. In that case, just return the type we have.
|
|
352
|
-
if (element && Object.hasOwn(element, 'isBuiltin'))
|
|
353
|
-
return element
|
|
354
|
-
}
|
|
427
|
+
if (element && Object.hasOwn(element, 'isBuiltin')) return element
|
|
355
428
|
|
|
356
429
|
const cardinality = this.getMaxCardinality(element)
|
|
357
430
|
|
package/lib/csn.js
CHANGED
|
@@ -14,8 +14,8 @@ class DraftUnroller {
|
|
|
14
14
|
this.#csn = c
|
|
15
15
|
this.#entities = Object.values(c.definitions)
|
|
16
16
|
this.#projections = this.#entities.reduce((pjs, entity) => {
|
|
17
|
-
if (entity
|
|
18
|
-
pjs[entity.name] = entity
|
|
17
|
+
if (isProjection(entity)) {
|
|
18
|
+
pjs[entity.name] = getProjectionTarget(entity)
|
|
19
19
|
}
|
|
20
20
|
return pjs
|
|
21
21
|
}, {})
|
|
@@ -234,12 +234,46 @@ function amendCSN(csn) {
|
|
|
234
234
|
*/
|
|
235
235
|
const isView = entity => entity.query && !entity.projection
|
|
236
236
|
|
|
237
|
+
const isProjection = entity => entity.projection
|
|
238
|
+
|
|
239
|
+
const getViewTarget = entity => entity.query?.SELECT?.from?.ref?.[0]
|
|
240
|
+
|
|
241
|
+
const getProjectionTarget = entity => entity.projection?.from?.ref?.[0]
|
|
242
|
+
|
|
243
|
+
const getProjectionAliases = entity => {
|
|
244
|
+
const aliases = {}
|
|
245
|
+
let all = false
|
|
246
|
+
for (const col of entity?.projection?.columns ?? []) {
|
|
247
|
+
if (col === '*') {
|
|
248
|
+
all = true
|
|
249
|
+
} else if (col.ref) {
|
|
250
|
+
(aliases[col.ref[0]] ??= []).push(col.as ?? col.ref[0])
|
|
251
|
+
} else {
|
|
252
|
+
// TODO: error, casting seems to miss ref...
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return { aliases, all }
|
|
256
|
+
}
|
|
257
|
+
|
|
237
258
|
const isDraftEnabled = entity => entity['@odata.draft.enabled'] === true
|
|
238
259
|
|
|
260
|
+
const isType = entity => entity?.kind === 'type'
|
|
261
|
+
|
|
239
262
|
/**
|
|
240
263
|
* @see isView
|
|
241
264
|
* Unresolved entities have to be looked up from inferred csn.
|
|
242
265
|
*/
|
|
243
266
|
const isUnresolved = entity => entity._unresolved === true
|
|
244
267
|
|
|
245
|
-
module.exports = {
|
|
268
|
+
module.exports = {
|
|
269
|
+
amendCSN,
|
|
270
|
+
isView,
|
|
271
|
+
isProjection,
|
|
272
|
+
isDraftEnabled,
|
|
273
|
+
isUnresolved,
|
|
274
|
+
isType,
|
|
275
|
+
getProjectionTarget,
|
|
276
|
+
getProjectionAliases,
|
|
277
|
+
getViewTarget,
|
|
278
|
+
propagateForeignKeys
|
|
279
|
+
}
|
package/lib/util.js
CHANGED
|
@@ -70,7 +70,7 @@ const singular4 = (dn, stripped = false) => {
|
|
|
70
70
|
n = n.match(last)[0]
|
|
71
71
|
}
|
|
72
72
|
return (
|
|
73
|
-
getSingularAnnotation(dn)
|
|
73
|
+
getSingularAnnotation(dn) ??
|
|
74
74
|
(/.*species|news$/i.test(n)
|
|
75
75
|
? n
|
|
76
76
|
: /.*ess$/.test(n)
|
|
@@ -102,7 +102,7 @@ const plural4 = (dn, stripped) => {
|
|
|
102
102
|
n = n.match(last)[0]
|
|
103
103
|
}
|
|
104
104
|
return (
|
|
105
|
-
getPluralAnnotation(dn)
|
|
105
|
+
getPluralAnnotation(dn) ??
|
|
106
106
|
(/.*analysis|status|species|news$/i.test(n)
|
|
107
107
|
? n
|
|
108
108
|
: /.*[^aeiou]y$/.test(n)
|
package/lib/visitor.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const util = require('./util')
|
|
4
4
|
|
|
5
|
-
const { amendCSN, isView, isUnresolved, propagateForeignKeys, isDraftEnabled } = require('./csn')
|
|
5
|
+
const { amendCSN, isView, isUnresolved, propagateForeignKeys, isDraftEnabled, isType, isProjection } = require('./csn')
|
|
6
6
|
// eslint-disable-next-line no-unused-vars
|
|
7
7
|
const { SourceFile, FileRepository, baseDefinitions, Buffer } = require('./file')
|
|
8
8
|
const { FlatInlineDeclarationResolver, StructuredInlineDeclarationResolver } = require('./components/inline')
|
|
@@ -93,7 +93,7 @@ class Visitor {
|
|
|
93
93
|
for (const [name, entity] of Object.entries(this.csn.xtended.definitions)) {
|
|
94
94
|
if (isView(entity)) {
|
|
95
95
|
this.visitEntity(name, this.csn.inferred.definitions[name])
|
|
96
|
-
} else if (!isUnresolved(entity)) {
|
|
96
|
+
} else if (isProjection(entity) || !isUnresolved(entity)) {
|
|
97
97
|
this.visitEntity(name, entity)
|
|
98
98
|
} else {
|
|
99
99
|
this.logger.warning(`Skipping unresolved entity: ${name}`)
|
|
@@ -149,13 +149,12 @@ class Visitor {
|
|
|
149
149
|
* @param {string} name the name of the entity
|
|
150
150
|
* @param {CSN} element the pointer into the CSN to extract the elements from
|
|
151
151
|
* @param {Buffer} buffer the buffer to write the resulting definitions into
|
|
152
|
-
* @param {string
|
|
152
|
+
* @param {{cleanName?: string}} options
|
|
153
153
|
*/
|
|
154
|
-
#aspectify(name, entity, buffer,
|
|
155
|
-
const clean = cleanName ?? this.resolver.trimNamespace(name)
|
|
154
|
+
#aspectify(name, entity, buffer, options = {}) {
|
|
155
|
+
const clean = options?.cleanName ?? this.resolver.trimNamespace(name)
|
|
156
156
|
const ns = this.resolver.resolveNamespace(name.split('.'))
|
|
157
157
|
const file = this.fileRepository.getNamespaceFile(ns)
|
|
158
|
-
|
|
159
158
|
const identSingular = (name) => name
|
|
160
159
|
const identAspect = (name) => `_${name}Aspect`
|
|
161
160
|
|
|
@@ -165,9 +164,11 @@ class Visitor {
|
|
|
165
164
|
buffer.addIndentedBlock(`export function ${identAspect(clean)}<TBase extends new (...args: any[]) => object>(Base: TBase) {`, function () {
|
|
166
165
|
buffer.addIndentedBlock(`return class ${clean} extends Base {`, function () {
|
|
167
166
|
const enums = []
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
167
|
+
for (let [ename, element] of Object.entries(entity.elements ?? {})) {
|
|
168
|
+
if (element.target && /\.texts?/.test(element.target)) {
|
|
169
|
+
this.logger.warning(`referring to .texts property in ${name}. This is currently not supported and will be ignored.`)
|
|
170
|
+
continue
|
|
171
|
+
}
|
|
171
172
|
this.visitElement(ename, element, file, buffer)
|
|
172
173
|
|
|
173
174
|
// make foreign keys explicit
|
|
@@ -241,24 +242,30 @@ class Visitor {
|
|
|
241
242
|
#printEntity(name, entity) {
|
|
242
243
|
// static .name has to be defined more forcefully: https://github.com/microsoft/TypeScript/issues/442
|
|
243
244
|
const overrideNameProperty = (clazz, content) => `Object.defineProperty(${clazz}, 'name', { value: '${content}' })`
|
|
244
|
-
const clean = this.resolver.
|
|
245
|
-
const ns = this.resolver.resolveNamespace(name.split('.'))
|
|
245
|
+
const [ns, clean] = this.resolver.untangle(name)
|
|
246
246
|
const file = this.fileRepository.getNamespaceFile(ns)
|
|
247
247
|
// entities are expected to be in plural anyway, so we would favour the regular name.
|
|
248
248
|
// If the user decides to pass a @plural annotation, that gets precedence over the regular name.
|
|
249
|
+
|
|
250
|
+
/*
|
|
249
251
|
let plural = this.resolver.trimNamespace(util.getPluralAnnotation(entity) ? util.plural4(entity, false) : name)
|
|
250
252
|
const singular = this.resolver.trimNamespace(util.singular4(entity, true))
|
|
253
|
+
*/
|
|
254
|
+
let { singular, plural } = this.resolver.inflect({csn: entity, plainName: clean}, ns.asNamespace())
|
|
255
|
+
|
|
251
256
|
// trimNamespace does not properly detect scoped entities, like A.B where both A and B are
|
|
252
257
|
// entities. So to see if we would run into a naming collision, we forcefully take the last
|
|
253
258
|
// part of the name, so "A.B" and "A.Bs" just become "B" and "Bs" to be compared.
|
|
254
259
|
// FIXME: put this in a util function
|
|
255
|
-
if (singular.split('.').at(-1) === plural.split('.').at(-1)) {
|
|
256
|
-
|
|
260
|
+
//if (singular.split('.').at(-1) === plural.split('.').at(-1)) {
|
|
261
|
+
if (plural.split('.').at(-1) === `${singular.split('.').at(-1)}_`) {
|
|
262
|
+
//plural += '_'
|
|
257
263
|
this.logger.warning(
|
|
258
264
|
`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
265
|
)
|
|
260
266
|
}
|
|
261
|
-
|
|
267
|
+
// as types are not inflected, their singular will always clash and there is also no plural for them anyway
|
|
268
|
+
if (!isType(entity) && singular in this.csn.xtended.definitions) {
|
|
262
269
|
this.logger.error(
|
|
263
270
|
`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.`
|
|
264
271
|
)
|
|
@@ -278,22 +285,33 @@ class Visitor {
|
|
|
278
285
|
// which would not be possible while already in singular form, as "Book.text" could not be resolved in CSN.
|
|
279
286
|
// edge case: @singular annotation present. singular4 will take care of that.
|
|
280
287
|
file.addInflection(util.singular4(entity, true), plural, clean)
|
|
281
|
-
|
|
282
|
-
docify(entity.doc).forEach((d) => buffer.add(d))
|
|
283
|
-
}
|
|
288
|
+
docify(entity.doc).forEach(d => buffer.add(d))
|
|
284
289
|
|
|
285
|
-
|
|
290
|
+
// in case of projections `entity` is empty -> retrieve from inferred csn where the actual properties are rolled out
|
|
291
|
+
const target = isProjection(entity) || isView(entity)
|
|
292
|
+
? this.csn.inferred.definitions[name]
|
|
293
|
+
: entity
|
|
294
|
+
|
|
295
|
+
// draft enablement is stored in csn.xtended. Iff we took the entity from csn.inferred, we have to carry the draft-enablement over at this point
|
|
296
|
+
target['@odata.draft.enabled'] = isDraftEnabled(entity)
|
|
297
|
+
|
|
298
|
+
this.#aspectify(name, target, buffer, { cleanName: singular })
|
|
299
|
+
|
|
300
|
+
buffer.add(overrideNameProperty(singular, entity.name))
|
|
286
301
|
|
|
287
302
|
// PLURAL
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
303
|
+
|
|
304
|
+
// types do not receive a plural
|
|
305
|
+
if (!isType(entity)) {
|
|
306
|
+
if (plural.includes('.')) {
|
|
307
|
+
// Foo.text -> namespace Foo { class text { ... }}
|
|
308
|
+
plural = plural.split('.').at(-1)
|
|
309
|
+
}
|
|
310
|
+
// plural can not be a type alias to $singular[] but needs to be a proper class instead,
|
|
311
|
+
// so it can get passed as value to CQL functions.
|
|
312
|
+
buffer.add(`export class ${plural} extends Array<${singular}> {${this.#staticClassContents(singular, entity).join('\n')}}`)
|
|
313
|
+
buffer.add(overrideNameProperty(plural, entity.name))
|
|
291
314
|
}
|
|
292
|
-
// plural can not be a type alias to $singular[] but needs to be a proper class instead,
|
|
293
|
-
// so it can get passed as value to CQL functions.
|
|
294
|
-
buffer.add(`export class ${plural} extends Array<${singular}> {${this.#staticClassContents(singular, entity).join('\n')}}`)
|
|
295
|
-
buffer.add(overrideNameProperty(singular, entity.name))
|
|
296
|
-
buffer.add(overrideNameProperty(plural, entity.name))
|
|
297
315
|
buffer.add('')
|
|
298
316
|
}
|
|
299
317
|
|
|
@@ -370,7 +388,7 @@ class Visitor {
|
|
|
370
388
|
// So we separate them into another buffer which is printed before the classes.
|
|
371
389
|
file.addClass(clean, name)
|
|
372
390
|
file.aspects.add(`// the following represents the CDS aspect '${clean}'`)
|
|
373
|
-
this.#aspectify(name, aspect, file.aspects, clean)
|
|
391
|
+
this.#aspectify(name, aspect, file.aspects, { cleanName: clean })
|
|
374
392
|
}
|
|
375
393
|
|
|
376
394
|
#printEvent(name, event) {
|
|
@@ -423,7 +441,7 @@ class Visitor {
|
|
|
423
441
|
// types like inline definitions can be used very similarly to entities.
|
|
424
442
|
// They can be extended, contain inline enums, etc., so we treat them as entities.
|
|
425
443
|
const handler = entity.elements ? this.#printEntity : this.#printType
|
|
426
|
-
handler.
|
|
444
|
+
handler.call(this, name, entity)
|
|
427
445
|
break
|
|
428
446
|
}
|
|
429
447
|
case 'event':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/cds-typer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Generates .ts files for a CDS model to receive code completion in VS Code",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": "github:cap-js/cds-typer",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@babel/eslint-parser": "^7.23.3",
|
|
44
|
+
"@stylistic/eslint-plugin-js": "^1.6.3",
|
|
44
45
|
"acorn": "^8.10.0",
|
|
45
46
|
"eslint": "^8.15.0",
|
|
46
47
|
"jest": "^29",
|
|
@@ -48,8 +49,7 @@
|
|
|
48
49
|
},
|
|
49
50
|
"jest": {
|
|
50
51
|
"projects": [
|
|
51
|
-
"test/unit.jest.config.js"
|
|
52
|
-
"test/int.jest.config.js"
|
|
52
|
+
"test/unit.jest.config.js"
|
|
53
53
|
]
|
|
54
54
|
}
|
|
55
55
|
}
|