@cap-js/cds-typer 0.6.1 → 0.8.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 +23 -1
- package/lib/components/resolver.js +4 -0
- package/lib/file.js +3 -0
- package/lib/util.js +18 -0
- package/lib/visitor.js +34 -18
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,12 +4,34 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
6
6
|
|
|
7
|
-
## Version 0.
|
|
7
|
+
## Version 0.8.1 - TBD
|
|
8
8
|
|
|
9
9
|
### Changed
|
|
10
10
|
|
|
11
11
|
### Added
|
|
12
12
|
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## Version 0.8.0 - 2023-09-05
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- Foreign keys that are inherited via aspects are now also generated in addition to the resolved property (see 0.7.0)
|
|
24
|
+
- Explicitly annotated `@singular` and `@plural` names are now properly used in generated _index.js_ files
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## Version 0.7.0 - 2023-08-22
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
- Support for `[many] $self` syntax in bound action parameters
|
|
33
|
+
- Foreign keys are now present in the generated types in addition to the resolved property
|
|
34
|
+
|
|
13
35
|
### Fixed
|
|
14
36
|
## Version 0.6.1 - 2023-08-10
|
|
15
37
|
|
|
@@ -445,6 +445,10 @@ class Resolver {
|
|
|
445
445
|
result.path = new Path(path) // FIXME: relative to current file
|
|
446
446
|
result.csn = this.csn.definitions[t]
|
|
447
447
|
result.plainName = this.trimNamespace(t)
|
|
448
|
+
} else if (t === '$self') {
|
|
449
|
+
result.type = 'this'
|
|
450
|
+
result.isBuiltin = true
|
|
451
|
+
result.plainName = 'this'
|
|
448
452
|
} else {
|
|
449
453
|
// type offered by some library
|
|
450
454
|
const lib = this.libraries.find((lib) => lib.offers(t))
|
package/lib/file.js
CHANGED
|
@@ -335,6 +335,9 @@ class SourceFile extends File {
|
|
|
335
335
|
.flatMap(([singular, plural, original]) => Array.from(new Set([
|
|
336
336
|
`module.exports.${singular} = csn.${original}`,
|
|
337
337
|
`module.exports.${plural} = csn.${original}`,
|
|
338
|
+
// FIXME: we currently produce at most 3 entries.
|
|
339
|
+
// This could be an issue when the user re-used the original name in a @singular/@plural annotation.
|
|
340
|
+
// Seems unlikely, but we have to eliminate the original entry if users start running into this.
|
|
338
341
|
`module.exports.${original} = csn.${original}`
|
|
339
342
|
])))
|
|
340
343
|
) // singular -> plural aliases
|
package/lib/util.js
CHANGED
|
@@ -201,6 +201,24 @@ const parseCommandlineArgs = (argv, validFlags) => {
|
|
|
201
201
|
* when removing an annotation which we also check.
|
|
202
202
|
*/
|
|
203
203
|
function fixCSN(csn) {
|
|
204
|
+
for (const element of Object.values(csn.definitions)) {
|
|
205
|
+
Object.defineProperty(element, 'keys', {
|
|
206
|
+
get: function () {
|
|
207
|
+
// cached access to all immediately defined _and_ inherited keys.
|
|
208
|
+
// They need to be explicitly accessible in subclasses to generate
|
|
209
|
+
// foreign key fields from Associations/ Compositions.
|
|
210
|
+
if (!Object.hasOwn(this, '__keys')) {
|
|
211
|
+
const ownKeys = Object.fromEntries(Object.entries(this.elements ?? {}).filter(([,el]) => el.key === true))
|
|
212
|
+
const inheritedKeys = this.includes?.flatMap(parent => csn.definitions[parent].keys) ?? []
|
|
213
|
+
this.__keys = inheritedKeys.reduce((ks, ps) => ({...ps, ...ks}), ownKeys)
|
|
214
|
+
}
|
|
215
|
+
return this.__keys
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// FIXME: delete after merge with draft-enablement PR
|
|
221
|
+
return
|
|
204
222
|
const erase = (entity, parent, attr) => {
|
|
205
223
|
if (attr in entity) {
|
|
206
224
|
const ea = entity[attr]
|
package/lib/visitor.js
CHANGED
|
@@ -130,16 +130,21 @@ class Visitor {
|
|
|
130
130
|
buffer.indent()
|
|
131
131
|
for (const [ename, element] of Object.entries(entity.elements ?? {})) {
|
|
132
132
|
this.visitElement(ename, element, file, buffer)
|
|
133
|
+
// make foreign keys explicit
|
|
134
|
+
if ('target' in element) {
|
|
135
|
+
// lookup in cds.definitions can fail for inline structs.
|
|
136
|
+
// We don't really have to care for this case, as keys from such structs are _not_ propagated to
|
|
137
|
+
// the containing entity.
|
|
138
|
+
for (const [kname, kelement] of Object.entries(this.csn.definitions[element.target]?.keys ?? {})) {
|
|
139
|
+
this.visitElement(`${ename}_${kname}`, kelement, file, buffer)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
133
142
|
}
|
|
134
143
|
for (const [aname, action] of Object.entries(entity.actions ?? {})) {
|
|
135
|
-
const lambdaString =
|
|
136
144
|
buffer.add(
|
|
137
145
|
SourceFile.stringifyLambda({
|
|
138
146
|
name: aname,
|
|
139
|
-
parameters:
|
|
140
|
-
n,
|
|
141
|
-
this.resolver.resolveAndRequire(t, file).typeName,
|
|
142
|
-
]),
|
|
147
|
+
parameters: this.#stringifyFunctionParams(action.params, file),
|
|
143
148
|
returns: action.returns ? this.resolver.resolveAndRequire(action.returns, file).typeName : 'any',
|
|
144
149
|
initialiser: `undefined as unknown as this['${aname}']`
|
|
145
150
|
})
|
|
@@ -208,7 +213,8 @@ class Visitor {
|
|
|
208
213
|
// to have Books.texts = Books.text, so we derive the singular once more without cutting off the ns.
|
|
209
214
|
// Directly deriving it from the plural makes sure we retain any parent namespaces of kind "entity",
|
|
210
215
|
// which would not be possible while already in singular form, as "Book.text" could not be resolved in CSN.
|
|
211
|
-
|
|
216
|
+
// edge case: @singular annotation present. singular4 will take care of that.
|
|
217
|
+
file.addInflection(util.singular4(entity, true), plural, clean)
|
|
212
218
|
if ('doc' in entity) {
|
|
213
219
|
docify(entity.doc).forEach((d) => buffer.add(d))
|
|
214
220
|
}
|
|
@@ -226,17 +232,32 @@ class Visitor {
|
|
|
226
232
|
buffer.add('')
|
|
227
233
|
}
|
|
228
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Stringifies function parameters in preparation of passing them to {@link SourceFile.stringifyLambda}.
|
|
237
|
+
* Resolves all parameters to a pair of parameter name and name of the resolved type.
|
|
238
|
+
* Also filters out parameters that indicate a binding parameter ({@link https://cap.cloud.sap/docs/releases/jan23#simplified-syntax-for-binding-parameters}).
|
|
239
|
+
* @param {[string, object][]} params - parameter list as found in CSN.
|
|
240
|
+
* @param {File} file - source file relative to which the parameter types should be resolved.
|
|
241
|
+
* @returns {[string, string][]} pair of names and types.
|
|
242
|
+
*/
|
|
243
|
+
#stringifyFunctionParams(params, file) {
|
|
244
|
+
return params
|
|
245
|
+
? Object.entries(params)
|
|
246
|
+
// filter params of type '[many] $self', as they are not to be part of the implementation
|
|
247
|
+
.filter(([, type]) => type?.type !== '$self' && !(type.items?.type === '$self'))
|
|
248
|
+
.map(([name, type]) => [
|
|
249
|
+
name,
|
|
250
|
+
this.resolver.resolveAndRequire(type, file).typeName,
|
|
251
|
+
])
|
|
252
|
+
: []
|
|
253
|
+
}
|
|
254
|
+
|
|
229
255
|
#printFunction(name, func) {
|
|
230
256
|
// FIXME: mostly duplicate of printAction -> reuse
|
|
231
257
|
this.logger.debug(`Printing function ${name}:\n${JSON.stringify(func, null, 2)}`)
|
|
232
258
|
const ns = this.resolver.resolveNamespace(name.split('.'))
|
|
233
259
|
const file = this.getNamespaceFile(ns)
|
|
234
|
-
const params = func.params
|
|
235
|
-
? Object.entries(func.params).map(([pname, ptype]) => [
|
|
236
|
-
pname,
|
|
237
|
-
this.resolver.resolveAndRequire(ptype, file).typeName,
|
|
238
|
-
])
|
|
239
|
-
: []
|
|
260
|
+
const params = this.#stringifyFunctionParams(func.params, file)
|
|
240
261
|
const returns = this.resolver.resolveAndRequire(func.returns, file).typeName
|
|
241
262
|
file.addFunction(name.split('.').at(-1), params, returns)
|
|
242
263
|
}
|
|
@@ -245,12 +266,7 @@ class Visitor {
|
|
|
245
266
|
this.logger.debug(`Printing action ${name}:\n${JSON.stringify(action, null, 2)}`)
|
|
246
267
|
const ns = this.resolver.resolveNamespace(name.split('.'))
|
|
247
268
|
const file = this.getNamespaceFile(ns)
|
|
248
|
-
const params = action.params
|
|
249
|
-
? Object.entries(action.params).map(([pname, ptype]) => [
|
|
250
|
-
pname,
|
|
251
|
-
this.resolver.resolveAndRequire(ptype, file).typeName,
|
|
252
|
-
])
|
|
253
|
-
: []
|
|
269
|
+
const params = this.#stringifyFunctionParams(action.params, file)
|
|
254
270
|
const returns = this.resolver.resolveAndRequire(action.returns, file).typeName
|
|
255
271
|
file.addAction(name.split('.').at(-1), params, returns)
|
|
256
272
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/cds-typer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"@sap/cds": ">=6"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
+
"acorn": "^8.10.0",
|
|
46
47
|
"eslint": "^8.15.0",
|
|
47
48
|
"eslint-config-prettier": "^8.5.0",
|
|
48
49
|
"eslint-plugin-prettier": "^4.0.0",
|