@sap/cds-compiler 3.0.2 → 3.1.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 (72) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/bin/.eslintrc.json +2 -1
  3. package/bin/cdsc.js +19 -0
  4. package/doc/API.md +11 -0
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +24 -2
  7. package/doc/CHANGELOG_DEPRECATED.md +21 -1
  8. package/lib/api/main.js +7 -7
  9. package/lib/api/options.js +2 -3
  10. package/lib/base/message-registry.js +17 -5
  11. package/lib/base/messages.js +18 -39
  12. package/lib/base/model.js +2 -0
  13. package/lib/checks/actionsFunctions.js +8 -7
  14. package/lib/checks/selectItems.js +96 -14
  15. package/lib/checks/types.js +5 -8
  16. package/lib/checks/validator.js +1 -2
  17. package/lib/compiler/assert-consistency.js +64 -12
  18. package/lib/compiler/base.js +6 -4
  19. package/lib/compiler/builtins.js +58 -8
  20. package/lib/compiler/checks.js +1 -1
  21. package/lib/compiler/define.js +25 -22
  22. package/lib/compiler/extend.js +16 -10
  23. package/lib/compiler/finalize-parse-cdl.js +5 -9
  24. package/lib/compiler/index.js +2 -0
  25. package/lib/compiler/populate.js +34 -31
  26. package/lib/compiler/propagator.js +11 -6
  27. package/lib/compiler/resolve.js +14 -15
  28. package/lib/compiler/shared.js +53 -26
  29. package/lib/compiler/tweak-assocs.js +5 -11
  30. package/lib/compiler/utils.js +13 -4
  31. package/lib/edm/annotations/preprocessAnnotations.js +8 -4
  32. package/lib/edm/csn2edm.js +3 -3
  33. package/lib/edm/edm.js +9 -1
  34. package/lib/edm/edmAnnoPreprocessor.js +349 -0
  35. package/lib/edm/edmInboundChecks.js +85 -0
  36. package/lib/edm/edmPreprocessor.js +295 -638
  37. package/lib/edm/edmUtils.js +85 -5
  38. package/lib/gen/Dictionary.json +29 -9
  39. package/lib/gen/language.checksum +1 -1
  40. package/lib/gen/language.interp +1 -2
  41. package/lib/gen/languageLexer.js +3 -0
  42. package/lib/gen/languageParser.js +4344 -4530
  43. package/lib/inspect/.eslintrc.json +4 -0
  44. package/lib/inspect/index.js +14 -0
  45. package/lib/inspect/inspectModelStatistics.js +81 -0
  46. package/lib/inspect/inspectPropagation.js +189 -0
  47. package/lib/inspect/inspectUtils.js +44 -0
  48. package/lib/json/from-csn.js +3 -2
  49. package/lib/json/to-csn.js +8 -6
  50. package/lib/language/genericAntlrParser.js +121 -63
  51. package/lib/language/language.g4 +19 -57
  52. package/lib/main.d.ts +1 -0
  53. package/lib/model/api.js +1 -1
  54. package/lib/model/csnRefs.js +55 -29
  55. package/lib/model/csnUtils.js +11 -7
  56. package/lib/model/revealInternalProperties.js +2 -3
  57. package/lib/modelCompare/compare.js +3 -0
  58. package/lib/optionProcessor.js +27 -0
  59. package/lib/render/toCdl.js +57 -32
  60. package/lib/render/toSql.js +24 -8
  61. package/lib/render/utils/common.js +3 -4
  62. package/lib/transform/db/associations.js +43 -35
  63. package/lib/transform/db/cdsPersistence.js +0 -1
  64. package/lib/transform/db/flattening.js +3 -4
  65. package/lib/transform/db/transformExists.js +7 -5
  66. package/lib/transform/draft/db.js +1 -1
  67. package/lib/transform/forHanaNew.js +11 -2
  68. package/lib/transform/forOdataNew.js +1 -1
  69. package/lib/transform/odata/typesExposure.js +14 -5
  70. package/lib/utils/moduleResolve.js +0 -1
  71. package/package.json +2 -2
  72. package/lib/checks/unknownMagic.js +0 -41
@@ -385,10 +385,9 @@ artifactDef[ outer, defOnly = false ] locals[ art = {} ] // cannot use `parent`
385
385
  }
386
386
  // #ATN: EXTEND elem, while CONTEXT, ENTITY etc are not reserved
387
387
  ( extendContext[ $art, $outer ]
388
- | extendEntity[ $art, $outer ]
388
+ | extendEntity[ $art, $outer ] // or aspect
389
389
  | extendProjection[ $art, $outer ]
390
390
  | extendType[ $art, $outer ]
391
- | extendAspect[ $art, $outer ]
392
391
  // Streamlined Syntax
393
392
  | extendArtifact[ $art, $outer ]
394
393
  )
@@ -544,11 +543,12 @@ projectionExclusion[ outer ] locals[ art = {} ]
544
543
  { this.addDef( $art, $outer, 'excludingDict', '', $name.id ); }
545
544
  ;
546
545
 
546
+ // also used for aspect
547
547
  extendEntity[ art, outer ] locals[ name = {} ]
548
548
  @after { /* #ATN 1 */ this.attachLocation( $art ); }
549
549
  :
550
- ENTITY simplePath[ $name, 'Extend' ]
551
- { $art.expectedKind = 'entity'; $art.name = $name;
550
+ kind=(ASPECT | ENTITY) simplePath[ $name, 'Extend' ]
551
+ { $art.expectedKind = $kind.text.toLowerCase(); $art.name = $name;
552
552
  this.addItem( $art, $outer, 'extensions', 'extend' );
553
553
  }
554
554
  (
@@ -556,7 +556,7 @@ extendEntity[ art, outer ] locals[ name = {} ]
556
556
  annotationAssignment_ll1[ $art ]*
557
557
  // ATN: the ref can start with ACTIONS
558
558
  (
559
- includeRef[ $art ]
559
+ includeRef[ $art ] ( ',' includeRef[ $art ] )*
560
560
  requiredSemi
561
561
  |
562
562
  extendForEntity[ $art ]
@@ -754,17 +754,6 @@ extendType[ art, outer ] locals[ name = {} ]
754
754
  extendWithOptElements[ $art, $art ]
755
755
  ;
756
756
 
757
- extendAspect[ art, outer ] locals[ name = {} ]
758
- @after { this.attachLocation( $art ); }
759
- :
760
- // aspects are types, i.e. kind is 'type' for aspects
761
- ASPECT simplePath[ $name, 'Extend' ]
762
- { $art.expectedKind = 'aspect'; $art.name = $name;
763
- this.addItem( $art, $outer, 'extensions', 'extend' );
764
- }
765
- extendWithOptElements[ $art, $art ]
766
- ;
767
-
768
757
  annotationDef[ art, outer ] locals[ name = {} ]
769
758
  @after { this.attachLocation( $art ); }
770
759
  :
@@ -805,7 +794,7 @@ extendArtifact[ art, outer ] locals[ name = {}, elemName = {} ]
805
794
  // #ATN: DEFINITIONS, COLUMNS, ACTIONS etc are not reserved and could be identifiers (ref).
806
795
  // TODO: exclude "expected" according to disallowElementExtension()
807
796
  (
808
- includeRef[ $art ]
797
+ includeRef[ $art ] ( ',' includeRef[ $art ] )*
809
798
  requiredSemi
810
799
  |
811
800
  '{' { $art.elements = this.createDict(); }
@@ -860,7 +849,7 @@ extendWithOptElements[ art ]
860
849
  WITH { this.noSemicolonHere(); this.docComment( $art ); }
861
850
  annotationAssignment_ll1[ $art ]*
862
851
  (
863
- includeRef[ $art ]
852
+ includeRef[ $art ] ( ',' includeRef[ $art ] )*
864
853
  requiredSemi
865
854
  |
866
855
  '{' { $art.elements = this.createDict(); }
@@ -1558,46 +1547,14 @@ typeSpecSemi[ art ] // with 'includes', for type and annotation defs
1558
1547
  |
1559
1548
  // alt lookahead includes MANY '{'
1560
1549
  { $art.type = {}; }
1550
+ // Can't use typeRefOptArgs because of clash with include rule below (ATN would change)
1561
1551
  simplePath[ $art.type, 'artref' ]
1562
1552
  (
1563
- typeRefArgs[ $art ]
1564
- { this.docComment( $art ); }
1565
- annotationAssignment_ll1[ $art ]*
1566
- (
1567
- ENUM '{' { $art.enum = this.createDict(); }
1568
- enumSymbolDef[ $art ]*
1569
- '}' { this.finalizeDictOrArray( $art.enum ); }
1570
- (
1571
- optionalSemi
1572
- |
1573
- defaultValue[ $art ]
1574
- requiredSemi
1575
- )
1576
- |
1577
- defaultValue[ $art ]?
1578
- requiredSemi
1579
- )
1580
- |
1581
- ':' // with element, e.g. `type T : E:elem enum { ... }`
1582
- { $art.type.scope = $art.type.path.length; }
1583
- simplePath[ $art.type, 'ref']
1584
- { this.docComment( $art ); }
1585
- annotationAssignment_ll1[ $art ]*
1586
- (
1587
- ENUM '{' { $art.enum = this.createDict(); }
1588
- enumSymbolDef[ $art ]*
1589
- '}' { this.finalizeDictOrArray( $art.enum ); }
1590
- (
1591
- optionalSemi
1592
- |
1593
- defaultValue[ $art ]
1594
- requiredSemi
1595
- )
1596
- |
1597
- defaultValue[ $art ]?
1598
- requiredSemi
1599
- )
1600
- |
1553
+ ( typeRefArgs[ $art ]
1554
+ | ':' // with element, e.g. `type T : E:elem enum { ... }`
1555
+ { $art.type.scope = $art.type.path.length; }
1556
+ simplePath[ $art.type, 'ref']
1557
+ )?
1601
1558
  { this.docComment( $art ); }
1602
1559
  annotationAssignment_ll1[ $art ]*
1603
1560
  (
@@ -2481,7 +2438,12 @@ pathArguments[ pathStep, considerSpecial ]
2481
2438
  '(' // dict or array, see below
2482
2439
  // Make sure that we do not introduce A:B paths in expressions!
2483
2440
  // Need to avoid adaptPredict(), otherwise Generic keywords won't work in funcExpression
2484
- { this.setLocalTokenForId( { ':': 'HelperToken1', '=>': 'HelperToken2' } ); }
2441
+ //
2442
+ // For code completion, we need to handle generic tokens directly after the
2443
+ // '('. To avoid invalidating an assoc `trim` to an entity with parameter
2444
+ // `leading` (ok, a bit constructed), we do not do it with named parameters.
2445
+ { if (!this.setLocalTokenForId( { ':': 'HelperToken1', '=>': 'HelperToken2' } ))
2446
+ this.prepareGenericKeywords( $considerSpecial ); }
2485
2447
  (
2486
2448
  { $pathStep.args = this.createDict(); $pathStep['$'+'syntax'] = ':'; }
2487
2449
  id=HelperToken1 ':'
package/lib/main.d.ts CHANGED
@@ -896,6 +896,7 @@ declare namespace compiler {
896
896
  * but not `cds.foundation.*`.
897
897
  *
898
898
  * @param definitionName Top-level definition name of the artifact.
899
+ * @since v2.14.0
899
900
  */
900
901
  function isInReservedNamespace(definitionName: string): boolean;
901
902
  }
package/lib/model/api.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * Each function in `userFunctions` and `defaultFunctions` is called with:
12
12
  * - `userFunctions`
13
13
  * - the current CSN node, i.e. ‹parent node›.‹property name›
14
- * - the the ‹parent node›
14
+ * - the ‹parent node›
15
15
  * - the ‹property name› (might be useful if the same function is used for several props)
16
16
  */
17
17
  const defaultFunctions = {
@@ -210,8 +210,11 @@ const referenceSemantics = {
210
210
  ref_where: { lexical: justDollar , dynamic: 'ref-target'}, // ...using baseEnv
211
211
  on: { lexical: justDollar, dynamic: 'query' }, // assoc defs, redirected to
212
212
  // there are also 'on_join' and 'on_mixin' with default semantics
213
- orderBy: { lexical: query => query, dynamic: 'query' },
214
- orderBy_set: { lexical: query => query.$next, dynamic: 'query' }, // to outer SELECT (from UNION)
213
+ orderBy_ref: { lexical: query => query, dynamic: 'query' },
214
+ orderBy_expr: { lexical: query => query, dynamic: 'source' }, // ref in ORDER BY expression
215
+ orderBy_set_ref: { lexical: query => query.$next, dynamic: 'query' }, // to outer SELECT (from UNION)
216
+ // refs in ORDER BY expr in UNION not really allowed - only with table alias (of outer queries) or $self
217
+ orderBy_set_expr: { lexical: query => query.$next, dynamic: false },
215
218
  // default: { lexical: query => query, dynamic: 'source' }
216
219
  }
217
220
 
@@ -551,37 +554,39 @@ function csnRefs( csn, universalReady ) {
551
554
  }
552
555
  }
553
556
  // now the dynamic environment: ------------------------------------------
554
- if (semantics.dynamic === 'target') { // ref in keys
555
- const target = assocTarget( parent, refCtx );
556
- return resolvePath( path, target.elements[head], target, 'target' );
557
- }
558
- if (baseEnv) // ref-target (filter condition), expand, inline
559
- return resolvePath( path, baseEnv.elements[head], baseEnv, semantics.dynamic );
560
- if (!query) { // outside queries - TODO: items?
561
- let art = parent.elements[head];
562
- // Ref to up_ in anonymous aspect
563
- if (!art && head === 'up_') {
564
- const up = getCache( parent, '_parent' );
565
- const target = up && typeof up.target === 'string' && csn.definitions[up.target];
566
- if (target && target.elements) {
567
- initDefinition( target );
568
- art = target.elements.up_;
557
+ if (semantics.dynamic !== false) {
558
+ if (semantics.dynamic === 'target') { // ref in keys
559
+ const target = assocTarget( parent, refCtx );
560
+ return resolvePath( path, target.elements[head], target, 'target' );
561
+ }
562
+ if (baseEnv) // ref-target (filter condition), expand, inline
563
+ return resolvePath( path, baseEnv.elements[head], baseEnv, semantics.dynamic );
564
+ if (!query) { // outside queries - TODO: items?
565
+ let art = parent.elements[head];
566
+ // Ref to up_ in anonymous aspect
567
+ if (!art && head === 'up_') {
568
+ const up = getCache( parent, '_parent' );
569
+ const target = up && typeof up.target === 'string' && csn.definitions[up.target];
570
+ if (target && target.elements) {
571
+ initDefinition( target );
572
+ art = target.elements.up_;
573
+ }
569
574
  }
575
+ return resolvePath( path, art, parent, 'parent' );
570
576
  }
571
- return resolvePath( path, art, parent, 'parent' );
572
- }
573
577
 
574
- if (semantics.dynamic === 'query')
575
- // TODO: for ON condition in expand, would need to use cached _element
576
- return resolvePath( path, qcache.elements[head], null, 'query' );
577
- for (const name in qcache.$aliases) {
578
- const alias = qcache.$aliases[name];
579
- const found = alias.elements[head];
580
- if (found)
581
- return resolvePath( path, found, alias._ref, 'source', name )
578
+ if (semantics.dynamic === 'query')
579
+ // TODO: for ON condition in expand, would need to use cached _element
580
+ return resolvePath( path, qcache.elements[head], null, 'query' );
581
+ for (const name in qcache.$aliases) {
582
+ const alias = qcache.$aliases[name];
583
+ const found = alias.elements[head];
584
+ if (found)
585
+ return resolvePath( path, found, alias._ref, 'source', name )
586
+ }
582
587
  }
583
588
  // console.log(query.SELECT,qcache,qcache.$next,main)
584
- throw new ModelError ( `Path item ${ 0 }=${ head } refers to nothing, refCtx: ${ refCtx }` );
589
+ throw new ModelError ( `Path item 0=${ head } refers to nothing, refCtx: ${ refCtx }` );
585
590
  }
586
591
 
587
592
  /**
@@ -933,6 +938,15 @@ function analyseCsnPath( csnPath, csn, resolve ) {
933
938
  parent = art;
934
939
  art = obj[prop];
935
940
  }
941
+ else if (refCtx === 'orderBy') {
942
+ const isSelect = isSelectQuery( query );
943
+ // use _query_ elements with direct refs (consider sub-optimal CSN,
944
+ // representation of the CAST function), otherwise source elements:
945
+ if (obj[prop].ref && !obj[prop].cast)
946
+ refCtx = (isSelect ? 'orderBy_ref' : 'orderBy_set_ref');
947
+ else
948
+ refCtx = (isSelect ? 'orderBy_expr' : 'orderBy_set_expr');
949
+ }
936
950
  isName = false;
937
951
  }
938
952
  else if (artifactProperties.includes( String(prop) )) {
@@ -983,7 +997,7 @@ function analyseCsnPath( csnPath, csn, resolve ) {
983
997
  refCtx = prop;
984
998
  }
985
999
  else if (prop === 'orderBy') {
986
- refCtx = (query.SET ? 'orderBy_set' : 'orderBy');
1000
+ refCtx = 'orderBy';
987
1001
  }
988
1002
  else if (prop !== 'xpr') {
989
1003
  refCtx = prop;
@@ -996,6 +1010,18 @@ function analyseCsnPath( csnPath, csn, resolve ) {
996
1010
  return resolve( obj, refCtx, main, query, parent, baseEnv );
997
1011
  }
998
1012
 
1013
+ // A SELECT which is (unnecessarily) put into parentheses, the CSN
1014
+ // representation uses SET without `op` and args of length 1:
1015
+ function isSelectQuery( query ) {
1016
+ while (query.SET) {
1017
+ const { args } = query.SET;
1018
+ if (args.length !== 1)
1019
+ return false;
1020
+ query = args[0];
1021
+ }
1022
+ return true;
1023
+ }
1024
+
999
1025
  module.exports = {
1000
1026
  csnRefs,
1001
1027
  traverseQuery,
@@ -492,15 +492,18 @@ function getUtils(model, universalReady) {
492
492
 
493
493
  /**
494
494
  * Resolve to the final type of a type, that means follow type chains, references, etc.
495
- * Input is a type name, i.e. string, or type ref, i.e. `{ ref: [...] }`.
495
+ * Input is a fully qualified type name, i.e. string, or type ref, i.e. `{ ref: [...] }`.
496
496
  *
497
497
  * Returns `null` if the type can't be resolved or if the referenced element has no type,
498
498
  * e.g. `typeof V:calculated`.
499
499
  * Otherwise, if scalar, returns an object that has a `type` property and all collected type
500
500
  * properties, or the type object with `elements` or `items` property if structured/arrayed.
501
501
  *
502
- * Caches type lookups. If the CSN changes drastically, you will need to re-call `csnUtils()`
503
- * and use the newly returned `getFinalBaseTypeWithProps()`.
502
+ * Notes:
503
+ * - Caches type lookups. If the CSN changes drastically, you will need to re-call
504
+ * `csnUtils()` and use the newly returned `getFinalBaseTypeWithProps()`.
505
+ * - Does _not_ return the underlying type definition! It is an object with all relevant
506
+ * type properties collected while traversing the type chain!
504
507
  *
505
508
  * @param {string|object} type Type as string or type ref, i.e. `{ ref: [...] }`
506
509
  * @returns {object|null}
@@ -517,7 +520,7 @@ function getUtils(model, universalReady) {
517
520
 
518
521
  // We differentiate between ref and type to avoid collisions due to dict key.
519
522
  // Delimiter chosen arbitrarily; just one that is rarely used.
520
- const resolvedKey = (typeof type === 'object') ? `ref:${type.ref.join('\\')}` : `type:${type}`;
523
+ const resolvedKey = (typeof type === 'object') ? `ref[${type.ref.length}]:${type.ref.join('\\')}` : `type:${type}`;
521
524
 
522
525
  if (finalBaseTypeCache[resolvedKey]) {
523
526
  if (finalBaseTypeCache[resolvedKey] === true)
@@ -786,10 +789,12 @@ function forEachGeneric( construct, prop, callback, path = [], iterateOptions =
786
789
  executeCallbacks( dictObj, name );
787
790
  }
788
791
  function executeCallbacks(o, name ) {
792
+ const p = iterateOptions.pathWithoutProp ? [ name ] : [ prop, name ];
793
+
789
794
  if (Array.isArray(callback))
790
- callback.forEach(cb => cb( o, name, prop, path.concat([prop, name]), construct ));
795
+ callback.forEach(cb => cb( o, name, prop, path.concat(p), construct ));
791
796
  else
792
- callback( o, name, prop, path.concat([prop, name]), construct )
797
+ callback( o, name, prop, path.concat(p), construct )
793
798
  }
794
799
  }
795
800
 
@@ -1286,7 +1291,6 @@ function copyAnnotationsAndDoc(fromNode, toNode, overwrite = false) {
1286
1291
  * override: Whether to ignore existing annotations.
1287
1292
  * filter: Positive filter. If it returns true, annotations for the referenced artifact
1288
1293
  * will be applied.
1289
- * @todo Improve to also apply element annotations
1290
1294
  */
1291
1295
  function applyAnnotationsFromExtensions(csn, config) {
1292
1296
  if (!csn.extensions)
@@ -58,7 +58,6 @@ function tableAliasAsLink( art, parent, name ) {
58
58
  *
59
59
  * @param {XSN.Model} model
60
60
  * @param {string} [nameOrPath]
61
- * @returns {string}
62
61
  */
63
62
  function revealInternalProperties( model, nameOrPath ) {
64
63
  const transformers = {
@@ -299,8 +298,8 @@ function revealInternalProperties( model, nameOrPath ) {
299
298
  function array( node, fn ) {
300
299
  const r = node.map( n => fn( n, node ) );
301
300
  if (node[$location])
302
- r.push( { $location: locationString( node[$location] ) } );
303
- return r;
301
+ r.push( { '[$location]': locationString( node[$location] ) } );
302
+ return (node.$prefix) ? [ { $prefix: node.$prefix }, ...node ] : r;
304
303
  }
305
304
 
306
305
  function artifactIdentifier( node, parent ) {
@@ -169,6 +169,9 @@ function getElementComparator(otherArtifact, addedElementsDict = null, changedEl
169
169
  }
170
170
  if (relevantTypeChange(element.type, otherElement.type) || typeParametersChanged(element, otherElement)) {
171
171
  // Type or parameters, e.g. association target, changed.
172
+ if(otherElement.notNull && element.notNull === undefined) {
173
+ element.$notNull = false; // Explictily set notNull to the implicit default so we render the correct ALTER
174
+ }
172
175
  changedElementsDict[name] = changedElement(element, otherElement);
173
176
  }
174
177
 
@@ -102,8 +102,11 @@ optionProcessor
102
102
  hanaAssocRealCardinality
103
103
  mapAssocToJoinCardinality
104
104
  ignoreAssocPublishingInUnion
105
+ odataOpenType
106
+ optionalActionFunctionParameters
105
107
  --deprecated <list> Comma separated list of deprecated options.
106
108
  Valid values are:
109
+ autoCorrectOrderBySourceRefs
107
110
  eagerPersistenceForGeneratedEntities
108
111
  --fallback-parser <type> If the language cannot be deduced by the file's extensions, use this
109
112
  parser as a fallback. Valid values are:
@@ -138,6 +141,7 @@ optionProcessor
138
141
  toRename [options] <files...> (internal) Generate SQL DDL rename statements
139
142
  manageConstraints [options] <files...> (internal) Generate ALTER TABLE statements to
140
143
  add / modify referential constraints.
144
+ inspect [options] <files...> (internal) Inspect the given CDS files.
141
145
  `);
142
146
 
143
147
  // ----------- toHana -----------
@@ -153,6 +157,7 @@ optionProcessor.command('H, toHana')
153
157
  .option(' --integrity-not-enforced')
154
158
  .option(' --assert-integrity <mode>', ['true', 'false', 'individual'])
155
159
  .option(' --assert-integrity-type <type>', ['RT', 'DB'], { ignoreCase: true })
160
+ .option(' --disable-hana-comments')
156
161
  .help(`
157
162
  Usage: cdsc toHana [options] <files...>
158
163
 
@@ -187,6 +192,7 @@ optionProcessor.command('H, toHana')
187
192
  RT : (default) No database constraint for an association
188
193
  if not explicitly demanded via annotation
189
194
  DB : Create database constraints for associations
195
+ --disable-hana-comments Disable rendering of doc comments as SAP HANA comments.
190
196
  `);
191
197
 
192
198
  optionProcessor.command('O, toOdata')
@@ -263,6 +269,7 @@ optionProcessor.command('Q, toSql')
263
269
  .option(' --assert-integrity <mode>', ['true', 'false', 'individual'])
264
270
  .option(' --assert-integrity-type <type>', ['RT', 'DB'], { ignoreCase: true })
265
271
  .option(' --constraints-in-create-table')
272
+ .option(' --disable-hana-comments')
266
273
  .help(`
267
274
  Usage: cdsc toSql [options] <files...>
268
275
 
@@ -310,6 +317,7 @@ optionProcessor.command('Q, toSql')
310
317
  --constraints-in-create-table If set, the foreign key constraints will be rendered as
311
318
  part of the "CREATE TABLE" statements rather than as separate
312
319
  "ALTER TABLE ADD CONSTRAINT" statements
320
+ --disable-hana-comments Disable rendering of doc comments as SAP HANA comments.
313
321
  `);
314
322
 
315
323
  optionProcessor.command('toRename')
@@ -377,6 +385,7 @@ optionProcessor.command('toCsn')
377
385
  .option('-h, --help')
378
386
  .option('-f, --csn-flavor <flavor>', ['client', 'gensrc', 'universal'], { aliases: ['--flavor'] })
379
387
  .option(' --with-localized')
388
+ .option(' --with-locations')
380
389
  .help(`
381
390
  Usage: cdsc toCsn [options] <files...>
382
391
 
@@ -391,6 +400,8 @@ optionProcessor.command('toCsn')
391
400
  statements, but not suitable for consumption by clients or
392
401
  backends
393
402
  universal: in development (BETA)
403
+ --with-locations Add $location to CSN artifacts. In contrast to \`--enrich-csn\`,
404
+ $location is an object with 'file', 'line' and 'col' properties.
394
405
 
395
406
  Internal options (for testing only, may be changed/removed at any time)
396
407
  --with-localized Add localized convenience views to the CSN output.
@@ -444,6 +455,22 @@ optionProcessor.command('explain')
444
455
  -h, --help Show this help text
445
456
  `);
446
457
 
458
+ optionProcessor.command('inspect')
459
+ .option('-h, --help')
460
+ .option(' --statistics')
461
+ .option(' --propagation <art>')
462
+ .positionalArgument('<files...>')
463
+ .help(`
464
+ Usage: cdsc inspect [options] <files...>
465
+
466
+ (internal): Inspect the CSN model compiled from the provided CDS files.
467
+
468
+ Options
469
+ -h, --help Show this help text
470
+ --statistics Print model statistics
471
+ --propagation <art> Show propagation sources for <art>
472
+ `);
473
+
447
474
  module.exports = {
448
475
  optionProcessor
449
476
  };
@@ -142,11 +142,10 @@ function csnToCdl(csn, options) {
142
142
  let result = renderAnnotationAssignmentsAndDocComment(ext, env);
143
143
 
144
144
  if (ext.includes && ext.includes.length > 0) {
145
- // Includes can't be combined with anything in braces {}. Multiple includes
146
- // are possible through CSN, but in CDL, only one include at once is possible.
145
+ // Includes can't be combined with anything in braces {}.
147
146
  const affix = isElementExtend ? 'element ' : '';
148
- for (const id of ext.includes)
149
- result += `${env.indent}extend ${affix}${extName} with ${quotePathIfRequired(id)};\n`;
147
+ const includes = ext.includes.map(inc => quotePathIfRequired(inc)).join(', ');
148
+ result += `${env.indent}extend ${affix}${extName} with ${includes};\n`;
150
149
  return result;
151
150
  }
152
151
 
@@ -358,17 +357,21 @@ function csnToCdl(csn, options) {
358
357
  case 'context':
359
358
  case 'service':
360
359
  return renderContextOrService(artifactName, art, env);
360
+
361
361
  case 'type':
362
362
  case 'aspect':
363
363
  case 'annotation': // annotation in 'csn.definitions' for compiler v1 compatibility
364
364
  return renderTypeOrAnnotation(artifactName, art, env);
365
+
365
366
  case 'action':
366
367
  case 'function':
367
368
  return renderActionOrFunction(artifactName, art, env);
369
+
368
370
  case 'event':
369
371
  return renderEvent(artifactName, art, env);
372
+
370
373
  default:
371
- throw new ModelError(`Unknown artifact kind: ${art.kind}`);
374
+ throw new ModelError(`to.cdl: Unknown artifact kind: ${art.kind}`);
372
375
  }
373
376
  }
374
377
 
@@ -426,14 +429,16 @@ function csnToCdl(csn, options) {
426
429
  */
427
430
  function renderEntity(artifactName, art, env) {
428
431
  let result = renderAnnotationAssignmentsAndDocComment(art, env);
429
- const childEnv = increaseIndent(env);
430
- const normalizedArtifactName = renderArtifactName(artifactName);
431
- result += `${env.indent + (art.abstract ? 'abstract ' : '')}entity ${normalizedArtifactName}`;
432
- const parameters = Object.keys(art.params || []).map(name => renderParameter(name, art.params[name], childEnv)).join(',\n');
433
- result += (parameters === '') ? '' : ` (\n${parameters}\n${env.indent})`;
432
+ result += env.indent + (art.abstract ? 'abstract ' : '');
433
+ result += `entity ${renderArtifactName(artifactName)}`;
434
+
435
+ if (art.params)
436
+ result += renderParameters(art, env);
437
+
434
438
  if (art.includes)
435
439
  result += renderIncludes(art.includes);
436
440
  result += ' {\n';
441
+ const childEnv = increaseIndent(env);
437
442
  for (const name in art.elements) {
438
443
  const element = art.elements[name];
439
444
  result += renderElement(name, element, childEnv);
@@ -664,8 +669,11 @@ function csnToCdl(csn, options) {
664
669
  if (path.ref[0].args)
665
670
  result += `(${renderArgs(path.ref[0], ':', env)})`;
666
671
 
667
- if (path.ref[0].where)
668
- result += `[${path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : ''}${renderExpr(path.ref[0].where, env, true, true)}]`;
672
+ if (path.ref[0].where) {
673
+ const cardinality = path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : '';
674
+ const expr = renderExpr(path.ref[0].where, env, true, true);
675
+ result += `[${cardinality}${expr}]`;
676
+ }
669
677
 
670
678
  // Add any path steps (possibly with parameters and filters) that may follow after that
671
679
  if (path.ref.length > 1)
@@ -841,14 +849,9 @@ function csnToCdl(csn, options) {
841
849
  const syntax = (art.projection) ? 'projection' : 'entity';
842
850
  let result = renderAnnotationAssignmentsAndDocComment(art, env);
843
851
  result += `${env.indent}${art.abstract ? 'abstract ' : ''}${syntax === 'projection' ? 'entity' : syntax} ${renderArtifactName(artifactName)}`;
844
- if (art.params) {
845
- const childEnv = increaseIndent(env);
846
- const parameters = Object.keys(art.params).map(name => renderParameter(name, art.params[name], childEnv)).join(',\n');
847
- result += `(\n${parameters}\n${env.indent}) as `;
848
- }
849
- else {
850
- result += ' as ';
851
- }
852
+ if (art.params)
853
+ result += renderParameters(art, env);
854
+ result += ' as ';
852
855
  result += renderQuery(getNormalizedQuery(art).query, true, syntax, env, [ 'definitions', artifactName, 'query' ], art.elements);
853
856
  result += ';\n';
854
857
  result += renderQueryElementAnnotations(artifactName, art, env);
@@ -959,9 +962,10 @@ function csnToCdl(csn, options) {
959
962
  if (limit.rows !== undefined)
960
963
  limitStr += `limit ${renderExpr(limit.rows, limitEnv)}`;
961
964
 
962
- if (limit.offset !== undefined)
963
- limitStr += `${limitStr !== '' ? `\n${increaseIndent(limitEnv).indent}` : ''}offset ${renderExpr(limit.offset, limitEnv)}`;
964
-
965
+ if (limit.offset !== undefined) {
966
+ const offsetIndent = (limitStr === '') ? '' : `\n${increaseIndent(limitEnv).indent}`;
967
+ limitStr += `${offsetIndent}offset ${renderExpr(limit.offset, limitEnv)}`;
968
+ }
965
969
  return limitStr;
966
970
  }
967
971
 
@@ -1040,10 +1044,9 @@ function csnToCdl(csn, options) {
1040
1044
  * @return {string}
1041
1045
  */
1042
1046
  function renderActionOrFunction(actionName, act, env) {
1043
- let result = `${renderAnnotationAssignmentsAndDocComment(act, env) + env.indent + act.kind} ${renderArtifactName(actionName)}`;
1044
- const childEnv = increaseIndent(env);
1045
- const parameters = Object.keys(act.params || []).map(name => renderParameter(name, act.params[name], childEnv)).join(',\n');
1046
- result += (parameters === '') ? '()' : `(\n${parameters}\n${env.indent})`;
1047
+ let result = renderAnnotationAssignmentsAndDocComment(act, env) + env.indent + act.kind;
1048
+ result += ` ${renderArtifactName(actionName)}`;
1049
+ result += renderParameters(act, env);
1047
1050
  if (act.returns) {
1048
1051
  const actEnv = envAddPath(env, [ 'returns' ]);
1049
1052
  result += ` returns ${renderTypeReferenceAndProps(act.returns, actEnv)}`;
@@ -1053,6 +1056,23 @@ function csnToCdl(csn, options) {
1053
1056
  return result;
1054
1057
  }
1055
1058
 
1059
+ /**
1060
+ * Render art.params, i.e. list of parameter in parentheses. If there is only one
1061
+ * parameter, a single line is used, otherwise an indented list is used.
1062
+ * If there are no params, an empty list `()` is returned.
1063
+ *
1064
+ * @param {CSN.Artifact} art
1065
+ * @param {CdlRenderEnvironment} env
1066
+ * @returns {string}
1067
+ */
1068
+ function renderParameters(art, env) {
1069
+ const childEnv = increaseIndent(env);
1070
+ const parameters = Object.keys(art.params || {}).map(name => renderParameter(name, art.params[name], childEnv));
1071
+ if (parameters.length === 0)
1072
+ return '()';
1073
+ return `(\n${parameters.join(',\n')}\n${env.indent})`;
1074
+ }
1075
+
1056
1076
  /**
1057
1077
  * Render an action or function parameter 'par' with name 'parName'. Return the resulting source string (no trailing LF).
1058
1078
  *
@@ -1075,7 +1095,7 @@ function csnToCdl(csn, options) {
1075
1095
  * @param {string} artifactName
1076
1096
  * @param {CSN.Artifact} art
1077
1097
  * @param {CdlRenderEnvironment} env
1078
- * @param {String} [artType] - used for rendering csn.vocabularies, as the annotations there do not have a kind. Only in toCdl mode
1098
+ * @param {String} [artType] - used for rendering csn.vocabularies, as the annotations there do not have a kind.
1079
1099
  * @return {string}
1080
1100
  */
1081
1101
  function renderTypeOrAnnotation(artifactName, art, env, artType) {
@@ -1085,9 +1105,11 @@ function csnToCdl(csn, options) {
1085
1105
  result += renderIncludes(art.includes);
1086
1106
 
1087
1107
  if (!art.type && art.elements) // For nicer output, no colon if unnamed structure is used.
1088
- result += ` ${renderTypeReferenceAndProps(art, env)};\n`;
1108
+ result += ` ${renderTypeReferenceAndProps(art, env)}`;
1089
1109
  else
1090
- result += ` : ${renderTypeReferenceAndProps(art, env)};\n`;
1110
+ result += ` : ${renderTypeReferenceAndProps(art, env)}`;
1111
+ // for aspects, but since types don't have `actions` this does not hurt
1112
+ result += `${renderActionsAndFunctions(art, env)};\n`;
1091
1113
  return result;
1092
1114
  }
1093
1115
 
@@ -1373,7 +1395,9 @@ function csnToCdl(csn, options) {
1373
1395
  }
1374
1396
  if (s.where) {
1375
1397
  // Filter, possibly with cardinality
1376
- result += `[${s.cardinality ? (`${s.cardinality.max}: `) : ''}${renderExpr(s.where, env, inline, true)}]`;
1398
+ const cardinality = s.cardinality ? (`${s.cardinality.max}: `) : '';
1399
+ const expr = renderExpr(s.where, env, inline, true);
1400
+ result += `[${cardinality}${expr}]`;
1377
1401
  }
1378
1402
 
1379
1403
  return result;
@@ -1516,7 +1540,8 @@ function csnToCdl(csn, options) {
1516
1540
  * @return {string}
1517
1541
  */
1518
1542
  function renderForeignKey(fKey, env) {
1519
- return `${renderExpr(fKey, env)}${fKey.as ? (` as ${fKey.as}`) : ''}`;
1543
+ const alias = fKey.as ? (` as ${fKey.as}`) : '';
1544
+ return renderExpr(fKey, env) + alias;
1520
1545
  }
1521
1546
 
1522
1547
  /**