@sap/cds-compiler 2.11.4 → 2.13.8

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 (133) hide show
  1. package/CHANGELOG.md +159 -1
  2. package/bin/cds_update_identifiers.js +7 -7
  3. package/bin/cdsc.js +22 -23
  4. package/bin/cdsse.js +2 -2
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +25 -6
  7. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  8. package/doc/NameResolution.md +21 -16
  9. package/lib/api/main.js +30 -63
  10. package/lib/api/options.js +5 -5
  11. package/lib/api/validate.js +0 -5
  12. package/lib/backends.js +15 -23
  13. package/lib/base/dictionaries.js +0 -8
  14. package/lib/base/error.js +26 -0
  15. package/lib/base/keywords.js +7 -17
  16. package/lib/base/location.js +9 -4
  17. package/lib/base/message-registry.js +52 -2
  18. package/lib/base/messages.js +16 -26
  19. package/lib/base/model.js +2 -62
  20. package/lib/base/optionProcessorHelper.js +246 -183
  21. package/lib/checks/.eslintrc.json +2 -0
  22. package/lib/checks/actionsFunctions.js +2 -1
  23. package/lib/checks/annotationsOData.js +1 -1
  24. package/lib/checks/cdsPersistence.js +2 -1
  25. package/lib/checks/enricher.js +17 -1
  26. package/lib/checks/foreignKeys.js +4 -4
  27. package/lib/checks/invalidTarget.js +3 -1
  28. package/lib/checks/managedInType.js +4 -4
  29. package/lib/checks/managedWithoutKeys.js +3 -1
  30. package/lib/checks/queryNoDbArtifacts.js +1 -3
  31. package/lib/checks/selectItems.js +4 -4
  32. package/lib/checks/sql-snippets.js +94 -0
  33. package/lib/checks/types.js +1 -1
  34. package/lib/checks/validator.js +12 -7
  35. package/lib/compiler/assert-consistency.js +10 -6
  36. package/lib/compiler/base.js +0 -1
  37. package/lib/compiler/builtins.js +8 -6
  38. package/lib/compiler/checks.js +46 -12
  39. package/lib/compiler/cycle-detector.js +1 -1
  40. package/lib/compiler/define.js +1103 -0
  41. package/lib/compiler/extend.js +983 -0
  42. package/lib/compiler/finalize-parse-cdl.js +231 -0
  43. package/lib/compiler/index.js +33 -14
  44. package/lib/compiler/kick-start.js +190 -0
  45. package/lib/compiler/moduleLayers.js +4 -4
  46. package/lib/compiler/populate.js +1226 -0
  47. package/lib/compiler/propagator.js +113 -47
  48. package/lib/compiler/resolve.js +1433 -0
  49. package/lib/compiler/shared.js +76 -38
  50. package/lib/compiler/tweak-assocs.js +529 -0
  51. package/lib/compiler/utils.js +204 -33
  52. package/lib/edm/.eslintrc.json +5 -0
  53. package/lib/edm/annotations/genericTranslation.js +38 -25
  54. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  55. package/lib/edm/csn2edm.js +10 -9
  56. package/lib/edm/edm.js +19 -20
  57. package/lib/edm/edmPreprocessor.js +166 -95
  58. package/lib/edm/edmUtils.js +127 -34
  59. package/lib/gen/Dictionary.json +92 -43
  60. package/lib/gen/language.checksum +1 -1
  61. package/lib/gen/language.interp +11 -1
  62. package/lib/gen/language.tokens +86 -82
  63. package/lib/gen/languageLexer.interp +18 -1
  64. package/lib/gen/languageLexer.js +925 -847
  65. package/lib/gen/languageLexer.tokens +78 -74
  66. package/lib/gen/languageParser.js +5434 -4298
  67. package/lib/json/from-csn.js +59 -17
  68. package/lib/json/to-csn.js +143 -71
  69. package/lib/language/antlrParser.js +3 -3
  70. package/lib/language/docCommentParser.js +3 -3
  71. package/lib/language/genericAntlrParser.js +144 -54
  72. package/lib/language/language.g4 +424 -203
  73. package/lib/language/multiLineStringParser.js +536 -0
  74. package/lib/main.d.ts +472 -61
  75. package/lib/main.js +38 -11
  76. package/lib/model/api.js +3 -1
  77. package/lib/model/csnRefs.js +321 -204
  78. package/lib/model/csnUtils.js +224 -263
  79. package/lib/model/enrichCsn.js +97 -40
  80. package/lib/model/revealInternalProperties.js +27 -6
  81. package/lib/model/sortViews.js +2 -1
  82. package/lib/modelCompare/compare.js +17 -12
  83. package/lib/optionProcessor.js +7 -6
  84. package/lib/render/DuplicateChecker.js +1 -1
  85. package/lib/render/manageConstraints.js +36 -33
  86. package/lib/render/toCdl.js +174 -275
  87. package/lib/render/toHdbcds.js +201 -115
  88. package/lib/render/toRename.js +7 -10
  89. package/lib/render/toSql.js +149 -75
  90. package/lib/render/utils/common.js +22 -8
  91. package/lib/render/utils/sql.js +10 -7
  92. package/lib/render/utils/stringEscapes.js +111 -0
  93. package/lib/sql-identifier.js +1 -1
  94. package/lib/transform/.eslintrc.json +5 -0
  95. package/lib/transform/braceExpression.js +4 -2
  96. package/lib/transform/db/.eslintrc.json +2 -0
  97. package/lib/transform/db/applyTransformations.js +35 -12
  98. package/lib/transform/db/assertUnique.js +1 -1
  99. package/lib/transform/db/associations.js +187 -0
  100. package/lib/transform/db/cdsPersistence.js +150 -0
  101. package/lib/transform/db/constraints.js +61 -56
  102. package/lib/transform/db/expansion.js +50 -29
  103. package/lib/transform/db/flattening.js +552 -105
  104. package/lib/transform/db/groupByOrderBy.js +3 -1
  105. package/lib/transform/db/temporal.js +236 -0
  106. package/lib/transform/db/transformExists.js +94 -28
  107. package/lib/transform/db/views.js +5 -4
  108. package/lib/transform/draft/.eslintrc.json +38 -0
  109. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  110. package/lib/transform/draft/odata.js +227 -0
  111. package/lib/transform/forHanaNew.js +94 -801
  112. package/lib/transform/forOdataNew.js +22 -175
  113. package/lib/transform/localized.js +36 -32
  114. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  115. package/lib/transform/odata/referenceFlattener.js +95 -89
  116. package/lib/transform/odata/structureFlattener.js +1 -1
  117. package/lib/transform/odata/toFinalBaseType.js +86 -12
  118. package/lib/transform/odata/typesExposure.js +5 -5
  119. package/lib/transform/odata/utils.js +2 -2
  120. package/lib/transform/transformUtilsNew.js +47 -33
  121. package/lib/transform/translateAssocsToJoins.js +10 -27
  122. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  123. package/lib/transform/universalCsn/coreComputed.js +170 -0
  124. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  125. package/lib/transform/universalCsn/utils.js +63 -0
  126. package/lib/utils/file.js +2 -1
  127. package/lib/utils/objectUtils.js +30 -0
  128. package/lib/utils/timetrace.js +8 -2
  129. package/package.json +1 -1
  130. package/share/messages/README.md +26 -0
  131. package/lib/compiler/definer.js +0 -2340
  132. package/lib/compiler/resolver.js +0 -2988
  133. package/lib/transform/universalCsnEnricher.js +0 -67
@@ -13,7 +13,8 @@
13
13
  function validateCdsPersistenceAnnotation(artifact, artifactName, prop, path) {
14
14
  if (artifact.kind === 'entity') {
15
15
  // filter for 'table', 'udf', 'calcview' === true
16
- const TableUdfCv = Object.keys(artifact).filter(p => [ '@cds.persistence.table', '@cds.persistence.udf', '@cds.persistence.calcview' ].includes(p) && artifact[p]);
16
+ const persistenceAnnos = [ '@cds.persistence.table', '@cds.persistence.udf', '@cds.persistence.calcview' ];
17
+ const TableUdfCv = Object.keys(artifact).filter(p => persistenceAnnos.includes(p) && artifact[p]);
17
18
  if (TableUdfCv.length > 1)
18
19
  this.error(null, path, `Annotations ${ TableUdfCv.join(', ') } can't be used in combination`);
19
20
  }
@@ -30,6 +30,7 @@ function enrichCsn( csn ) {
30
30
  type: simpleRef,
31
31
  target: simpleRef,
32
32
  includes: simpleRef,
33
+ columns,
33
34
  // Annotations are ignored.
34
35
  '@': () => { /* ignore annotations */ },
35
36
  };
@@ -40,7 +41,7 @@ function enrichCsn( csn ) {
40
41
  cleanupCallbacks = [];
41
42
  };
42
43
 
43
- const { inspectRef, artifactRef } = csnRefs( csn );
44
+ const { inspectRef, artifactRef, getElement } = csnRefs( csn );
44
45
  const csnPath = [];
45
46
  if (csn.definitions)
46
47
  dictionary( csn, 'definitions', csn.definitions );
@@ -82,6 +83,21 @@ function enrichCsn( csn ) {
82
83
  csnPath.pop();
83
84
  }
84
85
 
86
+ // eslint-disable-next-line jsdoc/require-jsdoc
87
+ function columns( parent, prop, node ) {
88
+ // Establish the link relationships
89
+ parent[prop].forEach((column) => {
90
+ const element = getElement(column);
91
+ if (element) {
92
+ setProp(column, '_element', element);
93
+ cleanupCallbacks.push(() => delete column._element);
94
+ setProp(element, '_column', column);
95
+ cleanupCallbacks.push(() => delete element._column);
96
+ }
97
+ });
98
+ standard(parent, prop, node);
99
+ }
100
+
85
101
  // eslint-disable-next-line jsdoc/require-jsdoc
86
102
  function simpleRef( node, prop ) {
87
103
  setProp(node, '$path', [ ...csnPath ]);
@@ -18,12 +18,12 @@ function validateForeignKeys(member) {
18
18
 
19
19
  // Declared as arrow-function to keep scope the same (this value)
20
20
  const handleAssociation = (mem) => {
21
- for (let i = 0; i < mem.keys.length; i++) {
22
- if (mem.keys[i].ref) {
23
- if (!mem.keys[i]._art)
21
+ for (const key of mem.keys) {
22
+ if (key.ref) {
23
+ if (!key._art)
24
24
  continue;
25
25
  // eslint-disable-next-line no-use-before-define
26
- checkForItems(mem.keys[i]._art);
26
+ checkForItems(key._art);
27
27
  }
28
28
  }
29
29
  };
@@ -2,6 +2,8 @@
2
2
 
3
3
  // Only to be used with validator.js - a correct this value needs to be provided!
4
4
 
5
+ const { ModelError } = require('../base/error');
6
+
5
7
  /**
6
8
  * Assert that targets of associations and compositions are entities.
7
9
  *
@@ -22,7 +24,7 @@ function invalidTarget(member) {
22
24
  if (mem.target) {
23
25
  const target = this.csn.definitions[mem.target];
24
26
  if (!target)
25
- throw new Error(`Expected target ${ mem.target }`);
27
+ throw new ModelError(`Expected target ${ mem.target }`);
26
28
  if (target.kind !== 'entity') {
27
29
  const isAssoc = this.csnUtils.getFinalBaseType(member.type) !== 'cds.Composition';
28
30
  this.error(
@@ -13,11 +13,11 @@
13
13
  function checkUsedTypesForAnonymousAspectComposition(member) {
14
14
  // Declared as arrow-function to keep scope the same (this value)
15
15
  const handleAssociation = (mem, fn) => {
16
- for (let i = 0; i < mem.keys.length; i++) {
17
- if (mem.keys[i].ref) {
18
- if (!mem.keys[i]._art)
16
+ for (const key of mem.keys) {
17
+ if (key.ref) {
18
+ if (!key._art)
19
19
  continue;
20
- fn(mem.keys[i]._art);
20
+ fn(key._art);
21
21
  }
22
22
  }
23
23
  };
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
 
4
+ const { ModelError } = require('../base/error');
5
+
4
6
  /**
5
7
  * Trigger a recompilation in case of an association without .keys and without .on
6
8
  *
@@ -10,7 +12,7 @@
10
12
  */
11
13
  function managedWithoutKeys(member, memberName, prop) {
12
14
  if (prop === 'elements' && member.target && !member.keys && !member.on) { // trigger recompilation
13
- throw new Error('Expected association to have either an on-condition or foreign keys.');
15
+ throw new ModelError('Expected association to have either an on-condition or foreign keys.');
14
16
  }
15
17
  }
16
18
 
@@ -124,10 +124,8 @@ function checkQueryForNoDBArtifacts(query) {
124
124
  for (const prop of generalQueryProperties) {
125
125
  const queryPart = (query.SELECT || query.SET)[prop];
126
126
  if (Array.isArray(queryPart)) {
127
- for (let i = 0; i < queryPart.length; i++) {
128
- const part = queryPart[i];
127
+ for (const part of queryPart)
129
128
  checkRef(part, prop === 'columns');
130
- }
131
129
  }
132
130
  else if (typeof queryPart === 'object') {
133
131
  checkRef(queryPart, prop === 'columns');
@@ -29,7 +29,7 @@ function validateSelectItems(query) {
29
29
  }
30
30
  });
31
31
  // .call() with 'this' to ensure we have access to the options
32
- rejectManagedAssociationsAndStructuresForHdbcsNames.call(this, SELECT, SELECT.$path);
32
+ rejectManagedAssociationsAndStructuresForHdbcdsNames.call(this, SELECT, SELECT.$path);
33
33
  }
34
34
 
35
35
 
@@ -41,10 +41,10 @@ function validateSelectItems(query) {
41
41
  * @param {CSN.Artifact} queryArtifact the query artifact which should be checked
42
42
  * @param {CSN.Path} artifactPath the path to that artifact
43
43
  */
44
- function rejectManagedAssociationsAndStructuresForHdbcsNames(queryArtifact, artifactPath) {
44
+ function rejectManagedAssociationsAndStructuresForHdbcdsNames(queryArtifact, artifactPath) {
45
45
  if (this.options.transformation === 'hdbcds' && this.options.sqlMapping === 'hdbcds') {
46
46
  forEachGeneric(queryArtifact, 'elements', (selectItem, elemName, prop, elementPath) => {
47
- if (this.csnUtils.isManagedAssociationElement(selectItem))
47
+ if (this.csnUtils.isManagedAssociation(selectItem))
48
48
  this.error('query-unexpected-assoc-hdbcds', elementPath);
49
49
  if (this.csnUtils.isStructured(selectItem))
50
50
  this.error('query-unexpected-structure-hdbcds', elementPath);
@@ -52,4 +52,4 @@ function rejectManagedAssociationsAndStructuresForHdbcsNames(queryArtifact, arti
52
52
  }
53
53
  }
54
54
 
55
- module.exports = { validateSelectItems, rejectManagedAssociationsAndStructuresForHdbcsNames };
55
+ module.exports = { validateSelectItems, rejectManagedAssociationsAndStructuresForHdbcdsNames };
@@ -0,0 +1,94 @@
1
+ 'use strict';
2
+
3
+ // Only to be used with validator.js - a correct this value needs to be provided!
4
+
5
+ /**
6
+ * Check that @sql.prepend annotation is not used on any elements and @sql.append is not used on elements in views.
7
+ *
8
+ * @param {CSN.Element} member
9
+ * @param {string} memberName
10
+ * @param {string} prop
11
+ * @param {CSN.Path} path
12
+ * @returns {void}
13
+ */
14
+ function checkSqlAnnotationOnElement(member, memberName, prop, path) {
15
+ if (member['@sql.replace'])
16
+ this.error(null, path, { anno: 'sql.replace' }, `Annotation $(ANNO) is reserved and must not be used`);
17
+ if (member['@sql.prepend'])
18
+ this.message('anno-invalid-sql-element', path, { anno: 'sql.prepend' }, `Annotation $(ANNO) can't be used on elements` );
19
+
20
+ if (member['@sql.append']) {
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` );
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` );
25
+ else
26
+ checkValidAnnoValue(member, '@sql.append', path, this.error, this.options);
27
+ }
28
+ }
29
+
30
+ /**
31
+ * @param {object} carrier element which has the annotation
32
+ * @param {string} annotation
33
+ * @param {CSN.Path} path
34
+ * @param {Function} error
35
+ * @param {CSN.Options} options
36
+ */
37
+ function checkValidAnnoValue(carrier, annotation, path, error, options) {
38
+ if (carrier[annotation] !== undefined && carrier[annotation] !== null) {
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)` );
41
+ else if (options.transformation === 'sql') // HDI and HDBCDS do their own checks
42
+ guardAgainstInjection(annotation, carrier[annotation], path, error);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Check that @sql.prepend is not used on views - only supported for entities (tables)
48
+ *
49
+ * @param {CSN.Artifact} artifact
50
+ * @param {string} artifactName
51
+ */
52
+ function checkSqlAnnotationOnArtifact(artifact, artifactName) {
53
+ if (artifact.kind !== 'entity') {
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)` );
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)` );
58
+ }
59
+ else if (artifact['@sql.prepend']) {
60
+ if (artifact.query)
61
+ this.message('anno-invalid-sql-view', [ 'definitions', artifactName ], { name: '@sql.prepend' }, `Annotation $(NAME) can't be used on views` );
62
+ else
63
+ checkValidAnnoValue(artifact, '@sql.prepend', [ 'definitions', artifactName ], this.error, this.options);
64
+ }
65
+
66
+
67
+ if (artifact['@sql.replace'])
68
+ this.error(null, [ 'definitions', artifactName ], { anno: 'sql.replace' }, `Annotation $(ANNO) is reserved and must not be used`);
69
+
70
+ checkValidAnnoValue(artifact, '@sql.append', [ 'definitions', artifactName ], this.error, this.options);
71
+ }
72
+
73
+ // Anything that could terminate the "old" statement and start a new one basically.
74
+ const invalidInSnippet = [ ';', '--', '/*', '*/' ];
75
+
76
+ /**
77
+ * Check that the common characters used to terminate the current statement and start a fresh one are not used.
78
+ *
79
+ * @param {string} annoName
80
+ * @param {string} annoValue
81
+ * @param {CSN.Path} path
82
+ * @param {Function} error
83
+ */
84
+ function guardAgainstInjection(annoName, annoValue, path, error) {
85
+ for (const invalid of invalidInSnippet) {
86
+ if (annoValue.indexOf(invalid) !== -1) // These should probably not be configurable, right?
87
+ error(null, path, { name: annoName, prop: invalid }, 'Annotation $(NAME) must not contain $(PROP)');
88
+ }
89
+ }
90
+
91
+ module.exports = {
92
+ checkSqlAnnotationOnArtifact,
93
+ checkSqlAnnotationOnElement,
94
+ };
@@ -19,7 +19,7 @@ function checkDecimalScale(member, memberName, prop, path) {
19
19
  // skip is already filtered in validator, here for completeness
20
20
  hasAnnotationValue(this.artifact, '@cds.persistence.skip'))
21
21
  return;
22
- if (member.scale && [ 'variable', 'floating' ].includes(member.scale))
22
+ if (member.scale && (member.scale === 'variable' || member.scale === 'floating'))
23
23
  this.error(null, path, { name: member.scale }, 'Unexpected scale $(NAME)');
24
24
  }
25
25
 
@@ -35,6 +35,10 @@ const checkExplicitlyNullableKeys = require('./nullableKeys');
35
35
  const nonexpandableStructuredInExpression = require('./nonexpandableStructured');
36
36
  const unknownMagic = require('./unknownMagic');
37
37
  const managedWithoutKeys = require('./managedWithoutKeys');
38
+ const {
39
+ checkSqlAnnotationOnArtifact,
40
+ checkSqlAnnotationOnElement,
41
+ } = require('./sql-snippets');
38
42
 
39
43
  const forHanaMemberValidators
40
44
  = [
@@ -45,6 +49,8 @@ const forHanaMemberValidators
45
49
  checkExplicitlyNullableKeys,
46
50
  managedWithoutKeys,
47
51
  warnAboutDefaultOnAssociationForHanaCds,
52
+ // sql.prepend/append
53
+ checkSqlAnnotationOnElement,
48
54
  ];
49
55
 
50
56
  const forHanaArtifactValidators
@@ -53,6 +59,8 @@ const forHanaArtifactValidators
53
59
  validateCdsPersistenceAnnotation,
54
60
  // virtual items are not persisted on the db
55
61
  checkForEmptyOrOnlyVirtual,
62
+ // sql.prepend/append
63
+ checkSqlAnnotationOnArtifact,
56
64
  ];
57
65
 
58
66
  const forHanaCsnValidators = [ nonexpandableStructuredInExpression, unknownMagic ];
@@ -121,7 +129,7 @@ function _validate(csn, that,
121
129
  iterateOptions = {}) {
122
130
  const { cleanup } = enrich(csn);
123
131
 
124
- applyTransformations(csn, mergeCsnValidators(csnValidators, that), [], true, { drillRef: true });
132
+ applyTransformations(csn, mergeCsnValidators(csnValidators, that), [], { drillRef: true });
125
133
 
126
134
  forEachDefinition(csn, (artifact, artifactName, prop, path) => {
127
135
  artifactValidators.forEach((artifactValidator) => {
@@ -196,12 +204,9 @@ function forHana(csn, that) {
196
204
  ),
197
205
  forHanaQueryValidators.concat(commonQueryValidators),
198
206
  {
199
- skipArtifact: artifact => artifact.abstract || hasAnnotationValue(artifact, '@cds.persistence.skip'),
200
- skip: [
201
- 'action',
202
- 'function',
203
- 'event',
204
- ],
207
+ skipArtifact: artifact => artifact.abstract ||
208
+ hasAnnotationValue(artifact, '@cds.persistence.skip') ||
209
+ [ 'action', 'function', 'event' ].includes(artifact.kind),
205
210
  });
206
211
  }
207
212
 
@@ -298,6 +298,7 @@ function assertConsistency( model, stage ) {
298
298
  },
299
299
  expand: { kind: [ 'element' ], inherits: 'columns' },
300
300
  inline: { kind: [ 'element' ], inherits: 'columns' },
301
+ $noOrigin: { kind: [ 'element' ], test: TODO },
301
302
  excludingDict: {
302
303
  kind: 'element',
303
304
  test: isDictionary( definition ), // definition since redef
@@ -325,8 +326,8 @@ function assertConsistency( model, stage ) {
325
326
  kind: true,
326
327
  requires: [ 'location' ],
327
328
  optional: [
328
- 'path', 'elements', '_outer',
329
- 'scope', '_artifact', '$inferred', '$expand',
329
+ 'path', 'elements', '_outer', '_parent', '_main', '_block', 'kind',
330
+ 'scope', '_artifact', '$inferred', '$expand', '$tableAliases', '_$next',
330
331
  '_effectiveType', // by propagation
331
332
  ],
332
333
  },
@@ -424,9 +425,12 @@ function assertConsistency( model, stage ) {
424
425
  val: {
425
426
  test: isVal, // the following for array/struct value
426
427
  requires: [ 'location' ],
427
- optional: [ 'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicate' ],
428
+ optional: [
429
+ 'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicate', 'upTo',
430
+ ],
428
431
  // TODO: restrict path to #simplePath
429
432
  },
433
+ upTo: { test: TODO },
430
434
  struct: { inherits: 'val', test: isDictionary( definition ) }, // def because double @
431
435
  args: {
432
436
  inherits: 'value',
@@ -638,7 +642,8 @@ function assertConsistency( model, stage ) {
638
642
  * and `localized` namespaces.
639
643
  */
640
644
  function builtin( node, parent, prop, spec, name ) {
641
- if (![ 'string', 'boolean' ].includes(typeof node))
645
+ const type = typeof node;
646
+ if (type !== 'string' && type !== 'boolean')
642
647
  throw new Error(`Property '${ prop }' must be a boolean or string but was '${ typeof node }'${ at( [ node, parent ], prop, name ) }` );
643
648
 
644
649
  if (parent.kind !== 'namespace')
@@ -792,8 +797,7 @@ function assertConsistency( model, stage ) {
792
797
  }
793
798
 
794
799
  function at( nodes, prop, name ) {
795
- // eslint-disable-next-line no-nested-ternary
796
- const n = name ? (typeof name === 'number' ? ` for index ${ name }` : ` for "${ name }"`) : '';
800
+ const n = name && (typeof name === 'number' ? ` for index ${ name }` : ` for "${ name }"`) || '';
797
801
  const loc = nodes.find( o => o && typeof o === 'object' && (o.location || o.start) );
798
802
  const f = (prop) ? `${ n } in property '${ prop }'` : n;
799
803
  const l = locationString( loc && loc.location || loc || model.location );
@@ -1,6 +1,5 @@
1
1
  // Base Definitions for the Core Compiler
2
2
 
3
-
4
3
  'use strict';
5
4
 
6
5
  const dictKinds = {
@@ -1,9 +1,14 @@
1
1
  // The builtin artifacts of CDS
2
2
 
3
+ // TODO: split this file
4
+ // - in base/: common definitions
5
+ // - in compiler/: XSN-specific
6
+ // - in ?: CSN-specific
7
+
3
8
  'use strict';
4
9
 
5
10
  const { builtinLocation } = require('../base/location');
6
- const { setProp } = require('./utils');
11
+ const { setLink: setProp } = require('./utils');
7
12
 
8
13
  const core = {
9
14
  String: { parameters: [ 'length' ], category: 'string' },
@@ -56,7 +61,6 @@ const coreHana = {
56
61
  * (do not add more - make it part of the SQL renderer to remove parentheses for
57
62
  * other funny SQL functions like CURRENT_UTCTIMESTAMP).
58
63
  */
59
-
60
64
  const functionsWithoutParens = [
61
65
  'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP',
62
66
  'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER',
@@ -176,7 +180,8 @@ function isRelationTypeName(typeName) {
176
180
  function isInReservedNamespace(absolute) {
177
181
  return absolute.startsWith( 'cds.') &&
178
182
  !absolute.match(/^cds\.foundation(\.|$)/) &&
179
- !absolute.match(/^cds\.outbox(\.|$)/); // Requested by Node runtime
183
+ !absolute.match(/^cds\.outbox(\.|$)/) && // Requested by Node runtime
184
+ !absolute.match(/^cds\.xt(\.|$)/); // Requested by Mtx
180
185
  }
181
186
 
182
187
  /**
@@ -214,9 +219,6 @@ function initBuiltins( model ) {
214
219
  model.$builtins.hana = hana;
215
220
  cds._subArtifacts.hana = hana;
216
221
  env( coreHana, 'cds.hana.', hana );
217
- // namespace:"localized" stores localized convenience views ---
218
- const localized = createNamespace( 'localized', true );
219
- model.definitions.localized = localized;
220
222
  model.$internal = { $frontend: '$internal' };
221
223
  return;
222
224
 
@@ -87,6 +87,17 @@ function check( model ) { // = XSN
87
87
  'Keyword “localized” may only be used in combination with string types');
88
88
  }
89
89
  }
90
+ // "key" keyword at localized element in SELECT list.
91
+ // TODO: This check should be moved to localized.js
92
+ if (elem.key && elem.key.val && elem._main && elem._main.query) {
93
+ // original element is localized but not key, as that would have
94
+ // already resulted in a warning
95
+ if (elem._origin && elem._origin.localized && elem._origin.localized.val &&
96
+ ( !elem._origin.key || !elem._origin.key.val)) {
97
+ warning('localized-key', [ elem.key.location, elem ], { keyword: 'localized' },
98
+ 'Keyword $(KEYWORD) is ignored for primary keys');
99
+ }
100
+ }
90
101
  }
91
102
 
92
103
  function checkQuery( query ) {
@@ -334,11 +345,17 @@ function check( model ) { // = XSN
334
345
  // Max cardinalities must be a positive number or '*'
335
346
  for (const prop of [ 'sourceMax', 'targetMax' ]) {
336
347
  if (elem.cardinality[prop]) {
337
- if (!(elem.cardinality[prop].literal === 'number' && elem.cardinality[prop].val > 0 ||
338
- elem.cardinality[prop].literal === 'string' && elem.cardinality[prop].val === '*')) {
339
- error(null, [ elem.cardinality[prop].location, elem ],
340
- { code: elem.cardinality[prop].val },
341
- 'Illegal value $(CODE) for max cardinality (must be a positive number or "*")');
348
+ const { literal, val, location } = elem.cardinality[prop];
349
+ if (!(literal === 'number' && val > 0 ||
350
+ literal === 'string' && val === '*')) {
351
+ error('invalid-cardinality', [ location, elem ], { '#': prop, code: val }, {
352
+ // eslint-disable-next-line max-len
353
+ std: 'Value $(CODE) is invalid for maximum cardinality, expecting a positive number or ‘*’',
354
+ // eslint-disable-next-line max-len
355
+ sourceMax: 'Value $(CODE) is invalid for maximum source cardinality, expecting a positive number or ‘*’',
356
+ // eslint-disable-next-line max-len
357
+ targetMax: 'Value $(CODE) is invalid for maximum target cardinality, expecting a positive number or ‘*’',
358
+ });
342
359
  }
343
360
  }
344
361
  }
@@ -348,10 +365,16 @@ function check( model ) { // = XSN
348
365
  // from-csn.json (expected non-negative number)
349
366
  for (const prop of [ 'sourceMin', 'targetMin' ]) {
350
367
  if (elem.cardinality[prop]) {
351
- if (!(elem.cardinality[prop].literal === 'number' && elem.cardinality[prop].val >= 0)) {
352
- error(null, [ elem.cardinality[prop].location, elem ],
353
- { code: elem.cardinality[prop].val },
354
- 'Illegal value $(CODE) for min cardinality (must be a non-negative number)');
368
+ const { literal, val, location } = elem.cardinality[prop];
369
+ if (!(literal === 'number' && val >= 0)) {
370
+ error('invalid-cardinality', [ location, elem ], { '#': prop, code: val }, {
371
+ // eslint-disable-next-line max-len
372
+ std: 'Value $(CODE) is invalid for minimum cardinality, expecting a non-negative number',
373
+ // eslint-disable-next-line max-len
374
+ targetMin: 'Value $(CODE) is invalid for minimum target cardinality, expecting a non-negative number',
375
+ // eslint-disable-next-line max-len
376
+ sourceMin: 'Value $(CODE) is invalid for minimum source cardinality, expecting a non-negative number',
377
+ });
355
378
  }
356
379
  }
357
380
  }
@@ -436,6 +459,10 @@ function check( model ) { // = XSN
436
459
  * Check whether the argument count of the given path expression matches its artifact.
437
460
  * If there is a mismatch, an error is issued.
438
461
  *
462
+ * TODO: remove this function - it also checks for parameter in type
463
+ * references. We could have a warning, see also configurable errors
464
+ * 'args-no-params', 'args-undefined-param'.
465
+ *
439
466
  * @param {object} pathStep The expression to check
440
467
  */
441
468
  function checkPathForMissingArguments(pathStep) {
@@ -471,8 +498,15 @@ function check( model ) { // = XSN
471
498
 
472
499
  const missingArgs = [];
473
500
  for (const fAName in formalArgs) {
474
- if (!actualArgs[fAName] && !formalArgs[fAName].default)
475
- missingArgs.push(fAName);
501
+ if (!actualArgs[fAName]) {
502
+ // Note: _effectiveType points to cds.String for `type T : DefaultString`.
503
+ // And `default` may appear at any `type` in the hierarchy.
504
+ let fArg = formalArgs[fAName];
505
+ while (fArg.type && !fArg.default)
506
+ fArg = fArg.type._artifact;
507
+ if (!fArg.default)
508
+ missingArgs.push(fAName);
509
+ }
476
510
  }
477
511
 
478
512
  if (missingArgs.length) {
@@ -612,7 +646,7 @@ function check( model ) { // = XSN
612
646
  return checkTreeLikeExpression(xpr, allowAssocTail);
613
647
  }
614
648
  /**
615
- * Check wether the supplied argument is a virtual element
649
+ * Check whether the supplied argument is a virtual element
616
650
  *
617
651
  * TO CLARIFY: do we want the "no virtual element" check for virtual elements/columns, too?
618
652
  *
@@ -16,7 +16,7 @@
16
16
 
17
17
  'use strict';
18
18
 
19
- const { setProp } = require('../base/model');
19
+ const { setLink: setProp } = require('./utils'); // check enum/non-enum
20
20
 
21
21
  // Detect cyclic dependencies between all nodes reachable from `definitions`.
22
22
  // If such a dependency is found, call `reportCycle` with arguments `dep.art`