@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 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.24.0 - TBD
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 tsconfig file exists.
13
+ * Check if the project is a TypeScript project by looking for a dependency on TypeScript.
14
+ * @returns {boolean}
14
15
  */
15
- const tsConfigExists = () => fs.existsSync('tsconfig.json')
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 tsConfigExists() }
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(cds.log.levels).concat(Object.keys(deprecated)),
27
- allowedHint: Object.keys(cds.log.levels).join(' | '), // FIXME: remove once old levels are faded out
28
- defaultHint: _keyFor(cds.log.levels.ERROR),
29
- default: cds?.env?.log?.levels?.['cds-typer'] ?? _keyFor(cds.log.levels.ERROR),
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
- const indent = (s, indentation) => s.split(EOL).map(line => `${indentation}${line}`).join(EOL)
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
- if (value.allowedHint) {
67
- s += ` ${value.allowedHint}`
68
- } else if (value.allowed) {
69
- s += `: <${value.allowed.join(' | ')}>`
70
- } else if (value.type) {
71
- s += `: <${value.type}>`
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) => {
@@ -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 {{enum: {[key: name]: string}, type: string}} enumCsn - the CSN type describing the enum
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
- options = {...{unwrapVals: true}, ...options}
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, (options.unwrapVals ? val : { val })]
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 {object} csn - the CSN model
96
- * @returns boolean
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 && !(element.type in csn.definitions)
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,
@@ -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 {string} fq - full qualifier of the type
16
- * @param {TypeResolveInfo} type - type info so far
17
- * @param {import('../file').Buffer} buffer - the buffer to write into
18
- * @param {string} statementEnd - statement ending character
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 {string} name - name of the element
62
- * @param {import('../resolution/resolver').CSN} element - CSN data belonging to the the element.
63
- * @param {SourceFile} file - the namespace file the surrounding entity is being printed into.
64
- * @param {Buffer} [buffer] - buffer to add the definition to. If no buffer is passed, the passed file's class buffer is used instead.
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 {{typeName: string, typeInfo: TypeResolveInfo & { inflection: Inflection } }} type - type of the property
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
- /** @param {import('../visitor').Visitor} visitor - the visitor */
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
- constructor(visitor) { super(visitor) }
152
-
170
+ /**
171
+ * @param {string} p - prefix to use
172
+ */
153
173
  prefix(p) {
154
174
  return p ? `${p}_` : ''
155
175
  }
156
176
 
157
- flatten(prefix, type) {
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(([k,v]) => this.flatten(`${this.prefix(prefix)}${k}`, v))
160
- : [`${normalise(prefix)}${this.getPropertyTypeSeparator()} ${this.getPropertyDatatype(type)}`]
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
- printInlineType(name, type, buffer) {
164
- for(const prop of this.flatten(name, type).flat()) {
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
- flatten(name, type, buffer, statementEnd = ';') {
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 = name ? `${normalise(name)}${this.getPropertyTypeSeparator()}`: ''
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(name)}${this.getPropertyTypeSeparator()} ${this.getPropertyDatatype(type)}${lineEnding}`)
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
- printInlineType(name, type, buffer, statementEnd) {
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(name, type, sub, statementEnd).join())
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
  */