@sap/cds-compiler 5.7.2 → 5.8.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 (73) hide show
  1. package/CHANGELOG.md +62 -2
  2. package/bin/cdsse.js +13 -1
  3. package/doc/CHANGELOG_BETA.md +7 -0
  4. package/lib/api/options.js +2 -1
  5. package/lib/api/validate.js +9 -0
  6. package/lib/base/location.js +1 -1
  7. package/lib/base/message-registry.js +55 -20
  8. package/lib/base/messages.js +5 -2
  9. package/lib/base/model.js +8 -6
  10. package/lib/checks/assocOutsideService.js +40 -0
  11. package/lib/checks/featureFlags.js +4 -1
  12. package/lib/checks/types.js +7 -4
  13. package/lib/checks/validator.js +3 -0
  14. package/lib/compiler/assert-consistency.js +11 -5
  15. package/lib/compiler/checks.js +79 -17
  16. package/lib/compiler/define.js +60 -3
  17. package/lib/compiler/extend.js +1 -2
  18. package/lib/compiler/generate.js +1 -1
  19. package/lib/compiler/populate.js +17 -6
  20. package/lib/compiler/propagator.js +1 -1
  21. package/lib/compiler/resolve.js +181 -150
  22. package/lib/compiler/shared.js +276 -22
  23. package/lib/compiler/tweak-assocs.js +15 -4
  24. package/lib/compiler/xpr-rewrite.js +76 -50
  25. package/lib/edm/annotations/edmJson.js +1 -1
  26. package/lib/edm/annotations/genericTranslation.js +2 -2
  27. package/lib/edm/csn2edm.js +2 -2
  28. package/lib/edm/edmPreprocessor.js +15 -9
  29. package/lib/edm/edmUtils.js +12 -5
  30. package/lib/gen/CdlGrammar.checksum +1 -0
  31. package/lib/gen/CdlParser.js +2239 -2229
  32. package/lib/gen/Dictionary.json +55 -8
  33. package/lib/json/from-csn.js +37 -17
  34. package/lib/json/to-csn.js +4 -0
  35. package/lib/language/genericAntlrParser.js +7 -0
  36. package/lib/main.d.ts +5 -1
  37. package/lib/model/cloneCsn.js +1 -0
  38. package/lib/model/csnRefs.js +1 -0
  39. package/lib/model/csnUtils.js +0 -5
  40. package/lib/modelCompare/utils/filter.js +2 -2
  41. package/lib/optionProcessor.js +2 -0
  42. package/lib/parsers/AstBuildingParser.js +72 -34
  43. package/lib/parsers/CdlGrammar.g4 +20 -19
  44. package/lib/parsers/XprTree.js +206 -0
  45. package/lib/parsers/index.js +1 -1
  46. package/lib/render/toCdl.js +61 -89
  47. package/lib/render/toSql.js +59 -29
  48. package/lib/render/utils/standardDatabaseFunctions.js +252 -15
  49. package/lib/transform/addTenantFields.js +9 -3
  50. package/lib/transform/db/assocsToQueries/transformExists.js +3 -0
  51. package/lib/transform/db/assocsToQueries/utils.js +10 -3
  52. package/lib/transform/db/expansion.js +3 -1
  53. package/lib/transform/db/flattening.js +7 -3
  54. package/lib/transform/db/killAnnotations.js +1 -0
  55. package/lib/transform/db/processSqlServices.js +70 -17
  56. package/lib/transform/draft/db.js +8 -3
  57. package/lib/transform/draft/odata.js +27 -4
  58. package/lib/transform/effective/main.js +37 -10
  59. package/lib/transform/effective/misc.js +4 -9
  60. package/lib/transform/effective/service.js +34 -0
  61. package/lib/transform/effective/types.js +28 -17
  62. package/lib/transform/forOdata.js +36 -10
  63. package/lib/transform/forRelationalDB.js +30 -18
  64. package/lib/transform/odata/adaptAnnotationRefs.js +37 -21
  65. package/lib/transform/odata/createForeignKeys.js +120 -116
  66. package/lib/transform/odata/flattening.js +10 -8
  67. package/lib/transform/transformUtils.js +58 -25
  68. package/lib/transform/translateAssocsToJoins.js +10 -6
  69. package/lib/transform/universalCsn/coreComputed.js +5 -1
  70. package/package.json +1 -1
  71. package/share/messages/message-explanations.json +1 -0
  72. package/share/messages/rewrite-not-supported.md +5 -0
  73. package/share/messages/rewrite-undefined-key.md +94 -0
@@ -45,6 +45,7 @@ tokens{ // reserved words
45
45
 
46
46
  start returns[ source = new XsnSource( 'cdl' ) ]
47
47
  :
48
+ { this.afterBrace( null, 'init' ); }<always> // init sloppy semicolon handling
48
49
  (
49
50
  ( <guard=namespaceRestriction> namespaceDeclaration[ $source ]
50
51
  | usingDeclaration[ $source ]
@@ -57,7 +58,7 @@ start returns[ source = new XsnSource( 'cdl' ) ]
57
58
 
58
59
  artifactsBlock[ art, start = undefined ]
59
60
  :
60
- '{'
61
+ '{' <prepare=afterBrace, arg=init>
61
62
  { $art.artifacts = this.createDict( $start ); $art.extensions = []; }
62
63
  (
63
64
  artifactDefOrExtend[ $art ]
@@ -235,7 +236,7 @@ aspectDef[ art, outer ]
235
236
  entityDef[ art, outer ]
236
237
  @finally{ this.attachLocation( $art ); }
237
238
  :
238
- ENTITY<prepare=afterBrace> // enable special
239
+ ENTITY
239
240
  name=namePath[ 'Entity' ]
240
241
  { this.addDef( $art, $outer, 'artifacts', 'entity', $name ); }
241
242
  { this.docComment( $art ); } annoAssignMid[ $art ]*
@@ -254,17 +255,12 @@ entityDef[ art, outer ]
254
255
  query=queryExpression
255
256
  { $art.query = $query; $art.$syntax = 'entity'; }
256
257
  |
258
+ <prepare=afterBrace, arg=sloppy> // enable special loop-exit, allow no `;`
257
259
  query=projectionSpec
258
260
  { $art.query = $query; $art.$syntax = 'projection'; }
259
261
  whereGroupByHaving[ $query ]?
260
262
  orderByLimitOffset[ $query ]?
261
- { if (this.lb().type !== '}' && ![ ';', '}', 'EOF' ].includes( this.l() ) && this.la().keyword !== 'actions') {
262
- // not worth to nicer location here - is standard error in v6
263
- this.warning( 'syntax-missing-proj-semicolon', this.la(),
264
- { expecting: [ "';'" ], offending: this.antlrName( this.la() ) },
265
- 'Missing $(EXPECTING) before $(OFFENDING)');
266
- } }
267
- <prepare=afterBrace> // disable special loop-exit, allow no `;`
263
+ {;}<prepare=afterBrace, arg=normal> // disable special loop-exit, allow no `;`
268
264
  // TODO v6: these <prepare=afterBrace>s are extremely strange
269
265
  )
270
266
  )
@@ -811,6 +807,9 @@ typeOrIncludesSpec[ art ]
811
807
  |
812
808
  ':'
813
809
  (
810
+ // Since cds-compiler v5.8; new parser only
811
+ query=projectionSpec { $art.query = $query; $art.$syntax = 'projection'; }
812
+ |
814
813
  typeExpression[ $art ]
815
814
  |
816
815
  <prefer>
@@ -1073,7 +1072,6 @@ projectionSpec returns[ default query = {} ]
1073
1072
  @finally{ this.attachLocation($query); }
1074
1073
  :
1075
1074
  PROJECTION
1076
- <prepare=afterBrace, arg=entity> // enable special loop-exit, TODO v6 delete
1077
1075
  { $query = { op: this.valueWithLocation( 'SELECT' ) }; }
1078
1076
  ON
1079
1077
  tab=fromRefWithOptAlias
@@ -1100,7 +1098,7 @@ queryExpression returns[ default expr = {} ] locals[ op, quantifier ]
1100
1098
  )
1101
1099
  query=queryExpression
1102
1100
  // with same op/quantifier: make left-assoc binary to nary:
1103
- { if ($expr.$parens || $op.val !== $expr.op.val || $quantifier?.val !== $expr.quantifier?.val) $expr = { op, args: [$expr], quantifier, location: { ...$.expr.location } }; } // TODO: ...$
1101
+ { if ($expr.$parens || $op.val !== $expr.op?.val || $quantifier?.val !== $expr.quantifier?.val) $expr = { op, args: [$expr], quantifier, location: { ...$.expr.location } }; } // TODO: ...$
1104
1102
  { $quantifier = undefined; }
1105
1103
  { $expr.args.push( $query ); this.attachLocation( $expr ); }
1106
1104
  )*
@@ -1169,9 +1167,9 @@ tableExpression returns[ default expr = {} ] // TableOrJoin
1169
1167
  (
1170
1168
  join=CROSS JOIN
1171
1169
  { if ($expr?.join?.val !== 'cross' || $expr.$parens) $expr = { op: this.valueWithLocation(), join: this.valueWithLocation( undefined, $join ), args: [ $expr ] }; }
1172
- ( tab=tableOrQueryParens { $expr.args.push( this.taggedIfQuery( $tab ) ); }
1170
+ ( tab=tableOrQueryParens { const r = this.taggedIfQuery( $tab ); if (r) $expr.args.push( r ); }
1173
1171
  { this.attachLocation( $expr ); }
1174
- | tab=fromRefWithOptAlias { $expr.args.push( $tab ); }
1172
+ | tab=fromRefWithOptAlias { if ($tab) $expr.args.push( $tab ); }
1175
1173
  { this.attachLocation( $expr ); }
1176
1174
  )
1177
1175
  |
@@ -1393,11 +1391,11 @@ selectItemDef[ columns ] locals[ art = new XsnArtifact() ]
1393
1391
  OVER { this.pushXprToken( $expr.suffix = [] ); }
1394
1392
  overClause[ $expr.suffix ]
1395
1393
  ( e=expression[ ...{ expr: $expr } ]<atAltStart>
1396
- { Object.assign( $e.location, $expr.location ); $art.value = this.attachLocation( $e )}
1394
+ { Object.assign( $e.location || {}, $expr.location ); $art.value = this.attachLocation( $e )}
1397
1395
  )?
1398
1396
  |
1399
1397
  e=expression[ ...{ expr: $expr } ]<atAltStart>
1400
- { Object.assign( $e.location, $expr.location ); $art.value = this.attachLocation( $e )}
1398
+ { Object.assign( $e.location || {}, $expr.location ); $art.value = this.attachLocation( $e )}
1401
1399
  )
1402
1400
  ( AS Id['ItemAlias'] { $art.name = this.identAst(); }
1403
1401
  | Id_restricted['ItemAlias'] { $art.name = this.fragileAlias( true ); }
@@ -1533,9 +1531,8 @@ valuePath returns[ default expr = { path: [] } ] locals[ pathItem ]
1533
1531
  )*
1534
1532
  ;
1535
1533
 
1536
- // TODO: ? params
1537
1534
  expression returns[ default expr = {} ]
1538
- //@finally{ if (!$expr?.$parens) this.attachLocation( $expr ); }
1535
+ @finally{ if (this.s == null) this.attachLocation( $expr ); }
1539
1536
  :
1540
1537
  (
1541
1538
  expressionOrQueryParens[ ...$ ]
@@ -1716,7 +1713,9 @@ options{ minTokensMatched = 1 }
1716
1713
  (
1717
1714
  ','<prepare=nextFunctionArgument>
1718
1715
  ( expr=funcExpression { $pathStep.args.push( $expr ); }
1719
- | <exitLoop> // <cond>: only before `)`
1716
+ | DeleteStarFromSet // Workaround for missing feature, see #13485, TODO
1717
+ | <exitLoop, guard=atRightParen>
1718
+ // TODO: later allow ')' <exitRule>, or ')'<mock, exitLoop>, or <exitBlock=MainAlt> with ( options{ block=MainAlt }: …)
1720
1719
  )
1721
1720
  )*
1722
1721
  ( // ORDER BY in generic functions, e.g. `first_value(id order by name)`
@@ -1725,6 +1724,8 @@ options{ minTokensMatched = 1 }
1725
1724
  orderByClauseAsXpr[ $expr.args ]
1726
1725
  { this.attachLocation( $expr ); }
1727
1726
  )?
1727
+ |
1728
+ DeleteStarFromSet // Workaround for missing feature, see #13485, TODO
1728
1729
  )?
1729
1730
  |
1730
1731
  // TODO: if we want perfect CC and error recovery, `isNamedArg` would work
@@ -1772,6 +1773,7 @@ funcExpression returns[ default expr ] locals[ args ]
1772
1773
  { $expr = this.applyOpToken(); $args = $expr.args; }
1773
1774
  e=expression { $expr.args.push( $e ); }
1774
1775
  )
1776
+ // TODO: some <restrict=Id> here?
1775
1777
  ( options{ lookahead = lGenericSeparator; }
1776
1778
  :
1777
1779
  GenericSeparator
@@ -2011,7 +2013,6 @@ annoValue returns[ default value = {} ]
2011
2013
  else { $value.struct = Object.create(null); $value.literal = 'struct'; }
2012
2014
  }
2013
2015
  (
2014
- // TODO: complain about empty loop if top-level as before
2015
2016
  // TOOL TODO → allow `<guard=…> '}'` below where the condition rejects `}`
2016
2017
  // after `{` if top-level
2017
2018
  sub=annoStructValue
@@ -0,0 +1,206 @@
1
+ // Create a hierarchical expression tree from an `xpr` array
2
+
3
+ // See ./CdlGrammar.js for the operator precedences.
4
+
5
+
6
+ 'use strict';
7
+
8
+
9
+ const prefixOperators = { // see <prec=…,prefix> in `expression` of CdlGrammar
10
+ __proto__: null,
11
+ new: 33, // special in CDL (only before ref), clarify with `.`
12
+ exists: 33, // special in CDL
13
+ '+': 30, // note: binary `.` and `over` have higher precedence!
14
+ '-': 30,
15
+ not: 8,
16
+ };
17
+
18
+ const binaryOperators = {
19
+ __proto__: null,
20
+ '.': 37, // nary
21
+ over: 35, // TODO: only after ref with arg?
22
+ '*': 24, // nary
23
+ '/': 24,
24
+ '+': 22,
25
+ '-': 22,
26
+ '||': 20,
27
+ '=': 10, // with ANY/SOME/ALL
28
+ '<>': 10,
29
+ '>': 10,
30
+ '>=': 10,
31
+ '<': 10,
32
+ '<=': 10,
33
+ '!=': 10,
34
+ '==': 10, // not supported yet in CDL or backends
35
+ and: 4,
36
+ or: 2,
37
+ // with second token or ternary (in the grammar, these ops have prec=10, but
38
+ // also assoc=none, i.e. could not be used without parens together):
39
+ is: 11, // is binary op here, not postfix
40
+ in: 13,
41
+ between: 13,
42
+ like: 13,
43
+ not: 15, // specially handled
44
+ };
45
+
46
+ const secondTokens = {
47
+ // the value is the precedence of the first token listed in `binaryOperators`
48
+ __proto__: null,
49
+ any: 10, // for `=` etc
50
+ some: 10,
51
+ all: 10,
52
+ not: 11, // for `is`
53
+ between: 15, // for `not`
54
+ in: 15,
55
+ like: 15,
56
+ };
57
+
58
+ const naryOperators = {
59
+ __proto__: null,
60
+ '.': true, // CSN-tree really as left-assoc binary?
61
+ '*': true,
62
+ '/': true,
63
+ '+': true,
64
+ '-': true,
65
+ '||': true,
66
+ and: true,
67
+ or: true,
68
+ between: 'and',
69
+ like: 'escape',
70
+ };
71
+
72
+
73
+ class XprTree {
74
+ nodes; // array value of CSN property `xpr`/`where`/…
75
+ nodeIdx = 0;
76
+ args; // corresponding XSN array, with already tree-like sub expressions
77
+ location;
78
+
79
+ constructor( nodes, args, location ) {
80
+ this.nodes = nodes;
81
+ this.args = args;
82
+ this.location = location;
83
+ }
84
+
85
+ tree() {
86
+ const args = [];
87
+ const { length } = this.args;
88
+ while (this.nodeIdx < length) {
89
+ const expr = this.expression( -1 );
90
+ if (expr)
91
+ args.push( expr );
92
+ }
93
+ return (args.length === 1) ? args[0] : this.create( args );
94
+ }
95
+
96
+ expression( parentPrec ) {
97
+ let append;
98
+ let naryOp;
99
+ let args;
100
+
101
+ // Term = ref/val or unary operator with expression as operand
102
+ let expr = this.args[this.nodeIdx];
103
+ if (!expr)
104
+ return expr;
105
+ let node = this.nodes[this.nodeIdx++];
106
+ if (typeof node === 'string') {
107
+ const prec = prefixOperators[node]; // <prec=…,prefix> in CdlGrammar
108
+ if (prec) {
109
+ const right = this.expression( prec - 1 );
110
+ if (!right)
111
+ return expr;
112
+ expr = this.create( [ expr, right ] );
113
+ }
114
+ else if (node === 'case') {
115
+ expr = this.caseWhen( [ expr ] );
116
+ }
117
+ else { // unknown token (keyword in CDL):
118
+ return expr; // …from fns with irregular syntax?
119
+ // also handles `null` as right side of `is` in `is null`
120
+ // TODO: `(` from CSN v0.x ?
121
+ // It is important not to handle binary ops after this, because otherwise
122
+ // we would not properly parse functions with irregular syntax
123
+ }
124
+ }
125
+
126
+ node = this.nodes[this.nodeIdx];
127
+ while (typeof node === 'string') {
128
+ const prec = binaryOperators[node]; // <prec=…> in CdlGrammar
129
+ if (!prec || parentPrec >= prec)
130
+ return expr;
131
+
132
+ // handle n-ary operators including ternary
133
+ append = (typeof naryOp === 'string')
134
+ ? !append && naryOp // `and` after `between`, `escape` after `like`˛
135
+ : naryOp && node; // nary operators like `+`
136
+ if (node === append) {
137
+ args.push( this.args[this.nodeIdx++] );
138
+ }
139
+ else {
140
+ naryOp = naryOperators[node];
141
+ append = false;
142
+ args = [ expr, this.args[this.nodeIdx++] ];
143
+ expr = this.create( args, naryOp === true );
144
+ }
145
+
146
+ // handle second token of operator:
147
+ const second = this.nodes[this.nodeIdx];
148
+ if (typeof second === 'string' && secondTokens[second] === prec && !append) {
149
+ args.push( this.args[this.nodeIdx++] );
150
+ if (node === 'not')
151
+ naryOp = naryOperators[second];
152
+ }
153
+
154
+ // the right side
155
+ const right = this.expression( prec );
156
+ if (!right) // incomplete
157
+ return expr;
158
+ args.push( right );
159
+ node = this.nodes[this.nodeIdx];
160
+ // TODO: simplyfy between-and and like-escape with pushTokenAndExpression()
161
+ }
162
+ return expr;
163
+ }
164
+
165
+ caseWhen( args ) {
166
+ const expr = this.create( args );
167
+ let node = this.nodes[this.nodeIdx];
168
+ if (node !== 'when') {
169
+ const value = this.expression( -1 );
170
+ if (value)
171
+ args.push( value );
172
+ node = this.nodes[this.nodeIdx];
173
+ }
174
+ while (node === 'when') {
175
+ node = this.pushTokenAndExpression( args );
176
+ if (node === 'then')
177
+ node = this.pushTokenAndExpression( args );
178
+ }
179
+ if (node === 'else')
180
+ node = this.pushTokenAndExpression( args );
181
+ if (node === 'end')
182
+ args.push( this.args[this.nodeIdx++] );
183
+ return expr;
184
+ }
185
+
186
+ pushTokenAndExpression( args ) {
187
+ args.push( this.args[this.nodeIdx++] );
188
+ const value = this.expression( -1 );
189
+ if (value)
190
+ args.push( value );
191
+ return this.nodes[this.nodeIdx];
192
+ }
193
+
194
+ create( args, isNary = false ) {
195
+ // could be adopted for CSN expression tree
196
+ return {
197
+ op: { val: (isNary ? 'nary' : 'ixpr'), location: this.location },
198
+ location: this.location,
199
+ args,
200
+ };
201
+ }
202
+ }
203
+
204
+ module.exports = {
205
+ xprAsTree: ( nodes, args, location ) => (new XprTree( nodes, args, location )).tree(),
206
+ };
@@ -21,7 +21,7 @@ function parseCdl( source, filename = '<undefined>.cds',
21
21
  options = {}, messageFunctions = null,
22
22
  rule = 'cdl' ) {
23
23
  const rulespec = rules[rule];
24
- if (!options.newParser) // TODO: (options.newParser === false)
24
+ if (!options.newParser && !options.newparser)
25
25
  return parseWithAntlr( source, filename, options, messageFunctions, rulespec );
26
26
  const { CdlParser } = gen;
27
27
  if (CdlParser.tracingParser) // tracing → direct console output of message
@@ -124,7 +124,7 @@ function csnToCdl( csn, options, msg ) {
124
124
  // This environment is passed down the call hierarchy, for dealing with
125
125
  // indentation and name resolution issues
126
126
  const env = createEnv({ path: [ 'vocabularies', name ] });
127
- const sourceStr = renderTypeOrAnnotation(name, anno, env, 'annotation');
127
+ const sourceStr = renderArtifact(name, anno, env, 'annotation');
128
128
  result += `${sourceStr}\n`;
129
129
  }
130
130
  }
@@ -452,7 +452,7 @@ function csnToCdl( csn, options, msg ) {
452
452
  case 'entity':
453
453
  if (art.query || art.projection)
454
454
  return renderView(artifactName, art, env);
455
- return renderEntity(artifactName, art, env);
455
+ return renderArtifact(artifactName, art, env);
456
456
  case 'aspect':
457
457
  return renderAspect(artifactName, art, env);
458
458
 
@@ -460,16 +460,16 @@ function csnToCdl( csn, options, msg ) {
460
460
  case 'service':
461
461
  return renderContextOrService(artifactName, art, env);
462
462
 
463
- case 'type':
464
463
  case 'annotation': // annotation in 'csn.definitions' for compiler v1 compatibility
465
- return renderTypeOrAnnotation(artifactName, art, env);
464
+ return renderArtifact(artifactName, art, env, 'annotation');
466
465
 
467
466
  case 'action':
468
467
  case 'function':
469
468
  return renderActionOrFunction(artifactName, art, env);
470
469
 
470
+ case 'type':
471
471
  case 'event':
472
- return renderEvent(artifactName, art, env);
472
+ return renderArtifact(artifactName, art, env);
473
473
 
474
474
  default:
475
475
  throw new ModelError(`to.cdl: Unknown artifact kind: '${art.kind}' at ${JSON.stringify(env.path)}`);
@@ -477,30 +477,70 @@ function csnToCdl( csn, options, msg ) {
477
477
  }
478
478
 
479
479
  /**
480
+ * TODO: Also use this function for other kinds such as entities, aspects and views.
481
+ *
480
482
  * @param {string} artifactName
481
483
  * @param {CSN.Artifact} art
482
484
  * @param {CdlRenderEnvironment} env
485
+ * @param {string} [overrideKind] If set, override the artifact kind.
483
486
  */
484
- function renderEvent( artifactName, art, env ) {
487
+ function renderArtifact( artifactName, art, env, overrideKind ) {
485
488
  let result = renderAnnotationAssignmentsAndDocComment(art, env);
489
+ let kind = overrideKind || art.$syntax === 'aspect' && 'aspect' || art.kind;
490
+ if (art.abstract)
491
+ kind = `abstract ${ kind }`;
486
492
  const normalizedArtifactName = renderArtifactName(artifactName, env);
487
- result += `${env.indent}event ${normalizedArtifactName}`;
488
- if (art.includes)
489
- result += renderIncludes(art.includes, env);
490
- if (art.query || art.projection) {
493
+ result += `${env.indent}${kind} ${normalizedArtifactName}`;
494
+
495
+ if (art.params)
496
+ result += renderParameters(art, env);
497
+
498
+ let isDirectStruct = false;
499
+ const isQuery = art.query || art.projection;
500
+ if (isQuery) {
491
501
  result += ' : ';
492
- // events (should) only support "projections"
502
+ // types/events (should) only support "projections"
493
503
  result += renderQuery(getNormalizedQuery(art).query, true, 'projection',
494
504
  env.withSubPath([ art.projection ? 'projection' : 'query' ]));
495
- result += ';\n';
496
505
  }
497
- else if (art.type) {
498
- // Derived type or annotation with non-anonymous type
499
- result += ` : ${renderTypeReferenceAndProps(art, env)};\n`;
506
+ else {
507
+ const type = renderTypeReferenceAndProps(art, env);
508
+ if (type) {
509
+ isDirectStruct = type.startsWith('{');
510
+
511
+ if (art.includes?.length && isDirectStruct) {
512
+ // We can only render includes, if the type is directly structured. Otherwise, we would
513
+ // render e.g. `type T : Include : T2;`, which is invalid. We use `extend` in such cases.
514
+ result += renderIncludes(art.includes, env);
515
+ }
516
+
517
+ // For nicer output, no colon if unnamed structure is used.
518
+ result += (!art.type && art.elements) ? ` ${type}` : ` : ${type}`;
519
+ }
520
+ else {
521
+ msg.warning('syntax-missing-type', env.path, { '#': art.kind, name: artifactName }, {
522
+ std: 'Missing type for definition $(NAME); can\'t be represented in CDL',
523
+ entity: 'Missing elements for entity $(NAME); can\'t be represented in CDL',
524
+ });
525
+ }
500
526
  }
501
- else if (art.elements) {
502
- result += ` ${renderElements(art, env)};\n`;
527
+
528
+ if (art.actions) {
529
+ if (!isQuery && !isDirectStruct) {
530
+ // If there are no elements nor query, but actions, CDL syntax requires braces.
531
+ result += ' { }';
532
+ }
533
+ result += renderActionsAndFunctions(art, env);
503
534
  }
535
+
536
+ result += ';\n';
537
+
538
+ if (art.includes?.length && !isDirectStruct) {
539
+ // If we're not a directly structured type, render the `includes` as `extend`
540
+ // statements directly below the type definition.
541
+ result += renderExtendStatement(artifactName, { includes: art.includes }, env);
542
+ }
543
+
504
544
  return result;
505
545
  }
506
546
 
@@ -516,35 +556,6 @@ function csnToCdl( csn, options, msg ) {
516
556
  return `${result} {};\n`;
517
557
  }
518
558
 
519
- /**
520
- * Render a (non-projection, non-view) entity. Return the resulting source string.
521
- *
522
- * @param {string} artifactName
523
- * @param {CSN.Artifact} art
524
- * @param {CdlRenderEnvironment} env
525
- * @return {string}
526
- */
527
- function renderEntity( artifactName, art, env ) {
528
- let result = renderAnnotationAssignmentsAndDocComment(art, env);
529
- result += env.indent + (art.abstract ? 'abstract ' : '');
530
- result += `entity ${renderArtifactName(artifactName, env)}`;
531
-
532
- if (art.params)
533
- result += renderParameters(art, env);
534
- if (art.includes)
535
- result += renderIncludes(art.includes, env);
536
-
537
- if (art.elements)
538
- result += ` ${renderElements(art, env)}`;
539
- else if (art.actions)
540
- // if there are no elements, but actions, CDL syntax requires braces.
541
- result += ' { }';
542
-
543
- result += `${renderActionsAndFunctions(art, env)};\n`;
544
-
545
- return result;
546
- }
547
-
548
559
  /**
549
560
  * Render an aspect. Return the resulting source string.
550
561
  * Behaves very similar to renderEntity, _except_ that aspects are
@@ -938,8 +949,10 @@ function csnToCdl( csn, options, msg ) {
938
949
  // No expression to render for { * } as alias
939
950
  let result = (obj.as && obj.expand && !obj.ref) ? '' : exprRenderer.renderExpr(withoutCast(obj), env);
940
951
 
952
+ const isAnonymousExpand = (obj.expand && !obj.ref);
953
+
941
954
  // s as alias { * }
942
- if (obj.as && (obj.ref || obj.xpr || obj.val !== undefined || obj.func !== undefined))
955
+ if (obj.as && !isAnonymousExpand)
943
956
  result += renderAlias(obj.as, env);
944
957
 
945
958
  // We found a leaf - no further drilling
@@ -971,7 +984,7 @@ function csnToCdl( csn, options, msg ) {
971
984
  result += ` excluding { ${obj.excluding.join(',')} }`;
972
985
 
973
986
  // { * } as expand
974
- if (!obj.ref && obj.as)
987
+ if (obj.as && isAnonymousExpand)
975
988
  result += renderAlias(obj.as, env);
976
989
 
977
990
  return result;
@@ -1268,47 +1281,6 @@ function csnToCdl( csn, options, msg ) {
1268
1281
  return result;
1269
1282
  }
1270
1283
 
1271
- /**
1272
- * Render a type (derived or structured) or an annotation decl with name 'artifactName'.
1273
- * Return the resulting source string.
1274
- *
1275
- * @param {string} artifactName
1276
- * @param {CSN.Artifact} art
1277
- * @param {CdlRenderEnvironment} env
1278
- * @param {String} [artType] Used for rendering `csn.vocabularies`, as the annotations there do not have a kind.
1279
- * @return {string}
1280
- */
1281
- function renderTypeOrAnnotation( artifactName, art, env, artType ) {
1282
- let result = renderAnnotationAssignmentsAndDocComment(art, env);
1283
- result += `${env.indent + (artType || art.$syntax || art.kind )} ${renderArtifactName(artifactName, env)}`;
1284
-
1285
- const type = renderTypeReferenceAndProps(art, env);
1286
- const isDirectStruct = type?.startsWith('{');
1287
- if (art.includes?.length && isDirectStruct)
1288
- // We can only render includes, if the type is directly structured. Otherwise, we would
1289
- // render e.g. `type T : Include : T2;`, which is invalid. We use `extend` in such cases.
1290
- result += renderIncludes(art.includes, env);
1291
-
1292
- if (type) {
1293
- // For nicer output, no colon if unnamed structure is used.
1294
- result += (!art.type && art.elements) ? ` ${type}` : ` : ${type}`;
1295
- }
1296
- else {
1297
- msg.warning('syntax-missing-type', env.path, { name: artifactName },
1298
- 'Missing type for definition $(NAME); can\'t be represented in CDL');
1299
- }
1300
-
1301
- result += ';\n';
1302
-
1303
- if (art.includes?.length && !isDirectStruct) {
1304
- // If we're not a directly structured type, render the `includes` as `extend`
1305
- // statements directly below the type definition.
1306
- result += renderExtendStatement(artifactName, { includes: art.includes }, env);
1307
- }
1308
-
1309
- return result;
1310
- }
1311
-
1312
1284
  /**
1313
1285
  * Render a reference to a type used by 'artifact' (named or inline) and (element) properties
1314
1286
  * such as `not null` and `default <xpr>`.