@cap-js/cds-typer 0.23.0 → 0.25.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,9 +2,9 @@
2
2
 
3
3
  const util = require('./util')
4
4
 
5
- const { amendCSN, isView, isUnresolved, propagateForeignKeys, isDraftEnabled, isType, isProjection, getMaxCardinality } = require('./csn')
5
+ const { amendCSN, isView, isUnresolved, propagateForeignKeys, isDraftEnabled, isType, isProjection, getMaxCardinality, isViewOrProjection, isEnum } = require('./csn')
6
6
  // eslint-disable-next-line no-unused-vars
7
- const { SourceFile, FileRepository, Buffer } = require('./file')
7
+ const { SourceFile, FileRepository, Buffer, Path } = require('./file')
8
8
  const { FlatInlineDeclarationResolver, StructuredInlineDeclarationResolver } = require('./components/inline')
9
9
  const { Resolver } = require('./resolution/resolver')
10
10
  const { LOG } = require('./logging')
@@ -13,14 +13,18 @@ const { csnToEnumPairs, propertyToInlineEnumName, isInlineEnumType, stringifyEnu
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')
16
+ const { EntityRepository, asIdentifier } = require('./resolution/entity')
17
17
  const { last } = require('./components/identifier')
18
+ const { getPropertyModifiers } = require('./components/property')
18
19
 
19
20
  /** @typedef {import('./file').File} File */
20
21
  /** @typedef {import('./typedefs').visitor.Context} Context */
21
22
  /** @typedef {import('./typedefs').visitor.CompileParameters} CompileParameters */
22
23
  /** @typedef {import('./typedefs').visitor.VisitorOptions} VisitorOptions */
23
24
  /** @typedef {import('./typedefs').visitor.Inflection} Inflection */
25
+ /** @typedef {import('./typedefs').resolver.CSN} CSN */
26
+ /** @typedef {import('./typedefs').resolver.EntityCSN} EntityCSN */
27
+ /** @typedef {import('./typedefs').resolver.EnumCSN} EnumCSN */
24
28
 
25
29
  const defaults = {
26
30
  // FIXME: add defaults for remaining parameters
@@ -36,12 +40,12 @@ class Visitor {
36
40
  * @returns {File[]} a full list of files to be written
37
41
  */
38
42
  getWriteoutFiles() {
39
- return this.fileRepository.getFiles().concat(this.resolver.getUsedLibraries())
43
+ return [...this.fileRepository.getFiles(), ...this.resolver.getUsedLibraries()]
40
44
  }
41
45
 
42
46
  /**
43
47
  * @param {{xtended: CSN, inferred: CSN}} csn - root CSN
44
- * @param {VisitorOptions} options - the options
48
+ * @param {VisitorOptions | {}} options - the options
45
49
  */
46
50
  constructor(csn, options = {}) {
47
51
  amendCSN(csn.xtended)
@@ -124,6 +128,34 @@ class Visitor {
124
128
  })
125
129
  }
126
130
 
131
+ /**
132
+ * @param {EntityCSN} entity - the entity to print the actions for
133
+ * @param {Buffer} buffer - the buffer to write the actions into
134
+ * @param {import('./typedefs').resolver.EntityInfo[]} ancestors - the fully qualified names of the ancestors of the entity
135
+ * @param {SourceFile} file - the file the entity is being printed into
136
+ */
137
+ #printStaticActions(entity, buffer, ancestors, file) {
138
+ // TODO: refactor away! All these printing functionalities need to go
139
+ const actions = Object.entries(entity.actions ?? {})
140
+ const inherited = ancestors.length
141
+ ? ancestors.map(a => `typeof ${asIdentifier({info: a, relative: file.path})}.actions`).join(' & ') + ' & '
142
+ : ''
143
+ if (actions.length) {
144
+ buffer.addIndentedBlock(`declare static readonly actions: ${inherited}{`,
145
+ actions.map(([aname, action]) => SourceFile.stringifyLambda({
146
+ name: aname,
147
+ parameters: this.#stringifyFunctionParams(action.params, file),
148
+ returns: action.returns
149
+ ? this.resolver.resolveAndRequire(action.returns, file).typeName
150
+ : 'any',
151
+ kind: action.kind
152
+ })), '}'
153
+ ) // end of actions
154
+ } else {
155
+ buffer.add(`declare static readonly actions: ${inherited}${empty}`)
156
+ }
157
+ }
158
+
127
159
  /**
128
160
  * Transforms an entity or CDS aspect into a JS aspect (aka mixin).
129
161
  * That is, for an element A we get:
@@ -131,42 +163,66 @@ class Visitor {
131
163
  * - the const AXtended which represents the entity A with all of its aspects mixed in (this const is not exported)
132
164
  * - the type A to use for external typing and is derived from AXtended.
133
165
  * @param {string} fq - the name of the entity
134
- * @param {CSN} entity - the pointer into the CSN to extract the elements from
166
+ * @param {EntityCSN} entity - the pointer into the CSN to extract the elements from
135
167
  * @param {Buffer} buffer - the buffer to write the resulting definitions into
136
168
  * @param {{cleanName?: string}} options - additional options
137
169
  */
138
170
  #aspectify(fq, entity, buffer, options = {}) {
139
171
  const info = this.entityRepository.getByFq(fq)
172
+ if (!info) throw new Error(`could not resolve entity ${fq}`)
140
173
  const clean = options?.cleanName ?? info.withoutNamespace
141
174
  const { namespace } = info
142
175
  const file = this.fileRepository.getNamespaceFile(namespace)
143
- const identSingular = name => name
176
+ const identSingular = (/** @type {string} */name) => name // FIXME: remove
177
+ //const identAspect = name => `_${name}Aspect`
178
+ /** @param {string} name - the name */
144
179
  const identAspect = name => `_${name}Aspect`
145
- const toAspectIdent = (wrapped, [ns, n, fq]) => {
180
+ /**
181
+ * @param {object} options - options
182
+ * @param {Path} [options.ns] - namespace
183
+ * @param {string} options.clean - the clean name of the entity
184
+ * @param {string} options.fq - fully qualified name
185
+ * @returns {string} the local identifier
186
+ */
187
+ // FIXME: replace with resolution/entity::asIdentifier
188
+ const toLocalIdent = ({ns, clean, fq}) => {
146
189
  // 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
- )
190
+ const csn = this.csn.inferred.definitions[fq]
191
+ const ident = isType(csn)
192
+ ? clean
193
+ : this.resolver.inflect({csn, plainName: clean}).singular
152
194
  return !ns || ns.isCwd(file.path.asDirectory())
153
- ? `${ident}(${wrapped})`
154
- : `${ns.asIdentifier()}.${ident}(${wrapped})`
195
+ ? ident
196
+ : `${ns.asIdentifier()}.${ident}`
155
197
  }
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]
198
+ // remove the ancestry of projections/ views.
199
+ // They explicitly define their properties.
200
+ // But at the same time they also carry their .includes clause, which can
201
+ // clash when the user aliases a selected property with the name of
202
+ // a properties of the entities they project on:
203
+ // entity foo as SELECT bar.baz AS ID FROM bar
204
+ // will produce an error if bar also has a property ID which now clashes with foo.ID
205
+ // WARNING: annotations from entities without properties should actually be propagated this way!
206
+ // So once we start caring about annotations, we have to revisit this part.
207
+ /** @type {import('./typedefs').resolver.EntityInfo[]} */
208
+ const ancestorInfos = ((!isViewOrProjection(entity) ? entity.includes : []) ?? [])
209
+ .map(ancestor => {
210
+ const info = this.entityRepository.getByFq(ancestor)
211
+ if (!info) throw new Error(`could not resolve ancestor ${ancestor} for ${fq}`)
212
+ file.addImport(info.namespace)
213
+ return info
161
214
  })
215
+
216
+ const ancestorsAspects = ancestorInfos
162
217
  .reverse() // reverse so that own aspect A is applied before extensions B,C: B(C(A(Entity)))
163
- .reduce(toAspectIdent, 'Base')
218
+ .reduce((wrapped, ancestor) => `${asIdentifier({info: ancestor, wrapper: name => `_${name}Aspect`, relative: file.path})}(${wrapped})`, 'Base')
164
219
 
165
220
  this.contexts.push({ entity: fq })
166
221
 
167
222
  // CLASS ASPECT
168
223
  buffer.addIndentedBlock(`export function ${identAspect(clean)}<TBase extends new (...args: any[]) => object>(Base: TBase) {`, () => {
169
- buffer.addIndentedBlock(`return class extends ${ancestors} {`, () => {
224
+ buffer.addIndentedBlock(`return class ${clean} extends ${ancestorsAspects} {`, () => {
225
+ /** @type {import('./typedefs').resolver.EnumCSN[]} */
170
226
  const enums = []
171
227
  for (let [ename, element] of Object.entries(entity.elements ?? {})) {
172
228
  if (element.target && /\.texts?/.test(element.target)) {
@@ -176,14 +232,14 @@ class Visitor {
176
232
  this.visitElement(ename, element, file, buffer)
177
233
 
178
234
  // make foreign keys explicit
179
- if ('target' in element) {
235
+ if (element.target) {
180
236
  // lookup in cds.definitions can fail for inline structs.
181
237
  // We don't really have to care for this case, as keys from such structs are _not_ propagated to
182
238
  // the containing entity.
183
239
  for (const [kname, originalKeyElement] of this.#keys(element.target)) {
184
240
  if (getMaxCardinality(element) === 1 && typeof element.on !== 'object') { // FIXME: kelement?
185
241
  const foreignKey = `${ename}_${kname}`
186
- if (Object.hasOwn(entity.elements, foreignKey)) {
242
+ if (entity.elements && Object.hasOwn(entity.elements, foreignKey)) {
187
243
  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.`)
188
244
  } else {
189
245
  const kelement = Object.assign(Object.create(originalKeyElement), {
@@ -206,37 +262,39 @@ class Visitor {
206
262
  buffer.add(`static ${e.name} = ${propertyToInlineEnumName(clean, e.name)}`)
207
263
  file.addInlineEnum(clean, fq, e.name, csnToEnumPairs(e, {unwrapVals: true}))
208
264
  }
209
- const actions = Object.entries(entity.actions ?? {})
210
- if (actions.length) {
211
- buffer.addIndentedBlock('static readonly actions: {',
212
- actions.map(([aname, action]) => SourceFile.stringifyLambda({
213
- name: aname,
214
- parameters: this.#stringifyFunctionParams(action.params, file),
215
- returns: action.returns ? this.resolver.resolveAndRequire(action.returns, file).typeName : 'any',
216
- kind: action.kind
217
- }))
218
- , '}') // end of actions
219
- } else {
220
- buffer.add(`static readonly actions: ${empty}`)
221
- }
265
+ this.#printStaticActions(entity, buffer, ancestorInfos, file)
222
266
  })
223
267
  }, '};') // end of generated class
224
268
  }, '}') // end of aspect
225
269
 
226
270
  // CLASS WITH ADDED ASPECTS
227
271
  file.addImport(baseDefinitions.path)
228
- buffer.add(`export class ${identSingular(clean)} extends ${toAspectIdent(`${baseDefinitions.path.asIdentifier()}.Entity`, [undefined, clean, fq])} {${this.#staticClassContents(clean, entity).join('\n')}}`)
272
+ buffer.add(`export class ${identSingular(clean)} extends ${identAspect(toLocalIdent({clean, fq}))}(${baseDefinitions.path.asIdentifier()}.Entity) {${this.#staticClassContents(clean, entity).join('\n')}}`)
229
273
  this.contexts.pop()
230
274
  }
231
275
 
276
+ /**
277
+ * @param {string} clean - the clean name of the entity
278
+ * @param {EntityCSN} entity - the entity to generate the static contents for
279
+ */
232
280
  #staticClassContents(clean, entity) {
233
281
  return isDraftEnabled(entity) ? [`static drafts: typeof ${clean}`] : []
234
282
  }
235
283
 
284
+ /**
285
+ * @param {string} fq - fully qualified name of the entity
286
+ * @param {EntityCSN} entity - the entity to print
287
+ */
236
288
  #printEntity(fq, entity) {
237
289
  // static .name has to be defined more forcefully: https://github.com/microsoft/TypeScript/issues/442
290
+ /**
291
+ * @param {string} clazz - the class to override the name property for
292
+ * @param {string} content - the content to set the name property to
293
+ */
238
294
  const overrideNameProperty = (clazz, content) => `Object.defineProperty(${clazz}, 'name', { value: '${content}' })`
239
- const { namespace: ns, entityName: clean, inflection } = this.entityRepository.getByFq(fq)
295
+ const info = this.entityRepository.getByFq(fq)
296
+ if (!info) throw new Error(`could not resolve entity ${fq}`)
297
+ const { namespace: ns, entityName: clean, inflection } = info
240
298
  const file = this.fileRepository.getNamespaceFile(ns)
241
299
  let { singular, plural } = inflection
242
300
 
@@ -250,7 +308,9 @@ class Visitor {
250
308
  }
251
309
 
252
310
  // as types are not inflected, their singular will always clash and there is also no plural for them anyway -> skip
253
- if (!isType(entity) && `${ns.asNamespace()}.${singular}` in this.csn.xtended.definitions) {
311
+ // if the user defined their entities in singular form we would also have a false positive here -> skip
312
+ const namespacedSingular = `${ns.asNamespace()}.${singular}`
313
+ if (!isType(entity) && namespacedSingular !== fq && namespacedSingular in this.csn.xtended.definitions) {
254
314
  LOG.error(
255
315
  `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.`
256
316
  )
@@ -307,15 +367,15 @@ class Visitor {
307
367
  * Stringifies function parameters in preparation of passing them to {@link SourceFile.stringifyLambda}.
308
368
  * Resolves all parameters to a pair of parameter name and name of the resolved type.
309
369
  * Also filters out parameters that indicate a binding parameter ({@link https://cap.cloud.sap/docs/releases/jan23#simplified-syntax-for-binding-parameters}).
310
- * @param {[string, object][]} params - parameter list as found in CSN.
311
- * @param {File} file - source file relative to which the parameter types should be resolved.
370
+ * @param {{[key:string]: EntityCSN}} params - parameter list as found in CSN.
371
+ * @param {SourceFile} file - source file relative to which the parameter types should be resolved.
312
372
  * @returns {[string, string][]} pair of names and types.
313
373
  */
314
374
  #stringifyFunctionParams(params, file) {
315
375
  return params
316
376
  ? Object.entries(params)
317
377
  // filter params of type '[many] $self', as they are not to be part of the implementation
318
- .filter(([, type]) => type?.type !== '$self' && !(type.items?.type === '$self'))
378
+ .filter(([, type]) => type?.type !== '$self' && type.items?.type !== '$self')
319
379
  .map(([name, type]) => [
320
380
  name,
321
381
  this.#stringifyFunctionParamType(type, file)
@@ -323,68 +383,98 @@ class Visitor {
323
383
  : []
324
384
  }
325
385
 
386
+ /**
387
+ * @param {EntityCSN | EnumCSN} type - type
388
+ * @param {SourceFile} file - the file to resolve types into
389
+ */
326
390
  #stringifyFunctionParamType(type, file) {
327
391
  // if type.type is not 'cds.String', 'cds.Integer', ..., then we are actually looking
328
392
  // at a named enum type. In that case also resolve that type name
329
- if (type.enum && this.resolver.builtinResolver.resolveBuiltin(type.type)) return stringifyEnumType(csnToEnumPairs(type))
393
+ const isNamedEnumType = isEnum(type) && this.resolver.builtinResolver.resolveBuiltin(type.type)
394
+ if (isNamedEnumType) return stringifyEnumType(csnToEnumPairs(type))
330
395
  const paramType = this.resolver.resolveAndRequire(type, file)
331
396
  return this.inlineDeclarationResolver.getPropertyDatatype(
332
397
  paramType,
333
- paramType.typeInfo.isArray || paramType.typeInfo.isDeepRequire ? paramType.typeName : paramType.typeInfo.inflection.singular
398
+ paramType.typeInfo.isArray || paramType.typeInfo.isDeepRequire
399
+ ? paramType.typeName
400
+ : paramType.typeInfo.inflection.singular
334
401
  )
335
402
  }
336
403
 
337
404
  /**
338
405
  * @param {string} fq - fully qualified name of the operation
339
- * @param {object} operation - CSN
406
+ * @param {import('./typedefs').resolver.OperationCSN} operation - CSN
340
407
  * @param {'function' | 'action'} kind - kind of operation
341
408
  */
342
409
  #printOperation(fq, operation, kind) {
343
410
  LOG.debug(`Printing operation ${fq}:\n${JSON.stringify(operation, null, 2)}`)
344
- const { namespace } = this.entityRepository.getByFq(fq)
411
+ const info = this.entityRepository.getByFq(fq)
412
+ if (!info) throw new Error(`could not resolve operation ${fq}`)
413
+ const { namespace } = info
345
414
  const file = this.fileRepository.getNamespaceFile(namespace)
346
415
  const params = this.#stringifyFunctionParams(operation.params, file)
347
416
  const returnType = operation.returns
348
417
  ? this.resolver.resolveAndRequire(operation.returns, file)
349
- : { typeName: 'void', typeInfo: { isArray: false, inflection: { singular: 'void', plural: 'void' } } }
418
+ : { typeName: 'void', typeInfo: { plainName: 'void', isArray: false, inflection: { singular: 'void', plural: 'void' } } }
350
419
  const returns = this.inlineDeclarationResolver.getPropertyDatatype(
351
420
  returnType,
352
- returnType.typeInfo.isArray ? returnType.typeName : returnType.typeInfo.inflection.singular
421
+ returnType.typeInfo.isArray
422
+ ? returnType.typeName
423
+ : returnType.typeInfo.inflection.singular
353
424
  )
354
425
  file.addOperation(last(fq), params, returns, kind)
355
426
  }
356
427
 
428
+ /**
429
+ * @param {string} fq - fully qualified name of the type
430
+ * @param {EntityCSN} type - CSN
431
+ */
357
432
  #printType(fq, type) {
358
433
  LOG.debug(`Printing type ${fq}:\n${JSON.stringify(type, null, 2)}`)
359
- const { namespace, entityName } = this.entityRepository.getByFq(fq)
434
+ const info = this.entityRepository.getByFq(fq)
435
+ if (!info) throw new Error(`could not resolve type ${fq}`)
436
+ const { namespace, entityName } = info
360
437
  const file = this.fileRepository.getNamespaceFile(namespace)
361
438
  // skip references to enums.
362
439
  // "Base" enums will always have a builtin type (don't skip those).
363
440
  // A type referencing an enum E will be considered an enum itself and have .type === E (skip).
364
- if ('enum' in type && !isReferenceType(type) && this.resolver.builtinResolver.resolveBuiltin(type.type)) {
441
+ if (isEnum(type) && !isReferenceType(type) && this.resolver.builtinResolver.resolveBuiltin(type.type)) {
365
442
  file.addEnum(fq, entityName, csnToEnumPairs(type))
366
443
  } else {
444
+ const isEnumReference = typeof type.type === 'string' && isEnum(this.csn.inferred.definitions[type?.type])
367
445
  // alias
368
- file.addType(fq, entityName, this.resolver.resolveAndRequire(type, file).typeName)
446
+ file.addType(fq, entityName, this.resolver.resolveAndRequire(type, file).typeName, isEnumReference)
369
447
  }
370
448
  // TODO: annotations not handled yet
371
449
  }
372
450
 
451
+ /**
452
+ * @param {string} fq - fully qualified name of the aspect
453
+ * @param {EntityCSN} aspect - CSN
454
+ */
373
455
  #printAspect(fq, aspect) {
374
456
  LOG.debug(`Printing aspect ${fq}`)
375
- const { namespace, entityName } = this.entityRepository.getByFq(fq)
457
+ const info = this.entityRepository.getByFq(fq)
458
+ if (!info) throw new Error(`could not resolve aspect ${fq}`)
459
+ const { namespace, entityName, inflection } = info
376
460
  const file = this.fileRepository.getNamespaceFile(namespace)
377
461
  // aspects are technically classes and can therefore be added to the list of defined classes.
378
462
  // Still, when using them as mixins for a class, they need to already be defined.
379
463
  // So we separate them into another buffer which is printed before the classes.
380
464
  file.addClass(entityName, fq)
381
465
  file.aspects.add(`// the following represents the CDS aspect '${entityName}'`)
382
- this.#aspectify(fq, aspect, file.aspects, { cleanName: entityName })
466
+ this.#aspectify(fq, aspect, file.aspects, { cleanName: inflection.singular })
383
467
  }
384
468
 
469
+ /**
470
+ * @param {string} fq - fully qualified name of the event
471
+ * @param {EntityCSN} event - CSN
472
+ */
385
473
  #printEvent(fq, event) {
386
474
  LOG.debug(`Printing event ${fq}`)
387
- const { namespace, entityName } = this.entityRepository.getByFq(fq)
475
+ const info = this.entityRepository.getByFq(fq)
476
+ if (!info) throw new Error(`could not resolve event ${fq}`)
477
+ const { namespace, entityName } = info
388
478
  const file = this.fileRepository.getNamespaceFile(namespace)
389
479
  file.addEvent(entityName, fq)
390
480
  const buffer = file.events.buffer
@@ -400,9 +490,15 @@ class Visitor {
400
490
  }, '}')
401
491
  }
402
492
 
493
+ /**
494
+ * @param {string} fq - fully qualified name of the service
495
+ * @param {EntityCSN} service - CSN
496
+ */
403
497
  #printService(fq, service) {
404
498
  LOG.debug(`Printing service ${fq}:\n${JSON.stringify(service, null, 2)}`)
405
- const { namespace } = this.entityRepository.getByFq(fq)
499
+ const info = this.entityRepository.getByFq(fq)
500
+ if (!info) throw new Error(`could not resolve service ${fq}`)
501
+ const { namespace } = info
406
502
  const file = this.fileRepository.getNamespaceFile(namespace)
407
503
  // service.name is clean of namespace
408
504
  file.services.buffer.add(`export default { name: '${service.name}' }`)
@@ -413,7 +509,7 @@ class Visitor {
413
509
  * Visits a single entity from the CSN's definition field.
414
510
  * Will call #printEntity or #printAction based on the entity's kind.
415
511
  * @param {string} fq - name of the entity, fully qualified as is used in the definition field.
416
- * @param {CSN} entity - CSN data belonging to the entity to perform lookups in.
512
+ * @param {EntityCSN} entity - CSN data belonging to the entity to perform lookups in.
417
513
  */
418
514
  visitEntity(fq, entity) {
419
515
  switch (entity.kind) {
@@ -422,6 +518,7 @@ class Visitor {
422
518
  break
423
519
  case 'action':
424
520
  case 'function':
521
+ // @ts-expect-error - we know entity is actually an OperationCSN
425
522
  this.#printOperation(fq, entity, entity.kind)
426
523
  break
427
524
  case 'aspect':
@@ -468,16 +565,25 @@ class Visitor {
468
565
  /**
469
566
  * Visits a single element in an entity.
470
567
  * @param {string} name - name of the element
471
- * @param {import('./resolution/resolver').CSN} element - CSN data belonging to the the element.
568
+ * @param {EntityCSN} element - CSN data belonging to the the element.
472
569
  * @param {SourceFile} file - the namespace file the surrounding entity is being printed into.
473
- * @param {Buffer} buffer - buffer to add the definition to. If no buffer is passed, the passed file's class buffer is used instead.
570
+ * @param {Buffer} [buffer] - buffer to add the definition to. If no buffer is passed, the passed file's class buffer is used instead.
474
571
  * @returns @see InlineDeclarationResolver.visitElement
475
572
  */
476
- visitElement(name, element, file, buffer) {
477
- return this.inlineDeclarationResolver.visitElement(name, element, file, buffer)
573
+ visitElement(name, element, file, buffer = file.classes) {
574
+ return this.inlineDeclarationResolver.visitElement({
575
+ name,
576
+ element,
577
+ file,
578
+ buffer,
579
+ // we explicitly pass the "declare" modifier here to avoid problems with noImplicitOverride and useDefineForClassFields in strict tsconfigs
580
+ // but not inside type defs (e.g. parameter types) where this would be a syntax error
581
+ modifiers: getPropertyModifiers(element)
582
+ })
478
583
  }
479
584
  }
480
585
 
481
586
  module.exports = {
482
587
  Visitor
483
588
  }
589
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js/cds-typer",
3
- "version": "0.23.0",
3
+ "version": "0.25.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",
@@ -24,7 +24,8 @@
24
24
  "doc:clean": "rm -rf ./doc",
25
25
  "doc:prepare": "npm run doc:clean && mkdir -p doc/types",
26
26
  "doc:typegen": "./node_modules/.bin/tsc ./lib/*.js --skipLibCheck --declaration --allowJs --emitDeclarationOnly --outDir doc/types && cd doc/types && tsc --init",
27
- "doc:cli": "npm run cli -- --help > ./doc/cli.txt"
27
+ "doc:cli": "npm run cli -- --help > ./doc/cli.txt",
28
+ "jsdoc:check": "tsc --noEmit --project jsconfig.json"
28
29
  },
29
30
  "files": [
30
31
  "lib/",
@@ -44,6 +45,7 @@
44
45
  },
45
46
  "devDependencies": {
46
47
  "@stylistic/eslint-plugin-js": "^1.6.3",
48
+ "@cap-js/cds-types": ">=0.6",
47
49
  "acorn": "^8.10.0",
48
50
  "eslint": "^9",
49
51
  "eslint-plugin-jsdoc": "^48.2.7",