@cap-js/cds-typer 0.34.0 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/boilerplate/tsBoilerplate.ts +95 -0
- package/lib/components/basedefs.js +20 -77
- package/lib/components/enum.js +0 -1
- package/lib/file.js +1 -1
- package/lib/printers/javascript.js +21 -0
- package/lib/resolution/resolver.js +36 -7
- package/lib/typedefs.d.ts +1 -0
- package/lib/visitor.js +32 -2
- package/package.json +2 -2
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import cds from '@sap/cds'
|
|
2
|
+
import { type } from '@sap/cds'
|
|
3
|
+
|
|
4
|
+
export type ElementsOf<T> = {[name in keyof Required<T>]: type }
|
|
5
|
+
|
|
6
|
+
export namespace Association {
|
|
7
|
+
export type to <T> = T;
|
|
8
|
+
export namespace to {
|
|
9
|
+
export type many <T extends readonly any[]> = T;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export namespace Composition {
|
|
14
|
+
export type of <T> = T;
|
|
15
|
+
export namespace of {
|
|
16
|
+
export type many <T extends readonly any[]> = T;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class Entity {
|
|
21
|
+
static data<T extends Entity> (this:T, _input:Object) : T {
|
|
22
|
+
return {} as T // mock
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type EntitySet<T> = T[] & {
|
|
27
|
+
data (input:object[]) : T[]
|
|
28
|
+
data (input:object) : T
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type DraftEntity<T> = T & {
|
|
32
|
+
IsActiveEntity?: boolean | null
|
|
33
|
+
HasActiveEntity?: boolean | null
|
|
34
|
+
HasDraftEntity?: boolean | null
|
|
35
|
+
DraftAdministrativeData_DraftUUID?: string | null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type DraftOf<T> = { new(...args: any[]): DraftEntity<T> }
|
|
39
|
+
export type DraftsOf<T> = typeof Array<DraftEntity<T>>
|
|
40
|
+
|
|
41
|
+
export type DeepRequired<T> = {
|
|
42
|
+
[K in keyof T]: DeepRequired<Unkey<T[K]>>
|
|
43
|
+
} & Exclude<Required<T>, null>;
|
|
44
|
+
|
|
45
|
+
const key = Symbol('key') // to avoid .key showing up in IDE's auto-completion
|
|
46
|
+
export type Key<T> = T & {[key]?: true}
|
|
47
|
+
|
|
48
|
+
export type KeysOf<T> = {
|
|
49
|
+
[K in keyof T as NonNullable<T[K]> extends Key<unknown> ? K : never]-?: Key<{}> // T[K]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type Unkey<T> = T extends Key<infer U> ? U : T
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Dates and timestamps are strings during runtime, so cds-typer represents them as such.
|
|
56
|
+
*/
|
|
57
|
+
export type CdsDate = `${number}${number}${number}${number}-${number}${number}-${number}${number}`;
|
|
58
|
+
/**
|
|
59
|
+
* @see {@link CdsDate}
|
|
60
|
+
*/
|
|
61
|
+
export type CdsDateTime = string;
|
|
62
|
+
/**
|
|
63
|
+
* @see {@link CdsDate}
|
|
64
|
+
*/
|
|
65
|
+
export type CdsTime = `${number}${number}:${number}${number}:${number}${number}`;
|
|
66
|
+
/**
|
|
67
|
+
* @see {@link CdsDate}
|
|
68
|
+
*/
|
|
69
|
+
export type CdsTimestamp = string;
|
|
70
|
+
|
|
71
|
+
export type CdsMap = { [key: string]: unknown };
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
export const createEntityProxy = function (fqParts: any, opts = {}) {
|
|
75
|
+
const { target, customProps } = { target: {}, customProps: [], ...opts }
|
|
76
|
+
const fq = fqParts.filter((p: any) => !!p).join('.')
|
|
77
|
+
return new Proxy(target, {
|
|
78
|
+
get: function (target:any, prop:any) {
|
|
79
|
+
if (cds.entities) {
|
|
80
|
+
target.__proto__ = cds.entities(fqParts[0])[fqParts[1]]
|
|
81
|
+
// overwrite/simplify getter after cds.entities is accessible
|
|
82
|
+
this.get = (target, prop) => target[prop]
|
|
83
|
+
return target[prop]
|
|
84
|
+
}
|
|
85
|
+
// we already know the name so we skip the cds.entities proxy access
|
|
86
|
+
if (prop === 'name') return fq
|
|
87
|
+
// custom properties access on 'target' as well as cached _entity property access goes here
|
|
88
|
+
if (Object.hasOwn(target, prop)) return target[prop]
|
|
89
|
+
// inline enums have to be caught here for first time access, as they do not exist on the entity
|
|
90
|
+
if (customProps.includes(prop as never)) return target[prop]
|
|
91
|
+
// last but not least we pass the property access to cds.entities
|
|
92
|
+
throw new Error(`Property ${prop} does not exist on entity '${fq}' or cds.entities is not yet defined. Ensure the CDS runtime is fully booted before accessing properties.`)
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
}
|
|
@@ -1,88 +1,31 @@
|
|
|
1
1
|
const { SourceFile } = require('../file')
|
|
2
|
+
const fs = require('node:fs')
|
|
3
|
+
const path = require('node:path')
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/** @type {string} */
|
|
6
|
+
let tsBoilerplate
|
|
7
|
+
/** @type {SourceFile} */
|
|
8
|
+
let baseDefinitions
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
|
-
*
|
|
10
|
-
* such as Associations and Compositions.
|
|
11
|
-
* @type {SourceFile}
|
|
11
|
+
* @returns {string}
|
|
12
12
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import { type } from '@sap/cds'
|
|
17
|
-
|
|
18
|
-
export type ElementsOf<T> = {[name in keyof Required<T>]: type }
|
|
19
|
-
|
|
20
|
-
export namespace Association {
|
|
21
|
-
export type to <T> = T;
|
|
22
|
-
export namespace to {
|
|
23
|
-
export type many <T extends readonly any[]> = T;
|
|
13
|
+
function getTsBoilerplate () {
|
|
14
|
+
if (!tsBoilerplate) {
|
|
15
|
+
tsBoilerplate = fs.readFileSync(path.join(__filename, '..', '..', 'boilerplate/tsBoilerplate.ts'), 'utf8')
|
|
24
16
|
}
|
|
17
|
+
return tsBoilerplate
|
|
25
18
|
}
|
|
26
19
|
|
|
27
|
-
export namespace Composition {
|
|
28
|
-
export type of <T> = T;
|
|
29
|
-
export namespace of {
|
|
30
|
-
export type many <T extends readonly any[]> = T;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export class Entity {
|
|
35
|
-
static data<T extends Entity> (this:T, _input:Object) : T {
|
|
36
|
-
return {} as T // mock
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export type EntitySet<T> = T[] & {
|
|
41
|
-
data (input:object[]) : T[]
|
|
42
|
-
data (input:object) : T
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export type DraftEntity<T> = T & {
|
|
46
|
-
IsActiveEntity?: boolean | null
|
|
47
|
-
HasActiveEntity?: boolean | null
|
|
48
|
-
HasDraftEntity?: boolean | null
|
|
49
|
-
DraftAdministrativeData_DraftUUID?: string | null
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export type DraftOf<T> = { new(...args: any[]): DraftEntity<T> }
|
|
53
|
-
export type DraftsOf<T> = typeof Array<DraftEntity<T>>
|
|
54
|
-
|
|
55
|
-
export type DeepRequired<T> = {
|
|
56
|
-
[K in keyof T]: DeepRequired<Unkey<T[K]>>
|
|
57
|
-
} & Exclude<Required<T>, null>;
|
|
58
|
-
|
|
59
|
-
const key = Symbol('key') // to avoid .key showing up in IDE's auto-completion
|
|
60
|
-
export type Key<T> = T & {[key]?: true}
|
|
61
|
-
|
|
62
|
-
export type KeysOf<T> = {
|
|
63
|
-
[K in keyof T as NonNullable<T[K]> extends Key<unknown> ? K : never]-?: Key<{}> // T[K]
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export type Unkey<T> = T extends Key<infer U> ? U : T
|
|
67
|
-
|
|
68
20
|
/**
|
|
69
|
-
*
|
|
21
|
+
* @returns {SourceFile}
|
|
70
22
|
*/
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
*/
|
|
79
|
-
export type CdsTime = ${timeRegex};
|
|
80
|
-
/**
|
|
81
|
-
* @see {@link CdsDate}
|
|
82
|
-
*/
|
|
83
|
-
export type CdsTimestamp = string;
|
|
84
|
-
|
|
85
|
-
export type CdsMap = { [key: string]: unknown };
|
|
86
|
-
`)
|
|
23
|
+
function getBaseDefinitions () {
|
|
24
|
+
if (!baseDefinitions) {
|
|
25
|
+
baseDefinitions = new SourceFile('_')
|
|
26
|
+
baseDefinitions.addPreamble(getTsBoilerplate())
|
|
27
|
+
}
|
|
28
|
+
return baseDefinitions
|
|
29
|
+
}
|
|
87
30
|
|
|
88
|
-
module.exports = {
|
|
31
|
+
module.exports = { getBaseDefinitions }
|
package/lib/components/enum.js
CHANGED
|
@@ -124,7 +124,6 @@ const isInlineEnumType = (element, csn) => element.enum
|
|
|
124
124
|
* @param {[string, string][]} kvs - a list of key-value pairs. Values that are falsey are replaced by
|
|
125
125
|
* @param {import('../printers/javascript').Printer} jsp - the printer to use
|
|
126
126
|
*/
|
|
127
|
-
// ??= for inline enums. If there is some static property of that name, we don't want to override it (for example: ".actions"
|
|
128
127
|
const stringifyEnumImplementation = (name, kvs, jsp) => jsp.printExport(
|
|
129
128
|
name,
|
|
130
129
|
`{ ${kvs.map(([k,v]) => `${normalise(k)}: ${v}`).join(', ')} }`,
|
package/lib/file.js
CHANGED
|
@@ -471,7 +471,7 @@ class SourceFile extends File {
|
|
|
471
471
|
const boilerplate = [AUTO_GEN_NOTE]
|
|
472
472
|
if (configuration.useEntitiesProxy) {
|
|
473
473
|
if (namespace === '_') {
|
|
474
|
-
boilerplate.push(jsp.
|
|
474
|
+
boilerplate.push(jsp.printDefaultImport('cds', '@sap/cds'), this.#getEntityProxyFunctionExport())
|
|
475
475
|
} else {
|
|
476
476
|
boilerplate.push(
|
|
477
477
|
jsp.printDeconstructedImport(
|
|
@@ -33,6 +33,17 @@ class JavaScriptPrinter {
|
|
|
33
33
|
throw Error('not implemented')
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* @abstract
|
|
38
|
+
* @param {string} alias - what the import should be known as within the importing file
|
|
39
|
+
* @param {string} from - the package/ location to import from
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
// eslint-disable-next-line no-unused-vars
|
|
43
|
+
printDefaultImport (alias, from) {
|
|
44
|
+
throw Error('not implemented')
|
|
45
|
+
}
|
|
46
|
+
|
|
36
47
|
/**
|
|
37
48
|
* @abstract
|
|
38
49
|
* @param {string[]} imports - the deconstructed elements
|
|
@@ -83,6 +94,11 @@ class ESMPrinter extends JavaScriptPrinter {
|
|
|
83
94
|
return `import * as ${alias} from '${from}'`
|
|
84
95
|
}
|
|
85
96
|
|
|
97
|
+
/** @type {JavaScriptPrinter['printDefaultImport']} */
|
|
98
|
+
printDefaultImport (alias, from) {
|
|
99
|
+
return `import ${alias} from '${from}'`
|
|
100
|
+
}
|
|
101
|
+
|
|
86
102
|
/** @type {JavaScriptPrinter['printDeconstructedImport']} */
|
|
87
103
|
printDeconstructedImport (imports, from) {
|
|
88
104
|
return `import { ${imports.join(', ')} } from '${from}/index.js'`
|
|
@@ -115,6 +131,11 @@ class CJSPrinter extends JavaScriptPrinter {
|
|
|
115
131
|
return `const ${alias} = require('${from}')`
|
|
116
132
|
}
|
|
117
133
|
|
|
134
|
+
/** @type {JavaScriptPrinter['printDefaultImport']} */
|
|
135
|
+
printDefaultImport (alias, from) {
|
|
136
|
+
return `const ${alias} = require('${from}')`
|
|
137
|
+
}
|
|
138
|
+
|
|
118
139
|
/** @type {JavaScriptPrinter['printDeconstructedImport']} */
|
|
119
140
|
printDeconstructedImport (imports, from) {
|
|
120
141
|
return `const { ${imports.join(', ')} } = require('${from}')`
|
|
@@ -8,13 +8,15 @@ const { StructuredInlineDeclarationResolver } = require('../components/inline')
|
|
|
8
8
|
const { isInlineEnumType, propertyToInlineEnumName } = require('../components/enum')
|
|
9
9
|
const { isReferenceType } = require('../components/reference')
|
|
10
10
|
const { isEntity, getMaxCardinality } = require('../csn')
|
|
11
|
-
const {
|
|
11
|
+
const { getBaseDefinitions } = require('../components/basedefs')
|
|
12
12
|
const { BuiltinResolver } = require('./builtin')
|
|
13
13
|
const { LOG } = require('../logging')
|
|
14
14
|
const { last } = require('../components/identifier')
|
|
15
15
|
const { getPropertyModifiers } = require('../components/property')
|
|
16
16
|
const { configuration } = require('../config')
|
|
17
17
|
|
|
18
|
+
const baseDefinitions = getBaseDefinitions()
|
|
19
|
+
|
|
18
20
|
/** @typedef {import('../visitor').Visitor} Visitor */
|
|
19
21
|
/** @typedef {import('../typedefs').resolver.CSN} CSN */
|
|
20
22
|
/** @typedef {import('../typedefs').resolver.EntityCSN} EntityCSN */
|
|
@@ -68,6 +70,14 @@ class Resolver {
|
|
|
68
70
|
return type.items ? !type.items.notNull : !type.notNull
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
/**
|
|
74
|
+
* @param {EntityCSN} type - a CSN type
|
|
75
|
+
* @returns {boolean} whether the type has the @mandatory annotation
|
|
76
|
+
*/
|
|
77
|
+
isMandatory(type) {
|
|
78
|
+
return type['@mandatory'] === true
|
|
79
|
+
}
|
|
80
|
+
|
|
71
81
|
/**
|
|
72
82
|
* Returns all libraries that have been referenced at least once.
|
|
73
83
|
* @returns {Library[]}
|
|
@@ -302,11 +312,24 @@ class Resolver {
|
|
|
302
312
|
}[element.constructor.name] ?? []
|
|
303
313
|
|
|
304
314
|
if (toOne && toMany) {
|
|
305
|
-
/**
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
315
|
+
/**
|
|
316
|
+
* Resolve a property from a CSN entity. If it is a reference, leave it as is.
|
|
317
|
+
* If it is a string, return an object with type set to the string.
|
|
318
|
+
* @param {Record<string, any>} el - the element to check
|
|
319
|
+
* @param {string} property - the property to check
|
|
320
|
+
* @returns {import('../typedefs').resolver.EntityCSN | { type: string }}
|
|
321
|
+
*/
|
|
322
|
+
const getTarget = (el, property) => typeof el[property] === 'string'
|
|
323
|
+
? { type: el[property] }
|
|
324
|
+
: el[property]
|
|
325
|
+
|
|
326
|
+
/** @type { EntityCSN | { type: string } | undefined } */
|
|
327
|
+
const target = element.items
|
|
328
|
+
?? getTarget(element, 'target')
|
|
329
|
+
?? getTarget(element, 'targetAspect') // Composition of aspects
|
|
330
|
+
if (!target) {
|
|
331
|
+
throw new Error(`Could not resolve target of ${element}`)
|
|
332
|
+
}
|
|
310
333
|
/** set `notNull = true` to avoid repeated `| not null` TS construction */
|
|
311
334
|
// @ts-expect-error - yes, we know that notNull is not part of the type in some cases
|
|
312
335
|
target.notNull = true
|
|
@@ -475,6 +498,11 @@ class Resolver {
|
|
|
475
498
|
: element?.key || element?.notNull || cardinality > 1,
|
|
476
499
|
}
|
|
477
500
|
|
|
501
|
+
// parameters with @mandatory are always not null
|
|
502
|
+
// as of today, it is not clear if this also applies to other fields annotated with @mandatory.
|
|
503
|
+
result.isNotNull ||= element.kind === 'param' && this.isMandatory(element)
|
|
504
|
+
|
|
505
|
+
|
|
478
506
|
if (element?.type === undefined) {
|
|
479
507
|
// "fallback" type "empty object". May be overriden via #resolveInlineDeclarationType
|
|
480
508
|
// later on with an inline declaration
|
|
@@ -521,9 +549,10 @@ class Resolver {
|
|
|
521
549
|
result.isBuiltin = true
|
|
522
550
|
this.resolveType(element.items, file)
|
|
523
551
|
//delete element.items
|
|
524
|
-
} else if (element?.elements && (options?.forceInlineStructs || !element?.type)) {
|
|
552
|
+
} else if (!result.isBuiltin && element?.elements && (options?.forceInlineStructs || !element?.type)) {
|
|
525
553
|
// explicitly skip named type definitions, which have elements too, but should not be considered inline declarations
|
|
526
554
|
// if the resolver option `forceInlineStructs` is `true`, named types in elements will be converted to inline
|
|
555
|
+
// Skipping isBuiltin will skip cds.Map, which has elements
|
|
527
556
|
this.#resolveInlineDeclarationType(element.elements, result, file, options)
|
|
528
557
|
}
|
|
529
558
|
|
package/lib/typedefs.d.ts
CHANGED
package/lib/visitor.js
CHANGED
|
@@ -10,7 +10,7 @@ const { docify, createPromiseOf, createUnionOf, createKeysOf, createElementsOf,
|
|
|
10
10
|
const { csnToEnumPairs, propertyToInlineEnumName, isInlineEnumType, stringifyEnumType } = require('./components/enum')
|
|
11
11
|
const { isReferenceType } = require('./components/reference')
|
|
12
12
|
const { empty } = require('./components/typescript')
|
|
13
|
-
const {
|
|
13
|
+
const { getBaseDefinitions } = require('./components/basedefs')
|
|
14
14
|
const { EntityRepository, asIdentifier } = require('./resolution/entity')
|
|
15
15
|
const { last } = require('./components/identifier')
|
|
16
16
|
const { getPropertyModifiers } = require('./components/property')
|
|
@@ -18,6 +18,9 @@ const { configuration } = require('./config')
|
|
|
18
18
|
const { createMember } = require('./components/class')
|
|
19
19
|
const { overrideNameProperty } = require('./printers/javascript')
|
|
20
20
|
|
|
21
|
+
const baseDefinitions = getBaseDefinitions()
|
|
22
|
+
const MAX_TRANSITIVE_RESOLUTION_STEPS = 10
|
|
23
|
+
|
|
21
24
|
/** @typedef {import('./file').File} File */
|
|
22
25
|
/** @typedef {import('./typedefs').visitor.Context} Context */
|
|
23
26
|
/** @typedef {import('./typedefs').visitor.Inflection} Inflection */
|
|
@@ -274,6 +277,31 @@ class Visitor {
|
|
|
274
277
|
}))
|
|
275
278
|
if (typeof e?.type !== 'string' && e?.type?.ref) {
|
|
276
279
|
e.resolvedType = /** @type {string} */(lookUpRefType(this.csn, e.type.ref)?.type)
|
|
280
|
+
try {
|
|
281
|
+
/**
|
|
282
|
+
* multi-level resolution does not contain a .ref property:
|
|
283
|
+
* ```cds
|
|
284
|
+
* entity A {
|
|
285
|
+
* x: enum ...
|
|
286
|
+
* }
|
|
287
|
+
* entity B {
|
|
288
|
+
* x: A:x
|
|
289
|
+
* }
|
|
290
|
+
* entity C {
|
|
291
|
+
* x: B:x
|
|
292
|
+
* }
|
|
293
|
+
* ```
|
|
294
|
+
* results in B.x having a ref to [A,x], but C.x only has a string 'A.x' as type.
|
|
295
|
+
* So we have to do yet another round of resolution on this string.
|
|
296
|
+
* We attempt to follow this chain for MAX_TRANSITIVE_RESOLUTION_STEPS tops,
|
|
297
|
+
* but we could finish earlier, when the type is a primitive ("string"), which
|
|
298
|
+
* then jumps to the catch {} to leave e.resolvedType at the last, resolvable type.
|
|
299
|
+
*/
|
|
300
|
+
for (let i = 0; i < MAX_TRANSITIVE_RESOLUTION_STEPS; i++) {
|
|
301
|
+
const { csn } = this.resolver.resolveTypeName(/** @type {string} */(e.resolvedType))
|
|
302
|
+
e.resolvedType = csn.type
|
|
303
|
+
}
|
|
304
|
+
} catch { /* ignore */ }
|
|
277
305
|
}
|
|
278
306
|
file.addInlineEnum(clean, fq, e.name, csnToEnumPairs(e, {unwrapVals: true}), buffer, eDoc)
|
|
279
307
|
}
|
|
@@ -403,7 +431,9 @@ class Visitor {
|
|
|
403
431
|
.filter(([, type]) => type?.type !== '$self' && type.items?.type !== '$self')
|
|
404
432
|
.map(([name, type]) => ({
|
|
405
433
|
name,
|
|
406
|
-
modifier: this.resolver.isOptional(type)
|
|
434
|
+
modifier: this.resolver.isOptional(type) && !this.resolver.isMandatory(type)
|
|
435
|
+
? '?'
|
|
436
|
+
: '',
|
|
407
437
|
type: this.#stringifyFunctionParamType(type, file),
|
|
408
438
|
doc: docify(type.doc).join('\n'),
|
|
409
439
|
}))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/cds-typer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "Generates .ts files for a CDS model to receive code completion in VS Code",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": "github:cap-js/cds-typer",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@stylistic/eslint-plugin-js": "^4.2.0",
|
|
49
49
|
"acorn": "^8.10.0",
|
|
50
50
|
"eslint": "^9",
|
|
51
|
-
"eslint-plugin-jsdoc": "^
|
|
51
|
+
"eslint-plugin-jsdoc": "^51.2.1",
|
|
52
52
|
"typescript": ">=4.6.4"
|
|
53
53
|
},
|
|
54
54
|
"cds": {
|