@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 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.6.2 - TBD
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: Object.entries(action.params ?? {}).map(([n, t]) => [
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
- file.addInflection(util.singular4(plural), plural, clean)
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.6.1",
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",