@sap/cds-compiler 4.8.0 → 4.9.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 (95) hide show
  1. package/CHANGELOG.md +38 -4
  2. package/bin/cds_remove_invalid_whitespace.js +135 -0
  3. package/bin/cds_update_annotations.js +180 -0
  4. package/bin/cds_update_identifiers.js +3 -4
  5. package/bin/cdsc.js +30 -17
  6. package/doc/CHANGELOG_BETA.md +19 -0
  7. package/lib/api/main.js +59 -24
  8. package/lib/api/options.js +12 -1
  9. package/lib/api/validate.js +1 -5
  10. package/lib/base/builtins.js +27 -0
  11. package/lib/base/message-registry.js +38 -21
  12. package/lib/base/messages.js +51 -20
  13. package/lib/base/model.js +4 -5
  14. package/lib/checks/actionsFunctions.js +2 -2
  15. package/lib/checks/annotationsOData.js +3 -0
  16. package/lib/checks/defaultValues.js +5 -2
  17. package/lib/checks/queryNoDbArtifacts.js +3 -2
  18. package/lib/checks/validator.js +2 -34
  19. package/lib/compiler/assert-consistency.js +10 -3
  20. package/lib/compiler/checks.js +44 -18
  21. package/lib/compiler/define.js +38 -30
  22. package/lib/compiler/extend.js +33 -10
  23. package/lib/compiler/index.js +0 -1
  24. package/lib/compiler/lsp-api.js +5 -0
  25. package/lib/compiler/populate.js +0 -2
  26. package/lib/compiler/propagator.js +23 -19
  27. package/lib/compiler/resolve.js +48 -29
  28. package/lib/compiler/shared.js +60 -20
  29. package/lib/compiler/tweak-assocs.js +72 -116
  30. package/lib/compiler/xpr-rewrite.js +762 -0
  31. package/lib/edm/annotations/edmJson.js +24 -7
  32. package/lib/edm/annotations/genericTranslation.js +81 -61
  33. package/lib/edm/edm.js +4 -4
  34. package/lib/edm/edmInboundChecks.js +33 -0
  35. package/lib/edm/edmPreprocessor.js +9 -6
  36. package/lib/gen/Dictionary.json +129 -14
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +1 -1
  39. package/lib/gen/languageParser.js +1523 -1518
  40. package/lib/json/from-csn.js +13 -4
  41. package/lib/json/to-csn.js +12 -12
  42. package/lib/language/genericAntlrParser.js +14 -6
  43. package/lib/main.d.ts +67 -14
  44. package/lib/main.js +1 -0
  45. package/lib/model/cloneCsn.js +6 -3
  46. package/lib/model/csnRefs.js +23 -11
  47. package/lib/model/csnUtils.js +13 -7
  48. package/lib/model/enrichCsn.js +3 -1
  49. package/lib/model/revealInternalProperties.js +2 -1
  50. package/lib/model/sortViews.js +14 -6
  51. package/lib/modelCompare/compare.js +33 -34
  52. package/lib/optionProcessor.js +27 -2
  53. package/lib/render/DuplicateChecker.js +6 -6
  54. package/lib/render/manageConstraints.js +1 -0
  55. package/lib/render/toCdl.js +3 -1
  56. package/lib/transform/db/applyTransformations.js +33 -0
  57. package/lib/transform/db/constraints.js +75 -28
  58. package/lib/transform/db/expansion.js +8 -3
  59. package/lib/transform/db/flattening.js +2 -2
  60. package/lib/transform/db/groupByOrderBy.js +2 -2
  61. package/lib/transform/db/temporal.js +6 -3
  62. package/lib/transform/db/transformExists.js +2 -2
  63. package/lib/transform/effective/annotations.js +194 -0
  64. package/lib/transform/effective/main.js +6 -8
  65. package/lib/transform/effective/misc.js +31 -10
  66. package/lib/transform/forOdata.js +23 -7
  67. package/lib/transform/forRelationalDB.js +3 -3
  68. package/lib/transform/localized.js +7 -6
  69. package/lib/transform/odata/flattening.js +221 -124
  70. package/lib/transform/odata/toFinalBaseType.js +1 -1
  71. package/lib/transform/odata/typesExposure.js +15 -12
  72. package/lib/transform/parseExpr.js +4 -4
  73. package/lib/transform/transformUtils.js +47 -42
  74. package/lib/transform/translateAssocsToJoins.js +47 -47
  75. package/lib/transform/universalCsn/universalCsnEnricher.js +16 -19
  76. package/package.json +1 -1
  77. package/share/messages/anno-missing-rewrite.md +45 -0
  78. package/share/messages/message-explanations.json +1 -0
  79. package/bin/.eslintrc.json +0 -17
  80. package/lib/api/.eslintrc.json +0 -37
  81. package/lib/checks/.eslintrc.json +0 -31
  82. package/lib/compiler/.eslintrc.json +0 -8
  83. package/lib/edm/.eslintrc.json +0 -46
  84. package/lib/inspect/.eslintrc.json +0 -4
  85. package/lib/json/.eslintrc.json +0 -4
  86. package/lib/language/.eslintrc.json +0 -4
  87. package/lib/model/.eslintrc.json +0 -13
  88. package/lib/modelCompare/utils/.eslintrc.json +0 -22
  89. package/lib/render/.eslintrc.json +0 -22
  90. package/lib/transform/.eslintrc.json +0 -13
  91. package/lib/transform/db/.eslintrc.json +0 -41
  92. package/lib/transform/draft/.eslintrc.json +0 -4
  93. package/lib/transform/effective/.eslintrc.json +0 -4
  94. package/lib/transform/universalCsn/.eslintrc.json +0 -37
  95. package/lib/utils/.eslintrc.json +0 -7
package/lib/base/model.js CHANGED
@@ -26,9 +26,6 @@ const queryOps = {
26
26
  */
27
27
  const availableBetaFlags = {
28
28
  // enabled by --beta-mode
29
- annotationExpressions: true,
30
- odataPathsInAnnotationExpressions: true,
31
- odataAnnotationExpressions: true,
32
29
  hanaAssocRealCardinality: true,
33
30
  mapAssocToJoinCardinality: true, // only SAP HANA HEX engine supports it
34
31
  enableUniversalCsn: true,
@@ -39,8 +36,10 @@ const availableBetaFlags = {
39
36
  tenantVariable: true,
40
37
  calcAssoc: true,
41
38
  v5preview: true,
39
+ temporalRawProjection: true,
42
40
  // disabled by --beta-mode
43
41
  nestedServices: false,
42
+ rewriteAnnotationExpressionsViaType: false,
44
43
  };
45
44
 
46
45
  // Used by isDeprecatedEnabled() to check if any flag ist set.
@@ -81,7 +80,7 @@ const oldDeprecatedFlags_v2 = [
81
80
  *
82
81
  * A feature always needs to be provided - otherwise false will be returned.
83
82
  *
84
- * Please do not move this function to the "option processor" code.
83
+ * Do not move this function to the "option processor" code.
85
84
  *
86
85
  * @param {object} options Options
87
86
  * @param {string} feature Feature to check for
@@ -101,7 +100,7 @@ function isBetaEnabled( options, feature ) {
101
100
  * Useful for newer functionality which might not work with some
102
101
  * deprecated feature turned on.
103
102
  *
104
- * Please do not move this function to the "option processor" code.
103
+ * Do not move this function to the "option processor" code.
105
104
  *
106
105
  * @param {object} options Options
107
106
  * @param {string|null} [feature] Feature to check for
@@ -53,8 +53,8 @@ function checkActionOrFunction( art, artName, prop, path ) {
53
53
  Object.entries(params).forEach(([ pn, p ], i) => {
54
54
  const type = p.items?.type || p.type;
55
55
  if (type === '$self' && !this.csn.definitions.$self && i > 0) {
56
- this.error(null, currPath.concat(pn),
57
- 'Binding parameter is expected to appear on first position only');
56
+ this.error('def-invalid-param', currPath.concat(pn),
57
+ 'Binding parameter is expected to appear in first position only');
58
58
  }
59
59
  });
60
60
  }
@@ -37,6 +37,7 @@ function checkCoreMediaTypeAllowance( member ) {
37
37
  function checkAnalytics( member ) {
38
38
  if (member['@Analytics.Measure'] && !member['@Aggregation.default']) {
39
39
  this.info(null, member.$path, {},
40
+ // eslint-disable-next-line cds-compiler/message-no-quotes
40
41
  'Annotation “@Analytics.Measure” expects “@Aggregation.default” to be assigned for the same element as well');
41
42
  }
42
43
  }
@@ -63,6 +64,7 @@ function checkReadOnlyAndInsertOnly( artifact, artifactName ) {
63
64
  if (!this.csnUtils.getServiceName(artifactName))
64
65
  return;
65
66
  if (artifact.kind === 'entity' && artifact['@readonly'] && artifact['@insertonly'])
67
+ // eslint-disable-next-line cds-compiler/message-no-quotes
66
68
  this.warning(null, artifact.$path, {}, 'Annotations “@readonly” and “@insertonly” can\'t be assigned in combination');
67
69
  }
68
70
 
@@ -93,6 +95,7 @@ function checkTemporalAnnotationsAssignment( artifact, artifactName ) {
93
95
 
94
96
  // if @cds.valid.key is defined, check whether @cds.valid.from and @cds.valid.to are also there
95
97
  if (valid.key.length && !(valid.from.length && valid.to.length))
98
+ // eslint-disable-next-line cds-compiler/message-no-quotes
96
99
  this.error(null, [ 'definitions', artifactName ], 'Annotation “@cds.valid.key” was used but “@cds.valid.from” and “@cds.valid.to” are missing');
97
100
 
98
101
  /**
@@ -23,6 +23,7 @@ function validateDefaultValues( member, memberName, prop, path ) {
23
23
  // TODO: This check only counts the number of leading signs, not inbetween (e.g. 1 - - 1).
24
24
  // The message also needs to be improved.
25
25
  if (i > 1)
26
+ // eslint-disable-next-line cds-compiler/message-no-quotes
26
27
  this.error(null, path, {}, 'Illegal number of unary ‘+’/‘-’ operators');
27
28
  }
28
29
  }
@@ -37,8 +38,10 @@ function validateDefaultValues( member, memberName, prop, path ) {
37
38
  * @param {CSN.Path} path Path to the member
38
39
  */
39
40
  function rejectParamDefaultsInHanaCds( member, memberName, prop, path ) {
40
- if (member.default && prop === 'params' && this.options.transformation === 'hdbcds')
41
- this.error(null, path, {}, 'Parameter default values are not supported in SAP HANA CDS');
41
+ if (member.default && prop === 'params' && this.options.transformation === 'hdbcds') {
42
+ this.error('def-unsupported-param', path, {},
43
+ 'Parameter default values are not supported in SAP HANA CDS');
44
+ }
42
45
  }
43
46
 
44
47
  /**
@@ -167,12 +167,13 @@ function _checkRef( ref, _links, $path, inColumns ) {
167
167
  const cdsPersistenceSkipped = hasAnnotationValue(targetArt, '@cds.persistence.skip');
168
168
  this.error( null, $path, {
169
169
  '#': cdsPersistenceSkipped ? 'std' : 'abstract',
170
+ anno: '@cds.persistence.skip',
170
171
  id: nonPersistedTarget.pathStep,
171
172
  elemref: { ref },
172
173
  name: nonPersistedTarget.name,
173
174
  }, {
174
- std: 'Unexpected “@cds.persistence.skip” annotation on association target $(NAME) of $(ID) in path $(ELEMREF)',
175
- abstract: 'Unexpected abstract association target $(NAME) of $(ID) in path $(ELEMREF)',
175
+ std: 'Unexpected $(ANNO) annotation on association target $(NAME) of $(ID) in path $(ELEMREF)',
176
+ abstract: 'Unexpected abstract association target $(NAME) of $(ID) in path $(ELEMREF)',
176
177
  } );
177
178
  break; // only one error per path
178
179
  }
@@ -3,7 +3,7 @@
3
3
  const {
4
4
  forEachDefinition, forEachMemberRecursively, forAllQueries,
5
5
  forEachMember, getNormalizedQuery, hasAnnotationValue,
6
- applyTransformations, functionList,
6
+ applyTransformations, functionList, mergeTransformers,
7
7
  } = require('../model/csnUtils');
8
8
  const enrichCsn = require('./enricher');
9
9
 
@@ -156,7 +156,7 @@ function _validate( csn, that,
156
156
  // TODO: Don't know if that's feasible? Do we really need to enrich annotations always?
157
157
  // const { cleanup } = enrich(csn, { processAnnotations: that.options.tranformation === 'odata' });
158
158
 
159
- applyTransformations(csn, mergeCsnValidators(csnValidators, that), [], { drillRef: true });
159
+ applyTransformations(csn, mergeTransformers(csnValidators, that), [], { drillRef: true });
160
160
 
161
161
  forEachDefinition(csn, (artifact, artifactName, prop, path) => {
162
162
  artifactValidators.forEach((artifactValidator) => {
@@ -180,38 +180,6 @@ function _validate( csn, that,
180
180
  return cleanup;
181
181
  }
182
182
 
183
- /**
184
- * Ensure the CSN validators adhere to the applyTransformation format - also, supply correct this value for each subfunction
185
- *
186
- * @param {object[]} csnValidators Validators
187
- * @param {object} that Value for this
188
- * @returns {object} Remapped validators.
189
- */
190
- function mergeCsnValidators( csnValidators, that ) {
191
- const remapped = {};
192
- for (const validator of csnValidators) {
193
- for (const [ n, fns ] of Object.entries(validator)) {
194
- if (!remapped[n])
195
- remapped[n] = [];
196
-
197
- if (Array.isArray(fns)) {
198
- remapped[n].push((parent, name, prop, path) => fns.forEach(
199
- fn => fn.bind(that)(parent, name, prop, path)
200
- ));
201
- }
202
- else {
203
- remapped[n].push((parent, name, prop, path) => fns.bind(that)(parent, name, prop, path));
204
- }
205
- }
206
- }
207
-
208
- for (const [ n, fns ] of Object.entries(remapped))
209
- remapped[n] = (parent, name, prop, path) => fns.forEach(fn => fn.bind(that)(parent, name, prop, path));
210
-
211
-
212
- return remapped;
213
- }
214
-
215
183
  /**
216
184
  * Depending on the dialect we need to run different validations.
217
185
  *
@@ -129,6 +129,7 @@ function assertConsistency( model, stage ) {
129
129
  ],
130
130
  instanceOf: XsnSource,
131
131
  },
132
+ tokenIndex: { test: isNumber },
132
133
  location: {
133
134
  // every thing with a $location in CSN must have a XSN location even
134
135
  // with syntax errors (currently even internal artifacts like $using):
@@ -136,7 +137,10 @@ function assertConsistency( model, stage ) {
136
137
  kind: true,
137
138
  instanceOf: Location,
138
139
  requires: [ 'file' ], // line is optional in top-level location
139
- optional: [ 'line', 'col', 'endLine', 'endCol', '$notFound' ],
140
+ optional: [
141
+ 'line', 'col', 'endLine', 'endCol', '$notFound',
142
+ 'tokenIndex', // in parser for $lsp
143
+ ],
140
144
  schema: {
141
145
  line: { test: isNumber },
142
146
  col: { test: isNumber },
@@ -203,7 +207,7 @@ function assertConsistency( model, stage ) {
203
207
  optional: [
204
208
  'elements', '$autoElement', '$uncheckedElements', '_origin', '_extensions',
205
209
  '$requireElementAccess', '_effectiveType', '$effectiveSeqNo', '_deps',
206
- '$calcDepElement', '$filtered', '_parent',
210
+ '$calcDepElement', '$filtered', '$enclosed', '_parent',
207
211
  ],
208
212
  schema: {
209
213
  kind: { test: isString, enum: [ 'builtin' ] },
@@ -232,7 +236,7 @@ function assertConsistency( model, stage ) {
232
236
  test: (model.$frontend !== 'json') ? standard : TODO,
233
237
  // TODO: the JSON parser should augment 'namespace' correctly or better: hide it
234
238
  requires: [ 'location' ],
235
- optional: [ 'path' ],
239
+ optional: [ 'kind', 'name' ],
236
240
  },
237
241
  usings: {
238
242
  test: isArray(),
@@ -257,6 +261,7 @@ function assertConsistency( model, stage ) {
257
261
  foreignKeys: { kind: true, inherits: 'definitions', instanceOf: 'ignore' },
258
262
  $keysNavigation: { kind: true, test: TODO },
259
263
  $filtered: { kind: true, inherits: 'value' }, // for assoc+filter
264
+ $enclosed: { kind: true, inherits: 'value' }, // for comp+filter
260
265
  params: { kind: true, inherits: 'definitions' },
261
266
  _extendType: { kind: true, test: TODO },
262
267
  mixin: { inherits: 'definitions' },
@@ -515,11 +520,13 @@ function assertConsistency( model, stage ) {
515
520
  '_effectiveType', '$effectiveSeqNo', '_origin', '_deps',
516
521
  // CSN parser may let these properties slip through to XSN, even if input is invalid.
517
522
  'args', 'op', 'func', 'suffix',
523
+ '$invalidPaths',
518
524
  ],
519
525
  // TODO: name requires if not in parser?
520
526
  },
521
527
  $priority: { test: isOneOf( [ undefined, false, 'extend', 'annotate' ] ) },
522
528
  $annotations: { parser: true, kind: true, test: TODO }, // deprecated, still there for cds-lsp
529
+ $invalidPaths: { test: isBoolean },
523
530
  name: {
524
531
  isRequired: stageParser && (() => false), // not required in parser
525
532
  kind: true,
@@ -19,6 +19,7 @@ const {
19
19
  } = require('../base/model');
20
20
  const { CompilerAssertion } = require('../base/error');
21
21
  const { typeParameters } = require('./builtins');
22
+ const { propagationRules } = require('../base/builtins');
22
23
 
23
24
  const $location = Symbol.for( 'cds.$location' );
24
25
 
@@ -69,6 +70,13 @@ function check( model ) {
69
70
  } );
70
71
  }
71
72
 
73
+ function* iterateAnnotations( art ) {
74
+ for (const prop in art) {
75
+ if (prop.charAt(0) === '@')
76
+ yield prop;
77
+ }
78
+ }
79
+
72
80
  function checkGenericConstruct( art ) {
73
81
  checkName( art );
74
82
  checkTypeArguments( art );
@@ -76,11 +84,9 @@ function check( model ) {
76
84
  if (art.value && !art.$calcDepElement && art.type)
77
85
  checkTypeCast( art.value, art );
78
86
 
79
- if (model.vocabularies) {
80
- Object.keys( art )
81
- .filter( a => a.startsWith( '@' ) )
82
- .forEach( a => checkAnnotationAssignment1( art, art[a] ) );
83
- }
87
+ for (const anno of iterateAnnotations( art ))
88
+ checkAnnotationAssignment1( art, art[anno] );
89
+
84
90
  checkTypeStructure( art );
85
91
  checkAssociation( art ); // type def could be assoc
86
92
  if (art.kind === 'enum')
@@ -236,6 +242,8 @@ function check( model ) {
236
242
  ? xpr.args[0]?._artifact
237
243
  : xpr._artifact;
238
244
  const type = isCast ? xpr.type : user.type;
245
+ if (!isCast && type.$inferred)
246
+ return; // e.g. $inferred:'generated'
239
247
  if (elem && type) { // has explicit type
240
248
  if (type._artifact?.elements)
241
249
  error( 'type-invalid-cast', [ type.location, user ], { '#': 'to-structure' } );
@@ -264,9 +272,11 @@ function check( model ) {
264
272
  // "key" keyword at localized element in SELECT list.
265
273
  // TODO: not in inferred elements, but also inside aspects
266
274
  if (elem.key?.val && elem._main?.query) {
275
+ // either the element was casted to localized (no `_origin`) or
267
276
  // original element is localized but not key, as that would have
268
277
  // already resulted in a warning by localized.js
269
- if (elem._origin?.localized?.val && !elem._origin.key?.val) {
278
+ if ((!elem._origin && elem.localized?.val) ||
279
+ (elem._origin?.localized?.val && !elem._origin.key?.val)) {
270
280
  warning( 'def-ignoring-localized', [ elem.key.location, elem ], { keyword: 'localized' },
271
281
  'Keyword $(KEYWORD) is ignored for primary keys' );
272
282
  }
@@ -860,21 +870,37 @@ function check( model ) {
860
870
  return false;
861
871
  }
862
872
 
873
+ /**
874
+ * Returns true if the given annotation accepts expressions as values.
875
+ *
876
+ * @param {object} anno
877
+ * @param {XSN.Artifact} art
878
+ * @returns {boolean}
879
+ */
880
+ function checkAnnotationAcceptsExpressions( anno, art ) {
881
+ const name = anno.name?.id;
882
+ if (!name)
883
+ return true;
884
+ if (!propagationRules[`@${ name }`])
885
+ return true;
886
+ error( 'anno-unexpected-expr', [ anno.location, art ], { anno: name },
887
+ 'Unexpected expression as value for $(ANNO)' );
888
+ return false;
889
+ }
863
890
 
864
- // Former checkAnnotationAssignments.js ------------------------------------
865
-
866
- // Check the annotation assignments (if any) of 'annotatable', possibly using annotation
867
- // definitions from 'model'. Report errors on 'options.messages.
868
- //
869
- // TODO: rework completely!
870
- // TODO: if we have such a check, consider #variant, anno.@anno, anno@anno
871
-
872
- // Has been slightly adapted for model.vocabularies but comments need to be
873
- // adapted, etc.
874
891
  function checkAnnotationAssignment1( art, anno ) {
875
- if (art.$contains?.$annotation)
876
- checkAnnotationExpressions( anno, art );
892
+ if (art.$contains?.$annotation && anno.kind === '$annotation') {
893
+ if (checkAnnotationAcceptsExpressions( anno, art ))
894
+ checkAnnotationExpressions( anno, art );
895
+ }
896
+
897
+ if (!model.vocabularies)
898
+ return;
877
899
 
900
+ // Has been slightly adapted for model.vocabularies but comments need to be
901
+ // adapted, etc.
902
+ // TODO: rework completely!
903
+ // TODO: if we have such a check, consider #variant, anno.@anno, anno@anno
878
904
  // Sanity checks (ignore broken assignments)
879
905
  if (!anno.name?.id)
880
906
  return;
@@ -213,6 +213,12 @@ function define( model ) {
213
213
  dictForEach( model.$collectedExtensions, e => e._extensions.forEach( initExtension ) );
214
214
 
215
215
  addI18nBlocks();
216
+
217
+ const { $self } = model.definitions;
218
+ if ($self) {
219
+ message( 'name-deprecated-$self', [ $self.location, $self ], { name: '$self' },
220
+ 'Do not use $(NAME) as name for an artifact definition' );
221
+ }
216
222
  }
217
223
 
218
224
  // Phase 1: ----------------------------------------------------------------
@@ -228,14 +234,15 @@ function define( model ) {
228
234
  if (!src.kind)
229
235
  src.kind = 'source';
230
236
 
231
- let { namespace } = src;
237
+ let namespace = src.namespace?.name;
232
238
  let prefix = '';
233
239
  if (namespace?.path && !namespace.path.broken) {
234
240
  namespace.id = pathName( namespace.path );
235
241
  prefix = `${ namespace.id }.`;
236
242
  }
237
243
  if (isInReservedNamespace( prefix )) {
238
- error( 'reserved-namespace-cds', [ src.namespace.location, src.namespace ], { name: 'cds' },
244
+ error( 'reserved-namespace-cds', [ src.namespace.name.location, src.namespace.name ],
245
+ { name: 'cds' },
239
246
  'The namespace $(NAME) is reserved for CDS builtins' );
240
247
  namespace = null;
241
248
  }
@@ -523,7 +530,7 @@ function define( model ) {
523
530
  if (src.$frontend && src.$frontend !== 'cdl')
524
531
  return;
525
532
  if (src.namespace) {
526
- const decl = src.namespace;
533
+ const decl = src.namespace.name;
527
534
  if (!decl.id) // parsing may have failed
528
535
  return;
529
536
  if (!model.definitions[decl.id]) {
@@ -562,8 +569,8 @@ function define( model ) {
562
569
  initArtifactParentLink( art, model.definitions );
563
570
  const block = art._block;
564
571
  checkRedefinition( art );
565
- initMembers( art, art, block );
566
572
  initDollarSelf( art ); // $self
573
+ initMembers( art, art, block );
567
574
  if (art.params)
568
575
  initDollarParameters( art ); // $parameters
569
576
 
@@ -636,6 +643,7 @@ function define( model ) {
636
643
  name: { id: '$parameters' },
637
644
  kind: '$parameters',
638
645
  location: art.location,
646
+ deprecated: true, // hide in code completion
639
647
  };
640
648
  setLink( parameters, '_parent', art );
641
649
  setLink( parameters, '_main', art );
@@ -679,8 +687,14 @@ function define( model ) {
679
687
  setLink( self, '_origin', query );
680
688
  setLink( self, '_parent', query );
681
689
  setLink( self, '_main', query._main );
690
+
691
+ const projection = { ...self, deprecated: true }; // hide in code completion
692
+ setLink( projection, '_origin', query );
693
+ setLink( projection, '_parent', query );
694
+ setLink( projection, '_main', query._main );
695
+
682
696
  query.$tableAliases.$self = self;
683
- query.$tableAliases.$projection = self;
697
+ query.$tableAliases.$projection = projection;
684
698
  }
685
699
  initSubQuery( query ); // check for SELECT clauses after from / mixin
686
700
  }
@@ -858,12 +872,13 @@ function define( model ) {
858
872
  }
859
873
 
860
874
  function initMixins( query, art ) {
861
- // TODO: re-check if mixins have already duplicates
862
- for (const name in query.mixin) {
863
- const mixin = query.mixin[name];
875
+ forEachInOrder( query, 'mixin', initMixin );
876
+
877
+ function initMixin( mixin, name ) {
878
+ setLink( mixin, '_block', art._block );
879
+ setMemberParent( mixin, name, query );
880
+ checkRedefinition( mixin );
864
881
  if (!(mixin.$duplicates)) {
865
- setMemberParent( mixin, name, query );
866
- setLink( mixin, '_block', art._block );
867
882
  // TODO: do some initMembers() ? If people had annotation
868
883
  // assignments on the mixin... (also for future mixin definitions
869
884
  // with generated values)
@@ -924,14 +939,14 @@ function define( model ) {
924
939
  setLink( col, '_block', parent._block );
925
940
  if (col.inline) { // `@anno elem.{ * }` does not work
926
941
  if (col.doc) {
927
- warning( 'syntax-ignoring-anno', [ col.doc.location, col ],
942
+ message( 'syntax-ignoring-anno', [ col.doc.location, col ],
928
943
  { '#': 'doc', code: '.{ ‹inline› }' } );
929
944
  }
930
945
  // col.$annotations no available for CSN input, have to search.
931
- // Warning about first annotation should be enough to avoid spam.
946
+ // Message about first annotation should be enough to avoid spam.
932
947
  const firstAnno = Object.keys( col ).find( key => key.startsWith( '@' ) );
933
948
  if (firstAnno) {
934
- warning( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ],
949
+ message( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ],
935
950
  { code: '.{ ‹inline› }' } );
936
951
  }
937
952
  }
@@ -1009,15 +1024,11 @@ function define( model ) {
1009
1024
  const { targetAspect } = obj;
1010
1025
  if (targetAspect) {
1011
1026
  if (obj.foreignKeys) {
1012
- error( 'type-unexpected-foreign-keys', [ obj.foreignKeys[$location], construct ],
1013
- {},
1014
- 'A managed aspect composition can\'t have a foreign keys specification' );
1027
+ error( 'type-unexpected-foreign-keys', [ obj.foreignKeys[$location], construct ] );
1015
1028
  delete obj.foreignKeys; // continuation semantics: not specified
1016
1029
  }
1017
1030
  if (obj.on && !obj.target) {
1018
- error( 'type-unexpected-on-condition', [ obj.on.location, construct ],
1019
- {},
1020
- 'A managed aspect composition can\'t have a specified ON-condition' );
1031
+ error( 'type-unexpected-on-condition', [ obj.on.location, construct ] );
1021
1032
  delete obj.on; // continuation semantics: not specified
1022
1033
  }
1023
1034
  if (targetAspect.elements)
@@ -1151,7 +1162,7 @@ function define( model ) {
1151
1162
  checkRedefinition( elem );
1152
1163
  initMembers( elem, elem, bl, initExtensions );
1153
1164
  if (boundSelfParamType && (elem.kind === 'action' || elem.kind === 'function'))
1154
- initBoundSelfParam( elem.params );
1165
+ initBoundSelfParam( elem.params, elem._main );
1155
1166
 
1156
1167
  // for a correct home path, setMemberParent needed to be called
1157
1168
 
@@ -1167,8 +1178,8 @@ function define( model ) {
1167
1178
  // TODO: it is not just "syntax" - maybe better test for `$calcDepElement`?
1168
1179
  createAndLinkCalcDepElement( elem );
1169
1180
 
1170
- // Special case (hack) for calculated elements that use associations+filter:
1171
- // See "Notes on `$filtered`" in `ExposingAssocWithFilter.md` for details.
1181
+ // Special case (hack) for calculated elements that use composition+filter:
1182
+ // See "Notes on `$enclosed`" in `ExposingAssocWithFilter.md` for details.
1172
1183
  if (elem.target && elem.value.path?.[elem.value.path.length - 1]?.where) {
1173
1184
  delete elem.type;
1174
1185
  delete elem.on;
@@ -1177,27 +1188,24 @@ function define( model ) {
1177
1188
  }
1178
1189
  }
1179
1190
 
1180
- function initBoundSelfParam( params ) {
1191
+ function initBoundSelfParam( params, main ) {
1181
1192
  if (!params)
1182
1193
  return;
1183
1194
  if (boundSelfParamType === true) { // first try
1184
1195
  const def = model.definitions.$self;
1185
1196
  if (def) {
1186
- // TODO v5: bring this always, probably even as error
1187
- warning( 'name-deprecated-for-artifacts', [ def.location, def ], { name: '$self' },
1188
- 'Do not use $(NAME) as name for an artifact definition' );
1189
1197
  boundSelfParamType = false;
1190
1198
  return;
1191
1199
  }
1192
- const location = { file: '' };
1193
- boundSelfParamType = { name: { id: '$self', location }, location };
1200
+ boundSelfParamType = '$self';
1194
1201
  }
1195
1202
  const first = params[Object.keys( params )[0] || ''];
1196
1203
  const type = first?.type || first?.items?.type; // this sequence = no derived type
1197
1204
  const path = type?.path;
1198
1205
  if (path?.length === 1 && path[0]?.id === '$self') { // TODO: no where: ?
1199
- setLink( type, '_artifact', boundSelfParamType );
1200
- setLink( path[0], '_artifact', boundSelfParamType );
1206
+ const { $self } = main.$tableAliases;
1207
+ setLink( type, '_artifact', $self );
1208
+ setLink( path[0], '_artifact', $self );
1201
1209
  }
1202
1210
  }
1203
1211
 
@@ -9,6 +9,7 @@ const {
9
9
  forEachDefinition,
10
10
  forEachMember,
11
11
  isDeprecatedEnabled,
12
+ isBetaEnabled,
12
13
  } = require('../base/model');
13
14
  const { dictAdd, pushToDict } = require('../base/dictionaries');
14
15
  const { kindProperties, dictKinds } = require('./base');
@@ -71,6 +72,7 @@ function extend( model ) {
71
72
  } );
72
73
 
73
74
  const includesNonShadowedFirst = isDeprecatedEnabled( model.options, 'includesNonShadowedFirst' );
75
+ const isV5preview = isBetaEnabled( model.options, 'v5preview' );
74
76
 
75
77
  sortModelSources();
76
78
  const extensionsDict = Object.create( null ); // TODO TMP
@@ -222,6 +224,7 @@ function extend( model ) {
222
224
  code: 'extend … with definitions',
223
225
  keyword: 'extend service',
224
226
  };
227
+ // TODO(v5): Discuss: make this an error?
225
228
  warning( 'ext-invalid-kind', [ loc, ext ], msgArgs, {
226
229
  std: 'Artifact $(ART) is not of kind $(KIND), use $(CODE) instead',
227
230
  annotate: 'There is no artifact $(ART), use $(CODE) instead',
@@ -818,7 +821,7 @@ function extend( model ) {
818
821
  annotate[prop] = art[prop];
819
822
  }
820
823
  }
821
- if (extensions.length === 1) { // i.e. no proper location if from more than one extensions
824
+ if (extensions.length === 1) { // i.e. no proper location if from more than one extension
822
825
  annotate.location = extensions[0].location;
823
826
  annotate.name.location = extensions[0].name.location;
824
827
  }
@@ -829,18 +832,38 @@ function extend( model ) {
829
832
  }
830
833
 
831
834
  function checkRemainingMainExtensions( art, ext, localized ) {
832
- if (localized) // TODO v5: ignore only for annotate
835
+ if (localized) {
836
+ if (isV5preview && ext.kind === 'extend') {
837
+ // In v5, reject any `extend` on localized.
838
+ error( 'ref-undefined-art', [ ext.location || ext.name.location, ext ],
839
+ { '#': 'localized', keyword: 'annotate' } );
840
+ }
833
841
  return;
842
+ }
843
+
834
844
  if (!resolvePath( ext.name, ext.kind, ext )) // error for extend, info for annotate
835
845
  return;
836
- if (art?.kind === 'namespace') {
837
- // TODO: not at all different to having no definition
838
- info( 'anno-namespace', [ ext.name.location, ext ], {}, // TODO: better location?
839
- 'Namespaces can\'t be annotated' );
840
- }
841
- else if (art?.builtin) {
842
- info( 'anno-builtin', [ ext.name.location, ext ], {}, // TODO: better location?
843
- 'Builtin types should not be annotated. Use custom type instead' );
846
+
847
+ if (art?.builtin) {
848
+ info( 'anno-builtin', [ ext.name.location, ext ], {} ); // TODO: better location?
849
+ }
850
+ else if (art?.kind === 'namespace') {
851
+ const hasAnnotations = Object.keys(ext).find(a => a.charAt(0) === '@');
852
+ const firstAnno = ext[hasAnnotations];
853
+ // In v5, extending namespaces is only allowed for `extend with definitions`.
854
+ // Neither annotations nor other extensions are allowed.
855
+ // Non-artifact extensions are reported in resolvePath() already (for v5).
856
+ if ((hasAnnotations || !ext.artifacts) ) {
857
+ if (isV5preview) {
858
+ error( 'ref-undefined-art', [ (firstAnno?.name || ext.name).location, ext ], {
859
+ '#': 'namespace', art: ext,
860
+ } );
861
+ }
862
+ else {
863
+ info( 'anno-namespace', [ (firstAnno?.name || ext.name).location, ext ], {},
864
+ 'Namespaces can\'t be annotated nor extended' );
865
+ }
866
+ }
844
867
  }
845
868
  }
846
869
 
@@ -56,7 +56,6 @@ class InvocationError extends Error {
56
56
  super( ...args );
57
57
  this.code = 'ERR_CDS_COMPILER_INVOCATION';
58
58
  this.errors = errs;
59
- this.hasBeenReported = false;
60
59
  }
61
60
  }
62
61
 
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ // Placeholder for future LSP API.
4
+
5
+ module.exports = {};
@@ -617,8 +617,6 @@ function populate( model ) {
617
617
 
618
618
  function resolveTabRef( alias ) {
619
619
  // effectiveType() must not be called on $self, is unnecessary for mixins:
620
- // TODO: have a test for `select from E { a, $self.a as b, $self.{ b as c } }`
621
- // TODO: have a negative test for `select from E { $self.*, assoc.* }`
622
620
  // (we might have those already)
623
621
  if (alias.kind === 'mixin' || alias.kind === '$self')
624
622
  return;