@cap-js/cds-typer 0.35.0 → 0.37.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/boilerplate/tsBoilerplate.ts +1 -1
- package/lib/components/enum.js +0 -1
- package/lib/components/property.js +7 -3
- package/lib/csn.js +6 -0
- package/lib/file.js +3 -2
- package/lib/resolution/resolver.js +21 -7
- package/lib/visitor.js +30 -2
- package/package.json +2 -2
|
@@ -72,7 +72,7 @@ export type CdsMap = { [key: string]: unknown };
|
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
export const createEntityProxy = function (fqParts: any, opts = {}) {
|
|
75
|
-
const { target, customProps } = { target: {}, customProps: [], ...opts }
|
|
75
|
+
const { target, customProps } = { target: {}, customProps: [] as any[], ...opts }
|
|
76
76
|
const fq = fqParts.filter((p: any) => !!p).join('.')
|
|
77
77
|
return new Proxy(target, {
|
|
78
78
|
get: function (target:any, prop:any) {
|
package/lib/components/enum.js
CHANGED
|
@@ -124,7 +124,6 @@ const isInlineEnumType = (element, csn) => element.enum
|
|
|
124
124
|
* @param {[string, string][]} kvs - a list of key-value pairs. Values that are falsey are replaced by
|
|
125
125
|
* @param {import('../printers/javascript').Printer} jsp - the printer to use
|
|
126
126
|
*/
|
|
127
|
-
// ??= for inline enums. If there is some static property of that name, we don't want to override it (for example: ".actions"
|
|
128
127
|
const stringifyEnumImplementation = (name, kvs, jsp) => jsp.printExport(
|
|
129
128
|
name,
|
|
130
129
|
`{ ${kvs.map(([k,v]) => `${normalise(k)}: ${v}`).join(', ')} }`,
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Determines the proper modifiers for a property.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* All properties must be modified by `declare`.
|
|
5
|
+
* The only exception to this is in case of an action return type being defined
|
|
6
|
+
* as an object. Here `declare` would cause an error.
|
|
7
|
+
* This case can be identified by checking the parent of the property:
|
|
8
|
+
* If the parent is a type and has no name, then it is an action return type.
|
|
5
9
|
* @param {import('../typedefs').resolver.EntityCSN} element - The element to determine the modifiers for.
|
|
6
10
|
* @returns {import('../typedefs').resolver.PropertyModifier[]} The modifiers for the property.
|
|
7
11
|
*/
|
|
8
|
-
const getPropertyModifiers = element => element?.parent?.kind
|
|
12
|
+
const getPropertyModifiers = element => element?.parent?.kind === 'type' && element?.parent?.name === undefined ? [] : ['declare']
|
|
9
13
|
|
|
10
14
|
module.exports = {
|
|
11
15
|
getPropertyModifiers
|
package/lib/csn.js
CHANGED
|
@@ -65,6 +65,11 @@ const isEntity = entity => entity?.kind === 'entity'
|
|
|
65
65
|
*/
|
|
66
66
|
const isEnum = entity => Boolean(entity && Object.hasOwn(entity, 'enum'))
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* @param {EntityCSN & { '@cds.external'?: boolean } | undefined} entity - the entity
|
|
70
|
+
*/
|
|
71
|
+
const isExternal = entity => entity?.['@cds.external'] === true
|
|
72
|
+
|
|
68
73
|
/**
|
|
69
74
|
* Attempts to retrieve the max cardinality of a CSN for an entity.
|
|
70
75
|
* @param {EntityCSN} element - csn of entity to retrieve cardinality for
|
|
@@ -365,6 +370,7 @@ module.exports = {
|
|
|
365
370
|
isEnum,
|
|
366
371
|
isUnresolved,
|
|
367
372
|
isType,
|
|
373
|
+
isExternal,
|
|
368
374
|
getMaxCardinality,
|
|
369
375
|
getProjectionTarget,
|
|
370
376
|
getProjectionAliases,
|
package/lib/file.js
CHANGED
|
@@ -162,6 +162,7 @@ class SourceFile extends File {
|
|
|
162
162
|
* @param {string} options.name - name of the lambda
|
|
163
163
|
* @param {import('./typedefs').visitor.ParamInfo[]} [options.parameters] - list of parameters, passed as [name, modifier, type, doc] pairs
|
|
164
164
|
* @param {string} [options.returns] - the return type of the function
|
|
165
|
+
* @param {string} [options.self] - what is set as "__self", which is used for bound actions
|
|
165
166
|
* @param {'action' | 'function'} options.kind - kind of the lambda
|
|
166
167
|
* @param {string} [options.initialiser] - the initialiser expression
|
|
167
168
|
* @param {boolean} [options.isStatic] - whether the lambda is static
|
|
@@ -187,7 +188,7 @@ class SourceFile extends File {
|
|
|
187
188
|
* stringifyLambda({name: 'f', parameters: [{name:'p',type:'T'}], returns: 'number'}) // { (p: T): number, __parameters: { p: T } }
|
|
188
189
|
* ```
|
|
189
190
|
*/
|
|
190
|
-
static stringifyLambda({name, parameters=[], returns='any', kind, initialiser, isStatic=false, callStyles={positional:true, named:true}, doc}) {
|
|
191
|
+
static stringifyLambda({name, parameters=[], returns='any', kind, initialiser, self='null', isStatic=false, callStyles={positional:true, named:true}, doc}) {
|
|
191
192
|
let docStr = doc?.length ? doc.join('\n')+'\n' : ''
|
|
192
193
|
const parameterTypes = parameters.map(({name, modifier, type, doc}) => `${doc?'\n'+doc:''}${normalise(name)}${modifier}: ${type}`).join(', ')
|
|
193
194
|
const parameterTypeAsObject = parameterTypes.length
|
|
@@ -212,7 +213,7 @@ class SourceFile extends File {
|
|
|
212
213
|
|
|
213
214
|
return [
|
|
214
215
|
`${prefix} {`,
|
|
215
|
-
[...callableSignatures, '// metadata (do not use)', `__parameters: ${parameterTypeAsObject}, __returns: ${returns}`, ...kindDef],
|
|
216
|
+
[...callableSignatures, '// metadata (do not use)', `__parameters: ${parameterTypeAsObject}, __returns: ${returns}, __self: ${self}`, ...kindDef],
|
|
216
217
|
`}${suffix}`,
|
|
217
218
|
]
|
|
218
219
|
}
|
|
@@ -7,7 +7,7 @@ const { deepRequire, createToManyAssociation, createToOneAssociation, createArra
|
|
|
7
7
|
const { StructuredInlineDeclarationResolver } = require('../components/inline')
|
|
8
8
|
const { isInlineEnumType, propertyToInlineEnumName } = require('../components/enum')
|
|
9
9
|
const { isReferenceType } = require('../components/reference')
|
|
10
|
-
const { isEntity, getMaxCardinality } = require('../csn')
|
|
10
|
+
const { isEntity, getMaxCardinality, isExternal } = require('../csn')
|
|
11
11
|
const { getBaseDefinitions } = require('../components/basedefs')
|
|
12
12
|
const { BuiltinResolver } = require('./builtin')
|
|
13
13
|
const { LOG } = require('../logging')
|
|
@@ -312,11 +312,24 @@ class Resolver {
|
|
|
312
312
|
}[element.constructor.name] ?? []
|
|
313
313
|
|
|
314
314
|
if (toOne && toMany) {
|
|
315
|
-
/**
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
315
|
+
/**
|
|
316
|
+
* Resolve a property from a CSN entity. If it is a reference, leave it as is.
|
|
317
|
+
* If it is a string, return an object with type set to the string.
|
|
318
|
+
* @param {Record<string, any>} el - the element to check
|
|
319
|
+
* @param {string} property - the property to check
|
|
320
|
+
* @returns {import('../typedefs').resolver.EntityCSN | { type: string }}
|
|
321
|
+
*/
|
|
322
|
+
const getTarget = (el, property) => typeof el[property] === 'string'
|
|
323
|
+
? { type: el[property] }
|
|
324
|
+
: el[property]
|
|
325
|
+
|
|
326
|
+
/** @type { EntityCSN | { type: string } | undefined } */
|
|
327
|
+
const target = element.items
|
|
328
|
+
?? getTarget(element, 'target')
|
|
329
|
+
?? getTarget(element, 'targetAspect') // Composition of aspects
|
|
330
|
+
if (!target) {
|
|
331
|
+
throw new Error(`Could not resolve target of ${element}`)
|
|
332
|
+
}
|
|
320
333
|
/** set `notNull = true` to avoid repeated `| not null` TS construction */
|
|
321
334
|
// @ts-expect-error - yes, we know that notNull is not part of the type in some cases
|
|
322
335
|
target.notNull = true
|
|
@@ -536,10 +549,11 @@ class Resolver {
|
|
|
536
549
|
result.isBuiltin = true
|
|
537
550
|
this.resolveType(element.items, file)
|
|
538
551
|
//delete element.items
|
|
539
|
-
} else if (!result.isBuiltin && element?.elements && (options?.forceInlineStructs || !element?.type)) {
|
|
552
|
+
} else if (!result.isBuiltin && !isExternal(element) && element?.elements && (options?.forceInlineStructs || !element?.type)) {
|
|
540
553
|
// explicitly skip named type definitions, which have elements too, but should not be considered inline declarations
|
|
541
554
|
// if the resolver option `forceInlineStructs` is `true`, named types in elements will be converted to inline
|
|
542
555
|
// Skipping isBuiltin will skip cds.Map, which has elements
|
|
556
|
+
// Skipping isExternal will skip @cds.external entities, which have elements as well
|
|
543
557
|
this.#resolveInlineDeclarationType(element.elements, result, file, options)
|
|
544
558
|
}
|
|
545
559
|
|
package/lib/visitor.js
CHANGED
|
@@ -19,6 +19,7 @@ const { createMember } = require('./components/class')
|
|
|
19
19
|
const { overrideNameProperty } = require('./printers/javascript')
|
|
20
20
|
|
|
21
21
|
const baseDefinitions = getBaseDefinitions()
|
|
22
|
+
const MAX_TRANSITIVE_RESOLUTION_STEPS = 10
|
|
22
23
|
|
|
23
24
|
/** @typedef {import('./file').File} File */
|
|
24
25
|
/** @typedef {import('./typedefs').visitor.Context} Context */
|
|
@@ -94,11 +95,12 @@ class Visitor {
|
|
|
94
95
|
|
|
95
96
|
/**
|
|
96
97
|
* @param {EntityCSN} entity - the entity to print the actions for
|
|
98
|
+
* @param {string} clean - the clean entity name
|
|
97
99
|
* @param {Buffer} buffer - the buffer to write the actions into
|
|
98
100
|
* @param {import('./typedefs').resolver.EntityInfo[]} ancestors - the fully qualified names of the ancestors of the entity
|
|
99
101
|
* @param {SourceFile} file - the file the entity is being printed into
|
|
100
102
|
*/
|
|
101
|
-
#printStaticActions(entity, buffer, ancestors, file) {
|
|
103
|
+
#printStaticActions(entity, clean, buffer, ancestors, file) {
|
|
102
104
|
// TODO: refactor away! All these printing functionalities need to go
|
|
103
105
|
const actions = Object.entries(entity.actions ?? {})
|
|
104
106
|
const inherited = ancestors.map(a => `typeof ${asIdentifier({info: a, relative: file.path})}.actions`)
|
|
@@ -109,6 +111,7 @@ class Visitor {
|
|
|
109
111
|
() => {
|
|
110
112
|
for (const [aname, action] of actions) {
|
|
111
113
|
const [opener, content, closer] = SourceFile.stringifyLambda({
|
|
114
|
+
self: clean,
|
|
112
115
|
name: aname,
|
|
113
116
|
parameters: this.#stringifyFunctionParams(action.params, file),
|
|
114
117
|
returns: action.returns
|
|
@@ -276,6 +279,31 @@ class Visitor {
|
|
|
276
279
|
}))
|
|
277
280
|
if (typeof e?.type !== 'string' && e?.type?.ref) {
|
|
278
281
|
e.resolvedType = /** @type {string} */(lookUpRefType(this.csn, e.type.ref)?.type)
|
|
282
|
+
try {
|
|
283
|
+
/**
|
|
284
|
+
* multi-level resolution does not contain a .ref property:
|
|
285
|
+
* ```cds
|
|
286
|
+
* entity A {
|
|
287
|
+
* x: enum ...
|
|
288
|
+
* }
|
|
289
|
+
* entity B {
|
|
290
|
+
* x: A:x
|
|
291
|
+
* }
|
|
292
|
+
* entity C {
|
|
293
|
+
* x: B:x
|
|
294
|
+
* }
|
|
295
|
+
* ```
|
|
296
|
+
* results in B.x having a ref to [A,x], but C.x only has a string 'A.x' as type.
|
|
297
|
+
* So we have to do yet another round of resolution on this string.
|
|
298
|
+
* We attempt to follow this chain for MAX_TRANSITIVE_RESOLUTION_STEPS tops,
|
|
299
|
+
* but we could finish earlier, when the type is a primitive ("string"), which
|
|
300
|
+
* then jumps to the catch {} to leave e.resolvedType at the last, resolvable type.
|
|
301
|
+
*/
|
|
302
|
+
for (let i = 0; i < MAX_TRANSITIVE_RESOLUTION_STEPS; i++) {
|
|
303
|
+
const { csn } = this.resolver.resolveTypeName(/** @type {string} */(e.resolvedType))
|
|
304
|
+
e.resolvedType = csn.type
|
|
305
|
+
}
|
|
306
|
+
} catch { /* ignore */ }
|
|
279
307
|
}
|
|
280
308
|
file.addInlineEnum(clean, fq, e.name, csnToEnumPairs(e, {unwrapVals: true}), buffer, eDoc)
|
|
281
309
|
}
|
|
@@ -293,7 +321,7 @@ class Visitor {
|
|
|
293
321
|
}
|
|
294
322
|
this.#printStaticKeys(buffer, clean, ancestorInfos, file)
|
|
295
323
|
this.#printStaticElements(buffer, clean)
|
|
296
|
-
this.#printStaticActions(entity, buffer, ancestorInfos, file)
|
|
324
|
+
this.#printStaticActions(entity, clean, buffer, ancestorInfos, file)
|
|
297
325
|
}, '};') // end of generated class
|
|
298
326
|
}, '}') // end of aspect
|
|
299
327
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/cds-typer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.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",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@stylistic/eslint-plugin-js": "^4.2.0",
|
|
49
49
|
"acorn": "^8.10.0",
|
|
50
50
|
"eslint": "^9",
|
|
51
|
-
"eslint-plugin-jsdoc": "^
|
|
51
|
+
"eslint-plugin-jsdoc": "^51.2.1",
|
|
52
52
|
"typescript": ">=4.6.4"
|
|
53
53
|
},
|
|
54
54
|
"cds": {
|