@cap-js/cds-typer 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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)
@@ -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,30 @@ 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
253
  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.`
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.`
264
256
  )
265
257
  }
266
- file.addClass(singular, name)
267
- file.addClass(plural, name)
258
+ file.addClass(singular, fq)
259
+ file.addClass(plural, fq)
268
260
 
269
261
  const parent = this.resolver.resolveParent(entity.name)
270
262
  const buffer = parent && parent.kind === 'entity'
@@ -282,13 +274,13 @@ class Visitor {
282
274
 
283
275
  // in case of projections `entity` is empty -> retrieve from inferred csn where the actual properties are rolled out
284
276
  const target = isProjection(entity) || isView(entity)
285
- ? this.csn.inferred.definitions[name]
277
+ ? this.csn.inferred.definitions[fq]
286
278
  : entity
287
-
279
+
288
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
289
281
  target['@odata.draft.enabled'] = isDraftEnabled(entity)
290
282
 
291
- this.#aspectify(name, target, buffer, { cleanName: singular })
283
+ this.#aspectify(fq, target, buffer, { cleanName: singular })
292
284
 
293
285
  buffer.add(overrideNameProperty(singular, entity.name))
294
286
  buffer.add(`Object.defineProperty(${singular}, 'is_singular', { value: true })`)
@@ -299,7 +291,7 @@ class Visitor {
299
291
  if (!isType(entity)) {
300
292
  if (plural.includes('.')) {
301
293
  // Foo.text -> namespace Foo { class text { ... }}
302
- plural = plural.split('.').at(-1)
294
+ plural = last(plural)
303
295
  }
304
296
  // plural can not be a type alias to $singular[] but needs to be a proper class instead,
305
297
  // so it can get passed as value to CQL functions.
@@ -343,14 +335,14 @@ class Visitor {
343
335
  }
344
336
 
345
337
  /**
346
- * @param {string} name
347
- * @param {object} operation
348
- * @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
349
341
  */
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)
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)
354
346
  const params = this.#stringifyFunctionParams(operation.params, file)
355
347
  const returnType = operation.returns
356
348
  ? this.resolver.resolveAndRequire(operation.returns, file)
@@ -359,59 +351,59 @@ class Visitor {
359
351
  returnType,
360
352
  returnType.typeInfo.isArray ? returnType.typeName : returnType.typeInfo.inflection.singular
361
353
  )
362
- file.addOperation(name.split('.').at(-1), params, returns, kind)
354
+ file.addOperation(last(fq), params, returns, kind)
363
355
  }
364
356
 
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)
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)
369
361
  // skip references to enums.
370
362
  // "Base" enums will always have a builtin type (don't skip those).
371
363
  // A type referencing an enum E will be considered an enum itself and have .type === E (skip).
372
364
  if ('enum' in type && !isReferenceType(type) && this.resolver.builtinResolver.resolveBuiltin(type.type)) {
373
- file.addEnum(name, clean, csnToEnumPairs(type))
365
+ file.addEnum(fq, entityName, csnToEnumPairs(type))
374
366
  } else {
375
367
  // alias
376
- file.addType(name, clean, this.resolver.resolveAndRequire(type, file).typeName)
368
+ file.addType(fq, entityName, this.resolver.resolveAndRequire(type, file).typeName)
377
369
  }
378
370
  // TODO: annotations not handled yet
379
371
  }
380
372
 
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)
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)
385
377
  // aspects are technically classes and can therefore be added to the list of defined classes.
386
378
  // Still, when using them as mixins for a class, they need to already be defined.
387
379
  // 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 })
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 })
391
383
  }
392
384
 
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)
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)
398
390
  const buffer = file.events.buffer
399
391
  buffer.add('// event')
400
392
  // 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() {
393
+ buffer.addIndentedBlock(`export declare class ${entityName} {`, () => {
402
394
  const propOpt = this.options.propertiesOptional
403
395
  this.options.propertiesOptional = false
404
396
  for (const [ename, element] of Object.entries(event.elements ?? {})) {
405
397
  this.visitElement(ename, element, file, buffer)
406
398
  }
407
399
  this.options.propertiesOptional = propOpt
408
- }.bind(this), '}')
400
+ }, '}')
409
401
  }
410
402
 
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)
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)
415
407
  // service.name is clean of namespace
416
408
  file.services.buffer.add(`export default { name: '${service.name}' }`)
417
409
  file.addService(service.name)
@@ -420,36 +412,36 @@ class Visitor {
420
412
  /**
421
413
  * Visits a single entity from the CSN's definition field.
422
414
  * 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.
415
+ * @param {string} fq - name of the entity, fully qualified as is used in the definition field.
424
416
  * @param {CSN} entity - CSN data belonging to the entity to perform lookups in.
425
417
  */
426
- visitEntity(name, entity) {
418
+ visitEntity(fq, entity) {
427
419
  switch (entity.kind) {
428
420
  case 'entity':
429
- this.#printEntity(name, entity)
421
+ this.#printEntity(fq, entity)
430
422
  break
431
423
  case 'action':
432
424
  case 'function':
433
- this.#printOperation(name, entity, entity.kind)
425
+ this.#printOperation(fq, entity, entity.kind)
434
426
  break
435
427
  case 'aspect':
436
- this.#printAspect(name, entity)
428
+ this.#printAspect(fq, entity)
437
429
  break
438
430
  case 'type': {
439
431
  // types like inline definitions can be used very similarly to entities.
440
432
  // They can be extended, contain inline enums, etc., so we treat them as entities.
441
433
  const handler = entity.elements ? this.#printEntity : this.#printType
442
- handler.call(this, name, entity)
434
+ handler.call(this, fq, entity)
443
435
  break
444
436
  }
445
437
  case 'event':
446
- this.#printEvent(name, entity)
438
+ this.#printEvent(fq, entity)
447
439
  break
448
440
  case 'service':
449
- this.#printService(name, entity)
441
+ this.#printService(fq, entity)
450
442
  break
451
443
  default:
452
- this.logger.debug(`Unhandled entity kind '${entity.kind}'.`)
444
+ LOG.debug(`Unhandled entity kind '${entity.kind}'.`)
453
445
  }
454
446
  }
455
447
 
@@ -459,7 +451,7 @@ class Visitor {
459
451
  * refer to types via their alias that hides the aspectification.
460
452
  * If we attempt to directly refer to this alias while it has not been fully created,
461
453
  * that will result in a TS error.
462
- * @param {string} entityName
454
+ * @param {string} fq - fully qualified name of the entity
463
455
  * @returns {boolean} true, if `entityName` refers to the surrounding class
464
456
  * @example
465
457
  * ```ts
@@ -469,14 +461,14 @@ class Visitor {
469
461
  * }
470
462
  * ```
471
463
  */
472
- isSelfReference(entityName) {
473
- return entityName === this.contexts.at(-1)?.entity
464
+ isSelfReference(fq) {
465
+ return fq === this.contexts.at(-1)?.entity
474
466
  }
475
467
 
476
468
  /**
477
469
  * Visits a single element in an entity.
478
470
  * @param {string} name - name of the element
479
- * @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.
480
472
  * @param {SourceFile} file - the namespace file the surrounding entity is being printed into.
481
473
  * @param {Buffer} buffer - buffer to add the definition to. If no buffer is passed, the passed file's class buffer is used instead.
482
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.22.0",
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",