@cap-js/cds-typer 0.21.2 → 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/lib/visitor.js CHANGED
@@ -2,17 +2,19 @@
2
2
 
3
3
  const util = require('./util')
4
4
 
5
- const { amendCSN, isView, isUnresolved, propagateForeignKeys, isDraftEnabled, isType, isProjection } = require('./csn')
5
+ const { amendCSN, isView, isUnresolved, propagateForeignKeys, isDraftEnabled, isType, isProjection, getMaxCardinality } = require('./csn')
6
6
  // eslint-disable-next-line no-unused-vars
7
7
  const { SourceFile, FileRepository, Buffer } = require('./file')
8
8
  const { FlatInlineDeclarationResolver, StructuredInlineDeclarationResolver } = require('./components/inline')
9
- const { Resolver } = require('./components/resolver')
10
- const { Logger } = require('./logging')
9
+ const { Resolver } = require('./resolution/resolver')
10
+ const { LOG } = require('./logging')
11
11
  const { docify } = require('./components/wrappers')
12
12
  const { csnToEnumPairs, propertyToInlineEnumName, isInlineEnumType, stringifyEnumType } = require('./components/enum')
13
13
  const { isReferenceType } = require('./components/reference')
14
14
  const { empty } = require('./components/typescript')
15
15
  const { baseDefinitions } = require('./components/basedefs')
16
+ const { EntityRepository } = require('./resolution/entity')
17
+ const { last } = require('./components/identifier')
16
18
 
17
19
  /** @typedef {import('./file').File} File */
18
20
  /** @typedef {import('./typedefs').visitor.Context} Context */
@@ -39,14 +41,12 @@ class Visitor {
39
41
 
40
42
  /**
41
43
  * @param {{xtended: CSN, inferred: CSN}} csn - root CSN
42
- * @param {VisitorOptions} options
43
- * @param {Logger} logger
44
+ * @param {VisitorOptions} options - the options
44
45
  */
45
- constructor(csn, options = {}, logger = new Logger()) {
46
+ constructor(csn, options = {}) {
46
47
  amendCSN(csn.xtended)
47
48
  propagateForeignKeys(csn.inferred)
48
49
  this.options = { ...defaults, ...options }
49
- this.logger = logger
50
50
  this.csn = csn
51
51
 
52
52
  /** @type {Context[]} **/
@@ -55,6 +55,9 @@ class Visitor {
55
55
  /** @type {Resolver} */
56
56
  this.resolver = new Resolver(this)
57
57
 
58
+ /** @type {EntityRepository} */
59
+ this.entityRepository = new EntityRepository(this.resolver)
60
+
58
61
  /** @type {FileRepository} */
59
62
  this.fileRepository = new FileRepository()
60
63
  this.fileRepository.add(baseDefinitions.path.asNamespace(), baseDefinitions)
@@ -76,7 +79,7 @@ class Visitor {
76
79
  } else if (isProjection(entity) || !isUnresolved(entity)) {
77
80
  this.visitEntity(name, entity)
78
81
  } else {
79
- this.logger.warning(`Skipping unresolved entity: ${name}`)
82
+ LOG.warn(`Skipping unresolved entity: ${name}`)
80
83
  }
81
84
  }
82
85
  // FIXME: optimise
@@ -91,13 +94,13 @@ class Visitor {
91
94
  // instead of using the definition from inferred CSN, we refer to the projected entity from xtended CSN instead.
92
95
  // The latter contains the CSN fixes (propagated foreign keys, etc) and none of the localised fields we don't handle yet.
93
96
  if (entity.projection) {
94
- const targetName = entity.projection.from.ref[0]
97
+ const targetName = entity.projection.from.ref[0]
95
98
  // FIXME: references to types of entity properties may be missing from xtendend flavour (see #103)
96
99
  // this should be revisted once we settle on a single flavour.
97
100
  const target = this.csn.xtended.definitions[targetName] ?? this.csn.inferred.definitions[targetName]
98
101
  this.visitEntity(name, target)
99
102
  } else {
100
- this.logger.error(`Expecting an autoexposed projection within a service. Skipping ${name}`)
103
+ LOG.error(`Expecting an autoexposed projection within a service. Skipping ${name}`)
101
104
  }
102
105
  }
103
106
  }
@@ -105,10 +108,10 @@ class Visitor {
105
108
  /**
106
109
  * Retrieves all the keys from an entity.
107
110
  * That is: all keys that are present in both inferred, as well as xtended flavour.
108
- * @param {string} name
111
+ * @param {string} fq - fully qualified name of the entity
109
112
  * @returns {[string, object][]} array of key name and key element pairs
110
113
  */
111
- #keys(name) {
114
+ #keys(fq) {
112
115
  // FIXME: this is actually pretty bad, as not only have to propagate keys through
113
116
  // both flavours of CSN (see constructor), but we are now also collecting them from
114
117
  // both flavours and deduplicating them.
@@ -116,8 +119,8 @@ class Visitor {
116
119
  // inferred contains keys from queried entities (thing `entity Foo as select from Bar`, where Bar has keys)
117
120
  // So we currently need them both.
118
121
  return Object.entries({
119
- ...this.csn.inferred.definitions[name]?.keys ?? {},
120
- ...this.csn.xtended.definitions[name]?.keys ?? {}
122
+ ...this.csn.inferred.definitions[fq]?.keys ?? {},
123
+ ...this.csn.xtended.definitions[fq]?.keys ?? {}
121
124
  })
122
125
  }
123
126
 
@@ -127,27 +130,47 @@ class Visitor {
127
130
  * - the function A(B) to mix the aspect into another class B
128
131
  * - the const AXtended which represents the entity A with all of its aspects mixed in (this const is not exported)
129
132
  * - the type A to use for external typing and is derived from AXtended.
130
- * @param {string} name - the name of the entity
133
+ * @param {string} fq - the name of the entity
131
134
  * @param {CSN} entity - the pointer into the CSN to extract the elements from
132
135
  * @param {Buffer} buffer - the buffer to write the resulting definitions into
133
- * @param {{cleanName?: string}} options
136
+ * @param {{cleanName?: string}} options - additional options
134
137
  */
135
- #aspectify(name, entity, buffer, options = {}) {
136
- const clean = options?.cleanName ?? this.resolver.trimNamespace(name)
137
- const namespace = this.resolver.resolveNamespace(name.split('.'))
138
+ #aspectify(fq, entity, buffer, options = {}) {
139
+ const info = this.entityRepository.getByFq(fq)
140
+ const clean = options?.cleanName ?? info.withoutNamespace
141
+ const { namespace } = info
138
142
  const file = this.fileRepository.getNamespaceFile(namespace)
139
143
  const identSingular = name => name
140
144
  const identAspect = name => `_${name}Aspect`
145
+ const toAspectIdent = (wrapped, [ns, n, fq]) => {
146
+ // types are not inflected, so don't change those to singular
147
+ const refersToType = isType(this.csn.inferred.definitions[fq])
148
+ const ident = identAspect(refersToType
149
+ ? n
150
+ : this.resolver.inflect({csn: this.csn.inferred.definitions[fq], plainName: n}).singular
151
+ )
152
+ return !ns || ns.isCwd(file.path.asDirectory())
153
+ ? `${ident}(${wrapped})`
154
+ : `${ns.asIdentifier()}.${ident}(${wrapped})`
155
+ }
156
+ const ancestors = (entity.includes ?? [])
157
+ .map(parent => {
158
+ const { namespace, entityName } = this.entityRepository.getByFq(parent)
159
+ file.addImport(namespace)
160
+ return [namespace, entityName, parent]
161
+ })
162
+ .reverse() // reverse so that own aspect A is applied before extensions B,C: B(C(A(Entity)))
163
+ .reduce(toAspectIdent, 'Base')
141
164
 
142
- this.contexts.push({ entity: name })
165
+ this.contexts.push({ entity: fq })
143
166
 
144
167
  // CLASS ASPECT
145
- buffer.addIndentedBlock(`export function ${identAspect(clean)}<TBase extends new (...args: any[]) => object>(Base: TBase) {`, function () {
146
- buffer.addIndentedBlock(`return class ${clean} extends Base {`, function () {
168
+ buffer.addIndentedBlock(`export function ${identAspect(clean)}<TBase extends new (...args: any[]) => object>(Base: TBase) {`, () => {
169
+ buffer.addIndentedBlock(`return class extends ${ancestors} {`, () => {
147
170
  const enums = []
148
171
  for (let [ename, element] of Object.entries(entity.elements ?? {})) {
149
172
  if (element.target && /\.texts?/.test(element.target)) {
150
- this.logger.warning(`referring to .texts property in ${name}. This is currently not supported and will be ignored.`)
173
+ LOG.warn(`referring to .texts property in ${fq}. This is currently not supported and will be ignored.`)
151
174
  continue
152
175
  }
153
176
  this.visitElement(ename, element, file, buffer)
@@ -157,13 +180,15 @@ class Visitor {
157
180
  // lookup in cds.definitions can fail for inline structs.
158
181
  // We don't really have to care for this case, as keys from such structs are _not_ propagated to
159
182
  // the containing entity.
160
- for (const [kname, kelement] of this.#keys(element.target)) {
161
- if (this.resolver.getMaxCardinality(element) === 1) { // FIXME: kelement?
183
+ for (const [kname, originalKeyElement] of this.#keys(element.target)) {
184
+ if (getMaxCardinality(element) === 1 && typeof element.on !== 'object') { // FIXME: kelement?
162
185
  const foreignKey = `${ename}_${kname}`
163
186
  if (Object.hasOwn(entity.elements, foreignKey)) {
164
- 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.`)
187
+ LOG.error(`Attempting to generate a foreign key reference called '${foreignKey}' in type definition for entity ${fq}. But a property of that name is already defined explicitly. Consider renaming that property.`)
165
188
  } else {
166
- kelement.isRefNotNull = !!element.notNull || !!element.key
189
+ const kelement = Object.assign(Object.create(originalKeyElement), {
190
+ isRefNotNull: !!element.notNull || !!element.key
191
+ })
167
192
  this.visitElement(foreignKey, kelement, file, buffer)
168
193
  }
169
194
  }
@@ -176,14 +201,14 @@ class Visitor {
176
201
  }
177
202
  }
178
203
 
179
- buffer.addIndented(function() {
204
+ buffer.addIndented(() => {
180
205
  for (const e of enums) {
181
206
  buffer.add(`static ${e.name} = ${propertyToInlineEnumName(clean, e.name)}`)
182
- file.addInlineEnum(clean, name, e.name, csnToEnumPairs(e, {unwrapVals: true}))
207
+ file.addInlineEnum(clean, fq, e.name, csnToEnumPairs(e, {unwrapVals: true}))
183
208
  }
184
209
  const actions = Object.entries(entity.actions ?? {})
185
210
  if (actions.length) {
186
- buffer.addIndentedBlock('static readonly actions: {',
211
+ buffer.addIndentedBlock('static readonly actions: {',
187
212
  actions.map(([aname, action]) => SourceFile.stringifyLambda({
188
213
  name: aname,
189
214
  parameters: this.#stringifyFunctionParams(action.params, file),
@@ -194,37 +219,13 @@ class Visitor {
194
219
  } else {
195
220
  buffer.add(`static readonly actions: ${empty}`)
196
221
  }
197
- }.bind(this))
198
- }.bind(this), '};') // end of generated class
199
- }.bind(this), '}') // end of aspect
222
+ })
223
+ }, '};') // end of generated class
224
+ }, '}') // end of aspect
200
225
 
201
226
  // CLASS WITH ADDED ASPECTS
202
227
  file.addImport(baseDefinitions.path)
203
- const ancestors = (entity.includes ?? [])
204
- .map(parent => {
205
- const { namespace, name } = this.resolver.untangle(parent)
206
- file.addImport(namespace)
207
- return [namespace, name, parent]
208
- })
209
- .concat([[undefined, clean, name]]) // add own aspect without namespace AFTER imports were created
210
- //.concat([[undefined, clean, [namespace, clean].filter(Boolean).join('.')]]) // add own aspect without namespace AFTER imports were created
211
- .reverse() // reverse so that own aspect A is applied before extensions B,C: B(C(A(Entity)))
212
- .reduce((wrapped, [ns, n, fq]) => {
213
- // types are not inflected, so don't change those to singular
214
- const refersToType = isType(this.csn.inferred.definitions[fq])
215
- const ident = identAspect(refersToType
216
- ? n
217
- : this.resolver.inflect({csn: this.csn.inferred.definitions[fq], plainName: n}).singular
218
- )
219
- return !ns || ns.isCwd(file.path.asDirectory())
220
- ? `${ident}(${wrapped})`
221
- : `${ns.asIdentifier()}.${ident}(${wrapped})`
222
- },
223
- `${baseDefinitions.path.asIdentifier()}.Entity`
224
- )
225
-
226
- buffer.add(`export class ${identSingular(clean)} extends ${ancestors} {${this.#staticClassContents(clean, entity).join('\n')}}`)
227
- //buffer.add(`export type ${clean} = InstanceType<typeof ${identSingular(clean)}>`)
228
+ buffer.add(`export class ${identSingular(clean)} extends ${toAspectIdent(`${baseDefinitions.path.asIdentifier()}.Entity`, [undefined, clean, fq])} {${this.#staticClassContents(clean, entity).join('\n')}}`)
228
229
  this.contexts.pop()
229
230
  }
230
231
 
@@ -232,37 +233,30 @@ class Visitor {
232
233
  return isDraftEnabled(entity) ? [`static drafts: typeof ${clean}`] : []
233
234
  }
234
235
 
235
- #printEntity(name, entity) {
236
+ #printEntity(fq, entity) {
236
237
  // static .name has to be defined more forcefully: https://github.com/microsoft/TypeScript/issues/442
237
238
  const overrideNameProperty = (clazz, content) => `Object.defineProperty(${clazz}, 'name', { value: '${content}' })`
238
- const { namespace: ns, name: clean } = this.resolver.untangle(name)
239
+ const { namespace: ns, entityName: clean, inflection } = this.entityRepository.getByFq(fq)
239
240
  const file = this.fileRepository.getNamespaceFile(ns)
240
- // entities are expected to be in plural anyway, so we would favour the regular name.
241
- // If the user decides to pass a @plural annotation, that gets precedence over the regular name.
242
-
243
- /*
244
- let plural = this.resolver.trimNamespace(util.getPluralAnnotation(entity) ? util.plural4(entity, false) : name)
245
- const singular = this.resolver.trimNamespace(util.singular4(entity, true))
246
- */
247
- let { singular, plural } = this.resolver.inflect({csn: entity, plainName: clean}, ns.asNamespace())
241
+ let { singular, plural } = inflection
248
242
 
249
243
  // trimNamespace does not properly detect scoped entities, like A.B where both A and B are
250
244
  // entities. So to see if we would run into a naming collision, we forcefully take the last
251
245
  // part of the name, so "A.B" and "A.Bs" just become "B" and "Bs" to be compared.
252
- // FIXME: put this in a util function
253
- if (plural.split('.').at(-1) === `${singular.split('.').at(-1)}_`) {
254
- this.logger.warning(
246
+ if (last(plural) === `${last(singular)}_`) {
247
+ LOG.warn(
255
248
  `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.`
256
249
  )
257
250
  }
251
+
258
252
  // as types are not inflected, their singular will always clash and there is also no plural for them anyway -> skip
259
253
  if (!isType(entity) && `${ns.asNamespace()}.${singular}` in this.csn.xtended.definitions) {
260
- this.logger.error(
261
- `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.`
254
+ LOG.error(
255
+ `Derived singular '${singular}' for your entity '${fq}', 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.`
262
256
  )
263
257
  }
264
- file.addClass(singular, name)
265
- file.addClass(plural, name)
258
+ file.addClass(singular, fq)
259
+ file.addClass(plural, fq)
266
260
 
267
261
  const parent = this.resolver.resolveParent(entity.name)
268
262
  const buffer = parent && parent.kind === 'entity'
@@ -280,13 +274,13 @@ class Visitor {
280
274
 
281
275
  // in case of projections `entity` is empty -> retrieve from inferred csn where the actual properties are rolled out
282
276
  const target = isProjection(entity) || isView(entity)
283
- ? this.csn.inferred.definitions[name]
277
+ ? this.csn.inferred.definitions[fq]
284
278
  : entity
285
-
279
+
286
280
  // 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
287
281
  target['@odata.draft.enabled'] = isDraftEnabled(entity)
288
282
 
289
- this.#aspectify(name, target, buffer, { cleanName: singular })
283
+ this.#aspectify(fq, target, buffer, { cleanName: singular })
290
284
 
291
285
  buffer.add(overrideNameProperty(singular, entity.name))
292
286
  buffer.add(`Object.defineProperty(${singular}, 'is_singular', { value: true })`)
@@ -297,7 +291,7 @@ class Visitor {
297
291
  if (!isType(entity)) {
298
292
  if (plural.includes('.')) {
299
293
  // Foo.text -> namespace Foo { class text { ... }}
300
- plural = plural.split('.').at(-1)
294
+ plural = last(plural)
301
295
  }
302
296
  // plural can not be a type alias to $singular[] but needs to be a proper class instead,
303
297
  // so it can get passed as value to CQL functions.
@@ -341,14 +335,14 @@ class Visitor {
341
335
  }
342
336
 
343
337
  /**
344
- * @param {string} name
345
- * @param {object} operation
346
- * @param {'function' | 'action'} kind
338
+ * @param {string} fq - fully qualified name of the operation
339
+ * @param {object} operation - CSN
340
+ * @param {'function' | 'action'} kind - kind of operation
347
341
  */
348
- #printOperation(name, operation, kind) {
349
- this.logger.debug(`Printing operation ${name}:\n${JSON.stringify(operation, null, 2)}`)
350
- const ns = this.resolver.resolveNamespace(name.split('.'))
351
- const file = this.fileRepository.getNamespaceFile(ns)
342
+ #printOperation(fq, operation, kind) {
343
+ LOG.debug(`Printing operation ${fq}:\n${JSON.stringify(operation, null, 2)}`)
344
+ const { namespace } = this.entityRepository.getByFq(fq)
345
+ const file = this.fileRepository.getNamespaceFile(namespace)
352
346
  const params = this.#stringifyFunctionParams(operation.params, file)
353
347
  const returnType = operation.returns
354
348
  ? this.resolver.resolveAndRequire(operation.returns, file)
@@ -357,59 +351,59 @@ class Visitor {
357
351
  returnType,
358
352
  returnType.typeInfo.isArray ? returnType.typeName : returnType.typeInfo.inflection.singular
359
353
  )
360
- file.addOperation(name.split('.').at(-1), params, returns, kind)
354
+ file.addOperation(last(fq), params, returns, kind)
361
355
  }
362
356
 
363
- #printType(name, type) {
364
- this.logger.debug(`Printing type ${name}:\n${JSON.stringify(type, null, 2)}`)
365
- const { namespace: ns, name: clean } = this.resolver.untangle(name)
366
- const file = this.fileRepository.getNamespaceFile(ns)
357
+ #printType(fq, type) {
358
+ LOG.debug(`Printing type ${fq}:\n${JSON.stringify(type, null, 2)}`)
359
+ const { namespace, entityName } = this.entityRepository.getByFq(fq)
360
+ const file = this.fileRepository.getNamespaceFile(namespace)
367
361
  // skip references to enums.
368
362
  // "Base" enums will always have a builtin type (don't skip those).
369
363
  // A type referencing an enum E will be considered an enum itself and have .type === E (skip).
370
364
  if ('enum' in type && !isReferenceType(type) && this.resolver.builtinResolver.resolveBuiltin(type.type)) {
371
- file.addEnum(name, clean, csnToEnumPairs(type))
365
+ file.addEnum(fq, entityName, csnToEnumPairs(type))
372
366
  } else {
373
367
  // alias
374
- file.addType(name, clean, this.resolver.resolveAndRequire(type, file).typeName)
368
+ file.addType(fq, entityName, this.resolver.resolveAndRequire(type, file).typeName)
375
369
  }
376
370
  // TODO: annotations not handled yet
377
371
  }
378
372
 
379
- #printAspect(name, aspect) {
380
- this.logger.debug(`Printing aspect ${name}`)
381
- const { namespace: ns, name: clean } = this.resolver.untangle(name)
382
- const file = this.fileRepository.getNamespaceFile(ns)
373
+ #printAspect(fq, aspect) {
374
+ LOG.debug(`Printing aspect ${fq}`)
375
+ const { namespace, entityName } = this.entityRepository.getByFq(fq)
376
+ const file = this.fileRepository.getNamespaceFile(namespace)
383
377
  // aspects are technically classes and can therefore be added to the list of defined classes.
384
378
  // Still, when using them as mixins for a class, they need to already be defined.
385
379
  // So we separate them into another buffer which is printed before the classes.
386
- file.addClass(clean, name)
387
- file.aspects.add(`// the following represents the CDS aspect '${clean}'`)
388
- this.#aspectify(name, aspect, file.aspects, { cleanName: clean })
380
+ file.addClass(entityName, fq)
381
+ file.aspects.add(`// the following represents the CDS aspect '${entityName}'`)
382
+ this.#aspectify(fq, aspect, file.aspects, { cleanName: entityName })
389
383
  }
390
384
 
391
- #printEvent(name, event) {
392
- this.logger.debug(`Printing event ${name}`)
393
- const { namespace: ns, name: clean } = this.resolver.untangle(name)
394
- const file = this.fileRepository.getNamespaceFile(ns)
395
- file.addEvent(clean, name)
385
+ #printEvent(fq, event) {
386
+ LOG.debug(`Printing event ${fq}`)
387
+ const { namespace, entityName } = this.entityRepository.getByFq(fq)
388
+ const file = this.fileRepository.getNamespaceFile(namespace)
389
+ file.addEvent(entityName, fq)
396
390
  const buffer = file.events.buffer
397
391
  buffer.add('// event')
398
392
  // only declare classes, as their properties are not optional, so we don't have to do awkward initialisation thereof.
399
- buffer.addIndentedBlock(`export declare class ${clean} {`, function() {
393
+ buffer.addIndentedBlock(`export declare class ${entityName} {`, () => {
400
394
  const propOpt = this.options.propertiesOptional
401
395
  this.options.propertiesOptional = false
402
396
  for (const [ename, element] of Object.entries(event.elements ?? {})) {
403
397
  this.visitElement(ename, element, file, buffer)
404
398
  }
405
399
  this.options.propertiesOptional = propOpt
406
- }.bind(this), '}')
400
+ }, '}')
407
401
  }
408
402
 
409
- #printService(name, service) {
410
- this.logger.debug(`Printing service ${name}:\n${JSON.stringify(service, null, 2)}`)
411
- const ns = this.resolver.resolveNamespace(name)
412
- const file = this.fileRepository.getNamespaceFile(ns)
403
+ #printService(fq, service) {
404
+ LOG.debug(`Printing service ${fq}:\n${JSON.stringify(service, null, 2)}`)
405
+ const { namespace } = this.entityRepository.getByFq(fq)
406
+ const file = this.fileRepository.getNamespaceFile(namespace)
413
407
  // service.name is clean of namespace
414
408
  file.services.buffer.add(`export default { name: '${service.name}' }`)
415
409
  file.addService(service.name)
@@ -418,36 +412,36 @@ class Visitor {
418
412
  /**
419
413
  * Visits a single entity from the CSN's definition field.
420
414
  * Will call #printEntity or #printAction based on the entity's kind.
421
- * @param {string} name - name of the entity, fully qualified as is used in the definition field.
415
+ * @param {string} fq - name of the entity, fully qualified as is used in the definition field.
422
416
  * @param {CSN} entity - CSN data belonging to the entity to perform lookups in.
423
417
  */
424
- visitEntity(name, entity) {
418
+ visitEntity(fq, entity) {
425
419
  switch (entity.kind) {
426
420
  case 'entity':
427
- this.#printEntity(name, entity)
421
+ this.#printEntity(fq, entity)
428
422
  break
429
423
  case 'action':
430
424
  case 'function':
431
- this.#printOperation(name, entity, entity.kind)
425
+ this.#printOperation(fq, entity, entity.kind)
432
426
  break
433
427
  case 'aspect':
434
- this.#printAspect(name, entity)
428
+ this.#printAspect(fq, entity)
435
429
  break
436
430
  case 'type': {
437
431
  // types like inline definitions can be used very similarly to entities.
438
432
  // They can be extended, contain inline enums, etc., so we treat them as entities.
439
433
  const handler = entity.elements ? this.#printEntity : this.#printType
440
- handler.call(this, name, entity)
434
+ handler.call(this, fq, entity)
441
435
  break
442
436
  }
443
437
  case 'event':
444
- this.#printEvent(name, entity)
438
+ this.#printEvent(fq, entity)
445
439
  break
446
440
  case 'service':
447
- this.#printService(name, entity)
441
+ this.#printService(fq, entity)
448
442
  break
449
443
  default:
450
- this.logger.debug(`Unhandled entity kind '${entity.kind}'.`)
444
+ LOG.debug(`Unhandled entity kind '${entity.kind}'.`)
451
445
  }
452
446
  }
453
447
 
@@ -457,7 +451,7 @@ class Visitor {
457
451
  * refer to types via their alias that hides the aspectification.
458
452
  * If we attempt to directly refer to this alias while it has not been fully created,
459
453
  * that will result in a TS error.
460
- * @param {string} entityName
454
+ * @param {string} fq - fully qualified name of the entity
461
455
  * @returns {boolean} true, if `entityName` refers to the surrounding class
462
456
  * @example
463
457
  * ```ts
@@ -467,14 +461,14 @@ class Visitor {
467
461
  * }
468
462
  * ```
469
463
  */
470
- isSelfReference(entityName) {
471
- return entityName === this.contexts.at(-1)?.entity
464
+ isSelfReference(fq) {
465
+ return fq === this.contexts.at(-1)?.entity
472
466
  }
473
467
 
474
468
  /**
475
469
  * Visits a single element in an entity.
476
470
  * @param {string} name - name of the element
477
- * @param {import('./components/resolver').CSN} element - CSN data belonging to the the element.
471
+ * @param {import('./resolution/resolver').CSN} element - CSN data belonging to the the element.
478
472
  * @param {SourceFile} file - the namespace file the surrounding entity is being printed into.
479
473
  * @param {Buffer} buffer - buffer to add the definition to. If no buffer is passed, the passed file's class buffer is used instead.
480
474
  * @returns @see InlineDeclarationResolver.visitElement
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js/cds-typer",
3
- "version": "0.21.2",
3
+ "version": "0.23.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",
@@ -19,6 +19,7 @@
19
19
  "test:all": "jest",
20
20
  "test": "npm run test:smoke && npm run test:unit",
21
21
  "lint": "npx eslint .",
22
+ "lint:fix": "npx eslint . --fix",
22
23
  "cli": "node lib/cli.js",
23
24
  "doc:clean": "rm -rf ./doc",
24
25
  "doc:prepare": "npm run doc:clean && mkdir -p doc/types",