@sap/cds-compiler 4.9.4 → 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 (87) hide show
  1. package/CHANGELOG.md +66 -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 +6 -18
  7. package/lib/api/options.js +3 -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/expansion.js +1 -6
  66. package/lib/transform/db/flattening.js +89 -111
  67. package/lib/transform/db/temporal.js +3 -4
  68. package/lib/transform/db/views.js +0 -1
  69. package/lib/transform/draft/odata.js +51 -3
  70. package/lib/transform/effective/annotations.js +3 -2
  71. package/lib/transform/effective/flattening.js +135 -0
  72. package/lib/transform/effective/main.js +6 -6
  73. package/lib/transform/effective/types.js +13 -9
  74. package/lib/transform/forOdata.js +0 -2
  75. package/lib/transform/forRelationalDB.js +0 -19
  76. package/lib/transform/localized.js +7 -8
  77. package/lib/transform/odata/flattening.js +39 -31
  78. package/lib/transform/odata/typesExposure.js +5 -17
  79. package/lib/transform/transformUtils.js +1 -1
  80. package/lib/transform/translateAssocsToJoins.js +21 -3
  81. package/lib/utils/file.js +13 -7
  82. package/lib/utils/moduleResolve.js +59 -8
  83. package/lib/utils/term.js +3 -2
  84. package/package.json +7 -3
  85. package/share/messages/message-explanations.json +2 -0
  86. package/share/messages/type-unexpected-foreign-keys.md +52 -0
  87. package/share/messages/type-unexpected-on-condition.md +52 -0
@@ -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 ],
@@ -8,8 +8,8 @@ const {
8
8
  forEachInOrder,
9
9
  forEachDefinition,
10
10
  forEachMember,
11
+ forEachGeneric,
11
12
  isDeprecatedEnabled,
12
- isBetaEnabled,
13
13
  } = require('../base/model');
14
14
  const { dictAdd, pushToDict } = require('../base/dictionaries');
15
15
  const { kindProperties, dictKinds } = require('./base');
@@ -30,7 +30,7 @@ const { Location } = require('../base/location');
30
30
 
31
31
  const $location = Symbol.for( 'cds.$location' );
32
32
 
33
- // attach stupid location - TODO: remove in v5
33
+ // attach stupid location - TODO: remove in v6
34
34
  const genLocation = new Location( '' );
35
35
 
36
36
  const draftElements = [
@@ -59,6 +59,7 @@ function extend( model ) {
59
59
  resolvePath,
60
60
  resolveUncheckedPath,
61
61
  resolveTypeArgumentsUnchecked,
62
+ resolveDefinitionName,
62
63
  attachAndEmitValidNames,
63
64
  initMembers,
64
65
  initSelectItems,
@@ -72,7 +73,6 @@ function extend( model ) {
72
73
  } );
73
74
 
74
75
  const includesNonShadowedFirst = isDeprecatedEnabled( model.options, 'includesNonShadowedFirst' );
75
- const isV5preview = isBetaEnabled( model.options, 'v5preview' );
76
76
 
77
77
  sortModelSources();
78
78
  const extensionsDict = Object.create( null ); // TODO TMP
@@ -161,6 +161,7 @@ function extend( model ) {
161
161
  moveReturnsExtensions( art, extensionsMap );
162
162
 
163
163
  if (art.returns) {
164
+ ensureArtifactNotProcessed( art.returns );
164
165
  pushToDict( art.returns, '_extensions', ...extensionsMap.elements || [] );
165
166
  pushToDict( art.returns, '_extensions', ...extensionsMap.enum || [] );
166
167
  if (art.kind !== 'annotate') {
@@ -171,16 +172,35 @@ function extend( model ) {
171
172
  }
172
173
  const sub = art.items || art.targetAspect?.elements && art.targetAspect;
173
174
  if (sub) {
175
+ ensureArtifactNotProcessed( sub );
174
176
  pushToDict( sub, '_extensions', ...extensionsMap.elements || [] );
175
177
  pushToDict( sub, '_extensions', ...extensionsMap.enum || [] );
176
178
  }
177
179
  else {
178
- moveDictExtensions( art, extensionsMap,
179
- (art.enum && art.kind !== 'annotate' ? 'enum' : 'elements'), 'elements' );
180
+ let elementsProp = 'elements';
181
+ if (art.kind !== 'annotate')
182
+ elementsProp = art.enum && 'enum' || art.foreignKeys && 'foreignKeys' || 'elements';
183
+ moveDictExtensions( art, extensionsMap, elementsProp, 'elements' );
180
184
  moveDictExtensions( art, extensionsMap, 'enum' );
181
185
  }
182
186
  }
183
187
 
188
+ /**
189
+ * Applying extensions is handled in extendArtifactAfter(). And only afterward,
190
+ * an effective sequence number is set. Meaning that if a sub-artifact already
191
+ * has a sequence number, then extensions would be lost.
192
+ */
193
+ function ensureArtifactNotProcessed( art ) {
194
+ if (!model.options.testMode)
195
+ return;
196
+
197
+ if (art.$effectiveSeqNo !== 0 && art.$effectiveSeqNo !== undefined) {
198
+ // if the artifact already has a sequence number, then
199
+ // extendArtifactAfter() was already called -> annotations would be lost.
200
+ throw new CompilerAssertion('artifact already processed; extensions would be lost');
201
+ }
202
+ }
203
+
184
204
  /**
185
205
  * Create super annotate statements for remaining extensions
186
206
  */
@@ -196,15 +216,25 @@ function extend( model ) {
196
216
  }
197
217
 
198
218
  // TODO: delete again - if not, what about extensions in contexts/services?
219
+ // Check test.lsp-api.js! Links in extensions are needed.
199
220
  function setArtifactLinkForExtensions( source ) {
200
221
  if (!source.extensions)
201
222
  return;
202
- for (const ext of source.extensions ) {
223
+ for (const ext of source.extensions) {
224
+ if (!ext.name?.id)
225
+ continue;
226
+
203
227
  const { name } = ext;
204
- if (name?.id && name._artifact === undefined) {
228
+ const { path } = name;
229
+ if (name._artifact === undefined) {
205
230
  const refCtx = (name.id.startsWith( 'localized.' )) ? '_extensions' : ext.kind;
206
231
  resolvePath( name, refCtx, ext ); // for LSP
207
232
  }
233
+ else if (model.options.lspMode && path?.[0]._artifact === undefined) {
234
+ // we don't use resolvePath(…,'extend'), as that would add a dependency
235
+ resolveDefinitionName( ext );
236
+ setArtifactLink( path[path.length - 1], name._artifact );
237
+ }
208
238
  }
209
239
  }
210
240
 
@@ -224,7 +254,7 @@ function extend( model ) {
224
254
  code: 'extend … with definitions',
225
255
  keyword: 'extend service',
226
256
  };
227
- // TODO(v5): Discuss: make this an error?
257
+ // TODO(v6): Discuss: make this an error?
228
258
  warning( 'ext-invalid-kind', [ loc, ext ], msgArgs, {
229
259
  std: 'Artifact $(ART) is not of kind $(KIND), use $(CODE) instead',
230
260
  annotate: 'There is no artifact $(ART), use $(CODE) instead',
@@ -393,6 +423,9 @@ function extend( model ) {
393
423
  for (const col of ext.columns)
394
424
  col.$extended = true;
395
425
 
426
+ if (art.kind === 'annotate' && art.$inferred === '')
427
+ return; // internal super-annotate for unknown artifacts
428
+
396
429
  if (!query?.from?.path) {
397
430
  const variant = (query?.from || query)?.op?.val || 'std';
398
431
  error( 'extend-columns', [ ext.columns[$location], ext ], { '#': variant, art } );
@@ -490,7 +523,7 @@ function extend( model ) {
490
523
  if ('val' in upToSpec) {
491
524
  if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
492
525
  return true;
493
- // TODO v5: delete the speciao UP TO comparison?
526
+ // TODO v6: delete the special UP TO comparison?
494
527
  const upToVal = upToSpec.val;
495
528
  const prevVal = previousItem.val;
496
529
  // eslint-disable-next-line eqeqeq
@@ -540,7 +573,7 @@ function extend( model ) {
540
573
  // - 'ext-unexpected-type-argument' (TODO) if the artifact does not have the prop
541
574
  // - 'ext-invalid-type-argument' if the value is wrong for extend (no overwrite)
542
575
  //
543
- // TODO v5: do not allow `extend … with (precision: …)` alone if original def also has `scale`
576
+ // TODO v6: do not allow `extend … with (precision: …)` alone if original def also has `scale`
544
577
  function applyTypeExtensions( art, ext, prop, scaleDiff ) {
545
578
  // console.log('ATE:',art?.[prop],ext?.[prop],scaleDiff)
546
579
  if (!ext?.[prop])
@@ -617,17 +650,17 @@ function extend( model ) {
617
650
  const artDict = art[artProp] || annotateFor( art, extProp ); // no auto-correction in annotate
618
651
 
619
652
  for (const ext of extensions) {
620
- const extDict = ext[extProp];
621
653
  let dictCheck = (art.kind !== 'annotate'); // no check in super annotate statement
622
- for (const name in extDict) {
623
- const elemExt = extDict[name];
654
+ forEachGeneric(ext, extProp, (elemExt, name) => {
624
655
  if (elemExt.kind !== 'annotate' && elemExt.kind !== 'extend') // TODO: specified elems
625
- continue; // definitions inside extend, already handled
656
+ return; // definitions inside extend, already handled
626
657
  dictCheck = dictCheck && checkRemainingMemberExtensions( art, elemExt, artProp, name );
627
658
  const elem = artDict[name] || annotateFor( art, extProp, name );
628
659
  setLink( elemExt.name, '_artifact', (elem.kind !== 'annotate' ? elem : null ) );
629
- pushToDict( elem, '_extensions', elemExt );
630
- }
660
+ ensureArtifactNotProcessed( elem );
661
+ if (elem.$duplicates !== true)
662
+ pushToDict( elem, '_extensions', elemExt );
663
+ });
631
664
  }
632
665
  }
633
666
 
@@ -778,6 +811,10 @@ function extend( model ) {
778
811
  { '#': (inReturns ? 'enum-returns' : 'enum'), art, name },
779
812
  parent.enum );
780
813
  break;
814
+ case 'foreignKeys':
815
+ notFound( 'ext-undefined-key', ext.name.location, ext,
816
+ { name }, parent.foreignKeys );
817
+ break;
781
818
  case 'params':
782
819
  notFound( 'ext-undefined-param', ext.name.location, ext,
783
820
  { '#': 'param', art: parent, name },
@@ -791,7 +828,8 @@ function extend( model ) {
791
828
  parent.actions );
792
829
  break;
793
830
  default:
794
- // assert
831
+ if (model.options.testMode)
832
+ throw new CompilerAssertion(`Missing case for prop: ${ prop }`);
795
833
  }
796
834
  }
797
835
  return true;
@@ -832,8 +870,9 @@ function extend( model ) {
832
870
  }
833
871
 
834
872
  function checkRemainingMainExtensions( art, ext, localized ) {
873
+ const isExtend = ext.kind === 'extend';
835
874
  if (localized) {
836
- if (isV5preview && ext.kind === 'extend') {
875
+ if (isExtend) {
837
876
  // In v5, reject any `extend` on localized.
838
877
  error( 'ref-undefined-art', [ ext.location || ext.name.location, ext ],
839
878
  { '#': 'localized', keyword: 'annotate' } );
@@ -847,22 +886,21 @@ function extend( model ) {
847
886
  if (art?.builtin) {
848
887
  info( 'anno-builtin', [ ext.name.location, ext ], {} ); // TODO: better location?
849
888
  }
850
- else if (art?.kind === 'namespace') {
889
+ else if (isExtend && art?.kind === 'namespace') {
890
+ // `annotate` on namespaces already handled before
851
891
  const hasAnnotations = Object.keys(ext).find(a => a.charAt(0) === '@');
852
892
  const firstAnno = ext[hasAnnotations];
853
893
  // In v5, extending namespaces is only allowed for `extend with definitions`.
854
894
  // Neither annotations nor other extensions are allowed.
855
895
  // 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
- }
896
+ // Because "namespaces" are the same as "unknown" artifacts in CSN, we don't report
897
+ // an error for `annotate`s.
898
+ // FIXME: The compiler generates empty `annotate` statements for
899
+ // `extend ns with definitions {…}`. That's why we check the frontend.
900
+ if (hasAnnotations || (!ext.artifacts && ext._block.$frontend !== 'json')) {
901
+ error( 'ref-undefined-art', [ (firstAnno?.name || ext.name).location, ext ], {
902
+ '#': 'namespace', art: ext,
903
+ } );
866
904
  }
867
905
  }
868
906
  }
@@ -1101,7 +1139,6 @@ function extend( model ) {
1101
1139
  { art: artName },
1102
1140
  {
1103
1141
  std: 'Unknown $(ART) - nothing to extend',
1104
- // eslint-disable-next-line max-len
1105
1142
  element: 'Artifact $(ART) has no element or enum $(MEMBER) - nothing to extend',
1106
1143
  action: 'Artifact $(ART) has no action $(MEMBER) - nothing to extend',
1107
1144
  } );
@@ -1232,7 +1269,7 @@ function extend( model ) {
1232
1269
  if (!parent) // not yet set for EXTEND foo WITH bar => linkToOrigin() did not add it
1233
1270
  dictAdd( ext[prop], name, elem );
1234
1271
  elem.$inferred = 'include';
1235
- if (origin.masked) // TODO(v5): remove 'masked'
1272
+ if (origin.masked) // TODO(v6): remove 'masked'
1236
1273
  elem.masked = Object.assign( { $inferred: 'include' }, origin.masked );
1237
1274
  if (origin.key)
1238
1275
  elem.key = Object.assign( { $inferred: 'include' }, origin.key );
@@ -1269,8 +1306,6 @@ function extend( model ) {
1269
1306
  /**
1270
1307
  * Report duplicates in parent[prop] that happen due to multiple includes having the
1271
1308
  * same member. Covers `entity G : E, G {};` but not `entity G : E {}; extend G with F;`.
1272
- *
1273
- * TODO(v5): Make this a hard error; see checkRedefinition(); maybe combine both;
1274
1309
  */
1275
1310
  function checkRedefinitionThroughIncludes( parent, prop ) {
1276
1311
  if (!parent[prop])
@@ -656,7 +656,7 @@ function generate( model ) {
656
656
 
657
657
  if (elem.type && !isDirectComposition( elem )) {
658
658
  // Only issue warning for direct usages, not for projections, includes, etc.
659
- // TODO: Make it configurable error; v5: error
659
+ // TODO: Make it configurable error; v6: error
660
660
  // TODO: move to resolve.js where we test the targetAspect,
661
661
  warning( 'type-expecting-composition', [ elem.type.location, elem ],
662
662
  { newcode: 'Composition of', code: 'Association to' },
@@ -83,11 +83,7 @@ function parseX( source, filename, options = {}, messageFunctions = null ) {
83
83
  if (!messageFunctions)
84
84
  messageFunctions = createMessageFunctions( options, 'parse' );
85
85
  const ext = path.extname( filename ).slice(1).toLowerCase();
86
- // eslint-disable-next-line no-nested-ternary
87
- const parser = options.fallbackParser === 'auto!'
88
- ? (source?.startsWith( '{' ) ? parseCsn.parse : parseLanguage)
89
- : (extensionParsers[ext] || extensionParsers[options.fallbackParser] ||
90
- source.startsWith( '{' ) && parseCsn.parse);
86
+ const parser = parserForFile( source, ext, options );
91
87
  if (parser)
92
88
  return parser( source, filename, options, messageFunctions );
93
89
 
@@ -101,6 +97,27 @@ function parseX( source, filename, options = {}, messageFunctions = null ) {
101
97
  return model;
102
98
  }
103
99
 
100
+ /**
101
+ * Get the correct parser for the given source / file extension.
102
+ * Respects the set fallback parser.
103
+ *
104
+ * @param {string} source
105
+ * @param {string} ext
106
+ * @param {object} options
107
+ */
108
+ function parserForFile( source, ext, options ) {
109
+ // 'auto!' ignores the file's extension
110
+ if (options.fallbackParser === 'auto!')
111
+ return (source?.startsWith( '{' ) ? parseCsn.parse : parseLanguage);
112
+
113
+ if (options.fallbackParser === 'csn!')
114
+ return parseCsn.parse;
115
+
116
+ return extensionParsers[ext] ||
117
+ extensionParsers[options.fallbackParser] ||
118
+ (source.startsWith( '{' ) && parseCsn.parse);
119
+ }
120
+
104
121
  // Main function: Compile the sources from the files given by the array of
105
122
  // `filenames`. As usual with the `fs` library, relative file names are
106
123
  // relative to the working directory `process.cwd()`. With argument `dir`, the
@@ -530,7 +547,7 @@ function processFilenamesSync( filenames, dir ) {
530
547
  // already handles non-existent files.
531
548
  name = fs.realpathSync.native( name );
532
549
  }
533
- catch (e) {
550
+ catch {
534
551
  // Ignore the not-found (ENOENT) error
535
552
  }
536
553
  filenameMap[originalName] = name;