@cap-js/cds-typer 0.28.1 → 0.30.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 +18 -0
- package/lib/cli.js +10 -1
- package/lib/components/inline.js +1 -1
- package/lib/config.js +30 -7
- package/lib/file.js +62 -21
- package/lib/printers/javascript.js +131 -0
- package/lib/{components → printers}/wrappers.js +8 -0
- package/lib/resolution/resolver.js +1 -1
- package/lib/typedefs.d.ts +1 -0
- package/lib/util.js +35 -5
- package/lib/visitor.js +25 -20
- package/package.json +18 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@ 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.31.0 - TBD
|
|
8
|
+
|
|
9
|
+
## Version 0.30.0 - 2024-12-02
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- [breaking] when running cds-typer in a CAP project, the default for the `outputDirectory` option will be `./@cds-models` instead of `./`. This default takes the lowest precedence after setting it in the project's `cds.env`, or explicitly as CLI argument.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- cds-typer no longer ignores the selected `outputDirectory`, which would also cause an issue during build
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## Version 0.29.0 - 2024-11-20
|
|
19
|
+
### Added
|
|
20
|
+
- [breaking] cds-typer now tries to automatically detect whether it has to generate ESM or CommonJS in the emitted _index.js_ files. This behaviour can be overridden via the `--targetModuleType` option. _If you rely on these generated index.js files to be CJS despite your project being of ESM type, you need to manually tell cds-typer to generate CJS files!_
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- The static `.keys` property now properly reels in key types from inherited classes.
|
|
24
|
+
|
|
7
25
|
## Version 0.28.1 - 2024-11-07
|
|
8
26
|
### Fixed
|
|
9
27
|
- `cds build` no longer fails on Windows with an `EINVAL` error.
|
package/lib/cli.js
CHANGED
|
@@ -142,6 +142,7 @@ const addCLIParamsToConfig = params => {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
// when changing/ adding anything in here, make sure to adjust scripts/write-cds-typer-schema.js accordingly!
|
|
145
146
|
const flags = enrichFlagSchema({
|
|
146
147
|
outputDirectory: {
|
|
147
148
|
desc: 'Root directory to write the generated files to.',
|
|
@@ -190,7 +191,12 @@ const flags = enrichFlagSchema({
|
|
|
190
191
|
IEEE754Compatible: parameterTypes.boolean({
|
|
191
192
|
desc: `If set to true, floating point properties are generated${EOL}as IEEE754 compatible '(number | string)' instead of 'number'.`,
|
|
192
193
|
default: 'false'
|
|
193
|
-
})
|
|
194
|
+
}),
|
|
195
|
+
targetModuleType: {
|
|
196
|
+
desc: `Output format for generated .js files.${EOL}Setting it to auto tries to derive the module type from${EOL}the package.json and falls back to CJS.`,
|
|
197
|
+
allowed: ['esm', 'cjs', 'auto'],
|
|
198
|
+
default: 'auto'
|
|
199
|
+
}
|
|
194
200
|
})
|
|
195
201
|
|
|
196
202
|
const hint = () => 'Missing or invalid parameter(s). Call with --help for more details.'
|
|
@@ -253,6 +259,9 @@ const prepareParameters = (/** @type {any[]} */ argv) => {
|
|
|
253
259
|
}
|
|
254
260
|
|
|
255
261
|
const main = async (/** @type {any[]} */ argv) => {
|
|
262
|
+
// when calling from CLI within a CAP project, make sure plugins (this includes cds-typer)
|
|
263
|
+
// are initialised and have their default values injected into cds.env
|
|
264
|
+
await cds.plugins
|
|
256
265
|
const { positional } = prepareParameters(argv)
|
|
257
266
|
compileFromFile(positional)
|
|
258
267
|
}
|
package/lib/components/inline.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { configuration } = require('../config')
|
|
2
2
|
const { SourceFile, Buffer } = require('../file')
|
|
3
3
|
const { normalise } = require('./identifier')
|
|
4
|
-
const { docify } = require('
|
|
4
|
+
const { docify } = require('../printers/wrappers')
|
|
5
5
|
|
|
6
6
|
/** @typedef {import('../resolution/resolver').TypeResolveInfo} TypeResolveInfo */
|
|
7
7
|
/** @typedef {import('../resolution/resolver').TypeResolveOptions} TypeResolverOptions */
|
package/lib/config.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
const fs = require('node:fs')
|
|
2
|
+
const path = require('node:path')
|
|
1
3
|
const cds = require('@sap/cds')
|
|
2
|
-
const { camelToSnake } = require('./util')
|
|
4
|
+
const { camelToSnake, getProjectTargetType } = require('./util')
|
|
5
|
+
const { LOG } = require('./logging')
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Makes properties of an object accessible in both camelCase and snake_case.
|
|
@@ -31,19 +34,39 @@ const camelSnakeHybrid = target => {
|
|
|
31
34
|
return proxy
|
|
32
35
|
}
|
|
33
36
|
class Config {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
/** @type {Record<string, unknown>} */
|
|
38
|
+
static #defaults = {}
|
|
39
|
+
static get defaults () {
|
|
40
|
+
// these are the exact defaults that are used when cds-typer is loaded as cds plugin
|
|
41
|
+
// which will shove the values into cds.env. When executed standalone, without cds environment,
|
|
42
|
+
// we need to load them from package.json ourselves.
|
|
43
|
+
if (Object.keys(Config.#defaults).length) return Config.#defaults
|
|
44
|
+
try {
|
|
45
|
+
const packageJsonPath = path.resolve(__dirname, path.join('..', 'package.json'))
|
|
46
|
+
const pjson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
47
|
+
Config.#defaults = pjson.cds.typer
|
|
48
|
+
} catch (e) {
|
|
49
|
+
LOG.error(`Failed to load default configuration from package.json: ${e}`)
|
|
50
|
+
}
|
|
51
|
+
return Config.#defaults
|
|
39
52
|
}
|
|
40
53
|
|
|
41
54
|
values = undefined
|
|
42
55
|
proxy = undefined
|
|
43
56
|
|
|
44
57
|
init () {
|
|
45
|
-
this.values = {...Config
|
|
58
|
+
this.values = {...Config.defaults, ...(cds.env.typer ?? {})}
|
|
46
59
|
this.proxy = camelSnakeHybrid(this.values)
|
|
60
|
+
if (this.proxy.targetModuleType === 'auto') {
|
|
61
|
+
const type = getProjectTargetType(cds.root)
|
|
62
|
+
if (type) {
|
|
63
|
+
LOG.info(`automatically detected module type '${type}' in ${cds.root}`)
|
|
64
|
+
this.values.targetModuleType = type
|
|
65
|
+
} else {
|
|
66
|
+
LOG.warn(`target module type was set to 'auto', but could not detect module type in ${cds.root}. Falling back to cjs`)
|
|
67
|
+
this.values.targetModuleType = 'cjs'
|
|
68
|
+
}
|
|
69
|
+
}
|
|
47
70
|
}
|
|
48
71
|
|
|
49
72
|
constructor() {
|
package/lib/file.js
CHANGED
|
@@ -7,8 +7,9 @@ const { printEnum, propertyToInlineEnumName, stringifyEnumImplementation } = req
|
|
|
7
7
|
const { normalise } = require('./components/identifier')
|
|
8
8
|
const { empty } = require('./components/typescript')
|
|
9
9
|
const { proxyAccessFunction } = require('./components/javascript')
|
|
10
|
-
const { createObjectOf } = require('./
|
|
10
|
+
const { createObjectOf, stringIdent } = require('./printers/wrappers')
|
|
11
11
|
const { configuration } = require('./config')
|
|
12
|
+
const { CJSPrinter, ESMPrinter } = require('./printers/javascript')
|
|
12
13
|
|
|
13
14
|
const AUTO_GEN_NOTE = '// This is an automatically generated file. Please do not change its contents manually!'
|
|
14
15
|
|
|
@@ -107,6 +108,9 @@ class Library extends File {
|
|
|
107
108
|
* Source file containing several buffers.
|
|
108
109
|
*/
|
|
109
110
|
class SourceFile extends File {
|
|
111
|
+
/** @type {import('../lib/printers/javascript').Printer | undefined} */
|
|
112
|
+
#jsPrinter
|
|
113
|
+
|
|
110
114
|
/**
|
|
111
115
|
* @param {string | Path} path - path to the file
|
|
112
116
|
*/
|
|
@@ -146,6 +150,12 @@ class SourceFile extends File {
|
|
|
146
150
|
this.entityProxies = {}
|
|
147
151
|
}
|
|
148
152
|
|
|
153
|
+
get jsPrinter () {
|
|
154
|
+
return this.#jsPrinter ??= configuration.targetModuleType === 'esm'
|
|
155
|
+
? new ESMPrinter()
|
|
156
|
+
: new CJSPrinter()
|
|
157
|
+
}
|
|
158
|
+
|
|
149
159
|
/**
|
|
150
160
|
* Stringifies a lambda expression.
|
|
151
161
|
* @param {object} options - options
|
|
@@ -428,7 +438,7 @@ class SourceFile extends File {
|
|
|
428
438
|
].filter(Boolean).join('\n')
|
|
429
439
|
}
|
|
430
440
|
#getEntityProxyFunctionExport() {
|
|
431
|
-
return
|
|
441
|
+
return this.jsPrinter.printExport('createEntityProxy', proxyAccessFunction)
|
|
432
442
|
}
|
|
433
443
|
/**
|
|
434
444
|
* Returns boilerplate code for `index.js` files
|
|
@@ -438,18 +448,23 @@ class SourceFile extends File {
|
|
|
438
448
|
*/
|
|
439
449
|
#getJSExportBoilerplate() {
|
|
440
450
|
const namespace = this.path.asNamespace()
|
|
441
|
-
|
|
451
|
+
const jsp = this.jsPrinter
|
|
442
452
|
const boilerplate = [AUTO_GEN_NOTE]
|
|
443
453
|
if (configuration.useEntitiesProxy) {
|
|
444
454
|
if (namespace === '_') {
|
|
445
|
-
boilerplate.push('
|
|
455
|
+
boilerplate.push(jsp.printImport('cds', '@sap/cds'), this.#getEntityProxyFunctionExport())
|
|
446
456
|
} else {
|
|
447
|
-
boilerplate.push(
|
|
457
|
+
boilerplate.push(
|
|
458
|
+
jsp.printDeconstructedImport(
|
|
459
|
+
['createEntityProxy'],
|
|
460
|
+
new Path(['_']).asDirectory({ relative: this.path.asDirectory() })
|
|
461
|
+
)
|
|
462
|
+
)
|
|
448
463
|
}
|
|
449
464
|
} else {
|
|
450
465
|
boilerplate.push(
|
|
451
|
-
'
|
|
452
|
-
|
|
466
|
+
jsp.printImport('cds', '@sap/cds'),
|
|
467
|
+
jsp.printConstant('csn', `cds.entities('${namespace}')`)
|
|
453
468
|
)
|
|
454
469
|
}
|
|
455
470
|
return boilerplate
|
|
@@ -480,18 +495,32 @@ class SourceFile extends File {
|
|
|
480
495
|
}
|
|
481
496
|
}
|
|
482
497
|
}
|
|
498
|
+
|
|
483
499
|
toJSExports() {
|
|
500
|
+
const jsp = this.jsPrinter
|
|
484
501
|
return this.#getJSExportBoilerplate() // boilerplate
|
|
485
502
|
.concat(
|
|
486
503
|
// FIXME: move stringification of service into own module
|
|
487
504
|
this.services.names.flatMap(name => {
|
|
488
|
-
const nameSimple = name.split('.').pop()
|
|
489
|
-
|
|
490
|
-
'
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
`module.exports.${nameSimple} = ${nameSimple}`
|
|
505
|
+
const nameSimple = /** @type {string} */(name.split('.').pop())
|
|
506
|
+
const service = [
|
|
507
|
+
jsp.printSingleLineComment('service'),
|
|
508
|
+
jsp.printConstant(nameSimple, `{ name: '${name}' }`),
|
|
509
|
+
jsp.printDefaultExport(nameSimple), // there should be only one and must be the first
|
|
494
510
|
]
|
|
511
|
+
if (jsp instanceof CJSPrinter) {
|
|
512
|
+
// unfortunately, we have to add a tiny bit of special logic for CJS, where we get:
|
|
513
|
+
//
|
|
514
|
+
// module.exports = FooService
|
|
515
|
+
// module.exports.FooService = FooService
|
|
516
|
+
//
|
|
517
|
+
// which, in ESM, would amount to:
|
|
518
|
+
//
|
|
519
|
+
// export const FooService
|
|
520
|
+
// default export FooService = FooService // <- duplicate identifier
|
|
521
|
+
service.push(jsp.printExport(nameSimple, nameSimple))
|
|
522
|
+
}
|
|
523
|
+
return service
|
|
495
524
|
})
|
|
496
525
|
)
|
|
497
526
|
.concat(this.inflections
|
|
@@ -502,10 +531,13 @@ class SourceFile extends File {
|
|
|
502
531
|
.flatMap(([singular, plural, original]) => {
|
|
503
532
|
const { singularRhs, pluralRhs } = this.#getEntityExportsRhs(singular, original)
|
|
504
533
|
|
|
505
|
-
const exports = [
|
|
534
|
+
const exports = [
|
|
535
|
+
jsp.printSingleLineComment(original),
|
|
536
|
+
jsp.printExport(singular, singularRhs)
|
|
537
|
+
]
|
|
506
538
|
if (!/Array<.*>/.test(plural) && plural !== original) {
|
|
507
539
|
// FIXME: this is a hack to support CDS types that will produce "Array<MyType>" as plural, which we do not want as export in the index.js files
|
|
508
|
-
exports.push(
|
|
540
|
+
exports.push(jsp.printExport(plural, pluralRhs))
|
|
509
541
|
}
|
|
510
542
|
// FIXME: we currently produce at most 3 entries.
|
|
511
543
|
// This could be an issue when the user re-used the original name in a @singular/@plural annotation.
|
|
@@ -513,16 +545,16 @@ class SourceFile extends File {
|
|
|
513
545
|
if (singular !== original) {
|
|
514
546
|
// do not do the is_singular spiel if the original name is used for the plural
|
|
515
547
|
const rhs = plural === original ? pluralRhs : singularRhs
|
|
516
|
-
exports.push(
|
|
548
|
+
exports.push(jsp.printExport(original, rhs))
|
|
517
549
|
}
|
|
518
550
|
return exports
|
|
519
551
|
})
|
|
520
552
|
) // singular -> plural aliases
|
|
521
|
-
.concat(
|
|
522
|
-
.concat(this.events.fqs.map(({fq, name}) =>
|
|
523
|
-
.concat(
|
|
524
|
-
.concat(this.operations.names.map(name =>
|
|
525
|
-
.concat(
|
|
553
|
+
.concat(jsp.printSingleLineComment('events'))
|
|
554
|
+
.concat(this.events.fqs.map(({fq, name}) => jsp.printExport(name, stringIdent(fq))))
|
|
555
|
+
.concat(jsp.printSingleLineComment('actions'))
|
|
556
|
+
.concat(this.operations.names.map(name => jsp.printExport(name, stringIdent(name))))
|
|
557
|
+
.concat(jsp.printSingleLineComment('enums'))
|
|
526
558
|
.concat(this.enums.data.map(({name, kvs}) => stringifyEnumImplementation(name, kvs)))
|
|
527
559
|
.join('\n') + '\n'
|
|
528
560
|
}
|
|
@@ -555,6 +587,15 @@ class Buffer {
|
|
|
555
587
|
this.closed = false
|
|
556
588
|
}
|
|
557
589
|
|
|
590
|
+
/**
|
|
591
|
+
* @returns {Buffer} an empty Buffer that starts at this Buffer's current indentation.
|
|
592
|
+
*/
|
|
593
|
+
createSubBuffer() {
|
|
594
|
+
const sub = new Buffer(this.indentation)
|
|
595
|
+
sub.currentIndent = this.currentIndent
|
|
596
|
+
return sub
|
|
597
|
+
}
|
|
598
|
+
|
|
558
599
|
/**
|
|
559
600
|
* Indents by the predefined spacing.
|
|
560
601
|
*/
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
class JavaScriptPrinter {
|
|
2
|
+
/**
|
|
3
|
+
* @param {string} text - comment text
|
|
4
|
+
*/
|
|
5
|
+
printSingleLineComment (text) {
|
|
6
|
+
return `// ${text}`
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} name - name of the constant
|
|
11
|
+
* @param {string} value - initial assignment to the constant
|
|
12
|
+
*/
|
|
13
|
+
printConstant (name, value) {
|
|
14
|
+
return `const ${name} = ${value}`
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @abstract
|
|
19
|
+
* @param {string} alias - what the import should be known as within the importing file
|
|
20
|
+
* @param {string} from - the package/ location to import from
|
|
21
|
+
* @returns {string}
|
|
22
|
+
*/
|
|
23
|
+
// eslint-disable-next-line no-unused-vars
|
|
24
|
+
printImport (alias, from) {
|
|
25
|
+
throw Error('not implemented')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @abstract
|
|
30
|
+
* @param {string[]} imports - the deconstructed elements
|
|
31
|
+
* @param {string} from - the package/ location to import from
|
|
32
|
+
* @returns {string}
|
|
33
|
+
*/
|
|
34
|
+
// eslint-disable-next-line no-unused-vars
|
|
35
|
+
printDeconstructedImport (imports, from) {
|
|
36
|
+
throw Error('not implemented')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @abstract
|
|
41
|
+
* @param {string} name - name the export should be known as
|
|
42
|
+
* @param {string} value - the value of the export
|
|
43
|
+
* @returns {string}
|
|
44
|
+
*/
|
|
45
|
+
// eslint-disable-next-line no-unused-vars
|
|
46
|
+
printExport (name, value) {
|
|
47
|
+
throw Error('not implemented')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @abstract
|
|
52
|
+
* @param {string} value - the value of the default export
|
|
53
|
+
* @returns {string}
|
|
54
|
+
*/
|
|
55
|
+
// eslint-disable-next-line no-unused-vars
|
|
56
|
+
printDefaultExport (value) {
|
|
57
|
+
throw Error('not implemented')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @abstract
|
|
62
|
+
* @param {string} file - the file name without extension
|
|
63
|
+
* @returns {string}
|
|
64
|
+
*/
|
|
65
|
+
// eslint-disable-next-line no-unused-vars
|
|
66
|
+
nameFile (file) {
|
|
67
|
+
throw Error('not implemented')
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class ESMPrinter extends JavaScriptPrinter {
|
|
72
|
+
/** @type {JavaScriptPrinter['printImport']} */
|
|
73
|
+
printImport (alias, from) {
|
|
74
|
+
return `import * as ${alias} from '${from}'`
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** @type {JavaScriptPrinter['printDeconstructedImport']} */
|
|
78
|
+
printDeconstructedImport (imports, from) {
|
|
79
|
+
return `import { ${imports.join(', ')} } from '${from}'`
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** @type {JavaScriptPrinter['printExport']} */
|
|
83
|
+
printExport (name, value) {
|
|
84
|
+
return `export const ${name} = ${value}`
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** @type {JavaScriptPrinter['printDefaultExport']} */
|
|
88
|
+
printDefaultExport (value) {
|
|
89
|
+
return `export default ${value}`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** @type {JavaScriptPrinter['nameFile']} */
|
|
93
|
+
nameFile (file) {
|
|
94
|
+
return `${file}.mjs`
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
class CJSPrinter extends JavaScriptPrinter {
|
|
99
|
+
/** @type {JavaScriptPrinter['printImport']} */
|
|
100
|
+
printImport (alias, from) {
|
|
101
|
+
return `const ${alias} = require('${from}')`
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** @type {JavaScriptPrinter['printDeconstructedImport']} */
|
|
105
|
+
printDeconstructedImport (imports, from) {
|
|
106
|
+
return `const { ${imports.join(', ')} } = require('${from}')`
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** @type {JavaScriptPrinter['printExport']} */
|
|
110
|
+
printExport (name, value) {
|
|
111
|
+
return `module.exports.${name} = ${value}`
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** @type {JavaScriptPrinter['printDefaultExport']} */
|
|
115
|
+
printDefaultExport (value) {
|
|
116
|
+
// not exactly "default", but you catch the drift...
|
|
117
|
+
return `module.exports = ${value}`
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** @type {JavaScriptPrinter['nameFile']} */
|
|
121
|
+
nameFile (file) {
|
|
122
|
+
return `${file}.js`
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** @typedef {JavaScriptPrinter} Printer */
|
|
127
|
+
|
|
128
|
+
module.exports = {
|
|
129
|
+
CJSPrinter,
|
|
130
|
+
ESMPrinter
|
|
131
|
+
}
|
|
@@ -87,6 +87,13 @@ const createObjectOf = t => `{${t}}`
|
|
|
87
87
|
*/
|
|
88
88
|
const createUnionOf = (...types) => types.join(' | ')
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Wraps types into a intersection type string
|
|
92
|
+
* @param {string[]} types - an array of types
|
|
93
|
+
* @returns {string}
|
|
94
|
+
*/
|
|
95
|
+
const createIntersectionOf = (...types) => types.join(' & ')
|
|
96
|
+
|
|
90
97
|
/**
|
|
91
98
|
* Wraps type into a promise
|
|
92
99
|
* @param {string} t - the type to wrap.
|
|
@@ -136,6 +143,7 @@ module.exports = {
|
|
|
136
143
|
createObjectOf,
|
|
137
144
|
createPromiseOf,
|
|
138
145
|
createUnionOf,
|
|
146
|
+
createIntersectionOf,
|
|
139
147
|
createToOneAssociation,
|
|
140
148
|
createToManyAssociation,
|
|
141
149
|
createCompositionOfOne,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const util = require('../util')
|
|
4
4
|
// eslint-disable-next-line no-unused-vars
|
|
5
5
|
const { Buffer, SourceFile, Path, Library } = require('../file')
|
|
6
|
-
const { deepRequire, createToManyAssociation, createToOneAssociation, createArrayOf, createCompositionOfMany, createCompositionOfOne, createKey } = require('../
|
|
6
|
+
const { deepRequire, createToManyAssociation, createToOneAssociation, createArrayOf, createCompositionOfMany, createCompositionOfOne, createKey } = require('../printers/wrappers')
|
|
7
7
|
const { StructuredInlineDeclarationResolver } = require('../components/inline')
|
|
8
8
|
const { isInlineEnumType, propertyToInlineEnumName } = require('../components/enum')
|
|
9
9
|
const { isReferenceType } = require('../components/reference')
|
package/lib/typedefs.d.ts
CHANGED
package/lib/util.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/** @typedef { import('./typedefs').util.Annotations} Annotations */
|
|
2
|
-
/** @typedef { import('./typedefs').util.CommandLineFlags } CommandlineFlag */
|
|
3
|
-
/** @typedef { import('./typedefs').util.ParsedFlag } ParsedFlags */
|
|
4
2
|
/** @typedef { import('./typedefs').resolver.EntityCSN } EntityCSN */
|
|
5
3
|
|
|
4
|
+
const fs = require('node:fs')
|
|
5
|
+
const path = require('node:path')
|
|
6
|
+
|
|
6
7
|
// inflection functions are stolen from github/cap/dev/blob/main/etc/inflect.js
|
|
7
8
|
|
|
8
9
|
// MONKEY PATCH to support Node v14
|
|
@@ -108,7 +109,7 @@ const singular4 = (dn, stripped = false) => {
|
|
|
108
109
|
const plural4 = (dn, stripped) => {
|
|
109
110
|
let n = dn.name ?? dn
|
|
110
111
|
if (stripped) {
|
|
111
|
-
n = n.match(last)?.[0]
|
|
112
|
+
n = /** @type {string} */(n.match(last)?.[0])
|
|
112
113
|
}
|
|
113
114
|
return (
|
|
114
115
|
getPluralAnnotation(dn) ??
|
|
@@ -125,8 +126,8 @@ const plural4 = (dn, stripped) => {
|
|
|
125
126
|
/**
|
|
126
127
|
* Performs a deep merge of the passed objects into the first object.
|
|
127
128
|
* See Object.assign(target, source).
|
|
128
|
-
* @param {
|
|
129
|
-
* @param {
|
|
129
|
+
* @param {{[key: string]: any}} target - object to assign into.
|
|
130
|
+
* @param {{[key: string]: any}} source - object to assign from.
|
|
130
131
|
*/
|
|
131
132
|
const deepMerge = (target, source) => {
|
|
132
133
|
Object.keys(target)
|
|
@@ -135,11 +136,40 @@ const deepMerge = (target, source) => {
|
|
|
135
136
|
Object.assign(target, source)
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Tries to determine the project's target type (ESM or CJS).
|
|
141
|
+
* @param {string} root - the root of the project
|
|
142
|
+
* @returns {'esm' | 'cjs' | undefined}
|
|
143
|
+
*/
|
|
144
|
+
const getProjectTargetType = root => {
|
|
145
|
+
const isFsRoot = (/** @type {string} **/ dir) => dir === path.dirname(dir)
|
|
146
|
+
let dir = root
|
|
147
|
+
let packageJson
|
|
148
|
+
|
|
149
|
+
do {
|
|
150
|
+
packageJson = fs.readdirSync(dir).find(f => f.endsWith('package.json'))
|
|
151
|
+
if (packageJson) {
|
|
152
|
+
packageJson = path.join(dir, packageJson)
|
|
153
|
+
}
|
|
154
|
+
dir = path.dirname(dir)
|
|
155
|
+
} while (!packageJson && !isFsRoot(dir))
|
|
156
|
+
|
|
157
|
+
if (packageJson) {
|
|
158
|
+
const json = JSON.parse(fs.readFileSync(packageJson, 'utf-8'))
|
|
159
|
+
return json.type === 'module'
|
|
160
|
+
? 'esm'
|
|
161
|
+
: 'cjs'
|
|
162
|
+
}
|
|
163
|
+
// no package.json found -> inconclusive
|
|
164
|
+
return undefined
|
|
165
|
+
}
|
|
166
|
+
|
|
138
167
|
module.exports = {
|
|
139
168
|
annotations,
|
|
140
169
|
camelToSnake,
|
|
141
170
|
getSingularAnnotation,
|
|
142
171
|
getPluralAnnotation,
|
|
172
|
+
getProjectTargetType,
|
|
143
173
|
unlocalize,
|
|
144
174
|
singular4,
|
|
145
175
|
plural4,
|
package/lib/visitor.js
CHANGED
|
@@ -8,7 +8,7 @@ const { SourceFile, FileRepository, Buffer, Path } = require('./file')
|
|
|
8
8
|
const { FlatInlineDeclarationResolver, StructuredInlineDeclarationResolver } = require('./components/inline')
|
|
9
9
|
const { Resolver } = require('./resolution/resolver')
|
|
10
10
|
const { LOG } = require('./logging')
|
|
11
|
-
const { docify, createPromiseOf, createUnionOf, createKeysOf, createElementsOf, stringIdent, createDraftsOf, createDraftOf } = require('./
|
|
11
|
+
const { docify, createPromiseOf, createUnionOf, createKeysOf, createElementsOf, stringIdent, createDraftsOf, createDraftOf, createIntersectionOf } = require('./printers/wrappers')
|
|
12
12
|
const { csnToEnumPairs, propertyToInlineEnumName, isInlineEnumType, stringifyEnumType } = require('./components/enum')
|
|
13
13
|
const { isReferenceType } = require('./components/reference')
|
|
14
14
|
const { empty } = require('./components/typescript')
|
|
@@ -138,11 +138,11 @@ class Visitor {
|
|
|
138
138
|
#printStaticActions(entity, buffer, ancestors, file) {
|
|
139
139
|
// TODO: refactor away! All these printing functionalities need to go
|
|
140
140
|
const actions = Object.entries(entity.actions ?? {})
|
|
141
|
-
const inherited = ancestors.
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
const inherited = ancestors.map(a => `typeof ${asIdentifier({info: a, relative: file.path})}.actions`)
|
|
142
|
+
|
|
143
|
+
const typeBuffer = buffer.createSubBuffer()
|
|
144
144
|
if (actions.length) {
|
|
145
|
-
|
|
145
|
+
typeBuffer.addIndentedBlock(createIntersectionOf(...inherited, '{'),
|
|
146
146
|
() => {
|
|
147
147
|
for (const [aname, action] of actions) {
|
|
148
148
|
const [opener, content, closer] = SourceFile.stringifyLambda({
|
|
@@ -153,28 +153,34 @@ class Visitor {
|
|
|
153
153
|
: 'any',
|
|
154
154
|
kind: action.kind,
|
|
155
155
|
doc: docify(action.doc)})
|
|
156
|
-
|
|
156
|
+
typeBuffer.addIndentedBlock(opener, content, closer)
|
|
157
157
|
}
|
|
158
|
-
}, '}'
|
|
159
|
-
) // end of actions
|
|
158
|
+
}, '}')
|
|
160
159
|
} else {
|
|
161
|
-
|
|
162
|
-
name: 'actions',
|
|
163
|
-
type: `${inherited}${empty}`,
|
|
164
|
-
isStatic: true,
|
|
165
|
-
isReadonly: true
|
|
166
|
-
}))
|
|
160
|
+
typeBuffer.add(createIntersectionOf(...inherited, empty))
|
|
167
161
|
}
|
|
162
|
+
buffer.add(createMember({
|
|
163
|
+
name: 'actions',
|
|
164
|
+
type: typeBuffer.join().trimStart(), // remove leading whitespace from indentation, as type is printed in line
|
|
165
|
+
isStatic: true,
|
|
166
|
+
isReadonly: true,
|
|
167
|
+
isDeclare: true,
|
|
168
|
+
}))
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
/**
|
|
171
172
|
* @param {Buffer} buffer - the buffer to write the keys into
|
|
172
173
|
* @param {string} clean - the clean name of the entity
|
|
174
|
+
* @param {import('./typedefs').resolver.EntityInfo[]} ancestors - ancestors infos to include in they type
|
|
175
|
+
* @param {SourceFile} file - the file the entity is being printed into
|
|
173
176
|
*/
|
|
174
|
-
#printStaticKeys(buffer, clean) {
|
|
177
|
+
#printStaticKeys(buffer, clean, ancestors, file) {
|
|
178
|
+
const ancestorKeys = ancestors
|
|
179
|
+
.filter(a => Object.entries(a.csn.keys ?? {}).length)
|
|
180
|
+
.map(a => `typeof ${asIdentifier({info: a, relative: file.path})}.keys`)
|
|
175
181
|
buffer.add(createMember({
|
|
176
182
|
name: 'keys',
|
|
177
|
-
type: createKeysOf(clean),
|
|
183
|
+
type: createIntersectionOf(createKeysOf(clean), ...ancestorKeys),
|
|
178
184
|
isDeclare: true,
|
|
179
185
|
isStatic: true,
|
|
180
186
|
isReadonly: true,
|
|
@@ -312,15 +318,15 @@ class Visitor {
|
|
|
312
318
|
if ('kind' in entity) {
|
|
313
319
|
buffer.add(createMember({
|
|
314
320
|
name: 'kind',
|
|
315
|
-
type: '
|
|
321
|
+
type: createUnionOf(...['entity', 'type', 'aspect'].map(stringIdent)),
|
|
316
322
|
isStatic: true,
|
|
317
323
|
isReadonly: true,
|
|
318
324
|
isDeclare: false,
|
|
319
|
-
isOverride: ancestorInfos.some(
|
|
325
|
+
isOverride: ancestorInfos.some(({csn}) => csn.kind),
|
|
320
326
|
initialiser: stringIdent(entity.kind)
|
|
321
327
|
}))
|
|
322
328
|
}
|
|
323
|
-
this.#printStaticKeys(buffer, clean)
|
|
329
|
+
this.#printStaticKeys(buffer, clean, ancestorInfos, file)
|
|
324
330
|
this.#printStaticElements(buffer, clean)
|
|
325
331
|
this.#printStaticActions(entity, buffer, ancestorInfos, file)
|
|
326
332
|
}, '};') // end of generated class
|
|
@@ -657,4 +663,3 @@ class Visitor {
|
|
|
657
663
|
module.exports = {
|
|
658
664
|
Visitor
|
|
659
665
|
}
|
|
660
|
-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/cds-typer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.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",
|
|
@@ -63,6 +63,13 @@
|
|
|
63
63
|
]
|
|
64
64
|
},
|
|
65
65
|
"cds": {
|
|
66
|
+
"typer": {
|
|
67
|
+
"output_directory": "@cds-models",
|
|
68
|
+
"inline_declarations": "flat",
|
|
69
|
+
"target_module_type": "auto",
|
|
70
|
+
"properties_optional": true,
|
|
71
|
+
"use_entities_proxy": false
|
|
72
|
+
},
|
|
66
73
|
"schema": {
|
|
67
74
|
"buildTaskType": {
|
|
68
75
|
"name": "typescript",
|
|
@@ -123,6 +130,16 @@
|
|
|
123
130
|
"type": "boolean",
|
|
124
131
|
"description": "If set to true, floating point properties are generated\nas IEEE754 compatible '(number | string)' instead of 'number'.",
|
|
125
132
|
"default": false
|
|
133
|
+
},
|
|
134
|
+
"target_module_type": {
|
|
135
|
+
"type": "string",
|
|
136
|
+
"description": "Output format for generated .js files.\nSetting it to auto tries to derive the module type from\nthe package.json and falls back to CJS.",
|
|
137
|
+
"enum": [
|
|
138
|
+
"esm",
|
|
139
|
+
"cjs",
|
|
140
|
+
"auto"
|
|
141
|
+
],
|
|
142
|
+
"default": "auto"
|
|
126
143
|
}
|
|
127
144
|
}
|
|
128
145
|
}
|