@sap/cds-compiler 5.8.0 → 5.9.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.
Files changed (89) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/cds_remove_invalid_whitespace.js +5 -3
  3. package/bin/cds_update_identifiers.js +9 -6
  4. package/bin/cdsc.js +79 -59
  5. package/bin/cdsse.js +14 -10
  6. package/bin/cdsv2m.js +3 -1
  7. package/lib/api/options.js +28 -6
  8. package/lib/base/message-registry.js +15 -4
  9. package/lib/checks/validator.js +3 -0
  10. package/lib/compiler/base.js +1 -1
  11. package/lib/compiler/checks.js +70 -50
  12. package/lib/compiler/extend.js +1 -1
  13. package/lib/compiler/generate.js +8 -2
  14. package/lib/compiler/index.js +1 -1
  15. package/lib/compiler/lsp-api.js +1 -1
  16. package/lib/compiler/propagator.js +2 -2
  17. package/lib/compiler/resolve.js +78 -31
  18. package/lib/compiler/shared.js +3 -3
  19. package/lib/compiler/tweak-assocs.js +1 -1
  20. package/lib/compiler/utils.js +10 -0
  21. package/lib/compiler/xpr-rewrite.js +1 -1
  22. package/lib/edm/annotations/edmJson.js +42 -39
  23. package/lib/edm/annotations/genericTranslation.js +55 -55
  24. package/lib/edm/annotations/preprocessAnnotations.js +5 -5
  25. package/lib/edm/csn2edm.js +21 -16
  26. package/lib/edm/edm.js +62 -62
  27. package/lib/edm/edmAnnoPreprocessor.js +2 -2
  28. package/lib/edm/edmInboundChecks.js +1 -1
  29. package/lib/edm/edmPreprocessor.js +32 -32
  30. package/lib/edm/edmUtils.js +8 -8
  31. package/lib/gen/CdlGrammar.checksum +1 -1
  32. package/lib/gen/CdlParser.js +77 -81
  33. package/lib/gen/Dictionary.json +3062 -3072
  34. package/lib/gen/language.checksum +1 -1
  35. package/lib/gen/language.interp +1 -1
  36. package/lib/gen/languageParser.js +1238 -1236
  37. package/lib/json/from-csn.js +1 -1
  38. package/lib/json/to-csn.js +30 -3
  39. package/lib/language/genericAntlrParser.js +16 -0
  40. package/lib/main.d.ts +79 -1
  41. package/lib/model/csnRefs.js +12 -5
  42. package/lib/model/xprAsTree.js +71 -0
  43. package/lib/modelCompare/utils/filter.js +1 -1
  44. package/lib/optionProcessor.js +46 -32
  45. package/lib/parsers/CdlGrammar.g4 +33 -28
  46. package/lib/parsers/Lexer.js +1 -1
  47. package/lib/parsers/XprTree.js +25 -16
  48. package/lib/render/toCdl.js +902 -414
  49. package/lib/render/toHdbcds.js +1 -1
  50. package/lib/render/toSql.js +8 -0
  51. package/lib/render/utils/common.js +2 -2
  52. package/lib/render/utils/operators.js +160 -0
  53. package/lib/render/utils/pretty.js +337 -0
  54. package/lib/sql-identifier.js +7 -9
  55. package/lib/transform/addTenantFields.js +39 -41
  56. package/lib/transform/db/applyTransformations.js +4 -4
  57. package/lib/transform/db/assertUnique.js +6 -5
  58. package/lib/transform/db/associations.js +3 -3
  59. package/lib/transform/db/assocsToQueries/transformExists.js +13 -13
  60. package/lib/transform/db/assocsToQueries/utils.js +8 -0
  61. package/lib/transform/db/backlinks.js +19 -14
  62. package/lib/transform/db/constraints.js +6 -6
  63. package/lib/transform/db/expansion.js +1 -1
  64. package/lib/transform/db/flattening.js +2 -2
  65. package/lib/transform/db/groupByOrderBy.js +1 -1
  66. package/lib/transform/db/processSqlServices.js +3 -3
  67. package/lib/transform/db/rewriteCalculatedElements.js +2 -2
  68. package/lib/transform/db/temporal.js +7 -9
  69. package/lib/transform/db/views.js +6 -6
  70. package/lib/transform/draft/odata.js +2 -0
  71. package/lib/transform/effective/annotations.js +1 -1
  72. package/lib/transform/effective/associations.js +1 -1
  73. package/lib/transform/effective/main.js +1 -0
  74. package/lib/transform/effective/service.js +2 -2
  75. package/lib/transform/forRelationalDB.js +11 -5
  76. package/lib/transform/localized.js +2 -0
  77. package/lib/transform/odata/adaptAnnotationRefs.js +10 -9
  78. package/lib/transform/odata/createForeignKeys.js +1 -1
  79. package/lib/transform/odata/flattening.js +2 -1
  80. package/lib/transform/parseExpr.js +2 -2
  81. package/lib/transform/transformUtils.js +9 -7
  82. package/lib/transform/translateAssocsToJoins.js +0 -2
  83. package/lib/transform/universalCsn/coreComputed.js +2 -2
  84. package/lib/utils/moduleResolve.js +7 -5
  85. package/package.json +1 -1
  86. package/share/messages/def-upcoming-virtual-change.md +55 -0
  87. package/share/messages/file-unexpected-case-mismatch.md +61 -0
  88. package/share/messages/message-explanations.json +2 -0
  89. package/lib/transform/braceExpression.js +0 -77
@@ -1,133 +1,623 @@
1
1
  'use strict';
2
2
 
3
+ // to.cdl() renderer
4
+ //
5
+ // This file contains the whole to.cdl(), which takes CSN and outputs CDL.
6
+ // It used e.g. by `cds import`.
7
+ //
8
+ //
9
+ // # Development Notes
10
+ //
11
+ // ## Abbreviations used
12
+ // - fqn : fully qualified name, i.e. a name that is a global definition reference
13
+ //
14
+
15
+
3
16
  const keywords = require('../base/keywords');
4
17
  const { cdlNewLineRegEx } = require('../language/textUtils');
5
18
  const { findElement, createExpressionRenderer, withoutCast } = require('./utils/common');
6
19
  const { escapeString, hasUnpairedUnicodeSurrogate } = require('./utils/stringEscapes');
7
20
  const { checkCSNVersion } = require('../json/csnVersion');
8
- const { forEachDefinition, normalizeTypeRef } = require('../model/csnUtils');
21
+ const { normalizeTypeRef, forEachDefinition } = require('../model/csnUtils');
9
22
  const enrichUniversalCsn = require('../transform/universalCsn/universalCsnEnricher');
10
23
  const { isBetaEnabled } = require('../base/model');
11
- const { ModelError } = require('../base/error');
24
+ const { ModelError, CompilerAssertion } = require('../base/error');
12
25
  const { typeParameters, specialFunctions } = require('../compiler/builtins');
13
26
  const { isAnnotationExpression } = require('../base/builtins');
14
27
  const { forEach } = require('../utils/objectUtils');
15
- const {
16
- getNormalizedQuery,
17
- } = require('../model/csnUtils');
18
28
  const { isBuiltinType } = require('../base/builtins');
19
29
  const { cloneFullCsn } = require('../model/cloneCsn');
20
- const { getKeysDict } = require('../model/csnRefs');
30
+ const { getKeysDict, implicitAs } = require('../model/csnRefs');
21
31
  const { undelimitedIdentifierRegex } = require('../parsers/identifiers');
32
+ const { getNormalizedQuery } = require('../model/csnUtils');
33
+ const {
34
+ line,
35
+ pretty,
36
+ nestBy,
37
+ bracketBlock,
38
+ joinDocuments,
39
+ } = require('./utils/pretty');
22
40
 
23
41
  const specialFunctionKeywords = Object.create(null);
24
42
 
43
+ const MAX_LINE_WIDTH = 72;
44
+ const INDENT_SIZE = 2;
45
+
46
+ function format( document ) {
47
+ return pretty(document, MAX_LINE_WIDTH);
48
+ }
49
+
25
50
  /**
26
- * Render the CSN model 'model' to CDS source text.
27
- * Returned object has the following properties:
28
- * - `model`: CSN model rendered as CDL (string).
29
- * - `namespace`: Namespace statement + `using from './model.cds'.
51
+ * @param {string} path
52
+ * @returns {string}
53
+ */
54
+ function rootPathSegment( path ) {
55
+ // RegEx is at least twice as fast as .split()[0]
56
+ return path.match(/^[^.]+/)[0];
57
+ }
58
+
59
+ /**
60
+ * Path alias to be rendered as a USING statement.
61
+ */
62
+ class UsingAlias {
63
+ path;
64
+ alias;
65
+
66
+ /**
67
+ * @param {string} path
68
+ * @param {string} alias
69
+ */
70
+ constructor(path, alias) {
71
+ this.path = path;
72
+ this.alias = alias;
73
+ }
74
+
75
+ requiresExplicitAlias() {
76
+ return this.alias && implicitAs(this.path) !== this.alias;
77
+ }
78
+ }
79
+
80
+ class NameScopeStack {
81
+ /** @type {DefinitionPathTree[]} */
82
+ #scopes = [];
83
+ /** @type {Record<string, UsingAlias>} */
84
+ #aliasToFqn = Object.create(null);
85
+ /** @type {Record<string, UsingAlias>} */
86
+ #fqnToAlias = Object.create(null);
87
+ /** @type {string|null} */
88
+ #namespaceAlias = null;
89
+
90
+ /**
91
+ @param {DefinitionPathTree} root
92
+ @param {CSN.Model} csn
93
+ */
94
+ setRootScope( root, csn ) {
95
+ root.availableRootPaths = Object.assign(Object.create(null), root.children);
96
+ this.#scopes = [ root ];
97
+
98
+ this.#prepareUniqueUsingsForRootPaths(csn);
99
+ }
100
+
101
+ /**
102
+ * @param {DefinitionPathTree} scope
103
+ */
104
+ pushNameEnv(scope) {
105
+ const outerScope = this.#scopes.at(-1);
106
+
107
+ const isNamespace = this.#scopes.length === 1 && !scope.definition;
108
+ if (isNamespace)
109
+ this.#namespaceAlias = implicitAs(scope.name);
110
+
111
+ // Own children are always available.
112
+ // Root paths of the outer scope are also available in the inner scope.
113
+ scope.availableRootPaths = Object.assign(Object.create(null), outerScope.availableRootPaths, scope.children);
114
+ this.#scopes.push(scope);
115
+ }
116
+
117
+ popNameEnv() {
118
+ const popped = this.#scopes.pop();
119
+ const wasNamespace = this.#scopes.length === 1 && !popped.definition;
120
+ if (wasNamespace)
121
+ this.#namespaceAlias = null;
122
+ }
123
+
124
+ /**
125
+ * To be able to refer to definitions outside the current scope, we need to have
126
+ * unique USING statements. The most stable way is to create a USING statement for
127
+ * root path-segments on-demand and give it an alias by having it unique in the set
128
+ * of all path segments of all definitions.
129
+ *
130
+ * While this will still lead to some long paths here and there, it is the most
131
+ * secure way to avoid ambiguities due to shadowing names.
132
+ *
133
+ * @param {CSN.Model} csn
134
+ */
135
+ #prepareUniqueUsingsForRootPaths(csn) {
136
+ // We include vocabularies here, too, because their names are affected by a global "namespace".
137
+ const names = [
138
+ ...Object.keys(csn.definitions || {}),
139
+ ...Object.keys(csn.vocabularies || {}),
140
+ ...(csn.extensions || []).map(ext => ext.extend || ext.annotate || ''),
141
+ ];
142
+ const segmentedNames = names.map(name => name.split('.'));
143
+ this.nonRootSegments = new Set(segmentedNames.map(segments => segments.slice(1)).flat(1));
144
+ // Don't use `this.#scopes[0].availableRootPaths`, as that will contain unreachable paths,
145
+ // e.g. for a file that contains `namespace a.b`, `a` is not reachable.
146
+ this.rootSegments = new Set(segmentedNames.map(name => name[0]));
147
+ this.rootSegments.add('cds'); // builtin namespaces
148
+ this.rootSegments.add('hana');
149
+ }
150
+
151
+ /**
152
+ * @param {string} fqn Path for which we want to add an alias.
153
+ */
154
+ #addUsingAlias( fqn ) {
155
+ const segments = fqn.split('.');
156
+ let aliasName = segments.at(-1);
157
+ // An explicit alias only needs to be used if the implicit one has the possibility of
158
+ // being shadowed in any scope or if there is already an alias of that name.
159
+ if (this.nonRootSegments.has(aliasName) || this.#aliasToFqn[aliasName]) {
160
+ // There is a non-root segment of the same root name, hence the need for aliases.
161
+ let counter = 0;
162
+ aliasName += '_';
163
+ const baseAlias = aliasName;
164
+ while (this.nonRootSegments.has(aliasName) || this.rootSegments.has(aliasName) || this.#aliasToFqn[aliasName]) {
165
+ // Alias must be unique among _all_ segments and existing USINGs.
166
+ aliasName = `${baseAlias}${++counter}`;
167
+ }
168
+ }
169
+
170
+ // Always add an alias, even if unnecessary, as we'd otherwise try to create
171
+ // it in #useAliasForPath() again if the same rootName is seen again.
172
+ if (this.#aliasToFqn[aliasName])
173
+ throw new CompilerAssertion(`to.cdl: Alias "${aliasName}" already exists; collision for ${fqn} and ${this.#aliasToFqn[aliasName].path}`);
174
+ const alias = new UsingAlias(fqn, aliasName);
175
+ this.#aliasToFqn[aliasName] = alias;
176
+ this.#fqnToAlias[fqn] = alias;
177
+ }
178
+
179
+ /**
180
+ * We assume that definition names, when rendered, are always relative
181
+ * to the current name environment.
182
+ *
183
+ * This function must only be used for statements that _create_ definitions
184
+ * and not for references _to_ definitions.
185
+ *
186
+ * @param {string} fqn
187
+ * @returns {string}
188
+ */
189
+ definitionName(fqn) {
190
+ const leaf = this.#scopes.at(-1);
191
+ if (!leaf?.name)
192
+ return fqn;
193
+ if (isBuiltinType(fqn)) {
194
+ // For e.g. `annotate` statements:
195
+ // - `annotate String;` is invalid
196
+ // - `annotate cds.String;` works
197
+ return fqn;
198
+ }
199
+ if (fqn.startsWith(`${leaf.name }.`))
200
+ return fqn.substring(leaf.name.length + 1); // '+1' => also remove '.'
201
+ throw new CompilerAssertion('to.cdl: Definition to be rendered is not in current name scope!');
202
+ }
203
+
204
+ /**
205
+ * Get a relative reference to the given definition name in the current name environment.
206
+ *
207
+ * This function must only be used for references _to_ definitions and not
208
+ * for statements that _create_ definitions, i.e _introduce_ a new name.
209
+ *
210
+ * @param {string} fqn
211
+ * @returns {string}
212
+ */
213
+ definitionReference(fqn) {
214
+ if (isBuiltinType(fqn)) {
215
+ const ref = this.builtinShorthandReference(fqn);
216
+ if (ref !== null)
217
+ return ref;
218
+ }
219
+
220
+ const name = rootPathSegment(fqn);
221
+ // Go through all scopes except the root one, since in there, paths are always absolute.
222
+ for (let i = this.#scopes.length - 1; i >= 1; i--) {
223
+ const tree = this.#scopes[i];
224
+
225
+ if (tree.name && fqn.startsWith(`${tree.name }.`)) {
226
+ // FQN is in current scope.
227
+ const relativeName = fqn.substring(tree.name.length + 1);
228
+ const relativeRoot = rootPathSegment(relativeName);
229
+
230
+ // Since CDS requires root path segments to be _known within a CDL document_, we
231
+ // need to check if the root path is _known_. If not, we need a USING statement.
232
+ // Example: `namespace ns; entity C : ns.D {};` -> must render alias, as 'D' would
233
+ // be invalid! Required for parseCdl.
234
+ if (!tree.children[relativeRoot])
235
+ return this.#useAliasForPathInScope(fqn, tree);
236
+
237
+ // Name can be used relative to scope 'tree'. We now need to check if the relative
238
+ // name does not collide with more inner scopes by checking for direct children.
239
+ for (let j = this.#scopes.length - 1; j > i; j--) {
240
+ if (this.#scopes[j].children[relativeRoot]) {
241
+ // collision; requires alias
242
+ return this.#useAliasForPathInScope(fqn, tree);
243
+ }
244
+ }
245
+
246
+ return relativeName;
247
+ }
248
+ else if (name in tree.children) {
249
+ // Name is in current scope, but it is not the artifact we're looking for.
250
+ // Use a global alias to avoid confusing it.
251
+ return this.#useAliasForPathInScope(fqn, null);
252
+ }
253
+ }
254
+
255
+ // At this point, the path is unknown and outside any non-root scope.
256
+
257
+ if (this.#namespaceAlias && (name !== 'cds' || this.#namespaceAlias === 'cds')) {
258
+ // There is a namespace. We need a USING for all non-builtin paths, but also for
259
+ // builtins if the namespace alias collides. Builtin collision, e.g.
260
+ // `type my.cds.String : cds.String;` with common namespace "my.cds".
261
+ return this.#useAliasForPathInScope(fqn, null);
262
+ }
263
+ if (name !== 'cds' && !this.#scopes[0].availableRootPaths?.[name]) {
264
+ // In case the non-builtin path is unknown, add a using statement. Required for parseCdl.
265
+ // Completely unknown: -> alias
266
+ return this.#useAliasForPathInScope(fqn, null);
267
+ }
268
+ // Builtin or root path is known.
269
+ return fqn;
270
+ }
271
+
272
+ /**
273
+ * Adapt the FQN to use a global alias. The alias is created for either
274
+ * the scope in which the FQN resides or the root path segment.
275
+ *
276
+ * @param {string} fqn
277
+ * @param {DefinitionPathTree} [scope]
278
+ * @returns {string}
279
+ */
280
+ #useAliasForPathInScope( fqn, scope ) {
281
+ const path = scope?.name ? scope.name : rootPathSegment(fqn);
282
+ if (!this.#fqnToAlias[path]?.path)
283
+ this.#addUsingAlias(path);
284
+
285
+ if (this.#fqnToAlias[path].alias === path)
286
+ return fqn; // shortcut to avoid substring()
287
+
288
+ return this.#fqnToAlias[path].alias + fqn.substring(path.length);
289
+ }
290
+
291
+ /**
292
+ * Returns a shorthand reference to the builtin type if possible or
293
+ * null otherwise, in which case the caller must ensure that the full type
294
+ * can be used.
295
+ *
296
+ * Example:
297
+ * cds.Integer -> Integer
298
+ * cds.hana.NCHAR -> hana.NCHAR
299
+ *
300
+ * @param {string} type
301
+ * @returns {string|null}
302
+ */
303
+ builtinShorthandReference(type) {
304
+ const shortHand = type.slice(4); // remove 'cds.'
305
+ const root = rootPathSegment(shortHand);
306
+ if (this.#scopes.at(-1).availableRootPaths[root])
307
+ return null; // there is already an artifact of the same name
308
+ if (this.#namespaceAlias === root)
309
+ return null; // alias collides with shorthand
310
+ return shortHand;
311
+ }
312
+
313
+ /**
314
+ * Get a list of objects meant to be rendered as USING statements.
315
+ *
316
+ * @returns {UsingAlias[]}
317
+ */
318
+ getUsings() {
319
+ const result = [];
320
+ for (const alias in this.#aliasToFqn)
321
+ result.push(this.#aliasToFqn[alias]);
322
+ return result;
323
+ }
324
+ }
325
+
326
+ /**
327
+ * @see createDefinitionPathTree()
328
+ */
329
+ class DefinitionPathTree {
330
+ name = null;
331
+ /** @type {Record<string, DefinitionPathTree>} */
332
+ children = Object.create(null);
333
+ definition = null;
334
+ /** @type {Record<string, DefinitionPathTree>} */
335
+ availableRootPaths = null; // used in NameScopeStack
336
+
337
+ /**
338
+ * @param {string} fqn
339
+ */
340
+ constructor(fqn) {
341
+ this.name = fqn;
342
+ }
343
+ }
344
+
345
+ /**
346
+ * For a CSN model, constructs a tree of all path segments of all definitions, e.g.
347
+ * definitions `a.b.c.d` and `a.b.e.f` will end up in:
348
+ * ```
349
+ * a
350
+ * └─ b
351
+ * ├─ c
352
+ * │ └─ d (link to definition)
353
+ * └─ e
354
+ * └─ f (link to definition)
355
+ * ```
30
356
  *
31
357
  * @param {CSN.Model} csn
32
- * @param {CSN.Options} options
33
- * @param {object} msg Message Functions
358
+ * @param {CdlOptions} options
359
+ * @returns {DefinitionPathTree}
34
360
  */
35
- function csnToCdl( csn, options, msg ) {
36
- const special$self = !csn?.definitions?.$self && '$self';
37
- if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn')) {
38
- // Since the expander modifies the CSN, we need to clone it first or
39
- // toCdl can't guarantee that the input CSN is not modified.
40
- csn = cloneFullCsn(csn, options);
41
- enrichUniversalCsn(csn, options);
361
+ function createDefinitionPathTree( csn, options ) {
362
+ const tree = new DefinitionPathTree('');
363
+ if (!csn.definitions)
364
+ return tree;
365
+
366
+ const useNesting = options.renderCdlDefinitionNesting;
367
+
368
+ for (const defName in csn.definitions) {
369
+ const segments = defName.split('.');
370
+ if (!useNesting) {
371
+ // If we don't want nesting, don't do more work than necessary:
372
+ // only the first path step is relevant
373
+ segments.length = 1;
374
+ }
375
+ let leaf = tree;
376
+ for (let i = 0; i < segments.length; i++) {
377
+ const level = segments[i];
378
+ const fqn = segments.slice(0, i + 1).join('.');
379
+ leaf.children[level] ??= new DefinitionPathTree(fqn);
380
+ leaf = leaf.children[level];
381
+ }
382
+ leaf.definition = csn.definitions[defName];
42
383
  }
384
+ return tree;
385
+ }
43
386
 
44
- checkCSNVersion(csn, options);
45
387
 
46
- const exprRenderer = createCdlExpressionRenderer();
47
- const usings = {
48
- list: [],
49
- available: availableFirstPathSteps(csn),
50
- addIfRequired(name) {
51
- // RegEx is at least twice as fast as .split()[0]
52
- const first = name.match(/^[^.]+/)[0];
53
- if (name !== special$self && !this.available.includes(first) && !this.list.includes(first))
54
- this.list.push(first);
55
- },
56
- renderUsings() {
57
- if (this.list.length === 0)
58
- return '';
59
- return `using { ${this.list.join(', ')} };\n\n`;
60
- },
61
- };
62
- const hanaRequiresAbsolutePath = usings.available.includes('hana');
388
+ class CsnToCdl {
389
+ /**
390
+ * @param {CSN.Model} csn
391
+ * @param {CdlOptions} options
392
+ * @param {object} msg
393
+ */
394
+ constructor(csn, options, msg) {
395
+ this.csn = csn;
396
+ this.options = options;
397
+ this.msg = msg;
398
+
399
+ if (this.options.csnFlavor === 'universal' && isBetaEnabled(this.options, 'enableUniversalCsn')) {
400
+ // Since the expander modifies the CSN, we need to clone it first or
401
+ // toCdl can't guarantee that the input CSN is not modified.
402
+ this.csn = cloneFullCsn(this.csn, this.options);
403
+ enrichUniversalCsn(this.csn, this.options);
404
+ }
63
405
 
64
- const cdlResult = Object.create(null);
65
- cdlResult.model = '';
406
+ checkCSNVersion(this.csn, this.options);
66
407
 
67
- const subelementAnnotates = [];
408
+ this.exprRenderer = this.createCdlExpressionRenderer();
409
+ this.subelementAnnotates = [];
410
+ }
68
411
 
69
- cdlResult.model += renderDefinitions();
70
- // sub-element annotations that can't be written directly.
71
- cdlResult.model += renderExtensions(subelementAnnotates, createEnv());
412
+ render() {
413
+ const cdlResult = Object.create(null);
414
+ cdlResult.model = '';
72
415
 
73
- if (csn.vocabularies)
74
- cdlResult.model += renderVocabularies(csn.vocabularies);
75
- if (csn.extensions)
76
- cdlResult.model += renderExtensions(csn.extensions, createEnv());
416
+ const env = createEnv();
77
417
 
78
- if (csn.namespace) {
79
- cdlResult.namespace = `namespace ${renderArtifactName(csn.namespace, createEnv())};\n`;
80
- cdlResult.namespace += 'using from \'./model.cds\';';
418
+ const useNesting = !!this.options.renderCdlDefinitionNesting;
419
+ this.definitionTree = createDefinitionPathTree(this.csn, this.options);
420
+ this.commonNamespace = this.getCommonNamespace();
421
+
422
+ env.nameEnvStack.setRootScope(this.definitionTree, this.csn);
423
+
424
+ const useNamespace = this.commonNamespace !== this.definitionTree;
425
+ if (useNamespace)
426
+ env.nameEnvStack.pushNameEnv(this.commonNamespace);
427
+
428
+ cdlResult.model += useNesting
429
+ ? this.renderNestedDefinitions(env)
430
+ : this.renderDefinitions(env);
431
+ // sub-element annotations that can't be written directly.
432
+ cdlResult.model += this.renderExtensions(this.subelementAnnotates, env);
433
+
434
+ if (this.csn.vocabularies)
435
+ cdlResult.model += this.renderVocabularies(this.csn.vocabularies, env);
436
+ if (this.csn.extensions)
437
+ cdlResult.model += this.renderExtensions(this.csn.extensions, env);
438
+
439
+ if (useNamespace)
440
+ env.nameEnvStack.popNameEnv();
441
+
442
+ cdlResult.model = this.renderUsingAliases(env.nameEnvStack.getUsings(), env) + cdlResult.model;
443
+ if (this.csn.requires) {
444
+ let usingsStr = this.csn.requires.map(req => `using from '${req}';`).join('\n');
445
+ usingsStr += '\n\n';
446
+ cdlResult.model = usingsStr + cdlResult.model;
447
+ }
448
+
449
+ if (this.commonNamespace.name)
450
+ cdlResult.model = `namespace ${this.renderArtifactName(this.commonNamespace.name, env)};\n\n${cdlResult.model}`;
451
+
452
+ if (this.csn.namespace) {
453
+ cdlResult.namespace = `namespace ${this.renderArtifactName(this.csn.namespace, createEnv())};\n`;
454
+ cdlResult.namespace += 'using from \'./model.cds\';';
455
+ }
456
+
457
+ this.msg.throwWithError();
458
+ return cdlResult;
81
459
  }
82
460
 
83
- cdlResult.model = usings.renderUsings() + cdlResult.model;
84
- if (csn.requires) {
85
- let usingsStr = csn.requires.map(req => `using from '${req}';`).join('\n');
86
- usingsStr += '\n\n';
87
- cdlResult.model = usingsStr + cdlResult.model;
461
+ /**
462
+ * Determine a common namespace along all definitions.
463
+ * Returns this.definitionTree if there is no common namespace.
464
+ *
465
+ * @returns {DefinitionPathTree}
466
+ */
467
+ getCommonNamespace() {
468
+ let root = this.definitionTree;
469
+ if (!this.options.renderCdlDefinitionNesting || !this.options.renderCdlCommonNamespace)
470
+ return root; // User does not want common namespace.
471
+
472
+ if (this.csn.vocabularies) {
473
+ // TODO: With vocabularies, we don't search for a common namespace.
474
+ // Reason being that `namespace` statements affect vocabularies, but
475
+ // we don't create definition trees for them.
476
+ return root;
477
+ }
478
+ if (this.csn.extensions?.length > 0) {
479
+ // TODO: Check for the case of `entity Unknown.E {}; annotate Unknown;`
480
+ // by going through all extensions.
481
+ return root;
482
+ }
483
+
484
+ while (root) {
485
+ const keys = Object.keys(root.children);
486
+ if (keys.length !== 1 || root.children[keys[0]].definition) {
487
+ // There is either more than one sibling path, or the path is a definition.
488
+ // We MUST NOT create a common namespace for `entity A {}; entity A.A {}`!
489
+ break;
490
+ }
491
+ if (keys[0] === 'cds') {
492
+ // Don't use 'cds' as common namespace _anywhere_, not even in `namespace foo.cds.bar;`
493
+ // While our code _does_ handle such cases, as it also needs to do so for `String`, etc.,
494
+ // it would make reading to.cdl() output worse.
495
+ return this.definitionTree;
496
+ }
497
+ root = root.children[keys[0]];
498
+ }
499
+
500
+ return root;
88
501
  }
89
502
 
90
- msg.throwWithError();
91
- return cdlResult;
503
+ /**
504
+ * @param {UsingAlias[]} aliases
505
+ * @param {CdlRenderEnvironment} env
506
+ * @returns {string}
507
+ */
508
+ renderUsingAliases(aliases, env) {
509
+ if (!this.options.renderCdlDefinitionNesting) {
510
+ // openAPI importer searches for a single USING statement and replaces it.
511
+ // Let's try to be backward compatible.
512
+ return aliases.length > 0 ? `using { ${aliases.map(entry => (entry.requiresExplicitAlias()
513
+ ? `${this.quotePathIfRequired(entry.path, env)} as ${this.quoteNonIdentifierOrKeyword(entry.alias, env)}`
514
+ : entry.path)).join(', ')} };\n\n` : '';
515
+ }
516
+
517
+ let result = '';
518
+ for (const entry of aliases) {
519
+ if (entry.requiresExplicitAlias())
520
+ result += `using { ${this.quotePathIfRequired(entry.path, env)} as ${this.quoteNonIdentifierOrKeyword(entry.alias, env)} };\n`;
521
+ else
522
+ result += `using { ${entry.path } };\n`;
523
+ }
524
+ return result !== '' ? `${ result }\n` : result;
525
+ }
92
526
 
93
527
  /**
94
- * Render entries from the `csn.definitions` dictionary.
95
- * Returns an empty string if nothing is rendered.
528
+ * Render definitions in a flat list, i.e. without nesting.
96
529
  *
97
- * @return {string}
530
+ * @param {CdlRenderEnvironment} env
531
+ * @returns {string}
98
532
  */
99
- function renderDefinitions() {
533
+ renderDefinitions(env) {
100
534
  let result = '';
101
- const env = createEnv();
102
- forEachDefinition(csn, (artifact, artifactName) => {
103
- const sourceStr = renderDefinition(artifactName, artifact, env);
535
+ forEachDefinition(this.csn, (artifact, artifactName) => {
536
+ const sourceStr = this.renderDefinition(artifactName, artifact, env);
104
537
  if (sourceStr !== '')
105
538
  result += `${sourceStr}\n`;
106
539
  });
107
540
  return result;
108
541
  }
109
542
 
543
+ /**
544
+ * Render entries from the `csn.definitions` dictionary.
545
+ * Returns an empty string if nothing is rendered.
546
+ *
547
+ * @return {string}
548
+ */
549
+ renderNestedDefinitions(env) {
550
+ const that = this;
551
+ let result = '';
552
+ renderTree(this.definitionTree);
553
+ return result;
554
+
555
+ /**
556
+ * @param {DefinitionPathTree} tree
557
+ */
558
+ function renderTree( tree ) {
559
+ for (const name in tree.children) {
560
+ const entry = tree.children[name];
561
+ const def = entry.definition;
562
+
563
+ if (def?.kind === 'service' || def?.kind === 'context') {
564
+ // Render service/context with nested definitions.
565
+ env.path = [ 'definitions', entry.name ];
566
+ result += that.renderAnnotationAssignmentsAndDocComment(def, env);
567
+ result += `${env.indent}${def.kind} ${ that.renderArtifactName(entry.name, env) } {\n`;
568
+ env.increaseIndent();
569
+ env.nameEnvStack.pushNameEnv(entry);
570
+ if (entry.children)
571
+ renderTree(entry);
572
+ env.nameEnvStack.popNameEnv();
573
+ env.decreaseIndent();
574
+ if (result.at(-1) === '\n' && result.at(-2) === '\n')
575
+ result = result.substring(0, result.length - 1); // to get the closing brace on the next line after a definition, remove one linebreak
576
+ result += `${env.indent}};\n\n`;
577
+ }
578
+ else if (def) {
579
+ const sourceStr = that.renderDefinition(entry.name, def, env);
580
+ if (sourceStr !== '')
581
+ result += `${sourceStr}\n`;
582
+ if (entry.children)
583
+ renderTree(entry);
584
+ }
585
+ else if (entry.children) {
586
+ renderTree(entry);
587
+ }
588
+ }
589
+ }
590
+ }
591
+
110
592
  /**
111
593
  * Render annotation definitions, i.e. entries from csn.vocabularies.
112
594
  * Returns an empty string if there isn't anything to render.
113
595
  *
114
596
  * @param {object} vocabularies
597
+ * @param {CdlRenderEnvironment} env
115
598
  * @return {string}
116
599
  */
117
- function renderVocabularies( vocabularies ) {
600
+ renderVocabularies( vocabularies, env ) {
118
601
  let result = '';
119
- forEach(vocabularies, renderVocabulariesEntry);
602
+ for (const key in vocabularies)
603
+ result += this.renderVocabulariesEntry(key, vocabularies[key], env);
120
604
  return result;
605
+ }
121
606
 
122
- function renderVocabulariesEntry( name, anno ) {
123
- if (!anno.$ignore) {
124
- // This environment is passed down the call hierarchy, for dealing with
125
- // indentation and name resolution issues
126
- const env = createEnv({ path: [ 'vocabularies', name ] });
127
- const sourceStr = renderArtifact(name, anno, env, 'annotation');
128
- result += `${sourceStr}\n`;
129
- }
130
- }
607
+ /**
608
+ * @param {string} name
609
+ * @param anno
610
+ * @param {CdlRenderEnvironment} env
611
+ * @returns {string}
612
+ */
613
+ renderVocabulariesEntry( name, anno, env ) {
614
+ if (anno.$ignore)
615
+ return '';
616
+ // This environment is passed down the call hierarchy, for dealing with
617
+ // indentation and name resolution issues
618
+ env.path = [ 'vocabularies', name ];
619
+ const sourceStr = this.renderArtifact(name, anno, env, 'annotation');
620
+ return `${sourceStr}\n`;
131
621
  }
132
622
 
133
623
  /**
@@ -139,10 +629,10 @@ function csnToCdl( csn, options, msg ) {
139
629
  * @param {CdlRenderEnvironment} env
140
630
  * @return {string}
141
631
  */
142
- function renderExtensions( extensions, env ) {
632
+ renderExtensions( extensions, env ) {
143
633
  if (!env.path)
144
634
  env = env.cloneWith({ path: [ 'extensions' ] });
145
- return extensions.map((ext, index) => renderExtension(ext, env.withSubPath([ index ]))).join('\n');
635
+ return extensions.map((ext, index) => this.renderExtension(ext, env.withSubPath([ index ]))).join('\n');
146
636
  }
147
637
 
148
638
  /**
@@ -152,10 +642,10 @@ function csnToCdl( csn, options, msg ) {
152
642
  * @param {CdlRenderEnvironment} env
153
643
  * @return {string}
154
644
  */
155
- function renderExtension( ext, env ) {
645
+ renderExtension( ext, env ) {
156
646
  if (ext.extend)
157
- return renderExtendStatement(ext.extend, ext, env);
158
- return renderAnnotateStatement(ext, env);
647
+ return this.renderExtendStatement(ext.extend, ext, env);
648
+ return this.renderAnnotateStatement(ext, env);
159
649
  }
160
650
 
161
651
  /**
@@ -168,21 +658,21 @@ function csnToCdl( csn, options, msg ) {
168
658
  * @param {CdlRenderEnvironment} env
169
659
  * @return {string}
170
660
  */
171
- function renderExtendStatement( extName, ext, env ) {
661
+ renderExtendStatement( extName, ext, env ) {
172
662
  // Element extensions have `kind` set. Don't use for enum extension.
173
663
  const isElementExtend = (ext.kind === 'extend' && !ext.enum);
174
- let result = renderAnnotationAssignmentsAndDocComment(ext, env);
175
- extName = isElementExtend ? renderArtifactName(extName, env) : renderDefinitionReference(extName, env);
664
+ let result = this.renderAnnotationAssignmentsAndDocComment(ext, env);
665
+ extName = this.renderArtifactName(extName, env);
176
666
 
177
667
  if (ext.includes && ext.includes.length > 0) {
178
668
  // Includes can't be combined with anything in braces {}.
179
669
  const affix = isElementExtend ? 'element ' : '';
180
- const includes = ext.includes.map((inc, i) => renderDefinitionReference(inc, env.withSubPath([ 'includes', i ]))).join(', ');
670
+ const includes = ext.includes.map((inc, i) => this.renderDefinitionReference(inc, env.withSubPath([ 'includes', i ]))).join(', ');
181
671
  result += `${env.indent}extend ${affix}${extName} with ${includes};\n`;
182
672
  return result;
183
673
  }
184
674
 
185
- const typeParams = renderTypeParameters(ext, true);
675
+ const typeParams = this.renderTypeParameters(ext, true);
186
676
  if (typeParams) {
187
677
  result += `${env.indent}extend ${extName} with ${typeParams};\n`;
188
678
  return result;
@@ -201,22 +691,22 @@ function csnToCdl( csn, options, msg ) {
201
691
  // If there are actions, check if there are also elements/columns, and if so, use the prefix notation.
202
692
  const usePrefixNotation = ext.actions && (ext.columns || ext.elements);
203
693
  if (usePrefixNotation)
204
- result += `${env.indent}extend ${getExtendPrefixVariant(ext)} ${extName} with {\n`;
694
+ result += `${env.indent}extend ${this.getExtendPrefixVariant(ext)} ${extName} with {\n`;
205
695
  else
206
- result += `${env.indent}extend ${extName} with ${getExtendPostfixVariant(ext)}{\n`;
696
+ result += `${env.indent}extend ${extName} with ${this.getExtendPostfixVariant(ext)}{\n`;
207
697
 
208
698
  if (ext.columns)
209
- result += renderViewColumns(ext, env.withIncreasedIndent());
699
+ result += this.renderViewColumns(ext, env.withIncreasedIndent());
210
700
 
211
701
  else if (ext.elements || ext.enum)
212
- result += renderExtendStatementElements(ext, env);
702
+ result += this.renderExtendStatementElements(ext, env);
213
703
 
214
704
  // Not part of if/else cascade, because it may be in postfix notation.
215
705
  if (ext.actions) {
216
706
  const childEnv = env.withIncreasedIndent();
217
707
  let actions = '';
218
708
  forEach(ext.actions, (actionName, action) => {
219
- actions += renderActionOrFunction(actionName, action, childEnv.withSubPath([ 'actions', actionName ]));
709
+ actions += this.renderActionOrFunction(actionName, action, childEnv.withSubPath([ 'actions', actionName ]), true);
220
710
  });
221
711
  if (!usePrefixNotation)
222
712
  result += actions;
@@ -234,7 +724,7 @@ function csnToCdl( csn, options, msg ) {
234
724
  * @param {object} ext
235
725
  * @return {string}
236
726
  */
237
- function getExtendPrefixVariant( ext ) {
727
+ getExtendPrefixVariant( ext ) {
238
728
  if (ext.kind === 'extend')
239
729
  return 'element'; // element extensions inside an `extend`
240
730
  if (ext.columns)
@@ -250,7 +740,7 @@ function csnToCdl( csn, options, msg ) {
250
740
  * @param {CSN.Extension} ext
251
741
  * @return {string}
252
742
  */
253
- function getExtendPostfixVariant( ext ) {
743
+ getExtendPostfixVariant( ext ) {
254
744
  if (ext.columns)
255
745
  return 'columns ';
256
746
  if (ext.actions)
@@ -274,17 +764,17 @@ function csnToCdl( csn, options, msg ) {
274
764
  * @param {CdlRenderEnvironment} env
275
765
  * @return {string}
276
766
  */
277
- function renderExtendStatementElements( ext, env ) {
767
+ renderExtendStatementElements( ext, env ) {
278
768
  let result = '';
279
769
  const prop = ext.elements ? 'elements' : 'enum';
280
770
  forEach(ext[prop] || {}, (elemName, element) => {
281
771
  const childEnv = env.withIncreasedIndent().withSubPath([ 'elements', elemName ]);
282
772
  if (element.kind === 'extend')
283
- result += renderExtendStatement(elemName, element, childEnv);
773
+ result += this.renderExtendStatement(elemName, element, childEnv);
284
774
  else
285
775
  // As soon as we are inside an element, nested `extend` are not possible,
286
776
  // since we can't extend an existing element of a new one.
287
- result += renderElement(elemName, element, childEnv.withSubPath([ prop, elemName ]));
777
+ result += this.renderElement(elemName, element, childEnv.withSubPath([ prop, elemName ]));
288
778
  });
289
779
  return result;
290
780
  }
@@ -296,7 +786,7 @@ function csnToCdl( csn, options, msg ) {
296
786
  * @param {CdlRenderEnvironment} env
297
787
  * @return {string}
298
788
  */
299
- function renderAnnotateStatement( ext, env ) {
789
+ renderAnnotateStatement( ext, env ) {
300
790
  // Special case: Super annotate has both "returns" and "elements".
301
791
  // Render as separate `annotate`s, but keep the order.
302
792
  if (ext.elements && ext.returns) {
@@ -304,27 +794,27 @@ function csnToCdl( csn, options, msg ) {
304
794
 
305
795
  // The first of 'elements' or 'returns' gets all other properties as well.
306
796
  // The second only gets one property (itself).
307
- let result = renderAnnotateStatement({ ...ext, [second]: undefined }, env);
308
- result += renderAnnotateStatement({ annotate: ext.annotate, [second]: ext[second] }, env);
797
+ let result = this.renderAnnotateStatement({ ...ext, [second]: undefined }, env);
798
+ result += this.renderAnnotateStatement({ annotate: ext.annotate, [second]: ext[second] }, env);
309
799
 
310
800
  return result;
311
801
  }
312
802
 
313
803
  // Top-level annotations of the artifact
314
- let result = renderAnnotationAssignmentsAndDocComment(ext, env);
804
+ let result = this.renderAnnotationAssignmentsAndDocComment(ext, env);
315
805
  // Note: Not renderDefinitionReference, because we don't care if there
316
806
  // are annotations to unknown things. That's allowed!
317
- result += `${env.indent}annotate ${renderArtifactName(ext.annotate, env)}`;
807
+ result += `${env.indent}annotate ${this.renderArtifactName(ext.annotate, env)}`;
318
808
 
319
809
  if (ext.params)
320
- result += renderAnnotateParamsInParentheses(ext, env);
810
+ result += this.renderAnnotateParamsInParentheses(ext, env);
321
811
 
322
812
  // Element extensions and annotations (possibly nested)
323
813
  if (ext.elements || ext.enum)
324
- result += ` ${renderAnnotateStatementElements(ext, env)}`;
814
+ result += ` ${this.renderAnnotateStatementElements(ext, env)}`;
325
815
 
326
816
  else if (ext.returns)
327
- result += renderAnnotateReturns(ext, env);
817
+ result += this.renderAnnotateReturns(ext, env);
328
818
 
329
819
 
330
820
  if (ext.actions) { // Bound action annotations
@@ -334,12 +824,12 @@ function csnToCdl( csn, options, msg ) {
334
824
  for (const name in ext.actions) {
335
825
  env.path[env.path.length - 1] = name;
336
826
  const action = ext.actions[name];
337
- result += renderAnnotationAssignmentsAndDocComment(action, env) + env.indent + quoteNonIdentifierOrKeyword(name, env);
827
+ result += this.renderAnnotationAssignmentsAndDocComment(action, env) + env.indent + this.quoteNonIdentifierOrKeyword(name, env);
338
828
  // Action parameter annotations
339
829
  if (action.params)
340
- result += renderAnnotateParamsInParentheses(action, env);
830
+ result += this.renderAnnotateParamsInParentheses(action, env);
341
831
  if (action.returns)
342
- result += renderAnnotateReturns(action, env);
832
+ result += this.renderAnnotateReturns(action, env);
343
833
 
344
834
  result = removeTrailingNewline(result);
345
835
  result += ';\n';
@@ -362,7 +852,7 @@ function csnToCdl( csn, options, msg ) {
362
852
  * @param {CdlRenderEnvironment} env
363
853
  * @return {string}
364
854
  */
365
- function renderAnnotateStatementElements( ext, env ) {
855
+ renderAnnotateStatementElements( ext, env ) {
366
856
  const elements = ext.enum ? ext.enum : ext.elements;
367
857
  let result = '{\n';
368
858
  env.increaseIndent();
@@ -370,16 +860,16 @@ function csnToCdl( csn, options, msg ) {
370
860
  for (const name in elements) {
371
861
  env.path[env.path.length - 1] = name;
372
862
  const elem = elements[name];
373
- result += renderAnnotationAssignmentsAndDocComment(elem, env);
374
- result += env.indent + quoteNonIdentifierOrKeyword(name, env);
863
+ result += this.renderAnnotationAssignmentsAndDocComment(elem, env);
864
+ result += env.indent + this.quoteNonIdentifierOrKeyword(name, env);
375
865
  if (elem.elements) {
376
866
  env.path.push('elements');
377
- result += ` ${renderAnnotateStatementElements(elem, env)}`;
867
+ result += ` ${this.renderAnnotateStatementElements(elem, env)}`;
378
868
  env.path.pop();
379
869
  }
380
870
  else if (elem.enum) {
381
871
  env.path.push('enum');
382
- result += ` ${renderAnnotateStatementElements(elem, env)}`;
872
+ result += ` ${this.renderAnnotateStatementElements(elem, env)}`;
383
873
  env.path.pop();
384
874
  }
385
875
 
@@ -399,18 +889,18 @@ function csnToCdl( csn, options, msg ) {
399
889
  * @param {CdlRenderEnvironment} env
400
890
  * @return {string}
401
891
  */
402
- function renderAnnotateReturns( ext, env ) {
892
+ renderAnnotateReturns( ext, env ) {
403
893
  env = env.withSubPath([ 'returns', 'elements' ]);
404
894
  let result = ' returns';
405
895
 
406
- const returnAnnos = renderAnnotationAssignmentsAndDocComment(ext.returns, env.withIncreasedIndent());
896
+ const returnAnnos = this.renderAnnotationAssignmentsAndDocComment(ext.returns, env.withIncreasedIndent());
407
897
  if (returnAnnos)
408
898
  result += `\n${returnAnnos}`;
409
899
 
410
900
  if (ext.returns.elements) {
411
901
  // Annotations are on separate lines: Have it aligned nicely
412
902
  result += returnAnnos ? `${env.indent}` : ' ';
413
- result += renderAnnotateStatementElements(ext.returns, env);
903
+ result += this.renderAnnotateStatementElements(ext.returns, env);
414
904
  }
415
905
  return result;
416
906
  }
@@ -422,13 +912,13 @@ function csnToCdl( csn, options, msg ) {
422
912
  * @param {CdlRenderEnvironment} env
423
913
  * @return {string}
424
914
  */
425
- function renderAnnotateParamsInParentheses( ext, env ) {
915
+ renderAnnotateParamsInParentheses( ext, env ) {
426
916
  const childEnv = env.withIncreasedIndent();
427
917
  let result = '(\n';
428
918
  const paramAnnotations = [];
429
919
  forEach(ext.params, (paramName, param) => {
430
- const annos = renderAnnotationAssignmentsAndDocComment(param, childEnv);
431
- const name = quoteNonIdentifierOrKeyword(paramName, childEnv);
920
+ const annos = this.renderAnnotationAssignmentsAndDocComment(param, childEnv);
921
+ const name = this.quoteNonIdentifierOrKeyword(paramName, childEnv);
432
922
  // Not supported, yet (#13052)
433
923
  // const sub = (param.elements || param.enum) ? ` ${renderAnnotateStatementElements(param, childEnv)}` : '';
434
924
  paramAnnotations.push( annos + childEnv.indent + name);
@@ -444,32 +934,32 @@ function csnToCdl( csn, options, msg ) {
444
934
  * @param {CSN.Artifact} art
445
935
  * @param {CdlRenderEnvironment} env
446
936
  */
447
- function renderDefinition( artifactName, art, env ) {
937
+ renderDefinition( artifactName, art, env ) {
448
938
  env = env.cloneWith({ path: [ 'definitions', artifactName ] });
449
939
 
450
940
  const kind = art.kind || 'type'; // the default kind is "type".
451
941
  switch (kind) {
452
942
  case 'entity':
453
943
  if (art.query || art.projection)
454
- return renderView(artifactName, art, env);
455
- return renderArtifact(artifactName, art, env);
944
+ return this.renderView(artifactName, art, env);
945
+ return this.renderArtifact(artifactName, art, env);
456
946
  case 'aspect':
457
- return renderAspect(artifactName, art, env);
947
+ return this.renderAspect(artifactName, art, env);
458
948
 
459
949
  case 'context':
460
950
  case 'service':
461
- return renderContextOrService(artifactName, art, env);
951
+ return this.renderContextOrService(artifactName, art, env);
462
952
 
463
953
  case 'annotation': // annotation in 'csn.definitions' for compiler v1 compatibility
464
- return renderArtifact(artifactName, art, env, 'annotation');
954
+ return this.renderArtifact(artifactName, art, env, 'annotation');
465
955
 
466
956
  case 'action':
467
957
  case 'function':
468
- return renderActionOrFunction(artifactName, art, env);
958
+ return this.renderActionOrFunction(artifactName, art, env, false);
469
959
 
470
960
  case 'type':
471
961
  case 'event':
472
- return renderArtifact(artifactName, art, env);
962
+ return this.renderArtifact(artifactName, art, env);
473
963
 
474
964
  default:
475
965
  throw new ModelError(`to.cdl: Unknown artifact kind: '${art.kind}' at ${JSON.stringify(env.path)}`);
@@ -477,48 +967,49 @@ function csnToCdl( csn, options, msg ) {
477
967
  }
478
968
 
479
969
  /**
480
- * TODO: Also use this function for other kinds such as entities, aspects and views.
481
- *
482
970
  * @param {string} artifactName
483
971
  * @param {CSN.Artifact} art
484
972
  * @param {CdlRenderEnvironment} env
485
973
  * @param {string} [overrideKind] If set, override the artifact kind.
486
974
  */
487
- function renderArtifact( artifactName, art, env, overrideKind ) {
488
- let result = renderAnnotationAssignmentsAndDocComment(art, env);
975
+ renderArtifact( artifactName, art, env, overrideKind ) {
976
+ let result = this.renderAnnotationAssignmentsAndDocComment(art, env);
489
977
  let kind = overrideKind || art.$syntax === 'aspect' && 'aspect' || art.kind;
490
978
  if (art.abstract)
491
979
  kind = `abstract ${ kind }`;
492
- const normalizedArtifactName = renderArtifactName(artifactName, env);
980
+ // Vocabularies are in a separate name environment. We can't shorten them.
981
+ const normalizedArtifactName = kind !== 'annotation'
982
+ ? this.renderArtifactName(artifactName, env)
983
+ : this.quotePathIfRequired(artifactName, env);
493
984
  result += `${env.indent}${kind} ${normalizedArtifactName}`;
494
985
 
495
986
  if (art.params)
496
- result += renderParameters(art, env);
987
+ result += this.renderParameters(art, env);
497
988
 
498
989
  let isDirectStruct = false;
499
990
  const isQuery = art.query || art.projection;
500
991
  if (isQuery) {
501
992
  result += ' : ';
502
993
  // types/events (should) only support "projections"
503
- result += renderQuery(getNormalizedQuery(art).query, true, 'projection',
504
- env.withSubPath([ art.projection ? 'projection' : 'query' ]));
994
+ result += this.renderQuery(getNormalizedQuery(art).query, true, 'projection',
995
+ env.withSubPath([ art.projection ? 'projection' : 'query' ]));
505
996
  }
506
997
  else {
507
- const type = renderTypeReferenceAndProps(art, env);
998
+ const type = this.renderTypeReferenceAndProps(art, env);
508
999
  if (type) {
509
1000
  isDirectStruct = type.startsWith('{');
510
1001
 
511
1002
  if (art.includes?.length && isDirectStruct) {
512
1003
  // We can only render includes, if the type is directly structured. Otherwise, we would
513
1004
  // render e.g. `type T : Include : T2;`, which is invalid. We use `extend` in such cases.
514
- result += renderIncludes(art.includes, env);
1005
+ result += this.renderIncludes(art.includes, env);
515
1006
  }
516
1007
 
517
1008
  // For nicer output, no colon if unnamed structure is used.
518
1009
  result += (!art.type && art.elements) ? ` ${type}` : ` : ${type}`;
519
1010
  }
520
1011
  else {
521
- msg.warning('syntax-missing-type', env.path, { '#': art.kind, name: artifactName }, {
1012
+ this.msg.warning('syntax-missing-type', env.path, { '#': art.kind, name: artifactName }, {
522
1013
  std: 'Missing type for definition $(NAME); can\'t be represented in CDL',
523
1014
  entity: 'Missing elements for entity $(NAME); can\'t be represented in CDL',
524
1015
  });
@@ -530,7 +1021,7 @@ function csnToCdl( csn, options, msg ) {
530
1021
  // If there are no elements nor query, but actions, CDL syntax requires braces.
531
1022
  result += ' { }';
532
1023
  }
533
- result += renderActionsAndFunctions(art, env);
1024
+ result += this.renderBoundActionsAndFunctions(art, env);
534
1025
  }
535
1026
 
536
1027
  result += ';\n';
@@ -538,7 +1029,7 @@ function csnToCdl( csn, options, msg ) {
538
1029
  if (art.includes?.length && !isDirectStruct) {
539
1030
  // If we're not a directly structured type, render the `includes` as `extend`
540
1031
  // statements directly below the type definition.
541
- result += renderExtendStatement(artifactName, { includes: art.includes }, env);
1032
+ result += this.renderExtendStatement(artifactName, { includes: art.includes }, env);
542
1033
  }
543
1034
 
544
1035
  return result;
@@ -550,9 +1041,9 @@ function csnToCdl( csn, options, msg ) {
550
1041
  * @param {CdlRenderEnvironment} env
551
1042
  * @returns {string}
552
1043
  */
553
- function renderContextOrService( artifactName, art, env ) {
554
- let result = renderAnnotationAssignmentsAndDocComment(art, env);
555
- result += `${env.indent}${art.kind} ${renderArtifactName(artifactName, env)}`;
1044
+ renderContextOrService( artifactName, art, env ) {
1045
+ let result = this.renderAnnotationAssignmentsAndDocComment(art, env);
1046
+ result += `${env.indent}${art.kind} ${this.renderArtifactName(artifactName, env)}`;
556
1047
  return `${result} {};\n`;
557
1048
  }
558
1049
 
@@ -566,19 +1057,19 @@ function csnToCdl( csn, options, msg ) {
566
1057
  * @param {CdlRenderEnvironment} env
567
1058
  * @return {string}
568
1059
  */
569
- function renderAspect( artifactName, art, env ) {
570
- let result = renderAnnotationAssignmentsAndDocComment(art, env);
571
- result += `${env.indent}aspect ${renderArtifactName(artifactName, env)}`;
1060
+ renderAspect( artifactName, art, env ) {
1061
+ let result = this.renderAnnotationAssignmentsAndDocComment(art, env);
1062
+ result += `${env.indent}aspect ${this.renderArtifactName(artifactName, env)}`;
572
1063
  if (art.includes)
573
- result += renderIncludes(art.includes, env);
1064
+ result += this.renderIncludes(art.includes, env);
574
1065
 
575
1066
  if (art.elements)
576
- result += ` ${renderElements(art, env)}`;
1067
+ result += ` ${this.renderElements(art, env)}`;
577
1068
  else if (art.actions)
578
1069
  // if there are no elements, but actions, CDL syntax requires braces.
579
1070
  result += ' { }';
580
1071
 
581
- result += `${renderActionsAndFunctions(art, env)};\n`;
1072
+ result += `${this.renderBoundActionsAndFunctions(art, env)};\n`;
582
1073
  return result;
583
1074
  }
584
1075
 
@@ -589,11 +1080,11 @@ function csnToCdl( csn, options, msg ) {
589
1080
  * @param {CdlRenderEnvironment} env
590
1081
  * @return {string}
591
1082
  */
592
- function renderElements( artifact, env ) {
1083
+ renderElements( artifact, env ) {
593
1084
  let elements = '';
594
1085
  const childEnv = env.withIncreasedIndent();
595
1086
  for (const name in artifact.elements)
596
- elements += renderElement(name, artifact.elements[name], childEnv.withSubPath([ 'elements', name ]));
1087
+ elements += this.renderElement(name, artifact.elements[name], childEnv.withSubPath([ 'elements', name ]));
597
1088
 
598
1089
  return (elements === '') ? '{ }' : `{\n${elements}${env.indent}}`;
599
1090
  }
@@ -607,19 +1098,19 @@ function csnToCdl( csn, options, msg ) {
607
1098
  * @param {CSN.Element} element
608
1099
  * @param {CdlRenderEnvironment} env
609
1100
  */
610
- function renderElement( elementName, element, env ) {
1101
+ renderElement( elementName, element, env ) {
611
1102
  const isCalcElement = (element.value !== undefined);
612
- let result = renderAnnotationAssignmentsAndDocComment(element, env);
1103
+ let result = this.renderAnnotationAssignmentsAndDocComment(element, env);
613
1104
  result += env.indent;
614
1105
  result += element.virtual ? 'virtual ' : '';
615
1106
  result += element.key ? 'key ' : '';
616
1107
  result += element.masked ? 'masked ' : '';
617
- result += quoteNonIdentifierOrKeyword(elementName, env);
1108
+ result += this.quoteNonIdentifierOrKeyword(elementName, env);
618
1109
  if (element['#'] !== undefined) { // enum symbol reference
619
1110
  result += ` = #${element['#']}`;
620
1111
  }
621
1112
  else if (element.val !== undefined) { // enum value
622
- result += ` = ${exprRenderer.renderExpr(element, env)}`;
1113
+ result += ` = ${this.exprRenderer.renderExpr(element, env)}`;
623
1114
  }
624
1115
  else if (!isCalcElement || !isDirectAssocOrComp(element.type) && !element.$filtered && !element.$enclosed) {
625
1116
  // If the element is a calculated element _and_ a direct association or
@@ -627,7 +1118,7 @@ function csnToCdl( csn, options, msg ) {
627
1118
  // would alter the ON-condition.
628
1119
  // If it is a calculated element _and_ an indirect association (via type chain),
629
1120
  // we'd get a cast to an association.
630
- const props = renderTypeReferenceAndProps(element, env);
1121
+ const props = this.renderTypeReferenceAndProps(element, env);
631
1122
  if (props !== '')
632
1123
  result += ` : ${props}`;
633
1124
  }
@@ -637,8 +1128,8 @@ function csnToCdl( csn, options, msg ) {
637
1128
  env.path.push('value');
638
1129
  const isSubExpr = (element.value.xpr && xprContainsCondition(element.value.xpr));
639
1130
  result += isSubExpr
640
- ? exprRenderer.renderSubExpr(element.value, env)
641
- : exprRenderer.renderExpr(element.value, env);
1131
+ ? this.exprRenderer.renderSubExpr(element.value, env)
1132
+ : this.exprRenderer.renderExpr(element.value, env);
642
1133
  if (element.value.stored === true)
643
1134
  result += ' stored';
644
1135
  env.path.length -= 1;
@@ -661,10 +1152,10 @@ function csnToCdl( csn, options, msg ) {
661
1152
  * @param {CdlRenderEnvironment} env
662
1153
  * @return {string}
663
1154
  */
664
- function renderQueryElementAndEnumAnnotations( art, env ) {
665
- const annotate = collectAnnotationsOfElementsAndEnum(art, env);
1155
+ renderQueryElementAndEnumAnnotations( art, env ) {
1156
+ const annotate = this.collectAnnotationsOfElementsAndEnum(art, env);
666
1157
  if (annotate)
667
- return renderExtensions([ annotate ], env);
1158
+ return this.renderExtensions([ annotate ], env);
668
1159
  return '';
669
1160
  }
670
1161
 
@@ -676,7 +1167,7 @@ function csnToCdl( csn, options, msg ) {
676
1167
  * @param {CdlRenderEnvironment} env
677
1168
  * @return {CSN.Extension|null}
678
1169
  */
679
- function collectAnnotationsOfElementsAndEnum( artifact, env ) {
1170
+ collectAnnotationsOfElementsAndEnum( artifact, env ) {
680
1171
  // Array, which may be annotated as well.
681
1172
  if (artifact.items) {
682
1173
  env = env.withSubPath([ 'items' ]);
@@ -763,13 +1254,13 @@ function csnToCdl( csn, options, msg ) {
763
1254
  * @param {CdlRenderEnvironment} env
764
1255
  * @return {string}
765
1256
  */
766
- function renderViewSource( source, env ) {
1257
+ renderViewSource( source, env ) {
767
1258
  // Sub-SELECT
768
1259
  if (source.SELECT || source.SET) {
769
1260
  const subEnv = env.withIncreasedIndent();
770
- let result = `(\n${subEnv.indent}${renderQuery(source, false, 'view', subEnv)}\n${env.indent})`;
1261
+ let result = `(\n${subEnv.indent}${this.renderQuery(source, false, 'view', subEnv)}\n${env.indent})`;
771
1262
  if (source.as)
772
- result += renderAlias(source.as, env);
1263
+ result += this.renderAlias(source.as, env);
773
1264
 
774
1265
  return result;
775
1266
  }
@@ -777,17 +1268,17 @@ function csnToCdl( csn, options, msg ) {
777
1268
  else if (source.join) {
778
1269
  // One join operation, possibly with ON-condition
779
1270
  env.path.push('args', 0);
780
- let result = `(${renderViewSource(source.args[0], env)}`;
1271
+ let result = `(${this.renderViewSource(source.args[0], env)}`;
781
1272
  for (let i = 1; i < source.args.length; i++) {
782
1273
  env.path[env.path.length - 1] = i;
783
1274
  result += ` ${source.join} `;
784
- result += renderJoinCardinality(source.cardinality);
785
- result += `join ${renderViewSource(source.args[i], env)}`;
1275
+ result += this.renderJoinCardinality(source.cardinality);
1276
+ result += `join ${this.renderViewSource(source.args[i], env)}`;
786
1277
  }
787
1278
  env.path.length -= 2;
788
1279
  if (source.on) {
789
1280
  env.path.push('on');
790
- result += ` on ${exprRenderer.renderExpr(source.on, env.withSubPath([ 'on' ]))}`;
1281
+ result += ` on ${this.exprRenderer.renderExpr(source.on, env.withSubPath([ 'on' ]))}`;
791
1282
  env.path.length -= 1;
792
1283
  }
793
1284
  result += ')';
@@ -795,10 +1286,10 @@ function csnToCdl( csn, options, msg ) {
795
1286
  }
796
1287
  // Ordinary path, possibly with an alias
797
1288
 
798
- return renderAbsolutePathWithAlias(source, env);
1289
+ return this.renderAbsolutePathWithAlias(source, env);
799
1290
  }
800
1291
 
801
- function renderJoinCardinality( card ) {
1292
+ renderJoinCardinality( card ) {
802
1293
  let result = '';
803
1294
  if (card) {
804
1295
  if (card.srcmin && card.srcmin === 1)
@@ -822,7 +1313,7 @@ function csnToCdl( csn, options, msg ) {
822
1313
  * @param {CdlRenderEnvironment} env
823
1314
  * @return {string}
824
1315
  */
825
- function renderAbsolutePath( path, env ) {
1316
+ renderAbsolutePath( path, env ) {
826
1317
  // Sanity checks
827
1318
  if (!path.ref)
828
1319
  throw new ModelError(`Expecting ref in path: ${JSON.stringify(path)}`);
@@ -831,19 +1322,19 @@ function csnToCdl( csn, options, msg ) {
831
1322
  const firstArtifactName = path.ref[0].id || path.ref[0];
832
1323
 
833
1324
  // Render the first path step (absolute name, with different quoting/naming ..)
834
- let result = renderDefinitionReference(firstArtifactName, env);
1325
+ let result = this.renderDefinitionReference(firstArtifactName, env);
835
1326
 
836
1327
  // Even the first step might have parameters and/or a filter
837
1328
  env.path.push('ref', 0);
838
1329
  if (path.ref[0].args)
839
- result += `(${renderArguments(path.ref[0], ':', env)})`;
1330
+ result += `(${this.renderArguments(path.ref[0], ':', env)})`;
840
1331
  if (path.ref[0].where)
841
- result += renderFilterAndCardinality(path.ref[0], env);
1332
+ result += this.renderFilterAndCardinality(path.ref[0], env);
842
1333
  env.path.length -= 2;
843
1334
 
844
1335
  // Add any path steps (possibly with parameters and filters) that may follow after that
845
1336
  if (path.ref.length > 1)
846
- result += `:${exprRenderer.renderExpr({ ref: path.ref.slice(1) }, env)}`;
1337
+ result += `:${this.exprRenderer.renderExpr({ ref: path.ref.slice(1) }, env)}`;
847
1338
 
848
1339
  return result;
849
1340
  }
@@ -859,11 +1350,24 @@ function csnToCdl( csn, options, msg ) {
859
1350
  * @param {CdlRenderEnvironment} env
860
1351
  * @return {string}
861
1352
  */
862
- function renderAbsolutePathWithAlias( path, env ) {
863
- let result = renderAbsolutePath(path, env);
1353
+ renderAbsolutePathWithAlias( path, env ) {
1354
+ // We may have changed the implicit alias due to renderAbsolutePath() and renderDefinitionReference()
1355
+ // introducing USING statements. We need to ensure that the implicit alias stays the same.
1356
+ const isElementRef = path.ref.length > 1;
1357
+ const alias = path.as || implicitAs(path.ref);
1358
+
1359
+ let result = this.renderAbsolutePath(path, env);
864
1360
  if (path.as) {
865
1361
  // Source had an alias - render it
866
- result += renderAlias(path.as, env);
1362
+ result += this.renderAlias(path.as, env);
1363
+ }
1364
+ else if (!isElementRef) {
1365
+ const defName = path.ref[0].id || path.ref[0];
1366
+ const sourcePath = env.nameEnvStack.definitionReference(defName);
1367
+ // Source did not have an alias, but we add one as we'd
1368
+ // otherwise have a different implicit alias.
1369
+ if (sourcePath.split('.').at(-1) !== alias)
1370
+ result += this.renderAlias(alias, env);
867
1371
  }
868
1372
  return result;
869
1373
  }
@@ -871,16 +1375,16 @@ function csnToCdl( csn, options, msg ) {
871
1375
  /**
872
1376
  * Render the given columns.
873
1377
  *
874
- * @param {CSN.Artifact} art
1378
+ * @param {CSN.Extension | CSN.QuerySelect} art
875
1379
  * @param {object} elements
876
1380
  * @param {CdlRenderEnvironment} env
877
1381
  * @return {string}
878
1382
  */
879
- function renderViewColumns( art, env, elements = Object.create(null) ) {
1383
+ renderViewColumns( art, env, elements = Object.create(null) ) {
880
1384
  env.path.push( 'columns', -1 );
881
1385
  const result = art.columns.map((col, i) => {
882
1386
  env.path[env.path.length - 1] = i;
883
- return renderViewColumn(col, env, findElement(elements, col));
1387
+ return this.renderViewColumn(col, env, findElement(elements, col));
884
1388
  }).join(',\n');
885
1389
  env.path.length -= 2;
886
1390
  return `${result}\n`;
@@ -895,15 +1399,15 @@ function csnToCdl( csn, options, msg ) {
895
1399
  * @param {CdlRenderEnvironment} env
896
1400
  * @param {CSN.Element} element Element corresponding to the column. Generated by the compiler.
897
1401
  */
898
- function renderViewColumn( col, env, element ) {
1402
+ renderViewColumn( col, env, element ) {
899
1403
  // Annotations and column
900
1404
  let result = '';
901
1405
  if (!col.doc) {
902
1406
  // TODO: In contrast to annotations, we do not render the doc comment as part
903
1407
  // of an `annotate` statement. That may change in the future.
904
- result += renderDocComment(element, env);
1408
+ result += this.renderDocComment(element, env);
905
1409
  }
906
- result += renderAnnotationAssignmentsAndDocComment(col, env);
1410
+ result += this.renderAnnotationAssignmentsAndDocComment(col, env);
907
1411
  result += env.indent;
908
1412
 
909
1413
  // only if column is virtual, keyword virtual was present in the source text
@@ -912,26 +1416,26 @@ function csnToCdl( csn, options, msg ) {
912
1416
 
913
1417
  // Use special rendering for .expand/.inline - renderExpr cannot easily handle some cases
914
1418
  if (col.expand || col.inline)
915
- result += renderInlineExpand(col, env);
1419
+ result += this.renderInlineExpand(col, env);
916
1420
  else if (col.xpr && xprContainsCondition(col.xpr))
917
- result += exprRenderer.renderSubExpr(withoutCast(col), env);
1421
+ result += this.exprRenderer.renderSubExpr(withoutCast(col), env);
918
1422
  else
919
- result += exprRenderer.renderExpr(withoutCast(col), env);
1423
+ result += this.exprRenderer.renderExpr(withoutCast(col), env);
920
1424
 
921
1425
  // Alias for inline/expand is already handled by renderInlineExpand
922
1426
  // A new association (cast with `type` and `target`) uses `as` as its primary name, not alias.
923
1427
  const isNewAssociation = col.cast?.type && col.cast.target;
924
1428
  if (!isNewAssociation && col.as && !col.inline && !col.expand)
925
- result += renderAlias(col.as, env);
1429
+ result += this.renderAlias(col.as, env);
926
1430
 
927
1431
  // Explicit type provided for the view element?
928
1432
  if (col.cast) {
929
1433
  env.path.push('cast');
930
1434
  // Special case: Explicit association type is actually a redirect
931
1435
  if (col.cast.target && !col.cast.type)
932
- result += ` : ${renderRedirectedTo(col.cast, env)}`;
1436
+ result += ` : ${this.renderRedirectedTo(col.cast, env)}`;
933
1437
  else
934
- result += ` : ${renderTypeReferenceAndProps(col.cast, env, { typeRefOnly: true, noAnnoCollect: true })}`;
1438
+ result += ` : ${this.renderTypeReferenceAndProps(col.cast, env, { typeRefOnly: true, noAnnoCollect: true })}`;
935
1439
  env.path.length -= 1;
936
1440
  }
937
1441
  return result;
@@ -945,23 +1449,23 @@ function csnToCdl( csn, options, msg ) {
945
1449
  * @param {CdlRenderEnvironment} env
946
1450
  * @returns {string}
947
1451
  */
948
- function renderInlineExpand( obj, env ) {
1452
+ renderInlineExpand( obj, env ) {
949
1453
  // No expression to render for { * } as alias
950
- let result = (obj.as && obj.expand && !obj.ref) ? '' : exprRenderer.renderExpr(withoutCast(obj), env);
1454
+ let result = (obj.as && obj.expand && !obj.ref) ? '' : this.exprRenderer.renderExpr(withoutCast(obj), env);
951
1455
 
952
1456
  const isAnonymousExpand = (obj.expand && !obj.ref);
953
1457
 
954
1458
  // s as alias { * }
955
1459
  if (obj.as && !isAnonymousExpand)
956
- result += renderAlias(obj.as, env);
1460
+ result += this.renderAlias(obj.as, env);
957
1461
 
958
1462
  // We found a leaf - no further drilling
959
1463
  if (!obj.inline && !obj.expand) {
960
1464
  env.path.push('cast');
961
1465
  if (obj.cast && obj.cast.type)
962
- result += ` : ${renderTypeReferenceAndProps(obj.cast, env, { noAnnoCollect: true })}`;
1466
+ result += ` : ${this.renderTypeReferenceAndProps(obj.cast, env, { noAnnoCollect: true })}`;
963
1467
  else if (obj.cast && obj.cast.target) // test tbd
964
- result += ` : ${renderRedirectedTo(obj.cast, env)}`;
1468
+ result += ` : ${this.renderRedirectedTo(obj.cast, env)}`;
965
1469
  env.path.length -= 1;
966
1470
  return result;
967
1471
  }
@@ -975,7 +1479,7 @@ function csnToCdl( csn, options, msg ) {
975
1479
  const childEnv = env.withIncreasedIndent();
976
1480
  const expandInline = obj.expand || obj.inline;
977
1481
  result += expandInline //
978
- .map(elm => renderAnnotationAssignmentsAndDocComment(elm, childEnv) + childEnv.indent + renderInlineExpand(elm, childEnv))
1482
+ .map(elm => this.renderAnnotationAssignmentsAndDocComment(elm, childEnv) + childEnv.indent + this.renderInlineExpand(elm, childEnv))
979
1483
  .join(',\n');
980
1484
  result += `\n${env.indent}}`;
981
1485
 
@@ -985,7 +1489,7 @@ function csnToCdl( csn, options, msg ) {
985
1489
 
986
1490
  // { * } as expand
987
1491
  if (obj.as && isAnonymousExpand)
988
- result += renderAlias(obj.as, env);
1492
+ result += this.renderAlias(obj.as, env);
989
1493
 
990
1494
  return result;
991
1495
  }
@@ -997,7 +1501,7 @@ function csnToCdl( csn, options, msg ) {
997
1501
  * @param {CdlRenderEnvironment} env
998
1502
  * @returns {String}
999
1503
  */
1000
- function renderDocComment( obj, env ) {
1504
+ renderDocComment( obj, env ) {
1001
1505
  if (!obj || obj && obj.doc === undefined)
1002
1506
  return '';
1003
1507
  else if (obj && obj.doc === null) // empty doc comment needs to be rendered
@@ -1012,7 +1516,7 @@ function csnToCdl( csn, options, msg ) {
1012
1516
  if (!obj.doc.includes('\n') && !/(^\s)|(\s$)/.test(obj.doc))
1013
1517
  return `${env.indent}/** ${doc} */\n`;
1014
1518
 
1015
- const comment = doc.split('\n').map(line => `${env.indent} * ${line}`).join('\n');
1519
+ const comment = doc.split('\n').map(l => `${env.indent} * ${l}`).join('\n');
1016
1520
  return `${env.indent}/**\n${comment}\n${env.indent} */\n`;
1017
1521
  }
1018
1522
 
@@ -1021,20 +1525,20 @@ function csnToCdl( csn, options, msg ) {
1021
1525
  * @param {CSN.Artifact} art
1022
1526
  * @param {CdlRenderEnvironment} env
1023
1527
  */
1024
- function renderView( artifactName, art, env ) {
1528
+ renderView( artifactName, art, env ) {
1025
1529
  const syntax = (art.projection) ? 'projection' : 'entity';
1026
- let result = renderAnnotationAssignmentsAndDocComment(art, env);
1027
- result += `${env.indent}entity ${renderArtifactName(artifactName, env)}`;
1530
+ let result = this.renderAnnotationAssignmentsAndDocComment(art, env);
1531
+ result += `${env.indent}entity ${this.renderArtifactName(artifactName, env)}`;
1028
1532
  if (art.params)
1029
- result += renderParameters(art, env);
1533
+ result += this.renderParameters(art, env);
1030
1534
  result += ' as ';
1031
- result += renderQuery(getNormalizedQuery(art).query, true, syntax, env.withSubPath([ art.projection ? 'projection' : 'query' ]), art.elements);
1535
+ result += this.renderQuery(getNormalizedQuery(art).query, true, syntax, env.withSubPath([ art.projection ? 'projection' : 'query' ]), art.elements);
1032
1536
  if (art.actions) // Views/Projections also allow actions. Just the VIEW keyword variant did not.
1033
- result += renderActionsAndFunctions(art, env);
1537
+ result += this.renderBoundActionsAndFunctions(art, env);
1034
1538
  result += ';\n';
1035
- result += renderQueryElementAndEnumAnnotations(art, env);
1539
+ result += this.renderQueryElementAndEnumAnnotations(art, env);
1036
1540
  if (art.includes)
1037
- result += renderExtension({ extend: artifactName, includes: art.includes }, env);
1541
+ result += this.renderExtension({ extend: artifactName, includes: art.includes }, env);
1038
1542
  return result;
1039
1543
  }
1040
1544
 
@@ -1050,7 +1554,8 @@ function csnToCdl( csn, options, msg ) {
1050
1554
  * @param {CdlRenderEnvironment} env
1051
1555
  * @param {object} [elements]
1052
1556
  */
1053
- function renderQuery( query, isLeadingQuery, syntax, env, elements = query.elements || Object.create(null) ) {
1557
+ renderQuery( query, isLeadingQuery, syntax, env, elements = query.elements || Object.create(null) ) {
1558
+ const that = this;
1054
1559
  if (query.SET) {
1055
1560
  // Set operator, such as UNION, INTERSECT, or EXCEPT...
1056
1561
  return renderQuerySet();
@@ -1067,7 +1572,7 @@ function csnToCdl( csn, options, msg ) {
1067
1572
  result += (syntax === 'projection') ? 'projection on ' : 'select from ';
1068
1573
 
1069
1574
  env.path.push('from');
1070
- result += renderViewSource(select.from, env);
1575
+ result += this.renderViewSource(select.from, env);
1071
1576
  env.path.length -= 1;
1072
1577
 
1073
1578
  if (select.mixin) {
@@ -1076,7 +1581,7 @@ function csnToCdl( csn, options, msg ) {
1076
1581
  env.increaseIndent();
1077
1582
  forEach(select.mixin, (name, mixin) => {
1078
1583
  env.path[env.path.length - 1] = name;
1079
- elems += renderElement(name, mixin, env);
1584
+ elems += this.renderElement(name, mixin, env);
1080
1585
  });
1081
1586
  env.decreaseIndent();
1082
1587
  env.path.length -= 2;
@@ -1090,35 +1595,35 @@ function csnToCdl( csn, options, msg ) {
1090
1595
  if (select.columns) {
1091
1596
  result += ' {\n';
1092
1597
  env.increaseIndent();
1093
- result += renderViewColumns(select, env, elements);
1598
+ result += this.renderViewColumns(select, env, elements);
1094
1599
  env.decreaseIndent();
1095
1600
  result += `${env.indent}}`;
1096
1601
  }
1097
1602
 
1098
1603
  const childEnv = env.withIncreasedIndent();
1099
1604
  if (select.excluding) {
1100
- const excludes = select.excluding.map(id => `${childEnv.indent}${quoteNonIdentifierOrKeyword(id, env)}`).join(',\n');
1605
+ const excludes = select.excluding.map(id => `${childEnv.indent}${this.quoteNonIdentifierOrKeyword(id, env)}`).join(',\n');
1101
1606
  result += ` excluding {\n${excludes}\n`;
1102
1607
  result += `${env.indent}}`;
1103
1608
  }
1104
1609
 
1105
1610
  if (isLeadingQuery && query.actions)
1106
- result += renderActionsAndFunctions(query, env);
1611
+ result += this.renderBoundActionsAndFunctions(query, env);
1107
1612
 
1108
1613
  if (select.where)
1109
- result += `${continueIndent(result, env)}where ${exprRenderer.renderExpr(select.where, env.withSubPath([ 'where' ]))}`;
1614
+ result += `${continueIndent(result, env)}where ${this.exprRenderer.renderExpr(select.where, env.withSubPath([ 'where' ]))}`;
1110
1615
 
1111
1616
  if (select.groupBy)
1112
- result += `${continueIndent(result, env)}group by ${select.groupBy.map((expr, i) => exprRenderer.renderExpr(expr, env.withSubPath([ 'groupBy', i ]))).join(', ')}`;
1617
+ result += `${continueIndent(result, env)}group by ${select.groupBy.map((expr, i) => this.exprRenderer.renderExpr(expr, env.withSubPath([ 'groupBy', i ]))).join(', ')}`;
1113
1618
 
1114
1619
  if (select.having)
1115
- result += `${continueIndent(result, env)}having ${exprRenderer.renderExpr(select.having, env.withSubPath([ 'having' ]))}`;
1620
+ result += `${continueIndent(result, env)}having ${this.exprRenderer.renderExpr(select.having, env.withSubPath([ 'having' ]))}`;
1116
1621
 
1117
1622
  if (select.orderBy)
1118
- result += `${continueIndent(result, env)}order by ${select.orderBy.map((entry, i) => renderOrderByEntry(entry, env.withSubPath([ 'orderBy', i ]))).join(', ')}`;
1623
+ result += `${continueIndent(result, env)}order by ${select.orderBy.map((entry, i) => this.renderOrderByEntry(entry, env.withSubPath([ 'orderBy', i ]))).join(', ')}`;
1119
1624
 
1120
1625
  if (select.limit)
1121
- result += `${continueIndent(result, env)}${renderLimit(select.limit, env.withSubPath([ 'limit' ]))}`;
1626
+ result += `${continueIndent(result, env)}${this.renderLimit(select.limit, env.withSubPath([ 'limit' ]))}`;
1122
1627
 
1123
1628
  return result;
1124
1629
 
@@ -1147,7 +1652,7 @@ function csnToCdl( csn, options, msg ) {
1147
1652
  const subQueries = query.SET.args.map((arg, i) => {
1148
1653
  // First arg may be leading query
1149
1654
  const subEnv = env.withSubPath([ 'SET', 'args', i ]);
1150
- const subQuery = renderQuery(arg, isLeadingQuery && (i === 0), 'view', subEnv, elements);
1655
+ const subQuery = that.renderQuery(arg, isLeadingQuery && (i === 0), 'view', subEnv, elements);
1151
1656
  return `(${subQuery})`;
1152
1657
  });
1153
1658
 
@@ -1155,10 +1660,10 @@ function csnToCdl( csn, options, msg ) {
1155
1660
  // Set operation may also have an ORDER BY and LIMIT/OFFSET (in contrast to the ones belonging to
1156
1661
  // each SELECT)
1157
1662
  if (query.SET.orderBy)
1158
- setResult += `${continueIndent(setResult, env)}order by ${query.SET.orderBy.map((entry, i) => renderOrderByEntry(entry, env.withSubPath([ 'SET', 'orderBy', i ]))).join(', ')}`;
1663
+ setResult += `${continueIndent(setResult, env)}order by ${query.SET.orderBy.map((entry, i) => that.renderOrderByEntry(entry, env.withSubPath([ 'SET', 'orderBy', i ]))).join(', ')}`;
1159
1664
 
1160
1665
  if (query.SET.limit)
1161
- setResult += `${continueIndent(setResult, env)}${renderLimit(query.SET.limit, env.withSubPath([ 'SET', 'limit' ]))}`;
1666
+ setResult += `${continueIndent(setResult, env)}${that.renderLimit(query.SET.limit, env.withSubPath([ 'SET', 'limit' ]))}`;
1162
1667
  return setResult;
1163
1668
  }
1164
1669
  }
@@ -1171,8 +1676,8 @@ function csnToCdl( csn, options, msg ) {
1171
1676
  * @param {CdlRenderEnvironment} env
1172
1677
  * @return {string}
1173
1678
  */
1174
- function renderOrderByEntry( entry, env ) {
1175
- let result = renderAnnotationAssignmentsAndDocComment(entry, env) + exprRenderer.renderExpr(entry, env);
1679
+ renderOrderByEntry( entry, env ) {
1680
+ let result = this.renderAnnotationAssignmentsAndDocComment(entry, env) + this.exprRenderer.renderExpr(entry, env);
1176
1681
  if (entry.sort)
1177
1682
  result += ` ${entry.sort}`;
1178
1683
 
@@ -1189,14 +1694,14 @@ function csnToCdl( csn, options, msg ) {
1189
1694
  * @param {CdlRenderEnvironment} limitEnv
1190
1695
  * @return {string}
1191
1696
  */
1192
- function renderLimit( limit, limitEnv ) {
1697
+ renderLimit( limit, limitEnv ) {
1193
1698
  let limitStr = '';
1194
1699
  if (limit.rows !== undefined)
1195
- limitStr += `limit ${exprRenderer.renderExpr(limit.rows, limitEnv.withSubPath([ 'rows' ]))}`;
1700
+ limitStr += `limit ${this.exprRenderer.renderExpr(limit.rows, limitEnv.withSubPath([ 'rows' ]))}`;
1196
1701
 
1197
1702
  if (limit.offset !== undefined) {
1198
1703
  const offsetIndent = (limitStr === '') ? '' : `\n${limitEnv.withIncreasedIndent().indent}`;
1199
- limitStr += `${offsetIndent}offset ${exprRenderer.renderExpr(limit.offset, limitEnv.withSubPath([ 'offset' ]))}`;
1704
+ limitStr += `${offsetIndent}offset ${this.exprRenderer.renderExpr(limit.offset, limitEnv.withSubPath([ 'offset' ]))}`;
1200
1705
  }
1201
1706
  return limitStr;
1202
1707
  }
@@ -1211,12 +1716,12 @@ function csnToCdl( csn, options, msg ) {
1211
1716
  * @param {CdlRenderEnvironment} env
1212
1717
  * @return {string}
1213
1718
  */
1214
- function renderActionsAndFunctions( art, env ) {
1719
+ renderBoundActionsAndFunctions( art, env ) {
1215
1720
  let result = '';
1216
1721
  if (art.actions) {
1217
1722
  const childEnv = env.withIncreasedIndent();
1218
1723
  for (const name in art.actions)
1219
- result += renderActionOrFunction(name, art.actions[name], childEnv.withSubPath([ 'actions', name ]));
1724
+ result += this.renderActionOrFunction(name, art.actions[name], childEnv.withSubPath([ 'actions', name ]), true);
1220
1725
  result = (result === '')
1221
1726
  ? ' actions { }'
1222
1727
  : ` actions {\n${result}${env.indent}}`;
@@ -1230,18 +1735,25 @@ function csnToCdl( csn, options, msg ) {
1230
1735
  * @param {string} actionName
1231
1736
  * @param {CSN.Action} act
1232
1737
  * @param {CdlRenderEnvironment} env
1738
+ * @param {boolean} isBound
1233
1739
  * @return {string}
1234
1740
  */
1235
- function renderActionOrFunction( actionName, act, env ) {
1236
- let result = renderAnnotationAssignmentsAndDocComment(act, env) + env.indent + act.kind;
1237
- result += ` ${renderArtifactName(actionName, env)}`;
1238
- result += renderParameters(act, env);
1741
+ renderActionOrFunction( actionName, act, env, isBound ) {
1742
+ let result = this.renderAnnotationAssignmentsAndDocComment(act, env) + env.indent + act.kind;
1743
+ if (isBound) {
1744
+ // for bound actions, paths are not global
1745
+ result += ` ${this.quotePathIfRequired(actionName, env)}`;
1746
+ }
1747
+ else {
1748
+ result += ` ${this.renderArtifactName(actionName, env)}`;
1749
+ }
1750
+ result += this.renderParameters(act, env);
1239
1751
  if (act.returns) {
1240
1752
  let actEnv = env.withSubPath([ 'returns' ]);
1241
- const annos = renderAnnotationAssignmentsAndDocComment(act.returns, actEnv.withIncreasedIndent());
1753
+ const annos = this.renderAnnotationAssignmentsAndDocComment(act.returns, actEnv.withIncreasedIndent());
1242
1754
  if (annos) // if `returns` has annotations, increase indent for nicer aligned output
1243
1755
  actEnv = actEnv.withIncreasedIndent();
1244
- const type = renderTypeReferenceAndProps(act.returns, actEnv);
1756
+ const type = this.renderTypeReferenceAndProps(act.returns, actEnv);
1245
1757
  result += ` returns${annos ? '\n' : ' '}${annos}${annos ? actEnv.indent : ''}${type}`;
1246
1758
  }
1247
1759
 
@@ -1258,9 +1770,9 @@ function csnToCdl( csn, options, msg ) {
1258
1770
  * @param {CdlRenderEnvironment} env
1259
1771
  * @returns {string}
1260
1772
  */
1261
- function renderParameters( art, env ) {
1773
+ renderParameters( art, env ) {
1262
1774
  const childEnv = env.withIncreasedIndent();
1263
- const parameters = Object.keys(art.params || {}).map(name => renderParameter(name, art.params[name], childEnv));
1775
+ const parameters = Object.keys(art.params || {}).map(name => this.renderParameter(name, art.params[name], childEnv));
1264
1776
  if (parameters.length === 0)
1265
1777
  return '()';
1266
1778
  return `(\n${parameters.join(',\n')}\n${env.indent})`;
@@ -1274,10 +1786,10 @@ function csnToCdl( csn, options, msg ) {
1274
1786
  * @param {CdlRenderEnvironment} env
1275
1787
  * @return {string}
1276
1788
  */
1277
- function renderParameter( parName, par, env ) {
1789
+ renderParameter( parName, par, env ) {
1278
1790
  env = env.withSubPath( [ 'params', parName ]);
1279
- let result = `${renderAnnotationAssignmentsAndDocComment(par, env)}${env.indent}`;
1280
- result += `${quoteNonIdentifierOrKeyword(parName, env)} : ${renderTypeReferenceAndProps(par, env)}`;
1791
+ let result = `${this.renderAnnotationAssignmentsAndDocComment(par, env)}${env.indent}`;
1792
+ result += `${this.quoteNonIdentifierOrKeyword(parName, env)} : ${this.renderTypeReferenceAndProps(par, env)}`;
1281
1793
  return result;
1282
1794
  }
1283
1795
 
@@ -1293,7 +1805,7 @@ function csnToCdl( csn, options, msg ) {
1293
1805
  * @param {boolean} [config.noAnnoCollect] Do not collect annotations of sub-elements.
1294
1806
  * @return {string}
1295
1807
  */
1296
- function renderTypeReferenceAndProps( artifact, env, config = {} ) {
1808
+ renderTypeReferenceAndProps( artifact, env, config = {} ) {
1297
1809
  let result = '';
1298
1810
  const { typeRefOnly, noAnnoCollect } = config;
1299
1811
 
@@ -1307,7 +1819,7 @@ function csnToCdl( csn, options, msg ) {
1307
1819
  // "not null". Keep a reference to the outer artifact.
1308
1820
  const origArtifact = artifact;
1309
1821
  if (!artifact.type && artifact.items) {
1310
- checkArrayedArtifact(artifact, env);
1822
+ this.checkArrayedArtifact(artifact, env);
1311
1823
  result += 'many '; // alternative: 'array of'; but not used
1312
1824
  artifact = artifact.items;
1313
1825
  env = env.withSubPath([ 'items' ]);
@@ -1316,8 +1828,8 @@ function csnToCdl( csn, options, msg ) {
1316
1828
  const type = normalizeTypeRef(artifact.type);
1317
1829
 
1318
1830
  if (!type && artifact.elements) {
1319
- result += renderElements(artifact, env);
1320
- result += renderNullability(artifact.notNull);
1831
+ result += this.renderElements(artifact, env);
1832
+ result += this.renderNullability(artifact.notNull);
1321
1833
  // structured default not possible at the moment
1322
1834
  return result;
1323
1835
  }
@@ -1330,7 +1842,7 @@ function csnToCdl( csn, options, msg ) {
1330
1842
  const isComp = type === 'cds.Composition';
1331
1843
  // Type, cardinality and target; CAPire uses CamelCase
1332
1844
  result += isComp ? 'Composition' : 'Association';
1333
- result += renderCardinality(artifact);
1845
+ result += this.renderCardinality(artifact);
1334
1846
 
1335
1847
  // `targetAspect` may be set by the core compiler and refers to the original named or unnamed aspect.
1336
1848
  // In parseCdl, `target` may still be an object containing elements. This would be replaced
@@ -1338,12 +1850,12 @@ function csnToCdl( csn, options, msg ) {
1338
1850
  // If a name exists (either in target or targetAspect), prefer it over rendering elements.
1339
1851
  const elements = artifact.target?.elements || artifact.targetAspect?.elements;
1340
1852
  if (typeof artifact.target === 'string' || typeof artifact.targetAspect === 'string') {
1341
- result += renderAbsolutePath({ ref: [ artifact.target || artifact.targetAspect ] },
1342
- { ...env, additionalKeywords: [ 'MANY', 'ONE' ] });
1853
+ result += this.renderAbsolutePath({ ref: [ artifact.target || artifact.targetAspect ] },
1854
+ { ...env, additionalKeywords: [ 'MANY', 'ONE' ] });
1343
1855
  }
1344
1856
  else if (elements) {
1345
1857
  // anonymous aspect, either parseCdl or client CSN.
1346
- result += renderElements({ elements }, env.withSubPath([ artifact.target?.elements ? 'target' : 'targetAspect' ]));
1858
+ result += this.renderElements({ elements }, env.withSubPath([ artifact.target?.elements ? 'target' : 'targetAspect' ]));
1347
1859
  }
1348
1860
  else {
1349
1861
  throw new ModelError('Association/Composition is missing its target! Throwing exception to trigger recompilation.');
@@ -1351,16 +1863,16 @@ function csnToCdl( csn, options, msg ) {
1351
1863
 
1352
1864
  // ON-condition (if any)
1353
1865
  if (artifact.on)
1354
- result += ` on ${exprRenderer.renderExpr(artifact.on, env.withSubPath([ 'on' ]))}`;
1866
+ result += ` on ${this.exprRenderer.renderExpr(artifact.on, env.withSubPath([ 'on' ]))}`;
1355
1867
 
1356
1868
  // Foreign keys (if any, unless we also have an ON_condition (which means we have been transformed from managed to unmanaged)
1357
1869
  if (artifact.keys && !artifact.on)
1358
- result += ` ${ renderForeignKeys(artifact, env) }`;
1870
+ result += ` ${ this.renderForeignKeys(artifact, env) }`;
1359
1871
 
1360
1872
  if (!artifact.on) {
1361
1873
  // unmanaged associations can't be followed by "not null" or "default"
1362
- result += renderNullability(notNull);
1363
- result += renderDefaultExpr(defaultValue, env.withSubPath([ 'default' ]));
1874
+ result += this.renderNullability(notNull);
1875
+ result += this.renderDefaultExpr(defaultValue, env.withSubPath([ 'default' ]));
1364
1876
  }
1365
1877
  return result;
1366
1878
  }
@@ -1372,28 +1884,28 @@ function csnToCdl( csn, options, msg ) {
1372
1884
  // We only extract annotations of enums, if "typeRefOnly" is true. Otherwise, since
1373
1885
  // the full enum is rendered below, we would have unnecessary annotations.
1374
1886
  if (!noAnnoCollect && (!artifact.enum || typeRefOnly)) {
1375
- const annotate = collectAnnotationsOfElementsAndEnum(artifact, env);
1887
+ const annotate = this.collectAnnotationsOfElementsAndEnum(artifact, env);
1376
1888
  if (annotate)
1377
- subelementAnnotates.push(annotate);
1889
+ this.subelementAnnotates.push(annotate);
1378
1890
  }
1379
1891
 
1380
1892
  // Reference to another artifact
1381
1893
  if (typeof type === 'string') {
1382
1894
  // If we get here, it must be a named type
1383
- result += renderNamedTypeWithParameters(artifact, env);
1895
+ result += this.renderNamedTypeWithParameters(artifact, env);
1384
1896
  }
1385
1897
  else if (type?.ref) {
1386
- result += renderAbsolutePath(artifact.type, env);
1898
+ result += this.renderAbsolutePath(artifact.type, env);
1387
1899
  }
1388
1900
 
1389
1901
  if (artifact.enum && !typeRefOnly)
1390
- result += renderEnum(artifact.enum, env);
1902
+ result += this.renderEnum(artifact.enum, env);
1391
1903
 
1392
- result += renderNullability(notNull);
1904
+ result += this.renderNullability(notNull);
1393
1905
  // If there is a default value, and it's a calculated element, do not
1394
1906
  // render the default (because it's not supported for calc elements).
1395
1907
  if (defaultValue !== undefined && !artifact.value)
1396
- result += renderDefaultExpr(defaultValue, env.withSubPath([ 'default' ]));
1908
+ result += this.renderDefaultExpr(defaultValue, env.withSubPath([ 'default' ]));
1397
1909
 
1398
1910
  return result;
1399
1911
  }
@@ -1405,12 +1917,12 @@ function csnToCdl( csn, options, msg ) {
1405
1917
  * @param {CdlRenderEnvironment} env
1406
1918
  * @return {string}
1407
1919
  */
1408
- function renderRedirectedTo( art, env ) {
1409
- let result = `redirected to ${renderDefinitionReference(art.target, env)}`;
1920
+ renderRedirectedTo( art, env ) {
1921
+ let result = `redirected to ${this.renderDefinitionReference(art.target, env)}`;
1410
1922
  if (art.on)
1411
- result += ` on ${exprRenderer.renderExpr(art.on, env.withSubPath([ 'on' ]))}`;
1923
+ result += ` on ${this.exprRenderer.renderExpr(art.on, env.withSubPath([ 'on' ]))}`;
1412
1924
  else if (art.keys)
1413
- result += ` ${ renderForeignKeys(art, env) }`;
1925
+ result += ` ${ this.renderForeignKeys(art, env) }`;
1414
1926
  return result;
1415
1927
  }
1416
1928
 
@@ -1421,26 +1933,10 @@ function csnToCdl( csn, options, msg ) {
1421
1933
  * @param {CdlRenderEnvironment} env
1422
1934
  * @return {string}
1423
1935
  */
1424
- function renderNamedTypeWithParameters( artWithType, env ) {
1936
+ renderNamedTypeWithParameters( artWithType, env ) {
1425
1937
  const type = normalizeTypeRef(artWithType.type);
1426
- let result = '';
1427
-
1428
- if (isBuiltinType(type)) {
1429
- // If there is a user-defined type that starts with the same short name
1430
- // (cds.Integer -> Integer), we render the full name, including the leading "cds."
1431
- const shortHand = type.slice(4);
1432
- if (shortHand.startsWith('hana.') && hanaRequiresAbsolutePath)
1433
- result += type;
1434
- else if (usings.available.includes(shortHand))
1435
- result += type;
1436
- else
1437
- result += shortHand;
1438
- }
1439
- else {
1440
- result += renderDefinitionReference(type, env);
1441
- }
1442
-
1443
- result += renderTypeParameters(artWithType);
1938
+ let result = this.renderDefinitionReference(type, env);
1939
+ result += this.renderTypeParameters(artWithType);
1444
1940
  return result;
1445
1941
  }
1446
1942
 
@@ -1452,11 +1948,11 @@ function csnToCdl( csn, options, msg ) {
1452
1948
  * @param {CdlRenderEnvironment} env
1453
1949
  * @return {string}
1454
1950
  */
1455
- function renderEnum( enumPart, env ) {
1951
+ renderEnum( enumPart, env ) {
1456
1952
  let result = ' enum {\n';
1457
1953
  const childEnv = env.withIncreasedIndent();
1458
1954
  for (const name in enumPart)
1459
- result += renderElement(name, enumPart[name], childEnv.withSubPath([ 'enum', name ]));
1955
+ result += this.renderElement(name, enumPart[name], childEnv.withSubPath([ 'enum', name ]));
1460
1956
  result += `${env.indent}}`;
1461
1957
  return result;
1462
1958
  }
@@ -1469,14 +1965,14 @@ function csnToCdl( csn, options, msg ) {
1469
1965
  * @param {any} annoValue
1470
1966
  * @param {CdlRenderEnvironment} env
1471
1967
  */
1472
- function renderAnnotationValue( annoValue, env ) {
1968
+ renderAnnotationValue( annoValue, env ) {
1473
1969
  if (isAnnotationExpression(annoValue)) {
1474
1970
  // Once inside an expression, we stay there.
1475
- const xpr = exprRenderer.renderExpr(annoValue, env);
1971
+ const xpr = this.exprRenderer.renderExpr(annoValue, env);
1476
1972
  return `( ${xpr} )`;
1477
1973
  }
1478
1974
  else if (Array.isArray(annoValue)) {
1479
- return renderAnnotationArrayValue( annoValue, env );
1975
+ return this.renderAnnotationArrayValue( annoValue, env );
1480
1976
  }
1481
1977
  else if (typeof annoValue === 'object' && annoValue !== null) {
1482
1978
  // Enum symbol
@@ -1486,26 +1982,24 @@ function csnToCdl( csn, options, msg ) {
1486
1982
  // Shorthand for absolute path (as string)
1487
1983
  else if (annoValue['='] !== undefined) {
1488
1984
  if (annoValue['='].startsWith('@'))
1489
- return quoteAnnotationPathIfRequired(annoValue['='], env);
1490
- return quotePathIfRequired(annoValue['='], env);
1985
+ return this.quoteAnnotationPathIfRequired(annoValue['='], env);
1986
+ return this.quotePathIfRequired(annoValue['='], env);
1491
1987
  }
1492
1988
  // Shorthand for ellipsis: `... up to <val>`
1493
1989
  else if (annoValue['...'] !== undefined) {
1494
1990
  if (annoValue['...'] === true)
1495
1991
  return '...';
1496
- return `... up to ${renderAnnotationValue(annoValue['...'], env)}`;
1992
+ return `... up to ${this.renderAnnotationValue(annoValue['...'], env)}`;
1497
1993
  }
1498
1994
 
1499
1995
  // Struct value (can currently only occur within an array)
1500
1996
  // Render as one-liner if there is at most one key. Render as multi-line
1501
1997
  // struct if there are more and use nicer indentation.
1502
1998
  const keys = Object.keys(annoValue);
1503
- const childEnv = keys.length <= 1 ? env : env.withIncreasedIndent();
1504
- const values = keys.map(key => `${quoteAnnotationPathIfRequired(key, env)}: ${renderAnnotationValue(annoValue[key], childEnv.withSubPath([ key ]))}`);
1505
- if (values.length <= 1)
1506
- return `{ ${values.join(', ')} }`;
1507
- const valueList = values.join(`,\n${childEnv.indent}`);
1508
- return `{\n${childEnv.indent}${valueList}\n${env.indent}}`;
1999
+ const childEnv = env.withIncreasedIndent();
2000
+ const values = keys.map(key => `${this.quoteAnnotationPathIfRequired(key, env)}: ${this.renderAnnotationValue(annoValue[key], childEnv.withSubPath([ key ]))}`);
2001
+ const result = joinDocuments(values, [ ',', line() ]);
2002
+ return format(nestBy(env.indent.length, bracketBlock(INDENT_SIZE, '{', result, '}') ));
1509
2003
  }
1510
2004
  // Null
1511
2005
  else if (annoValue === null) {
@@ -1527,25 +2021,12 @@ function csnToCdl( csn, options, msg ) {
1527
2021
  * @param {CdlRenderEnvironment} env
1528
2022
  * @return {string}
1529
2023
  */
1530
- function renderAnnotationArrayValue( annoValue, env ) {
2024
+ renderAnnotationArrayValue( annoValue, env ) {
1531
2025
  const childEnv = env.withIncreasedIndent();
1532
2026
  // Render array parts as values.
1533
- let length = 0;
1534
- let hasLineBreak = false;
1535
- const items = annoValue.map((item, i) => {
1536
- const result = renderAnnotationValue(item, childEnv.withSubPath([ i ]));
1537
- length += result.length + 2; // just a heuristic; add 2 for `, `.
1538
- if (!hasLineBreak && result.includes('\n'))
1539
- hasLineBreak = true;
1540
- return result;
1541
- });
1542
-
1543
- if (!hasLineBreak && (length + env.indent.length) < 100) {
1544
- // Spaces required if last array value is a delimited identifier.
1545
- return `[ ${items.join(', ')} ]`;
1546
- }
1547
- const renderedItems = items.join(`,\n${childEnv.indent}`);
1548
- return `[\n${childEnv.indent}${renderedItems}\n${env.indent}]`;
2027
+ const items = annoValue.map((item, i) => this.renderAnnotationValue(item, childEnv.withSubPath([ i ])));
2028
+ const result = joinDocuments(items, [ ',', line() ]);
2029
+ return format(nestBy(env.indent.length, bracketBlock(INDENT_SIZE, '[', result, ']')));
1549
2030
  }
1550
2031
 
1551
2032
  /**
@@ -1556,14 +2037,14 @@ function csnToCdl( csn, options, msg ) {
1556
2037
  * @param {object} env
1557
2038
  * @returns {string}
1558
2039
  */
1559
- function renderPathStep( s, idx, env ) {
2040
+ renderPathStep( s, idx, env ) {
1560
2041
  // Simple id or absolute name
1561
2042
  if (typeof s === 'string') {
1562
2043
  // In first path position, do not quote $projection and magic $-variables like CURRENT_DATE, $now etc.
1563
2044
  // FIXME: We should rather explicitly recognize quoting somehow
1564
2045
  if (idx === 0 && s.startsWith('$'))
1565
2046
  return s;
1566
- return quoteNonIdentifierOrKeyword(s, env);
2047
+ return this.quoteNonIdentifierOrKeyword(s, env);
1567
2048
  }
1568
2049
  // ID with filters or parameters
1569
2050
  else if (typeof s === 'object') {
@@ -1573,16 +2054,16 @@ function csnToCdl( csn, options, msg ) {
1573
2054
 
1574
2055
  // Not really a path step but an object-like function call
1575
2056
  if (s.func)
1576
- return `${s.func}(${renderArguments(s, '=>', env)})`;
2057
+ return `${s.func}(${this.renderArguments(s, '=>', env)})`;
1577
2058
 
1578
2059
  // Path step, possibly with view parameters and/or filters
1579
- let result = `${quoteNonIdentifierOrKeyword(s.id, env)}`;
2060
+ let result = `${this.quoteNonIdentifierOrKeyword(s.id, env)}`;
1580
2061
  if (s.args) {
1581
2062
  // View parameters
1582
- result += `(${renderArguments(s, ':', env)})`;
2063
+ result += `(${this.renderArguments(s, ':', env)})`;
1583
2064
  }
1584
2065
 
1585
- result += renderFilterAndCardinality(s, env);
2066
+ result += this.renderFilterAndCardinality(s, env);
1586
2067
 
1587
2068
  return result;
1588
2069
  }
@@ -1599,13 +2080,13 @@ function csnToCdl( csn, options, msg ) {
1599
2080
  * @param {CdlRenderEnvironment} env
1600
2081
  * @returns {string}
1601
2082
  */
1602
- function renderArguments( node, sep, env ) {
2083
+ renderArguments( node, sep, env ) {
1603
2084
  if (!node.args)
1604
2085
  return '';
1605
2086
  else if (Array.isArray(node.args))
1606
- return renderPositionalArguments(node, env);
2087
+ return this.renderPositionalArguments(node, env);
1607
2088
  else if (typeof node.args === 'object')
1608
- return renderNamedArguments(node, sep, env);
2089
+ return this.renderNamedArguments(node, sep, env);
1609
2090
  throw new ModelError(`Unknown args: ${JSON.stringify(node.args)}; expected array/object`);
1610
2091
  }
1611
2092
 
@@ -1618,9 +2099,10 @@ function csnToCdl( csn, options, msg ) {
1618
2099
  * @param {CdlRenderEnvironment} env
1619
2100
  * @returns {string}
1620
2101
  */
1621
- function renderNamedArguments( node, separator, env ) {
2102
+ renderNamedArguments( node, separator, env ) {
2103
+ const that = this;
1622
2104
  return Object.keys(node.args).map(function renderNamedArgument(key) {
1623
- return `${quoteNonIdentifierOrKeyword(key, env)} ${separator} ${renderArgument(node.args[key], env.withSubPath([ 'args', key ]))}`;
2105
+ return `${that.quoteNonIdentifierOrKeyword(key, env)} ${separator} ${that.renderArgument(node.args[key], env.withSubPath([ 'args', key ]))}`;
1624
2106
  }).join(', ');
1625
2107
  }
1626
2108
 
@@ -1631,16 +2113,17 @@ function csnToCdl( csn, options, msg ) {
1631
2113
  * @param {CdlRenderEnvironment} env
1632
2114
  * @returns {string}
1633
2115
  */
1634
- function renderPositionalArguments( node, env ) {
2116
+ renderPositionalArguments( node, env ) {
1635
2117
  if (!node.args)
1636
2118
  return '';
1637
2119
  const func = node.func?.toUpperCase();
2120
+ const that = this;
1638
2121
  if (func) {
1639
2122
  return node.args.map(function renderFunctionArg(arg, i) {
1640
- return renderArgument(arg, env.withSubPath([ 'args', i ]), getKeywordsForSpecialFunctionArgument(func, i));
2123
+ return that.renderArgument(arg, env.withSubPath([ 'args', i ]), getKeywordsForSpecialFunctionArgument(func, i));
1641
2124
  }).join(', ');
1642
2125
  }
1643
- return node.args.map((arg, i) => renderArgument(arg, env.withSubPath([ 'args', i ]))).join(', ');
2126
+ return node.args.map((arg, i) => this.renderArgument(arg, env.withSubPath([ 'args', i ]))).join(', ');
1644
2127
  }
1645
2128
 
1646
2129
  /**
@@ -1652,13 +2135,13 @@ function csnToCdl( csn, options, msg ) {
1652
2135
  * @param {string[]} additionalKeywords
1653
2136
  * @return {string}
1654
2137
  */
1655
- function renderArgument( arg, env, additionalKeywords = [] ) {
2138
+ renderArgument( arg, env, additionalKeywords = [] ) {
1656
2139
  // If the argument is a xpr with e.g. `=`, it may require parentheses.
1657
2140
  // For nested xpr, `exprRenderer.renderExpr()` will already add parentheses.
1658
2141
  env = env.cloneWith({ additionalKeywords });
1659
2142
  if (isSimpleFunctionExpression(arg && arg.xpr, additionalKeywords))
1660
- return exprRenderer.renderExpr(arg, env);
1661
- return exprRenderer.renderSubExpr(arg, env);
2143
+ return this.exprRenderer.renderExpr(arg, env);
2144
+ return this.exprRenderer.renderSubExpr(arg, env);
1662
2145
  }
1663
2146
 
1664
2147
  /**
@@ -1667,10 +2150,10 @@ function csnToCdl( csn, options, msg ) {
1667
2150
  * @param artifact
1668
2151
  * @returns {string}
1669
2152
  */
1670
- function renderCardinality( artifact ) {
1671
- if (isSimpleCardinality(artifact.cardinality))
1672
- return renderSimpleCardinality(artifact);
1673
- return renderBracketCardinality(artifact);
2153
+ renderCardinality( artifact ) {
2154
+ if (this.isSimpleCardinality(artifact.cardinality))
2155
+ return this.renderSimpleCardinality(artifact);
2156
+ return this.renderBracketCardinality(artifact);
1674
2157
  }
1675
2158
 
1676
2159
  /**
@@ -1679,7 +2162,7 @@ function csnToCdl( csn, options, msg ) {
1679
2162
  * @param {CSN.Artifact} art
1680
2163
  * @return {string}
1681
2164
  */
1682
- function renderBracketCardinality( art ) {
2165
+ renderBracketCardinality( art ) {
1683
2166
  const isComp = normalizeTypeRef(art.type) === 'cds.Composition';
1684
2167
  const suffix = (isComp ? ' of ' : ' to ');
1685
2168
  const card = art.cardinality;
@@ -1705,7 +2188,7 @@ function csnToCdl( csn, options, msg ) {
1705
2188
  * @param {CSN.Cardinality} cardinality
1706
2189
  * @return {boolean}
1707
2190
  */
1708
- function isSimpleCardinality( cardinality ) {
2191
+ isSimpleCardinality( cardinality ) {
1709
2192
  return !cardinality || (
1710
2193
  cardinality.min === undefined &&
1711
2194
  cardinality.src === undefined &&
@@ -1721,7 +2204,7 @@ function csnToCdl( csn, options, msg ) {
1721
2204
  * @param {CSN.Element} elem
1722
2205
  * @return {string}
1723
2206
  */
1724
- function renderSimpleCardinality( elem ) {
2207
+ renderSimpleCardinality( elem ) {
1725
2208
  let result = (normalizeTypeRef(elem.type) === 'cds.Association' ? ' to ' : ' of ');
1726
2209
  if (!elem.cardinality)
1727
2210
  return result;
@@ -1732,24 +2215,23 @@ function csnToCdl( csn, options, msg ) {
1732
2215
  return result;
1733
2216
  }
1734
2217
 
1735
-
1736
- function renderFilterAndCardinality( s, env ) {
2218
+ renderFilterAndCardinality( s, env ) {
1737
2219
  let result = '';
1738
2220
  const cardinality = s.cardinality ? (`${s.cardinality.max}: `) : '';
1739
2221
  let filter = '';
1740
2222
 
1741
2223
  // TODO: Unify with other filter rendering for SELECT
1742
2224
  if (s.groupBy)
1743
- filter += ` group by ${s.groupBy.map((expr, i) => exprRenderer.renderExpr(expr, env.withSubPath([ 'groupBy', i ]))).join(', ')}`;
2225
+ filter += ` group by ${s.groupBy.map((expr, i) => this.exprRenderer.renderExpr(expr, env.withSubPath([ 'groupBy', i ]))).join(', ')}`;
1744
2226
  if (s.having)
1745
- filter += ` having ${exprRenderer.renderExpr(s.having, env.withSubPath([ 'having' ]))}`;
2227
+ filter += ` having ${this.exprRenderer.renderExpr(s.having, env.withSubPath([ 'having' ]))}`;
1746
2228
  if (s.orderBy)
1747
- filter += ` order by ${s.orderBy.map((entry, i) => renderOrderByEntry(entry, env.withSubPath([ 'orderBy', i ]))).join(', ')}`;
2229
+ filter += ` order by ${s.orderBy.map((entry, i) => this.renderOrderByEntry(entry, env.withSubPath([ 'orderBy', i ]))).join(', ')}`;
1748
2230
  if (s.limit)
1749
- filter += ` ${renderLimit(s.limit, env.withSubPath([ 'limit' ]))}`;
2231
+ filter += ` ${this.renderLimit(s.limit, env.withSubPath([ 'limit' ]))}`;
1750
2232
 
1751
2233
  if (s.where) {
1752
- let where = exprRenderer.renderExpr(s.where, env.withSubPath([ 'where' ]));
2234
+ let where = this.exprRenderer.renderExpr(s.where, env.withSubPath([ 'where' ]));
1753
2235
  // Special rules in CDS parser: If filter starts with one of these SQL keywords, WHERE is mandatory.
1754
2236
  if (filter || /^(?:group|having|order|limit)\s/i.test(where))
1755
2237
  where = ` where ${where}`;
@@ -1767,19 +2249,19 @@ function csnToCdl( csn, options, msg ) {
1767
2249
  return result;
1768
2250
  }
1769
2251
 
1770
- function renderDefaultExpr( defaultValue, env ) {
2252
+ renderDefaultExpr( defaultValue, env ) {
1771
2253
  if (!defaultValue)
1772
2254
  return '';
1773
2255
  let result = ' default ';
1774
2256
  if (defaultValue.xpr && xprContainsCondition( defaultValue.xpr))
1775
- result += exprRenderer.renderSubExpr(withoutCast(defaultValue), env);
2257
+ result += this.exprRenderer.renderSubExpr(withoutCast(defaultValue), env);
1776
2258
  else
1777
- result += exprRenderer.renderExpr(withoutCast(defaultValue), env);
2259
+ result += this.exprRenderer.renderExpr(withoutCast(defaultValue), env);
1778
2260
  return result;
1779
2261
  }
1780
2262
 
1781
2263
  // Render the nullability of an element or parameter (can be unset, true, or false)
1782
- function renderNullability( notNull /* , env */) {
2264
+ renderNullability( notNull /* , env */) {
1783
2265
  if (notNull === undefined) {
1784
2266
  // Attribute not set at all
1785
2267
  return '';
@@ -1794,7 +2276,7 @@ function csnToCdl( csn, options, msg ) {
1794
2276
  * @param {CdlRenderEnvironment} env
1795
2277
  * @return {string}
1796
2278
  */
1797
- function renderForeignKeys( art, env ) {
2279
+ renderForeignKeys( art, env ) {
1798
2280
  const renderedKeys = [];
1799
2281
  let hasAnnotations = false;
1800
2282
  env = env.withSubPath([ 'keys', -1 ]);
@@ -1804,14 +2286,14 @@ function csnToCdl( csn, options, msg ) {
1804
2286
  env.path[env.path.length - 1] = i;
1805
2287
  const fKey = art.keys[i];
1806
2288
 
1807
- const annos = renderAnnotationAssignmentsAndDocComment(fKey, env).trim();
2289
+ const annos = this.renderAnnotationAssignmentsAndDocComment(fKey, env).trim();
1808
2290
  if (annos) {
1809
2291
  hasAnnotations = true;
1810
2292
  renderedKeys.push(annos);
1811
2293
  }
1812
2294
 
1813
- const alias = fKey.as ? renderAlias(fKey.as, env) : '';
1814
- const key = exprRenderer.renderExpr(fKey, env);
2295
+ const alias = fKey.as ? this.renderAlias(fKey.as, env) : '';
2296
+ const key = this.exprRenderer.renderExpr(fKey, env);
1815
2297
  renderedKeys.push(`${key}${alias},`);
1816
2298
  }
1817
2299
 
@@ -1834,8 +2316,8 @@ function csnToCdl( csn, options, msg ) {
1834
2316
  * @param {CdlRenderEnvironment} env
1835
2317
  * @return {string}
1836
2318
  */
1837
- function renderAlias( alias, env ) {
1838
- return ` as ${quoteNonIdentifierOrKeyword(alias, env)}`;
2319
+ renderAlias( alias, env ) {
2320
+ return ` as ${this.quoteNonIdentifierOrKeyword(alias, env)}`;
1839
2321
  }
1840
2322
 
1841
2323
  /**
@@ -1847,7 +2329,7 @@ function csnToCdl( csn, options, msg ) {
1847
2329
  * for length instead of `(length: 10)`.
1848
2330
  * @returns {string}
1849
2331
  */
1850
- function renderTypeParameters( artWithType, noShortVersion = false ) {
2332
+ renderTypeParameters( artWithType, noShortVersion = false ) {
1851
2333
  const params = typeParameters.list.filter(param => artWithType[param] !== undefined);
1852
2334
  if (params.length === 0)
1853
2335
  return '';
@@ -1876,11 +2358,11 @@ function csnToCdl( csn, options, msg ) {
1876
2358
  * @param {{parentheses: boolean}} [config] Config for renderAnnotationAssignment()
1877
2359
  * @return {string}
1878
2360
  */
1879
- function renderAnnotationAssignmentsAndDocComment( obj, env, config ) {
1880
- let result = renderDocComment(obj, env);
2361
+ renderAnnotationAssignmentsAndDocComment( obj, env, config ) {
2362
+ let result = this.renderDocComment(obj, env);
1881
2363
  for (const name in obj) {
1882
2364
  if (name.startsWith('@'))
1883
- result += renderAnnotationAssignment(obj[name], name, env.withSubPath([ name ]), config);
2365
+ result += this.renderAnnotationAssignment(obj[name], name, env.withSubPath([ name ]), config);
1884
2366
  }
1885
2367
  return result;
1886
2368
  }
@@ -1896,7 +2378,7 @@ function csnToCdl( csn, options, msg ) {
1896
2378
  * @param {object} [config] parentheses: Whether the annotation assignment must be surrounded by parentheses.
1897
2379
  * @return {string} Rendered annotation, possibly quoted: `@![A.B.C#foo.C]: value`
1898
2380
  */
1899
- function renderAnnotationAssignment( anno, name, env, config = { parentheses: false } ) {
2381
+ renderAnnotationAssignment( anno, name, env, config = { parentheses: false } ) {
1900
2382
  name = name.substring(1);
1901
2383
  // Take the annotation assignment apart into <nameBeforeVariant>#<variantAndRest>
1902
2384
  const parts = name.split('#');
@@ -1912,14 +2394,14 @@ function csnToCdl( csn, options, msg ) {
1912
2394
  if (variant === '')
1913
2395
  nameBeforeVariant += '#';
1914
2396
 
1915
- result += quoteAnnotationPathIfRequired(nameBeforeVariant, env);
2397
+ result += this.quoteAnnotationPathIfRequired(nameBeforeVariant, env);
1916
2398
 
1917
2399
  if (variant !== undefined && variant !== '') {
1918
2400
  // Unfortunately, the compiler does not allow `.@` after the first variant identifier,
1919
2401
  // nor multiple `#`, so we're back at simple paths that are possibly quoted.
1920
- result += `#${quotePathIfRequired(variant, env)}`;
2402
+ result += `#${this.quotePathIfRequired(variant, env)}`;
1921
2403
  }
1922
- result += ` : ${renderAnnotationValue(anno, env)}`;
2404
+ result += ` : ${this.renderAnnotationValue(anno, env)}`;
1923
2405
 
1924
2406
  if (parentheses)
1925
2407
  result += ')';
@@ -1933,8 +2415,8 @@ function csnToCdl( csn, options, msg ) {
1933
2415
  * @param {CdlRenderEnvironment} env
1934
2416
  * @return {string} Artifact name ready for rendering
1935
2417
  */
1936
- function renderArtifactName( artifactName, env ) {
1937
- return quotePathIfRequired(artifactName, env);
2418
+ renderArtifactName( artifactName, env ) {
2419
+ return this.quotePathIfRequired(env.nameEnvStack.definitionName(artifactName), env);
1938
2420
  }
1939
2421
 
1940
2422
  /**
@@ -1945,9 +2427,11 @@ function csnToCdl( csn, options, msg ) {
1945
2427
  * @param {CdlRenderEnvironment} env
1946
2428
  * @return {string}
1947
2429
  */
1948
- function renderDefinitionReference( name, env ) {
1949
- usings.addIfRequired(name);
1950
- return quotePathIfRequired(name, env);
2430
+ renderDefinitionReference( name, env ) {
2431
+ if (name === '$self' && !this.csn.definitions.$self)
2432
+ return '$self';
2433
+ name = env.nameEnvStack.definitionReference(name);
2434
+ return this.quotePathIfRequired(name, env);
1951
2435
  }
1952
2436
 
1953
2437
  /**
@@ -1955,17 +2439,18 @@ function csnToCdl( csn, options, msg ) {
1955
2439
  * @param {CdlRenderEnvironment} env
1956
2440
  * @return {string}
1957
2441
  */
1958
- function renderIncludes( includes, env ) {
1959
- return ` : ${includes.map((name, i) => renderDefinitionReference(name, env.withSubPath([ 'includes', i ]))).join(', ')}`;
2442
+ renderIncludes( includes, env ) {
2443
+ return ` : ${includes.map((name, i) => this.renderDefinitionReference(name, env.withSubPath([ 'includes', i ]))).join(', ')}`;
1960
2444
  }
1961
2445
 
1962
- function createCdlExpressionRenderer() {
2446
+ createCdlExpressionRenderer() {
2447
+ const that = this;
1963
2448
  return createExpressionRenderer({
1964
2449
  finalize: x => x,
1965
2450
  typeCast(x) {
1966
- const typeRef = renderTypeReferenceAndProps(x.cast, this.env.withSubPath([ 'cast' ]), { typeRefOnly: true, noAnnoCollect: true });
2451
+ const typeRef = that.renderTypeReferenceAndProps(x.cast, this.env.withSubPath([ 'cast' ]), { typeRefOnly: true, noAnnoCollect: true });
1967
2452
  const arg = { ...x, cast: null }; // "arg" without cast to avoid recursion.
1968
- return `cast(${renderArgument(arg, this.env)} as ${typeRef})`;
2453
+ return `cast(${that.renderArgument(arg, this.env)} as ${typeRef})`;
1969
2454
  },
1970
2455
  val(x) {
1971
2456
  // Literal value, possibly with explicit 'literal' property
@@ -1992,7 +2477,7 @@ function csnToCdl( csn, options, msg ) {
1992
2477
  aliasOnly: x => x.as,
1993
2478
  enum: x => `#${x['#']}`,
1994
2479
  ref(x) {
1995
- return `${x.param ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, this.env.withSubPath([ 'ref', index ]))).join('.')}`;
2480
+ return `${x.param ? ':' : ''}${x.ref.map((step, index) => that.renderPathStep(step, index, this.env.withSubPath([ 'ref', index ]))).join('.')}`;
1996
2481
  },
1997
2482
  windowFunction(x) {
1998
2483
  const funcDef = this.func(x);
@@ -2001,10 +2486,10 @@ function csnToCdl( csn, options, msg ) {
2001
2486
  func(x) {
2002
2487
  if (keywords.cdl_functions.includes(x.func.toUpperCase()) && !x.args)
2003
2488
  return x.func;
2004
- const name = quoteFunctionIfRequired(x.func, this.env);
2489
+ const name = that.quoteFunctionIfRequired(x.func, this.env);
2005
2490
  if (!x.args) // e.g. for methods without arguments, `args` is not set at all.
2006
2491
  return `${name}`;
2007
- return `${name}(${renderArguments( x, '=>', this.env )})`;
2492
+ return `${name}(${that.renderArguments( x, '=>', this.env )})`;
2008
2493
  },
2009
2494
  xpr(x) {
2010
2495
  const xprEnv = this.env.withSubPath([ 'xpr' ]);
@@ -2017,10 +2502,10 @@ function csnToCdl( csn, options, msg ) {
2017
2502
  // For example: `select from E where id in (select from E union select from E);`:
2018
2503
  // Without parentheses, it would be different query.
2019
2504
  SET(x) {
2020
- return `(${renderQuery(x, false, 'view', this.env.withIncreasedIndent())})`;
2505
+ return `(${that.renderQuery(x, false, 'view', this.env.withIncreasedIndent())})`;
2021
2506
  },
2022
2507
  SELECT(x) {
2023
- return `(${renderQuery(x, false, 'view', this.env.withIncreasedIndent())})`;
2508
+ return `(${that.renderQuery(x, false, 'view', this.env.withIncreasedIndent())})`;
2024
2509
  },
2025
2510
  });
2026
2511
  }
@@ -2040,22 +2525,22 @@ function csnToCdl( csn, options, msg ) {
2040
2525
  * @param {CSN.Artifact} art
2041
2526
  * @param {CdlRenderEnvironment} env
2042
2527
  */
2043
- function checkArrayedArtifact( art, env ) {
2528
+ checkArrayedArtifact( art, env ) {
2044
2529
  if (!art.items)
2045
2530
  return;
2046
2531
  if (art.notNull !== undefined) {
2047
- msg.warning('def-unexpected-nullability', env.path, { prop: 'not null', otherprop: 'items' },
2048
- 'Property $(PROP) not rendered, because it can only be rendered inside $(OTHERPROP) for arrayed artifacts');
2532
+ this.msg.warning('def-unexpected-nullability', env.path, { prop: 'not null', otherprop: 'items' },
2533
+ 'Property $(PROP) not rendered, because it can only be rendered inside $(OTHERPROP) for arrayed artifacts');
2049
2534
  }
2050
2535
 
2051
2536
  if (art.items.items && !art.items.type)
2052
- msg.message('type-invalid-items', [ ...env.path, 'items' ], { '#': 'nested', prop: 'items' } );
2537
+ this.msg.message('type-invalid-items', [ ...env.path, 'items' ], { '#': 'nested', prop: 'items' } );
2053
2538
 
2054
2539
  const type = art.items.type && normalizeTypeRef(art.items.type);
2055
2540
  if (type === 'cds.Association' || type === 'cds.Composition') {
2056
2541
  // check for `art.items.target` not sufficient; could be indirect type reference
2057
2542
  const isComp = type === 'cds.Composition';
2058
- msg.message('type-invalid-items', [ ...env.path, 'items' ], { '#': isComp ? 'comp' : 'assoc', prop: 'items' });
2543
+ this.msg.message('type-invalid-items', [ ...env.path, 'items' ], { '#': isComp ? 'comp' : 'assoc', prop: 'items' });
2059
2544
  }
2060
2545
  }
2061
2546
 
@@ -2073,12 +2558,12 @@ function csnToCdl( csn, options, msg ) {
2073
2558
  * @param {CdlRenderEnvironment} env
2074
2559
  * @returns {string}
2075
2560
  */
2076
- function quotePathIfRequired( path, env ) {
2561
+ quotePathIfRequired( path, env ) {
2077
2562
  return path.split('.').map((step, index) => {
2078
2563
  if (index === 0)
2079
- return quoteNonIdentifierOrKeyword(step, env);
2564
+ return this.quoteNonIdentifierOrKeyword(step, env);
2080
2565
  else if (!undelimitedIdentifierRegex.test(step))
2081
- return delimitedId(step, env);
2566
+ return this.delimitedId(step, env);
2082
2567
  return step;
2083
2568
  }).join('.');
2084
2569
  }
@@ -2095,10 +2580,10 @@ function csnToCdl( csn, options, msg ) {
2095
2580
  * @param {CdlRenderEnvironment} env
2096
2581
  * @return {string}
2097
2582
  */
2098
- function quoteNonIdentifierOrKeyword( id, env ) {
2583
+ quoteNonIdentifierOrKeyword( id, env ) {
2099
2584
  // Quote if required for CDL
2100
2585
  if (requiresQuotingForCdl(id, env?.additionalKeywords || []))
2101
- return delimitedId(id, env);
2586
+ return this.delimitedId(id, env);
2102
2587
  return id;
2103
2588
  }
2104
2589
 
@@ -2114,9 +2599,9 @@ function csnToCdl( csn, options, msg ) {
2114
2599
  * @param {CdlRenderEnvironment} env
2115
2600
  * @return {string}
2116
2601
  */
2117
- function quoteNonIdentifier( id, env ) {
2602
+ quoteNonIdentifier( id, env ) {
2118
2603
  if (!undelimitedIdentifierRegex.test(id))
2119
- return delimitedId(id, env);
2604
+ return this.delimitedId(id, env);
2120
2605
  return id;
2121
2606
  }
2122
2607
 
@@ -2127,10 +2612,10 @@ function csnToCdl( csn, options, msg ) {
2127
2612
  * @param {CdlRenderEnvironment} env
2128
2613
  * @return {string}
2129
2614
  */
2130
- function quoteFunctionIfRequired( funcName, env ) {
2615
+ quoteFunctionIfRequired( funcName, env ) {
2131
2616
  if (cdlNewLineRegEx.test(funcName)) {
2132
- msg.error('name-invalid-identifier', env.path, {},
2133
- 'An identifier can\'t contain newline characters in CDL');
2617
+ this.msg.error('name-invalid-identifier', env.path, {},
2618
+ 'An identifier can\'t contain newline characters in CDL');
2134
2619
  }
2135
2620
  return apiSmartFunctionId(funcName);
2136
2621
  }
@@ -2145,11 +2630,11 @@ function csnToCdl( csn, options, msg ) {
2145
2630
  * @param {CdlRenderEnvironment} env
2146
2631
  * @returns {string}
2147
2632
  */
2148
- function quoteAnnotationPathIfRequired( anno, env ) {
2633
+ quoteAnnotationPathIfRequired( anno, env ) {
2149
2634
  return anno.split('.').map((segment) => {
2150
2635
  if (segment.startsWith('@'))
2151
- return `@${quoteNonIdentifier(segment.slice(1), env)}`;
2152
- return quoteNonIdentifier(segment, env);
2636
+ return `@${this.quoteNonIdentifier(segment.slice(1), env)}`;
2637
+ return this.quoteNonIdentifier(segment, env);
2153
2638
  }).join('.');
2154
2639
  }
2155
2640
 
@@ -2161,10 +2646,10 @@ function csnToCdl( csn, options, msg ) {
2161
2646
  * @param {CdlRenderEnvironment} env
2162
2647
  * @return {string}
2163
2648
  */
2164
- function delimitedId( id, env ) {
2649
+ delimitedId( id, env ) {
2165
2650
  if (cdlNewLineRegEx.test(id)) {
2166
- msg.error('name-invalid-identifier', env.path, {},
2167
- 'An identifier can\'t contain newline characters in CDL');
2651
+ this.msg.error('name-invalid-identifier', env.path, {},
2652
+ 'An identifier can\'t contain newline characters in CDL');
2168
2653
  }
2169
2654
  return apiDelimitedId(id);
2170
2655
  }
@@ -2175,6 +2660,7 @@ class CdlRenderEnvironment {
2175
2660
  path = null;
2176
2661
  elementName = null;
2177
2662
  additionalKeywords = [];
2663
+ nameEnvStack = new NameScopeStack();
2178
2664
 
2179
2665
  constructor(values) {
2180
2666
  Object.assign(this, values);
@@ -2184,10 +2670,11 @@ class CdlRenderEnvironment {
2184
2670
  this.indent = ` ${this.indent}`;
2185
2671
  }
2186
2672
  decreaseIndent() {
2187
- this.indent = this.indent.substring(0, this.indent.length - 2);
2673
+ this.indent = this.indent.substring(0, this.indent.length - INDENT_SIZE);
2188
2674
  }
2189
2675
  withIncreasedIndent() {
2190
- return new CdlRenderEnvironment({ ...this, indent: ` ${this.indent}` });
2676
+ const indent = ' '.repeat(this.indent.length + INDENT_SIZE);
2677
+ return new CdlRenderEnvironment({ ...this, indent });
2191
2678
  }
2192
2679
  withSubPath(path) {
2193
2680
  return new CdlRenderEnvironment({ ...this, path: [ ...this.path, ...path ] });
@@ -2258,8 +2745,8 @@ const conditionOperators = [
2258
2745
  // Antlr rule 'condition', 'conditionAnd'
2259
2746
  'AND', 'OR',
2260
2747
 
2261
- // Antlr rule 'conditionTerm'
2262
- '=', '<>', '>', '>=', '<', '<=', '!=',
2748
+ // redepage CdlGrammar.g4 rule 'expression'
2749
+ '=', '<>', '>', '>=', '<', '<=', '==', '!=',
2263
2750
  // These are not forbidden, since they must be preceded by one of the comparators above.
2264
2751
  // 'any', 'some', 'all',
2265
2752
 
@@ -2435,20 +2922,6 @@ function isSimpleString( str ) {
2435
2922
  !hasUnpairedUnicodeSurrogate(str));
2436
2923
  }
2437
2924
 
2438
- /**
2439
- * Get a list of top-level artifact names, which are not in contexts/usings/, i.e. those
2440
- * before the first dot ('.'). For example for `S.E.F`, `S` is used.
2441
- *
2442
- * @param {CSN.Model} csn
2443
- * @return {string[]}
2444
- */
2445
- function availableFirstPathSteps( csn ) {
2446
- if (!csn.definitions)
2447
- return [];
2448
- const unique = new Set(Object.keys(csn.definitions).map(name => name.split('.')[0]));
2449
- return Array.from(unique);
2450
- }
2451
-
2452
2925
  /**
2453
2926
  * Quotes the identifier using CDL-style ![]-quotes.
2454
2927
  *
@@ -2503,6 +2976,21 @@ function apiSmartFunctionId( funcName ) {
2503
2976
  return funcName;
2504
2977
  }
2505
2978
 
2979
+ /**
2980
+ * Render the CSN model 'model' to CDS source text.
2981
+ * Returned object has the following properties:
2982
+ * - `model`: CSN model rendered as CDL (string).
2983
+ * - `namespace`: Namespace statement + `using from './model.cds'.
2984
+ *
2985
+ * @param {CSN.Model} csn
2986
+ * @param {CdlOptions} options
2987
+ * @param {object} msg Message Functions
2988
+ */
2989
+ function csnToCdl( csn, options, msg ) {
2990
+ const renderer = new CsnToCdl(csn, options, msg);
2991
+ return renderer.render();
2992
+ }
2993
+
2506
2994
 
2507
2995
  module.exports = {
2508
2996
  csnToCdl,