@cap-js/cds-typer 0.21.1 → 0.22.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,7 +4,15 @@ 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.22.0 - TBD
7
+ ## Version 0.23.0 - TBD
8
+
9
+ ## Version 0.22.0 - 2024-06-20
10
+ ### Fixed
11
+ - Fixed a bug where keys would sometimes inconsistently become nullable
12
+
13
+ ## Version 0.21.2 - 2024-06-06
14
+ ### Fixed
15
+ - The typescript build task will no longer attempt to run unless at least cds 8 is installed
8
16
 
9
17
  ## Version 0.21.1 - 2024-06-03
10
18
  ### Fixed
package/cds-plugin.js CHANGED
@@ -21,7 +21,7 @@ const tsConfigExists = () => fs.existsSync('tsconfig.json')
21
21
  const buildConfigExists = () => fs.existsSync(BUILD_CONFIG)
22
22
 
23
23
  /**
24
- * @param {string} dir The directory to remove.
24
+ * @param {string} dir - The directory to remove.
25
25
  */
26
26
  const rmDirIfExists = dir => {
27
27
  try { fs.rmSync(dir, { recursive: true }) } catch { /* ignore */ }
@@ -29,8 +29,8 @@ const rmDirIfExists = dir => {
29
29
 
30
30
  /**
31
31
  * Remove files with given extensions from a directory recursively.
32
- * @param {string} dir The directory to start from.
33
- * @param {string[]} exts The extensions to remove.
32
+ * @param {string} dir - The directory to start from.
33
+ * @param {string[]} exts - The extensions to remove.
34
34
  * @returns {Promise<void>}
35
35
  */
36
36
  const rmFiles = async (dir, exts) => fs.existsSync(dir)
@@ -47,6 +47,12 @@ const rmFiles = async (dir, exts) => fs.existsSync(dir)
47
47
  )
48
48
  : undefined
49
49
 
50
+ // FIXME: remove once cds7 has been phased out
51
+ if (!cds?.version || cds.version < '8.0.0') {
52
+ DEBUG?.('typescript build task requires @sap/cds-dk version >= 8.0.0, skipping registration')
53
+ return
54
+ }
55
+
50
56
  // requires @sap/cds-dk version >= 7.5.0
51
57
  cds.build?.register?.('typescript', class extends cds.build.Plugin {
52
58
  static taskDefaults = { src: '.' }
package/lib/compile.js CHANGED
@@ -15,8 +15,8 @@ const { Visitor } = require('./visitor')
15
15
  /**
16
16
  * Writes the accompanying jsconfig.json file to the specified paths.
17
17
  * Tries to merge nicely if an existing file is found.
18
- * @param path {string} filepath to jsconfig.json.
19
- * @param logger {import('./logging').Logger} logger
18
+ * @param {string} path - filepath to jsconfig.json.
19
+ * @param {import('./logging').Logger} logger - logger
20
20
  * @private
21
21
  */
22
22
  const writeJsConfig = (path, logger) => {
@@ -41,7 +41,7 @@ const writeJsConfig = (path, logger) => {
41
41
  /**
42
42
  * Compiles a CSN object to Typescript types.
43
43
  * @param {{xtended: CSN, inferred: CSN}} csn
44
- * @param parameters {CompileParameters} path to root directory for all generated files, min log level
44
+ * @param {CompileParameters} parameters - path to root directory for all generated files, min log level
45
45
  */
46
46
  const compileFromCSN = async (csn, parameters) => {
47
47
  const envSettings = cds.env?.typer ?? {}
@@ -59,8 +59,8 @@ const compileFromCSN = async (csn, parameters) => {
59
59
 
60
60
  /**
61
61
  * Compiles a .cds file to Typescript types.
62
- * @param inputFile {string} path to input .cds file
63
- * @param parameters {CompileParameters} path to root directory for all generated files, min log level, etc.
62
+ * @param {string} inputFile - path to input .cds file
63
+ * @param {CompileParameters} parameters - path to root directory for all generated files, min log level, etc.
64
64
  */
65
65
  const compileFromFile = async (inputFile, parameters) => {
66
66
  const paths = typeof inputFile === 'string' ? normalize(inputFile) : inputFile.map(f => normalize(f))
@@ -9,7 +9,7 @@ const uniqueValues = kvs => new Set(kvs.map(([,v]) => v?.val ?? v)) // in case
9
9
 
10
10
  /**
11
11
  * Stringifies a list of enum key-value pairs into the righthand side of a TS type.
12
- * @param {[string, string][]} kvs list of key-value pairs
12
+ * @param {[string, string][]} kvs - list of key-value pairs
13
13
  * @returns {string} a stringified type
14
14
  * @example
15
15
  * ```js
@@ -29,7 +29,6 @@ const enumVal = (key, value, enumType) => enumType === 'cds.String' ? JSON.strin
29
29
  * and a type containing all disctinct values.
30
30
  * We can get away with this as TS doesn't feature nominal typing, so the structure
31
31
  * is all we care about.
32
- *
33
32
  * @example
34
33
  * ```cds
35
34
  * type E: enum of String {
@@ -42,10 +41,10 @@ const enumVal = (key, value, enumType) => enumType === 'cds.String' ? JSON.strin
42
41
  * const E = { a: 'A', b: 'B' }
43
42
  * type E = 'A' | 'B'
44
43
  * ```
45
- *
46
- * @param {Buffer} buffer Buffer to write into
47
- * @param {string} name local name of the enum, i.e. the name under which it should be created in the .ts file
48
- * @param {[string, string][]} kvs list of key-value pairs
44
+ * @param {Buffer} buffer - Buffer to write into
45
+ * @param {string} name - local name of the enum, i.e. the name under which it should be created in the .ts file
46
+ * @param {[string, string][]} kvs - list of key-value pairs
47
+ * @param {object} options
49
48
  */
50
49
  function printEnum(buffer, name, kvs, options = {}) {
51
50
  const opts = {...{export: true}, ...options}
@@ -61,14 +60,12 @@ function printEnum(buffer, name, kvs, options = {}) {
61
60
  * Converts a CSN type describing an enum into a list of kv-pairs.
62
61
  * Values from CSN are unwrapped from their `.val` structure and
63
62
  * will fall back to the key if no value is provided.
64
- *
65
63
  * @param {{enum: {[key: name]: string}, type: string}} enumCsn
66
- * @param {{unwrapVals: boolean}} options if `unwrapVals` is passed,
64
+ * @param {{unwrapVals: boolean}} options - if `unwrapVals` is passed,
67
65
  * then the CSN structure `{val:x}` is flattened to just `x`.
68
66
  * Retaining `val` is closer to the actual CSN structure and should be used where we want
69
67
  * to mimic the runtime as closely as possible (inline enum types).
70
68
  * Stripping that additional wrapper would be more readable for users.
71
- *
72
69
  * @example
73
70
  * ```ts
74
71
  * const csn = {enum: {X: {val: 'a'}, Y: {val: 'b'}, Z: {}}}
@@ -94,7 +91,6 @@ const propertyToInlineEnumName = (entity, property) => `${entity}_${property}`
94
91
  * A type is considered to be an inline enum, iff it has a `.enum` property
95
92
  * _and_ its type is a CDS primitive, i.e. it is not contained in `cds.definitions`.
96
93
  * If it is contained there, then it is a standard enum declaration that has its own name.
97
- *
98
94
  * @param {{type: string}} element
99
95
  * @param {object} csn
100
96
  * @returns boolean
@@ -116,7 +112,7 @@ const isInlineEnumType = (element, csn) => element.enum && !(element.type in csn
116
112
  * module.exports.Language = { DE: "German", EN: "English", FR: "FR" }
117
113
  * ```
118
114
  * @param {string} name
119
- * @param {[string, string][]} kvs a list of key-value pairs. Values that are falsey are replaced by
115
+ * @param {[string, string][]} kvs - a list of key-value pairs. Values that are falsey are replaced by
120
116
  */
121
117
  // ??= for inline enums. If there is some static property of that name, we don't want to override it (for example: ".actions"
122
118
  const stringifyEnumImplementation = (name, kvs) => `module.exports.${name} ??= { ${kvs.map(([k,v]) => `${normalise(k)}: ${v}`).join(', ')} }`
@@ -3,7 +3,7 @@ const isValidIdent = /^[_$a-zA-Z][$\w]*$/
3
3
  /**
4
4
  * Normalises an identifier to a valid JavaScript identifier.
5
5
  * I.e. either the identifier itself or a quoted string.
6
- * @param {string} ident the identifier to normalise
6
+ * @param {string} ident - the identifier to normalise
7
7
  * @returns {string} the normalised identifier
8
8
  */
9
9
  const normalise = ident => ident && !isValidIdent.test(ident)
@@ -23,8 +23,8 @@ class InlineDeclarationResolver {
23
23
  /**
24
24
  * Attempts to resolve a type that could reference another type.
25
25
  * @param {any} items
26
- * @param {import('./resolver').TypeResolveInfo} into @see Visitor.resolveType
27
- * @param {SourceFile} file temporary file to resolve dummy types into.
26
+ * @param {import('./resolver').TypeResolveInfo} into - @see Visitor.resolveType
27
+ * @param {SourceFile} relativeTo - file to which the resolved type should be relative to
28
28
  * @public
29
29
  */
30
30
  resolveInlineDeclaration(items, into, relativeTo) {
@@ -56,10 +56,10 @@ class InlineDeclarationResolver {
56
56
 
57
57
  /**
58
58
  * Visits a single element in an entity.
59
- * @param {string} name name of the element
60
- * @param {import('./resolver').CSN} element CSN data belonging to the the element.
61
- * @param {SourceFile} file the namespace file the surrounding entity is being printed into.
62
- * @param {Buffer} [buffer] buffer to add the definition to. If no buffer is passed, the passed file's class buffer is used instead.
59
+ * @param {string} name - name of the element
60
+ * @param {import('./resolver').CSN} element - CSN data belonging to the the element.
61
+ * @param {SourceFile} file - the namespace file the surrounding entity is being printed into.
62
+ * @param {Buffer} [buffer] - buffer to add the definition to. If no buffer is passed, the passed file's class buffer is used instead.
63
63
  * @public
64
64
  */
65
65
  visitElement(name, element, file, buffer = file.classes) {
@@ -71,7 +71,7 @@ class InlineDeclarationResolver {
71
71
  this.depth--
72
72
  if (this.depth === 0) {
73
73
  this.printInlineType(name, type, buffer)
74
- }
74
+ }
75
75
  return type
76
76
  }
77
77
 
@@ -88,8 +88,8 @@ class InlineDeclarationResolver {
88
88
  /**
89
89
  * It returns TypeScript datatype for provided TS property
90
90
  * @param {{typeName: string, typeInfo: TypeResolveInfo & { inflection: Inflection } }} type
91
- * @param {string} typeName name of the TypeScript property
92
- * @return {string} the datatype to be presented on TypeScript layer
91
+ * @param {string} typeName - name of the TypeScript property
92
+ * @returns {string} the datatype to be presented on TypeScript layer
93
93
  * @public
94
94
  */
95
95
  getPropertyDatatype(type, typeName = type.typeName) {
@@ -118,7 +118,7 @@ class InlineDeclarationResolver {
118
118
  * T['a']['b'] // number
119
119
  * ```
120
120
  * but especially with inline declarations, the access will differ between flattened and nested representations.
121
- * @param {string[]} members a list of members, in the above example it would be `['a', 'b']`
121
+ * @param {string[]} members - a list of members, in the above example it would be `['a', 'b']`
122
122
  * @returns {string} type access string snippet. In the above sample, we would return `"['a']['b']"`
123
123
  * @public
124
124
  * @abstract
@@ -2,17 +2,16 @@
2
2
  * Check if an element references another type.
3
3
  * This happens for foreign key relationships
4
4
  * and for the typeof syntax.
5
- *
5
+ *
6
6
  * ```cds
7
7
  * entity E {
8
- * x: Integer
8
+ * x: Integer
9
9
  * }
10
- *
10
+ *
11
11
  * entity F {
12
- * y: E.x // <- ref
12
+ * y: E.x // <- ref
13
13
  * }
14
14
  * ```
15
- *
16
15
  * @param {{type: any}} element
17
16
  * @returns boolean
18
17
  */
@@ -10,34 +10,9 @@ const { isReferenceType } = require('./reference')
10
10
  const { isEntity } = require('../csn')
11
11
  const { baseDefinitions } = require('./basedefs')
12
12
 
13
- /** @typedef {{ cardinality?: { max?: '*' | number } }} EntityCSN */
14
- /** @typedef {{ definitions?: Object<string, EntityCSN> }} CSN */
15
13
  /** @typedef {import('../visitor').Visitor} Visitor */
16
-
17
- /**
18
- * When nested inline types require additional imports. E.g.:
19
- * ```cds
20
- * // mymodel.cds
21
- * Foo {
22
- * bar: {
23
- * baz: a.b.c.Baz // need to require a.b.c in mymodel.cds!
24
- * }
25
- * }
26
- * ```
27
- * @typedef {{
28
- * isBuiltin: boolean,
29
- * isDeepRequire: boolean,
30
- * isNotNull: boolean,
31
- * isInlineDeclaration: boolean,
32
- * isForeignKeyReference: boolean,
33
- * isArray: boolean,
34
- * type: string,
35
- * path?: Path,
36
- * csn?: CSN,
37
- * imports: Path[]
38
- * inner: TypeResolveInfo
39
- * }} TypeResolveInfo
40
- */
14
+ /** @typedef {import('../typedefs').resolver.CSN} CSN */
15
+ /** @typedef {import('../typedefs').resolver.TypeResolveInfo} TypeResolveInfo */
41
16
 
42
17
  class BuiltinResolver {
43
18
  /**
@@ -82,7 +57,7 @@ class BuiltinResolver {
82
57
  }
83
58
 
84
59
  /**
85
- * @param {string | string[]} type name or parts of the type name split on dots
60
+ * @param {string | string[]} t - name or parts of the type name split on dots
86
61
  * @returns {string | undefined | false} if t refers to a builtin, the name of the corresponding TS type is returned.
87
62
  * If t _looks like_ a builtin (`cds.X`), undefined is returned.
88
63
  * If t is obviously not a builtin, false is returned.
@@ -172,7 +147,7 @@ class Resolver {
172
147
  /**
173
148
  * TODO: this should probably be a class where we can also cache the properties
174
149
  * and only retrieve them on demand
175
- * @typedef {Object} Untangled
150
+ * @typedef {object} Untangled
176
151
  * @property {string[]} scope in case the entity is wrapped in another entity `a.b.C.D.E.f.g` -> `[C,D]`
177
152
  * @property {string} name name of the leaf entity `a.b.C.D.E.f.g` -> `E`
178
153
  * @property {string[]} property the property access path `a.b.C.D.E.f.g` -> `[f,g]`
@@ -183,7 +158,7 @@ class Resolver {
183
158
  * Conveniently combines resolveNamespace and trimNamespace
184
159
  * to end up with both the resolved Path of the namespace,
185
160
  * and the clean name of the class.
186
- * @param {string} fq the fully qualified name of an entity.
161
+ * @param {string} fq - the fully qualified name of an entity.
187
162
  * @returns {Untangled} untangled qualifier
188
163
  */
189
164
  untangle(fq) {
@@ -212,7 +187,7 @@ class Resolver {
212
187
  * a.b.c.Foo -> Foo
213
188
  * Bar -> Bar
214
189
  * sap.cap.Book.text -> Book.text (assuming Book and text are both of kind "entity")
215
- * @param {string} p path
190
+ * @param {string} p - path
216
191
  * @returns {string} the entity name without leading namespace.
217
192
  */
218
193
  trimNamespace(p) {
@@ -236,7 +211,7 @@ class Resolver {
236
211
  * From a fully qualified path, finds the parts that are property accesses.
237
212
  * This are specifically used in CDS' `typeof` syntax, where a property can
238
213
  * refer to another entity's property type.
239
- * @param {string} p path
214
+ * @param {string} p - path
240
215
  * @example
241
216
  * ```
242
217
  * namespace namespace;
@@ -249,7 +224,6 @@ class Resolver {
249
224
  * x: namespace.Entity.x.y.z;
250
225
  * }
251
226
  * ```
252
- *
253
227
  * @example
254
228
  * ```js
255
229
  * findPropertyAccess('namespace') // []
@@ -292,8 +266,8 @@ class Resolver {
292
266
  * - type definitions, which are not inflected
293
267
  * - inline type definitions, which don't really have a linguistic plural,
294
268
  * but need to expressed as array type to be consumable by the likes of Composition.of.many<T>
295
- * @param {import('./resolver').TypeResolveInfo} typeInfo information about the type gathered so far.
296
- * @param {string} [namespace] namespace the type occurs in. If passed, will be shaved off from the name
269
+ * @param {import('./resolver').TypeResolveInfo} typeInfo - information about the type gathered so far.
270
+ * @param {string} [namespace] - namespace the type occurs in. If passed, will be shaved off from the name
297
271
  * @returns {Inflection}
298
272
  */
299
273
  inflect(typeInfo, namespace) {
@@ -364,9 +338,8 @@ class Resolver {
364
338
  * 1. add an import of model1 to model2 with proper path resolution and alias, e.g. "import * as m1 from './model1'"
365
339
  * 2. resolve any singular/ plural issues and association/ composition around it
366
340
  * 3. return a properly prefixed name to use within model2.d.ts, e.g. "m1.Foo"
367
- *
368
- * @param {CSN} element the CSN element to resolve the type for.
369
- * @param {SourceFile} file source file for context.
341
+ * @param {CSN} element - the CSN element to resolve the type for.
342
+ * @param {SourceFile} file - source file for context.
370
343
  * @returns {{typeName: string, typeInfo: TypeResolveInfo & { inflection: Inflection } }} info about the resolved type
371
344
  */
372
345
  resolveAndRequire(element, file) {
@@ -476,7 +449,7 @@ class Resolver {
476
449
 
477
450
  /**
478
451
  * Attempts to retrieve the max cardinality of a CSN for an entity.
479
- * @param {EntityCSN} element csn of entity to retrieve cardinality for
452
+ * @param {EntityCSN} element - csn of entity to retrieve cardinality for
480
453
  * @returns {number} max cardinality of the element.
481
454
  * If no cardinality is attached to the element, cardinality is 1.
482
455
  * If it is set to '*', result is Infinity.
@@ -489,7 +462,7 @@ class Resolver {
489
462
  /**
490
463
  * Resolves the fully qualified name of an entity to its parent entity.
491
464
  * resolveParent(a.b.c.D) -> CSN {a.b.c}
492
- * @param {string} name fully qualified name of the entity to resolve the parent of.
465
+ * @param {string} name - fully qualified name of the entity to resolve the parent of.
493
466
  * @returns {CSN} the resolved parent CSN.
494
467
  */
495
468
  resolveParent(name) {
@@ -502,7 +475,7 @@ class Resolver {
502
475
  * read from left to right which does not contain a kind 'context' or 'service'.
503
476
  * That is, if in the above example 'D' is a context and 'E' is a service,
504
477
  * the resulting namespace is 'a.b.c'.
505
- * @param {string[] | string} pathParts the distinct parts of the namespace, i.e. ['a','b','c','D','E'] or a single path interspersed with periods
478
+ * @param {string[] | string} pathParts - the distinct parts of the namespace, i.e. ['a','b','c','D','E'] or a single path interspersed with periods
506
479
  * @returns {string} the namespace's name, i.e. 'a.b.c'.
507
480
  */
508
481
  resolveNamespace(pathParts) {
@@ -528,8 +501,8 @@ class Resolver {
528
501
  /**
529
502
  * Resolves an element's type to either a builtin or a user defined type.
530
503
  * Enriched with additional information for improved printout (see return type).
531
- * @param {CSN} element the CSN element to resolve the type for.
532
- * @param {SourceFile} file source file for context.
504
+ * @param {CSN} element - the CSN element to resolve the type for.
505
+ * @param {SourceFile} file - source file for context.
533
506
  * @returns {TypeResolveInfo} description of the resolved type
534
507
  */
535
508
  resolveType(element, file) {
@@ -611,15 +584,14 @@ class Resolver {
611
584
  * We can encounter declarations like:
612
585
  *
613
586
  * record : array of {
614
- * column : String;
615
- * data : String;
587
+ * column : String;
588
+ * data : String;
616
589
  * }
617
590
  *
618
591
  * These have to be resolved to a new type.
619
- *
620
- * @param {any[]} items the properties of the inline declaration.
621
- * @param {TypeResolveInfo} into @see resolveType()
622
- * @param {SourceFile} relativeTo the sourcefile in which we have found the reference to the type.
592
+ * @param {any[]} items - the properties of the inline declaration.
593
+ * @param {TypeResolveInfo} into - @see resolveType()
594
+ * @param {SourceFile} relativeTo - the sourcefile in which we have found the reference to the type.
623
595
  * This is important to correctly detect when a field in the inline declaration is referencing
624
596
  * types from the CWD. In that case, we will not add an import for that type and not add a namespace-prefix.
625
597
  */
@@ -630,8 +602,8 @@ class Resolver {
630
602
  /**
631
603
  * Attempts to resolve a type that could reference another type.
632
604
  * @param {?} val
633
- * @param {TypeResolveInfo} into see resolveType()
634
- * @param {SourceFile} file only needed as we may call #resolveInlineDeclarationType from here. Will be expelled at some point.
605
+ * @param {TypeResolveInfo} into - see resolveType()
606
+ * @param {SourceFile} file - only needed as we may call #resolveInlineDeclarationType from here. Will be expelled at some point.
635
607
  */
636
608
  resolvePotentialReferenceType(val, into, file) {
637
609
  // FIXME: get rid of file parameter! it is only used to pass to #resolveInlineDeclarationType
@@ -650,8 +622,8 @@ class Resolver {
650
622
  * Attempts to resolve a string to a type.
651
623
  * String is supposed to refer to either a builtin type
652
624
  * or any type defined in CSN.
653
- * @param {string} t fully qualified type, like cds.String, or a.b.c.d.Foo
654
- * @param {TypeResolveInfo} into optional dictionary to fill by reference, see resolveType()
625
+ * @param {string} t - fully qualified type, like cds.String, or a.b.c.d.Foo
626
+ * @param {TypeResolveInfo} into - optional dictionary to fill by reference, see resolveType()
655
627
  * @returns @see resolveType
656
628
  */
657
629
  #resolveTypeName(t, into) {
@@ -5,57 +5,57 @@ const base = '__'
5
5
 
6
6
  /**
7
7
  * Wraps type into association to scalar.
8
- * @param {string} t the singular type name.
8
+ * @param {string} t - the singular type name.
9
9
  * @returns {string}
10
10
  */
11
11
  const createToOneAssociation = t => `${base}.Association.to<${t}>`
12
12
 
13
13
  /**
14
14
  * Wraps type into association to vector.
15
- * @param {string} t the singular type name.
15
+ * @param {string} t - the singular type name.
16
16
  * @returns {string}
17
17
  */
18
18
  const createToManyAssociation = t => `${base}.Association.to.many<${t}>`
19
19
 
20
20
  /**
21
21
  * Wraps type into composition of scalar.
22
- * @param {string} t the singular type name.
22
+ * @param {string} t - the singular type name.
23
23
  * @returns {string}
24
24
  */
25
25
  const createCompositionOfOne = t => `${base}.Composition.of<${t}>`
26
26
 
27
27
  /**
28
28
  * Wraps type into composition of vector.
29
- * @param {string} t the singular type name.
29
+ * @param {string} t - the singular type name.
30
30
  * @returns {string}
31
31
  */
32
32
  const createCompositionOfMany = t => `${base}.Composition.of.many<${t}>`
33
33
 
34
34
  /**
35
35
  * Wraps type into an array.
36
- * @param {string} t the singular type name.
36
+ * @param {string} t - the singular type name.
37
37
  * @returns {string}
38
38
  */
39
39
  const createArrayOf = t => `Array<${t}>`
40
40
 
41
41
  /**
42
42
  * Wraps type into object braces
43
- * @param {string} t the properties, stringified and comma separated.
43
+ * @param {string} t - the properties, stringified and comma separated.
44
44
  * @returns {string}
45
45
  */
46
46
  const createObjectOf = t => `{${t}}`
47
47
 
48
48
  /**
49
49
  * Wraps type into a deep require (removes all posibilities of undefined recursively).
50
- * @param {string} t the singular type name.
51
- * @param {string?} lookup a property lookup of the required type (`['Foo']`)
50
+ * @param {string} t - the singular type name.
51
+ * @param {string?} lookup - a property lookup of the required type (`['Foo']`)
52
52
  * @returns {string}
53
53
  */
54
54
  const deepRequire = (t, lookup = '') => `${base}.DeepRequired<${t}>${lookup}`
55
55
 
56
56
  /**
57
57
  * Puts a passed string in docstring format.
58
- * @param {string} doc raw string to docify. May contain linebreaks.
58
+ * @param {string} doc - raw string to docify. May contain linebreaks.
59
59
  * @returns {string[]} an array of lines wrapped in doc format. The result is not
60
60
  * concatenated to be properly indented by `buffer.add(...)`.
61
61
  */
package/lib/csn.js CHANGED
@@ -5,6 +5,7 @@ const annotation = '@odata.draft.enabled'
5
5
  * i.e. ones that have a query, but are not a cds level projection.
6
6
  * Those are still not expanded and we have to retrieve their definition
7
7
  * with all properties from the inferred model.
8
+ * @param {any} entity
8
9
  */
9
10
  const isView = entity => entity.query && !entity.projection
10
11
 
@@ -21,6 +22,7 @@ const isType = entity => entity?.kind === 'type'
21
22
  const isEntity = entity => entity?.kind === 'entity'
22
23
 
23
24
  /**
25
+ * @param {any} entity
24
26
  * @see isView
25
27
  * Unresolved entities have to be looked up from inferred csn.
26
28
  */
@@ -51,8 +53,8 @@ class DraftUnroller {
51
53
  get csn() { return this.#csn }
52
54
 
53
55
  /**
54
- * @param entity {object | string} - entity to set draftable annotation for.
55
- * @param value {boolean} - whether the entity is draftable.
56
+ * @param {object | string} entity - entity to set draftable annotation for.
57
+ * @param {boolean} value - whether the entity is draftable.
56
58
  */
57
59
  #setDraftable(entity, value) {
58
60
  if (typeof entity === 'string') entity = this.#getDefinition(entity)
@@ -67,7 +69,7 @@ class DraftUnroller {
67
69
  }
68
70
 
69
71
  /**
70
- * @param entity {object | string} - entity to look draftability up for.
72
+ * @param {object | string} entityOrName - entity to look draftability up for.
71
73
  * @returns {boolean}
72
74
  */
73
75
  #getDraftable(entityOrName) {
@@ -80,7 +82,7 @@ class DraftUnroller {
80
82
  }
81
83
 
82
84
  /**
83
- * @param name {string} - name of the entity.
85
+ * @param {string} name - name of the entity.
84
86
  */
85
87
  #getDefinition(name) { return this.csn.definitions[name] }
86
88
 
@@ -88,8 +90,7 @@ class DraftUnroller {
88
90
  * Propagate draft annotations through inheritance (includes).
89
91
  * The latest annotation through the inheritance chain "wins".
90
92
  * Annotations on the entity itself are always queued last, so they will always be decisive over ancestors.
91
- *
92
- * @param entity {object} - entity to pull draftability from its parents.
93
+ * @param {object} entity - entity to pull draftability from its parents.
93
94
  */
94
95
  #propagateInheritance(entity) {
95
96
  const annotations = (entity?.includes ?? []).map(parent => this.#getDraftable(parent))
@@ -118,8 +119,7 @@ class DraftUnroller {
118
119
  /**
119
120
  * If an entity E is draftable and contains any composition of entities,
120
121
  * then those entities also become draftable. Recursively.
121
- *
122
- * @param entity {object} - entity to propagate all compositions from.
122
+ * @param {object} entity - entity to propagate all compositions from.
123
123
  */
124
124
  #propagateCompositions(entity) {
125
125
  if (!this.#getDraftable(entity)) return
@@ -160,6 +160,7 @@ class DraftUnroller {
160
160
  * (a) aspects via `A: B`, where `B` is draft enabled.
161
161
  * Note that when an entity extends two other entities of which one has drafts enabled and
162
162
  * one has not, then the one that is later in the list of mixins "wins":
163
+ * @param {any} csn
163
164
  * @example
164
165
  * ```ts
165
166
  * @odata.draft.enabled true
@@ -194,13 +195,13 @@ function unrollDraftability(csn) {
194
195
 
195
196
  /**
196
197
  * Propagates keys elements through the CSN. This includes
197
- *
198
+ *
198
199
  * (a) keys that are explicitly declared as key in an entity
199
200
  * (b) keys from aspects the entity extends
200
- *
201
+ *
201
202
  * This explicit propagation is required to add foreign key relations
202
203
  * to referring entities.
203
- *
204
+ * @param {any} csn
204
205
  * @example
205
206
  * ```cds
206
207
  * entity A: cuid { key name: String; }
@@ -218,7 +219,6 @@ function unrollDraftability(csn) {
218
219
  * ref_name: String;
219
220
  * }
220
221
  * ```
221
- * @returns {{[key: string]: object}}
222
222
  */
223
223
  function propagateForeignKeys(csn) {
224
224
  for (const element of Object.values(csn.definitions)) {
@@ -249,6 +249,10 @@ function propagateForeignKeys(csn) {
249
249
  }
250
250
  }
251
251
 
252
+ /**
253
+ *
254
+ * @param {any} csn
255
+ */
252
256
  function amendCSN(csn) {
253
257
  unrollDraftability(csn)
254
258
  propagateForeignKeys(csn)
package/lib/file.js CHANGED
@@ -10,7 +10,7 @@ const { createObjectOf } = require('./components/wrappers')
10
10
 
11
11
  const AUTO_GEN_NOTE = '// This is an automatically generated file. Please do not change its contents manually!'
12
12
 
13
- /** @typedef {Object<string, Buffer>} Namespace */
13
+ /** @typedef {import('./typedefs').file.Namespace} Namespace */
14
14
 
15
15
  class File {
16
16
  /**
@@ -79,7 +79,7 @@ class Library extends File {
79
79
 
80
80
  /**
81
81
  * Whether this library offers an entity of a given type (fully qualified).
82
- * @param {string} entity the entity's name, e.g. cap.hana.TINYINT
82
+ * @param {string} entity - the entity's name, e.g. cap.hana.TINYINT
83
83
  * @returns {boolean} true, iff the namespace inferred from the passed string matches that of this library
84
84
  * and this library contains a class of that name. i.e.:
85
85
  * ```js
@@ -102,7 +102,7 @@ class SourceFile extends File {
102
102
  super()
103
103
  /** @type {Path} */
104
104
  this.path = path instanceof Path ? path : new Path(path.split('.'))
105
- /** @type {Object} */
105
+ /** @type {object} */
106
106
  this.imports = {}
107
107
  /** @type {Buffer} */
108
108
  this.preamble = new Buffer()
@@ -122,9 +122,9 @@ class SourceFile extends File {
122
122
  this.aspects = new Buffer()
123
123
  /** @type {Namespace} */
124
124
  this.namespaces = {}
125
- /** @type {Object<string, any>} */
125
+ /** @type {{[key: string]: any}} */
126
126
  this.classNames = {} // for .js file
127
- /** @type {Object<string, any>} */
127
+ /** @type {{[key: string]: any}} */
128
128
  this.typeNames = {}
129
129
  /** @type {[string, string, string][]} */
130
130
  this.inflections = []
@@ -134,7 +134,7 @@ class SourceFile extends File {
134
134
 
135
135
  /**
136
136
  * Stringifies a lambda expression.
137
- * @param {{name: string, parameters: [string, string][], returns: string, initialiser: string}} - name, parameters, return type, and initialiser expression
137
+ * @param {{name: string, parameters: [string, string][], returns: string, initialiser: string}} param - name, parameters, return type, and initialiser expression
138
138
  * @returns {string} - the stringified lambda
139
139
  * @example
140
140
  * ```js
@@ -150,7 +150,6 @@ class SourceFile extends File {
150
150
  * The reason for this is that the CDS runtime actually treats the function parameters as a named object. This can not be rectified via
151
151
  * type magic, as parameter names do not exist on type level. So we can not use these names to reuse them as object properties.
152
152
  * Instead, we generate this utility object for the runtime to use:
153
- *
154
153
  * @example
155
154
  * ```js
156
155
  * stringifyLambda({name: 'f', parameters: [['p','T']], returns: 'number'}) // { (p: T): number, __parameters: { p: T } }
@@ -176,9 +175,9 @@ class SourceFile extends File {
176
175
  * Adds a pair of singular and plural inflection.
177
176
  * These are later used to generate the singular -> plural
178
177
  * aliases in the index.js file.
179
- * @param {string} singular singular type without namespace.
180
- * @param {string} plural plural type without namespace
181
- * @param {string} original original entity name without namespace.
178
+ * @param {string} singular - singular type without namespace.
179
+ * @param {string} plural - plural type without namespace
180
+ * @param {string} original - original entity name without namespace.
182
181
  * In many cases this will be the same as plural.
183
182
  */
184
183
  addInflection(singular, plural, original) {
@@ -187,10 +186,10 @@ class SourceFile extends File {
187
186
 
188
187
  /**
189
188
  * Adds a function definition in form of a arrow function to the file.
190
- * @param {string} name name of the function
191
- * @param {{relative: string | undefined, local: boolean, posix: boolean}} parameters list of parameters, passed as [name, type] pairs
189
+ * @param {string} name - name of the function
190
+ * @param {{relative: string | undefined, local: boolean, posix: boolean}} parameters - list of parameters, passed as [name, type] pairs
191
+ * @param {string} returns - the return type of the function
192
192
  * @param {'function' | 'action'} kind
193
- * @param returns the return type of the function
194
193
  */
195
194
  addOperation(name, parameters, returns, kind) {
196
195
  //this.operations.buffer.add("// operation")
@@ -201,7 +200,7 @@ class SourceFile extends File {
201
200
  /**
202
201
  * Retrieves or creates and retrieves a sub namespace
203
202
  * with a given name.
204
- * @param {string} name of the sub namespace.
203
+ * @param {string} name - of the sub namespace.
205
204
  * @returns {Namespace} the sub namespace.
206
205
  */
207
206
  getSubNamespace(name) {
@@ -221,28 +220,26 @@ class SourceFile extends File {
221
220
 
222
221
  /**
223
222
  * Adds an enum to this file.
224
- * @param {string} fq fully qualified name of the enum (entity name within CSN)
225
- * @param {string} name local name of the enum
226
- * @param {[string, string][]} kvs list of key-value pairs
227
- * @param {string} [property] property to which the enum is attached.
223
+ * @param {string} fq - fully qualified name of the enum (entity name within CSN)
224
+ * @param {string} name - local name of the enum
225
+ * @param {[string, string][]} kvs - list of key-value pairs
226
+ * @param {string?} property - property to which the enum is attached.
228
227
  * If given, the enum is considered to be an inline definition of an enum.
229
228
  * If not, it is considered to be regular, named enum.
230
229
  */
231
- addEnum(fq, name, kvs) {
230
+ addEnum(fq, name, kvs, property) {
232
231
  this.enums.data.push({ name, fq, kvs })
233
232
  printEnum(this.enums.buffer, name, kvs)
234
233
  }
235
234
 
236
235
  /**
237
236
  * Adds an inline enum to this file.
238
- * @param {string} entityCleanName name of the entity the enum is attached to without namespace
239
- * @param {string} entityFqName name of the entity the enum is attached to with namespace
240
- *
241
- * @param {string} propertyName property to which the enum is attached.
242
- * @param {[string, string][]} kvs list of key-value pairs
237
+ * @param {string} entityCleanName - name of the entity the enum is attached to without namespace
238
+ * @param {string} entityFqName - name of the entity the enum is attached to with namespace
239
+ * @param {string} propertyName - property to which the enum is attached.
240
+ * @param {[string, string][]} kvs - list of key-value pairs
243
241
  * If given, the enum is considered to be an inline definition of an enum.
244
242
  * If not, it is considered to be regular, named enum.
245
- *
246
243
  * @example
247
244
  * ```js
248
245
  * addInlineEnum('Books.genre', 'Books', 'genre', [['horror','horror']])
@@ -279,8 +276,8 @@ class SourceFile extends File {
279
276
  * This differs from writing to the classes buffer,
280
277
  * as it is just a cache to collect all classes that
281
278
  * are supposed to be present in this file.
282
- * @param {string} clean cleaned name of the class
283
- * @param {string} fq fully qualified name, including the namespace
279
+ * @param {string} clean - cleaned name of the class
280
+ * @param {string} fq - fully qualified name, including the namespace
284
281
  */
285
282
  addClass(clean, fq) {
286
283
  this.classNames[clean] = fq
@@ -289,8 +286,8 @@ class SourceFile extends File {
289
286
  /**
290
287
  * Adds an event to this file.
291
288
  * are supposed to be present in this file.
292
- * @param {string} name cleaned name of the event
293
- * @param {string} fq fully qualified name, including the namespace
289
+ * @param {string} name - cleaned name of the event
290
+ * @param {string} fq - fully qualified name, including the namespace
294
291
  */
295
292
  addEvent(name, fq) {
296
293
  this.events.fqs.push({ name, fq })
@@ -298,7 +295,7 @@ class SourceFile extends File {
298
295
 
299
296
  /**
300
297
  * Adds an import if it does not exist yet.
301
- * @param {Path} imp qualifier for the namespace to import.
298
+ * @param {Path} imp - qualifier for the namespace to import.
302
299
  */
303
300
  addImport(imp) {
304
301
  const dir = imp.asDirectory({relative: this.path.asDirectory()})
@@ -310,7 +307,7 @@ class SourceFile extends File {
310
307
  /**
311
308
  * Adds an arbitrary piece of code that is added
312
309
  * right after the imports.
313
- * @param {string} code the preamble code.
310
+ * @param {string} code - the preamble code.
314
311
  */
315
312
  addPreamble(code) {
316
313
  this.preamble.add(code)
@@ -318,9 +315,9 @@ class SourceFile extends File {
318
315
 
319
316
  /**
320
317
  * Adds a type alias to this file.
321
- * @param {string} fq fully qualified name of the enum
322
- * @param {string} name local name of the enum
323
- * @param {string} rhs the right hand side of the assignment
318
+ * @param {string} fq - fully qualified name of the enum
319
+ * @param {string} clean - local name of the enum
320
+ * @param {string} rhs - the right hand side of the assignment
324
321
  */
325
322
  addType(fq, clean, rhs) {
326
323
  this.typeNames[clean] = fq
@@ -331,7 +328,7 @@ class SourceFile extends File {
331
328
  * Adds a service to the file.
332
329
  * We consider each service its own distinct namespace and therefore expect
333
330
  * at most one service per file.
334
- * @param {string} fq the fully qualified name of the service
331
+ * @param {string} fq - the fully qualified name of the service
335
332
  */
336
333
  addService(fq) {
337
334
  // FIXME: warn the user when they're trying to add an entity/ type/ enum called "name", which will override our name export
@@ -458,7 +455,7 @@ class Buffer {
458
455
 
459
456
  /**
460
457
  * Concats all elements in the buffer into a single string.
461
- * @param {string} glue string to intersperse all buffer contents with
458
+ * @param {string} glue - string to intersperse all buffer contents with
462
459
  * @returns {string} string spilled buffer contents.
463
460
  */
464
461
  join(glue = '\n') {
@@ -482,7 +479,7 @@ class Buffer {
482
479
 
483
480
  /**
484
481
  * Adds an element to the buffer with one level of indent.
485
- * @param {string | (() => void)} part either a string or a function. If it is a string, it is added to the buffer.
482
+ * @param {string | (() => void)} part - either a string or a function. If it is a string, it is added to the buffer.
486
483
  * If not, it is expected to be a function that manipulates the buffer as a side effect.
487
484
  */
488
485
  addIndented(part) {
@@ -497,9 +494,9 @@ class Buffer {
497
494
 
498
495
  /**
499
496
  * Adds an element to a buffer with one level of indent and and opener and closer surrounding it.
500
- * @param {string} opener the string to put before the indent
501
- * @param {string} content the content to indent (see {@link addIndented})
502
- * @param {string} closer the string to put after the indent
497
+ * @param {string} opener - the string to put before the indent
498
+ * @param {string} content - the content to indent (see {@link addIndented})
499
+ * @param {string} closer - the string to put after the indent
503
500
  */
504
501
  addIndentedBlock(opener, content, closer) {
505
502
  this.add(opener)
@@ -514,8 +511,8 @@ class Buffer {
514
511
  class Path {
515
512
 
516
513
  /**
517
- * @param {string[]} parts parts of the path. 'a.b.c' -> ['a', 'b', 'c']
518
- * @param kind FIXME: currently unused
514
+ * @param {string[]} parts - parts of the path. 'a.b.c' -> ['a', 'b', 'c']
515
+ * @param {string} kind - FIXME: currently unused
519
516
  */
520
517
  constructor(parts, kind) {
521
518
  this.parts = parts
@@ -531,9 +528,10 @@ class Path {
531
528
 
532
529
  /**
533
530
  * Transfoms the Path into a directory path.
534
- * @param {string?} params.relative if defined, the path is constructed relative to this directory
535
- * @param {boolean} params.local if set to true, './' is prefixed to the directory
536
- * @param {boolean} params.posix if set to true, all slashes will be forward slashes on every OS. Useful for require/ import
531
+ * @param {object} params
532
+ * @param {string?} params.relative - if defined, the path is constructed relative to this directory
533
+ * @param {boolean} params.local - if set to true, './' is prefixed to the directory
534
+ * @param {boolean} params.posix - if set to true, all slashes will be forward slashes on every OS. Useful for require/ import
537
535
  * @returns {string} directory 'a.b.c'.asDirectory() -> 'a/b/c' (or a\b\c when on Windows without passing posix = true)
538
536
  */
539
537
  asDirectory(params = {}) {
@@ -567,6 +565,7 @@ class Path {
567
565
  }
568
566
 
569
567
  /**
568
+ * @param {string} relative
570
569
  * @returns {boolean} true, iff the Path refers to the current working directory, aka './'
571
570
  */
572
571
  isCwd(relative = undefined) {
@@ -590,7 +589,7 @@ class FileRepository {
590
589
  /**
591
590
  * Determines the file corresponding to the namespace.
592
591
  * If no such file exists yet, it is created first.
593
- * @param {string | Path} path the name of the namespace (foo.bar.baz)
592
+ * @param {string | Path} path - the name of the namespace (foo.bar.baz)
594
593
  * @returns {SourceFile} the file corresponding to that namespace name
595
594
  */
596
595
  getNamespaceFile(path) {
@@ -610,8 +609,8 @@ class FileRepository {
610
609
  * Writes the files to disk. For each source, a index.d.ts holding the type definitions
611
610
  * and a index.js holding implementation stubs is generated at the appropriate directory.
612
611
  * Missing directories are created automatically and asynchronously.
613
- * @param {string} root root directory to prefix all directories with
614
- * @param {File[]} sources source files to write to disk
612
+ * @param {string} root - root directory to prefix all directories with
613
+ * @param {File[]} sources - source files to write to disk
615
614
  * @returns {Promise<string[]>} Promise that resolves to a list of all directory paths pointing to generated files.
616
615
  */
617
616
  const writeout = async (root, sources) =>
package/lib/logging.js CHANGED
@@ -25,7 +25,7 @@ class Logger {
25
25
 
26
26
  /**
27
27
  * Add all log levels starting at level.
28
- * @param {number} baseLevel level to start from.
28
+ * @param {number} baseLevel - level to start from.
29
29
  */
30
30
  addFrom(baseLevel) {
31
31
  const vals = Object.values(Levels)
@@ -37,7 +37,7 @@ class Logger {
37
37
 
38
38
  /**
39
39
  * Adds a log level to react to.
40
- * @param {number} level the level to react to.
40
+ * @param {number} level - the level to react to.
41
41
  */
42
42
  add(level) {
43
43
  this.mask = this.mask | level
@@ -45,7 +45,7 @@ class Logger {
45
45
 
46
46
  /**
47
47
  * Ignores a log level.
48
- * @param {number} level the level to ignore.
48
+ * @param {number} level - the level to ignore.
49
49
  */
50
50
  ignore(level) {
51
51
  this.mask = this.mask ^ level
@@ -56,8 +56,8 @@ class Logger {
56
56
  * Only iff levelName is a valid log level
57
57
  * and the corresponding number if part of mask,
58
58
  * the message gets logged.
59
- * @param {Levels} levelName name of the log level.
60
- * @param {string} message message to log.
59
+ * @param {Levels} levelName - name of the log level.
60
+ * @param {string} message - message to log.
61
61
  */
62
62
  _log(levelName, message) {
63
63
  const level = Levels[levelName]
@@ -0,0 +1,75 @@
1
+ export module resolver {
2
+ export type EntityCSN = {
3
+ cardinality?: { max?: '*' | number }
4
+ }
5
+
6
+ export type CSN = {
7
+ definitions?: { [key: string]: EntityCSN }
8
+ }
9
+
10
+ /**
11
+ * When nested inline types require additional imports. E.g.:
12
+ * ```cds
13
+ * // mymodel.cds
14
+ * Foo {
15
+ * bar: {
16
+ * baz: a.b.c.Baz // need to require a.b.c in mymodel.cds!
17
+ * }
18
+ * }
19
+ * ```
20
+ */
21
+ export type TypeResolveInfo = {
22
+ isBuiltin: boolean,
23
+ isDeepRequire: boolean,
24
+ isNotNull: boolean,
25
+ isInlineDeclaration: boolean,
26
+ isForeignKeyReference: boolean,
27
+ isArray: boolean,
28
+ type: string,
29
+ path?: Path,
30
+ csn?: CSN,
31
+ imports: Path[]
32
+ inner: TypeResolveInfo
33
+ }
34
+ }
35
+
36
+ export module util {
37
+ export type Annotations = {
38
+ name?: string,
39
+ '@singular'?: string,
40
+ '@plural'?: string
41
+ }
42
+
43
+ export type CommandLineFlags = {
44
+ desc: string,
45
+ default?: any
46
+ }
47
+
48
+ export type ParsedFlag = {
49
+ positional: string[],
50
+ named: { [key: string]: any }
51
+ }
52
+ }
53
+
54
+ export module visitor {
55
+ export type CompileParameters = {
56
+ outputDirectory: string,
57
+ logLevel: number,
58
+ jsConfigPath?: string,
59
+ }
60
+
61
+ export type VisitorOptions = {
62
+ propertiesOptional: boolean,
63
+ inlineDeclarations: 'flat' | 'structured',
64
+ }
65
+
66
+ export type Inflection = {
67
+ typeName: string,
68
+ singular: string,
69
+ plural: string
70
+ }
71
+ }
72
+
73
+ export module file {
74
+ export type Namespace = Object<string, Buffer>
75
+ }
package/lib/util.js CHANGED
@@ -1,8 +1,6 @@
1
- /**
2
- * @typedef { {name?: string, '@singular'?: string, '@plural'?: string,} } Annotations
3
- * @typedef { {desc: string, default?: any} } CommandlineFlag
4
- * @typedef { {positional: string[], named: Object<string, any>} } ParsedFlags
5
- */
1
+ /** @typedef { import('./typedefs').util.Annotations} Annotations */
2
+ /** @typedef { import('./typedefs').util.CommandLineFlags } CommandlineFlag */
3
+ /** @typedef { import('./typedefs').util.ParsedFlag } ParsedFlags */
6
4
 
7
5
  // inflection functions are stolen from github/cap/dev/blob/main/etc/inflect.js
8
6
 
@@ -25,7 +23,7 @@ const annotations = {
25
23
  * from a CSN. Valid annotations are listed in util.annotations
26
24
  * and their precedence is in order of definition.
27
25
  * If no singular is specified at all, undefined is returned.
28
- * @param {Object} csn the CSN of an entity to check
26
+ * @param {object} csn - the CSN of an entity to check
29
27
  * @returns {string | undefined} the singular annotation or undefined
30
28
  */
31
29
  const getSingularAnnotation = csn => csn[annotations.singular.find(a => Object.hasOwn(csn, a))]
@@ -35,22 +33,22 @@ const getSingularAnnotation = csn => csn[annotations.singular.find(a => Object.h
35
33
  * from a CSN. Valid annotations are listed in util.annotations
36
34
  * and their precedence is in order of definition.
37
35
  * If no plural is specified at all, undefined is returned.
38
- * @param {Object} csn the CSN of an entity to check
36
+ * @param {object} csn - the CSN of an entity to check
39
37
  * @returns {string | undefined} the plural annotation or undefined
40
38
  */
41
39
  const getPluralAnnotation = csn => csn[annotations.plural.find(a => Object.hasOwn(csn, a))]
42
40
 
43
41
  /**
44
- * Users can specify that they want to refer to localisation
45
- * using the syntax {i18n>Foo}, where Foo is the name of the
46
- * entity as found in the .cds file
47
- * (see: https://pages.github.tools.sap/cap/docs/guides/i18n)
48
- * As this throws off the naming, we remove this wrapper
49
- * unlocalize("{i18n>Foo}") -> "Foo"
50
- * @param {string} name the entity name (singular or plural).
51
- * @returns {string} the name without localisation syntax or untouched.
52
- * @deprecated we have dropped this feature altogether, users specify custom names via @singular/@plural now
53
- */
42
+ * Users can specify that they want to refer to localisation
43
+ * using the syntax {i18n>Foo}, where Foo is the name of the
44
+ * entity as found in the .cds file
45
+ * (see: https://pages.github.tools.sap/cap/docs/guides/i18n)
46
+ * As this throws off the naming, we remove this wrapper
47
+ * unlocalize("{i18n>Foo}") -> "Foo"
48
+ * @param {string} name - the entity name (singular or plural).
49
+ * @returns {string} the name without localisation syntax or untouched.
50
+ * @deprecated we have dropped this feature altogether, users specify custom names via @singular/@plural now
51
+ */
54
52
  const unlocalize = name => {
55
53
  const match = name.match(/\{i18n>(.*)\}/)
56
54
  return match ? match[1] : name
@@ -59,8 +57,8 @@ const unlocalize = name => {
59
57
  /**
60
58
  * Attempts to derive the singular form of an English noun.
61
59
  * If '@singular' is passed as annotation, that is preferred.
62
- * @param {Annotations} dn annotations
63
- * @param {boolean?} stripped if true, leading namespace will be stripped
60
+ * @param {Annotations} dn - annotations
61
+ * @param {boolean?} stripped - if true, leading namespace will be stripped
64
62
  */
65
63
  const singular4 = (dn, stripped = false) => {
66
64
  let n = dn.name || dn
@@ -91,8 +89,8 @@ const singular4 = (dn, stripped = false) => {
91
89
  /**
92
90
  * Attempts to derive the plural form of an English noun.
93
91
  * If '@plural' is passed as annotation, that is preferred.
94
- * @param {Annotations} dn annotations
95
- * @param {boolean} stripped if true, leading namespace will be stripped
92
+ * @param {Annotations} dn - annotations
93
+ * @param {boolean} stripped - if true, leading namespace will be stripped
96
94
  */
97
95
  const plural4 = (dn, stripped) => {
98
96
  let n = dn.name || dn
@@ -114,8 +112,8 @@ const plural4 = (dn, stripped) => {
114
112
  /**
115
113
  * Performs a deep merge of the passed objects into the first object.
116
114
  * See Object.assign(target, source).
117
- * @param {Object} target object to assign into.
118
- * @param {Object} source object to assign from.
115
+ * @param {object} target - object to assign into.
116
+ * @param {object} source - object to assign from.
119
117
  */
120
118
  const deepMerge = (target, source) => {
121
119
  Object.keys(target)
@@ -134,8 +132,8 @@ const deepMerge = (target, source) => {
134
132
  * will cause an error.
135
133
  * Named parameters that are either not specified or do not have a value assigned to them may draw a default value
136
134
  * from their definition in validFlags.
137
- * @param {string[]} argv list of command line arguments
138
- * @param {Object<string, CommandlineFlag>} validFlags allowed flags. May specify default values.
135
+ * @param {string[]} argv - list of command line arguments
136
+ * @param {{[key: string]: CommandlineFlag}} validFlags - allowed flags. May specify default values.
139
137
  * @returns {ParsedFlags}
140
138
  */
141
139
  const parseCommandlineArgs = (argv, validFlags) => {
package/lib/visitor.js CHANGED
@@ -15,33 +15,10 @@ const { empty } = require('./components/typescript')
15
15
  const { baseDefinitions } = require('./components/basedefs')
16
16
 
17
17
  /** @typedef {import('./file').File} File */
18
- /** @typedef {{ entity: String }} Context */
19
-
20
- /**
21
- * @typedef { {
22
- * outputDirectory: string,
23
- * logLevel: number,
24
- * jsConfigPath?: string
25
- * }} CompileParameters
26
- */
27
-
28
- /**
29
- * - `propertiesOptional = true` -> all properties are generated as optional ?:. (standard CAP behaviour, where properties be unavailable)
30
- * - `inlineDeclarations = 'structured'` -> @see inline.StructuredInlineDeclarationResolver
31
- * - `inlineDeclarations = 'flat'` -> @see inline.FlatInlineDeclarationResolver
32
- * @typedef { {
33
- * propertiesOptional: boolean,
34
- * inlineDeclarations: 'flat' | 'structured',
35
- * }} VisitorOptions
36
- */
37
-
38
- /**
39
- * @typedef {{
40
- * typeName: string,
41
- * singular: string,
42
- * plural: string
43
- * }} Inflection
44
- */
18
+ /** @typedef {import('./typedefs').visitor.Context} Context */
19
+ /** @typedef {import('./typedefs').visitor.CompileParameters} CompileParameters */
20
+ /** @typedef {import('./typedefs').visitor.VisitorOptions} VisitorOptions */
21
+ /** @typedef {import('./typedefs').visitor.Inflection} Inflection */
45
22
 
46
23
  const defaults = {
47
24
  // FIXME: add defaults for remaining parameters
@@ -61,8 +38,9 @@ class Visitor {
61
38
  }
62
39
 
63
40
  /**
64
- * @param {{xtended: CSN, inferred: CSN}} csn root CSN
41
+ * @param {{xtended: CSN, inferred: CSN}} csn - root CSN
65
42
  * @param {VisitorOptions} options
43
+ * @param {Logger} logger
66
44
  */
67
45
  constructor(csn, options = {}, logger = new Logger()) {
68
46
  amendCSN(csn.xtended)
@@ -127,6 +105,7 @@ class Visitor {
127
105
  /**
128
106
  * Retrieves all the keys from an entity.
129
107
  * That is: all keys that are present in both inferred, as well as xtended flavour.
108
+ * @param {string} name
130
109
  * @returns {[string, object][]} array of key name and key element pairs
131
110
  */
132
111
  #keys(name) {
@@ -148,9 +127,9 @@ class Visitor {
148
127
  * - the function A(B) to mix the aspect into another class B
149
128
  * - the const AXtended which represents the entity A with all of its aspects mixed in (this const is not exported)
150
129
  * - the type A to use for external typing and is derived from AXtended.
151
- * @param {string} name the name of the entity
152
- * @param {CSN} element the pointer into the CSN to extract the elements from
153
- * @param {Buffer} buffer the buffer to write the resulting definitions into
130
+ * @param {string} name - the name of the entity
131
+ * @param {CSN} entity - the pointer into the CSN to extract the elements from
132
+ * @param {Buffer} buffer - the buffer to write the resulting definitions into
154
133
  * @param {{cleanName?: string}} options
155
134
  */
156
135
  #aspectify(name, entity, buffer, options = {}) {
@@ -178,13 +157,15 @@ class Visitor {
178
157
  // lookup in cds.definitions can fail for inline structs.
179
158
  // We don't really have to care for this case, as keys from such structs are _not_ propagated to
180
159
  // the containing entity.
181
- for (const [kname, kelement] of this.#keys(element.target)) {
182
- if (this.resolver.getMaxCardinality(element) === 1) { // FIXME: kelement?
160
+ for (const [kname, originalKeyElement] of this.#keys(element.target)) {
161
+ if (this.resolver.getMaxCardinality(element) === 1 && typeof element.on !== 'object') { // FIXME: kelement?
183
162
  const foreignKey = `${ename}_${kname}`
184
163
  if (Object.hasOwn(entity.elements, foreignKey)) {
185
164
  this.logger.error(`Attempting to generate a foreign key reference called '${foreignKey}' in type definition for entity ${name}. But a property of that name is already defined explicitly. Consider renaming that property.`)
186
165
  } else {
187
- kelement.isRefNotNull = !!element.notNull || !!element.key
166
+ const kelement = Object.assign(Object.create(originalKeyElement), {
167
+ isRefNotNull: !!element.notNull || !!element.key
168
+ })
188
169
  this.visitElement(foreignKey, kelement, file, buffer)
189
170
  }
190
171
  }
@@ -439,8 +420,8 @@ class Visitor {
439
420
  /**
440
421
  * Visits a single entity from the CSN's definition field.
441
422
  * Will call #printEntity or #printAction based on the entity's kind.
442
- * @param {string} name name of the entity, fully qualified as is used in the definition field.
443
- * @param {CSN} entity CSN data belonging to the entity to perform lookups in.
423
+ * @param {string} name - name of the entity, fully qualified as is used in the definition field.
424
+ * @param {CSN} entity - CSN data belonging to the entity to perform lookups in.
444
425
  */
445
426
  visitEntity(name, entity) {
446
427
  switch (entity.kind) {
@@ -478,7 +459,7 @@ class Visitor {
478
459
  * refer to types via their alias that hides the aspectification.
479
460
  * If we attempt to directly refer to this alias while it has not been fully created,
480
461
  * that will result in a TS error.
481
- * @param {String} entityName
462
+ * @param {string} entityName
482
463
  * @returns {boolean} true, if `entityName` refers to the surrounding class
483
464
  * @example
484
465
  * ```ts
@@ -494,10 +475,10 @@ class Visitor {
494
475
 
495
476
  /**
496
477
  * Visits a single element in an entity.
497
- * @param {string} name name of the element
498
- * @param {import('./components/resolver').CSN} element CSN data belonging to the the element.
499
- * @param {SourceFile} file the namespace file the surrounding entity is being printed into.
500
- * @param {Buffer} buffer buffer to add the definition to. If no buffer is passed, the passed file's class buffer is used instead.
478
+ * @param {string} name - name of the element
479
+ * @param {import('./components/resolver').CSN} element - CSN data belonging to the the element.
480
+ * @param {SourceFile} file - the namespace file the surrounding entity is being printed into.
481
+ * @param {Buffer} buffer - buffer to add the definition to. If no buffer is passed, the passed file's class buffer is used instead.
501
482
  * @returns @see InlineDeclarationResolver.visitElement
502
483
  */
503
484
  visitElement(name, element, file, buffer) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js/cds-typer",
3
- "version": "0.21.1",
3
+ "version": "0.22.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",
@@ -45,6 +45,7 @@
45
45
  "@stylistic/eslint-plugin-js": "^1.6.3",
46
46
  "acorn": "^8.10.0",
47
47
  "eslint": "^9",
48
+ "eslint-plugin-jsdoc": "^48.2.7",
48
49
  "globals": "^15.0.0",
49
50
  "jest": "^29",
50
51
  "typescript": ">=4.6.4"