@cap-js/cds-typer 0.23.0 → 0.25.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 +23 -1
- package/cds-plugin.js +8 -3
- package/lib/cli.js +24 -14
- package/lib/compile.js +3 -3
- package/lib/components/enum.js +16 -9
- package/lib/components/identifier.js +1 -1
- package/lib/components/inline.js +81 -26
- package/lib/components/property.js +12 -0
- package/lib/components/wrappers.js +1 -1
- package/lib/csn.js +90 -26
- package/lib/file.js +43 -19
- package/lib/logging.js +5 -1
- package/lib/resolution/builtin.js +3 -2
- package/lib/resolution/entity.js +46 -7
- package/lib/resolution/resolver.js +51 -20
- package/lib/typedefs.d.ts +67 -13
- package/lib/util.js +4 -3
- package/lib/visitor.js +167 -61
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,13 +4,35 @@ 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.26.0 - TBD
|
|
8
|
+
|
|
9
|
+
## Version 0.25.0 - 2024-08-13
|
|
10
|
+
### Added
|
|
11
|
+
- Declaring a type alias on an enum in cds now also exports it on value level in the resulting type
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- Classes representing views and projections will no longer carry ancestry to avoid clashes thereof with aliases fields
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- All properties are now preceeded with the `declare` modifier to pass strict tsconfigs using `useDefineForClassFields` or `noImplicitOverride`
|
|
18
|
+
- The static `actions` property of generated classes now includes the types from all inherited classes to also suggest actions defined in a base entity/aspect/type.
|
|
19
|
+
|
|
20
|
+
## Version 0.24.0 - 2024-07-18
|
|
21
|
+
### Fixed
|
|
22
|
+
- Suppressed an error that would incorrectly point out naming clashes when an entity was named in singular inflection in the model
|
|
23
|
+
- CDS aspects now also generate a aspect-function in singular inflection, similar to how entities do
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Aspects generate named classes again so that tooltips will show more meaningful provenance for properties
|
|
27
|
+
- The TypeScript task for `cds build` no longer looks for tsconfig.json to determine if the project has TS nature and instead checks the dependencies in the project's package.json for an occurrence of `typescript`
|
|
8
28
|
|
|
9
29
|
## Version 0.23.0 - 2024-07-04
|
|
30
|
+
|
|
10
31
|
### Fixed
|
|
11
32
|
- Plurals no longer have `is_singular` attached in the resulting .js files
|
|
12
33
|
- Properties are properly propagated beyond just one level of inheritance
|
|
13
34
|
|
|
35
|
+
|
|
14
36
|
## Version 0.22.0 - 2024-06-20
|
|
15
37
|
### Fixed
|
|
16
38
|
- Fixed a bug where keys would sometimes inconsistently become nullable
|
package/cds-plugin.js
CHANGED
|
@@ -10,9 +10,14 @@ const DEBUG = cds.debug('cli|build')
|
|
|
10
10
|
const BUILD_CONFIG = 'tsconfig.cdsbuild.json'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Check if a
|
|
13
|
+
* Check if the project is a TypeScript project by looking for a dependency on TypeScript.
|
|
14
|
+
* @returns {boolean}
|
|
14
15
|
*/
|
|
15
|
-
const
|
|
16
|
+
const isTypeScriptProject = () => {
|
|
17
|
+
if (!fs.existsSync('package.json')) return false
|
|
18
|
+
const pkg = require(path.resolve('package.json'))
|
|
19
|
+
return Boolean(pkg.devDependencies?.typescript || pkg.dependencies?.typescript)
|
|
20
|
+
}
|
|
16
21
|
|
|
17
22
|
/**
|
|
18
23
|
* Check if separate tsconfig file that is used for building the project.
|
|
@@ -56,7 +61,7 @@ if (!cds?.version || cds.version < '8.0.0') {
|
|
|
56
61
|
// requires @sap/cds-dk version >= 7.5.0
|
|
57
62
|
cds.build?.register?.('typescript', class extends cds.build.Plugin {
|
|
58
63
|
static taskDefaults = { src: '.' }
|
|
59
|
-
static hasTask() { return
|
|
64
|
+
static hasTask() { return isTypeScriptProject() }
|
|
60
65
|
|
|
61
66
|
// lower priority than the nodejs task
|
|
62
67
|
get priority() { return -1 }
|
package/lib/cli.js
CHANGED
|
@@ -12,6 +12,9 @@ const { EOL } = require('node:os')
|
|
|
12
12
|
const EOL2 = EOL + EOL
|
|
13
13
|
const toolName = 'cds-typer'
|
|
14
14
|
|
|
15
|
+
// @ts-expect-error - nope, it is actually there. Types just seem to be out of sync.
|
|
16
|
+
const lls = cds.log.levels
|
|
17
|
+
|
|
15
18
|
const flags = {
|
|
16
19
|
outputDirectory: {
|
|
17
20
|
desc: 'Root directory to write the generated files to.',
|
|
@@ -23,10 +26,10 @@ const flags = {
|
|
|
23
26
|
},
|
|
24
27
|
logLevel: {
|
|
25
28
|
desc: `Minimum log level that is printed.${EOL}The default is only used if no explicit value is passed${EOL}and there is no configuration passed via cds.env either.`,
|
|
26
|
-
allowed: Object.keys(
|
|
27
|
-
allowedHint: Object.keys(
|
|
28
|
-
defaultHint: _keyFor(
|
|
29
|
-
default: cds?.env?.log?.levels?.['cds-typer'] ?? _keyFor(
|
|
29
|
+
allowed: Object.keys(lls).concat(Object.keys(deprecated)),
|
|
30
|
+
allowedHint: Object.keys(lls).join(' | '), // FIXME: remove once old levels are faded out
|
|
31
|
+
defaultHint: _keyFor(lls.ERROR),
|
|
32
|
+
default: cds?.env?.log?.levels?.['cds-typer'] ?? _keyFor(lls.ERROR),
|
|
30
33
|
},
|
|
31
34
|
jsConfigPath: {
|
|
32
35
|
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.`,
|
|
@@ -53,25 +56,32 @@ const flags = {
|
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
const hint = () => 'Missing or invalid parameter(s). Call with --help for more details.'
|
|
56
|
-
|
|
59
|
+
/**
|
|
60
|
+
* @param {string} s - the string to indent
|
|
61
|
+
* @param {string} indentation - the indentation to use
|
|
62
|
+
*/
|
|
63
|
+
const indent = (s, indentation) => s
|
|
64
|
+
.split(EOL)
|
|
65
|
+
.map((/** @type {string} */ line) => `${indentation}${line}`)
|
|
66
|
+
.join(EOL)
|
|
57
67
|
|
|
58
68
|
const help = () => `SYNOPSIS${EOL2}` +
|
|
59
69
|
indent('cds-typer [cds file | "*"]', ' ') + EOL2 +
|
|
60
70
|
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 +
|
|
61
71
|
`OPTIONS${EOL2}` +
|
|
62
72
|
Object.entries(flags)
|
|
63
|
-
.sort()
|
|
73
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
64
74
|
.map(([key, value]) => {
|
|
65
75
|
let s = indent(`--${key}`, ' ')
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
76
|
+
// @ts-expect-error - not going to check presence of each property. Same for the following expect-errors.
|
|
77
|
+
if (value.allowedHint) s += ` ${value.allowedHint}`
|
|
78
|
+
// @ts-expect-error
|
|
79
|
+
else if (value.allowed) s += `: <${value.allowed.join(' | ')}>`
|
|
80
|
+
else if ('type' in value && value.type) s += `: <${value.type}>`
|
|
81
|
+
// @ts-expect-error
|
|
73
82
|
if (value.defaultHint || value.default) {
|
|
74
83
|
s += EOL
|
|
84
|
+
// @ts-expect-error
|
|
75
85
|
s += indent(`(default: ${value.defaultHint ?? value.default})`, ' ')
|
|
76
86
|
}
|
|
77
87
|
s += `${EOL2}${indent(value.desc, ' ')}`
|
|
@@ -81,7 +91,7 @@ const help = () => `SYNOPSIS${EOL2}` +
|
|
|
81
91
|
|
|
82
92
|
const version = () => require('../package.json').version
|
|
83
93
|
|
|
84
|
-
const main = async args => {
|
|
94
|
+
const main = async (/** @type {any} */ args) => {
|
|
85
95
|
if ('help' in args.named) {
|
|
86
96
|
console.log(help())
|
|
87
97
|
process.exit(0)
|
package/lib/compile.js
CHANGED
|
@@ -26,7 +26,7 @@ const writeJsConfig = path => {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
if (fs.existsSync(path)) {
|
|
29
|
-
const currentContents = JSON.parse(fs.readFileSync(path))
|
|
29
|
+
const currentContents = JSON.parse(fs.readFileSync(path, 'utf8'))
|
|
30
30
|
if (currentContents?.compilerOptions?.checkJs) {
|
|
31
31
|
LOG.warn(`jsconfig at location ${path} already exists. Attempting to merge.`)
|
|
32
32
|
}
|
|
@@ -39,7 +39,7 @@ const writeJsConfig = path => {
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Compiles a CSN object to Typescript types.
|
|
42
|
-
* @param {{xtended: CSN, inferred: CSN}} csn - csn tuple
|
|
42
|
+
* @param {{xtended: import('./typedefs').resolver.CSN, inferred: import('./typedefs').resolver.CSN}} csn - csn tuple
|
|
43
43
|
* @param {CompileParameters} parameters - path to root directory for all generated files, min log level
|
|
44
44
|
*/
|
|
45
45
|
const compileFromCSN = async (csn, parameters) => {
|
|
@@ -57,7 +57,7 @@ const compileFromCSN = async (csn, parameters) => {
|
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* Compiles a .cds file to Typescript types.
|
|
60
|
-
* @param {string} inputFile - path to input .cds file
|
|
60
|
+
* @param {string | string[]} inputFile - path to input .cds file
|
|
61
61
|
* @param {CompileParameters} parameters - path to root directory for all generated files, min log level, etc.
|
|
62
62
|
*/
|
|
63
63
|
const compileFromFile = async (inputFile, parameters) => {
|
package/lib/components/enum.js
CHANGED
|
@@ -19,6 +19,11 @@ const uniqueValues = kvs => new Set(kvs.map(([,v]) => v?.val ?? v)) // in case
|
|
|
19
19
|
const stringifyEnumType = kvs => [...uniqueValues(kvs)].join(' | ')
|
|
20
20
|
|
|
21
21
|
// in case of strings, wrap in quotes and fallback to key to make sure values are attached for every key
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} key - the key of the enum
|
|
24
|
+
* @param {any} value - the value of the enum
|
|
25
|
+
* @param {string | import('@sap/cds').ref} enumType - the type of the enum
|
|
26
|
+
*/
|
|
22
27
|
const enumVal = (key, value, enumType) => enumType === 'cds.String' ? JSON.stringify(`${value ?? key}`) : value
|
|
23
28
|
|
|
24
29
|
/**
|
|
@@ -41,7 +46,7 @@ const enumVal = (key, value, enumType) => enumType === 'cds.String' ? JSON.strin
|
|
|
41
46
|
* const E = { a: 'A', b: 'B' }
|
|
42
47
|
* type E = 'A' | 'B'
|
|
43
48
|
* ```
|
|
44
|
-
* @param {Buffer} buffer - Buffer to write into
|
|
49
|
+
* @param {import('../file').Buffer} buffer - Buffer to write into
|
|
45
50
|
* @param {string} name - local name of the enum, i.e. the name under which it should be created in the .ts file
|
|
46
51
|
* @param {[string, string][]} kvs - list of key-value pairs
|
|
47
52
|
* @param {object} options - options for printing the enum
|
|
@@ -60,12 +65,13 @@ function printEnum(buffer, name, kvs, options = {}) {
|
|
|
60
65
|
* Converts a CSN type describing an enum into a list of kv-pairs.
|
|
61
66
|
* Values from CSN are unwrapped from their `.val` structure and
|
|
62
67
|
* will fall back to the key if no value is provided.
|
|
63
|
-
* @param {
|
|
64
|
-
* @param {{unwrapVals: boolean}} options - if `unwrapVals` is passed,
|
|
68
|
+
* @param {import('../typedefs').resolver.EnumCSN} enumCsn - the CSN type describing the enum
|
|
69
|
+
* @param {{unwrapVals: boolean} | {}} options - if `unwrapVals` is passed,
|
|
65
70
|
* then the CSN structure `{val:x}` is flattened to just `x`.
|
|
66
71
|
* Retaining `val` is closer to the actual CSN structure and should be used where we want
|
|
67
72
|
* to mimic the runtime as closely as possible (inline enum types).
|
|
68
73
|
* Stripping that additional wrapper would be more readable for users.
|
|
74
|
+
* @returns {[string, any][]}
|
|
69
75
|
* @example
|
|
70
76
|
* ```ts
|
|
71
77
|
* const csn = {enum: {X: {val: 'a'}, Y: {val: 'b'}, Z: {}}}
|
|
@@ -74,10 +80,10 @@ function printEnum(buffer, name, kvs, options = {}) {
|
|
|
74
80
|
* ```
|
|
75
81
|
*/
|
|
76
82
|
const csnToEnumPairs = ({enum: enm, type}, options = {}) => {
|
|
77
|
-
|
|
83
|
+
const actualOptions = {...{unwrapVals: true}, ...options}
|
|
78
84
|
return Object.entries(enm).map(([k, v]) => {
|
|
79
85
|
const val = enumVal(k, v.val, type)
|
|
80
|
-
return [k, (
|
|
86
|
+
return [k, (actualOptions.unwrapVals ? val : { val })]
|
|
81
87
|
})
|
|
82
88
|
}
|
|
83
89
|
|
|
@@ -91,11 +97,12 @@ const propertyToInlineEnumName = (entity, property) => `${entity}_${property}`
|
|
|
91
97
|
* A type is considered to be an inline enum, iff it has a `.enum` property
|
|
92
98
|
* _and_ its type is a CDS primitive, i.e. it is not contained in `cds.definitions`.
|
|
93
99
|
* If it is contained there, then it is a standard enum declaration that has its own name.
|
|
94
|
-
* @param {{type: string}} element - the element to check
|
|
95
|
-
* @param {
|
|
96
|
-
* @returns
|
|
100
|
+
* @param {{type: string | import('../typedefs').resolver.ref, [key: string]: any}} element - the element to check
|
|
101
|
+
* @param {import('../typedefs').resolver.CSN} csn - the CSN model
|
|
102
|
+
* @returns {element is import('../typedefs').resolver.EnumCSN}
|
|
97
103
|
*/
|
|
98
|
-
const isInlineEnumType = (element, csn) => element.enum
|
|
104
|
+
const isInlineEnumType = (element, csn) => element.enum
|
|
105
|
+
&& !(typeof element.type === 'string' && element.type in csn.definitions)
|
|
99
106
|
|
|
100
107
|
/**
|
|
101
108
|
* Stringifies an enum into a runtime artifact.
|
|
@@ -15,7 +15,7 @@ const normalise = ident => ident && !isValidIdent.test(ident)
|
|
|
15
15
|
* @param {string} ident - the identifier to extract the last part from
|
|
16
16
|
* @returns {string} the last part of the identifier
|
|
17
17
|
*/
|
|
18
|
-
const last = ident => ident.split('.').at(-1)
|
|
18
|
+
const last = ident => ident.split('.').at(-1) ?? ident
|
|
19
19
|
|
|
20
20
|
module.exports = {
|
|
21
21
|
normalise,
|
package/lib/components/inline.js
CHANGED
|
@@ -3,6 +3,10 @@ const { normalise } = require('./identifier')
|
|
|
3
3
|
const { docify } = require('./wrappers')
|
|
4
4
|
|
|
5
5
|
/** @typedef {import('../resolution/resolver').TypeResolveInfo} TypeResolveInfo */
|
|
6
|
+
/** @typedef {import('../typedefs').visitor.Inflection} Inflection */
|
|
7
|
+
/** @typedef {import('../typedefs').resolver.PropertyModifier} PropertyModifier */
|
|
8
|
+
/** @typedef {import('../visitor').Visitor} Visitor */
|
|
9
|
+
/** @typedef {{typeName: string, typeInfo: TypeResolveInfo}} TypeResolveInfo_ */
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
12
|
* Inline declarations of types can come in different flavours.
|
|
@@ -12,15 +16,17 @@ const { docify } = require('./wrappers')
|
|
|
12
16
|
*/
|
|
13
17
|
class InlineDeclarationResolver {
|
|
14
18
|
/**
|
|
15
|
-
* @param {
|
|
16
|
-
* @param {
|
|
17
|
-
* @param {
|
|
18
|
-
* @param {
|
|
19
|
+
* @param {object} options - options to be passed to the resolver
|
|
20
|
+
* @param {string} options.fq - full qualifier of the type
|
|
21
|
+
* @param {TypeResolveInfo_} options.type - type info so far
|
|
22
|
+
* @param {import('../file').Buffer} options.buffer - the buffer to write into
|
|
23
|
+
* @param {PropertyModifier[]} options.modifiers - modifiers to add to each generated property
|
|
24
|
+
* @param {string} [options.statementEnd] - statement ending character
|
|
19
25
|
* @protected
|
|
20
26
|
* @abstract
|
|
21
27
|
*/
|
|
22
28
|
// eslint-disable-next-line no-unused-vars
|
|
23
|
-
printInlineType(fq, type, buffer, statementEnd) { /* abstract */ }
|
|
29
|
+
printInlineType({fq, type, buffer, modifiers, statementEnd}) { /* abstract */ }
|
|
24
30
|
|
|
25
31
|
/**
|
|
26
32
|
* Attempts to resolve a type that could reference another type.
|
|
@@ -39,6 +45,8 @@ class InlineDeclarationResolver {
|
|
|
39
45
|
for (const [subname, subelement] of Object.entries(items ?? {})) {
|
|
40
46
|
// in inline definitions, we sometimes have to resolve first
|
|
41
47
|
// FIXME: does this tie in with how we sometimes end up with resolved typed in resolveType()?
|
|
48
|
+
// FIXME2: I don't think the if branch is actually called in real world situations.
|
|
49
|
+
// so we can probably get rid of this distinction and make #resolveTypeName private again
|
|
42
50
|
const se = (typeof subelement === 'string')
|
|
43
51
|
? this.visitor.resolver.resolveTypeName(subelement)
|
|
44
52
|
: subelement
|
|
@@ -58,13 +66,15 @@ class InlineDeclarationResolver {
|
|
|
58
66
|
|
|
59
67
|
/**
|
|
60
68
|
* Visits a single element in an entity.
|
|
61
|
-
* @param {
|
|
62
|
-
* @param {
|
|
63
|
-
* @param {
|
|
64
|
-
* @param {
|
|
69
|
+
* @param {object} options - options
|
|
70
|
+
* @param {string} options.name - name of the element
|
|
71
|
+
* @param {import('../typedefs').resolver.EntityCSN} options.element - CSN data belonging to the the element.
|
|
72
|
+
* @param {SourceFile} options.file - the namespace file the surrounding entity is being printed into.
|
|
73
|
+
* @param {Buffer} options.buffer - buffer to add the definition to. If no buffer is passed, the passed file's class buffer is used instead.
|
|
74
|
+
* @param {PropertyModifier[]} options.modifiers - modifiers to add to each generated property
|
|
65
75
|
* @public
|
|
66
76
|
*/
|
|
67
|
-
visitElement(name, element, file, buffer = file.classes) {
|
|
77
|
+
visitElement({name, element, file, buffer = file.classes, modifiers = []}) {
|
|
68
78
|
this.depth++
|
|
69
79
|
for (const d of docify(element.doc)) {
|
|
70
80
|
buffer.add(d)
|
|
@@ -72,7 +82,7 @@ class InlineDeclarationResolver {
|
|
|
72
82
|
const type = this.visitor.resolver.resolveAndRequire(element, file)
|
|
73
83
|
this.depth--
|
|
74
84
|
if (this.depth === 0) {
|
|
75
|
-
this.printInlineType(name, type, buffer)
|
|
85
|
+
this.printInlineType({fq: name, type, buffer, modifiers})
|
|
76
86
|
}
|
|
77
87
|
return type
|
|
78
88
|
}
|
|
@@ -89,7 +99,7 @@ class InlineDeclarationResolver {
|
|
|
89
99
|
|
|
90
100
|
/**
|
|
91
101
|
* It returns TypeScript datatype for provided TS property
|
|
92
|
-
* @param {
|
|
102
|
+
* @param {TypeResolveInfo_} type - type of the property
|
|
93
103
|
* @param {string} typeName - name of the TypeScript property
|
|
94
104
|
* @returns {string} the datatype to be presented on TypeScript layer
|
|
95
105
|
* @public
|
|
@@ -98,7 +108,16 @@ class InlineDeclarationResolver {
|
|
|
98
108
|
return type.typeInfo.isNotNull ? typeName : `${typeName} | null`
|
|
99
109
|
}
|
|
100
110
|
|
|
101
|
-
/**
|
|
111
|
+
/**
|
|
112
|
+
* Stringifies additional modifiers for a property
|
|
113
|
+
* @param {(PropertyModifier)[]} modifiers - modifiers to stringify
|
|
114
|
+
* @returns {string} the modifiers as a string
|
|
115
|
+
*/
|
|
116
|
+
stringifyModifiers (modifiers) {
|
|
117
|
+
return modifiers?.length > 0 ? modifiers.join(' ') + ' ' : ''
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** @param {Visitor} visitor - the visitor */
|
|
102
121
|
constructor(visitor) {
|
|
103
122
|
this.visitor = visitor
|
|
104
123
|
// type resolution might recurse. This indicator is used to determine
|
|
@@ -148,24 +167,42 @@ class InlineDeclarationResolver {
|
|
|
148
167
|
* ```
|
|
149
168
|
*/
|
|
150
169
|
class FlatInlineDeclarationResolver extends InlineDeclarationResolver {
|
|
151
|
-
|
|
152
|
-
|
|
170
|
+
/**
|
|
171
|
+
* @param {string} p - prefix to use
|
|
172
|
+
*/
|
|
153
173
|
prefix(p) {
|
|
154
174
|
return p ? `${p}_` : ''
|
|
155
175
|
}
|
|
156
176
|
|
|
157
|
-
|
|
177
|
+
/**
|
|
178
|
+
* @param {object} options - options
|
|
179
|
+
* @param {string} options.prefix - prefix to use
|
|
180
|
+
* @param {TypeResolveInfo_} options.type - type to flatten
|
|
181
|
+
* @param {PropertyModifier[]} options.modifiers - modifiers to add to each generated property
|
|
182
|
+
* @returns {string[]} the flattened properties
|
|
183
|
+
*/
|
|
184
|
+
flatten({prefix, type, modifiers}) {
|
|
158
185
|
return type.typeInfo.structuredType
|
|
159
|
-
? Object.entries(type.typeInfo.structuredType).map(
|
|
160
|
-
|
|
186
|
+
? Object.entries(type.typeInfo.structuredType).map(
|
|
187
|
+
([k,v]) => this.flatten({prefix: `${this.prefix(prefix)}${k}`, type: v, modifiers}) // for flat we pass the modifiers!
|
|
188
|
+
).flat()
|
|
189
|
+
: [`${this.stringifyModifiers(modifiers)}${normalise(prefix)}${this.getPropertyTypeSeparator()} ${this.getPropertyDatatype(type)}`]
|
|
161
190
|
}
|
|
162
191
|
|
|
163
|
-
|
|
164
|
-
|
|
192
|
+
/**
|
|
193
|
+
* @override
|
|
194
|
+
* @type {InlineDeclarationResolver['printInlineType']}
|
|
195
|
+
*/
|
|
196
|
+
printInlineType({fq, type, buffer, modifiers}) {
|
|
197
|
+
for(const prop of this.flatten({prefix: fq, type, modifiers}).flat()) {
|
|
165
198
|
buffer.add(prop)
|
|
166
199
|
}
|
|
167
200
|
}
|
|
168
201
|
|
|
202
|
+
/**
|
|
203
|
+
* @override
|
|
204
|
+
* @type {InlineDeclarationResolver['getTypeLookup']}
|
|
205
|
+
*/
|
|
169
206
|
getTypeLookup(members) {
|
|
170
207
|
return `['${members.join('_')}']`
|
|
171
208
|
}
|
|
@@ -189,39 +226,57 @@ class FlatInlineDeclarationResolver extends InlineDeclarationResolver {
|
|
|
189
226
|
* ```
|
|
190
227
|
*/
|
|
191
228
|
class StructuredInlineDeclarationResolver extends InlineDeclarationResolver {
|
|
229
|
+
/** @param {Visitor} visitor - the visitor */
|
|
192
230
|
constructor(visitor) {
|
|
193
231
|
super(visitor)
|
|
194
232
|
this.printDepth = 0
|
|
195
233
|
}
|
|
196
234
|
|
|
197
|
-
|
|
235
|
+
/**
|
|
236
|
+
* @param {object} options - options
|
|
237
|
+
* @param {string} options.fq - full qualifier of the type
|
|
238
|
+
* @param {TypeResolveInfo_} options.type - type info so far
|
|
239
|
+
* @param {Buffer} options.buffer - the buffer to write into
|
|
240
|
+
* @param {PropertyModifier[]} [options.modifiers] - modifiers to add to each generated property
|
|
241
|
+
* @param {string} [options.statementEnd] - statement ending character
|
|
242
|
+
* FIXME: reuse type
|
|
243
|
+
*/
|
|
244
|
+
flatten({fq, type, buffer, modifiers = [], statementEnd = ';'}) {
|
|
198
245
|
// in addition to the regular depth during resolution, we may have another depth while printing
|
|
199
246
|
// nested types on which the line ending depends
|
|
200
247
|
this.printDepth++
|
|
201
248
|
const lineEnding = this.printDepth > 1 ? ',' : statementEnd
|
|
202
249
|
if (type.typeInfo.structuredType) {
|
|
203
|
-
const prefix =
|
|
250
|
+
const prefix = fq ? `${this.stringifyModifiers(modifiers)}${normalise(fq)}${this.getPropertyTypeSeparator()}`: ''
|
|
204
251
|
buffer.add(`${prefix} {`)
|
|
205
252
|
buffer.indent()
|
|
206
253
|
for (const [n, t] of Object.entries(type.typeInfo.structuredType)) {
|
|
207
|
-
this.flatten(n, t, buffer)
|
|
254
|
+
this.flatten({fq: n, type: t, buffer})
|
|
208
255
|
}
|
|
209
256
|
buffer.outdent()
|
|
210
257
|
buffer.add(`}${this.getPropertyDatatype(type, '')}${lineEnding}`)
|
|
211
258
|
} else {
|
|
212
|
-
buffer.add(`${normalise(
|
|
259
|
+
buffer.add(`${this.stringifyModifiers(modifiers)}${normalise(fq)}${this.getPropertyTypeSeparator()} ${this.getPropertyDatatype(type)}${lineEnding}`)
|
|
213
260
|
}
|
|
214
261
|
this.printDepth--
|
|
215
262
|
return buffer
|
|
216
263
|
}
|
|
217
264
|
|
|
218
|
-
|
|
265
|
+
/**
|
|
266
|
+
* @override
|
|
267
|
+
* @type {InlineDeclarationResolver['printInlineType']}
|
|
268
|
+
*/
|
|
269
|
+
printInlineType({fq, type, buffer, modifiers, statementEnd}) {
|
|
219
270
|
// FIXME: indent not quite right
|
|
220
271
|
const sub = new Buffer()
|
|
221
272
|
sub.currentIndent = buffer.currentIndent
|
|
222
|
-
buffer.add(this.flatten(
|
|
273
|
+
buffer.add(this.flatten({fq, type, buffer: sub, modifiers, statementEnd}).join())
|
|
223
274
|
}
|
|
224
275
|
|
|
276
|
+
/**
|
|
277
|
+
* @override
|
|
278
|
+
* @type {InlineDeclarationResolver['getTypeLookup']}
|
|
279
|
+
*/
|
|
225
280
|
getTypeLookup(members) {
|
|
226
281
|
return members.map(m => `['${m}']`).join('')
|
|
227
282
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines the proper modifiers for a property.
|
|
3
|
+
* For most properties, this will be "declare". But for some properties,
|
|
4
|
+
* like fields of a type, we don't want any modifiers.
|
|
5
|
+
* @param {import('../typedefs').resolver.EntityCSN} element - The element to determine the modifiers for.
|
|
6
|
+
* @returns {import('../typedefs').resolver.PropertyModifier[]} The modifiers for the property.
|
|
7
|
+
*/
|
|
8
|
+
const getPropertyModifiers = element => element?.parent?.kind !== 'type' ? ['declare'] : []
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
getPropertyModifiers
|
|
12
|
+
}
|
|
@@ -55,7 +55,7 @@ const deepRequire = (t, lookup = '') => `${base}.DeepRequired<${t}>${lookup}`
|
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* Puts a passed string in docstring format.
|
|
58
|
-
* @param {string} doc - raw string to docify. May contain linebreaks.
|
|
58
|
+
* @param {string | undefined} doc - raw string to docify. May contain linebreaks.
|
|
59
59
|
* @returns {string[]} an array of lines wrapped in doc format. The result is not
|
|
60
60
|
* concatenated to be properly indented by `buffer.add(...)`.
|
|
61
61
|
*/
|