@sap/cds-compiler 2.11.2 → 2.13.6

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 (140) hide show
  1. package/CHANGELOG.md +175 -2
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +23 -17
  5. package/bin/cdsse.js +2 -2
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +25 -6
  9. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  10. package/doc/NameResolution.md +21 -16
  11. package/lib/api/main.js +32 -79
  12. package/lib/api/options.js +3 -2
  13. package/lib/api/validate.js +2 -1
  14. package/lib/backends.js +16 -26
  15. package/lib/base/dictionaries.js +0 -8
  16. package/lib/base/error.js +26 -0
  17. package/lib/base/keywords.js +10 -19
  18. package/lib/base/location.js +9 -4
  19. package/lib/base/message-registry.js +75 -9
  20. package/lib/base/messages.js +31 -35
  21. package/lib/base/model.js +2 -62
  22. package/lib/base/optionProcessorHelper.js +246 -183
  23. package/lib/checks/.eslintrc.json +2 -0
  24. package/lib/checks/actionsFunctions.js +2 -1
  25. package/lib/checks/annotationsOData.js +1 -1
  26. package/lib/checks/cdsPersistence.js +2 -1
  27. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  28. package/lib/checks/enricher.js +17 -1
  29. package/lib/checks/foreignKeys.js +4 -4
  30. package/lib/checks/invalidTarget.js +3 -1
  31. package/lib/checks/managedInType.js +4 -4
  32. package/lib/checks/managedWithoutKeys.js +3 -1
  33. package/lib/checks/queryNoDbArtifacts.js +1 -3
  34. package/lib/checks/selectItems.js +4 -4
  35. package/lib/checks/sql-snippets.js +94 -0
  36. package/lib/checks/types.js +1 -1
  37. package/lib/checks/unknownMagic.js +1 -1
  38. package/lib/checks/validator.js +12 -7
  39. package/lib/compiler/assert-consistency.js +12 -8
  40. package/lib/compiler/base.js +0 -1
  41. package/lib/compiler/builtins.js +42 -21
  42. package/lib/compiler/checks.js +46 -12
  43. package/lib/compiler/cycle-detector.js +1 -1
  44. package/lib/compiler/define.js +1103 -0
  45. package/lib/compiler/extend.js +983 -0
  46. package/lib/compiler/finalize-parse-cdl.js +231 -0
  47. package/lib/compiler/index.js +46 -39
  48. package/lib/compiler/kick-start.js +190 -0
  49. package/lib/compiler/moduleLayers.js +4 -4
  50. package/lib/compiler/populate.js +1226 -0
  51. package/lib/compiler/propagator.js +113 -47
  52. package/lib/compiler/resolve.js +1433 -0
  53. package/lib/compiler/shared.js +100 -65
  54. package/lib/compiler/tweak-assocs.js +529 -0
  55. package/lib/compiler/utils.js +215 -33
  56. package/lib/edm/.eslintrc.json +5 -0
  57. package/lib/edm/annotations/genericTranslation.js +38 -25
  58. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  59. package/lib/edm/csn2edm.js +10 -9
  60. package/lib/edm/edm.js +19 -20
  61. package/lib/edm/edmPreprocessor.js +166 -95
  62. package/lib/edm/edmUtils.js +127 -34
  63. package/lib/gen/Dictionary.json +92 -43
  64. package/lib/gen/language.checksum +1 -1
  65. package/lib/gen/language.interp +11 -1
  66. package/lib/gen/language.tokens +86 -82
  67. package/lib/gen/languageLexer.interp +18 -1
  68. package/lib/gen/languageLexer.js +925 -847
  69. package/lib/gen/languageLexer.tokens +78 -74
  70. package/lib/gen/languageParser.js +5434 -4298
  71. package/lib/json/from-csn.js +59 -17
  72. package/lib/json/to-csn.js +189 -71
  73. package/lib/language/antlrParser.js +3 -3
  74. package/lib/language/docCommentParser.js +3 -3
  75. package/lib/language/errorStrategy.js +26 -8
  76. package/lib/language/genericAntlrParser.js +144 -53
  77. package/lib/language/language.g4 +424 -200
  78. package/lib/language/multiLineStringParser.js +536 -0
  79. package/lib/main.d.ts +550 -61
  80. package/lib/main.js +38 -11
  81. package/lib/model/api.js +3 -1
  82. package/lib/model/csnRefs.js +322 -198
  83. package/lib/model/csnUtils.js +226 -370
  84. package/lib/model/enrichCsn.js +124 -69
  85. package/lib/model/revealInternalProperties.js +29 -7
  86. package/lib/model/sortViews.js +10 -2
  87. package/lib/modelCompare/compare.js +17 -12
  88. package/lib/optionProcessor.js +8 -3
  89. package/lib/render/.eslintrc.json +1 -2
  90. package/lib/render/DuplicateChecker.js +1 -1
  91. package/lib/render/manageConstraints.js +36 -33
  92. package/lib/render/toCdl.js +174 -275
  93. package/lib/render/toHdbcds.js +203 -122
  94. package/lib/render/toRename.js +7 -10
  95. package/lib/render/toSql.js +161 -82
  96. package/lib/render/utils/common.js +22 -8
  97. package/lib/render/utils/sql.js +10 -7
  98. package/lib/render/utils/stringEscapes.js +111 -0
  99. package/lib/sql-identifier.js +1 -1
  100. package/lib/transform/.eslintrc.json +5 -0
  101. package/lib/transform/braceExpression.js +4 -2
  102. package/lib/transform/db/.eslintrc.json +2 -0
  103. package/lib/transform/db/applyTransformations.js +212 -0
  104. package/lib/transform/db/assertUnique.js +1 -1
  105. package/lib/transform/db/associations.js +187 -0
  106. package/lib/transform/db/cdsPersistence.js +150 -0
  107. package/lib/transform/db/constraints.js +61 -56
  108. package/lib/transform/db/expansion.js +50 -29
  109. package/lib/transform/db/flattening.js +556 -106
  110. package/lib/transform/db/groupByOrderBy.js +3 -1
  111. package/lib/transform/db/temporal.js +236 -0
  112. package/lib/transform/db/transformExists.js +103 -28
  113. package/lib/transform/db/views.js +92 -44
  114. package/lib/transform/draft/.eslintrc.json +38 -0
  115. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  116. package/lib/transform/draft/odata.js +227 -0
  117. package/lib/transform/forHanaNew.js +98 -783
  118. package/lib/transform/forOdataNew.js +22 -175
  119. package/lib/transform/localized.js +36 -32
  120. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  121. package/lib/transform/odata/referenceFlattener.js +95 -89
  122. package/lib/transform/odata/structureFlattener.js +1 -1
  123. package/lib/transform/odata/toFinalBaseType.js +86 -12
  124. package/lib/transform/odata/typesExposure.js +5 -5
  125. package/lib/transform/odata/utils.js +2 -2
  126. package/lib/transform/transformUtilsNew.js +47 -33
  127. package/lib/transform/translateAssocsToJoins.js +13 -30
  128. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  129. package/lib/transform/universalCsn/coreComputed.js +170 -0
  130. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  131. package/lib/transform/universalCsn/utils.js +63 -0
  132. package/lib/utils/file.js +8 -3
  133. package/lib/utils/objectUtils.js +30 -0
  134. package/lib/utils/timetrace.js +8 -2
  135. package/package.json +1 -1
  136. package/share/messages/README.md +26 -0
  137. package/lib/compiler/definer.js +0 -2349
  138. package/lib/compiler/resolver.js +0 -2922
  139. package/lib/transform/db/helpers.js +0 -58
  140. 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
  }
@@ -12,9 +12,9 @@ const { isPersistedOnDatabase } = require('../model/csnUtils.js');
12
12
  * @param {CSN.Path} path Path to the artifact
13
13
  */
14
14
  function validateEmptyOrOnlyVirtual(artifact, artifactName, prop, path) {
15
- if (artifact.kind === 'entity' && !artifact.query && isPersistedOnDatabase(artifact)) {
15
+ if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact)) {
16
16
  if (!artifact.elements || !hasRealElements(artifact.elements))
17
- this.error(null, path, "Artifacts containing only virtual or empty elements can't be deployed");
17
+ this.error('def-missing-element', path, { '#': artifact.query ? 'view' : 'std' });
18
18
  }
19
19
  }
20
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
 
@@ -32,7 +32,7 @@ function unknownMagicVariable(parent, name, ref) {
32
32
  const magicVariable = magicVariables[head];
33
33
  if (magicVariable && magicVariable.indexOf(tail) === -1 &&
34
34
  getVariableReplacement(ref, this.options) === null)
35
- this.error(null, parent.$location, { id: tail, elemref: parent }, 'No configuration for magic variable was provided - path $(ELEMREF), step $(ID)');
35
+ this.error('ref-missing-replacement', parent.$location, { elemref: parent }, 'Missing replacement for variable $(ELEMREF)');
36
36
  }
37
37
  }
38
38
 
@@ -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
 
@@ -249,7 +249,7 @@ function assertConsistency( model, stage ) {
249
249
  'name', '$parens', 'quantifier', 'mixin', 'excludingDict', 'columns', 'elements', '_deps',
250
250
  'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit',
251
251
  '_projections', '_block', '_parent', '_main', '_effectiveType', '$expand',
252
- '$tableAliases', 'kind', '_$next', '_combined', '$inlines',
252
+ '$tableAliases', 'kind', '_$next', '_combined', '$inlines', '_status',
253
253
  ],
254
254
  },
255
255
  none: { optional: () => true }, // parse error
@@ -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',
@@ -542,7 +546,7 @@ function assertConsistency( model, stage ) {
542
546
  // query specific
543
547
  'where', 'columns', 'mixin', 'quantifier', 'offset',
544
548
  'orderBy', '$orderBy', 'groupBy', 'excludingDict', 'having',
545
- 'limit',
549
+ 'limit', '_status',
546
550
  ],
547
551
  },
548
552
  _leadingQuery: { kind: true, test: TODO },
@@ -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,10 +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
- const { forEachInDict } = require('../base/dictionaries');
6
10
  const { builtinLocation } = require('../base/location');
7
- const { setProp } = require('./utils');
11
+ const { setLink: setProp } = require('./utils');
8
12
 
9
13
  const core = {
10
14
  String: { parameters: [ 'length' ], category: 'string' },
@@ -57,7 +61,6 @@ const coreHana = {
57
61
  * (do not add more - make it part of the SQL renderer to remove parentheses for
58
62
  * other funny SQL functions like CURRENT_UTCTIMESTAMP).
59
63
  */
60
-
61
64
  const functionsWithoutParens = [
62
65
  'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP',
63
66
  'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER',
@@ -82,18 +85,19 @@ const specialFunctions = {
82
85
  */
83
86
  const magicVariables = {
84
87
  $user: {
88
+ // id and locale are always available
85
89
  elements: { id: {}, locale: {} },
86
90
  // Allow $user.<any>
87
91
  $uncheckedElements: true,
88
92
  // Allow shortcut in CDL: `$user` becomes `$user.id` in CSN.
89
93
  $autoElement: 'id',
90
- }, // CDS-specific, not part of SQL
91
- $at: {
94
+ },
95
+ $at: { // CDS-specific, not part of SQL
92
96
  elements: {
93
97
  from: {}, to: {},
94
98
  },
95
99
  },
96
- $now: {}, // Dito
100
+ $now: {}, // Dito
97
101
  $session: {
98
102
  // In ABAP CDS session variables are accessed in a generic way via
99
103
  // the pseudo variable $session.
@@ -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
  /**
@@ -199,6 +204,7 @@ function isBuiltinType(type) {
199
204
  * @param {XSN.Model} model XSN model without CDS builtins
200
205
  */
201
206
  function initBuiltins( model ) {
207
+ const { options } = model;
202
208
  setMagicVariables( magicVariables );
203
209
  // namespace:"cds" stores the builtins ---
204
210
  const cds = createNamespace( 'cds', 'reserved' );
@@ -213,9 +219,6 @@ function initBuiltins( model ) {
213
219
  model.$builtins.hana = hana;
214
220
  cds._subArtifacts.hana = hana;
215
221
  env( coreHana, 'cds.hana.', hana );
216
- // namespace:"localized" stores localized convenience views ---
217
- const localized = createNamespace( 'localized', true );
218
- model.definitions.localized = localized;
219
222
  model.$internal = { $frontend: '$internal' };
220
223
  return;
221
224
 
@@ -266,27 +269,45 @@ function initBuiltins( model ) {
266
269
  for (const name in builtins) {
267
270
  const magic = builtins[name];
268
271
  // TODO: rename to $builtinFunction
269
- const art = { kind: 'builtin', name: { id: name, element: name } };
272
+ const art = { kind: 'builtin', name: { element: name, id: name } };
270
273
  artifacts[name] = art;
271
- if (magic.elements)
272
- art.elements = forEachInDict( magic.elements, (e, n) => magicElement( e, n, art ));
274
+
273
275
  if (magic.$autoElement)
274
276
  art.$autoElement = magic.$autoElement;
275
277
  if (magic.$uncheckedElements)
276
278
  art.$uncheckedElements = magic.$uncheckedElements;
279
+
280
+ createMagicElements( art, magic.elements );
281
+ if (options.variableReplacements)
282
+ createMagicElements( art, options.variableReplacements[name] );
277
283
  // setProp( art, '_effectiveType', art );
278
284
  }
279
285
  model.$magicVariables = { kind: '$magicVariables', artifacts };
280
286
  }
281
287
 
282
- function magicElement( spec, name, parent ) {
283
- const magic = {
284
- kind: 'builtin',
285
- name: { id: name, element: `${ parent.name.element }.${ name }` },
286
- };
287
- setProp( magic, '_parent', parent );
288
- // setProp( magic, '_effectiveType', magic );
289
- return magic;
288
+ function createMagicElements( art, elements ) {
289
+ if (!elements)
290
+ return;
291
+
292
+ const names = Object.keys(elements);
293
+ if (names.length > 0 && !art.elements)
294
+ art.elements = Object.create(null);
295
+
296
+ for (const n of names) {
297
+ const magic = {
298
+ kind: 'builtin',
299
+ name: { id: n, element: `${ art.name.element }.${ n }` },
300
+ };
301
+ // Propagate this property so that it is available for sub-elements.
302
+ if (art.$uncheckedElements)
303
+ magic.$uncheckedElements = art.$uncheckedElements;
304
+ setProp( magic, '_parent', art );
305
+ // setProp( magic, '_effectiveType', magic );
306
+ if (elements[n] && typeof elements[n] === 'object')
307
+ createMagicElements(magic, elements[n]);
308
+
309
+ art.elements[n] = magic;
310
+ }
290
311
  }
291
312
  }
292
313
 
@@ -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
  *