@cap-js/cds-typer 0.3.0 → 0.5.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 -1
- package/README.md +6 -165
- package/lib/cli.js +32 -26
- package/lib/compile.js +5 -0
- package/lib/components/resolver.js +15 -10
- package/lib/file.js +24 -9
- package/lib/util.js +8 -10
- package/lib/visitor.js +5 -3
- package/package.json +12 -2
- package/lib/compile.d.ts +0 -273
- package/lib/file.d.ts +0 -208
- package/lib/logging.d.ts +0 -50
- package/lib/util.d.ts +0 -87
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +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.
|
|
7
|
+
## Version 0.5.1 - TBD
|
|
8
|
+
|
|
9
|
+
## Version 0.5.0 - 2023-07-25
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- Facilitate strict property checks. Note: `checkJs: true` must be present in the project's _jsconfig.json_ or _tsconfig.json_ respectively for this feature to become effective
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- Support for `array of` syntax
|
|
16
|
+
|
|
17
|
+
### Fixes
|
|
18
|
+
- Generate `string` type for date-related types in CDS definitions
|
|
19
|
+
- Generate `Buffer | string` type for the CDS type `LargeBinary`
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## Version 0.4.0 - 2023-07-06
|
|
23
|
+
### Added
|
|
24
|
+
- Support for enums when they are defined separately (not inline in the property type of an entity)
|
|
8
25
|
|
|
9
26
|
## Version 0.3.0 - 2023-06-26
|
|
10
27
|
### Added
|
package/README.md
CHANGED
|
@@ -1,172 +1,13 @@
|
|
|
1
1
|
# CDS type generator for JavaScript
|
|
2
2
|
|
|
3
|
+
[](https://api.reuse.software/info/github.com/cap-js/cds-typer)
|
|
4
|
+

|
|
5
|
+
|
|
3
6
|
## About this project
|
|
4
7
|
|
|
5
8
|
Generates `.ts` files for a CDS model to receive code completion in VS Code.
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
## Requirements and Setup
|
|
9
|
-
This project is [available as `@cds-js/cds-typer`](https://www.npmjs.com/package/@cap-js/cds-typer) as NPM package.
|
|
10
|
-
|
|
11
|
-
### Usage
|
|
12
|
-
The type generator can either be used as a standalone tool, or as part of of the [CDS VSCode-Extension](https://www.npmjs.com/package/@sap/vscode-cds).
|
|
13
|
-
|
|
14
|
-
#### Standalone CLI
|
|
15
|
-
Assuming you have the following CDS project structure:
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
/home/
|
|
19
|
-
├── mybookshop/
|
|
20
|
-
│ ├── db/
|
|
21
|
-
│ │ └── schema.cds
|
|
22
|
-
│ ├── srv/
|
|
23
|
-
│ │ └── service.js
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
a typical workflow to generate types for your CDS project could look something like this:
|
|
27
|
-
|
|
28
|
-
```sh
|
|
29
|
-
npx @cap-js/cds-typer /home/mybookshop/db/schema.cds --outputDirectory /home/mybookshop/@types
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
You would then end up with a directory `@types`, which contains your entities and their accompanying types in a directory structure. The directory structure directly reflects the namespaces you have defined your entities in. They have to be imported in any JavaScript-based service handlers you want to have type support in and can replace calls to `cds.entities(...)`:
|
|
33
|
-
|
|
34
|
-
```js
|
|
35
|
-
// srv/service.js
|
|
36
|
-
const { Books } = require('my.bookshop')
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
becomes
|
|
40
|
-
|
|
41
|
-
```js
|
|
42
|
-
// srv/service.js
|
|
43
|
-
const { Books } = require('../@types/mybookshop')
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
From that point on you should receive code completion from the type system for `Books`.
|
|
47
|
-
|
|
48
|
-
_cds-typer_ comes with rudimentary CLI support and a few command line options:
|
|
49
|
-
|
|
50
|
-
- `--help`: prints all available parameters.
|
|
51
|
-
- `--outputDirectory`: specifies the root directory where all generated files should be put. Defaults to the CWD.
|
|
52
|
-
- `--jsConfigPath`: specifies the path to the `jsconfig.json` file to generate. Usually your project's root directory. If specified, a config file is created that restricts the usage of types even further:
|
|
53
|
-
|
|
54
|
-
```js
|
|
55
|
-
// generated .ts file
|
|
56
|
-
class Book {
|
|
57
|
-
title: string;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// some hook in your service
|
|
61
|
-
SELECT(Books, b => {
|
|
62
|
-
b.title // 👍 no problem, property exists
|
|
63
|
-
b.numberOfPages // ❌ property does not exist
|
|
64
|
-
})
|
|
65
|
-
```
|
|
66
|
-
With the generated config in place, the language server will display an error, telling you that `numberOfPages` does not exist in this context. Without the config it would just infer it as `any`.
|
|
67
|
-
|
|
68
|
-
- `--loglevel`: minimum log level that should be printed. Defaults to `NONE`. Available log levels roughly follow [Microsoft's dotnet log levels](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=dotnet-plat-ext-6.0):
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
TRACE
|
|
72
|
-
DEBUG
|
|
73
|
-
INFO
|
|
74
|
-
WARNING
|
|
75
|
-
ERROR
|
|
76
|
-
CRITICAL
|
|
77
|
-
NONE
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
The utility expects (at least) one path to a `.cds` file as positional parameter which serves as entry point to the model in question, e.g.:
|
|
81
|
-
|
|
82
|
-
```sh
|
|
83
|
-
npx @cap-js/cds-typer ./path/to/my/model/model.cds --outputDirectory /tmp/
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Note that you can also pass multiple paths or `"*"` as glob pattern (with quotes to circumvent expansion by the shell). This passes the pattern on to the compiler where the [regular resolve strategy](https://cap.cloud.sap/docs/node.js/cds-compile?q=compiler#cds-resolve) is used.
|
|
87
|
-
|
|
88
|
-
#### From VSCode
|
|
89
|
-
Installing the [CDS VSCode Extension](https://www.npmjs.com/package/@sap/vscode-cds) also adds support for generating types for your model from within VSCode. Adding the appropriate facet to your project via `cds add typer` (and installing the added dependencies thereafter) allows you to simply hit save on any `.cds` file that is part of your model to trigger the generation process.
|
|
90
|
-
#### Programmatically
|
|
91
|
-
The main API for using _cds-typer_ within another project is contained in [`compile.js`](https://github.tools.sap/cap/cds-typer/blob/master/lib/compile.js), specifically:
|
|
92
|
-
|
|
93
|
-
- `compileFromFile(…)` to parse a `.cds` file. This involves compiling it to CSN first.
|
|
94
|
-
- `compileFromCSN(…)` to directly compile from CSN object. This is useful when you already have your CSN available as part of a tool chain. ⚠️ **WARNING**: the application of `cdstyper` may be impure, meaning that it _could_ alter the provided CSN. If you use the typer this way, you may want to apply it as last step of your tool chain.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
### Features
|
|
98
|
-
#### Plural Types
|
|
99
|
-
While CDS encourages the use of plural form for defined entities, their OOP equivalent classes are usually named in singular. _cds-typer_ automatically transforms entity names to singular and adds the plural form for arrays:
|
|
100
|
-
|
|
101
|
-
```cds
|
|
102
|
-
entity Books : cuid {
|
|
103
|
-
…
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
becomes
|
|
108
|
-
|
|
109
|
-
```ts
|
|
110
|
-
class Book {
|
|
111
|
-
…
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
class Books extends Array<Book> {}
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
If you need to customise the singular or plural form, or if your entities are already in singular form, you can do so using annotations:
|
|
118
|
-
|
|
119
|
-
```cds
|
|
120
|
-
@singular: 'Mouse'
|
|
121
|
-
entity Mice {}
|
|
122
|
-
|
|
123
|
-
@plural: 'SomeListList'
|
|
124
|
-
entity SomeList {}
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
results in
|
|
128
|
-
|
|
129
|
-
```ts
|
|
130
|
-
class Mouse { … }
|
|
131
|
-
class Mice extends Array<Mouse> { … }
|
|
132
|
-
|
|
133
|
-
class SomeList { … }
|
|
134
|
-
class SomeListList extends Array<SomeList> { … }
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### Relation to _cds2types_
|
|
138
|
-
This project is inspired by the existing [_cds2types_](https://github.com/mrbandler/cds2types), but differs in a few aspects:
|
|
139
|
-
|
|
140
|
-
#### Reworked Imports
|
|
141
|
-
Instead of one monolithic `.d.ts` file containing all entities in nested namespaces, multiple files are generated where each namespace is represented by a directory structure. This facilicates simpler imports in a more Java-esque style:
|
|
142
|
-
|
|
143
|
-
```js
|
|
144
|
-
const types = require('./cds2types/compiled.d.ts')
|
|
145
|
-
|
|
146
|
-
console.log(types.sap.cap.bookshop.Books) // a class
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
becomes
|
|
150
|
-
|
|
151
|
-
```js
|
|
152
|
-
const { Books } = require('./cds-typer/sap/cap/bookshop')
|
|
153
|
-
|
|
154
|
-
console.log(Books) // the same class
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
#### Usable in Javascript Projects
|
|
158
|
-
Generated code is usable from within plain Javascript projects. The code generated by _cds2types_ would represent each cds-entity as an interface, which are not visible to Javascript projects. _cds-typer_ uses classes instead.
|
|
159
|
-
|
|
160
|
-
#### Faster
|
|
161
|
-
_cds2types_ takes a detour to create a Typescript AST first and then print out the formatted source files. _cds-typer_ directly walks the linked CSN and creates strings on the fly. Also, file operations are `async`. These two changes speed up _cds-typer_ by around one to two orders of magnitude compared to _cds2types_.
|
|
162
|
-
|
|
163
|
-
#### Small Footprint
|
|
164
|
-
_cds-typer_ tries to keep its dependency footprint as small as possible. Libraries like `typescript` are only needed as dev dependencies.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
10
|
+
Exhaustive documentation can be found on [CAPire](https://cap.cloud.sap/docs/tools/cds-typer).
|
|
170
11
|
|
|
171
12
|
## Support, Feedback, Contributing
|
|
172
13
|
|
|
@@ -174,8 +15,8 @@ This project is open to feature requests/suggestions, bug reports etc. via [GitH
|
|
|
174
15
|
|
|
175
16
|
## Code of Conduct
|
|
176
17
|
|
|
177
|
-
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone. By participating in this project, you agree to abide by its [Code of Conduct](CODE_OF_CONDUCT.md) at all times.
|
|
18
|
+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone. By participating in this project, you agree to abide by its [Code of Conduct](https://github.com/cap-js/.github/blob/main/CODE_OF_CONDUCT.md) at all times.
|
|
178
19
|
|
|
179
20
|
## Licensing
|
|
180
21
|
|
|
181
|
-
Copyright 2022-2022 SAP SE or an SAP affiliate company and cds-
|
|
22
|
+
Copyright 2022-2022 SAP SE or an SAP affiliate company and cds-typer contributors. Please see our [LICENSE](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/SAP/cds-dts-generator).
|
package/lib/cli.js
CHANGED
|
@@ -6,80 +6,82 @@ const { compileFromFile } = require('./compile')
|
|
|
6
6
|
const { parseCommandlineArgs } = require('./util')
|
|
7
7
|
const { Levels } = require('./logging')
|
|
8
8
|
const path = require('path')
|
|
9
|
+
const { EOL } = require('node:os')
|
|
9
10
|
|
|
11
|
+
const EOL2 = EOL + EOL
|
|
10
12
|
const toolName = 'cds-typer'
|
|
11
13
|
|
|
12
14
|
const flags = {
|
|
13
|
-
// FIXME: remove asap
|
|
14
|
-
rootDir: {
|
|
15
|
-
desc: '[DEPRICATED] use outputDirectory instead',
|
|
16
|
-
default: './',
|
|
17
|
-
},
|
|
18
15
|
outputDirectory: {
|
|
19
|
-
desc: '
|
|
16
|
+
desc: 'Root directory to write the generated files to.',
|
|
20
17
|
default: './',
|
|
18
|
+
type: 'string'
|
|
21
19
|
},
|
|
22
20
|
help: {
|
|
23
|
-
desc: '
|
|
21
|
+
desc: 'This text.',
|
|
24
22
|
},
|
|
25
23
|
logLevel: {
|
|
26
|
-
desc: `
|
|
24
|
+
desc: `Minimum log level that is printed.`,
|
|
27
25
|
allowed: Object.keys(Levels),
|
|
28
26
|
default: Object.keys(Levels).at(-1),
|
|
29
27
|
},
|
|
30
28
|
jsConfigPath: {
|
|
31
|
-
desc: `Path to where the jsconfig.json should be written
|
|
29
|
+
desc: `Path to where the jsconfig.json should be written.${EOL}If specified, ${toolName} will create a jsconfig.json file and${EOL}set it up to restrict property usage in types entities to${EOL}existing properties only.`,
|
|
30
|
+
type: 'string'
|
|
32
31
|
},
|
|
33
32
|
version: {
|
|
34
|
-
desc: '
|
|
33
|
+
desc: 'Prints the version of this tool.'
|
|
35
34
|
},
|
|
36
35
|
inlineDeclarations: {
|
|
37
|
-
desc:
|
|
36
|
+
desc: `Whether to resolve inline type declarations${EOL}flat: (x_a, x_b, ...)${EOL}or structured: (x: {a, b}).`,
|
|
38
37
|
allowed: ['flat', 'structured'],
|
|
39
38
|
default: 'structured'
|
|
40
39
|
},
|
|
41
40
|
propertiesOptional: {
|
|
42
|
-
desc:
|
|
41
|
+
desc: `If set to true, properties in entities are${EOL}always generated as optional (a?: T).`,
|
|
43
42
|
allowed: ['true', 'false'],
|
|
44
43
|
default: 'true'
|
|
45
44
|
}
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
const hint = () => console.log('Missing or invalid parameter(s). Call with --help for more details.')
|
|
48
|
+
const indent = (s, indentation) => s.split(EOL).map(line => `${indentation}${line}`).join(EOL)
|
|
49
49
|
|
|
50
|
-
const help = () =>
|
|
51
|
-
|
|
52
|
-
'
|
|
53
|
-
|
|
54
|
-
'Additionaly, you can use the following parameters:\n' +
|
|
50
|
+
const help = () => `SYNOPSIS${EOL2}` +
|
|
51
|
+
indent(`cds-typer [cds file | "*"]`, ' ') + EOL2 +
|
|
52
|
+
indent(`Generates type information based on a CDS model.${EOL}Call with at least one positional parameter pointing${EOL}to the (root) CDS file you want to compile.`, ' ') + EOL2 +
|
|
53
|
+
`OPTIONS${EOL2}` +
|
|
55
54
|
Object.entries(flags)
|
|
56
55
|
.sort()
|
|
57
56
|
.map(([key, value]) => {
|
|
58
|
-
let s = `--${key}
|
|
57
|
+
let s = indent(`--${key}`, ' ')
|
|
59
58
|
if (value.allowed) {
|
|
60
|
-
s +=
|
|
59
|
+
s += `: <${value.allowed.join(' | ')}>`
|
|
60
|
+
} else if (value.type) {
|
|
61
|
+
s += `: <${value.type}>`
|
|
61
62
|
}
|
|
62
63
|
if (value.default) {
|
|
63
|
-
s +=
|
|
64
|
+
s += EOL
|
|
65
|
+
s += indent(`(default: ${value.default})`, ' ')
|
|
64
66
|
}
|
|
67
|
+
s += `${EOL2}${indent(value.desc, ' ')}`
|
|
65
68
|
return s
|
|
66
69
|
}
|
|
67
|
-
).join(
|
|
68
|
-
)
|
|
70
|
+
).join(EOL2)
|
|
69
71
|
|
|
70
|
-
const version = () =>
|
|
72
|
+
const version = () => require('../package.json').version
|
|
71
73
|
|
|
72
74
|
const main = async (args) => {
|
|
73
75
|
if ('help' in args.named) {
|
|
74
|
-
help()
|
|
76
|
+
console.log(help())
|
|
75
77
|
process.exit(0)
|
|
76
78
|
}
|
|
77
79
|
if ('version' in args.named) {
|
|
78
|
-
version()
|
|
80
|
+
console.log(version())
|
|
79
81
|
process.exit(0)
|
|
80
82
|
}
|
|
81
83
|
if (args.positional.length === 0) {
|
|
82
|
-
hint()
|
|
84
|
+
console.log(hint())
|
|
83
85
|
process.exit(1)
|
|
84
86
|
}
|
|
85
87
|
if (args.named.jsConfigPath && !args.named.jsConfigPath.endsWith('jsconfig.json')) {
|
|
@@ -98,3 +100,7 @@ const main = async (args) => {
|
|
|
98
100
|
if (require.main === module) {
|
|
99
101
|
main(parseCommandlineArgs(process.argv.slice(2), flags))
|
|
100
102
|
}
|
|
103
|
+
|
|
104
|
+
function helpToCapire() {
|
|
105
|
+
|
|
106
|
+
}
|
package/lib/compile.js
CHANGED
|
@@ -8,11 +8,16 @@ const { writeout } = require('./file')
|
|
|
8
8
|
const { Logger } = require('./logging')
|
|
9
9
|
const { Visitor } = require('./visitor')
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {import('./visitor').CompileParameters} CompileParameters
|
|
13
|
+
*/
|
|
14
|
+
|
|
11
15
|
/**
|
|
12
16
|
* Writes the accompanying jsconfig.json file to the specified paths.
|
|
13
17
|
* Tries to merge nicely if an existing file is found.
|
|
14
18
|
* @param path {string} filepath to jsconfig.json.
|
|
15
19
|
* @param logger {import('./logging').Logger} logger
|
|
20
|
+
* @private
|
|
16
21
|
*/
|
|
17
22
|
const writeJsConfig = (path, logger) => {
|
|
18
23
|
let values = {
|
|
@@ -40,7 +40,7 @@ const Builtins = {
|
|
|
40
40
|
String: 'string',
|
|
41
41
|
Binary: 'string',
|
|
42
42
|
LargeString: 'string',
|
|
43
|
-
LargeBinary: 'string',
|
|
43
|
+
LargeBinary: 'Buffer | string',
|
|
44
44
|
Integer: 'number',
|
|
45
45
|
UInt8: 'number',
|
|
46
46
|
Int16: 'number',
|
|
@@ -52,10 +52,11 @@ const Builtins = {
|
|
|
52
52
|
Float: 'number',
|
|
53
53
|
Double: 'number',
|
|
54
54
|
Boolean: 'boolean',
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
// note: the date-related types _can_ be Date in some cases, but let's start with string
|
|
56
|
+
Date: 'string', // yyyy-mm-dd
|
|
57
|
+
DateTime: 'string', // yyyy-mm-dd + time + TZ (precision: seconds
|
|
58
|
+
Time: 'string',
|
|
59
|
+
Timestamp: 'string', // yyy-mm-dd + time + TZ (ms precision)
|
|
59
60
|
//
|
|
60
61
|
Composition: 'Array',
|
|
61
62
|
Association: 'Array'
|
|
@@ -212,13 +213,14 @@ class Resolver {
|
|
|
212
213
|
{
|
|
213
214
|
Association: [createToOneAssociation, createToManyAssociation],
|
|
214
215
|
Composition: [createCompositionOfOne, createCompositionOfMany],
|
|
216
|
+
array: [createArrayOf, createArrayOf]
|
|
215
217
|
}[element.constructor.name] ?? []
|
|
216
218
|
|
|
217
219
|
if (toOne && toMany) {
|
|
218
|
-
const target = typeof element.target === 'string' ? { type: element.target } : element.target
|
|
220
|
+
const target = element.items ?? (typeof element.target === 'string' ? { type: element.target } : element.target)
|
|
219
221
|
const { singular, plural } = this.resolveAndRequire(target, file).typeInfo.inflection
|
|
220
222
|
typeName =
|
|
221
|
-
cardinality > 1 ? toMany(plural) : toOne(this.visitor.isSelfReference(
|
|
223
|
+
cardinality > 1 ? toMany(plural) : toOne(this.visitor.isSelfReference(target) ? 'this' : singular)
|
|
222
224
|
file.addImport(baseDefinitions.path)
|
|
223
225
|
}
|
|
224
226
|
} else {
|
|
@@ -258,8 +260,11 @@ class Resolver {
|
|
|
258
260
|
typeInfo.inflection = this.inflect(typeInfo)
|
|
259
261
|
}
|
|
260
262
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
+
// add fallback inflection. Mainly needed for array-of with builtin types.
|
|
264
|
+
// (array-of relies on inflection being present, which is not the case in builtin)
|
|
265
|
+
typeInfo.inflection ??= {
|
|
266
|
+
singular: typeName,
|
|
267
|
+
plural: typeName
|
|
263
268
|
}
|
|
264
269
|
|
|
265
270
|
// FIXME: typeName could probably just become part of typeInfo
|
|
@@ -346,8 +351,8 @@ class Resolver {
|
|
|
346
351
|
|
|
347
352
|
// objects and arrays
|
|
348
353
|
if (element?.items) {
|
|
349
|
-
// FIXME: builtin = true? arrays are kinda builtin
|
|
350
354
|
result.isArray = true
|
|
355
|
+
result.isBuiltin = true
|
|
351
356
|
this.resolveType(element.items, file)
|
|
352
357
|
//delete element.items
|
|
353
358
|
} else if (element?.elements && !element?.type) {
|
package/lib/file.js
CHANGED
|
@@ -101,11 +101,11 @@ class SourceFile extends File {
|
|
|
101
101
|
this.preamble = new Buffer()
|
|
102
102
|
/** @type {Buffer} */
|
|
103
103
|
this.types = new Buffer()
|
|
104
|
-
/** @type {Buffer} */
|
|
105
|
-
this.enums = new Buffer()
|
|
104
|
+
/** @type {{ buffer: Buffer, fqs: {name: string, fq: string}[]}} */
|
|
105
|
+
this.enums = { buffer: new Buffer(), fqs: [] }
|
|
106
106
|
/** @type {Buffer} */
|
|
107
107
|
this.classes = new Buffer()
|
|
108
|
-
/** @type {{ buffer: Buffer, names: string[]} */
|
|
108
|
+
/** @type {{ buffer: Buffer, names: string[]}} */
|
|
109
109
|
this.actions = { buffer: new Buffer(), names: [] }
|
|
110
110
|
/** @type {Buffer} */
|
|
111
111
|
this.aspects = new Buffer()
|
|
@@ -191,13 +191,26 @@ class SourceFile extends File {
|
|
|
191
191
|
* @param {[string, string][]} kvs list of key-value pairs
|
|
192
192
|
*/
|
|
193
193
|
addEnum(fq, name, kvs) {
|
|
194
|
-
|
|
195
|
-
|
|
194
|
+
// CDS differ from TS enums as they can use bools as value (TS: only number and string)
|
|
195
|
+
// So we have to emulate enums by adding an object (name -> value mappings)
|
|
196
|
+
// and a type containing all disctinct values.
|
|
197
|
+
// We can get away with this as TS doesn't feature nominal typing, so the structure
|
|
198
|
+
// is all we care about.
|
|
199
|
+
this.enums.fqs.push({ name, fq })
|
|
200
|
+
const bu = this.enums.buffer
|
|
201
|
+
bu.add('// enum')
|
|
202
|
+
bu.add(`export const ${name} = {`)
|
|
203
|
+
bu.indent()
|
|
204
|
+
const vals = new Set()
|
|
196
205
|
for (const [k, v] of kvs) {
|
|
197
|
-
|
|
206
|
+
bu.add(`${k}: ${v},`)
|
|
207
|
+
vals.add(v)
|
|
198
208
|
}
|
|
199
|
-
|
|
200
|
-
|
|
209
|
+
bu.outdent()
|
|
210
|
+
bu.add('}')
|
|
211
|
+
bu.add(`export type ${name} = ${[...vals].join(' | ')}`)
|
|
212
|
+
bu.add('')
|
|
213
|
+
|
|
201
214
|
}
|
|
202
215
|
|
|
203
216
|
/**
|
|
@@ -272,7 +285,7 @@ class SourceFile extends File {
|
|
|
272
285
|
this.getImports().join(),
|
|
273
286
|
this.preamble.join(),
|
|
274
287
|
this.types.join(),
|
|
275
|
-
this.enums.join(),
|
|
288
|
+
this.enums.buffer.join(),
|
|
276
289
|
namespaces.join(),
|
|
277
290
|
this.aspects.join(), // needs to be before classes
|
|
278
291
|
this.classes.join(),
|
|
@@ -300,6 +313,8 @@ class SourceFile extends File {
|
|
|
300
313
|
) // singular -> plural aliases
|
|
301
314
|
.concat(['// actions'])
|
|
302
315
|
.concat(this.actions.names.map(name => `module.exports.${name} = '${name}'`))
|
|
316
|
+
.concat(['// enums'])
|
|
317
|
+
.concat(this.enums.fqs.map(({fq, name}) => `module.exports.${name} = Object.fromEntries(Object.entries(cds.model.definitions['${fq}'].enum).map(([k,v]) => [k,v.val]))`))
|
|
303
318
|
.join('\n') + '\n'
|
|
304
319
|
}
|
|
305
320
|
}
|
package/lib/util.js
CHANGED
|
@@ -208,22 +208,20 @@ function fixCSN(csn) {
|
|
|
208
208
|
entity.erased ??= {}
|
|
209
209
|
entity.erased[attr] = ea
|
|
210
210
|
delete entity[attr]
|
|
211
|
-
this.logger.info(`Removing inherited attribute ${attr} from ${entity.name}.`)
|
|
211
|
+
//this.logger.info(`Removing inherited attribute ${attr} from ${entity.name}.`)
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
for (const entity of Object.values(csn.definitions)) {
|
|
217
217
|
let i = 0
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
i++
|
|
226
|
-
}
|
|
218
|
+
while (
|
|
219
|
+
(getSingularAnnotation(entity) || getPluralAnnotation(entity)) &&
|
|
220
|
+
i < (entity.includes ?? []).length
|
|
221
|
+
) {
|
|
222
|
+
const parent = csn.definitions[entity.includes[i]]
|
|
223
|
+
Object.values(annotations).flat().forEach(an => erase(entity, parent, an))
|
|
224
|
+
i++
|
|
227
225
|
}
|
|
228
226
|
}
|
|
229
227
|
}
|
package/lib/visitor.js
CHANGED
|
@@ -124,7 +124,7 @@ class Visitor {
|
|
|
124
124
|
})
|
|
125
125
|
|
|
126
126
|
// CLASS ASPECT
|
|
127
|
-
buffer.add(`export function ${identAspect(clean)}<TBase extends new (...args: any[]) =>
|
|
127
|
+
buffer.add(`export function ${identAspect(clean)}<TBase extends new (...args: any[]) => object>(Base: TBase) {`)
|
|
128
128
|
buffer.indent()
|
|
129
129
|
buffer.add(`return class ${clean} extends Base {`)
|
|
130
130
|
buffer.indent()
|
|
@@ -260,10 +260,12 @@ class Visitor {
|
|
|
260
260
|
const ns = this.resolver.resolveNamespace(name.split('.'))
|
|
261
261
|
const file = this.getNamespaceFile(ns)
|
|
262
262
|
if ('enum' in type) {
|
|
263
|
+
// in case of strings, wrap in quotes and fallback to key to make sure values are attached for every key
|
|
264
|
+
const val = (k,v) => type.type === 'cds.String' ? `"${v ?? k}"` : v
|
|
263
265
|
file.addEnum(
|
|
264
266
|
name,
|
|
265
267
|
clean,
|
|
266
|
-
Object.entries(type.enum).map(([k, v]) => [k, v.val])
|
|
268
|
+
Object.entries(type.enum).map(([k, v]) => [k, val(k, v.val)])
|
|
267
269
|
)
|
|
268
270
|
} else {
|
|
269
271
|
// alias
|
|
@@ -316,7 +318,7 @@ class Visitor {
|
|
|
316
318
|
/**
|
|
317
319
|
* A self reference is a property that references the class it appears in.
|
|
318
320
|
* They need to be detected on CDS level, as the emitted TS types will try to
|
|
319
|
-
* refer to
|
|
321
|
+
* refer to types via their alias that hides the aspectification.
|
|
320
322
|
* If we attempt to directly refer to this alias while it has not been fully created,
|
|
321
323
|
* that will result in a TS error.
|
|
322
324
|
* @param {String} entityName
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/cds-typer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Generates .ts files for a CDS model to receive code completion in VS Code",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"repository": "github:cap-js/cds-typer",
|
|
6
7
|
"homepage": "https://cap.cloud.sap/",
|
|
7
8
|
"keywords": [
|
|
8
9
|
"CAP",
|
|
@@ -17,7 +18,14 @@
|
|
|
17
18
|
"test:all": "jest",
|
|
18
19
|
"test": "npm run test:unit",
|
|
19
20
|
"lint": "eslint",
|
|
20
|
-
"cli": "node lib/cli.js"
|
|
21
|
+
"cli": "node lib/cli.js",
|
|
22
|
+
"doc:clean": "rm -rf ./doc",
|
|
23
|
+
"doc:prepare": "npm run doc:clean && mkdir -p doc/types",
|
|
24
|
+
"doc:typegen": "./node_modules/.bin/tsc ./lib/*.js --skipLibCheck --declaration --allowJs --emitDeclarationOnly --outDir doc/types && cd doc/types && tsc --init",
|
|
25
|
+
"doc:html": "npm run doc:typegen && ./node_modules/.bin/typedoc 'doc/types/**/*.d.ts' --entryPointStrategy expand --out doc/html --tsconfig doc/types/tsconfig.json",
|
|
26
|
+
"doc:md": "npm run doc:typegen && ./node_modules/.bin/typedoc --plugin typedoc-plugin-markdown 'doc/types/compile.d.ts' --out doc/md --tsconfig doc/types/tsconfig.json",
|
|
27
|
+
"doc:cli": "npm run cli -- --help > ./doc/cli.txt",
|
|
28
|
+
"doc:full": "npm run doc:prepare && npm run doc:html && npm run doc:cli"
|
|
21
29
|
},
|
|
22
30
|
"files": [
|
|
23
31
|
"lib/",
|
|
@@ -39,6 +47,8 @@
|
|
|
39
47
|
"eslint-config-prettier": "^8.5.0",
|
|
40
48
|
"eslint-plugin-prettier": "^4.0.0",
|
|
41
49
|
"jest": "^29",
|
|
50
|
+
"typedoc": "^0.24.8",
|
|
51
|
+
"typedoc-plugin-markdown": "^3.15.3",
|
|
42
52
|
"typescript": ">=4.6.4"
|
|
43
53
|
},
|
|
44
54
|
"jest": {
|
package/lib/compile.d.ts
DELETED
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import { Path, SourceFile} from './file'
|
|
2
|
-
import { Logger } from './logging';
|
|
3
|
-
|
|
4
|
-
// mock
|
|
5
|
-
interface CSN {
|
|
6
|
-
definitions?: { [key: string]: EntityCSN }
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
interface EntityCSN {
|
|
10
|
-
cardinality?: {
|
|
11
|
-
max?: '*' | number
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface TypeResolveInfo {
|
|
16
|
-
isBuiltin: boolean,
|
|
17
|
-
isInlineDefinition: boolean,
|
|
18
|
-
isForeignKeyReference: boolean,
|
|
19
|
-
type: string,
|
|
20
|
-
path?: Path,
|
|
21
|
-
csn?: CSN,
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* When nested inline types require additional imports. E.g.:
|
|
25
|
-
* // mymodel.cds
|
|
26
|
-
* Foo {
|
|
27
|
-
* bar: {
|
|
28
|
-
* baz: a.b.c.Baz // need to require a.b.c in mymodel.cds!
|
|
29
|
-
* }
|
|
30
|
-
* }
|
|
31
|
-
*/
|
|
32
|
-
imports: Path[]
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface CompileParameters {
|
|
36
|
-
rootDirectory: String,
|
|
37
|
-
logLevel: number,
|
|
38
|
-
jsConfigPath?: string
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
interface VisitorOptions {
|
|
42
|
-
// if set to true, _all_ properties are generated as optional ?:.
|
|
43
|
-
// This is the standard CAP behaviour, where any property could not be available/ not yet be constructed
|
|
44
|
-
// at any point
|
|
45
|
-
propertiesOptional: boolean
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
type VisitorParameters = { logger?: Logger, options?: VisitorOptions }
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Compiles a .cds file to Typescript types.
|
|
52
|
-
* @param inputFile path to input .cds file
|
|
53
|
-
* @param parameters path to root directory for all generated files, min log level
|
|
54
|
-
*/
|
|
55
|
-
export function compileFromFile(inputFile: string, parameters: CompileParameters): Promise<string[]>;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Compiles a CSN object to Typescript types.
|
|
59
|
-
* @param csn CSN
|
|
60
|
-
* @param parameters path to root directory for all generated files, min log level
|
|
61
|
-
*/
|
|
62
|
-
export function compileFromCSN(csn: CSN, parameters: CompileParameters): Promise<string[]>;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Writes the accompanying jsconfig.json file to the specified paths.
|
|
66
|
-
* Tries to merge nicely if an existing file is found.
|
|
67
|
-
* @param file filepath to jsconfig.json.
|
|
68
|
-
* @param logger logger
|
|
69
|
-
*/
|
|
70
|
-
export function writeJsConfig(file: string, logger: Logger);
|
|
71
|
-
|
|
72
|
-
export class Visitor {
|
|
73
|
-
/**
|
|
74
|
-
* @param csn root CSN
|
|
75
|
-
*/
|
|
76
|
-
constructor(csn: CSN, params: VisitorParameters);
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Determines the file corresponding to the namespace.
|
|
80
|
-
* If no such file exists yet, it is created first.
|
|
81
|
-
* @param {string} path the name of the namespace (foo.bar.baz)
|
|
82
|
-
* @returns the file corresponding to that namespace name
|
|
83
|
-
*/
|
|
84
|
-
private getNamespaceFile(path: Path): SourceFile;
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Conveniently combines _resolveNamespace and _trimNamespace
|
|
88
|
-
* to end up with both the resolved Path of the namespace,
|
|
89
|
-
* and the clean name of the class.
|
|
90
|
-
* @param fq the fully qualified name of an entity.
|
|
91
|
-
* @returns a tuple, [0] holding the path to the namespace, [1] holding the clean name of the entity.
|
|
92
|
-
*/
|
|
93
|
-
private untangle(fq: string): [Path, string];
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Visits all definitions within the CSN definitions.
|
|
97
|
-
*/
|
|
98
|
-
private visitDefinitions(): void;
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Visits a single entity from the CSN's definition field.
|
|
102
|
-
* Will call _printEntity or _printAction based on the entity's kind.
|
|
103
|
-
* @param name name of the entity, fully qualified as is used in the definition field.
|
|
104
|
-
* @param entity CSN data belonging to the entity to perform lookups in.
|
|
105
|
-
*/
|
|
106
|
-
private visitEntity(name: string, entity: CSN): void;
|
|
107
|
-
private _printEntity(name: string, entity: CSN): void;
|
|
108
|
-
private _printAction(name: string, action: CSN): void;
|
|
109
|
-
private _printType(name: string, type: CSN): void;
|
|
110
|
-
private _printAspect(name: string, aspect: CSN): void;
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Visits a single element in an entity.
|
|
114
|
-
* @param name name of the element
|
|
115
|
-
* @param element CSN data belonging to the the element.
|
|
116
|
-
* @param file the namespace file the surrounding entity is being printed into.
|
|
117
|
-
* @param buffer buffer to add the definition to. If no buffer is passed, the passed file's class buffer is used instead.
|
|
118
|
-
*/
|
|
119
|
-
public visitElement(name: string, element: CSN, file: SourceFile, buffer?: Buffer): void;
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Attempts to retrieve the max cardinality of a CSN for an entity.
|
|
123
|
-
* @param element csn of entity to retrieve cardinality for
|
|
124
|
-
* @returns max cardinality of the element.
|
|
125
|
-
* If no cardinality is attached to the element, cardinality is 1.
|
|
126
|
-
* If it is set to '*', result is Infinity.
|
|
127
|
-
*/
|
|
128
|
-
private getMaxCardinality(element: EntityCSN): number;
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Convenience method to shave off the namespace of a fully qualified path.
|
|
132
|
-
* More specifically, only the parts (reading from right to left) that are of
|
|
133
|
-
* kind "entity" are retained.
|
|
134
|
-
* a.b.c.Foo -> Foo
|
|
135
|
-
* Bar -> Bar
|
|
136
|
-
* sap.cap.Book.text -> Book.text (assuming Book and text are both of kind "entity")
|
|
137
|
-
* @param p path
|
|
138
|
-
* @returns the entity name without leading namespace.
|
|
139
|
-
*/
|
|
140
|
-
private _trimNamespace(p: string): string;
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Resolves a fully qualified identifier to a namespace.
|
|
144
|
-
* In an identifier 'a.b.c.D.E', the namespace is the part of the identifier
|
|
145
|
-
* read from left to right which does not contain a kind 'context' or 'service'.
|
|
146
|
-
* That is, if in the above example 'D' is a context and 'E' is a service,
|
|
147
|
-
* the resulting namespace is 'a.b.c'.
|
|
148
|
-
* @param pathParts the distinct parts of the namespace, i.e. ['a','b','c','D','E']
|
|
149
|
-
* @returns the namespace's name, i.e. 'a.b.c'.
|
|
150
|
-
*/
|
|
151
|
-
private _resolveNamespace(pathParts: string[]): string;
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Puts a passed string in docstring format.
|
|
155
|
-
* @param doc raw string to docify. May contain linebreaks.
|
|
156
|
-
* @returns an array of lines wrapped in doc format. The result is not
|
|
157
|
-
* concatenated to be properly indented by `buffer.add(...)`.
|
|
158
|
-
*/
|
|
159
|
-
private _docify(doc: string): string[];
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Transforms an entity or CDS aspect into a JS aspect (aka mixin).
|
|
163
|
-
* That is, for an element A we get:
|
|
164
|
-
* - the function A(B) to mix the aspect into another class B
|
|
165
|
-
* - the const AXtended which represents the entity A with all of its aspects mixed in (this const is not exported)
|
|
166
|
-
* - the type A to use for external typing and is derived from AXtended.
|
|
167
|
-
* @param name the name of the entity
|
|
168
|
-
* @param element the pointer into the CSN to extract the elements from
|
|
169
|
-
* @param buffer the buffer to write the resulting definitions into
|
|
170
|
-
* @param cleanName the clean name to use. If not passed, it is derived from the passed name instead.
|
|
171
|
-
*/
|
|
172
|
-
private _aspectify(name: string, element: CSN, buffer: Buffer, cleanName?: string);
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Convenient API to consume resolveType.
|
|
176
|
-
* Internally calls resolveType, determines how it has to be imported,
|
|
177
|
-
* used, etc. relative to file and just returns the name under
|
|
178
|
-
* which it will finally be known within file.
|
|
179
|
-
*
|
|
180
|
-
* For example:
|
|
181
|
-
* model1.cds contains entity Foo
|
|
182
|
-
* model2.cds references Foo
|
|
183
|
-
*
|
|
184
|
-
* calling resolveAndRequire({... Foo}, model2.d.ts) would then:
|
|
185
|
-
* 1. add an import of model1 to model2 with proper path resolution and alias, e.g. "import * as m1 from './model1'"
|
|
186
|
-
* 2. resolve any singular/ plural issues and association/ composition around it
|
|
187
|
-
* 3. return a properly prefixed name to use within model2.d.ts, e.g. "m1.Foo"
|
|
188
|
-
*
|
|
189
|
-
* @param element the CSN element to resolve the type for.
|
|
190
|
-
* @param file source file for context.
|
|
191
|
-
* @returns name of the resolved type
|
|
192
|
-
*/
|
|
193
|
-
private resolveAndRequire(element: CSN, file: SourceFile): string;
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Resolves an element's type to either a builtin or a user defined type.
|
|
197
|
-
* Enriched with additional information for improved printout (see return type).
|
|
198
|
-
* @param element the CSN element to resolve the type for.
|
|
199
|
-
* @param file source file for context.
|
|
200
|
-
* @returns description of the resolved type
|
|
201
|
-
*/
|
|
202
|
-
private resolveType(element: CSN, file: SourceFile): TypeResolveInfo;
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Resolves the fully qualified name of an entity to its parent entity.
|
|
206
|
-
* _resolveParent(a.b.c.D) -> CSN {a.b.c}
|
|
207
|
-
* @param name fully qualified name of the entity to resolve the parent of.
|
|
208
|
-
* @returns the resolved parent CSN.
|
|
209
|
-
*/
|
|
210
|
-
private _resolveParent(name: String): CSN;
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Attempts to resolve a type that could reference another type.
|
|
214
|
-
* @param val
|
|
215
|
-
* @param into see resolveType()
|
|
216
|
-
* @param file only needed as we may call _resolveInlineDeclarationType from here. Will be expelled at some point.
|
|
217
|
-
*/
|
|
218
|
-
private _resolvePotentialReferenceType(val: any, into: TypeResolveInfo, file: SourceFile);
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Resolves an inline declaration of a type.
|
|
223
|
-
* We can encounter declarations like:
|
|
224
|
-
*
|
|
225
|
-
* record : array of {
|
|
226
|
-
* column : String;
|
|
227
|
-
* data : String;
|
|
228
|
-
* }
|
|
229
|
-
*
|
|
230
|
-
* These have to be resolved to a new type.
|
|
231
|
-
*
|
|
232
|
-
* @param items the properties of the inline declaration.
|
|
233
|
-
* @param into see resolveType()
|
|
234
|
-
* @param relativeToindent the sourcefile in which we have found the reference to the type.
|
|
235
|
-
* This is important to correctly detect when a field in the inline declaration is referencing
|
|
236
|
-
* types from the CWD. In that case, we will not add an import for that type and not add a namespace-prefix.
|
|
237
|
-
*/
|
|
238
|
-
private _resolveInlineDeclarationType(items: Array<unknown>, into: TypeResolveInfo, relativeTo: SourceFile): void;
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Attempts to resolve a string to a type.
|
|
242
|
-
* String is supposed to refer to either a builtin type
|
|
243
|
-
* or any type defined in CSN.
|
|
244
|
-
* @param t fully qualified type, like cds.String, or a.b.c.d.Foo
|
|
245
|
-
* @param into optional dictionary to fill by reference, see resolveType()
|
|
246
|
-
* @returns see resolveType()
|
|
247
|
-
*/
|
|
248
|
-
private _resolveTypeName(t: string, into: TypeResolveInfo): TypeResolveInfo;
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Wraps type into association to scalar.
|
|
252
|
-
* @param t the singular type name.
|
|
253
|
-
*/
|
|
254
|
-
private _createToOneAssociation(t: string): string;
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Wraps type into association to vector.
|
|
258
|
-
* @param t the singular type name.
|
|
259
|
-
*/
|
|
260
|
-
private _createToManyAssociation(t: string): string;
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Wraps type into composition of scalar.
|
|
264
|
-
* @param t the singular type name.
|
|
265
|
-
*/
|
|
266
|
-
private _createCompositionOfOne(t: string): string;
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Wraps type into composition of vector.
|
|
270
|
-
* @param t the singular type name.
|
|
271
|
-
*/
|
|
272
|
-
private _createCompositionOfMany(t: string): string;
|
|
273
|
-
}
|
package/lib/file.d.ts
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
type KVs = [string, string][]
|
|
2
|
-
type Namespace = {[key: string]: Buffer}
|
|
3
|
-
type ActionParams = [string, string][]
|
|
4
|
-
type AsDirectoryParams = {relative: string | undefined, local: boolean, posix: boolean}
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* String buffer to conveniently append strings to.
|
|
8
|
-
*/
|
|
9
|
-
export class Buffer {
|
|
10
|
-
public parts: string[];
|
|
11
|
-
private indentation: string;
|
|
12
|
-
private currentIndent: string;
|
|
13
|
-
|
|
14
|
-
constructor(indentation: string);
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Indents by the predefined spacing.
|
|
18
|
-
*/
|
|
19
|
-
indent(): void;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Removes one level of indentation.
|
|
23
|
-
*/
|
|
24
|
-
outdent(): void;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Concats all elements in the buffer into a single string.
|
|
28
|
-
* @param glue string to intersperse all buffer contents with
|
|
29
|
-
* @returns string spilled buffer contents.
|
|
30
|
-
*/
|
|
31
|
-
join(glue: string): string;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Clears the buffer.
|
|
35
|
-
*/
|
|
36
|
-
clear(): void;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Adds an element to the buffer with the current indentation level.
|
|
40
|
-
* @param part
|
|
41
|
-
*/
|
|
42
|
-
add(part: string): void;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Convenience class to handle path qualifiers.
|
|
47
|
-
*/
|
|
48
|
-
export class Path {
|
|
49
|
-
private parts: string[];
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
*
|
|
53
|
-
* @param parts parts of the path. 'a.b.c' -> ['a', 'b', 'c']
|
|
54
|
-
* @param kind FIXME: currently unused
|
|
55
|
-
*/
|
|
56
|
-
constructor(parts: string[], kind: string);
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* @returns the path to the parent directory. 'a.b.c'.getParent() -> 'a.b'
|
|
60
|
-
*/
|
|
61
|
-
getParent(): Path;
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Transfoms the Path into a directory path.
|
|
65
|
-
* @param relative if defined, the path is constructed relative to this directory
|
|
66
|
-
* @param local if set to true, './' is prefixed to the directory
|
|
67
|
-
* @param posix if set to true, all slashes will be forward slashes on every OS. Useful for require/ import
|
|
68
|
-
* @returns directory 'a.b.c'.asDirectory() -> 'a/b/c' (or a\b\c when on Windows without passing posix = true)
|
|
69
|
-
*/
|
|
70
|
-
asDirectory(params: AsDirectoryParams): string;
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Transforms the Path into a namespace qualifier.
|
|
74
|
-
* @returns namespace qualifier 'a.b.c'.asNamespace() -> 'a.b.c'
|
|
75
|
-
*/
|
|
76
|
-
asNamespace(): string;
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Transforms the Path into an identifier that can be used as variable name.
|
|
80
|
-
* @returns identifier 'a.b.c'.asIdentifier() -> '_a_b_c', ''.asIdentifier() -> '_'
|
|
81
|
-
*/
|
|
82
|
-
asIdentifier(): string;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* @returns true, iff the Path refers to the current working directory, aka './'
|
|
86
|
-
*/
|
|
87
|
-
isCwd(relative: string | undefined): boolean;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Source file containing several buffers.
|
|
92
|
-
*/
|
|
93
|
-
export class SourceFile {
|
|
94
|
-
public readonly path: Path;
|
|
95
|
-
private imports: {}
|
|
96
|
-
private types: Buffer;
|
|
97
|
-
private classes: Buffer;
|
|
98
|
-
private enums: Buffer;
|
|
99
|
-
private actions: Buffer;
|
|
100
|
-
private namespaces: Namespace;
|
|
101
|
-
private classNames: {}
|
|
102
|
-
private inflections: [string, string][]
|
|
103
|
-
|
|
104
|
-
constructor(path: string);
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Adds a pair of singular and plural inflection.
|
|
108
|
-
* These are later used to generate the singular -> plural
|
|
109
|
-
* aliases in the index.js file.
|
|
110
|
-
* @param singular singular type without namespace.
|
|
111
|
-
* @param plural plural type without namespace
|
|
112
|
-
* @param original original entity name without namespace.
|
|
113
|
-
* In many cases this will be the same as plural.
|
|
114
|
-
*/
|
|
115
|
-
addInflection(singular: string, plural: string, original: string);
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Adds an action definition in form of a arrow function to the file.
|
|
119
|
-
* @param name name of the action
|
|
120
|
-
* @param params list of parameters, passed as [name, type] pairs
|
|
121
|
-
* @param returns the return type of the action
|
|
122
|
-
*/
|
|
123
|
-
addAction(name: string, params: ActionParams, returns: string);
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Adds an enum to this file.
|
|
127
|
-
* @param fq fully qualified name of the enum
|
|
128
|
-
* @param name local name of the enum
|
|
129
|
-
* @param kvs list of key-value pairs
|
|
130
|
-
*/
|
|
131
|
-
addEnum(fq: string, clean: string, kvs: KVs);
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Adds an arbitrary piece of code that is added
|
|
135
|
-
* right after the imports.
|
|
136
|
-
* @param code the preamble code.
|
|
137
|
-
*/
|
|
138
|
-
addPreamble(code: string);
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Adds a type alias to this file.
|
|
142
|
-
* @param fq fully qualified name of the enum
|
|
143
|
-
* @param name local name of the enum
|
|
144
|
-
* @param rhs the right hand side of the assignment
|
|
145
|
-
*/
|
|
146
|
-
addType(fq: string, clean: string, rhs: string);
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Adds a class to this file.
|
|
150
|
-
* This differs from writing to the classes buffer,
|
|
151
|
-
* as it is just a cache to collect all classes that
|
|
152
|
-
* are supposed to be present in this file.
|
|
153
|
-
* @param clean cleaned name of the class
|
|
154
|
-
* @param fq fully qualified name, including the namespace
|
|
155
|
-
*/
|
|
156
|
-
addClass(clean: string, fq: string): void;
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Retrieves or creates and retrieves a sub namespace
|
|
160
|
-
* with a given name.
|
|
161
|
-
* @param name of the sub namespace.
|
|
162
|
-
* @returns the sub namespace.
|
|
163
|
-
*/
|
|
164
|
-
getSubNamespace(name: string): Namespace;
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Adds an import if it does not exist yet.
|
|
168
|
-
* @param imp qualifier for the namespace to import.
|
|
169
|
-
*/
|
|
170
|
-
addImport(imp: Path): void;
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Writes all imports to a buffer, relative to the current file.
|
|
174
|
-
* Creates a new buffer on each call, as concatenating import strings directly
|
|
175
|
-
* upon discovering them would complicate filtering out duplicate entries.
|
|
176
|
-
* @returns all imports written to a buffer.
|
|
177
|
-
*/
|
|
178
|
-
getImports(): Buffer;
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Creates one string from the buffers representing the type definitions.
|
|
182
|
-
* @returns complete file contents.
|
|
183
|
-
*/
|
|
184
|
-
toTypeDefs(): string;
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Concats the classnames to an export dictionary
|
|
188
|
-
* to create the accompanying JS file for the typings.
|
|
189
|
-
* @returns a string containing the module.exports for the JS file.
|
|
190
|
-
*/
|
|
191
|
-
toJSExports(): string;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Base definitions used throughout the typing process,
|
|
196
|
-
* such as Associations and Compositions.
|
|
197
|
-
*/
|
|
198
|
-
declare const baseDefinitions: SourceFile;
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Writes the files to disk. For each source, a index.d.ts holding the type definitions
|
|
202
|
-
* and a index.js holding implementation stubs is generated at the appropriate directory.
|
|
203
|
-
* Missing directories are created automatically and asynchronously.
|
|
204
|
-
* @param root root directory to prefix all directories with
|
|
205
|
-
* @param sources source files to write to disk
|
|
206
|
-
* @returns Promise that resolves to a list of all directory paths pointing to generated files.
|
|
207
|
-
*/
|
|
208
|
-
export function writeout(root: string, sources: SourceFile[]): Promise<string[]>;
|
package/lib/logging.d.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
export enum Levels {
|
|
2
|
-
TRACE = 1,
|
|
3
|
-
DEBUG = 2,
|
|
4
|
-
INFO = 3,
|
|
5
|
-
WARNING = 4,
|
|
6
|
-
ERROR = 8,
|
|
7
|
-
CRITICAL = 16,
|
|
8
|
-
NONE = 32
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export class Logger {
|
|
12
|
-
private mask: number;
|
|
13
|
-
|
|
14
|
-
public constructor();
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Add all log levels starting at level.
|
|
18
|
-
* @param level level to start from.
|
|
19
|
-
*/
|
|
20
|
-
public addFrom(level: number): void;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Adds a log level to react to.
|
|
24
|
-
* @param level the level to react to.
|
|
25
|
-
*/
|
|
26
|
-
public add(level: number): void;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Ignores a log level.
|
|
30
|
-
* @param level the level to ignore.
|
|
31
|
-
*/
|
|
32
|
-
public ignore(level: number): void;
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Attempts to log a message.
|
|
36
|
-
* Only iff levelName is a valid log level
|
|
37
|
-
* and the corresponding number if part of mask,
|
|
38
|
-
* the message gets logged.
|
|
39
|
-
* @param levelName name of the log level.
|
|
40
|
-
* @param message message to log.
|
|
41
|
-
*/
|
|
42
|
-
private _log(levelName: Levels, message: string);
|
|
43
|
-
|
|
44
|
-
public trace(message: string);
|
|
45
|
-
public debug(message: string);
|
|
46
|
-
public info(message: string);
|
|
47
|
-
public warning(message: string);
|
|
48
|
-
public error(message: string);
|
|
49
|
-
public critical(message: string);
|
|
50
|
-
}
|
package/lib/util.d.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
interface Annotations {
|
|
2
|
-
name?: string,
|
|
3
|
-
'@singular'?: string,
|
|
4
|
-
'@plural'?: string
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
interface CommandlineFlag {
|
|
8
|
-
desc: string,
|
|
9
|
-
default?: any
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface ParsedFlags {
|
|
13
|
-
positional: string[],
|
|
14
|
-
named: {[key: string]: any}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Tries to retrieve an annotation that specifies the singular name
|
|
19
|
-
* from a CSN. Valid annotations are listed in util.annotations
|
|
20
|
-
* and their precedence is in order of definition.
|
|
21
|
-
* If no singular is specified at all, undefined is returned.
|
|
22
|
-
* @param csn the CSN of an entity to check
|
|
23
|
-
* @returns the singular annotation or undefined
|
|
24
|
-
*/
|
|
25
|
-
export function getSingularAnnotation(csn: {}): string | undefined;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Tries to retrieve an annotation that specifies the plural name
|
|
29
|
-
* from a CSN. Valid annotations are listed in util.annotations
|
|
30
|
-
* and their precedence is in order of definition.
|
|
31
|
-
* If no plural is specified at all, undefined is returned.
|
|
32
|
-
* @param csn the CSN of an entity to check
|
|
33
|
-
* @returns the plural annotation or undefined
|
|
34
|
-
*/
|
|
35
|
-
export function getPluralAnnotation(csn: {}): string | undefined;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Users can specify that they want to refer to localisation
|
|
40
|
-
* using the syntax {i18n>Foo}, where Foo is the name of the
|
|
41
|
-
* entity as found in the .cds file
|
|
42
|
-
* (see: https://pages.github.tools.sap/cap/docs/guides/i18n)
|
|
43
|
-
* As this throws off the naming, we remove this wrapper
|
|
44
|
-
* unlocalize("{i18n>Foo}") -> "Foo"
|
|
45
|
-
* @param name the entity name (singular or plural).
|
|
46
|
-
* @returns the name without localisation syntax or untouched.
|
|
47
|
-
*/
|
|
48
|
-
export function unlocalize(name: string): string;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Attempts to derive the singular form of an English noun.
|
|
52
|
-
* If '@singular' is passed as annotation, that is preferred.
|
|
53
|
-
* @param dn annotations
|
|
54
|
-
* @param stripped if true, leading namespace will be stripped
|
|
55
|
-
*/
|
|
56
|
-
export function singular4(dn: Annotations | string, stripped: boolean): string;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Attempts to derive the plural form of an English noun.
|
|
60
|
-
* If '@plural' is passed as annotation, that is preferred.
|
|
61
|
-
* @param dn annotations
|
|
62
|
-
* @param stripped if true, leading namespace will be stripped
|
|
63
|
-
*/
|
|
64
|
-
export function plural4(dn: Annotations | string, stripped: boolean): string;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Parses command line arguments into named and positional parameters.
|
|
68
|
-
* Named parameters are expected to start with a double dash (--).
|
|
69
|
-
* If the next argument `B` after a named parameter `A` is not a named parameter itself,
|
|
70
|
-
* `B` is used as value for `A`.
|
|
71
|
-
* If `A` and `B` are both named parameters, `A` is just treated as a flag (and may receive a default value).
|
|
72
|
-
* Only named parameters that occur in validFlags are allowed. Specifying named flags that are not listed there
|
|
73
|
-
* will cause an error.
|
|
74
|
-
* Named parameters that are either not specified or do not have a value assigned to them may draw a default value
|
|
75
|
-
* from their definition in validFlags.
|
|
76
|
-
* @param argv list of command line arguments
|
|
77
|
-
* @param validFlags allowed flags. May specify default values.
|
|
78
|
-
*/
|
|
79
|
-
export function parseCommandlineArgs(argv: string[], validFlags: {[key: string]: CommandlineFlag}): ParsedFlags;
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Performs a deep merge of the passed objects into the first object.
|
|
83
|
-
* See Object.assign(target, source).
|
|
84
|
-
* @param target object to assign into.
|
|
85
|
-
* @param source object to assign from.
|
|
86
|
-
*/
|
|
87
|
-
export function deepMerge(target: {}, source: {}): void;
|