@cap-js/cds-typer 0.28.0 → 0.29.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 +13 -1
- package/cds-plugin.js +3 -3
- package/lib/cli.js +6 -1
- package/lib/components/inline.js +1 -1
- package/lib/config.js +14 -2
- package/lib/file.js +58 -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 +27 -20
- package/package.json +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,19 @@ 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.30.0 - TBD
|
|
8
|
+
|
|
9
|
+
## Version 0.29.0 - 2024-11-20
|
|
10
|
+
### Added
|
|
11
|
+
- [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!_
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- The static `.keys` property now properly reels in key types from inherited classes.
|
|
15
|
+
|
|
16
|
+
## Version 0.28.1 - 2024-11-07
|
|
17
|
+
### Fixed
|
|
18
|
+
- `cds build` no longer fails on Windows with an `EINVAL` error.
|
|
19
|
+
- `cds build` also supports custom model paths in `tsconfig.json` that do not end with `/index.ts`. This is the case for projects running with `tsx`.
|
|
8
20
|
|
|
9
21
|
## Version 0.28.0 - 24-10-24
|
|
10
22
|
### Added
|
package/cds-plugin.js
CHANGED
|
@@ -75,12 +75,12 @@ cds.build?.register?.('typescript', class extends cds.build.Plugin {
|
|
|
75
75
|
const outputDirectory = cds.env.typer?.outputDirectory
|
|
76
76
|
if (outputDirectory) return outputDirectory
|
|
77
77
|
try {
|
|
78
|
-
// expected format: { '#cds-models/*': [ './@cds-models
|
|
78
|
+
// expected format: { '#cds-models/*': [ './@cds-models/*' ] }
|
|
79
79
|
// ^^^^^^^^^^^
|
|
80
80
|
// relevant part - may be changed by user
|
|
81
81
|
const config = JSON.parse(fs.readFileSync ('tsconfig.json', 'utf8'))
|
|
82
82
|
const alias = config.compilerOptions.paths['#cds-models/*'][0]
|
|
83
|
-
const directory = alias.match(/(?:\.\/)?(.*)
|
|
83
|
+
const directory = alias.match(/(?:\.\/)?(.*)\/\*/)[1]
|
|
84
84
|
return normalize(directory) // could contain forward slashes in tsconfig.json
|
|
85
85
|
} catch {
|
|
86
86
|
DEBUG?.('tsconfig.json not found, not parsable, or inconclusive. Using default model directory name')
|
|
@@ -110,7 +110,7 @@ cds.build?.register?.('typescript', class extends cds.build.Plugin {
|
|
|
110
110
|
DEBUG?.('building without config')
|
|
111
111
|
// this will include gen/ that was created by the nodejs task
|
|
112
112
|
// _within_ the project directory. So we need to remove it afterwards.
|
|
113
|
-
await exec(`npx tsc --outDir "${this.task.dest}"`)
|
|
113
|
+
await exec(`npx tsc --outDir "${this.task.dest.replace(/\\/g, '/')}"`) // see https://github.com/cap-js/cds-typer/issues/374
|
|
114
114
|
rmDirIfExists(path.join(this.task.dest, cds.env.build.target))
|
|
115
115
|
rmDirIfExists(path.join(this.task.dest, this.#appFolder))
|
|
116
116
|
}
|
package/lib/cli.js
CHANGED
|
@@ -190,7 +190,12 @@ const flags = enrichFlagSchema({
|
|
|
190
190
|
IEEE754Compatible: parameterTypes.boolean({
|
|
191
191
|
desc: `If set to true, floating point properties are generated${EOL}as IEEE754 compatible '(number | string)' instead of 'number'.`,
|
|
192
192
|
default: 'false'
|
|
193
|
-
})
|
|
193
|
+
}),
|
|
194
|
+
targetModuleType: {
|
|
195
|
+
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.`,
|
|
196
|
+
allowed: ['esm', 'cjs', 'auto'],
|
|
197
|
+
default: 'auto'
|
|
198
|
+
}
|
|
194
199
|
})
|
|
195
200
|
|
|
196
201
|
const hint = () => 'Missing or invalid parameter(s). Call with --help for more details.'
|
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,6 @@
|
|
|
1
1
|
const cds = require('@sap/cds')
|
|
2
|
-
const { camelToSnake } = require('./util')
|
|
2
|
+
const { camelToSnake, getProjectTargetType } = require('./util')
|
|
3
|
+
const { LOG } = require('./logging')
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Makes properties of an object accessible in both camelCase and snake_case.
|
|
@@ -35,7 +36,8 @@ class Config {
|
|
|
35
36
|
propertiesOptional: true,
|
|
36
37
|
useEntitiesProxy: false,
|
|
37
38
|
inlineDeclarations: 'flat',
|
|
38
|
-
outputDirectory: '.'
|
|
39
|
+
outputDirectory: '.',
|
|
40
|
+
targetModuleType: 'auto'
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
values = undefined
|
|
@@ -44,6 +46,16 @@ class Config {
|
|
|
44
46
|
init () {
|
|
45
47
|
this.values = {...Config.#defaults, ...(cds.env.typer ?? {})}
|
|
46
48
|
this.proxy = camelSnakeHybrid(this.values)
|
|
49
|
+
if (this.values.targetModuleType === 'auto') {
|
|
50
|
+
const type = getProjectTargetType(cds.root)
|
|
51
|
+
if (type) {
|
|
52
|
+
LOG.info(`automatically detected module type '${type}' in ${cds.root}`)
|
|
53
|
+
this.values.targetModuleType = type
|
|
54
|
+
} else {
|
|
55
|
+
LOG.warn(`target module type was set to 'auto', but could not detect module type in ${cds.root}. Falling back to cjs`)
|
|
56
|
+
this.values.targetModuleType = 'cjs'
|
|
57
|
+
}
|
|
58
|
+
}
|
|
47
59
|
}
|
|
48
60
|
|
|
49
61
|
constructor() {
|
package/lib/file.js
CHANGED
|
@@ -7,8 +7,10 @@ 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')
|
|
13
|
+
const { getProjectTargetType } = require('./util')
|
|
12
14
|
|
|
13
15
|
const AUTO_GEN_NOTE = '// This is an automatically generated file. Please do not change its contents manually!'
|
|
14
16
|
|
|
@@ -144,6 +146,10 @@ class SourceFile extends File {
|
|
|
144
146
|
this.services = { buffer: new Buffer(), names: [] }
|
|
145
147
|
/** @type {Record<string,string[]>} */
|
|
146
148
|
this.entityProxies = {}
|
|
149
|
+
/** @type {import('../lib/printers/javascript').Printer} */
|
|
150
|
+
this.jsPrinter = configuration.targetModuleType === 'esm'
|
|
151
|
+
? new ESMPrinter()
|
|
152
|
+
: new CJSPrinter()
|
|
147
153
|
}
|
|
148
154
|
|
|
149
155
|
/**
|
|
@@ -428,7 +434,7 @@ class SourceFile extends File {
|
|
|
428
434
|
].filter(Boolean).join('\n')
|
|
429
435
|
}
|
|
430
436
|
#getEntityProxyFunctionExport() {
|
|
431
|
-
return
|
|
437
|
+
return this.jsPrinter.printExport('createEntityProxy', proxyAccessFunction)
|
|
432
438
|
}
|
|
433
439
|
/**
|
|
434
440
|
* Returns boilerplate code for `index.js` files
|
|
@@ -438,18 +444,23 @@ class SourceFile extends File {
|
|
|
438
444
|
*/
|
|
439
445
|
#getJSExportBoilerplate() {
|
|
440
446
|
const namespace = this.path.asNamespace()
|
|
441
|
-
|
|
447
|
+
const jsp = this.jsPrinter
|
|
442
448
|
const boilerplate = [AUTO_GEN_NOTE]
|
|
443
449
|
if (configuration.useEntitiesProxy) {
|
|
444
450
|
if (namespace === '_') {
|
|
445
|
-
boilerplate.push('
|
|
451
|
+
boilerplate.push(jsp.printImport('cds', '@sap/cds'), this.#getEntityProxyFunctionExport())
|
|
446
452
|
} else {
|
|
447
|
-
boilerplate.push(
|
|
453
|
+
boilerplate.push(
|
|
454
|
+
jsp.printDeconstructedImport(
|
|
455
|
+
['createEntityProxy'],
|
|
456
|
+
new Path(['_']).asDirectory({ relative: this.path.asDirectory() })
|
|
457
|
+
)
|
|
458
|
+
)
|
|
448
459
|
}
|
|
449
460
|
} else {
|
|
450
461
|
boilerplate.push(
|
|
451
|
-
'
|
|
452
|
-
|
|
462
|
+
jsp.printImport('cds', '@sap/cds'),
|
|
463
|
+
jsp.printConstant('csn', `cds.entities('${namespace}')`)
|
|
453
464
|
)
|
|
454
465
|
}
|
|
455
466
|
return boilerplate
|
|
@@ -480,18 +491,32 @@ class SourceFile extends File {
|
|
|
480
491
|
}
|
|
481
492
|
}
|
|
482
493
|
}
|
|
494
|
+
|
|
483
495
|
toJSExports() {
|
|
496
|
+
const jsp = this.jsPrinter
|
|
484
497
|
return this.#getJSExportBoilerplate() // boilerplate
|
|
485
498
|
.concat(
|
|
486
499
|
// FIXME: move stringification of service into own module
|
|
487
500
|
this.services.names.flatMap(name => {
|
|
488
|
-
const nameSimple = name.split('.').pop()
|
|
489
|
-
|
|
490
|
-
'
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
`module.exports.${nameSimple} = ${nameSimple}`
|
|
501
|
+
const nameSimple = /** @type {string} */(name.split('.').pop())
|
|
502
|
+
const service = [
|
|
503
|
+
jsp.printSingleLineComment('service'),
|
|
504
|
+
jsp.printConstant(nameSimple, `{ name: '${name}' }`),
|
|
505
|
+
jsp.printDefaultExport(nameSimple), // there should be only one and must be the first
|
|
494
506
|
]
|
|
507
|
+
if (jsp instanceof CJSPrinter) {
|
|
508
|
+
// unfortunately, we have to add a tiny bit of special logic for CJS, where we get:
|
|
509
|
+
//
|
|
510
|
+
// module.exports = FooService
|
|
511
|
+
// module.exports.FooService = FooService
|
|
512
|
+
//
|
|
513
|
+
// which, in ESM, would amount to:
|
|
514
|
+
//
|
|
515
|
+
// export const FooService
|
|
516
|
+
// default export FooService = FooService // <- duplicate identifier
|
|
517
|
+
service.push(jsp.printExport(nameSimple, nameSimple))
|
|
518
|
+
}
|
|
519
|
+
return service
|
|
495
520
|
})
|
|
496
521
|
)
|
|
497
522
|
.concat(this.inflections
|
|
@@ -502,10 +527,13 @@ class SourceFile extends File {
|
|
|
502
527
|
.flatMap(([singular, plural, original]) => {
|
|
503
528
|
const { singularRhs, pluralRhs } = this.#getEntityExportsRhs(singular, original)
|
|
504
529
|
|
|
505
|
-
const exports = [
|
|
530
|
+
const exports = [
|
|
531
|
+
jsp.printSingleLineComment(original),
|
|
532
|
+
jsp.printExport(singular, singularRhs)
|
|
533
|
+
]
|
|
506
534
|
if (!/Array<.*>/.test(plural) && plural !== original) {
|
|
507
535
|
// 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(
|
|
536
|
+
exports.push(jsp.printExport(plural, pluralRhs))
|
|
509
537
|
}
|
|
510
538
|
// FIXME: we currently produce at most 3 entries.
|
|
511
539
|
// This could be an issue when the user re-used the original name in a @singular/@plural annotation.
|
|
@@ -513,16 +541,16 @@ class SourceFile extends File {
|
|
|
513
541
|
if (singular !== original) {
|
|
514
542
|
// do not do the is_singular spiel if the original name is used for the plural
|
|
515
543
|
const rhs = plural === original ? pluralRhs : singularRhs
|
|
516
|
-
exports.push(
|
|
544
|
+
exports.push(jsp.printExport(original, rhs))
|
|
517
545
|
}
|
|
518
546
|
return exports
|
|
519
547
|
})
|
|
520
548
|
) // singular -> plural aliases
|
|
521
|
-
.concat(
|
|
522
|
-
.concat(this.events.fqs.map(({fq, name}) =>
|
|
523
|
-
.concat(
|
|
524
|
-
.concat(this.operations.names.map(name =>
|
|
525
|
-
.concat(
|
|
549
|
+
.concat(jsp.printSingleLineComment('events'))
|
|
550
|
+
.concat(this.events.fqs.map(({fq, name}) => jsp.printExport(name, stringIdent(fq))))
|
|
551
|
+
.concat(jsp.printSingleLineComment('actions'))
|
|
552
|
+
.concat(this.operations.names.map(name => jsp.printExport(name, stringIdent(name))))
|
|
553
|
+
.concat(jsp.printSingleLineComment('enums'))
|
|
526
554
|
.concat(this.enums.data.map(({name, kvs}) => stringifyEnumImplementation(name, kvs)))
|
|
527
555
|
.join('\n') + '\n'
|
|
528
556
|
}
|
|
@@ -555,6 +583,15 @@ class Buffer {
|
|
|
555
583
|
this.closed = false
|
|
556
584
|
}
|
|
557
585
|
|
|
586
|
+
/**
|
|
587
|
+
* @returns {Buffer} an empty Buffer that starts at this Buffer's current indentation.
|
|
588
|
+
*/
|
|
589
|
+
createSubBuffer() {
|
|
590
|
+
const sub = new Buffer(this.indentation)
|
|
591
|
+
sub.currentIndent = this.currentIndent
|
|
592
|
+
return sub
|
|
593
|
+
}
|
|
594
|
+
|
|
558
595
|
/**
|
|
559
596
|
* Indents by the predefined spacing.
|
|
560
597
|
*/
|
|
@@ -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, createReturnTypeOf } = 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
|
|
@@ -486,6 +492,7 @@ class Visitor {
|
|
|
486
492
|
// TODO find a better way to detect ABAP RFC actions
|
|
487
493
|
const isRFC = Object.values(operation.params ?? {}).some(p => Object.keys(p).some(k => k.startsWith('@RFC')))
|
|
488
494
|
file.addOperation(last(fq), params, returns, kind, docify(operation.doc), {named: true, positional: !isRFC})
|
|
495
|
+
file.addImport(baseDefinitions.path)
|
|
489
496
|
}
|
|
490
497
|
|
|
491
498
|
/**
|
|
@@ -545,6 +552,7 @@ class Visitor {
|
|
|
545
552
|
}
|
|
546
553
|
configuration.propertiesOptional = propOpt
|
|
547
554
|
}, '}')
|
|
555
|
+
file.addImport(baseDefinitions.path)
|
|
548
556
|
}
|
|
549
557
|
|
|
550
558
|
/**
|
|
@@ -655,4 +663,3 @@ class Visitor {
|
|
|
655
663
|
module.exports = {
|
|
656
664
|
Visitor
|
|
657
665
|
}
|
|
658
|
-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/cds-typer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.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",
|
|
@@ -123,6 +123,16 @@
|
|
|
123
123
|
"type": "boolean",
|
|
124
124
|
"description": "If set to true, floating point properties are generated\nas IEEE754 compatible '(number | string)' instead of 'number'.",
|
|
125
125
|
"default": false
|
|
126
|
+
},
|
|
127
|
+
"target_module_type": {
|
|
128
|
+
"type": "string",
|
|
129
|
+
"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.",
|
|
130
|
+
"enum": [
|
|
131
|
+
"esm",
|
|
132
|
+
"cjs",
|
|
133
|
+
"auto"
|
|
134
|
+
],
|
|
135
|
+
"default": "auto"
|
|
126
136
|
}
|
|
127
137
|
}
|
|
128
138
|
}
|