@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 +9 -1
- package/cds-plugin.js +9 -3
- package/lib/compile.js +5 -5
- package/lib/components/enum.js +7 -11
- package/lib/components/identifier.js +1 -1
- package/lib/components/inline.js +10 -10
- package/lib/components/reference.js +4 -5
- package/lib/components/resolver.js +25 -53
- package/lib/components/wrappers.js +9 -9
- package/lib/csn.js +16 -12
- package/lib/file.js +47 -48
- package/lib/logging.js +5 -5
- package/lib/typedefs.d.ts +75 -0
- package/lib/util.js +23 -25
- package/lib/visitor.js +22 -41
- package/package.json +2 -1
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.
|
|
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
|
|
19
|
-
* @param
|
|
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
|
|
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
|
|
63
|
-
* @param
|
|
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))
|
package/lib/components/enum.js
CHANGED
|
@@ -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 {
|
|
47
|
-
* @param {string}
|
|
48
|
-
* @param {
|
|
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)
|
package/lib/components/inline.js
CHANGED
|
@@ -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}
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
8
|
+
* x: Integer
|
|
9
9
|
* }
|
|
10
|
-
*
|
|
10
|
+
*
|
|
11
11
|
* entity F {
|
|
12
|
-
*
|
|
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[]}
|
|
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 {
|
|
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 {
|
|
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
|
-
*
|
|
615
|
-
*
|
|
587
|
+
* column : String;
|
|
588
|
+
* data : String;
|
|
616
589
|
* }
|
|
617
590
|
*
|
|
618
591
|
* These have to be resolved to a new type.
|
|
619
|
-
*
|
|
620
|
-
* @param {
|
|
621
|
-
* @param {
|
|
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
|
|
55
|
-
* @param
|
|
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
|
|
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
|
|
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 {
|
|
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 {
|
|
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 {
|
|
125
|
+
/** @type {{[key: string]: any}} */
|
|
126
126
|
this.classNames = {} // for .js file
|
|
127
|
-
/** @type {
|
|
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}
|
|
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}
|
|
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}
|
|
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 {
|
|
535
|
-
* @param {
|
|
536
|
-
* @param {boolean} params.
|
|
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
|
-
|
|
3
|
-
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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 {
|
|
118
|
-
* @param {
|
|
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 {
|
|
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 {
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
|
|
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}
|
|
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,
|
|
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
|
|
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 {
|
|
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.
|
|
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"
|