@sap/cds-compiler 3.3.2 → 3.4.2

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 (76) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/bin/cdsc.js +3 -1
  3. package/doc/CHANGELOG_BETA.md +17 -0
  4. package/lib/api/main.js +147 -18
  5. package/lib/api/validate.js +8 -3
  6. package/lib/base/dictionaries.js +6 -6
  7. package/lib/base/keywords.js +104 -0
  8. package/lib/base/message-registry.js +137 -68
  9. package/lib/base/messages.js +59 -48
  10. package/lib/base/model.js +1 -0
  11. package/lib/checks/actionsFunctions.js +1 -1
  12. package/lib/checks/cdsPersistence.js +1 -1
  13. package/lib/checks/checkForTypes.js +13 -8
  14. package/lib/checks/defaultValues.js +3 -1
  15. package/lib/checks/elements.js +1 -1
  16. package/lib/checks/parameters.js +4 -2
  17. package/lib/checks/queryNoDbArtifacts.js +1 -1
  18. package/lib/checks/sql-snippets.js +12 -10
  19. package/lib/checks/validator.js +14 -4
  20. package/lib/compiler/assert-consistency.js +8 -7
  21. package/lib/compiler/checks.js +30 -20
  22. package/lib/compiler/define.js +89 -25
  23. package/lib/compiler/extend.js +33 -28
  24. package/lib/compiler/finalize-parse-cdl.js +14 -9
  25. package/lib/compiler/populate.js +30 -8
  26. package/lib/compiler/propagator.js +23 -28
  27. package/lib/compiler/resolve.js +11 -5
  28. package/lib/compiler/shared.js +66 -48
  29. package/lib/compiler/tweak-assocs.js +2 -3
  30. package/lib/compiler/utils.js +11 -0
  31. package/lib/edm/annotations/genericTranslation.js +7 -4
  32. package/lib/edm/csn2edm.js +1 -1
  33. package/lib/gen/language.checksum +1 -1
  34. package/lib/gen/language.interp +1 -1
  35. package/lib/gen/languageParser.js +3565 -3544
  36. package/lib/json/csnVersion.js +13 -13
  37. package/lib/json/from-csn.js +140 -158
  38. package/lib/json/to-csn.js +23 -5
  39. package/lib/language/.eslintrc.json +4 -0
  40. package/lib/language/antlrParser.js +7 -10
  41. package/lib/language/docCommentParser.js +1 -2
  42. package/lib/language/errorStrategy.js +54 -27
  43. package/lib/language/genericAntlrParser.js +115 -84
  44. package/lib/language/language.g4 +29 -25
  45. package/lib/language/multiLineStringParser.js +75 -63
  46. package/lib/main.js +1 -0
  47. package/lib/model/csnRefs.js +4 -3
  48. package/lib/model/csnUtils.js +39 -7
  49. package/lib/model/sortViews.js +7 -3
  50. package/lib/modelCompare/compare.js +49 -15
  51. package/lib/modelCompare/filter.js +83 -0
  52. package/lib/optionProcessor.js +5 -1
  53. package/lib/render/manageConstraints.js +9 -5
  54. package/lib/render/toCdl.js +120 -62
  55. package/lib/render/toHdbcds.js +1 -1
  56. package/lib/render/toSql.js +6 -2
  57. package/lib/render/utils/common.js +7 -0
  58. package/lib/sql-identifier.js +7 -0
  59. package/lib/transform/db/assertUnique.js +27 -38
  60. package/lib/transform/db/expansion.js +11 -4
  61. package/lib/transform/db/temporal.js +3 -1
  62. package/lib/transform/db/transformExists.js +7 -1
  63. package/lib/transform/db/views.js +42 -13
  64. package/lib/transform/draft/db.js +2 -2
  65. package/lib/transform/forOdataNew.js +7 -3
  66. package/lib/transform/forRelationalDB.js +12 -6
  67. package/lib/transform/localized.js +1 -1
  68. package/lib/transform/odata/typesExposure.js +2 -1
  69. package/lib/transform/parseExpr.js +245 -0
  70. package/lib/transform/transformUtilsNew.js +23 -14
  71. package/lib/transform/translateAssocsToJoins.js +12 -12
  72. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  73. package/lib/utils/term.js +5 -5
  74. package/package.json +2 -2
  75. package/share/messages/message-explanations.json +1 -1
  76. package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +1 -1
@@ -93,7 +93,7 @@ function checkPrimaryKey(art) {
93
93
  function checkVirtualElement(member) {
94
94
  if (member.virtual) {
95
95
  if (this.csnUtils.isAssociation(member.type)) { // or Composition ???
96
- this.error(null, member.$path, {}, `Element can't be virtual and an association`);
96
+ this.error(null, member.$path, {}, 'Element can\'t be virtual and an association');
97
97
  }
98
98
  }
99
99
  }
@@ -12,8 +12,10 @@ const { isPersistedOnDatabase } = require('../model/csnUtils.js');
12
12
  */
13
13
  function checkForParams(parent, name, params, path) {
14
14
  const artifact = this.csn.definitions[path[1]];
15
- if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact) && !(parent.kind !== 'entity'))
16
- this.error('ref-unexpected-params', [ ...path, 'params' ], `Parameterized views can't be used with sqlDialect “postgres”`);
15
+ if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact) && !(parent.kind !== 'entity')) {
16
+ this.error('ref-unexpected-params', [ ...path, 'params' ], { value: this.options.sqlDialect },
17
+ 'Parameterized views can\'t be used with sqlDialect $(VALUE)');
18
+ }
17
19
  }
18
20
 
19
21
  module.exports = {
@@ -101,7 +101,7 @@ function checkQueryForNoDBArtifacts(query) {
101
101
  this.error(null,
102
102
  obj.$path,
103
103
  { id: pathStep, elemref: obj },
104
- `Path step $(ID) of $(ELEMREF) has no foreign keys`);
104
+ 'Path step $(ID) of $(ELEMREF) has no foreign keys');
105
105
  }
106
106
 
107
107
  if (art.on) {
@@ -13,15 +13,15 @@
13
13
  */
14
14
  function checkSqlAnnotationOnElement(member, memberName, prop, path) {
15
15
  if (member['@sql.replace'])
16
- this.error(null, path, { anno: 'sql.replace' }, `Annotation $(ANNO) is reserved and must not be used`);
16
+ this.error(null, path, { anno: 'sql.replace' }, 'Annotation $(ANNO) is reserved and must not be used');
17
17
  if (member['@sql.prepend'])
18
- this.message('anno-invalid-sql-element', path, { anno: 'sql.prepend' }, `Annotation $(ANNO) can't be used on elements` );
18
+ this.message('anno-invalid-sql-element', path, { anno: 'sql.prepend' }, 'Annotation $(ANNO) can\'t be used on elements' );
19
19
 
20
20
  if (member['@sql.append']) {
21
21
  if (this.artifact.query)
22
- this.message('anno-invalid-sql-view-element', path, { anno: 'sql.append' }, `Annotation $(ANNO) can't be used on elements in views` );
22
+ this.message('anno-invalid-sql-view-element', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on elements in views' );
23
23
  else if (this.csnUtils.isStructured(member))
24
- this.message('anno-invalid-sql-struct', path, { anno: 'sql.append' }, `Annotation $(ANNO) can't be used on structured elements` );
24
+ this.message('anno-invalid-sql-struct', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on structured elements' );
25
25
  else
26
26
  checkValidAnnoValue(member, '@sql.append', path, this.error, this.options);
27
27
  }
@@ -37,7 +37,7 @@ function checkSqlAnnotationOnElement(member, memberName, prop, path) {
37
37
  function checkValidAnnoValue(carrier, annotation, path, error, options) {
38
38
  if (carrier[annotation] !== undefined && carrier[annotation] !== null) {
39
39
  if (typeof carrier[annotation] !== 'string')
40
- error(null, path, { anno: annotation.slice(1), type: typeof carrier[annotation] }, `Annotation $(ANNO) must be a string, found $(TYPE)` );
40
+ error(null, path, { anno: annotation.slice(1), type: typeof carrier[annotation] }, 'Annotation $(ANNO) must be a string, found $(TYPE)' );
41
41
  else if (options.transformation === 'sql') // HDI and HDBCDS do their own checks
42
42
  guardAgainstInjection(annotation, carrier[annotation], path, error);
43
43
  }
@@ -52,20 +52,22 @@ function checkValidAnnoValue(carrier, annotation, path, error, options) {
52
52
  function checkSqlAnnotationOnArtifact(artifact, artifactName) {
53
53
  if (artifact.kind !== 'entity') {
54
54
  if (artifact['@sql.prepend'])
55
- this.message('anno-invalid-sql-kind', [ 'definitions', artifactName ], { name: '@sql.prepend', kind: artifact.kind }, `Annotation $(NAME) can't be used on an artifact of kind $(KIND)` );
55
+ this.message('anno-invalid-sql-kind', [ 'definitions', artifactName ], { name: '@sql.prepend', kind: artifact.kind }, 'Annotation $(NAME) can\'t be used on an artifact of kind $(KIND)' );
56
56
  if (artifact['@sql.append'])
57
- this.message('anno-invalid-sql-kind', [ 'definitions', artifactName ], { name: '@sql.append', kind: artifact.kind }, `Annotation $(NAME) can't be used on an artifact of kind $(KIND)` );
57
+ this.message('anno-invalid-sql-kind', [ 'definitions', artifactName ], { name: '@sql.append', kind: artifact.kind }, 'Annotation $(NAME) can\'t be used on an artifact of kind $(KIND)' );
58
58
  }
59
59
  else if (artifact['@sql.prepend']) {
60
60
  if (artifact.query)
61
- this.message('anno-invalid-sql-view', [ 'definitions', artifactName ], { name: '@sql.prepend' }, `Annotation $(NAME) can't be used on views` );
61
+ this.message('anno-invalid-sql-view', [ 'definitions', artifactName ], { name: '@sql.prepend' }, 'Annotation $(NAME) can\'t be used on views' );
62
62
  else
63
63
  checkValidAnnoValue(artifact, '@sql.prepend', [ 'definitions', artifactName ], this.error, this.options);
64
64
  }
65
65
 
66
66
 
67
- if (artifact['@sql.replace'])
68
- this.error(null, [ 'definitions', artifactName ], { anno: 'sql.replace' }, `Annotation $(ANNO) is reserved and must not be used`);
67
+ if (artifact['@sql.replace']) {
68
+ this.error(null, [ 'definitions', artifactName ], { anno: 'sql.replace' },
69
+ 'Annotation $(ANNO) is reserved and must not be used');
70
+ }
69
71
 
70
72
  checkValidAnnoValue(artifact, '@sql.append', [ 'definitions', artifactName ], this.error, this.options);
71
73
  }
@@ -190,6 +190,19 @@ function mergeCsnValidators(csnValidators, that) {
190
190
  return remapped;
191
191
  }
192
192
 
193
+ /**
194
+ * Depending on the dialect we need to run different validations.
195
+ *
196
+ * @param {CSN.Options} options
197
+ * @returns {any[]} Array of validator functions (or objects?)
198
+ */
199
+ function getDBCsnValidators(options) {
200
+ if (options.sqlDialect === 'postgres' || options.sqlDialect === 'h2')
201
+ return [ ...forRelationalDBCsnValidators, checkForHanaTypes, checkForParams ];
202
+
203
+ return forRelationalDBCsnValidators;
204
+ }
205
+
193
206
  /**
194
207
  * @param {CSN.Model} csn CSN to check
195
208
  * @param {object} that Will be provided to the validators via "this"
@@ -197,10 +210,7 @@ function mergeCsnValidators(csnValidators, that) {
197
210
  */
198
211
  function forRelationalDB(csn, that) {
199
212
  return _validate(csn, that,
200
- that.options.sqlDialect === 'postgres'
201
- ? [ ...forRelationalDBCsnValidators,
202
- checkForHanaTypes,
203
- checkForParams ] : forRelationalDBCsnValidators,
213
+ getDBCsnValidators(that.options),
204
214
  forRelationalDBMemberValidators.concat(commonMemberValidators),
205
215
  forRelationalDBArtifactValidators.concat(commonArtifactValidators).concat(
206
216
  // why is this hana exclusive
@@ -295,7 +295,7 @@ function assertConsistency( model, stage ) {
295
295
  none: { optional: () => true }, // parse error
296
296
  },
297
297
  columns: {
298
- kind: [ 'extend' ],
298
+ kind: [ 'extend', '$column' ],
299
299
  test: isArray( column ),
300
300
  optional: thoseWithKind,
301
301
  enum: [ '*' ],
@@ -363,12 +363,13 @@ function assertConsistency( model, stage ) {
363
363
  suffix: { test: TODO },
364
364
  kind: {
365
365
  isRequired: !stageParser && (() => true),
366
+ kind: true,
366
367
  // required to be set by Core Compiler even with parse errors
367
368
  test: isString,
368
369
  enum: [
369
370
  'context', 'service', 'entity', 'type', 'aspect', 'const', 'annotation',
370
371
  'element', 'enum', 'action', 'function', 'param', 'key', 'event',
371
- 'annotate', 'extend',
372
+ 'annotate', 'extend', '$column',
372
373
  'select', '$join', 'mixin',
373
374
  'source', 'namespace', 'using',
374
375
  '$tableAlias', '$navElement',
@@ -398,7 +399,7 @@ function assertConsistency( model, stage ) {
398
399
  requires: [ 'location', 'path' ],
399
400
  optional: [ 'scope', 'variant', '_artifact', '$inferred', '$parens', 'sort', 'nulls' ],
400
401
  },
401
- none: { optional: [ 'location', '$parens' ] },
402
+ none: { optional: () => true }, // parse error
402
403
  // TODO: why optional / enough in name?
403
404
  // TODO: "yes" instead "none": val: true, optional literal/location
404
405
  val: {
@@ -821,7 +822,7 @@ function assertConsistency( model, stage ) {
821
822
  const sub = Object.assign( {}, s.inherits && schema[s.inherits], s );
822
823
  if (spec.requires && sub.requires)
823
824
  sub.requires = [ ...sub.requires, ...spec.requires ];
824
- if (spec.optional && sub.optional)
825
+ if (Array.isArray( spec.optional ) && Array.isArray( sub.optional ))
825
826
  sub.optional = [ ...sub.optional, ...spec.optional ];
826
827
  // console.log(expressionSpec(node) );
827
828
  (sub.test || standard)( node, parent, prop, sub, idx );
@@ -834,9 +835,9 @@ function assertConsistency( model, stage ) {
834
835
  return 'val';
835
836
  else if (node.query)
836
837
  return 'query';
837
- else if (!node.op)
838
- return 'none';
839
- return 'op';
838
+ else if (node.op)
839
+ return 'op';
840
+ return 'none'; // parse error
840
841
  }
841
842
 
842
843
  function args( node, parent, prop, spec ) {
@@ -18,6 +18,8 @@ const builtins = require('../compiler/builtins');
18
18
  const {
19
19
  forEachGeneric, forEachDefinition, forEachMember,
20
20
  } = require('../base/model');
21
+ const { CompilerAssertion } = require('../base/error');
22
+ const { pathName } = require('./utils');
21
23
 
22
24
  function check( model ) { // = XSN
23
25
  const {
@@ -66,7 +68,7 @@ function check( model ) { // = XSN
66
68
  forEachGeneric( elem, 'elements', checkElement );
67
69
  }
68
70
 
69
- function checkName( construct ) {
71
+ function checkName( construct ) { // TODO: move to define.js
70
72
  if (model.options.$skipNameCheck)
71
73
  return;
72
74
  // TODO: Move a corrected version of this check to definer (but do not rely
@@ -78,11 +80,11 @@ function check( model ) { // = XSN
78
80
  }
79
81
  }
80
82
 
81
- // TODO: move into definer.js
82
83
  function checkLocalizedElement(elem) {
83
84
  // if it is directly a localized element
84
85
  if (elem.localized && elem.localized.val) {
85
86
  const type = elem._effectiveType;
87
+ // See discussion issue #6520: should we allow all scalar types?
86
88
  if (!type || !type.builtin || type.category !== 'string') {
87
89
  warning(null, [ elem.type?.location, elem ], { keyword: 'localized' },
88
90
  'Keyword $(KEYWORD) should only be used in combination with string types');
@@ -339,6 +341,7 @@ function check( model ) { // = XSN
339
341
  }
340
342
 
341
343
  // Check that min and max cardinalities of 'elem' in 'art' have legal values
344
+ // TODO: move to define.js or parsers
342
345
  function checkCardinality(elem) {
343
346
  if (!elem.cardinality)
344
347
  return;
@@ -382,13 +385,16 @@ function check( model ) { // = XSN
382
385
 
383
386
  // If provided, min cardinality must not exceed max cardinality (note that
384
387
  // '*' is considered to be >= any number)
385
- const pair = [ [ 'sourceMin', 'sourceMax', 'Source' ], [ 'targetMin', 'targetMax', 'Target' ] ];
388
+ const pair = [ [ 'sourceMin', 'sourceMax', 'source' ], [ 'targetMin', 'targetMax', 'target' ] ];
386
389
  pair.forEach((p) => {
387
390
  if (elem.cardinality[p[0]] && elem.cardinality[p[1]] &&
388
391
  elem.cardinality[p[1]].literal === 'number' &&
389
392
  elem.cardinality[p[0]].val > elem.cardinality[p[1]].val) {
390
- error(null, [ elem.cardinality.location, elem ], {},
391
- `${ p[2] } minimum cardinality must not be greater than ${ p[2].toLowerCase() } maximum cardinality`);
393
+ error(null, [ elem.cardinality.location, elem ], { '#': p[2] }, {
394
+ std: 'Minimum cardinality must not be greater than maximum cardinality', // variant unused
395
+ source: 'Source minimum cardinality must not be greater than source maximum cardinality',
396
+ target: 'Target minimum cardinality must not be greater than target maximum cardinality',
397
+ });
392
398
  }
393
399
  });
394
400
  }
@@ -494,7 +500,7 @@ function check( model ) { // = XSN
494
500
  // associations can be followed (in the ON condition)
495
501
  //
496
502
  // TODO: this function must be completely reworked, probably even before
497
- // integration into name resulution - did the first step.
503
+ // integration into name resolution - did the first step.
498
504
  // It is also incomplete, as associations in structures are not checked.
499
505
  // Additionally, `$self.assoc` references are also not found.
500
506
  function singleCheckUnmanagedAssocCondArgumentNoFollowUnmanagedAssoc(elem, arg, op) {
@@ -639,8 +645,10 @@ function check( model ) { // = XSN
639
645
  'An association can\'t be used as a value in an expression');
640
646
  }
641
647
 
642
- if (isDollarSelfOrProjectionOperand(arg))
643
- error(null, arg.location, {}, `"${ arg.path[0].id }" can only be used as a value in a comparison to an association`);
648
+ if (isDollarSelfOrProjectionOperand(arg)) {
649
+ error(null, arg.location, { id: arg.path[0].id },
650
+ '$(ID) can only be used as a value in a comparison to an association');
651
+ }
644
652
 
645
653
  // Recursively traverse the argument expression
646
654
  checkTreeLikeExpression(arg, allowAssocTail);
@@ -739,7 +747,9 @@ function check( model ) { // = XSN
739
747
 
740
748
  // Element must exist in annotation
741
749
  if (!elementDecl) {
742
- warning(null, anno.location || anno.name.location, {}, `Element "${ anno.name.path.map(step => step.id).join('.') }" not found for annotation "${ annoDecl.name.absolute }"`);
750
+ warning(null, anno.location || anno.name.location,
751
+ { name: pathName(anno.name.path), anno: annoDecl.name.absolute },
752
+ 'Element $(NAME) not found for annotation $(ANNO)');
743
753
  return;
744
754
  }
745
755
 
@@ -751,8 +761,8 @@ function check( model ) { // = XSN
751
761
  // Must have literal or path unless it is a boolean
752
762
  if (!anno.literal && !anno.path && getFinalTypeNameOf(elementDecl) !== 'cds.Boolean') {
753
763
  if (elementDecl.type && elementDecl.type._artifact.name.absolute) {
754
- warning(null, anno.location || anno.name.location, {},
755
- `Expecting a value of type "${ elementDecl.type._artifact.name.absolute }" for the annotation`);
764
+ warning(null, anno.location || anno.name.location, { type: elementDecl.type._artifact },
765
+ 'Expecting a value of type $(TYPE) for the annotation');
756
766
  }
757
767
  else {
758
768
  warning(null, anno.location || anno.name.location, {},
@@ -807,31 +817,31 @@ function check( model ) { // = XSN
807
817
  if (builtins.isStringTypeName(type)) {
808
818
  if (value.literal !== 'string' && value.literal !== 'enum' &&
809
819
  !elementDecl._effectiveType.enum)
810
- warning(null, loc, {}, `A string value is required for type "${ type }"`);
820
+ warning(null, loc, { type }, 'A string value is required for type $(TYPE)');
811
821
  }
812
822
  else if (builtins.isBinaryTypeName(type)) {
813
823
  if (value.literal !== 'string' && value.literal !== 'x')
814
- warning(null, loc, {}, `A hexadecimal string value is required for type "${ type }"`);
824
+ warning(null, loc, { type }, 'A hexadecimal string value is required for type $(TYPE)');
815
825
  }
816
826
  else if (builtins.isNumericTypeName(type)) {
817
827
  if (value.literal !== 'number' && value.literal !== 'enum' &&
818
828
  !elementDecl._effectiveType.enum)
819
- warning(null, loc, {}, `A numerical value is required for type "${ type }"`);
829
+ warning(null, loc, { type }, 'A numerical value is required for type $(TYPE)');
820
830
  }
821
831
  else if (builtins.isDateOrTimeTypeName(type)) {
822
832
  if (value.literal !== 'date' && value.literal !== 'time' &&
823
833
  value.literal !== 'timestamp' && value.literal !== 'string')
824
- warning(null, loc, {}, `A date/time value or a string is required for type "${ type }"`);
834
+ warning(null, loc, { type }, 'A date/time value or a string is required for type $(TYPE)');
825
835
  }
826
836
  else if (builtins.isBooleanTypeName(type)) {
827
837
  if (value.literal && value.literal !== 'boolean')
828
- warning(null, loc, {}, `A boolean value is required for type "${ type }"`);
838
+ warning(null, loc, { type }, 'A boolean value is required for type $(TYPE)');
829
839
  }
830
840
  else if (builtins.isRelationTypeName(type) || builtins.isGeoTypeName(type)) {
831
- warning(null, loc, {}, `Type "${ type }" can't be assigned a value`);
841
+ warning(null, loc, { type }, 'Type $(TYPE) can\'t be assigned a value');
832
842
  }
833
843
  else {
834
- throw new Error(`Unknown primitive type name: ${ type }`);
844
+ throw new CompilerAssertion(`Unknown primitive type name: ${ type }`);
835
845
  }
836
846
 
837
847
  // Check enums
@@ -841,12 +851,12 @@ function check( model ) { // = XSN
841
851
  // Enum symbol provided and expected
842
852
  if (!expectedEnum[value.sym.id]) {
843
853
  // .. but no such constant
844
- warning(null, loc, {}, `Enum symbol "#${ value.sym.id }" not found in enum`);
854
+ warning(null, loc, { id: `#${ value.sym.id }` }, 'Enum symbol $(ID) not found in enum');
845
855
  }
846
856
  }
847
857
  else {
848
858
  // Enum symbol provided but not expected
849
- warning(null, loc, {}, `Can't use enum symbol "#${ value.sym.id }" for non-enum type "${ type }"`);
859
+ warning(null, loc, { id: `#${ value.sym.id }`, type }, 'Can\'t use enum symbol $(ID) for non-enum type $(TYPE)');
850
860
  }
851
861
  }
852
862
  else if (expectedEnum) {
@@ -1,15 +1,31 @@
1
- // Compiler phase "define": transform dictionary of AST-like CSNs into augmented CSN
1
+ // Compiler phase 1 = "define": transform dictionary of AST-like XSNs into XSN
2
2
 
3
- // AST-like CSN looks as follows:
4
- // { kind: 'source', env: <dictionary of artifact defs>, namespace: {}, ... }
3
+ // The 'define' phase (function 'define' below) is the first phase of the compile
4
+ // function. In it, the compiler
5
+ //
6
+ // - collects definitions and extensions from the XSN representation of CDL and
7
+ // CSN sources (“ASTs”) into _one_ XSN model,
8
+ // - sets “structural” links between XSN nodes and completes the “name”,
9
+ // some links and names inside `extensions` are set at a later stage
10
+ // - reports errors for: “late” syntax errors (when it is more convenient to do
11
+ // it here instead of doing it in both CDL and CSN parser), “structural” errors
12
+ // and “duplicate definition errors”
13
+
14
+ // The 'define' phase is the only compile() phase which is also called for
15
+ // parse.cdl. See file ./finalize-parse-cdl.js for details.
16
+
17
+ // --------- TODO: begin in extra markdown document -----------------------------
18
+
19
+ // An XSN for a source looks like
20
+ // { kind: 'source', artifacts: <dictionary of artifact defs>, namespace: {}, ... }
5
21
  //
6
22
  // The property `artifacts` of a source contains the top-level definitions.
7
23
  // Definitions inside a context are not listed here (as opposed to
8
24
  // `definitions`, see below), but inside the property `artifacts` of that context.
9
25
 
10
26
  // The 'define' phase (function 'define' below) enriches a dictionary of
11
- // (file names to) AST-like CSNs and restructure them a little bit, the result
12
- // is called "augmented CSN":
27
+ // (file names to) AST-like XSNs and restructure them a little bit, the result
28
+ // is called XSN ("augmented CSN"):
13
29
  // { sources: <dictionary of ASTs>, definitions: <dictionary of artifact defs> }
14
30
  //
15
31
  // The property `sources` is the input argument (dictionary of source ASTs).
@@ -20,14 +36,8 @@
20
36
  // objects as the definitions accessible via `sources` and `artifacts` of the
21
37
  // corresponding source/context.
22
38
  //
23
- // Because different sources could define artifacts with the same absolute
24
- // name, this compiler phase also put a property `messages` to the resulting
25
- // model, which is a vector of messages for the redefinitions. (Using the same
26
- // name for different definitions in one source is already recognized during
27
- // parsing.)
28
- //
29
39
  // You get the compact "official" CSN format by applying the function exported
30
- // by "../json/to-csn.js" to the augmented CSN.
40
+ // by "../json/to-csn.js" to the XSN.
31
41
 
32
42
  // Example 'file.cds':
33
43
  // namespace A;
@@ -54,7 +64,7 @@
54
64
  // An artifact definition looks as follows (example: context "A.B" above):
55
65
  // {
56
66
  // kind: 'context',
57
- // name: { id: 'B', absolute: 'A.B', location: { <for the id "B"> } },
67
+ // name: { path: [ { id: 'B'} ], absolute: 'A.B', location: { <for the id "B"> } },
58
68
  // artifacts: <for contexts, a dictionary of artifacts defined within>,
59
69
  // location: { <of the complete artifact definition> } },
60
70
  // _parent: <the parent artifact, here the source 'file.cds'>
@@ -74,11 +84,10 @@
74
84
  // location: { <of the complete element definition> } },
75
85
  // _parent: <the parent artifact, here the type "A.B.C">
76
86
  // }
77
- // References are resolved in the "resolve" phase of the compiler, see
78
- // './resolver.js'. We then get the properties `type.absolute` and `length`.
87
+ // --------- TODO: end in extra markdown document -------------------------------
79
88
 
80
89
  // Sub phase 1 (addXYZ) - only for main artifacts
81
- // - set _block links
90
+ // - set _block links for main definitions, vocabulary and extensions
82
91
  // - store definitions (including context extensions), NO duplicate check
83
92
  // - artifact name check
84
93
  // - Note: the only allow name resolving is resolveUncheckedPath(),
@@ -96,6 +105,7 @@
96
105
  // More sub phases...
97
106
 
98
107
  // The main difficulty is the correct behavior concerning duplicate definitions
108
+ // - For code completion, all duplicate definitions must be further checked.
99
109
  // - We need a unique object for the _subArtifacts dictionary.
100
110
  // - We must have a property at the artifact whether there are duplicates in order
101
111
  // to avoid consequential or repeated errors.
@@ -121,6 +131,7 @@ const {
121
131
  dependsOnSilent,
122
132
  pathName,
123
133
  splitIntoPath,
134
+ annotationHasEllipsis,
124
135
  } = require('./utils');
125
136
  const { compareLayer } = require('./moduleLayers');
126
137
  const { initBuiltins, isInReservedNamespace } = require('./builtins');
@@ -148,15 +159,15 @@ function define( model ) {
148
159
  const {
149
160
  resolveUncheckedPath,
150
161
  checkAnnotate,
151
- defineAnnotations,
152
162
  } = model.$functions;
153
163
 
154
164
  const extensionsDict = Object.create(null);
155
165
  Object.assign( model.$functions, {
156
166
  initArtifact,
157
167
  initMembers,
158
- extensionsDict, // a dictionary - TODO: remove
168
+ extensionsDict, // a dictionary - TODO: put directly into model?
159
169
  checkDefinitions,
170
+ initAnnotations,
160
171
  } );
161
172
  // During the definer, we can only resolve artifact references, i.e,
162
173
  // after a `.`, we only search in the `_subArtifacts` dictionary:
@@ -193,6 +204,7 @@ function define( model ) {
193
204
  initNamespaceAndUsing( model.sources[name] );
194
205
  dictForEach( model.definitions, initArtifact );
195
206
  dictForEach( model.vocabularies, initVocabulary );
207
+ dictForEach( extensionsDict, initExtension );
196
208
 
197
209
  mergeI18nBlocks();
198
210
  }
@@ -394,7 +406,7 @@ function define( model ) {
394
406
 
395
407
  // Phase 2 ("init") --------------------------------------------------------
396
408
  // Functions called from top-level: initNamespaceAndUsing(), initArtifact(),
397
- // initVocabulary()
409
+ // initVocabulary(), initExtension()
398
410
 
399
411
  function checkRedefinition( art ) {
400
412
  if (!art.$duplicates || art.$errorReported === 'syntax-duplicate-extend' ||
@@ -461,11 +473,11 @@ function define( model ) {
461
473
  initParentLink( art, model.definitions );
462
474
  const block = art._block;
463
475
  checkRedefinition( art );
464
- defineAnnotations( art, art, block );
476
+ initAnnotations( art, block );
465
477
  initMembers( art, art, block );
466
478
  initDollarSelf( art ); // $self
467
479
  if (art.params)
468
- initParams( art ); // $parameters
480
+ initDollarParameters( art );
469
481
  if (art.includes && !(art.name.absolute in extensionsDict)) // TODO: in next phase?
470
482
  extensionsDict[art.name.absolute] = []; // structure with includes must be "extended"
471
483
 
@@ -492,7 +504,7 @@ function define( model ) {
492
504
  initParentLink( art, model.vocabularies );
493
505
  checkRedefinition( art );
494
506
  const block = art._block;
495
- defineAnnotations( art, art, block );
507
+ initAnnotations( art, block );
496
508
  initMembers( art, art, block );
497
509
  }
498
510
 
@@ -518,6 +530,57 @@ function define( model ) {
518
530
  parent._subArtifacts[absolute.substring( dot + 1 )] = art; // not dictAdd()
519
531
  }
520
532
 
533
+ /** Initialize the extension `ext`.
534
+ *
535
+ * Currently:
536
+ *
537
+ * - initialize annotations (set _block, $priority, `...` check) on "main"
538
+ * extension and its columns do more later
539
+ * - for members in compile(): init annotations via extendMembers/annotateMembers
540
+ * - for members in parse.cdl(): init annotation via initMembers
541
+ *
542
+ * In the future:
543
+ *
544
+ * - also initialize members and member extensions/annotations here
545
+ * - we might also do other things, like calculating whether an `extend` is
546
+ * `annotate`-like, i.e. only contains name-resolution irrelevant extensions.
547
+ */
548
+ function initExtension( ext ) {
549
+ const block = ext._block;
550
+ initAnnotations( ext, block, ext.kind );
551
+ if (ext.columns) // the columns themselves are "definitions"
552
+ ext.columns.forEach( col => initAnnotations( col, block ) );
553
+ }
554
+
555
+ // Set _block links for annotations (necessary for layering) and do a late
556
+ // syntax check (`...` only with extensions, not definitions).
557
+ // extKind is either ext.kind (=art is extension) or false (=art is not an extension)
558
+ function initAnnotations( art, block, extKind = false ) {
559
+ // TODO: think of removing $priority, then
560
+ // no _block: define, _block: annotate/extend/edmx
561
+ // would fit with extending defs with props like length
562
+ for (const prop in art) {
563
+ if (prop.charAt(0) === '@') {
564
+ const anno = art[prop];
565
+ // TODO: make anno never be an array, see addAnnotation() in genericAntlrParser
566
+ if (Array.isArray( anno ))
567
+ anno.forEach( init );
568
+ else
569
+ init( anno );
570
+ }
571
+ }
572
+ return;
573
+
574
+ function init( anno ) {
575
+ setLink( anno, '_block', block );
576
+ anno.$priority = extKind;
577
+ if (!extKind && annotationHasEllipsis( anno )) {
578
+ error( 'anno-unexpected-ellipsis',
579
+ [ anno.name.location, art ], { code: '...' } );
580
+ }
581
+ }
582
+ }
583
+
521
584
  // From here til EOF, reexamine code ---------------------------------------
522
585
  // See populate:
523
586
  // - userQuery() or _query property?
@@ -543,7 +606,8 @@ function define( model ) {
543
606
  setLink( art, '_$next', model.$magicVariables );
544
607
  }
545
608
 
546
- function initParams( art ) {
609
+ function initDollarParameters( art ) {
610
+ // TODO: remove $parameters in v4?
547
611
  // TODO: use setMemberParent() ?
548
612
  const parameters = {
549
613
  name: { id: '$parameters', param: '$parameters', absolute: art.name.absolute },
@@ -606,7 +670,7 @@ function define( model ) {
606
670
  // Either expression (value), expand or new association (target && type)
607
671
  else if (col.value || col.expand || (col.target && col.type)) {
608
672
  setLink( col, '_block', parent._block );
609
- defineAnnotations( col, col, parent._block );
673
+ initAnnotations( col, parent._block );
610
674
  if (col.inline) { // `@anno elem.{ * }` does not work
611
675
  if (col.doc)
612
676
  warning( 'syntax-ignoring-anno', [ col.doc.location, col ], { '#': 'doc' } );
@@ -1025,7 +1089,7 @@ function define( model ) {
1025
1089
  checkRedefinition( elem );
1026
1090
  if (elem.kind === 'annotate' || elem.kind === 'extend')
1027
1091
  checkAnnotate( elem, elem );
1028
- defineAnnotations( elem, elem, bl );
1092
+ initAnnotations( elem, bl );
1029
1093
  initMembers( elem, elem, bl, initExtensions );
1030
1094
 
1031
1095
  // for a correct home path, setMemberParent needed to be called