@sap/cds-compiler 2.13.8 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/CHANGELOG.md +155 -1594
  2. package/bin/cdsc.js +144 -66
  3. package/doc/CHANGELOG_ARCHIVE.md +1592 -0
  4. package/doc/CHANGELOG_BETA.md +3 -4
  5. package/doc/CHANGELOG_DEPRECATED.md +35 -1
  6. package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
  7. package/doc/Versioning.md +20 -1
  8. package/lib/api/.eslintrc.json +2 -2
  9. package/lib/api/main.js +237 -122
  10. package/lib/api/options.js +17 -88
  11. package/lib/api/validate.js +12 -16
  12. package/lib/base/keywords.js +216 -109
  13. package/lib/base/message-registry.js +152 -37
  14. package/lib/base/messages.js +145 -83
  15. package/lib/base/model.js +44 -2
  16. package/lib/base/optionProcessorHelper.js +19 -0
  17. package/lib/checks/actionsFunctions.js +7 -5
  18. package/lib/checks/annotationsOData.js +11 -32
  19. package/lib/checks/arrayOfs.js +1 -34
  20. package/lib/checks/cdsPersistence.js +1 -0
  21. package/lib/checks/elements.js +6 -6
  22. package/lib/checks/invalidTarget.js +1 -1
  23. package/lib/checks/nonexpandableStructured.js +1 -1
  24. package/lib/checks/queryNoDbArtifacts.js +2 -1
  25. package/lib/checks/selectItems.js +5 -1
  26. package/lib/checks/types.js +4 -2
  27. package/lib/checks/utils.js +2 -2
  28. package/lib/checks/validator.js +4 -5
  29. package/lib/compiler/assert-consistency.js +16 -10
  30. package/lib/compiler/base.js +1 -0
  31. package/lib/compiler/builtins.js +98 -9
  32. package/lib/compiler/checks.js +22 -70
  33. package/lib/compiler/define.js +61 -13
  34. package/lib/compiler/extend.js +79 -14
  35. package/lib/compiler/finalize-parse-cdl.js +46 -29
  36. package/lib/compiler/index.js +100 -37
  37. package/lib/compiler/moduleLayers.js +7 -0
  38. package/lib/compiler/populate.js +19 -18
  39. package/lib/compiler/propagator.js +7 -4
  40. package/lib/compiler/resolve.js +297 -234
  41. package/lib/compiler/shared.js +107 -102
  42. package/lib/compiler/tweak-assocs.js +16 -11
  43. package/lib/compiler/utils.js +5 -0
  44. package/lib/edm/annotations/genericTranslation.js +93 -21
  45. package/lib/edm/csn2edm.js +230 -115
  46. package/lib/edm/edm.js +305 -226
  47. package/lib/edm/edmPreprocessor.js +509 -438
  48. package/lib/edm/edmUtils.js +31 -45
  49. package/lib/gen/Dictionary.json +98 -22
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +10 -30
  52. package/lib/gen/language.tokens +105 -114
  53. package/lib/gen/languageLexer.interp +1 -34
  54. package/lib/gen/languageLexer.js +889 -1007
  55. package/lib/gen/languageLexer.tokens +95 -106
  56. package/lib/gen/languageParser.js +20786 -22199
  57. package/lib/json/csnVersion.js +10 -11
  58. package/lib/json/from-csn.js +59 -51
  59. package/lib/json/to-csn.js +10 -10
  60. package/lib/language/antlrParser.js +2 -2
  61. package/lib/language/docCommentParser.js +62 -39
  62. package/lib/language/errorStrategy.js +52 -40
  63. package/lib/language/genericAntlrParser.js +348 -229
  64. package/lib/language/language.g4 +629 -653
  65. package/lib/language/multiLineStringParser.js +14 -42
  66. package/lib/language/textUtils.js +44 -0
  67. package/lib/main.d.ts +46 -43
  68. package/lib/main.js +108 -79
  69. package/lib/model/csnRefs.js +34 -7
  70. package/lib/model/csnUtils.js +337 -332
  71. package/lib/model/enrichCsn.js +1 -0
  72. package/lib/model/revealInternalProperties.js +30 -10
  73. package/lib/model/sortViews.js +32 -31
  74. package/lib/modelCompare/compare.js +6 -6
  75. package/lib/optionProcessor.js +73 -46
  76. package/lib/render/.eslintrc.json +1 -1
  77. package/lib/render/DuplicateChecker.js +4 -7
  78. package/lib/render/manageConstraints.js +70 -2
  79. package/lib/render/toCdl.js +1042 -882
  80. package/lib/render/toHdbcds.js +195 -245
  81. package/lib/render/toRename.js +44 -22
  82. package/lib/render/toSql.js +225 -241
  83. package/lib/render/utils/common.js +145 -15
  84. package/lib/render/utils/sql.js +20 -19
  85. package/lib/sql-identifier.js +6 -0
  86. package/lib/transform/db/.eslintrc.json +4 -3
  87. package/lib/transform/db/associations.js +2 -2
  88. package/lib/transform/db/cdsPersistence.js +5 -15
  89. package/lib/transform/db/constraints.js +4 -2
  90. package/lib/transform/db/expansion.js +22 -16
  91. package/lib/transform/db/flattening.js +109 -80
  92. package/lib/transform/db/transformExists.js +7 -7
  93. package/lib/transform/db/views.js +9 -6
  94. package/lib/transform/draft/.eslintrc.json +2 -2
  95. package/lib/transform/draft/db.js +6 -6
  96. package/lib/transform/draft/odata.js +6 -7
  97. package/lib/transform/forHanaNew.js +62 -48
  98. package/lib/transform/forOdataNew.js +49 -50
  99. package/lib/transform/localized.js +31 -20
  100. package/lib/transform/odata/toFinalBaseType.js +16 -14
  101. package/lib/transform/odata/typesExposure.js +146 -198
  102. package/lib/transform/odata/utils.js +1 -38
  103. package/lib/transform/transformUtilsNew.js +67 -84
  104. package/lib/transform/translateAssocsToJoins.js +7 -3
  105. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  106. package/lib/transform/universalCsn/coreComputed.js +16 -9
  107. package/lib/transform/universalCsn/universalCsnEnricher.js +60 -10
  108. package/lib/utils/file.js +3 -3
  109. package/lib/utils/moduleResolve.js +13 -6
  110. package/lib/utils/timetrace.js +20 -21
  111. package/package.json +35 -4
  112. package/share/messages/message-explanations.json +2 -1
  113. package/share/messages/syntax-expected-integer.md +37 -0
  114. package/doc/ApiMigration.md +0 -237
  115. package/doc/CommandLineMigration.md +0 -58
  116. package/doc/ErrorMessages.md +0 -175
  117. package/doc/FioriAnnotations.md +0 -94
  118. package/doc/ODataTransformation.md +0 -273
  119. package/lib/backends.js +0 -529
  120. package/lib/fix_antlr4-8_warning.js +0 -56
  121. package/lib/transform/odata/attachPath.js +0 -96
  122. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  123. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  124. package/lib/transform/odata/referenceFlattener.js +0 -296
  125. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  126. package/lib/transform/odata/structuralPath.js +0 -72
  127. package/lib/transform/odata/structureFlattener.js +0 -171
@@ -13,42 +13,22 @@
13
13
  * @param {CSN.Element} member Member to be checked
14
14
  */
15
15
  function checkCoreMediaTypeAllowence(member) {
16
- const allowedCoreMediaTypes = [
17
- 'cds.String',
18
- 'cds.LargeString',
19
- 'cds.hana.VARCHAR',
20
- 'cds.hana.CHAR',
21
- 'cds.Binary',
22
- 'cds.LargeBinary',
23
- 'cds.hana.CLOB',
24
- 'cds.hana.BINARY',
25
- ];
26
- if (member['@Core.MediaType'] && member.type && !allowedCoreMediaTypes.includes(member.type)) {
16
+ const allowedCoreMediaTypes = {
17
+ 'cds.String': 1,
18
+ 'cds.LargeString': 1,
19
+ 'cds.hana.VARCHAR': 1,
20
+ 'cds.hana.CHAR': 1,
21
+ 'cds.Binary': 1,
22
+ 'cds.LargeBinary': 1,
23
+ 'cds.hana.CLOB': 1,
24
+ 'cds.hana.BINARY': 1,
25
+ };
26
+ if (member['@Core.MediaType'] && member.type && !(this.csnUtils.getFinalBaseTypeWithProps(member.type)?.type in allowedCoreMediaTypes)) {
27
27
  this.warning(null, member.$path, { names: [ 'Edm.String', 'Edm.Binary' ] },
28
28
  'Element annotated with “@Core.MediaType” should be of a type mapped to $(NAMES)');
29
29
  }
30
30
  }
31
31
 
32
- /**
33
- * Make sure only one element in a definition is annotated with `@Core.MediaType`
34
- * This is only OData V2 relevant.
35
- *
36
- * @param {CSN.Artifact} artifact Definition to be checked
37
- * @param {string} artifactName The name of the artifact
38
- */
39
- function checkForMultipleCoreMediaTypes(artifact, artifactName) {
40
- if (!this.csnUtils.getServiceName(artifactName))
41
- return;
42
- if (this.options.toOdata && this.options.toOdata.version === 'v2' && artifact.elements) {
43
- const mediaTypeElementsNames = Object.keys(artifact.elements)
44
- .filter(elementName => artifact.elements[elementName]['@Core.MediaType']);
45
- if (mediaTypeElementsNames.length > 1) {
46
- this.error(null, artifact.$path, { names: mediaTypeElementsNames },
47
- `Multiple elements $(NAMES) annotated with “@Core.MediaType”, OData V2 allows only one`);
48
- }
49
- }
50
- }
51
-
52
32
  /**
53
33
  * Check if `@Aggregation.default` is assigned together with `@Analytics.Measure`
54
34
  *
@@ -88,7 +68,6 @@ function checkReadOnlyAndInsertOnly(artifact, artifactName) {
88
68
 
89
69
  module.exports = {
90
70
  checkCoreMediaTypeAllowence,
91
- checkForMultipleCoreMediaTypes,
92
71
  checkAnalytics,
93
72
  checkAtSapAnnotations,
94
73
  checkReadOnlyAndInsertOnly,
@@ -1,7 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const { forEachMemberRecursively } = require('../model/csnUtils');
4
-
5
3
  // Only to be used with validator.js - a correct `this` value needs to be provided!
6
4
 
7
5
  /**
@@ -43,35 +41,4 @@ function validateAssociationsInItems(member) {
43
41
  }
44
42
  }
45
43
 
46
- /**
47
- * Check that there are no .items containing .items.
48
- *
49
- * @param {CSN.Artifact} art Artifact
50
- * @param {string} artName Name of the artifact
51
- */
52
- function checkChainedArray(art, artName) {
53
- if (!this.csnUtils.getServiceName(artName))
54
- return;
55
- checkIfItemsOfItems.bind(this)(art);
56
- forEachMemberRecursively(art, checkIfItemsOfItems.bind(this));
57
-
58
- /**
59
- *
60
- * @param {object} construct the construct to be checked
61
- */
62
- function checkIfItemsOfItems(construct) {
63
- const constructType = this.csnUtils.effectiveType(construct);
64
- if (constructType.items) {
65
- if (constructType.items.items) {
66
- this.error('chained-array-of', construct.$path, '"Array of"/"many" must not be chained with another "array of"/"many" inside a service');
67
- return;
68
- }
69
-
70
- const itemsType = this.csnUtils.effectiveType(constructType.items);
71
- if (itemsType.items)
72
- this.error('chained-array-of', construct.$path, '"Array of"/"many" must not be chained with another "array of"/"many" inside a service');
73
- }
74
- }
75
- }
76
-
77
- module.exports = { validateAssociationsInItems, checkChainedArray };
44
+ module.exports = { validateAssociationsInItems };
@@ -14,6 +14,7 @@ function validateCdsPersistenceAnnotation(artifact, artifactName, prop, path) {
14
14
  if (artifact.kind === 'entity') {
15
15
  // filter for 'table', 'udf', 'calcview' === true
16
16
  const persistenceAnnos = [ '@cds.persistence.table', '@cds.persistence.udf', '@cds.persistence.calcview' ];
17
+ // TODO: Why not filter over persistenceAnnos, is shorter!
17
18
  const TableUdfCv = Object.keys(artifact).filter(p => persistenceAnnos.includes(p) && artifact[p]);
18
19
  if (TableUdfCv.length > 1)
19
20
  this.error(null, path, `Annotations ${ TableUdfCv.join(', ') } can't be used in combination`);
@@ -36,10 +36,10 @@ function checkPrimaryKey(art) {
36
36
  */
37
37
  function checkIfPrimaryKeyIsOfGeoType(member, elemFqName, parentIsKey, parentPath) {
38
38
  if (member.key || parentIsKey) {
39
- const finalBaseType = this.csnUtils.getFinalBaseType(member.type);
40
- if (typeof finalBaseType === 'string' && isGeoTypeName(finalBaseType)) {
39
+ const finalBaseType = this.csnUtils.getFinalBaseTypeWithProps(member.type);
40
+ if (isGeoTypeName(finalBaseType?.type)) {
41
41
  this.error(null, parentPath || member.$path,
42
- { type: finalBaseType, name: elemFqName },
42
+ { type: finalBaseType.type, name: elemFqName },
43
43
  'Type $(TYPE) can\'t be used as primary key in element $(NAME)');
44
44
  }
45
45
  else if (finalBaseType && this.csnUtils.isStructured(finalBaseType)) {
@@ -63,7 +63,7 @@ function checkPrimaryKey(art) {
63
63
  */
64
64
  function checkIfPrimaryKeyIsArray(member, elemFqName, parentIsKey, parentPath) {
65
65
  if (member.key || parentIsKey) {
66
- const finalBaseType = this.csnUtils.getFinalBaseType(member.type);
66
+ const finalBaseType = this.csnUtils.getFinalBaseTypeWithProps(member.type);
67
67
  if (member.items || (finalBaseType && finalBaseType.items)) {
68
68
  this.error(null, parentPath || member.$path, { name: elemFqName },
69
69
  'Array-like type in element $(NAME) can\'t be used as primary key');
@@ -96,10 +96,10 @@ function checkVirtualElement(member) {
96
96
 
97
97
  /**
98
98
  * Checks whether managed associations
99
- * with cardinality 'to many' have an on-condition
100
- * and if managed associations have foreign keys.
99
+ * with cardinality 'to many' have an on-condition.
101
100
  *
102
101
  * @param {CSN.Artifact} art The artifact
102
+ * @todo this is a member validator, is it not?
103
103
  */
104
104
  function checkManagedAssoc(art) {
105
105
  forEachMemberRecursively(art, (member) => {
@@ -26,7 +26,7 @@ function invalidTarget(member) {
26
26
  if (!target)
27
27
  throw new ModelError(`Expected target ${ mem.target }`);
28
28
  if (target.kind !== 'entity') {
29
- const isAssoc = this.csnUtils.getFinalBaseType(member.type) !== 'cds.Composition';
29
+ const isAssoc = this.csnUtils.getFinalBaseTypeWithProps(member.type)?.type !== 'cds.Composition';
30
30
  this.error(
31
31
  null,
32
32
  member.$path,
@@ -21,7 +21,7 @@ function nonexpandableStructuredInExpression(parent, name, expression) {
21
21
  if (_art) {
22
22
  _art = resolveArtifactType.call(this, _art);
23
23
  // Paths of an expression may end on a structured element only if both operands in the expression end on a structured element
24
- if (_art.elements && !validStructuredElement && $scope !== '$self') { // TODO: Use $self to navigate to struct
24
+ if (_art?.elements && !validStructuredElement && $scope !== '$self') { // TODO: Use $self to navigate to struct
25
25
  this.error(null, expression[i].$path, { elemref: { ref } },
26
26
  'Unexpected usage of structured type $(ELEMREF)');
27
27
  }
@@ -3,7 +3,8 @@
3
3
  const { hasAnnotationValue, isPersistedOnDatabase, isBuiltinType } = require('../model/csnUtils');
4
4
  /**
5
5
  * Make sure that all source artifacts and association targets reach the database
6
- * (otherwise the view can't be activated), but only if the source artifact is NOT activated against the database
6
+ * (otherwise the view can't be activated), but only if the source artifact is NOT activated against the database // <- what does this mean?
7
+ *
7
8
  * Check the given query for:
8
9
  * - Associations-traversal over skipped/abstract things
9
10
  * - Associations (indirectly) using managed associations without foreign keys
@@ -5,9 +5,13 @@ const { forEachGeneric } = require('../model/csnUtils');
5
5
  // Only to be used with validator.js - a correct this value needs to be provided!
6
6
 
7
7
  /**
8
- * Validate select items of a query.
8
+ * Validate select items of a query. If a column reference starts with $self or $projection, it must not contain association steps.
9
+ * Furthermore, for to.hdbcds, window functions are not allowed.
10
+ *
11
+ * For to.hdbcds-hdbcds, structures and managed associations are not allowed as they are not flattened - @see rejectManagedAssociationsAndStructuresForHdbcdsNames
9
12
  *
10
13
  * @param {CSN.Query} query query object
14
+ * @todo Why do we care about this with $self?
11
15
  */
12
16
  function validateSelectItems(query) {
13
17
  const { SELECT } = query;
@@ -129,8 +129,8 @@ function checkTypeOfHasProperType(artOrElement, name, model, error, path, derive
129
129
  if (!artOrElement.type)
130
130
  return;
131
131
 
132
- const { getFinalBaseType } = getUtils(model);
133
- const typeOfType = getFinalBaseType(artOrElement.type, path);
132
+ const { getFinalBaseTypeWithProps } = getUtils(model);
133
+ const typeOfType = getFinalBaseTypeWithProps(artOrElement.type);
134
134
 
135
135
  if (typeOfType === null) {
136
136
  if (artOrElement.type.ref) {
@@ -157,6 +157,7 @@ function checkTypeOfHasProperType(artOrElement, name, model, error, path, derive
157
157
  * @param {CSN.Path} path the path to the element or the artifact
158
158
  * @param {string} name of the element or the artifact which is dubious
159
159
  * @param {boolean} isElement indicates whether we are dealing with an element or an artifact
160
+ * @todo Rename, is an error not a warning
160
161
  */
161
162
  function warnAboutMissingType(error, path, name, isElement = false) {
162
163
  error('check-proper-type', path, { art: name, '#': isElement ? 'elm' : 'std' }, {
@@ -173,6 +174,7 @@ function warnAboutMissingType(error, path, name, isElement = false) {
173
174
  *
174
175
  * @param {CSN.Artifact} artifact the artifact to check
175
176
  * @returns {boolean} indicates whether the artifact has type information
177
+ * @todo What is the point of isBuiltinType here if we check for artifact.type at the end?
176
178
  */
177
179
  function hasArtifactTypeInformation(artifact) {
178
180
  // When is what property set?
@@ -31,7 +31,7 @@ function otherSideIsExpandableStructure(on, startIndex) {
31
31
  return false;
32
32
 
33
33
  /**
34
- * Artifact is structured or a managed association/compoisition
34
+ * Artifact is structured or a managed association/composition
35
35
  *
36
36
  * @param {CSN.Artifact} art Artifact
37
37
  * @returns {boolean} True if expandable
@@ -49,7 +49,7 @@ function otherSideIsExpandableStructure(on, startIndex) {
49
49
  */
50
50
  function resolveArtifactType(art) {
51
51
  if (art && art.type && !isBuiltinType(art.type))
52
- return this.getFinalBaseType(art);
52
+ return this.csnUtils.getFinalBaseTypeWithProps(art.type);
53
53
 
54
54
  return art;
55
55
  }
@@ -14,11 +14,10 @@ const checkUsedTypesForAnonymousAspectComposition = require('./managedInType');
14
14
  const checkForEmptyOrOnlyVirtual = require('./emptyOrOnlyVirtual');
15
15
  // forOdata
16
16
  const { validateDefaultValues } = require('./defaultValues');
17
- // const { checkChainedArray } = require('./arrayOfs');
18
17
  const { checkActionOrFunction } = require('./actionsFunctions');
19
18
  const {
20
- checkCoreMediaTypeAllowence, checkForMultipleCoreMediaTypes,
21
- checkAnalytics, checkAtSapAnnotations, checkReadOnlyAndInsertOnly,
19
+ checkCoreMediaTypeAllowence, checkAnalytics,
20
+ checkAtSapAnnotations, checkReadOnlyAndInsertOnly,
22
21
  } = require('./annotationsOData');
23
22
  // both
24
23
  const { validateOnCondition, validateMixinOnCondition } = require('./onConditions');
@@ -92,7 +91,6 @@ const forOdataArtifactValidators
92
91
  // the renderer does not work because the enricher can't handle certain
93
92
  // OData specifics.
94
93
  // checkChainedArray,
95
- checkForMultipleCoreMediaTypes,
96
94
  checkReadOnlyAndInsertOnly,
97
95
  ];
98
96
 
@@ -105,8 +103,9 @@ const commonMemberValidators
105
103
  validateAssociationsInItems, checkForInvalidTarget,
106
104
  checkVirtualElement, checkElementTypeDefinitionHasType ];
107
105
 
106
+ // TODO: checkManagedAssoc is a forEachMemberRecursively!
108
107
  const commonArtifactValidators = [ checkTypeDefinitionHasType, checkPrimaryKey, checkManagedAssoc ];
109
-
108
+ // TODO: Does it make sense to run the on-condition check as part of a CSN validator?
110
109
  const commonQueryValidators = [ validateMixinOnCondition ];
111
110
 
112
111
  /**
@@ -72,6 +72,7 @@ const { locationString, hasErrors } = require('../base/messages');
72
72
  // Properties that can appear where a type can have type arguments.
73
73
  const typeProperties = [
74
74
  'type', '$typeArgs', 'length', 'precision', 'scale', 'srid',
75
+ '_effectiveType',
75
76
  ];
76
77
 
77
78
  function assertConsistency( model, stage ) {
@@ -117,8 +118,10 @@ function assertConsistency( model, stage ) {
117
118
  '$sources',
118
119
  ],
119
120
  },
120
- location: { // location req if at least one property:
121
- isRequired: parent => noSyntaxErrors() || Object.keys( parent ).length,
121
+ location: {
122
+ // every thing with a $location in CSN must have a XSN location even
123
+ // with syntax errors (currently even internal artifacts like $using):
124
+ isRequired: parent => noSyntaxErrors() || parent && parent.kind,
122
125
  kind: true,
123
126
  requires: [ 'file' ], // line is optional in top-level location
124
127
  optional: [ 'line', 'col', 'endLine', 'endCol', '$notFound' ],
@@ -184,12 +187,16 @@ function assertConsistency( model, stage ) {
184
187
  // are missing the location property
185
188
  test: isDictionary( definition ),
186
189
  requires: [ 'kind', 'name' ],
187
- optional: [ 'elements', '$autoElement', '$uncheckedElements', '_effectiveType', '_deps' ],
190
+ optional: [
191
+ 'elements', '$autoElement', '$uncheckedElements',
192
+ '$requireElementAccess', '_effectiveType', '_deps',
193
+ ],
188
194
  schema: {
189
195
  kind: { test: isString, enum: [ 'builtin' ] },
190
196
  name: { test: isObject, requires: [ 'id', 'element' ] },
191
197
  $autoElement: { test: isString },
192
198
  $uncheckedElements: { test: isBoolean },
199
+ $requireElementAccess: { test: isBoolean },
193
200
  // missing location for normal "elements"
194
201
  elements: { test: TODO },
195
202
  },
@@ -216,8 +223,7 @@ function assertConsistency( model, stage ) {
216
223
  usings: {
217
224
  test: isArray(),
218
225
  requires: [ 'kind', 'location' ],
219
- optional: [ 'name', 'extern', 'usings', '$annotations', 'fileDep' ],
220
- // TODO: get rid of $annotations: []
226
+ optional: [ 'name', 'extern', 'usings', 'fileDep' ],
221
227
  },
222
228
  extern: {
223
229
  requires: [ 'location', 'path' ],
@@ -303,7 +309,6 @@ function assertConsistency( model, stage ) {
303
309
  kind: 'element',
304
310
  test: isDictionary( definition ), // definition since redef
305
311
  requires: [ 'location', 'name' ],
306
- optional: [ '$annotations' ], // TODO: get rid of annos: []
307
312
  },
308
313
  orderBy: { inherits: 'value', test: isArray( expression ) },
309
314
  sort: { test: locationVal( isString ), enum: [ 'asc', 'desc' ] },
@@ -448,11 +453,11 @@ function assertConsistency( model, stage ) {
448
453
  '@': {
449
454
  kind: true,
450
455
  inherits: 'value',
451
- optional: [ 'name', '_block', '$priority', '$duplicate', '$inferred', '$duplicates' ],
456
+ optional: [ 'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported' ],
452
457
  // TODO: name requires if not in parser?
453
458
  },
454
- $priority: { test: TODO }, // TODO: rename to $priority
455
- $annotations: { parser: true, kind: true, test: TODO },
459
+ $priority: { test: TODO },
460
+ $annotations: { parser: true, kind: true, test: TODO }, // deprecated, still there for cds-lsp
456
461
  name: {
457
462
  isRequired: stageParser && (() => false), // not required in parser
458
463
  kind: true,
@@ -467,7 +472,7 @@ function assertConsistency( model, stage ) {
467
472
  ],
468
473
  },
469
474
  absolute: { test: isString },
470
- variant: { test: TODO }, // TODO: not set in CDL parser, only in $annotations
475
+ variant: { test: TODO }, // TODO: not set in CDL parser
471
476
  element: { test: TODO }, // TODO: { test: isString },
472
477
  action: { test: isString },
473
478
  param: { test: isString },
@@ -580,6 +585,7 @@ function assertConsistency( model, stage ) {
580
585
  // (it can contain the artifact itself with no/failed autoexposure):
581
586
  _descendants: { kind: [ 'entity' ], test: isDictionary( isArray( TODO ) ) },
582
587
 
588
+ $errorReported: { parser: true, test: isBoolean }, // to avoid duplicate messages
583
589
  $duplicates: { parser: true, kind: true, test: TODO }, // array of arts or true
584
590
  $extension: { kind: true, test: TODO }, // TODO: introduce $applied instead or $status
585
591
  $inferred: { parser: true, kind: true, test: isString },
@@ -45,6 +45,7 @@ const kindProperties = {
45
45
  noDep: 'special',
46
46
  elements: true, /* only for parse-cdl */
47
47
  actions: true, /* only for parse-cdl */
48
+ enum: true, /* only for parse-cdl */
48
49
  },
49
50
  annotate: {
50
51
  isExtension: true, noDep: 'special', elements: true, enum: true, actions: true, params: true,
@@ -46,6 +46,16 @@ const coreHana = {
46
46
  ST_GEOMETRY: { parameters: [ { name: 'srid', literal: 'number', val: 0 } ], category: 'geo' },
47
47
  };
48
48
 
49
+ const typeParameters = {
50
+ expectedLiteralsFor: {
51
+ length: [ 'number' ],
52
+ scale: [ 'number', 'string' ],
53
+ precision: [ 'number' ],
54
+ srid: [ 'number' ],
55
+ },
56
+ };
57
+ typeParameters.list = Object.keys( typeParameters.expectedLiteralsFor );
58
+
49
59
  // const hana = {
50
60
  // BinaryFloat: {},
51
61
  // LocalDate: {},
@@ -66,19 +76,92 @@ const functionsWithoutParens = [
66
76
  'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER',
67
77
  ];
68
78
 
69
- const specialFunctions = {
79
+ const specialFunctions = compileFunctions( {
80
+ '': [ // the default
81
+ {
82
+ intro: [ 'ALL', 'DISTINCT' ],
83
+ introMsg: [], // do not list them in code completion
84
+ },
85
+ {},
86
+ ],
70
87
  ROUND: [
71
88
  null, null, { // 3rd argument: rounding mode
72
- ROUND_HALF_UP: 'argFull',
73
- ROUND_HALF_DOWN: 'argFull',
74
- ROUND_HALF_EVEN: 'argFull',
75
- ROUND_UP: 'argFull',
76
- ROUND_DOWN: 'argFull',
77
- ROUND_CEILING: 'argFull',
78
- ROUND_FLOOR: 'argFull',
89
+ expr: [ 'ROUND_HALF_UP', 'ROUND_HALF_DOWN', 'ROUND_HALF_EVEN',
90
+ 'ROUND_UP', 'ROUND_DOWN', 'ROUND_CEILING', 'ROUND_FLOOR' ],
79
91
  },
80
92
  ],
81
- };
93
+ TRIM: [
94
+ {
95
+ intro: [ 'LEADING', 'TRAILING', 'BOTH' ],
96
+ expr: [ 'LEADING', 'TRAILING', 'BOTH' ],
97
+ separator: [ 'FROM' ],
98
+ },
99
+ ],
100
+ EXTRACT: [
101
+ {
102
+ expr: [ 'YEAR', 'MONTH', 'DAY', 'HOUR', 'MINUTE', 'SECOND' ],
103
+ separator: [ 'FROM' ],
104
+ },
105
+ ],
106
+ COUNT: [
107
+ {
108
+ expr: [ '*' ],
109
+ intro: [ 'ALL', 'DISTINCT' ],
110
+ },
111
+ ],
112
+ MIN: 'COUNT',
113
+ MAX: 'COUNT',
114
+ SUM: 'COUNT',
115
+ AVG: 'COUNT',
116
+ STDDDEV: 'COUNT',
117
+ VAR: 'COUNT',
118
+ LOCATE_REGEXPR: [
119
+ {
120
+ intro: [ 'START', 'AFTER' ],
121
+ separator: [ 'FLAG', 'IN', 'FROM', 'OCCURRENCE', 'GROUP' ],
122
+ },
123
+ ],
124
+ OCCURRENCES_REGEXPR: [
125
+ {
126
+ separator: [ 'FLAG', 'IN', 'FROM' ],
127
+ },
128
+ ],
129
+ REPLACE_REGEXPR: [
130
+ {
131
+ separator: [ 'FLAG', 'IN', 'WITH', 'FROM', 'OCCURRENCE' ],
132
+ expr: [ 'ALL' ],
133
+ },
134
+ ],
135
+ SUBSTRING_REGEXPR: [
136
+ {
137
+ separator: [ 'FLAG', 'IN', 'FROM', 'OCCURRENCE', 'GROUP' ],
138
+ },
139
+ ],
140
+ } );
141
+
142
+ function compileFunctions( special ) {
143
+ const compiled = {};
144
+ for (const [ name, val ] of Object.entries( special ))
145
+ compiled[name] = (typeof val === 'string' ? special[val] : val).map( compileArg );
146
+ return compiled;
147
+ }
148
+
149
+ function compileArg( src ) {
150
+ if (!src)
151
+ return src;
152
+ const tgt = {
153
+ intro: src.intro || [],
154
+ expr: src.expr || [],
155
+ separator: src.separator || [],
156
+ };
157
+ for (const generic of [ 'intro', 'expr', 'separator' ]) { // intro before expr!
158
+ for (const token of src[generic] || [])
159
+ tgt[token] = generic;
160
+ }
161
+ if (tgt.intro) // same token could be in both 'expr' and 'intro':
162
+ tgt.introMsg = src.introMsg || tgt.intro.filter( token => tgt[token] === 'intro' );
163
+ return tgt;
164
+ }
82
165
 
83
166
  /**
84
167
  * Variables that have special meaning in CDL/CSN.
@@ -96,12 +179,15 @@ const magicVariables = {
96
179
  elements: {
97
180
  from: {}, to: {},
98
181
  },
182
+ // Require that elements are accessed, i.e. no $at, only $at.<element>.
183
+ $requireElementAccess: true,
99
184
  },
100
185
  $now: {}, // Dito
101
186
  $session: {
102
187
  // In ABAP CDS session variables are accessed in a generic way via
103
188
  // the pseudo variable $session.
104
189
  $uncheckedElements: true,
190
+ $requireElementAccess: true,
105
191
  },
106
192
  };
107
193
 
@@ -276,6 +362,8 @@ function initBuiltins( model ) {
276
362
  art.$autoElement = magic.$autoElement;
277
363
  if (magic.$uncheckedElements)
278
364
  art.$uncheckedElements = magic.$uncheckedElements;
365
+ if (magic.$requireElementAccess)
366
+ art.$requireElementAccess = magic.$requireElementAccess;
279
367
 
280
368
  createMagicElements( art, magic.elements );
281
369
  if (options.variableReplacements)
@@ -312,6 +400,7 @@ function initBuiltins( model ) {
312
400
  }
313
401
 
314
402
  module.exports = {
403
+ typeParameters,
315
404
  functionsWithoutParens,
316
405
  specialFunctions,
317
406
  initBuiltins,
@@ -24,6 +24,7 @@ function check( model ) { // = XSN
24
24
  error, warning, message,
25
25
  } = model.$messageFunctions;
26
26
  forEachDefinition( model, checkArtifact );
27
+ checkSapCommonLocale( model, model.$messageFunctions );
27
28
  return;
28
29
 
29
30
  function checkArtifact( art ) {
@@ -447,80 +448,10 @@ function check( model ) { // = XSN
447
448
  // params are limited to actual values and params
448
449
  if (pathStep.args)
449
450
  checkExpression(pathStep.args);
450
-
451
- if (!path[0] || !path[0]._navigation) { // TODO: Discuss (see #4108)
452
- checkPathForMissingArguments(pathStep);
453
- }
454
451
  }
455
452
  });
456
453
  }
457
454
 
458
- /**
459
- * Check whether the argument count of the given path expression matches its artifact.
460
- * If there is a mismatch, an error is issued.
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
- *
466
- * @param {object} pathStep The expression to check
467
- */
468
- function checkPathForMissingArguments(pathStep) {
469
- // _artifact may not be set, e.g. for functions like `convert_currency( amount => 3 )`
470
- // _navigation must not be set or we would (for example) check each field of an entity
471
- if (!pathStep._artifact || pathStep._navigation)
472
- return;
473
-
474
- const isAssociation = !!pathStep._artifact.target;
475
- if (isAssociation) {
476
- const targetFinalType = pathStep._artifact.target._artifact &&
477
- pathStep._artifact.target._artifact._effectiveType;
478
- const finalTypeParams = targetFinalType ? targetFinalType.params : null;
479
- compareActualNamedArgsWithFormalNamedArgs(pathStep.args, finalTypeParams);
480
- }
481
- else {
482
- // Parameters can only be provided when navigating along associations, so because this path
483
- // is for non-associations, checking arguments along a navigation is unnecessary and faulty.
484
- compareActualNamedArgsWithFormalNamedArgs(pathStep.args, pathStep._artifact.params);
485
- }
486
-
487
- /**
488
- * Compare two argument dictionaries for correct argument count.
489
- * @param {object} actualArgs
490
- * @param {object} formalArgs
491
- */
492
- function compareActualNamedArgsWithFormalNamedArgs(actualArgs, formalArgs) {
493
- actualArgs = actualArgs || {};
494
- formalArgs = formalArgs || {};
495
-
496
- const aArgsCount = Object.keys(actualArgs).length;
497
- const expectedNames = Object.keys(formalArgs);
498
-
499
- const missingArgs = [];
500
- for (const fAName in formalArgs) {
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
- }
510
- }
511
-
512
- if (missingArgs.length) {
513
- error(null, [ pathStep.location, pathStep ],
514
- { names: missingArgs, expected: expectedNames.length, given: aArgsCount },
515
- 'Expected $(EXPECTED) arguments but $(GIVEN) given; missing: $(NAMES)');
516
- }
517
- // Note:
518
- // Unknown arguments are already handled by messages
519
- // args-expected-named and args-undefined-param
520
- }
521
- }
522
-
523
-
524
455
  function checkAssociation(elem) {
525
456
  // TODO: yes, a check similar to this could make it into the compiler)
526
457
  // when virtual element is part of association
@@ -956,6 +887,27 @@ function check( model ) { // = XSN
956
887
  }
957
888
  }
958
889
 
890
+ /**
891
+ * Checks that sap.common.Locale is of type cds.String. This limitation may
892
+ * be lifted later on.
893
+ *
894
+ * @param {XSN.Model} model
895
+ * @param {object} messageFunctions
896
+ */
897
+ function checkSapCommonLocale( model, messageFunctions ) {
898
+ const localeArt = model.definitions['sap.common.Locale'];
899
+ if (localeArt) {
900
+ const type = localeArt._effectiveType;
901
+ const isCdsString = type && type.name && type.name.absolute === 'cds.String';
902
+ if (!isCdsString) {
903
+ const { message } = messageFunctions;
904
+ message('type-expected-builtin', [ localeArt.name.location, localeArt ],
905
+ { name: 'sap.common.Locale' },
906
+ 'Expected $(NAME) to be a string type');
907
+ }
908
+ }
909
+ }
910
+
959
911
  // For each property named 'path' in 'node' (recursively), call callback(path, node)
960
912
  //
961
913
  // TODO: remove - this is not a good way to traverse expressions