@cap-js/cds-typer 0.22.0 → 0.24.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 ${clean} 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)
@@ -158,13 +181,13 @@ class Visitor {
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
183
  for (const [kname, originalKeyElement] of this.#keys(element.target)) {
161
- if (this.resolver.getMaxCardinality(element) === 1 && typeof element.on !== 'object') { // FIXME: kelement?
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
- const kelement = Object.assign(Object.create(originalKeyElement), {
167
- isRefNotNull: !!element.notNull || !!element.key
189
+ const kelement = Object.assign(Object.create(originalKeyElement), {
190
+ isRefNotNull: !!element.notNull || !!element.key
168
191
  })
169
192
  this.visitElement(foreignKey, kelement, file, buffer)
170
193
  }
@@ -178,14 +201,14 @@ class Visitor {
178
201
  }
179
202
  }
180
203
 
181
- buffer.addIndented(function() {
204
+ buffer.addIndented(() => {
182
205
  for (const e of enums) {
183
206
  buffer.add(`static ${e.name} = ${propertyToInlineEnumName(clean, e.name)}`)
184
- file.addInlineEnum(clean, name, e.name, csnToEnumPairs(e, {unwrapVals: true}))
207
+ file.addInlineEnum(clean, fq, e.name, csnToEnumPairs(e, {unwrapVals: true}))
185
208
  }
186
209
  const actions = Object.entries(entity.actions ?? {})
187
210
  if (actions.length) {
188
- buffer.addIndentedBlock('static readonly actions: {',
211
+ buffer.addIndentedBlock('static readonly actions: {',
189
212
  actions.map(([aname, action]) => SourceFile.stringifyLambda({
190
213
  name: aname,
191
214
  parameters: this.#stringifyFunctionParams(action.params, file),
@@ -196,37 +219,13 @@ class Visitor {
196
219
  } else {
197
220
  buffer.add(`static readonly actions: ${empty}`)
198
221
  }
199
- }.bind(this))
200
- }.bind(this), '};') // end of generated class
201
- }.bind(this), '}') // end of aspect
222
+ })
223
+ }, '};') // end of generated class
224
+ }, '}') // end of aspect
202
225
 
203
226
  // CLASS WITH ADDED ASPECTS
204
227
  file.addImport(baseDefinitions.path)
205
- const ancestors = (entity.includes ?? [])
206
- .map(parent => {
207
- const { namespace, name } = this.resolver.untangle(parent)
208
- file.addImport(namespace)
209
- return [namespace, name, parent]
210
- })
211
- .concat([[undefined, clean, name]]) // add own aspect without namespace AFTER imports were created
212
- //.concat([[undefined, clean, [namespace, clean].filter(Boolean).join('.')]]) // add own aspect without namespace AFTER imports were created
213
- .reverse() // reverse so that own aspect A is applied before extensions B,C: B(C(A(Entity)))
214
- .reduce((wrapped, [ns, n, fq]) => {
215
- // types are not inflected, so don't change those to singular
216
- const refersToType = isType(this.csn.inferred.definitions[fq])
217
- const ident = identAspect(refersToType
218
- ? n
219
- : this.resolver.inflect({csn: this.csn.inferred.definitions[fq], plainName: n}).singular
220
- )
221
- return !ns || ns.isCwd(file.path.asDirectory())
222
- ? `${ident}(${wrapped})`
223
- : `${ns.asIdentifier()}.${ident}(${wrapped})`
224
- },
225
- `${baseDefinitions.path.asIdentifier()}.Entity`
226
- )
227
-
228
- buffer.add(`export class ${identSingular(clean)} extends ${ancestors} {${this.#staticClassContents(clean, entity).join('\n')}}`)
229
- //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')}}`)
230
229
  this.contexts.pop()
231
230
  }
232
231
 
@@ -234,37 +233,32 @@ class Visitor {
234
233
  return isDraftEnabled(entity) ? [`static drafts: typeof ${clean}`] : []
235
234
  }
236
235
 
237
- #printEntity(name, entity) {
236
+ #printEntity(fq, entity) {
238
237
  // static .name has to be defined more forcefully: https://github.com/microsoft/TypeScript/issues/442
239
238
  const overrideNameProperty = (clazz, content) => `Object.defineProperty(${clazz}, 'name', { value: '${content}' })`
240
- const { namespace: ns, name: clean } = this.resolver.untangle(name)
239
+ const { namespace: ns, entityName: clean, inflection } = this.entityRepository.getByFq(fq)
241
240
  const file = this.fileRepository.getNamespaceFile(ns)
242
- // entities are expected to be in plural anyway, so we would favour the regular name.
243
- // If the user decides to pass a @plural annotation, that gets precedence over the regular name.
244
-
245
- /*
246
- let plural = this.resolver.trimNamespace(util.getPluralAnnotation(entity) ? util.plural4(entity, false) : name)
247
- const singular = this.resolver.trimNamespace(util.singular4(entity, true))
248
- */
249
- let { singular, plural } = this.resolver.inflect({csn: entity, plainName: clean}, ns.asNamespace())
241
+ let { singular, plural } = inflection
250
242
 
251
243
  // trimNamespace does not properly detect scoped entities, like A.B where both A and B are
252
244
  // entities. So to see if we would run into a naming collision, we forcefully take the last
253
245
  // part of the name, so "A.B" and "A.Bs" just become "B" and "Bs" to be compared.
254
- // FIXME: put this in a util function
255
- if (plural.split('.').at(-1) === `${singular.split('.').at(-1)}_`) {
256
- this.logger.warning(
246
+ if (last(plural) === `${last(singular)}_`) {
247
+ LOG.warn(
257
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.`
258
249
  )
259
250
  }
251
+
260
252
  // as types are not inflected, their singular will always clash and there is also no plural for them anyway -> skip
261
- if (!isType(entity) && `${ns.asNamespace()}.${singular}` in this.csn.xtended.definitions) {
262
- this.logger.error(
263
- `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.`
253
+ // if the user defined their entities in singular form we would also have a false positive here -> skip
254
+ const namespacedSingular = `${ns.asNamespace()}.${singular}`
255
+ if (!isType(entity) && namespacedSingular !== fq && namespacedSingular in this.csn.xtended.definitions) {
256
+ LOG.error(
257
+ `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.`
264
258
  )
265
259
  }
266
- file.addClass(singular, name)
267
- file.addClass(plural, name)
260
+ file.addClass(singular, fq)
261
+ file.addClass(plural, fq)
268
262
 
269
263
  const parent = this.resolver.resolveParent(entity.name)
270
264
  const buffer = parent && parent.kind === 'entity'
@@ -282,13 +276,13 @@ class Visitor {
282
276
 
283
277
  // in case of projections `entity` is empty -> retrieve from inferred csn where the actual properties are rolled out
284
278
  const target = isProjection(entity) || isView(entity)
285
- ? this.csn.inferred.definitions[name]
279
+ ? this.csn.inferred.definitions[fq]
286
280
  : entity
287
-
281
+
288
282
  // 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
289
283
  target['@odata.draft.enabled'] = isDraftEnabled(entity)
290
284
 
291
- this.#aspectify(name, target, buffer, { cleanName: singular })
285
+ this.#aspectify(fq, target, buffer, { cleanName: singular })
292
286
 
293
287
  buffer.add(overrideNameProperty(singular, entity.name))
294
288
  buffer.add(`Object.defineProperty(${singular}, 'is_singular', { value: true })`)
@@ -299,7 +293,7 @@ class Visitor {
299
293
  if (!isType(entity)) {
300
294
  if (plural.includes('.')) {
301
295
  // Foo.text -> namespace Foo { class text { ... }}
302
- plural = plural.split('.').at(-1)
296
+ plural = last(plural)
303
297
  }
304
298
  // plural can not be a type alias to $singular[] but needs to be a proper class instead,
305
299
  // so it can get passed as value to CQL functions.
@@ -343,14 +337,14 @@ class Visitor {
343
337
  }
344
338
 
345
339
  /**
346
- * @param {string} name
347
- * @param {object} operation
348
- * @param {'function' | 'action'} kind
340
+ * @param {string} fq - fully qualified name of the operation
341
+ * @param {object} operation - CSN
342
+ * @param {'function' | 'action'} kind - kind of operation
349
343
  */
350
- #printOperation(name, operation, kind) {
351
- this.logger.debug(`Printing operation ${name}:\n${JSON.stringify(operation, null, 2)}`)
352
- const ns = this.resolver.resolveNamespace(name.split('.'))
353
- const file = this.fileRepository.getNamespaceFile(ns)
344
+ #printOperation(fq, operation, kind) {
345
+ LOG.debug(`Printing operation ${fq}:\n${JSON.stringify(operation, null, 2)}`)
346
+ const { namespace } = this.entityRepository.getByFq(fq)
347
+ const file = this.fileRepository.getNamespaceFile(namespace)
354
348
  const params = this.#stringifyFunctionParams(operation.params, file)
355
349
  const returnType = operation.returns
356
350
  ? this.resolver.resolveAndRequire(operation.returns, file)
@@ -359,59 +353,59 @@ class Visitor {
359
353
  returnType,
360
354
  returnType.typeInfo.isArray ? returnType.typeName : returnType.typeInfo.inflection.singular
361
355
  )
362
- file.addOperation(name.split('.').at(-1), params, returns, kind)
356
+ file.addOperation(last(fq), params, returns, kind)
363
357
  }
364
358
 
365
- #printType(name, type) {
366
- this.logger.debug(`Printing type ${name}:\n${JSON.stringify(type, null, 2)}`)
367
- const { namespace: ns, name: clean } = this.resolver.untangle(name)
368
- const file = this.fileRepository.getNamespaceFile(ns)
359
+ #printType(fq, type) {
360
+ LOG.debug(`Printing type ${fq}:\n${JSON.stringify(type, null, 2)}`)
361
+ const { namespace, entityName } = this.entityRepository.getByFq(fq)
362
+ const file = this.fileRepository.getNamespaceFile(namespace)
369
363
  // skip references to enums.
370
364
  // "Base" enums will always have a builtin type (don't skip those).
371
365
  // A type referencing an enum E will be considered an enum itself and have .type === E (skip).
372
366
  if ('enum' in type && !isReferenceType(type) && this.resolver.builtinResolver.resolveBuiltin(type.type)) {
373
- file.addEnum(name, clean, csnToEnumPairs(type))
367
+ file.addEnum(fq, entityName, csnToEnumPairs(type))
374
368
  } else {
375
369
  // alias
376
- file.addType(name, clean, this.resolver.resolveAndRequire(type, file).typeName)
370
+ file.addType(fq, entityName, this.resolver.resolveAndRequire(type, file).typeName)
377
371
  }
378
372
  // TODO: annotations not handled yet
379
373
  }
380
374
 
381
- #printAspect(name, aspect) {
382
- this.logger.debug(`Printing aspect ${name}`)
383
- const { namespace: ns, name: clean } = this.resolver.untangle(name)
384
- const file = this.fileRepository.getNamespaceFile(ns)
375
+ #printAspect(fq, aspect) {
376
+ LOG.debug(`Printing aspect ${fq}`)
377
+ const { namespace, entityName, inflection } = this.entityRepository.getByFq(fq)
378
+ const file = this.fileRepository.getNamespaceFile(namespace)
385
379
  // aspects are technically classes and can therefore be added to the list of defined classes.
386
380
  // Still, when using them as mixins for a class, they need to already be defined.
387
381
  // So we separate them into another buffer which is printed before the classes.
388
- file.addClass(clean, name)
389
- file.aspects.add(`// the following represents the CDS aspect '${clean}'`)
390
- this.#aspectify(name, aspect, file.aspects, { cleanName: clean })
382
+ file.addClass(entityName, fq)
383
+ file.aspects.add(`// the following represents the CDS aspect '${entityName}'`)
384
+ this.#aspectify(fq, aspect, file.aspects, { cleanName: inflection.singular })
391
385
  }
392
386
 
393
- #printEvent(name, event) {
394
- this.logger.debug(`Printing event ${name}`)
395
- const { namespace: ns, name: clean } = this.resolver.untangle(name)
396
- const file = this.fileRepository.getNamespaceFile(ns)
397
- file.addEvent(clean, name)
387
+ #printEvent(fq, event) {
388
+ LOG.debug(`Printing event ${fq}`)
389
+ const { namespace, entityName } = this.entityRepository.getByFq(fq)
390
+ const file = this.fileRepository.getNamespaceFile(namespace)
391
+ file.addEvent(entityName, fq)
398
392
  const buffer = file.events.buffer
399
393
  buffer.add('// event')
400
394
  // only declare classes, as their properties are not optional, so we don't have to do awkward initialisation thereof.
401
- buffer.addIndentedBlock(`export declare class ${clean} {`, function() {
395
+ buffer.addIndentedBlock(`export declare class ${entityName} {`, () => {
402
396
  const propOpt = this.options.propertiesOptional
403
397
  this.options.propertiesOptional = false
404
398
  for (const [ename, element] of Object.entries(event.elements ?? {})) {
405
399
  this.visitElement(ename, element, file, buffer)
406
400
  }
407
401
  this.options.propertiesOptional = propOpt
408
- }.bind(this), '}')
402
+ }, '}')
409
403
  }
410
404
 
411
- #printService(name, service) {
412
- this.logger.debug(`Printing service ${name}:\n${JSON.stringify(service, null, 2)}`)
413
- const ns = this.resolver.resolveNamespace(name)
414
- const file = this.fileRepository.getNamespaceFile(ns)
405
+ #printService(fq, service) {
406
+ LOG.debug(`Printing service ${fq}:\n${JSON.stringify(service, null, 2)}`)
407
+ const { namespace } = this.entityRepository.getByFq(fq)
408
+ const file = this.fileRepository.getNamespaceFile(namespace)
415
409
  // service.name is clean of namespace
416
410
  file.services.buffer.add(`export default { name: '${service.name}' }`)
417
411
  file.addService(service.name)
@@ -420,36 +414,36 @@ class Visitor {
420
414
  /**
421
415
  * Visits a single entity from the CSN's definition field.
422
416
  * Will call #printEntity or #printAction based on the entity's kind.
423
- * @param {string} name - name of the entity, fully qualified as is used in the definition field.
417
+ * @param {string} fq - name of the entity, fully qualified as is used in the definition field.
424
418
  * @param {CSN} entity - CSN data belonging to the entity to perform lookups in.
425
419
  */
426
- visitEntity(name, entity) {
420
+ visitEntity(fq, entity) {
427
421
  switch (entity.kind) {
428
422
  case 'entity':
429
- this.#printEntity(name, entity)
423
+ this.#printEntity(fq, entity)
430
424
  break
431
425
  case 'action':
432
426
  case 'function':
433
- this.#printOperation(name, entity, entity.kind)
427
+ this.#printOperation(fq, entity, entity.kind)
434
428
  break
435
429
  case 'aspect':
436
- this.#printAspect(name, entity)
430
+ this.#printAspect(fq, entity)
437
431
  break
438
432
  case 'type': {
439
433
  // types like inline definitions can be used very similarly to entities.
440
434
  // They can be extended, contain inline enums, etc., so we treat them as entities.
441
435
  const handler = entity.elements ? this.#printEntity : this.#printType
442
- handler.call(this, name, entity)
436
+ handler.call(this, fq, entity)
443
437
  break
444
438
  }
445
439
  case 'event':
446
- this.#printEvent(name, entity)
440
+ this.#printEvent(fq, entity)
447
441
  break
448
442
  case 'service':
449
- this.#printService(name, entity)
443
+ this.#printService(fq, entity)
450
444
  break
451
445
  default:
452
- this.logger.debug(`Unhandled entity kind '${entity.kind}'.`)
446
+ LOG.debug(`Unhandled entity kind '${entity.kind}'.`)
453
447
  }
454
448
  }
455
449
 
@@ -459,7 +453,7 @@ class Visitor {
459
453
  * refer to types via their alias that hides the aspectification.
460
454
  * If we attempt to directly refer to this alias while it has not been fully created,
461
455
  * that will result in a TS error.
462
- * @param {string} entityName
456
+ * @param {string} fq - fully qualified name of the entity
463
457
  * @returns {boolean} true, if `entityName` refers to the surrounding class
464
458
  * @example
465
459
  * ```ts
@@ -469,14 +463,14 @@ class Visitor {
469
463
  * }
470
464
  * ```
471
465
  */
472
- isSelfReference(entityName) {
473
- return entityName === this.contexts.at(-1)?.entity
466
+ isSelfReference(fq) {
467
+ return fq === this.contexts.at(-1)?.entity
474
468
  }
475
469
 
476
470
  /**
477
471
  * Visits a single element in an entity.
478
472
  * @param {string} name - name of the element
479
- * @param {import('./components/resolver').CSN} element - CSN data belonging to the the element.
473
+ * @param {import('./resolution/resolver').CSN} element - CSN data belonging to the the element.
480
474
  * @param {SourceFile} file - the namespace file the surrounding entity is being printed into.
481
475
  * @param {Buffer} buffer - buffer to add the definition to. If no buffer is passed, the passed file's class buffer is used instead.
482
476
  * @returns @see InlineDeclarationResolver.visitElement
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js/cds-typer",
3
- "version": "0.22.0",
3
+ "version": "0.24.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",