@cap-js/cds-typer 0.24.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/CHANGELOG.md +14 -1
- package/lib/cli.js +24 -14
- package/lib/compile.js +3 -3
- package/lib/components/enum.js +16 -9
- package/lib/components/identifier.js +1 -1
- package/lib/components/inline.js +81 -26
- package/lib/components/property.js +12 -0
- package/lib/components/wrappers.js +1 -1
- package/lib/csn.js +90 -26
- package/lib/file.js +43 -19
- package/lib/logging.js +5 -1
- package/lib/resolution/builtin.js +3 -2
- package/lib/resolution/entity.js +46 -7
- package/lib/resolution/resolver.js +51 -20
- package/lib/typedefs.d.ts +67 -13
- package/lib/util.js +4 -3
- package/lib/visitor.js +163 -59
- package/package.json +4 -2
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()
|
|
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 {
|
|
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
|
-
|
|
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
|
|
148
|
-
const ident =
|
|
149
|
-
?
|
|
150
|
-
: this.resolver.inflect({csn
|
|
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
|
-
?
|
|
154
|
-
: `${ns.asIdentifier()}.${ident}
|
|
195
|
+
? ident
|
|
196
|
+
: `${ns.asIdentifier()}.${ident}`
|
|
155
197
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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(
|
|
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 ${clean} extends ${
|
|
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 (
|
|
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
|
-
|
|
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 ${
|
|
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
|
|
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
|
|
|
@@ -309,15 +367,15 @@ class Visitor {
|
|
|
309
367
|
* Stringifies function parameters in preparation of passing them to {@link SourceFile.stringifyLambda}.
|
|
310
368
|
* Resolves all parameters to a pair of parameter name and name of the resolved type.
|
|
311
369
|
* Also filters out parameters that indicate a binding parameter ({@link https://cap.cloud.sap/docs/releases/jan23#simplified-syntax-for-binding-parameters}).
|
|
312
|
-
* @param {[string
|
|
313
|
-
* @param {
|
|
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.
|
|
314
372
|
* @returns {[string, string][]} pair of names and types.
|
|
315
373
|
*/
|
|
316
374
|
#stringifyFunctionParams(params, file) {
|
|
317
375
|
return params
|
|
318
376
|
? Object.entries(params)
|
|
319
377
|
// filter params of type '[many] $self', as they are not to be part of the implementation
|
|
320
|
-
.filter(([, type]) => type?.type !== '$self' &&
|
|
378
|
+
.filter(([, type]) => type?.type !== '$self' && type.items?.type !== '$self')
|
|
321
379
|
.map(([name, type]) => [
|
|
322
380
|
name,
|
|
323
381
|
this.#stringifyFunctionParamType(type, file)
|
|
@@ -325,56 +383,80 @@ class Visitor {
|
|
|
325
383
|
: []
|
|
326
384
|
}
|
|
327
385
|
|
|
386
|
+
/**
|
|
387
|
+
* @param {EntityCSN | EnumCSN} type - type
|
|
388
|
+
* @param {SourceFile} file - the file to resolve types into
|
|
389
|
+
*/
|
|
328
390
|
#stringifyFunctionParamType(type, file) {
|
|
329
391
|
// if type.type is not 'cds.String', 'cds.Integer', ..., then we are actually looking
|
|
330
392
|
// at a named enum type. In that case also resolve that type name
|
|
331
|
-
|
|
393
|
+
const isNamedEnumType = isEnum(type) && this.resolver.builtinResolver.resolveBuiltin(type.type)
|
|
394
|
+
if (isNamedEnumType) return stringifyEnumType(csnToEnumPairs(type))
|
|
332
395
|
const paramType = this.resolver.resolveAndRequire(type, file)
|
|
333
396
|
return this.inlineDeclarationResolver.getPropertyDatatype(
|
|
334
397
|
paramType,
|
|
335
|
-
paramType.typeInfo.isArray || paramType.typeInfo.isDeepRequire
|
|
398
|
+
paramType.typeInfo.isArray || paramType.typeInfo.isDeepRequire
|
|
399
|
+
? paramType.typeName
|
|
400
|
+
: paramType.typeInfo.inflection.singular
|
|
336
401
|
)
|
|
337
402
|
}
|
|
338
403
|
|
|
339
404
|
/**
|
|
340
405
|
* @param {string} fq - fully qualified name of the operation
|
|
341
|
-
* @param {
|
|
406
|
+
* @param {import('./typedefs').resolver.OperationCSN} operation - CSN
|
|
342
407
|
* @param {'function' | 'action'} kind - kind of operation
|
|
343
408
|
*/
|
|
344
409
|
#printOperation(fq, operation, kind) {
|
|
345
410
|
LOG.debug(`Printing operation ${fq}:\n${JSON.stringify(operation, null, 2)}`)
|
|
346
|
-
const
|
|
411
|
+
const info = this.entityRepository.getByFq(fq)
|
|
412
|
+
if (!info) throw new Error(`could not resolve operation ${fq}`)
|
|
413
|
+
const { namespace } = info
|
|
347
414
|
const file = this.fileRepository.getNamespaceFile(namespace)
|
|
348
415
|
const params = this.#stringifyFunctionParams(operation.params, file)
|
|
349
416
|
const returnType = operation.returns
|
|
350
417
|
? this.resolver.resolveAndRequire(operation.returns, file)
|
|
351
|
-
: { typeName: 'void', typeInfo: { isArray: false, inflection: { singular: 'void', plural: 'void' } } }
|
|
418
|
+
: { typeName: 'void', typeInfo: { plainName: 'void', isArray: false, inflection: { singular: 'void', plural: 'void' } } }
|
|
352
419
|
const returns = this.inlineDeclarationResolver.getPropertyDatatype(
|
|
353
420
|
returnType,
|
|
354
|
-
returnType.typeInfo.isArray
|
|
421
|
+
returnType.typeInfo.isArray
|
|
422
|
+
? returnType.typeName
|
|
423
|
+
: returnType.typeInfo.inflection.singular
|
|
355
424
|
)
|
|
356
425
|
file.addOperation(last(fq), params, returns, kind)
|
|
357
426
|
}
|
|
358
427
|
|
|
428
|
+
/**
|
|
429
|
+
* @param {string} fq - fully qualified name of the type
|
|
430
|
+
* @param {EntityCSN} type - CSN
|
|
431
|
+
*/
|
|
359
432
|
#printType(fq, type) {
|
|
360
433
|
LOG.debug(`Printing type ${fq}:\n${JSON.stringify(type, null, 2)}`)
|
|
361
|
-
const
|
|
434
|
+
const info = this.entityRepository.getByFq(fq)
|
|
435
|
+
if (!info) throw new Error(`could not resolve type ${fq}`)
|
|
436
|
+
const { namespace, entityName } = info
|
|
362
437
|
const file = this.fileRepository.getNamespaceFile(namespace)
|
|
363
438
|
// skip references to enums.
|
|
364
439
|
// "Base" enums will always have a builtin type (don't skip those).
|
|
365
440
|
// A type referencing an enum E will be considered an enum itself and have .type === E (skip).
|
|
366
|
-
if (
|
|
441
|
+
if (isEnum(type) && !isReferenceType(type) && this.resolver.builtinResolver.resolveBuiltin(type.type)) {
|
|
367
442
|
file.addEnum(fq, entityName, csnToEnumPairs(type))
|
|
368
443
|
} else {
|
|
444
|
+
const isEnumReference = typeof type.type === 'string' && isEnum(this.csn.inferred.definitions[type?.type])
|
|
369
445
|
// alias
|
|
370
|
-
file.addType(fq, entityName, this.resolver.resolveAndRequire(type, file).typeName)
|
|
446
|
+
file.addType(fq, entityName, this.resolver.resolveAndRequire(type, file).typeName, isEnumReference)
|
|
371
447
|
}
|
|
372
448
|
// TODO: annotations not handled yet
|
|
373
449
|
}
|
|
374
450
|
|
|
451
|
+
/**
|
|
452
|
+
* @param {string} fq - fully qualified name of the aspect
|
|
453
|
+
* @param {EntityCSN} aspect - CSN
|
|
454
|
+
*/
|
|
375
455
|
#printAspect(fq, aspect) {
|
|
376
456
|
LOG.debug(`Printing aspect ${fq}`)
|
|
377
|
-
const
|
|
457
|
+
const info = this.entityRepository.getByFq(fq)
|
|
458
|
+
if (!info) throw new Error(`could not resolve aspect ${fq}`)
|
|
459
|
+
const { namespace, entityName, inflection } = info
|
|
378
460
|
const file = this.fileRepository.getNamespaceFile(namespace)
|
|
379
461
|
// aspects are technically classes and can therefore be added to the list of defined classes.
|
|
380
462
|
// Still, when using them as mixins for a class, they need to already be defined.
|
|
@@ -384,9 +466,15 @@ class Visitor {
|
|
|
384
466
|
this.#aspectify(fq, aspect, file.aspects, { cleanName: inflection.singular })
|
|
385
467
|
}
|
|
386
468
|
|
|
469
|
+
/**
|
|
470
|
+
* @param {string} fq - fully qualified name of the event
|
|
471
|
+
* @param {EntityCSN} event - CSN
|
|
472
|
+
*/
|
|
387
473
|
#printEvent(fq, event) {
|
|
388
474
|
LOG.debug(`Printing event ${fq}`)
|
|
389
|
-
const
|
|
475
|
+
const info = this.entityRepository.getByFq(fq)
|
|
476
|
+
if (!info) throw new Error(`could not resolve event ${fq}`)
|
|
477
|
+
const { namespace, entityName } = info
|
|
390
478
|
const file = this.fileRepository.getNamespaceFile(namespace)
|
|
391
479
|
file.addEvent(entityName, fq)
|
|
392
480
|
const buffer = file.events.buffer
|
|
@@ -402,9 +490,15 @@ class Visitor {
|
|
|
402
490
|
}, '}')
|
|
403
491
|
}
|
|
404
492
|
|
|
493
|
+
/**
|
|
494
|
+
* @param {string} fq - fully qualified name of the service
|
|
495
|
+
* @param {EntityCSN} service - CSN
|
|
496
|
+
*/
|
|
405
497
|
#printService(fq, service) {
|
|
406
498
|
LOG.debug(`Printing service ${fq}:\n${JSON.stringify(service, null, 2)}`)
|
|
407
|
-
const
|
|
499
|
+
const info = this.entityRepository.getByFq(fq)
|
|
500
|
+
if (!info) throw new Error(`could not resolve service ${fq}`)
|
|
501
|
+
const { namespace } = info
|
|
408
502
|
const file = this.fileRepository.getNamespaceFile(namespace)
|
|
409
503
|
// service.name is clean of namespace
|
|
410
504
|
file.services.buffer.add(`export default { name: '${service.name}' }`)
|
|
@@ -415,7 +509,7 @@ class Visitor {
|
|
|
415
509
|
* Visits a single entity from the CSN's definition field.
|
|
416
510
|
* Will call #printEntity or #printAction based on the entity's kind.
|
|
417
511
|
* @param {string} fq - name of the entity, fully qualified as is used in the definition field.
|
|
418
|
-
* @param {
|
|
512
|
+
* @param {EntityCSN} entity - CSN data belonging to the entity to perform lookups in.
|
|
419
513
|
*/
|
|
420
514
|
visitEntity(fq, entity) {
|
|
421
515
|
switch (entity.kind) {
|
|
@@ -424,6 +518,7 @@ class Visitor {
|
|
|
424
518
|
break
|
|
425
519
|
case 'action':
|
|
426
520
|
case 'function':
|
|
521
|
+
// @ts-expect-error - we know entity is actually an OperationCSN
|
|
427
522
|
this.#printOperation(fq, entity, entity.kind)
|
|
428
523
|
break
|
|
429
524
|
case 'aspect':
|
|
@@ -470,16 +565,25 @@ class Visitor {
|
|
|
470
565
|
/**
|
|
471
566
|
* Visits a single element in an entity.
|
|
472
567
|
* @param {string} name - name of the element
|
|
473
|
-
* @param {
|
|
568
|
+
* @param {EntityCSN} element - CSN data belonging to the the element.
|
|
474
569
|
* @param {SourceFile} file - the namespace file the surrounding entity is being printed into.
|
|
475
|
-
* @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.
|
|
476
571
|
* @returns @see InlineDeclarationResolver.visitElement
|
|
477
572
|
*/
|
|
478
|
-
visitElement(name, element, file, buffer) {
|
|
479
|
-
return this.inlineDeclarationResolver.visitElement(
|
|
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
|
+
})
|
|
480
583
|
}
|
|
481
584
|
}
|
|
482
585
|
|
|
483
586
|
module.exports = {
|
|
484
587
|
Visitor
|
|
485
588
|
}
|
|
589
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/cds-typer",
|
|
3
|
-
"version": "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",
|