@sap/cds-compiler 4.9.2 → 5.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/bin/cds_remove_invalid_whitespace.js +2 -1
  3. package/bin/cdsc.js +15 -11
  4. package/bin/cdshi.js +1 -0
  5. package/doc/CHANGELOG_BETA.md +7 -0
  6. package/lib/api/main.js +7 -19
  7. package/lib/api/options.js +5 -11
  8. package/lib/api/trace.js +0 -1
  9. package/lib/base/builtins.js +1 -0
  10. package/lib/base/location.js +4 -1
  11. package/lib/base/message-registry.js +29 -29
  12. package/lib/base/messages.js +22 -26
  13. package/lib/base/model.js +0 -2
  14. package/lib/base/node-helpers.js +0 -1
  15. package/lib/checks/enricher.js +1 -5
  16. package/lib/checks/structuredAnnoExpressions.js +30 -0
  17. package/lib/checks/validator.js +8 -0
  18. package/lib/compiler/assert-consistency.js +4 -1
  19. package/lib/compiler/base.js +1 -1
  20. package/lib/compiler/builtins.js +18 -2
  21. package/lib/compiler/checks.js +2 -5
  22. package/lib/compiler/define.js +7 -7
  23. package/lib/compiler/extend.js +68 -33
  24. package/lib/compiler/generate.js +1 -1
  25. package/lib/compiler/index.js +23 -6
  26. package/lib/compiler/lsp-api.js +501 -2
  27. package/lib/compiler/populate.js +2 -2
  28. package/lib/compiler/propagator.js +1 -4
  29. package/lib/compiler/resolve.js +2 -15
  30. package/lib/compiler/shared.js +112 -31
  31. package/lib/compiler/tweak-assocs.js +2 -16
  32. package/lib/compiler/utils.js +2 -1
  33. package/lib/compiler/xsn-model.js +4 -0
  34. package/lib/edm/annotations/genericTranslation.js +95 -42
  35. package/lib/edm/csn2edm.js +16 -4
  36. package/lib/edm/edm.js +2 -3
  37. package/lib/edm/edmAnnoPreprocessor.js +1 -2
  38. package/lib/edm/edmPreprocessor.js +1 -7
  39. package/lib/gen/Dictionary.json +29 -2
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +2 -1
  42. package/lib/gen/languageParser.js +4995 -4817
  43. package/lib/json/csnVersion.js +1 -1
  44. package/lib/json/from-csn.js +4 -7
  45. package/lib/json/to-csn.js +23 -12
  46. package/lib/language/antlrParser.js +2 -2
  47. package/lib/language/errorStrategy.js +0 -1
  48. package/lib/language/genericAntlrParser.js +35 -12
  49. package/lib/language/multiLineStringParser.js +3 -2
  50. package/lib/language/textUtils.js +1 -0
  51. package/lib/main.d.ts +28 -9
  52. package/lib/main.js +7 -4
  53. package/lib/model/csnRefs.js +20 -4
  54. package/lib/model/csnUtils.js +0 -2
  55. package/lib/model/revealInternalProperties.js +1 -1
  56. package/lib/modelCompare/compare.js +1 -1
  57. package/lib/optionProcessor.js +28 -9
  58. package/lib/render/manageConstraints.js +1 -1
  59. package/lib/render/toCdl.js +36 -7
  60. package/lib/render/toSql.js +1 -0
  61. package/lib/render/utils/common.js +12 -9
  62. package/lib/render/utils/stringEscapes.js +1 -0
  63. package/lib/transform/db/applyTransformations.js +13 -8
  64. package/lib/transform/db/associations.js +62 -54
  65. package/lib/transform/db/constraints.js +23 -25
  66. package/lib/transform/db/expansion.js +1 -6
  67. package/lib/transform/db/flattening.js +89 -111
  68. package/lib/transform/db/temporal.js +3 -4
  69. package/lib/transform/db/views.js +0 -1
  70. package/lib/transform/draft/odata.js +51 -3
  71. package/lib/transform/effective/annotations.js +3 -2
  72. package/lib/transform/effective/flattening.js +135 -0
  73. package/lib/transform/effective/main.js +6 -6
  74. package/lib/transform/effective/types.js +13 -9
  75. package/lib/transform/forOdata.js +0 -2
  76. package/lib/transform/forRelationalDB.js +0 -19
  77. package/lib/transform/localized.js +7 -8
  78. package/lib/transform/odata/flattening.js +39 -31
  79. package/lib/transform/odata/typesExposure.js +5 -17
  80. package/lib/transform/transformUtils.js +1 -1
  81. package/lib/transform/translateAssocsToJoins.js +21 -3
  82. package/lib/utils/file.js +13 -7
  83. package/lib/utils/moduleResolve.js +59 -8
  84. package/lib/utils/term.js +3 -2
  85. package/package.json +7 -3
  86. package/share/messages/message-explanations.json +2 -0
  87. package/share/messages/type-unexpected-foreign-keys.md +52 -0
  88. package/share/messages/type-unexpected-on-condition.md +52 -0
@@ -73,8 +73,8 @@ function isDowngradable( messageId, moduleName, options ) {
73
73
  return false;
74
74
  if (msg.severity !== 'Error')
75
75
  return true;
76
- // v5 messages are downgradable (except if errorFor also contains the current module).
77
- if (msg.errorFor && msg.errorFor.includes('v5'))
76
+ // v6 messages are downgradable (except if errorFor also contains the current module).
77
+ if (msg.errorFor && msg.errorFor.includes('v6'))
78
78
  return true;
79
79
  const { configurableFor } = msg;
80
80
  return (Array.isArray( configurableFor ))
@@ -91,9 +91,11 @@ function isDowngradable( messageId, moduleName, options ) {
91
91
  function severityChangeMarker(msg, config) {
92
92
  const severity = msg.severity || 'Error';
93
93
  if (config.moduleForMarker) {
94
- if (severity === 'Error' && isDowngradable(msg.messageId, config.moduleForMarker, {}))
94
+ if (severity === 'Error' &&
95
+ isDowngradable( msg.messageId, config.moduleForMarker,
96
+ { deprecated: { downgradableErrors: true } }))
95
97
  return '‹↓›';
96
- if (msg.messageId && centralMessages[msg.messageId]?.errorFor?.includes('v5'))
98
+ if (msg.messageId && centralMessages[msg.messageId]?.errorFor?.includes('v6'))
97
99
  return '‹↑›';
98
100
  }
99
101
  return '';
@@ -107,12 +109,16 @@ function severityChangeMarker(msg, config) {
107
109
  class CompilationError extends Error {
108
110
  /**
109
111
  * @param {CompileMessage[]} messages
110
- * @param {boolean} [dontSerializeMessages] If true, compiler messages are NOT part of the errors message text.
111
112
  * @param {XSN.Model} [model] the XSN model, only to be set with options.attachValidNames
112
113
  */
113
- constructor(messages, model, dontSerializeMessages) {
114
- const msg = !dontSerializeMessages && messages?.map( m => m.toString() ).join('\n') || '';
115
- super( `CDS compilation failed\n${msg}` );
114
+ constructor(messages, model) {
115
+ // Because test frameworks such as mocha and jest to not call `toString()` on
116
+ // an unhandled CompilationError and instead use `e.stack` directly, there is
117
+ // no proper message about _what_ the root cause of the exception was.
118
+ // To mitigate that, we serialize the first error in the message as well.
119
+ const firstError = messages.find( m => m.severity === 'Error' )?.toString() || '';
120
+ super( `CDS compilation failed\n${firstError}` );
121
+
116
122
  /** @since v4.0.0 */
117
123
  this.code = 'ERR_CDS_COMPILATION_FAILURE';
118
124
  this.messages = [ ...messages ].sort(compareMessageSeverityAware);
@@ -240,7 +246,7 @@ function reclassifiedSeverity( msg, options, moduleName ) {
240
246
  if (errorFor.includes(moduleName))
241
247
  return 'Error';
242
248
 
243
- if (errorFor.includes('v5') && isBetaEnabled(options, 'v5preview')) {
249
+ if (errorFor.includes('v6') && isBetaEnabled(options, 'v6preview')) {
244
250
  severity = 'Error';
245
251
  if (!isDowngradable(msg.messageId, moduleName, options))
246
252
  return severity;
@@ -555,8 +561,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
555
561
 
556
562
  function throwWithError() {
557
563
  if (hasNewError) {
558
- const dontSerializeMessages = isBetaEnabled(options, 'v5preview');
559
- throw new CompilationError(messages, options.attachValidNames && model, dontSerializeMessages);
564
+ throw new CompilationError(messages, options.attachValidNames && model);
560
565
  }
561
566
  }
562
567
 
@@ -573,8 +578,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
573
578
  return;
574
579
  const hasError = options.testMode ? hasNonDowngradableErrors : hasErrors;
575
580
  if (hasError( messages, moduleName, options )) {
576
- const dontSerializeMessages = isBetaEnabled(options, 'v5preview');
577
- throw new CompilationError(messages, options.attachValidNames && model, dontSerializeMessages);
581
+ throw new CompilationError(messages, options.attachValidNames && model);
578
582
  }
579
583
  }
580
584
 
@@ -1018,10 +1022,6 @@ function replaceInString( text, params ) {
1018
1022
  * @param {boolean} [config.noMessageId]
1019
1023
  * If true, will _not_ show the message ID (+ explanation hint) in the output.
1020
1024
  *
1021
- * @param {boolean} [config.idInBrackets]
1022
- * If true, the message ID (if there is one and noMessageId is falsey) will be put in brackets.
1023
- * This will be the default in cds-compiler v5.
1024
- *
1025
1025
  * @param {boolean} [config.noHome]
1026
1026
  * If true, will _not_ show message's semantic location.
1027
1027
  *
@@ -1049,11 +1049,7 @@ function messageString( err, config ) {
1049
1049
  const downgradable = severityChangeMarker(err, config);
1050
1050
  // even with noHome, print err.home if the location is weak
1051
1051
  const home = !err.home || config.noHome && err.$location?.endLine ? '' : ` (in ${ err.home })`;
1052
-
1053
- let msgId = ''; // TODO(v5): Use brackets only
1054
- if (err.messageId && !config.noMessageId)
1055
- msgId = (config.idInBrackets) ? `[${err.messageId}]` : ` ${err.messageId}`;
1056
-
1052
+ const msgId = (err.messageId && !config.noMessageId) ? `[${err.messageId}]` : '';
1057
1053
  return `${ location }${ severity }${ downgradable }${ msgId }: ${ err.message }${ home }`;
1058
1054
  }
1059
1055
 
@@ -1065,7 +1061,7 @@ function messageString( err, config ) {
1065
1061
  * @returns {string} can be used to uniquely identify a message
1066
1062
  */
1067
1063
  function messageHash( msg ) {
1068
- // parser messages do not provide semantic location, therefore we need to use the file location
1064
+ // parser messages do not provide semantic location, therefore$ we need to use the file location
1069
1065
  if (!msg.home)
1070
1066
  return messageString(msg);
1071
1067
  const copy = { ...msg };
@@ -1118,7 +1114,7 @@ function messageHash( msg ) {
1118
1114
  * If true/'always', ANSI escape codes will be used for coloring the severity. If false/'never',
1119
1115
  * no coloring will be used. If 'auto', we will decide based on certain factors such
1120
1116
  * as whether the shell is a TTY and whether the environment variable `NO_COLOR` is
1121
- * unset.
1117
+ * unset or whether `FORCE_COLOR` is set.
1122
1118
  *
1123
1119
  * @returns {string}
1124
1120
  */
@@ -1285,7 +1281,7 @@ function _messageContext( err, config ) {
1285
1281
  * @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the `^`. If false, no
1286
1282
  * coloring will be used. If 'auto', we will decide based on certain factors such
1287
1283
  * as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
1288
- * unset.
1284
+ * unset or `FORCE_COLOR` is set.
1289
1285
  * @returns {string}
1290
1286
  *
1291
1287
  * @deprecated Use `messageStringMultiline()` with `config.sourceMap` and `config.sourceLineMap` instead!
@@ -1353,7 +1349,7 @@ function compareMessageSeverityAware( a, b ) {
1353
1349
  /**
1354
1350
  * Return sort-relevant part of semantic location (after the ':').
1355
1351
  * Messages without semantic locations are considered smaller (for syntax errors)
1356
- * and (currently - should not happen in v5) larger for other messages.
1352
+ * and (currently - should not happen in v6) larger for other messages.
1357
1353
  *
1358
1354
  * @param {CompileMessage} msg
1359
1355
  */
package/lib/base/model.js CHANGED
@@ -31,11 +31,9 @@ const availableBetaFlags = {
31
31
  enableUniversalCsn: true,
32
32
  odataTerms: true,
33
33
  optionalActionFunctionParameters: true, // not supported by runtime, yet.
34
- annotateForeignKeys: true,
35
34
  effectiveCsn: true,
36
35
  tenantVariable: true,
37
36
  calcAssoc: true,
38
- v5preview: true,
39
37
  temporalRawProjection: true,
40
38
  // disabled by --beta-mode
41
39
  nestedServices: false,
@@ -28,7 +28,6 @@ class PromiseAllError extends Error {
28
28
  }
29
29
  }
30
30
 
31
-
32
31
  module.exports = {
33
32
  promiseAllDoNotRejectImmediately,
34
33
  };
@@ -49,7 +49,6 @@ function enrichCsn( csn, options ) {
49
49
  dictionary( csn, 'definitions', csn.definitions );
50
50
  return { csn, cleanup };
51
51
 
52
- // eslint-disable-next-line jsdoc/require-jsdoc
53
52
  function standard( parent, prop, node ) {
54
53
  if (!node || typeof node !== 'object' || !{}.propertyIsEnumerable.call( parent, prop ))
55
54
  return;
@@ -69,7 +68,7 @@ function enrichCsn( csn, options ) {
69
68
  }
70
69
  csnPath.pop();
71
70
  }
72
- // eslint-disable-next-line jsdoc/require-jsdoc
71
+
73
72
  function dictionary( node, prop, dict ) {
74
73
  setProp(node, '$path', [ ...csnPath ]);
75
74
  cleanupCallbacks.push(() => delete node.$path);
@@ -115,7 +114,6 @@ function enrichCsn( csn, options ) {
115
114
  }
116
115
  }
117
116
 
118
- // eslint-disable-next-line jsdoc/require-jsdoc
119
117
  function columns( parent, prop, node ) {
120
118
  // Establish the link relationships
121
119
  parent[prop].forEach((column) => {
@@ -130,7 +128,6 @@ function enrichCsn( csn, options ) {
130
128
  standard(parent, prop, node);
131
129
  }
132
130
 
133
- // eslint-disable-next-line jsdoc/require-jsdoc
134
131
  function simpleRef( node, prop, ref ) {
135
132
  setProp(node, '$path', [ ...csnPath ]);
136
133
  cleanupCallbacks.push(() => delete node.$path);
@@ -158,7 +155,6 @@ function enrichCsn( csn, options ) {
158
155
  }
159
156
  }
160
157
 
161
- // eslint-disable-next-line jsdoc/require-jsdoc
162
158
  function pathRef( node, prop, path ) {
163
159
  const {
164
160
  links, art, scope, $env,
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ const { isBuiltinType } = require('../base/builtins');
4
+ const { transformExpression, applyTransformationsOnNonDictionary } = require('../model/csnUtils');
5
+ /**
6
+ *
7
+ * @param {object} member
8
+ */
9
+ function checkAnnotationExpression( member, _memberName, _prop, path ) {
10
+ Object.keys(member).filter(pn => pn[0] === '@').forEach((anno) => {
11
+ applyTransformationsOnNonDictionary(member, anno, {
12
+ xpr: (parent, prop, _xpr, xprPath) => {
13
+ transformExpression(parent, prop, {
14
+ ref: (elemref, __prop, ref, refPath) => {
15
+ const { art, scope } = this.csnUtils.inspectRef(refPath);
16
+ if (scope !== '$magic' && art) {
17
+ const ft = this.csnUtils.getFinalTypeInfo(art.type);
18
+ if (!isBuiltinType(ft?.type))
19
+ this.error('odata-anno-xpr-ref', refPath, { anno, elemref, '#': 'flatten_builtin_type' });
20
+ }
21
+ },
22
+ }, xprPath);
23
+ },
24
+ }, {}, path);
25
+ });
26
+ }
27
+
28
+ module.exports = {
29
+ checkAnnotationExpression,
30
+ };
@@ -15,6 +15,7 @@ const navigationIntoMany = require('./manyNavigations');
15
15
  const checkUsedTypesForAnonymousAspectComposition = require('./managedInType');
16
16
  const validateHasPersistedElements = require('./hasPersistedElements');
17
17
  const checkForHanaTypes = require('./checkForTypes');
18
+ const { checkAnnotationExpression } = require('./structuredAnnoExpressions');
18
19
  const checkForParams = require('./parameters');
19
20
  // forOdata
20
21
  const { validateDefaultValues } = require('./defaultValues');
@@ -210,6 +211,13 @@ function forRelationalDB( csn, that ) {
210
211
  duplicate messages due to the forEachMemberRecursively.
211
212
  TODO: check if this recursion can be factored out of the validator */
212
213
  forEachMember(artifact, checkUsedTypesForAnonymousAspectComposition.bind(that));
214
+ },
215
+ (artifact, artifactName) => {
216
+ if (that.options.transformation === 'effective') {
217
+ forEachMemberRecursively(artifact, checkAnnotationExpression.bind(that), [ 'definitions', artifactName ], false, {
218
+ skipArtifact: a => a.returns || (a.params && !a.query),
219
+ });
220
+ }
213
221
  }
214
222
  ),
215
223
  forRelationalDBQueryValidators.concat(commonQueryValidators),
@@ -208,6 +208,7 @@ function assertConsistency( model, stage ) {
208
208
  'elements', '$autoElement', '$uncheckedElements', '_origin', '_extensions',
209
209
  '$requireElementAccess', '_effectiveType', '$effectiveSeqNo', '_deps',
210
210
  '$calcDepElement', '$filtered', '$enclosed', '_parent',
211
+ 'deprecated', '$onlyInExprCtx',
211
212
  ],
212
213
  schema: {
213
214
  kind: { test: isString, enum: [ 'builtin' ] },
@@ -215,6 +216,8 @@ function assertConsistency( model, stage ) {
215
216
  $autoElement: { test: isString },
216
217
  $uncheckedElements: { test: isBoolean },
217
218
  $requireElementAccess: { test: isBoolean },
219
+ deprecated: { test: isBoolean },
220
+ $onlyInExprCtx: { test: TODO },
218
221
  // missing location for normal "elements"
219
222
  elements: { test: TODO },
220
223
  },
@@ -703,7 +706,7 @@ function assertConsistency( model, stage ) {
703
706
  'localized', // e.g. compiler-generated elements for localized: `text` assoc, etc.
704
707
  'localized-entity', // `.texts` entity
705
708
  'localized-origin', // `.texts` entity
706
- 'nav', // only used for MASKED, TODO(v5): Remove
709
+ 'nav', // only used for MASKED, TODO(v6): Remove
707
710
  'none', // only used in ensureColumnName(): Used in object representing empty alias
708
711
  'query', // inferred query properties, e.g. `key`
709
712
  'rewrite', // on-conditions or FKeys are rewritten
@@ -48,7 +48,7 @@ const kindProperties = {
48
48
  normalized: 'action',
49
49
  dict: 'actions',
50
50
  }, // no extend params, only annotate
51
- key: { normalized: 'element' },
51
+ key: { normalized: 'element', dict: 'elements' }, // dict for annotate
52
52
  param: { elements: () => false, enum: () => false, dict: 'params' },
53
53
  source: { artifacts: true }, // TODO -> $source
54
54
  using: {},
@@ -178,14 +178,15 @@ const magicVariables = {
178
178
  // Allow shortcut in CDL: `$user` becomes `$user.id` in CSN.
179
179
  $autoElement: 'id',
180
180
  },
181
- $at: {
181
+ $at: { // $at is considered deprecated since cds-compiler v5
182
182
  elements: {
183
183
  from: {}, to: {},
184
184
  },
185
185
  // Require that elements are accessed, i.e. no $at, only $at.<element>.
186
186
  $requireElementAccess: true,
187
+ deprecated: true, // $at is deprecated; use $valid
187
188
  },
188
- $valid: { // dito
189
+ $valid: {
189
190
  elements: {
190
191
  from: {}, to: {},
191
192
  },
@@ -200,6 +201,15 @@ const magicVariables = {
200
201
  $uncheckedElements: true,
201
202
  $requireElementAccess: true,
202
203
  },
204
+ $draft: {
205
+ elements: {
206
+ IsActiveEntity: {},
207
+ },
208
+ // Require that elements are accessed, i.e. no $draft, only $draft.<element>.
209
+ $requireElementAccess: true,
210
+ // See reference semantics in shared.js
211
+ $onlyInExprCtx: [ 'annotation', 'annoRewrite' ],
212
+ },
203
213
  };
204
214
 
205
215
  // see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP CDS via function
@@ -437,6 +447,10 @@ function initBuiltins( model ) {
437
447
  art.$uncheckedElements = magic.$uncheckedElements;
438
448
  if (magic.$requireElementAccess)
439
449
  art.$requireElementAccess = magic.$requireElementAccess;
450
+ if (magic.deprecated)
451
+ art.deprecated = magic.deprecated;
452
+ if (magic.$onlyInExprCtx)
453
+ art.$onlyInExprCtx = magic.$onlyInExprCtx;
440
454
 
441
455
  createMagicElements( art, magic.elements );
442
456
  if (options.variableReplacements?.[id])
@@ -461,6 +475,8 @@ function initBuiltins( model ) {
461
475
  // Propagate this property so that it is available for sub-elements.
462
476
  if (art.$uncheckedElements)
463
477
  magic.$uncheckedElements = art.$uncheckedElements;
478
+ if (art.$onlyInExprCtx)
479
+ magic.$onlyInExprCtx = art.$onlyInExprCtx;
464
480
  setProp( magic, '_parent', art );
465
481
  // setProp( magic, '_effectiveType', magic );
466
482
  if (elements[id] && typeof elements[id] === 'object')
@@ -54,9 +54,6 @@ function check( model ) {
54
54
  function checkEvent( def ) {
55
55
  // Ensure that events are structured. Up to compiler v4, we allowed non-structured events,
56
56
  // because when we introduced them, it was not fully specified what they are.
57
- // TODO(v5):
58
- // - Make this a (configurable) error; move to acceptTypeOrElement
59
- // - require type/elements to be set in parser
60
57
  if (def.kind === 'event' && !def._effectiveType?.elements && !def._effectiveType?.projection)
61
58
  message( 'def-expected-structured', [ (def.type || def.name).location, def ] );
62
59
  }
@@ -862,8 +859,8 @@ function check( model ) {
862
859
  // Tree-ish expression from the compiler (not augmented)
863
860
  // eslint-disable-next-line max-len
864
861
  return (isAssociationOperand( xpr.args[0] ) && isDollarSelfOrProjectionOperand( xpr.args[2] ) ||
865
- // eslint-disable-next-line max-len
866
- isAssociationOperand( xpr.args[2] ) && isDollarSelfOrProjectionOperand( xpr.args[0] ));
862
+ // eslint-disable-next-line max-len
863
+ isAssociationOperand( xpr.args[2] ) && isDollarSelfOrProjectionOperand( xpr.args[0] ));
867
864
  }
868
865
 
869
866
  // Nothing else qualifies
@@ -364,7 +364,7 @@ function define( model ) {
364
364
  // must be called after addUsing().
365
365
  function addNamespace( namespace, src ) {
366
366
  // create using for own namespace:
367
- // TODO: should we really do that in v5? See also initNamespaceAndUsing().
367
+ // TODO: should we really do that (in v6)? See also initNamespaceAndUsing().
368
368
  const last = namespace.path[namespace.path.length - 1];
369
369
  const { id } = last;
370
370
  if (src.artifacts[id] || last.id.includes( '.' ))
@@ -507,9 +507,10 @@ function define( model ) {
507
507
  // TODO: message ids
508
508
  function checkRedefinition( art ) {
509
509
  if (!art.$duplicates || !art.name.id ||
510
- art.$errorReported === 'syntax-duplicate-extend' ||
511
- art.$errorReported === 'syntax-duplicate-annotate')
510
+ art.$errorReported === 'syntax-duplicate-extend')
512
511
  return;
512
+ if (art.kind === 'annotate' || art.kind === 'extend')
513
+ return; // extensions are merged into a super-annotate; $duplicates are only kept for LSP
513
514
  if (art._main) {
514
515
  error( 'duplicate-definition', [ art.name.location, art ], {
515
516
  name: art.name.id,
@@ -637,7 +638,6 @@ function define( model ) {
637
638
  }
638
639
 
639
640
  function initDollarParameters( art ) {
640
- // TODO: remove $parameters in v5?
641
641
  // TODO: use setMemberParent() ?
642
642
  const parameters = {
643
643
  name: { id: '$parameters' },
@@ -939,14 +939,14 @@ function define( model ) {
939
939
  setLink( col, '_block', parent._block );
940
940
  if (col.inline) { // `@anno elem.{ * }` does not work
941
941
  if (col.doc) {
942
- message( 'syntax-ignoring-anno', [ col.doc.location, col ],
942
+ message( 'syntax-unexpected-anno', [ col.doc.location, col ],
943
943
  { '#': 'doc', code: '.{ ‹inline› }' } );
944
944
  }
945
945
  // col.$annotations no available for CSN input, have to search.
946
946
  // Message about first annotation should be enough to avoid spam.
947
947
  const firstAnno = Object.keys( col ).find( key => key.startsWith( '@' ) );
948
948
  if (firstAnno) {
949
- message( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ],
949
+ message( 'syntax-unexpected-anno', [ col[firstAnno].name.location, col ],
950
950
  { code: '.{ ‹inline› }' } );
951
951
  }
952
952
  }
@@ -1083,7 +1083,7 @@ function define( model ) {
1083
1083
  hasElement = true; // warning with CDL input or `name: {}` in CSN input
1084
1084
  }
1085
1085
  if (hasElement) {
1086
- // This message is similar to the one above. In v5/6, we could probably
1086
+ // This message is similar to the one above. In v6, we could probably
1087
1087
  // turn this warning into an error, remove `$syntax: 'element' (also in
1088
1088
  // language.g4), and use the above `ext-unexpected-element` only for CSN input.
1089
1089
  warning( 'ext-expecting-enum', [ obj.elements[$location], construct ],