@cap-js/cds-typer 0.2.5-beta.1
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 +43 -0
- package/LICENSE +201 -0
- package/README.md +192 -0
- package/index.js +5 -0
- package/lib/cli.js +100 -0
- package/lib/compile.d.ts +273 -0
- package/lib/compile.js +926 -0
- package/lib/components/inline.js +217 -0
- package/lib/file.d.ts +208 -0
- package/lib/file.js +497 -0
- package/lib/logging.d.ts +50 -0
- package/lib/logging.js +73 -0
- package/lib/util.d.ts +87 -0
- package/lib/util.js +196 -0
- package/library/cds.hana.ts +15 -0
- package/package.json +50 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
const { SourceFile, Buffer } = require('../file')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Inline declarations of types can come in different flavours.
|
|
5
|
+
* The compiler can therefore be adjusted to print out one or the other
|
|
6
|
+
* by plugging different implementations of this abstract class into
|
|
7
|
+
* their resolution mechanism.
|
|
8
|
+
*/
|
|
9
|
+
class InlineDeclarationResolver {
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} name
|
|
13
|
+
* @param {import('../compile').TypeResolveInfo} type
|
|
14
|
+
* @param {import('../file').Buffer} buffer
|
|
15
|
+
* @param {string} statementEnd
|
|
16
|
+
* @private
|
|
17
|
+
* @abstract
|
|
18
|
+
*/
|
|
19
|
+
printInlineType(name, type, buffer, statementEnd) { /* abstract */ }
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Attempts to resolve a type that could reference another type.
|
|
23
|
+
* @param {any} val
|
|
24
|
+
* @param {import('../compile').TypeResolveInfo} into @see Visitor.resolveType
|
|
25
|
+
* @param {SourceFile} file temporary file to resolve dummy types into.
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
resolveInlineDeclaration(items, into, relativeTo) {
|
|
29
|
+
const dummy = new SourceFile(relativeTo.path.asDirectory())
|
|
30
|
+
dummy.classes.currentIndet = relativeTo.classes.currentIndent
|
|
31
|
+
dummy.classes.add('{')
|
|
32
|
+
dummy.classes.indent()
|
|
33
|
+
|
|
34
|
+
into.structuredType = {}
|
|
35
|
+
for (const [subname, subelement] of Object.entries(items ?? {})) {
|
|
36
|
+
// in inline definitions, we sometimes have to resolve first
|
|
37
|
+
// FIXME: does this tie in with how we sometimes end up with resolved typed in resolveType()?
|
|
38
|
+
const se = (typeof subelement === 'string')
|
|
39
|
+
? this.visitor._resolveTypeName(subelement)
|
|
40
|
+
: subelement
|
|
41
|
+
into.structuredType[subname] = this.visitor.visitElement(subname, se, dummy)
|
|
42
|
+
}
|
|
43
|
+
dummy.classes.outdent()
|
|
44
|
+
dummy.classes.add('}')
|
|
45
|
+
// FIXME: pass as param
|
|
46
|
+
//dummy.classes.add(element.constructor.name === 'array' ? '}[]' : '}')
|
|
47
|
+
into.imports = (into.imports ?? []).concat(...Object.values(dummy.imports))
|
|
48
|
+
into.isInlineDeclaration = true
|
|
49
|
+
//into.structuredType = dummy
|
|
50
|
+
Object.defineProperty(into, 'type', {
|
|
51
|
+
get: () => dummy.classes.join('\n')
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Visits a single element in an entity.
|
|
57
|
+
* @param {string} name name of the element
|
|
58
|
+
* @param {import('../compile').CSN} element CSN data belonging to the the element.
|
|
59
|
+
* @param {SourceFile} file the namespace file the surrounding entity is being printed into.
|
|
60
|
+
* @param {Buffer} [buffer] buffer to add the definition to. If no buffer is passed, the passed file's class buffer is used instead.
|
|
61
|
+
* @public
|
|
62
|
+
*/
|
|
63
|
+
visitElement(name, element, file, buffer = file.classes) {
|
|
64
|
+
this.depth++
|
|
65
|
+
for (const d of this.visitor._docify(element.doc)) {
|
|
66
|
+
buffer.add(d)
|
|
67
|
+
}
|
|
68
|
+
const type = this.visitor.resolveAndRequire(element, file)
|
|
69
|
+
this.depth--
|
|
70
|
+
if (this.depth === 0) {
|
|
71
|
+
this.printInlineType(name, type, buffer)
|
|
72
|
+
}
|
|
73
|
+
return type
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Separator between value V and type T: `v : T`.
|
|
78
|
+
* Depending on the visitor's setting, this is may be `?:` for optional
|
|
79
|
+
* properties or `:` for required properties.
|
|
80
|
+
* @returns {'?'|':'}
|
|
81
|
+
*/
|
|
82
|
+
getPropertyTypeSeparator() {
|
|
83
|
+
return this.visitor.options.propertiesOptional ? '?:' : ':'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** @param {import('../compile').Visitor} visitor */
|
|
87
|
+
constructor(visitor) {
|
|
88
|
+
this.visitor = visitor
|
|
89
|
+
// type resolution might recurse. This indicator is used to determine
|
|
90
|
+
// when the type has been fully resolve (depth === 0) and should be printed
|
|
91
|
+
this.depth = 0
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Produces a string representation of how to produce a [Typescript type lookup](https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.html#handbook-content)
|
|
96
|
+
* under the current configuration.
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* type T = {
|
|
100
|
+
* a: {
|
|
101
|
+
* b: number
|
|
102
|
+
* }
|
|
103
|
+
* }
|
|
104
|
+
*
|
|
105
|
+
* T['a']['b'] // number
|
|
106
|
+
* ```
|
|
107
|
+
* but especially with inline declarations, the access will differ between flattened and nested representations.
|
|
108
|
+
* @param {string[]} members a list of members, in the above example it would be `['a', 'b']`
|
|
109
|
+
* @returns {string} type access string snippet. In the above sample, we would return `"['a']['b']"`
|
|
110
|
+
* @public
|
|
111
|
+
* @abstract
|
|
112
|
+
*/
|
|
113
|
+
getTypeLookup(members) { /* abstract */ }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Resolves inline declarations in a flat fashion.
|
|
118
|
+
* @example
|
|
119
|
+
* ```cds
|
|
120
|
+
* // cds
|
|
121
|
+
* entity E {
|
|
122
|
+
* x: { a: Integer; b: String; }
|
|
123
|
+
* }
|
|
124
|
+
* ```
|
|
125
|
+
* ↓
|
|
126
|
+
* ```ts
|
|
127
|
+
* // ts
|
|
128
|
+
* class E {
|
|
129
|
+
* x_a: number;
|
|
130
|
+
* x_b: string;
|
|
131
|
+
* }
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
class FlatInlineDeclarationResolver extends InlineDeclarationResolver {
|
|
135
|
+
constructor(visitor) { super(visitor) }
|
|
136
|
+
|
|
137
|
+
prefix(p) {
|
|
138
|
+
return p ? `${p}_` : ''
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
flatten(prefix, type) {
|
|
142
|
+
return type.typeInfo.structuredType
|
|
143
|
+
? Object.entries(type.typeInfo.structuredType).map(([k,v]) => this.flatten(`${this.prefix(prefix)}${k}`, v))
|
|
144
|
+
: [`${prefix} ${this.getPropertyTypeSeparator()} ${type.typeName}`]
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
printInlineType(name, type, buffer) {
|
|
148
|
+
for(const prop of this.flatten(name, type).flat()) {
|
|
149
|
+
buffer.add(prop)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
getTypeLookup(members) {
|
|
154
|
+
return `['${members.join('_')}']`
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Resolves inline declarations to a structured type.
|
|
160
|
+
* @example
|
|
161
|
+
* ```cds
|
|
162
|
+
* // cds
|
|
163
|
+
* entity E {
|
|
164
|
+
* x: { a: Integer; b: String; }
|
|
165
|
+
* }
|
|
166
|
+
* ```
|
|
167
|
+
* ↓
|
|
168
|
+
* ```ts
|
|
169
|
+
* // ts
|
|
170
|
+
* class E {
|
|
171
|
+
* x: { a: number; b: string; }
|
|
172
|
+
* }
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
class StructuredInlineDeclarationResolver extends InlineDeclarationResolver {
|
|
176
|
+
constructor(visitor) {
|
|
177
|
+
super(visitor)
|
|
178
|
+
this.printDepth = 0
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
flatten(name, type, buffer, statementEnd = ';') {
|
|
182
|
+
// in addition to the regular depth during resolution, we may have another depth while printing
|
|
183
|
+
// nested types on which the line ending depends
|
|
184
|
+
this.printDepth++
|
|
185
|
+
const lineEnding = this.printDepth > 1 ? ',' : statementEnd
|
|
186
|
+
if (type.typeInfo.structuredType) {
|
|
187
|
+
const prefix = name ? `${name} ${this.getPropertyTypeSeparator()}`: ''
|
|
188
|
+
buffer.add(`${prefix} {`)
|
|
189
|
+
buffer.indent()
|
|
190
|
+
for (const [n, t] of Object.entries(type.typeInfo.structuredType)) {
|
|
191
|
+
this.flatten(n, t, buffer)
|
|
192
|
+
}
|
|
193
|
+
buffer.outdent()
|
|
194
|
+
buffer.add(`}${lineEnding}`)
|
|
195
|
+
} else {
|
|
196
|
+
buffer.add(`${name} ${this.getPropertyTypeSeparator()} ${type.typeName}${lineEnding}`)
|
|
197
|
+
}
|
|
198
|
+
this.printDepth--
|
|
199
|
+
return buffer
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
printInlineType(name, type, buffer, statementEnd) {
|
|
203
|
+
// FIXME: indent not quite right
|
|
204
|
+
const sub = new Buffer()
|
|
205
|
+
sub.currentIndent = buffer.currentIndent
|
|
206
|
+
buffer.add(this.flatten(name, type, sub, statementEnd).join())
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
getTypeLookup(members) {
|
|
210
|
+
return members.map(m => `['${m}']`).join('')
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = {
|
|
215
|
+
FlatInlineDeclarationResolver,
|
|
216
|
+
StructuredInlineDeclarationResolver
|
|
217
|
+
}
|
package/lib/file.d.ts
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
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[]>;
|