@sap/cds-compiler 2.15.2 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/CHANGELOG.md +66 -1590
  2. package/bin/cdsc.js +42 -46
  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 +312 -143
  10. package/lib/api/options.js +15 -85
  11. package/lib/api/validate.js +6 -10
  12. package/lib/base/keywords.js +280 -110
  13. package/lib/base/message-registry.js +80 -24
  14. package/lib/base/messages.js +103 -52
  15. package/lib/base/model.js +44 -2
  16. package/lib/base/optionProcessorHelper.js +53 -21
  17. package/lib/checks/actionsFunctions.js +7 -5
  18. package/lib/checks/annotationsOData.js +1 -1
  19. package/lib/checks/cdsPersistence.js +1 -0
  20. package/lib/checks/elements.js +6 -6
  21. package/lib/checks/invalidTarget.js +1 -1
  22. package/lib/checks/nonexpandableStructured.js +1 -1
  23. package/lib/checks/queryNoDbArtifacts.js +2 -1
  24. package/lib/checks/selectItems.js +5 -1
  25. package/lib/checks/types.js +4 -2
  26. package/lib/checks/utils.js +2 -2
  27. package/lib/checks/validator.js +2 -1
  28. package/lib/compiler/assert-consistency.js +15 -10
  29. package/lib/compiler/builtins.js +127 -10
  30. package/lib/compiler/define.js +6 -4
  31. package/lib/compiler/extend.js +63 -12
  32. package/lib/compiler/finalize-parse-cdl.js +20 -9
  33. package/lib/compiler/index.js +25 -11
  34. package/lib/compiler/moduleLayers.js +7 -0
  35. package/lib/compiler/populate.js +16 -14
  36. package/lib/compiler/propagator.js +3 -3
  37. package/lib/compiler/resolve.js +194 -222
  38. package/lib/compiler/shared.js +56 -76
  39. package/lib/compiler/tweak-assocs.js +9 -10
  40. package/lib/compiler/utils.js +7 -2
  41. package/lib/edm/annotations/genericTranslation.js +60 -6
  42. package/lib/edm/annotations/preprocessAnnotations.js +10 -11
  43. package/lib/edm/csn2edm.js +39 -41
  44. package/lib/edm/edm.js +22 -15
  45. package/lib/edm/edmPreprocessor.js +66 -69
  46. package/lib/edm/edmUtils.js +12 -62
  47. package/lib/gen/Dictionary.json +8 -6
  48. package/lib/gen/language.checksum +1 -1
  49. package/lib/gen/language.interp +8 -30
  50. package/lib/gen/language.tokens +105 -114
  51. package/lib/gen/languageLexer.interp +1 -34
  52. package/lib/gen/languageLexer.js +889 -1007
  53. package/lib/gen/languageLexer.tokens +95 -106
  54. package/lib/gen/languageParser.js +20717 -22376
  55. package/lib/json/from-csn.js +73 -68
  56. package/lib/json/to-csn.js +13 -10
  57. package/lib/language/antlrParser.js +2 -2
  58. package/lib/language/docCommentParser.js +61 -38
  59. package/lib/language/errorStrategy.js +52 -40
  60. package/lib/language/genericAntlrParser.js +333 -259
  61. package/lib/language/language.g4 +600 -645
  62. package/lib/language/multiLineStringParser.js +14 -42
  63. package/lib/language/textUtils.js +44 -0
  64. package/lib/main.d.ts +27 -42
  65. package/lib/main.js +104 -81
  66. package/lib/model/csnRefs.js +2 -1
  67. package/lib/model/csnUtils.js +183 -285
  68. package/lib/model/revealInternalProperties.js +32 -9
  69. package/lib/model/sortViews.js +32 -31
  70. package/lib/optionProcessor.js +64 -57
  71. package/lib/render/.eslintrc.json +1 -1
  72. package/lib/render/DuplicateChecker.js +4 -7
  73. package/lib/render/manageConstraints.js +70 -2
  74. package/lib/render/toCdl.js +334 -339
  75. package/lib/render/toHdbcds.js +20 -16
  76. package/lib/render/toRename.js +44 -22
  77. package/lib/render/toSql.js +60 -54
  78. package/lib/render/utils/common.js +15 -1
  79. package/lib/render/utils/sql.js +20 -19
  80. package/lib/sql-identifier.js +6 -0
  81. package/lib/transform/db/.eslintrc.json +3 -2
  82. package/lib/transform/db/cdsPersistence.js +5 -15
  83. package/lib/transform/db/constraints.js +1 -1
  84. package/lib/transform/db/expansion.js +7 -6
  85. package/lib/transform/db/flattening.js +18 -19
  86. package/lib/transform/db/views.js +3 -3
  87. package/lib/transform/draft/.eslintrc.json +2 -2
  88. package/lib/transform/draft/db.js +6 -6
  89. package/lib/transform/draft/odata.js +6 -7
  90. package/lib/transform/forHanaNew.js +19 -22
  91. package/lib/transform/forOdataNew.js +13 -15
  92. package/lib/transform/localized.js +35 -25
  93. package/lib/transform/odata/toFinalBaseType.js +11 -9
  94. package/lib/transform/odata/typesExposure.js +3 -3
  95. package/lib/transform/odata/utils.js +1 -38
  96. package/lib/transform/transformUtilsNew.js +63 -77
  97. package/lib/transform/translateAssocsToJoins.js +6 -2
  98. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  99. package/lib/transform/universalCsn/coreComputed.js +11 -6
  100. package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
  101. package/lib/utils/file.js +31 -21
  102. package/lib/utils/timetrace.js +20 -21
  103. package/package.json +34 -4
  104. package/share/messages/syntax-expected-integer.md +9 -8
  105. package/doc/ApiMigration.md +0 -237
  106. package/doc/CommandLineMigration.md +0 -58
  107. package/doc/ErrorMessages.md +0 -175
  108. package/doc/FioriAnnotations.md +0 -94
  109. package/doc/ODataTransformation.md +0 -273
  110. package/lib/backends.js +0 -529
  111. package/lib/fix_antlr4-8_warning.js +0 -56
@@ -153,36 +153,67 @@ function createOptionProcessor() {
153
153
  * Internal: Define a general or command option.
154
154
  * Throws if the option is already registered in the given command context.
155
155
  * or in the given command.
156
+ *
156
157
  * @private
157
158
  * @see option()
158
159
  */
159
160
  function _addOption(cmd, optString, validValues, options) {
160
- const opt = _parseOptionString(optString, validValues);
161
- Object.assign(opt, options);
161
+ const cliOpt = _parseOptionString(optString, validValues);
162
+ Object.assign(cliOpt, options);
163
+ _addLongOption(cmd, cliOpt.longName, cliOpt);
164
+ _addShortOption(cmd, cliOpt.shortName, cliOpt);
165
+
166
+ for (const alias of cliOpt.aliases || []) {
167
+ const aliasOpt = Object.assign({ }, cliOpt, { isAlias: true });
168
+ _addLongOption(cmd, alias, aliasOpt); // use same camelName, etc. for alias
169
+ }
170
+
171
+ return cmd;
172
+ }
162
173
 
163
- if (cmd.options[opt.longName]) {
164
- throw new Error(`Duplicate assignment for long option ${opt.longName}`);
165
- } else if (optionProcessor.options[opt.longName]) {
174
+ /**
175
+ * Internal: Add longName to the list of options.
176
+ * Throws if the option is already registered in the given command context.
177
+ * or in the given command.
178
+ * `longName` may differ from `opt.longName`, e.g. for aliases.
179
+ *
180
+ * @private
181
+ * @see _addOption()
182
+ */
183
+ function _addLongOption(cmd, longName, opt) {
184
+ if (cmd.options[longName]) {
185
+ throw new Error(`Duplicate assignment for long option ${longName}`);
186
+ } else if (optionProcessor.options[longName]) {
166
187
  // This path is only taken if optString is for commands
167
188
  optionProcessor.optionClashes.push({
168
- option: opt.longName,
169
- description: `Command '${cmd.longName}' has option clash with general options for: ${opt.longName}`
189
+ option: longName,
190
+ description: `Command '${cmd.longName}' has option clash with general options for: ${longName}`
170
191
  });
171
192
  }
172
- cmd.options[opt.longName] = opt;
173
- if (opt.shortName) {
174
- if (cmd.options[opt.shortName]) {
175
- throw new Error(`Duplicate assignment for short option ${opt.shortName}`);
176
- } else if (optionProcessor.options[opt.shortName]) {
177
- // This path is only taken if optString is for commands
178
- optionProcessor.optionClashes.push({
179
- option: opt.shortName,
180
- description: `Command '${cmd.longName}' has option clash with general options for: ${opt.shortName}`
181
- });
182
- }
183
- cmd.options[opt.shortName] = opt;
193
+ cmd.options[longName] = opt;
194
+ }
195
+ /**
196
+ * Internal: Add shortName to the list of options.
197
+ * Throws if the option is already registered in the given command context.
198
+ * or in the given command.
199
+ * `longName` may differ from `opt.longName`, e.g. for aliases.
200
+ *
201
+ * @private
202
+ * @see _addOption()
203
+ */
204
+ function _addShortOption(cmd, shortName, opt) {
205
+ if (!shortName)
206
+ return;
207
+ if (cmd.options[shortName]) {
208
+ throw new Error(`Duplicate assignment for short option ${shortName}`);
209
+ } else if (optionProcessor.options[shortName]) {
210
+ // This path is only taken if optString is for commands
211
+ optionProcessor.optionClashes.push({
212
+ option: shortName,
213
+ description: `Command '${cmd.longName}' has option clash with general options for: ${shortName}`
214
+ });
184
215
  }
185
- return cmd;
216
+ cmd.options[shortName] = opt;
186
217
  }
187
218
 
188
219
  // Internal: Parse one command string like "F, toFoo". Return an object like this
@@ -275,7 +306,8 @@ function createOptionProcessor() {
275
306
  shortName,
276
307
  camelName: camelifyLongOption(longName),
277
308
  param,
278
- validValues
309
+ validValues,
310
+ isAlias: false, // default
279
311
  }
280
312
  }
281
313
 
@@ -16,8 +16,8 @@ function checkActionOrFunction(art, artName, prop, path) {
16
16
  if (!(art.kind === 'action' || art.kind === 'function') && !art.actions)
17
17
  return;
18
18
 
19
- // const isMultiSchema = this.options.toOdata.odataFormat === 'structured' &&
20
- // (this.options.toOdata.odataProxies || this.options.toOdata.odataXServiceRefs);
19
+ // const isMultiSchema = this.options.odataFormat === 'structured' &&
20
+ // (this.options.odataProxies || this.options.odataXServiceRefs);
21
21
 
22
22
  const serviceName = this.csnUtils.getServiceName(artName);
23
23
  if (!serviceName)
@@ -85,9 +85,11 @@ function checkActionOrFunction(art, artName, prop, path) {
85
85
  * @param {string} actKind 'action' or 'function'
86
86
  */
87
87
  function checkReturns(returns, currPath, actKind) {
88
- const finalReturnType = returns.type ? this.csnUtils.getFinalBaseType(returns.type) : returns;
88
+ const finalReturnType = returns.type ? this.csnUtils.getFinalBaseTypeWithProps(returns.type) : returns;
89
+ if (!finalReturnType)
90
+ return; // no type, e.g. `type of V:calculated`; already an error in `checkTypeOfHasProperType()`
89
91
 
90
- if (this.csnUtils.isAssocOrComposition(finalReturnType)) {
92
+ if (this.csnUtils.isAssocOrComposition(finalReturnType.type)) {
91
93
  this.error(null, currPath, { '#': actKind },
92
94
  {
93
95
  std: 'An association is not allowed as this artifact\'s return type', // Not used
@@ -98,7 +100,7 @@ function checkActionOrFunction(art, artName, prop, path) {
98
100
 
99
101
  if (finalReturnType.items) // check array return type
100
102
  checkReturns.bind(this)(finalReturnType.items, currPath.concat('items'), actKind);
101
- else // check if return type is user definited from the current service
103
+ else // check if return type is user defined from the current service
102
104
  checkUserDefinedType.bind(this)(finalReturnType, returns.type, currPath);
103
105
  }
104
106
 
@@ -23,7 +23,7 @@ function checkCoreMediaTypeAllowence(member) {
23
23
  'cds.hana.CLOB': 1,
24
24
  'cds.hana.BINARY': 1,
25
25
  };
26
- if (member['@Core.MediaType'] && member.type && !(this.csnUtils.getFinalBaseType(member.type) in allowedCoreMediaTypes)) {
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
  }
@@ -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
  }
@@ -103,8 +103,9 @@ const commonMemberValidators
103
103
  validateAssociationsInItems, checkForInvalidTarget,
104
104
  checkVirtualElement, checkElementTypeDefinitionHasType ];
105
105
 
106
+ // TODO: checkManagedAssoc is a forEachMemberRecursively!
106
107
  const commonArtifactValidators = [ checkTypeDefinitionHasType, checkPrimaryKey, checkManagedAssoc ];
107
-
108
+ // TODO: Does it make sense to run the on-condition check as part of a CSN validator?
108
109
  const commonQueryValidators = [ validateMixinOnCondition ];
109
110
 
110
111
  /**
@@ -118,8 +118,10 @@ function assertConsistency( model, stage ) {
118
118
  '$sources',
119
119
  ],
120
120
  },
121
- location: { // location req if at least one property:
122
- 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,
123
125
  kind: true,
124
126
  requires: [ 'file' ], // line is optional in top-level location
125
127
  optional: [ 'line', 'col', 'endLine', 'endCol', '$notFound' ],
@@ -185,12 +187,16 @@ function assertConsistency( model, stage ) {
185
187
  // are missing the location property
186
188
  test: isDictionary( definition ),
187
189
  requires: [ 'kind', 'name' ],
188
- optional: [ 'elements', '$autoElement', '$uncheckedElements', '_effectiveType', '_deps' ],
190
+ optional: [
191
+ 'elements', '$autoElement', '$uncheckedElements',
192
+ '$requireElementAccess', '_effectiveType', '_deps',
193
+ ],
189
194
  schema: {
190
195
  kind: { test: isString, enum: [ 'builtin' ] },
191
196
  name: { test: isObject, requires: [ 'id', 'element' ] },
192
197
  $autoElement: { test: isString },
193
198
  $uncheckedElements: { test: isBoolean },
199
+ $requireElementAccess: { test: isBoolean },
194
200
  // missing location for normal "elements"
195
201
  elements: { test: TODO },
196
202
  },
@@ -217,8 +223,7 @@ function assertConsistency( model, stage ) {
217
223
  usings: {
218
224
  test: isArray(),
219
225
  requires: [ 'kind', 'location' ],
220
- optional: [ 'name', 'extern', 'usings', '$annotations', 'fileDep' ],
221
- // TODO: get rid of $annotations: []
226
+ optional: [ 'name', 'extern', 'usings', 'fileDep' ],
222
227
  },
223
228
  extern: {
224
229
  requires: [ 'location', 'path' ],
@@ -304,7 +309,6 @@ function assertConsistency( model, stage ) {
304
309
  kind: 'element',
305
310
  test: isDictionary( definition ), // definition since redef
306
311
  requires: [ 'location', 'name' ],
307
- optional: [ '$annotations' ], // TODO: get rid of annos: []
308
312
  },
309
313
  orderBy: { inherits: 'value', test: isArray( expression ) },
310
314
  sort: { test: locationVal( isString ), enum: [ 'asc', 'desc' ] },
@@ -449,11 +453,11 @@ function assertConsistency( model, stage ) {
449
453
  '@': {
450
454
  kind: true,
451
455
  inherits: 'value',
452
- optional: [ 'name', '_block', '$priority', '$duplicate', '$inferred', '$duplicates' ],
456
+ optional: [ 'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported' ],
453
457
  // TODO: name requires if not in parser?
454
458
  },
455
- $priority: { test: TODO }, // TODO: rename to $priority
456
- $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
457
461
  name: {
458
462
  isRequired: stageParser && (() => false), // not required in parser
459
463
  kind: true,
@@ -468,7 +472,7 @@ function assertConsistency( model, stage ) {
468
472
  ],
469
473
  },
470
474
  absolute: { test: isString },
471
- variant: { test: TODO }, // TODO: not set in CDL parser, only in $annotations
475
+ variant: { test: TODO }, // TODO: not set in CDL parser
472
476
  element: { test: TODO }, // TODO: { test: isString },
473
477
  action: { test: isString },
474
478
  param: { test: isString },
@@ -581,6 +585,7 @@ function assertConsistency( model, stage ) {
581
585
  // (it can contain the artifact itself with no/failed autoexposure):
582
586
  _descendants: { kind: [ 'entity' ], test: isDictionary( isArray( TODO ) ) },
583
587
 
588
+ $errorReported: { parser: true, kind: true, test: isString }, // to avoid duplicate messages
584
589
  $duplicates: { parser: true, kind: true, test: TODO }, // array of arts or true
585
590
  $extension: { kind: true, test: TODO }, // TODO: introduce $applied instead or $status
586
591
  $inferred: { parser: true, kind: true, test: isString },
@@ -1,7 +1,7 @@
1
1
  // The builtin artifacts of CDS
2
2
 
3
3
  // TODO: split this file
4
- // - in base/: common definitions
4
+ // - in base/: common definitions, datetime formats
5
5
  // - in compiler/: XSN-specific
6
6
  // - in ?: CSN-specific
7
7
 
@@ -76,19 +76,92 @@ const functionsWithoutParens = [
76
76
  'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER',
77
77
  ];
78
78
 
79
- 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
+ ],
80
87
  ROUND: [
81
88
  null, null, { // 3rd argument: rounding mode
82
- ROUND_HALF_UP: 'argFull',
83
- ROUND_HALF_DOWN: 'argFull',
84
- ROUND_HALF_EVEN: 'argFull',
85
- ROUND_UP: 'argFull',
86
- ROUND_DOWN: 'argFull',
87
- ROUND_CEILING: 'argFull',
88
- ROUND_FLOOR: 'argFull',
89
+ expr: [ 'ROUND_HALF_UP', 'ROUND_HALF_DOWN', 'ROUND_HALF_EVEN',
90
+ 'ROUND_UP', 'ROUND_DOWN', 'ROUND_CEILING', 'ROUND_FLOOR' ],
89
91
  },
90
92
  ],
91
- };
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
+ }
92
165
 
93
166
  /**
94
167
  * Variables that have special meaning in CDL/CSN.
@@ -106,17 +179,58 @@ const magicVariables = {
106
179
  elements: {
107
180
  from: {}, to: {},
108
181
  },
182
+ // Require that elements are accessed, i.e. no $at, only $at.<element>.
183
+ $requireElementAccess: true,
109
184
  },
110
185
  $now: {}, // Dito
111
186
  $session: {
112
187
  // In ABAP CDS session variables are accessed in a generic way via
113
188
  // the pseudo variable $session.
114
189
  $uncheckedElements: true,
190
+ $requireElementAccess: true,
115
191
  },
116
192
  };
117
193
 
118
194
  // see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP Cds via function
119
195
 
196
+ /**
197
+ * Patterns for literal token tests and creation. The value is a map from the
198
+ * `prefix` argument of function `quotedliteral` to the following properties:
199
+ * - `test_msg`: error message which is issued if `test_fn` or `test_re` fail.
200
+ * - `test_fn`: function called with argument `value`, fails falsy return value
201
+ * - `test_re`: regular expression, fails if it does not match argument `value`
202
+ * - `unexpected_msg`: error message which is issued if `unexpected_char` matches
203
+ * - `unexpected_char`: regular expression matching an illegal character in `value`,
204
+ * the error location is only correct for a literal <prefix>'<value>'
205
+ * - `literal`: the value which is used instead of `prefix` in the AST
206
+ * TODO: we might do a range check (consider leap seconds, i.e. max value 60),
207
+ * but always allow Feb 29 (no leap year computation)
208
+ */
209
+ const quotedLiteralPatterns = {
210
+ x: {
211
+ test_variant: 'uneven-hex',
212
+ test_fn: (str => Number.isInteger(str.length / 2)),
213
+ unexpected_variant: 'invalid-hex',
214
+ unexpected_char: /[^0-9a-f]/i,
215
+ json_type: 'string',
216
+ },
217
+ time: {
218
+ test_variant: 'time',
219
+ test_re: /^[0-9]{1,2}:[0-9]{1,2}(:[0-9]{1,2})?$/,
220
+ json_type: 'string',
221
+ },
222
+ date: {
223
+ test_variant: 'date',
224
+ test_re: /^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/,
225
+ json_type: 'string',
226
+ },
227
+ timestamp: {
228
+ test_variant: 'timestamp',
229
+ test_re: /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}(:[0-9]{2}(\.[0-9]{1,7})?)?$/,
230
+ json_type: 'string',
231
+ },
232
+ };
233
+
120
234
  /** All types belong to one category. */
121
235
  const typeCategories = {
122
236
  string: [],
@@ -286,6 +400,8 @@ function initBuiltins( model ) {
286
400
  art.$autoElement = magic.$autoElement;
287
401
  if (magic.$uncheckedElements)
288
402
  art.$uncheckedElements = magic.$uncheckedElements;
403
+ if (magic.$requireElementAccess)
404
+ art.$requireElementAccess = magic.$requireElementAccess;
289
405
 
290
406
  createMagicElements( art, magic.elements );
291
407
  if (options.variableReplacements)
@@ -325,6 +441,7 @@ module.exports = {
325
441
  typeParameters,
326
442
  functionsWithoutParens,
327
443
  specialFunctions,
444
+ quotedLiteralPatterns,
328
445
  initBuiltins,
329
446
  isInReservedNamespace,
330
447
  isBuiltinType,
@@ -390,14 +390,14 @@ function define( model ) {
390
390
  setLink( vocab, '_block', block );
391
391
  const { name } = vocab;
392
392
  if (!name.absolute)
393
- name.absolute = prefix + name.path.map( id => id.id ).join('.');
393
+ name.absolute = prefix + pathName( name.path );
394
394
  dictAdd( model.vocabularies, name.absolute, vocab );
395
395
  }
396
396
 
397
397
  // Phase 2 ("init") --------------------------------------------------------
398
398
 
399
399
  function checkRedefinition( art ) {
400
- if (!art.$duplicates)
400
+ if (!art.$duplicates || art.$errorReported === 'syntax-duplicate-extend')
401
401
  return;
402
402
  if (art._main) {
403
403
  error( 'duplicate-definition', [ art.name.location, art ], {
@@ -505,13 +505,13 @@ function define( model ) {
505
505
  initParentLink( parent, definitions );
506
506
  }
507
507
  if (art.kind !== 'namespace' &&
508
- isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' )) {
508
+ isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' )) {
509
509
  let p = parent;
510
510
  while (p && kindProperties[p.kind].artifacts)
511
511
  p = p._parent;
512
512
  if (p) {
513
513
  error( 'subartifacts-not-supported', [ art.name.location, art ],
514
- { art: p, prop: 'deprecated.generatedEntityNameWithUnderscore' },
514
+ { art: p, prop: 'deprecated._generatedEntityNameWithUnderscore' },
515
515
  // eslint-disable-next-line max-len
516
516
  'With the option $(PROP), no sub artifact can be defined for a non-context/service $(ART)' );
517
517
  }
@@ -670,6 +670,8 @@ function define( model ) {
670
670
  * @param {object} exprOrPathElement starts w/ an expr but then subelem from .path or .where.args
671
671
  */
672
672
  function approveExistsInChildren(exprOrPathElement) {
673
+ if (!exprOrPathElement) // may be null in case of parse error
674
+ return;
673
675
  if (exprOrPathElement.$expected === 'exists')
674
676
  exprOrPathElement.$expected = 'approved-exists';
675
677
  // Drill down