@sap/cds-compiler 3.4.4 → 3.5.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 (129) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +1 -0
  3. package/bin/cds_update_identifiers.js +5 -5
  4. package/bin/cdsc.js +12 -12
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +9 -1
  7. package/doc/CHANGELOG_DEPRECATED.md +2 -0
  8. package/lib/api/main.js +58 -59
  9. package/lib/api/options.js +4 -2
  10. package/lib/api/validate.js +2 -2
  11. package/lib/base/cleanSymbols.js +2 -3
  12. package/lib/base/dictionaries.js +6 -6
  13. package/lib/base/error.js +2 -2
  14. package/lib/base/keywords.js +6 -6
  15. package/lib/base/location.js +11 -12
  16. package/lib/base/message-registry.js +124 -28
  17. package/lib/base/messages.js +247 -179
  18. package/lib/base/model.js +14 -11
  19. package/lib/base/node-helpers.js +9 -10
  20. package/lib/base/optionProcessorHelper.js +138 -129
  21. package/lib/checks/actionsFunctions.js +5 -5
  22. package/lib/checks/annotationsOData.js +4 -4
  23. package/lib/checks/arrayOfs.js +1 -1
  24. package/lib/checks/cdsPersistence.js +1 -1
  25. package/lib/checks/checkForTypes.js +3 -3
  26. package/lib/checks/defaultValues.js +3 -3
  27. package/lib/checks/elements.js +7 -7
  28. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  29. package/lib/checks/foreignKeys.js +1 -1
  30. package/lib/checks/invalidTarget.js +4 -4
  31. package/lib/checks/managedInType.js +1 -1
  32. package/lib/checks/managedWithoutKeys.js +1 -1
  33. package/lib/checks/nonexpandableStructured.js +5 -3
  34. package/lib/checks/nullableKeys.js +1 -1
  35. package/lib/checks/onConditions.js +5 -6
  36. package/lib/checks/parameters.js +1 -1
  37. package/lib/checks/queryNoDbArtifacts.js +2 -2
  38. package/lib/checks/selectItems.js +4 -4
  39. package/lib/checks/sql-snippets.js +4 -4
  40. package/lib/checks/types.js +7 -7
  41. package/lib/checks/utils.js +4 -4
  42. package/lib/checks/validator.js +16 -13
  43. package/lib/compiler/.eslintrc.json +1 -1
  44. package/lib/compiler/assert-consistency.js +0 -1
  45. package/lib/compiler/builtins.js +1 -1
  46. package/lib/compiler/checks.js +73 -15
  47. package/lib/compiler/define.js +3 -7
  48. package/lib/compiler/extend.js +212 -32
  49. package/lib/compiler/finalize-parse-cdl.js +7 -2
  50. package/lib/compiler/index.js +17 -14
  51. package/lib/compiler/populate.js +2 -5
  52. package/lib/compiler/propagator.js +2 -0
  53. package/lib/compiler/shared.js +23 -12
  54. package/lib/compiler/tweak-assocs.js +5 -6
  55. package/lib/compiler/utils.js +6 -0
  56. package/lib/edm/annotations/genericTranslation.js +553 -319
  57. package/lib/edm/annotations/preprocessAnnotations.js +39 -35
  58. package/lib/edm/csn2edm.js +88 -75
  59. package/lib/edm/edm.js +17 -3
  60. package/lib/edm/edmAnnoPreprocessor.js +5 -5
  61. package/lib/edm/edmPreprocessor.js +106 -76
  62. package/lib/edm/edmUtils.js +41 -2
  63. package/lib/gen/Dictionary.json +34 -0
  64. package/lib/gen/language.checksum +1 -1
  65. package/lib/gen/language.interp +66 -63
  66. package/lib/gen/language.tokens +81 -81
  67. package/lib/gen/languageLexer.interp +4 -10
  68. package/lib/gen/languageLexer.js +854 -869
  69. package/lib/gen/languageLexer.tokens +79 -81
  70. package/lib/gen/languageParser.js +14360 -14146
  71. package/lib/inspect/inspectModelStatistics.js +2 -2
  72. package/lib/inspect/inspectPropagation.js +6 -6
  73. package/lib/inspect/inspectUtils.js +2 -2
  74. package/lib/json/from-csn.js +82 -40
  75. package/lib/json/to-csn.js +82 -157
  76. package/lib/language/.eslintrc.json +1 -4
  77. package/lib/language/genericAntlrParser.js +59 -38
  78. package/lib/language/language.g4 +1508 -1490
  79. package/lib/language/multiLineStringParser.js +1 -1
  80. package/lib/main.js +3 -3
  81. package/lib/model/csnUtils.js +130 -122
  82. package/lib/model/revealInternalProperties.js +1 -1
  83. package/lib/model/sortViews.js +4 -6
  84. package/lib/modelCompare/utils/filter.js +4 -3
  85. package/lib/optionProcessor.js +5 -0
  86. package/lib/render/DuplicateChecker.js +1 -1
  87. package/lib/render/manageConstraints.js +12 -12
  88. package/lib/render/toCdl.js +225 -159
  89. package/lib/render/toHdbcds.js +63 -63
  90. package/lib/render/toRename.js +5 -5
  91. package/lib/render/toSql.js +55 -65
  92. package/lib/render/utils/common.js +20 -37
  93. package/lib/render/utils/delta.js +3 -3
  94. package/lib/render/utils/sql.js +22 -6
  95. package/lib/render/utils/stringEscapes.js +3 -3
  96. package/lib/transform/db/applyTransformations.js +3 -3
  97. package/lib/transform/db/assertUnique.js +13 -12
  98. package/lib/transform/db/associations.js +5 -5
  99. package/lib/transform/db/cdsPersistence.js +10 -8
  100. package/lib/transform/db/constraints.js +14 -14
  101. package/lib/transform/db/expansion.js +20 -22
  102. package/lib/transform/db/flattening.js +24 -42
  103. package/lib/transform/db/groupByOrderBy.js +3 -3
  104. package/lib/transform/db/temporal.js +6 -6
  105. package/lib/transform/db/transformExists.js +23 -23
  106. package/lib/transform/db/views.js +16 -16
  107. package/lib/transform/draft/db.js +10 -10
  108. package/lib/transform/draft/odata.js +2 -2
  109. package/lib/transform/forOdataNew.js +12 -40
  110. package/lib/transform/forRelationalDB.js +17 -7
  111. package/lib/transform/localized.js +2 -2
  112. package/lib/transform/odata/toFinalBaseType.js +41 -27
  113. package/lib/transform/odata/typesExposure.js +106 -62
  114. package/lib/transform/parseExpr.js +209 -106
  115. package/lib/transform/transformUtilsNew.js +2 -2
  116. package/lib/transform/translateAssocsToJoins.js +24 -19
  117. package/lib/transform/universalCsn/coreComputed.js +10 -10
  118. package/lib/transform/universalCsn/universalCsnEnricher.js +26 -26
  119. package/lib/transform/universalCsn/utils.js +3 -3
  120. package/lib/utils/file.js +5 -5
  121. package/lib/utils/moduleResolve.js +13 -13
  122. package/lib/utils/objectUtils.js +6 -6
  123. package/lib/utils/term.js +5 -2
  124. package/lib/utils/timetrace.js +51 -24
  125. package/package.json +5 -7
  126. package/share/messages/check-proper-type-of.md +1 -1
  127. package/share/messages/message-explanations.json +1 -1
  128. package/share/messages/redirected-to-complex.md +4 -4
  129. package/share/messages/{syntax-expecting-integer.md → syntax-expecting-unsigned-int.md} +7 -4
@@ -10,7 +10,7 @@ const { isDeprecatedEnabled } = require('./model');
10
10
  const { centralMessages, centralMessageTexts, oldMessageIds } = require('./message-registry');
11
11
  const _messageIdsWithExplanation = require('../../share/messages/message-explanations.json').messages;
12
12
  const { analyseCsnPath, traverseQuery } = require('../model/csnRefs');
13
- const { CompilerAssertion } = require("./error");
13
+ const { CompilerAssertion } = require('./error');
14
14
 
15
15
  const fs = require('fs');
16
16
  const path = require('path');
@@ -95,8 +95,8 @@ class CompilationError extends Error {
95
95
  * @memberOf CompilationError
96
96
  */
97
97
  constructor(messages, model, text, ...args) {
98
- super( text || 'CDS compilation failed\n' + messages.map( m => m.toString() ).join('\n'),
99
- // @ts-ignore Error does not take more arguments according to TypeScript...
98
+ super( text || `CDS compilation failed\n${ messages.map( m => m.toString() ).join('\n') }`,
99
+ // @ts-ignore Error does not take more arguments according to TypeScript...
100
100
  ...args );
101
101
  this.messages = messages;
102
102
 
@@ -105,11 +105,13 @@ class CompilationError extends Error {
105
105
  // property `model` is only set with options.attachValidNames:
106
106
  Object.defineProperty( this, 'model', { value: model || undefined, configurable: true } );
107
107
  }
108
+
108
109
  toString() { // does not really help -> set message
109
110
  return this.message.includes('\n')
110
111
  ? this.message
111
- : this.message + '\n' + this.messages.map( m => m.toString() ).join('\n');
112
+ : `${ this.message }\n${ this.messages.map( m => m.toString() ).join('\n') }`;
112
113
  }
114
+
113
115
  /**
114
116
  * @deprecated Use `.messages` instead.
115
117
  */
@@ -143,8 +145,11 @@ class CompileMessage {
143
145
  this.home = home; // semantic location, e.g. 'entity:"E"/element:"x"'
144
146
  this.severity = severity;
145
147
  Object.defineProperty( this, 'messageId', { value: id } );
146
- // this.messageId = id; // ids not yet finalized
147
148
  Object.defineProperty( this, '$module', { value: moduleName, configurable: true } );
149
+ // Uncomment when running TypeScript linter
150
+ // this.messageId = id;
151
+ // this.$module = moduleName;
152
+ // this.error = null;
148
153
  }
149
154
 
150
155
  toString() { // should have no argument...
@@ -182,8 +187,8 @@ function reclassifiedSeverity( msg, options, moduleName, deprecatedDowngradable
182
187
  if (spec.severity === 'Error') {
183
188
  const { configurableFor } = spec;
184
189
  if (!(Array.isArray( configurableFor )
185
- ? configurableFor.includes( moduleName )
186
- : configurableFor && (configurableFor !== 'deprecated' || deprecatedDowngradable)))
190
+ ? configurableFor.includes( moduleName )
191
+ : configurableFor && (configurableFor !== 'deprecated' || deprecatedDowngradable)))
187
192
  return 'Error';
188
193
  }
189
194
  else {
@@ -198,7 +203,7 @@ function reclassifiedSeverity( msg, options, moduleName, deprecatedDowngradable
198
203
  let newSeverity = options.severities[msg.messageId];
199
204
  // The user could have specified a severity through an old message ID.
200
205
  if (!newSeverity && spec.oldNames) {
201
- const oldName = spec.oldNames.find((name => options.severities[name]))
206
+ const oldName = spec.oldNames.find((name => options.severities[name]));
202
207
  newSeverity = options.severities[oldName];
203
208
  }
204
209
  return normalizedSeverity( newSeverity ) || spec.severity;
@@ -207,7 +212,7 @@ function reclassifiedSeverity( msg, options, moduleName, deprecatedDowngradable
207
212
  function normalizedSeverity( severity ) {
208
213
  if (typeof severity !== 'string')
209
214
  return (severity == null) ? null : 'Error';
210
- let s = severitySpecs[ severity.toLowerCase() ];
215
+ const s = severitySpecs[severity.toLowerCase()];
211
216
  return s ? s.name : 'Error';
212
217
  }
213
218
 
@@ -322,12 +327,19 @@ function makeMessageFunction( model, options, moduleName = null ) {
322
327
  reclassifyMessagesForModule();
323
328
 
324
329
  return {
325
- message, error, warning, info, debug, messages,
326
- throwWithError, throwWithAnyError,
327
- callTransparently, moduleName,
330
+ message,
331
+ error,
332
+ warning,
333
+ info,
334
+ debug,
335
+ messages,
336
+ throwWithError,
337
+ throwWithAnyError,
338
+ callTransparently,
339
+ moduleName,
328
340
  };
329
341
 
330
- function _message(id, location, textOrArguments, severity, texts = null) {
342
+ function _message( id, location, textOrArguments, severity, texts = null ) {
331
343
  _validateFunctionArguments(id, location, textOrArguments, severity, texts);
332
344
 
333
345
  // Special case for _info, etc.: textOrArguments may be a string.
@@ -348,7 +360,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
348
360
 
349
361
  if (id) {
350
362
  if (options.testMode && !options.$recompile)
351
- _check$Consistency( id, moduleName, severity, texts, options )
363
+ _check$Consistency( id, moduleName, severity, texts, options );
352
364
  msg.severity = reclassifiedSeverity(msg, options, moduleName, deprecatedDowngradable );
353
365
  }
354
366
 
@@ -364,33 +376,34 @@ function makeMessageFunction( model, options, moduleName = null ) {
364
376
  * Validate the arguments for the message() function. This is needed during the transition
365
377
  * to the new makeMessageFunction().
366
378
  */
367
- function _validateFunctionArguments(id, location, textArguments, severity, texts) {
379
+ function _validateFunctionArguments( id, location, textArguments, severity, texts ) {
368
380
  if (!options.testMode)
369
381
  return;
370
382
 
371
383
  if (id !== null && typeof id !== 'string')
372
- _expectedType('id', id, 'string')
384
+ _expectedType('id', id, 'string');
373
385
 
374
386
  if (location !== null && location !== undefined && !Array.isArray(location) && typeof location !== 'object')
375
- _expectedType('location', location, 'XSN/CSN location, CSN path')
387
+ _expectedType('location', location, 'XSN/CSN location, CSN path');
376
388
 
377
389
  if (severity != null && typeof severity !== 'string')
378
- _expectedType('severity', severity, 'string')
390
+ _expectedType('severity', severity, 'string');
379
391
 
380
- const isShortSignature = (typeof textArguments === 'string') // textArguments => texts
392
+ const isShortSignature = (typeof textArguments === 'string'); // textArguments => texts
381
393
 
382
394
  if (isShortSignature) {
383
395
  if (texts)
384
396
  throw new CompilerAssertion('No "texts" argument expected because text was already provided as third argument.');
385
- } else {
397
+ }
398
+ else {
386
399
  if (textArguments !== undefined && typeof textArguments !== 'object')
387
- _expectedType('textArguments', textArguments, 'object')
400
+ _expectedType('textArguments', textArguments, 'object');
388
401
  if (texts !== undefined && typeof texts !== 'object' && typeof texts !== 'string')
389
- _expectedType('texts', texts, 'object or string')
402
+ _expectedType('texts', texts, 'object or string');
390
403
  }
391
404
 
392
- function _expectedType(field, value, type) {
393
- throw new CompilerAssertion(`Invalid argument type for ${ field }! Expected ${ type } but got ${ typeof value }. Do you use the old function signature?`);
405
+ function _expectedType( field, val, type ) {
406
+ throw new CompilerAssertion(`Invalid argument type for ${ field }! Expected ${ type } but got ${ typeof val }. Do you use the old function signature?`);
394
407
  }
395
408
  }
396
409
 
@@ -401,27 +414,27 @@ function makeMessageFunction( model, options, moduleName = null ) {
401
414
  * @param {any} location
402
415
  * @returns {[CSN.Location, string, string]} Location, semantic location and definition.
403
416
  */
404
- function _normalizeMessageLocation(location) {
417
+ function _normalizeMessageLocation( location ) {
405
418
  if (!location)
406
419
  // e.g. for general messages unrelated to code
407
- return [ null, null, null ]
420
+ return [ null, null, null ];
408
421
 
409
422
  if (typeof location === 'object' && !Array.isArray(location))
410
423
  // CSN.Location (with line/endLine, col/endCol)
411
- return [ location, location.home || null, null ]
424
+ return [ location, location.home || null, null ];
412
425
 
413
426
  const isCsnPath = (typeof location[0] === 'string'); // could be `definitions`, `extensions`, ....
414
427
  if (isCsnPath) {
415
428
  return [
416
429
  findNearestLocationForPath( model, location ),
417
- constructSemanticLocationFromCsnPath( location, model ),
418
- location[1] // location[0] is 'definitions'
430
+ constructSemanticLocationFromCsnPath( model, options, location ),
431
+ location[1], // location[0] is 'definitions'
419
432
  ];
420
433
  }
421
434
 
422
435
  let semanticLocation = location[1] ? homeName( location[1], false ) : null;
423
436
  if (location[2]) // optional suffix
424
- semanticLocation += '/' + location[2]
437
+ semanticLocation += `/${ location[2] }`;
425
438
 
426
439
  const definition = location[1] ? homeName( location[1], true ) : null;
427
440
 
@@ -443,7 +456,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
443
456
  * @param {object} [textArguments] Text parameters that are replaced in the texts.
444
457
  * @param {string|object} [texts]
445
458
  */
446
- function message(id, location, textArguments = null, texts = null) {
459
+ function message( id, location, textArguments = null, texts = null ) {
447
460
  if (!id)
448
461
  throw new CompilerAssertion('A message id is missing!');
449
462
  if (!centralMessages[id])
@@ -455,7 +468,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
455
468
  * Create a compiler error message.
456
469
  * @see message()
457
470
  */
458
- function error(id, location, textOrArguments = null, texts = null) {
471
+ function error( id, location, textOrArguments = null, texts = null ) {
459
472
  return _message(id, location, textOrArguments, 'Error', texts);
460
473
  }
461
474
 
@@ -463,7 +476,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
463
476
  * Create a compiler warning message.
464
477
  * @see message()
465
478
  */
466
- function warning(id, location, textOrArguments = null, texts = null) {
479
+ function warning( id, location, textOrArguments = null, texts = null ) {
467
480
  return _message(id, location, textOrArguments, 'Warning', texts);
468
481
  }
469
482
 
@@ -471,7 +484,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
471
484
  * Create a compiler info message.
472
485
  * @see message()
473
486
  */
474
- function info(id, location, textOrArguments = null, texts = null) {
487
+ function info( id, location, textOrArguments = null, texts = null ) {
475
488
  return _message(id, location, textOrArguments, 'Info', texts);
476
489
  }
477
490
 
@@ -479,7 +492,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
479
492
  * Create a compiler debug message (usually not shown).
480
493
  * @see message()
481
494
  */
482
- function debug(id, location, textOrArguments = null, texts = null) {
495
+ function debug( id, location, textOrArguments = null, texts = null ) {
483
496
  return _message(id, location, textOrArguments, 'Debug', texts);
484
497
  }
485
498
 
@@ -533,7 +546,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
533
546
  * @param {...any} args
534
547
  * @returns {CompileMessage[]}
535
548
  */
536
- function callTransparently(callback, ...args) {
549
+ function callTransparently( callback, ...args ) {
537
550
  const backup = messages;
538
551
  messages = [];
539
552
  callback(...args);
@@ -552,7 +565,7 @@ function _check$Init( options ) {
552
565
  test$severities = Object.create(null);
553
566
  if (!test$texts) {
554
567
  test$texts = Object.create(null);
555
- for (const [id, texts] of Object.entries( centralMessageTexts ))
568
+ for (const [ id, texts ] of Object.entries( centralMessageTexts ))
556
569
  test$texts[id] = (typeof texts === 'string') ? { std: texts } : { ...texts };
557
570
  }
558
571
  }
@@ -574,12 +587,13 @@ function _check$Init( options ) {
574
587
  * @private
575
588
  */
576
589
  function _check$Consistency( id, moduleName, severity, texts, options ) {
577
- if (id.length > 30 && !centralMessages[id])
578
- throw new CompilerAssertion( `The message ID "${id}" has more than 30 chars and must be listed centrally` );
590
+ // TODO: replace by linter?
591
+ if (id.length > 32 && !centralMessages[id])
592
+ throw new CompilerAssertion( `The message ID "${ id }" has more than 30 chars and must be listed centrally` );
579
593
  if (!options.severities)
580
594
  _check$Severities( id, moduleName || '?', severity );
581
- for (const [variant, text] of
582
- Object.entries( (typeof texts === 'string') ? { std: texts } : texts || {} ))
595
+ for (const [ variant, text ] of
596
+ Object.entries( (typeof texts === 'string') ? { std: texts } : texts || {} ))
583
597
  _check$Texts( id, variant, text );
584
598
  }
585
599
 
@@ -602,19 +616,19 @@ function _check$Severities( id, moduleName, severity ) {
602
616
  if (!expected)
603
617
  test$severities[id] = severity;
604
618
  else if (expected !== severity)
605
- throw new CompilerAssertion( `Inconsistent severity: Expecting "${expected}" from previous call, not "${severity}" for message ID "${id}"` );
619
+ throw new CompilerAssertion( `Inconsistent severity: Expecting "${ expected }" from previous call, not "${ severity }" for message ID "${ id }"` );
606
620
  return;
607
621
  }
608
622
  // now try whether the message could be something less than an Error in the module due to user wishes
609
623
  if (!isDowngradable( id, moduleName, true )) { // always an error in module
610
624
  if (severity !== 'Error')
611
- throw new CompilerAssertion( `Inconsistent severity: Expecting "Error", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
625
+ throw new CompilerAssertion( `Inconsistent severity: Expecting "Error", not "${ severity }" for message ID "${ id }" in module "${ moduleName }"` );
612
626
  }
613
627
  else if (spec.severity === 'Error') {
614
- throw new CompilerAssertion( `Inconsistent severity: Expecting the use of function message() when message ID "${id}" is a configurable error in module "${moduleName}"` );
628
+ throw new CompilerAssertion( `Inconsistent severity: Expecting the use of function message() when message ID "${ id }" is a configurable error in module "${ moduleName }"` );
615
629
  }
616
630
  else if (spec.severity !== severity) {
617
- throw new CompilerAssertion( `Inconsistent severity: Expecting "${spec.severity}", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
631
+ throw new CompilerAssertion( `Inconsistent severity: Expecting "${ spec.severity }", not "${ severity }" for message ID "${ id }" in module "${ moduleName }"` );
618
632
  }
619
633
  }
620
634
 
@@ -627,52 +641,55 @@ function _check$Severities( id, moduleName, severity ) {
627
641
  *
628
642
  * @param {string} id
629
643
  * @param {string} prop
630
- * @param {string} value
644
+ * @param {string} val
631
645
  * @private
632
646
  */
633
- function _check$Texts( id, prop, value ) {
647
+ function _check$Texts( id, prop, val ) {
634
648
  if (!test$texts[id])
635
649
  test$texts[id] = Object.create(null);
636
650
  const expected = test$texts[id][prop];
637
651
  if (!expected)
638
- test$texts[id][prop] = value;
639
- else if (expected !== value)
640
- throw new CompilerAssertion( `Different texts for the same message ID. Expecting "${expected}", not "${value}" for ID "${id}" and text variant "${prop}"`);
652
+ test$texts[id][prop] = val;
653
+ else if (expected !== val)
654
+ throw new CompilerAssertion( `Different texts for the same message ID. Expecting “${ expected }”, not “${ val } for ID “${ id } and text variant “${ prop }”`);
641
655
  }
642
656
 
643
657
  const quote = { // could be an option in the future
644
658
  double: p => `“${ p }”`, // for names, including annotation names (with preceeding `@`)
645
- single: p => `‘${ p }’`, // for other things cited from the model
646
- angle: p => `‹${ p }›`, // for tokens like ‹Identifier›, and similar
647
- number: p => p, // for numbers like 42
648
- upper: p => p.toUpperCase(), // for keywords reported by ANTLR, use prop.single in v4
649
- }
659
+ single: p => `‘${ p }’`, // for other things cited from or expected in the model
660
+ angle: p => `‹${ p }›`, // for tokens like ‹Identifier›, and similar
661
+ direct: p => p, // e.g. for numbers _not cited from or expected in_ the source
662
+ upper: p => p.toUpperCase(), // for keywords reported by ANTLR, use prop.single in v4
663
+ };
650
664
 
651
665
  const paramsTransform = {
652
666
  // simple convenience:
653
667
  name: quoted,
654
668
  id: quoted,
655
669
  alias: quoted,
656
- anno: a => (a.charAt(0) === '@' ? quote.double( a ) : quote.double( '@' + a )),
657
- annos: anno => anno.map(paramsTransform.anno).join(', '),
670
+ anno,
671
+ annos: transformManyWith( anno ),
658
672
  delimited: n => quote.single( `![${ n }]` ), // represent as delimited id (TODO: double-']')
659
673
  file: quote.single,
674
+ option: quote.single,
660
675
  prop: quote.single,
661
676
  siblingprop: quote.single,
662
677
  parentprop: quote.single,
663
678
  otherprop: quote.single,
664
679
  code: quote.single,
665
680
  newcode: quote.single,
666
- kind: quote.angle, // consider using text variants instead
667
- keyword: quote.upper,
681
+ kind: quote.single,
682
+ meta: quote.angle,
683
+ keyword,
668
684
  // more complex convenience:
669
685
  names: transformManyWith( quoted ),
670
- number: quote.number,
671
- line: quote.number,
672
- col: quote.number,
673
- value: n => (typeof n !== 'number' ? quote.single( n ) : quote.number( n )),
674
- values: value => value.map(paramsTransform.value).join(', '),
675
- othervalue: n => (typeof n !== 'number' ? quote.single( n ) : quote.number( n )),
686
+ number: quote.single, // number cited from source or expected in source
687
+ count: quote.direct,
688
+ line: quote.direct,
689
+ col: quote.direct,
690
+ value,
691
+ rawvalues: transformManyWith( quote.single ), // no 'double' quotes for strings
692
+ othervalue: value,
676
693
  art: transformArg,
677
694
  service: transformArg,
678
695
  sorted_arts: transformManyWith( transformArg, true ),
@@ -680,6 +697,7 @@ const paramsTransform = {
680
697
  source: transformArg,
681
698
  elemref: transformElementRef,
682
699
  type: transformArg,
700
+ othertype: transformArg,
683
701
  offending: tokenSymbol,
684
702
  op: quote.single,
685
703
  expecting: transformManyWith( tokenSymbol ),
@@ -688,13 +706,53 @@ const paramsTransform = {
688
706
  version: quote.single, // TODO delete: just use for OData $(VERSION), with version: 2.0
689
707
  };
690
708
 
709
+ function anno( name ) {
710
+ return (name.charAt(0) === '@') ? quote.double( name ) : quote.double( `@${ name }` );
711
+ }
712
+
713
+ function value( val ) {
714
+ switch (typeof val) {
715
+ case 'number':
716
+ case 'boolean':
717
+ case 'bigint':
718
+ case 'undefined': {
719
+ return quote.single( val );
720
+ }
721
+ case 'string': {
722
+ // TODO: should we also shorten the string if too long?
723
+ return (!val ||
724
+ Number.parseFloat( val ).toString() === val ||
725
+ // with quotes (TODO: use `…` with escape chars):
726
+ /'/.test( val )) // sync ')
727
+ ? quote.single( `'${ val.replace(/'/g, '\'\'') }'` )
728
+ : quote.single( val );
729
+ }
730
+ case 'object': {
731
+ return (val)
732
+ ? quote.angle( Array.isArray( val ) ? 'array' : 'object' )
733
+ : quote.single( val );
734
+ }
735
+ default:
736
+ return quote.angle( typeof val );
737
+ }
738
+ }
739
+
740
+ const keywordRepresentations = {
741
+ association: 'Association',
742
+ composition: 'Composition',
743
+ };
744
+ function keyword( val ) {
745
+ const v = val.toLowerCase();
746
+ return quote.single( keywordRepresentations[v] || v );
747
+ }
748
+
691
749
  function ignoreTextTransform() {
692
750
  return null;
693
751
  }
694
752
 
695
753
  function transformManyWith( t, sorted ) {
696
754
  return function transformMany( many, r, args, texts ) {
697
- const prop = ['none','one','two'][ many.length ];
755
+ const prop = [ 'none', 'one', 'two' ][many.length];
698
756
  const names = many.map(t);
699
757
  if (sorted)
700
758
  names.sort();
@@ -718,21 +776,20 @@ function tokenSymbol( token ) {
718
776
  return quote.upper( token );
719
777
  else if (token.match( /^[A-Z][a-z]/ )) // Number, Identifier, ...
720
778
  return quote.angle( token );
721
- if (token.startsWith("'") && token.endsWith("'")) // operator token symbol
779
+ if (token.startsWith('\'') && token.endsWith('\'')) // operator token symbol
722
780
  return quote.single( token.slice( 1, -1 ));
723
781
  else if (token === '<EOF>')
724
782
  return quote.angle( 'EOF' );
725
- else
726
- return quote.single( token ); // should not happen
783
+ return quote.single( token ); // should not happen
727
784
  }
728
785
 
729
786
  /**
730
787
  * Transform an element reference (/path), e.g. on-condition path.
731
788
  */
732
- function transformElementRef(arg) {
789
+ function transformElementRef( arg ) {
733
790
  if (arg.ref) {
734
791
  // Can be used by CSN backends to create a simple path such as E:elem
735
- return quoted(arg.ref.map(ref => {
792
+ return quoted(arg.ref.map((ref) => {
736
793
  if (ref.id) {
737
794
  // Indicate that the path has a filter.
738
795
  if (ref.where)
@@ -757,16 +814,16 @@ function transformArg( arg, r, args, texts ) {
757
814
  if (arg.ref) {
758
815
  // Can be used by CSN backends to create a simple path such as E:elem
759
816
  if (arg.ref.length > 1)
760
- return quoted(arg.ref[0] + ':' + arg.ref.slice(1).join('.'));
817
+ return quoted(`${ arg.ref[0] }:${ arg.ref.slice(1).join('.') }`);
761
818
  return quoted(arg.ref);
762
819
  }
763
- let name = arg.name;
820
+ const { name } = arg;
764
821
  if (!name)
765
822
  return quoted( name );
766
- let prop = ['element','param','action','alias'].find( p => name[p] );
823
+ const prop = [ 'element', 'param', 'action', 'alias' ].find( p => name[p] );
767
824
  if (!prop || !texts[prop] )
768
825
  return shortArtName( arg );
769
- r['#'] = texts[ name.$variant ] && name.$variant || prop; // text variant (set by searchName)
826
+ r['#'] = texts[name.$variant] && name.$variant || prop; // text variant (set by searchName)
770
827
  r.member = quoted( name[prop] );
771
828
  return artName( arg, prop );
772
829
  }
@@ -781,7 +838,7 @@ function searchName( art, id, variant ) {
781
838
  if (!variant) {
782
839
  // used to mention the "effective" type in the message, not the
783
840
  // originally provided one (TODO: mention that in the message text)
784
- let type = art._effectiveType && art._effectiveType.kind !== 'undefined' ? art._effectiveType : art;
841
+ const type = art._effectiveType && art._effectiveType.kind !== 'undefined' ? art._effectiveType : art;
785
842
  if (type.elements) { // only mentioned elements
786
843
  art = type.target && type.target._artifact || type;
787
844
  variant = 'element';
@@ -790,43 +847,45 @@ function searchName( art, id, variant ) {
790
847
  variant = 'absolute';
791
848
  }
792
849
  }
793
- let prop = nameProp[variant] || variant;
794
- let name = Object.assign( { $variant: variant }, (art._artifact||art).name );
795
- name[prop] = (name[prop]) ? name[prop] + '.' + id : id || '?';
850
+ const prop = nameProp[variant] || variant;
851
+ const name = Object.assign( { $variant: variant }, (art._artifact || art).name );
852
+ name[prop] = name[prop] ? `${ name[prop] }.${ id }` : id || '?';
796
853
  return { name, kind: art.kind };
797
854
  }
798
855
 
799
856
  function messageText( texts, params, transform ) {
800
857
  if (typeof texts === 'string')
801
858
  texts = { std: texts };
802
- let args = {};
803
- for (let p in params) {
804
- let t = transform && transform[p] || paramsTransform[p];
805
- if (params[p] !== undefined)
859
+ const args = {};
860
+ for (const p in params) {
861
+ if (params[p] !== undefined) {
862
+ const t = transform && transform[p] || paramsTransform[p];
806
863
  args[p] = (t) ? t( params[p], args, params, texts ) : params[p];
864
+ }
807
865
  }
808
- let variant = args['#'];
809
- return replaceInString( variant && texts[ variant ] || texts.std, args );
866
+ const variant = args['#'];
867
+ return replaceInString( variant && texts[variant] || texts.std, args );
810
868
  }
811
869
 
812
870
  function replaceInString( text, params ) {
813
871
  const usedParams = [ '#', '$reviewed' ];
814
- let pattern = /\$\(([A-Z_]+)\)/g;
815
- let parts = [];
872
+ const pattern = /\$\(([A-Z_]+)\)/g;
873
+ const parts = [];
816
874
  let start = 0;
817
875
  for (let p = pattern.exec( text ); p; p = pattern.exec( text )) {
818
- let prop = p[1].toLowerCase();
876
+ const prop = p[1].toLowerCase();
819
877
  parts.push( text.substring( start, p.index ),
820
878
  (prop in params ? params[prop] : p[0]) );
821
879
  usedParams.push(prop);
822
880
  start = pattern.lastIndex;
823
881
  }
824
882
  parts.push( text.substring( start ) );
825
- let remain = (params['#']) ? [] : Object.keys( params ).filter( n => !usedParams.includes(n) );
826
- return (remain.length)
827
- ? parts.join('') + '; ' +
828
- remain.map( n => n.toUpperCase() + ' = ' + params[n] ).join(', ')
829
- : parts.join('');
883
+ const remain = (params['#']) ? [] : Object.keys( params ).filter( n => !usedParams.includes(n) );
884
+ if (remain.length) {
885
+ const remains = remain.map( n => `${ n.toUpperCase() } = ${ params[n] }` ).join(', ');
886
+ return `${ parts.join('') }; ${ remains }`;
887
+ }
888
+ return parts.join('');
830
889
  }
831
890
 
832
891
  /**
@@ -834,6 +893,8 @@ function replaceInString( text, params ) {
834
893
  * @returns {CSN.Location}
835
894
  */
836
895
  function weakLocation( loc ) {
896
+ if (!loc)
897
+ return {};
837
898
  // no endLine/endCol
838
899
  return { file: loc.file, line: loc.line, col: loc.col };
839
900
  }
@@ -852,14 +913,14 @@ function weakLocation( loc ) {
852
913
  */
853
914
  function messageString( err, normalizeFilename, noMessageId, noHome ) {
854
915
  return (err.$location && err.$location.file
855
- ? locationString( err.$location, normalizeFilename ) + ': '
856
- : '') +
916
+ ? `${ locationString( err.$location, normalizeFilename ) }: `
917
+ : '') +
857
918
  (err.severity || 'Error') +
858
919
  // TODO: use [message-id]
859
- (err.messageId && !noMessageId ? ' ' + err.messageId + ': ' : ': ') +
920
+ (err.messageId && !noMessageId ? ` ${ err.messageId }: ` : ': ') +
860
921
  err.message +
861
922
  // even with noHome, print err.home if the location is weak
862
- (!err.home || noHome && err.$location && err.$location.endLine ? '' : ' (in ' + err.home + ')');
923
+ (!err.home || noHome && err.$location && err.$location.endLine ? '' : ` (in ${ err.home })`);
863
924
  }
864
925
 
865
926
  /**
@@ -869,11 +930,11 @@ function messageString( err, normalizeFilename, noMessageId, noHome ) {
869
930
  * @param {CompileMessage} msg
870
931
  * @returns {string} can be used to uniquely identify a message
871
932
  */
872
- function messageHash(msg) {
933
+ function messageHash( msg ) {
873
934
  // parser messages do not provide semantic location, therefore we need to use the file location
874
- if(!msg.home)
935
+ if (!msg.home)
875
936
  return messageString(msg);
876
- const copy = {...msg};
937
+ const copy = { ...msg };
877
938
  // Note: This is a hack. deduplicateMessages() would otherwise remove
878
939
  // all but one message about duplicated artifacts.
879
940
  if (!msg.messageId || !msg.messageId.includes('duplicate'))
@@ -909,25 +970,26 @@ function messageStringMultiline( err, config = {} ) {
909
970
 
910
971
  const explainHelp = (config.hintExplanation && hasMessageExplanation(err.messageId)) ? '…' : '';
911
972
  const msgId = (err.messageId && !config.noMessageId) ? `[${ err.messageId }${ explainHelp }]` : '';
912
- const home = !err.home ? '' : ('at ' + err.home);
973
+ const home = !err.home ? '' : (`at ${ err.home }`);
913
974
  const severity = err.severity || 'Error';
914
975
 
915
976
  let location = '';
916
977
  if (err.$location && err.$location.file) {
917
- location += locationString( err.$location, config.normalizeFilename )
978
+ location += locationString( err.$location, config.normalizeFilename );
918
979
  if (home)
919
- location += ', '
980
+ location += ', ';
981
+ }
982
+ else if (!home) {
983
+ return `${ colorTerm.severity(severity, severity + msgId) } ${ err.message }`;
920
984
  }
921
- else if (!home)
922
- return colorTerm.severity(severity, severity + msgId) + ' ' + err.message;
923
985
 
924
986
  let lineSpacer = '';
925
987
  if (config.withLineSpacer) {
926
- let additionalIndent = err.$location ? `${ err.$location.endLine || err.$location.line || 1 }`.length : 1;
988
+ const additionalIndent = err.$location ? `${ err.$location.endLine || err.$location.line || 1 }`.length : 1;
927
989
  lineSpacer = `\n ${ ' '.repeat( additionalIndent ) }|`;
928
990
  }
929
991
 
930
- return colorTerm.severity(severity, severity + msgId) + ': ' + err.message + lineSpacer + '\n ' + location + home;
992
+ return `${ colorTerm.severity(severity, severity + msgId) }: ${ err.message }${ lineSpacer }\n ${ location }${ home }`;
931
993
  }
932
994
 
933
995
  /**
@@ -948,23 +1010,23 @@ function messageStringMultiline( err, config = {} ) {
948
1010
  * unset.
949
1011
  * @returns {string}
950
1012
  */
951
- function messageContext(sourceLines, err, config) {
1013
+ function messageContext( sourceLines, err, config ) {
952
1014
  colorTerm.changeColorMode(config ? config.color : 'auto');
953
1015
  const MAX_COL_LENGTH = 100;
954
1016
 
955
1017
  const loc = err.$location;
956
- if (!loc || !loc.line) {
1018
+ if (!loc || !loc.line)
957
1019
  return '';
958
- }
1020
+
959
1021
 
960
1022
  // Lines are 1-based, we need 0-based ones for arrays
961
1023
  const startLine = loc.line - 1;
962
1024
  const endLine = loc.endLine ? loc.endLine - 1 : startLine;
963
1025
 
964
1026
  // check that source lines exists
965
- if (typeof sourceLines[startLine] !== 'string' || typeof sourceLines[endLine] !== 'string') {
1027
+ if (typeof sourceLines[startLine] !== 'string' || typeof sourceLines[endLine] !== 'string')
966
1028
  return '';
967
- }
1029
+
968
1030
 
969
1031
  const digits = String(endLine + 1).length;
970
1032
  const severity = err.severity || 'Error';
@@ -981,7 +1043,7 @@ function messageContext(sourceLines, err, config) {
981
1043
  /** Only print N lines even if the error spans more lines. */
982
1044
  const maxLine = Math.min((startLine + 2), endLine);
983
1045
 
984
- let msg = indent + '|\n';
1046
+ let msg = `${ indent }|\n`;
985
1047
 
986
1048
  // print source line(s)
987
1049
  for (let line = startLine; line <= maxLine; line++) {
@@ -990,8 +1052,8 @@ function messageContext(sourceLines, err, config) {
990
1052
  if (sourceCode.length >= MAX_COL_LENGTH)
991
1053
  sourceCode = sourceCode.slice(0, MAX_COL_LENGTH);
992
1054
  // Only prepend space if the line contains any sources.
993
- sourceCode = sourceCode.length ? ' ' + sourceCode : '';
994
- msg += ' ' + String(line + 1).padStart(digits, ' ') + ' |' + sourceCode + '\n';
1055
+ sourceCode = sourceCode.length ? ` ${ sourceCode }` : '';
1056
+ msg += ` ${ String(line + 1).padStart(digits, ' ') } |${ sourceCode }\n`;
995
1057
  }
996
1058
 
997
1059
  if (startLine === endLine && loc.col > 0) {
@@ -1001,14 +1063,14 @@ function messageContext(sourceLines, err, config) {
1001
1063
  // Indicate that the error is further to the right.
1002
1064
  if (endColumn === MAX_COL_LENGTH)
1003
1065
  highlighter = highlighter.replace(' ^', '..^');
1004
- msg += indent + '| ' + colorTerm.severity(severity, highlighter);
1005
-
1006
- } else if (maxLine !== endLine) {
1066
+ msg += `${ indent }| ${ colorTerm.severity(severity, highlighter) }`;
1067
+ }
1068
+ else if (maxLine !== endLine) {
1007
1069
  // error spans more lines which we don't print
1008
- msg += indent + '| ...';
1009
-
1010
- } else {
1011
- msg += indent + '|';
1070
+ msg += `${ indent }| ...`;
1071
+ }
1072
+ else {
1073
+ msg += `${ indent }|`;
1012
1074
  }
1013
1075
 
1014
1076
  return msg;
@@ -1037,13 +1099,14 @@ function compareMessage( a, b ) {
1037
1099
  // TODO: severities?
1038
1100
  c( a.message, b.message ) );
1039
1101
  }
1040
- else if (!aFile && !bFile)
1102
+ else if (!aFile && !bFile) {
1041
1103
  return ( c( homeSortName( a ), homeSortName( b ) ) ||
1042
1104
  c( a.message, b.message ) );
1043
- else if (!aFile)
1105
+ }
1106
+ else if (!aFile) {
1044
1107
  return (a.messageId && a.messageId.startsWith( 'api-' )) ? -1 : 1;
1045
- else
1046
- return (b.messageId && b.messageId.startsWith( 'api-' )) ? 1 : -1;
1108
+ }
1109
+ return (b.messageId && b.messageId.startsWith( 'api-' )) ? 1 : -1;
1047
1110
 
1048
1111
  function c( x, y ) {
1049
1112
  return (x === y) ? 0 : (x > y) ? 1 : -1;
@@ -1072,9 +1135,8 @@ function compareMessageSeverityAware( a, b ) {
1072
1135
  */
1073
1136
  function homeSortName( { home, messageId } ) {
1074
1137
  if (!home)
1075
- return (messageId && /^(syntax|api)-/.test( messageId ) ? ' ' + messageId : '~')
1076
- else
1077
- return home.substring( home.indexOf(':') ); // i.e. starting with the ':', is always there
1138
+ return (messageId && /^(syntax|api)-/.test( messageId ) ? ` ${ messageId }` : '~');
1139
+ return home.substring( home.indexOf(':') ); // i.e. starting with the ':', is always there
1078
1140
  }
1079
1141
 
1080
1142
  /**
@@ -1091,22 +1153,24 @@ function homeSortName( { home, messageId } ) {
1091
1153
  * @param {CompileMessage[]} messages
1092
1154
  */
1093
1155
  function deduplicateMessages( messages ) {
1156
+ // sort messages to make it processing sequence independent which messages (with
1157
+ // which $location!) wins
1158
+ messages.sort(compareMessage);
1094
1159
  const seen = new Map();
1095
1160
  for (const msg of messages) {
1096
1161
  const hash = messageHash(msg);
1097
1162
 
1098
1163
  if (!seen.has(hash)) {
1099
1164
  seen.set(hash, msg);
1100
-
1101
- } else if (msg.$location) {
1165
+ }
1166
+ else if (msg.$location) {
1102
1167
  const existing = seen.get(hash);
1103
1168
  // If this messages has an end but the existing does not, then the new message is more precise.
1104
1169
  // If both messages do (or don't) have an endLine, then compare them based on their location.
1105
1170
  // Assume that a message is more precise if it comes later (i.e. may be included in the other).
1106
1171
  if (msg.$location.endLine && !existing.$location.endLine ||
1107
- (!msg.$location.endLine === !existing.$location.endLine && compareMessage(msg, existing) > 0)) {
1172
+ (!msg.$location.endLine === !existing.$location.endLine && compareMessage(msg, existing) > 0))
1108
1173
  seen.set(hash, msg);
1109
- }
1110
1174
  }
1111
1175
  }
1112
1176
 
@@ -1123,21 +1187,21 @@ function shortArtName( art ) {
1123
1187
  }
1124
1188
 
1125
1189
  function artName( art, omit ) {
1126
- let name = art.name;
1127
- let r = (name.absolute) ? [ quoted( name.absolute ) ] : [];
1190
+ const { name } = art;
1191
+ const r = (name.absolute) ? [ quoted( name.absolute ) ] : [];
1128
1192
  if (name.select && name.select > 1 || name.select != null && art.kind !== 'element') // Yes, omit select:1 for element - TODO: re-check
1129
1193
  r.push( (art.kind === 'extend' ? 'block:' : 'query:') + name.select ); // TODO: rename to 'select:1' and consider whether there are more selects
1130
1194
  if (name.action && omit !== 'action')
1131
- r.push( memberActionName(art) + ':' + quoted( name.action ) );
1195
+ r.push( `${ memberActionName(art) }:${ quoted( name.action ) }` );
1132
1196
  if (name.alias && art.kind !== '$self')
1133
- r.push( (art.kind === 'mixin' ? 'mixin:' : 'alias:') + quoted( name.alias ) )
1197
+ r.push( (art.kind === 'mixin' ? 'mixin:' : 'alias:') + quoted( name.alias ) );
1134
1198
  if (name.param != null && omit !== 'param')
1135
- r.push( name.param ? 'param:' + quoted( name.param ) : 'returns' ); // TODO: join
1199
+ r.push( name.param ? `param:${ quoted( name.param ) }` : 'returns' ); // TODO: join
1136
1200
  if (name.element && omit !== 'element')
1137
1201
  // r.push( `${ art.kind }: ${ quoted( name.element )}` ); or even better element:"assoc"/key:"i" same with enum
1138
1202
  r.push( (art.kind === 'enum' ? 'enum:' : 'element:') + quoted( name.element ) );
1139
1203
  if (art.kind === '$self')
1140
- r.push( 'alias:' + quoted( name.alias ) ) // should be late due to $self in anonymous aspect
1204
+ r.push( `alias:${ quoted( name.alias ) }` ); // should be late due to $self in anonymous aspect
1141
1205
  return r.join('/');
1142
1206
  }
1143
1207
 
@@ -1159,9 +1223,9 @@ function homeName( art, absoluteOnly ) {
1159
1223
  else if (art.kind === 'source' || !art.name) // error reported in parser or on source level
1160
1224
  return null;
1161
1225
  else if (art.kind === 'using')
1162
- return 'using:' + quoted( art.name.id );
1226
+ return `using:${ quoted( art.name.id ) }`;
1163
1227
  else if (art.kind === 'extend' || art.kind === 'annotate')
1164
- return !absoluteOnly && homeNameForExtend ( art );
1228
+ return !absoluteOnly && homeNameForExtend( art );
1165
1229
  else if (art.name._artifact) // block, extend, annotate
1166
1230
  return homeName( art.name._artifact, absoluteOnly ); // use corresponding definition
1167
1231
  else if (absoluteOnly)
@@ -1169,7 +1233,7 @@ function homeName( art, absoluteOnly ) {
1169
1233
  let main = art._main || art;
1170
1234
  while (main._outer) // anonymous aspect
1171
1235
  main = main._outer._main;
1172
- return main.kind + ':' + artName( art );
1236
+ return `${ main.kind }:${ artName( art ) }`;
1173
1237
  }
1174
1238
 
1175
1239
  // The "home" for extensions is handled differently because `_artifact` is not
@@ -1178,24 +1242,24 @@ function homeNameForExtend( art ) {
1178
1242
  const kind = art.kind || 'extend';
1179
1243
  // TODO: fix the following - do like in collectArtifactExtensions() or
1180
1244
  // basically resolveUncheckedPath()
1181
- const absoluteName = art.name.id != null ? art.name.id :
1182
- (!art.name.element && art.name.absolute || art.name.path && art.name.path.map(s => s && s.id).join('.'));
1245
+ const absoluteName = art.name.id != null ? art.name.id
1246
+ : (!art.name.element && art.name.absolute || art.name.path && art.name.path.map(s => s && s.id).join('.'));
1183
1247
 
1184
1248
  // Surrounding parent may be another extension.
1185
1249
  const parent = art._parent;
1186
1250
  if (!parent && art.name.absolute)
1187
- return kind + ':' + artName(removeBlock(art));
1251
+ return `${ kind }:${ artName(removeBlock(art)) }`;
1188
1252
  else if (!parent)
1189
- return kind + ':' + quoted(absoluteName);
1253
+ return `${ kind }:${ quoted(absoluteName) }`;
1190
1254
 
1191
1255
  if (art.name.param && parent.params) {
1192
1256
  const fakeArt = { kind: 'param', name: { param: absoluteName } };
1193
- return homeNameForExtend(parent) + '/' + artName(fakeArt);
1257
+ return `${ homeNameForExtend(parent) }/${ artName(fakeArt) }`;
1194
1258
  }
1195
1259
  else if (art.name.action && parent.actions) {
1196
1260
  const type = art.name._artifact?.kind || 'action';
1197
1261
  const fakeArt = { kind: type, name: { action: absoluteName }, _main: art.name._artifact?._main };
1198
- return homeNameForExtend(parent) + '/' + artName(fakeArt);
1262
+ return `${ homeNameForExtend(parent) }/${ artName(fakeArt) }`;
1199
1263
  }
1200
1264
  else if (parent.enum || parent.elements || parent.returns?.elements) {
1201
1265
  // For enum, extensions may store them in `elements`, i.e. don't differ between enum/elements,
@@ -1208,21 +1272,21 @@ function homeNameForExtend( art ) {
1208
1272
  while (parentOfElementChain.name?.element && parentOfElementChain._parent)
1209
1273
  parentOfElementChain = parentOfElementChain._parent;
1210
1274
 
1211
- return homeNameForExtend(parentOfElementChain) + '/' + artName(fakeArt);
1275
+ return `${ homeNameForExtend(parentOfElementChain) }/${ artName(fakeArt) }`;
1212
1276
  }
1213
1277
  // This case should not happen, but just in case
1214
- return kind + ':' + artName(parent);
1278
+ return `${ kind }:${ artName(parent) }`;
1215
1279
 
1216
1280
  /**
1217
1281
  * Remove `select` from the 'art's name to avoid `block:1` in artName().
1218
1282
  * @TODO: Refactor homeNameForExtend (and possibly artName) to get rid of this function
1219
- **/
1220
- function removeBlock(obj) {
1283
+ * */
1284
+ function removeBlock( obj ) {
1221
1285
  return { ...obj, name: { ...obj.name, select: null } };
1222
1286
  }
1223
1287
  }
1224
1288
 
1225
- function constructSemanticLocationFromCsnPath(csnPath, model) {
1289
+ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1226
1290
  if (!model)
1227
1291
  return null;
1228
1292
  // Copy because this function shift()s from the path.
@@ -1235,8 +1299,8 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1235
1299
  if (csnPath[0] === 'extensions') {
1236
1300
  const ext = model.extensions && model.extensions[csnPath[1]] || {};
1237
1301
  if (ext.annotate)
1238
- return 'annotate:' + quoted(ext.annotate);
1239
- return 'extend:' + quoted(ext.extend);
1302
+ return `annotate:${ quoted(ext.annotate) }`;
1303
+ return `extend:${ quoted(ext.extend) }`;
1240
1304
  }
1241
1305
 
1242
1306
  let { query } = analyseCsnPath(csnPath, model, false);
@@ -1249,7 +1313,7 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1249
1313
  ? currentThing.kind
1250
1314
  : (dictName === 'vocabularies'
1251
1315
  ? 'annotation'
1252
- : 'artifact') }:${ _quoted(artifactName) }`;
1316
+ : 'artifact') }:${ quoted(artifactName) }`;
1253
1317
 
1254
1318
  if (!currentThing)
1255
1319
  return result;
@@ -1269,6 +1333,7 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1269
1333
  let inColumn = false;
1270
1334
  let inMixin = false;
1271
1335
  let inItems = false;
1336
+ let currentAnno = null;
1272
1337
 
1273
1338
  // for top level actions
1274
1339
  if (currentThing.kind === 'action')
@@ -1279,7 +1344,7 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1279
1344
  inCsnDict = true;
1280
1345
  switch (step) {
1281
1346
  case 'elements':
1282
- if (!inElement){
1347
+ if (!inElement) {
1283
1348
  inElement = true;
1284
1349
  // do not print intermediate items
1285
1350
  inItems = false;
@@ -1382,13 +1447,13 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1382
1447
  else if ( inKeys || inColumn) {
1383
1448
  if (typeof step === 'number') {
1384
1449
  if (currentThing.as)
1385
- result += `:${ _quoted(currentThing.as) }`;
1450
+ result += `:${ quoted(currentThing.as) }`;
1386
1451
  else if (inRef)
1387
- result += `:${ _quoted(currentThing) }`;
1452
+ result += `:${ quoted(currentThing) }`;
1388
1453
  else if (currentThing.ref)
1389
- result += `:${ _quoted(currentThing.ref.map(r => r.id ? r.id : r).join('.')) }`;
1454
+ result += `:${ quoted(currentThing.ref.map(r => (r.id ? r.id : r)).join('.')) }`;
1390
1455
  else
1391
- return'';
1456
+ return '';
1392
1457
 
1393
1458
  break;
1394
1459
  }
@@ -1410,11 +1475,17 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1410
1475
 
1411
1476
  inCsnDict = false;
1412
1477
  }
1478
+ else if (step[0] === '@') {
1479
+ currentAnno = step;
1480
+ break; // we don't go inside annotation values
1481
+ }
1413
1482
  }
1414
1483
  if ( inItems )
1415
1484
  result += `${ element() }/items`;
1416
1485
  else if ( inElement )
1417
1486
  result += element();
1487
+ if ( currentAnno )
1488
+ result += `/${ quoted(currentAnno) }`;
1418
1489
  return result;
1419
1490
 
1420
1491
  function select() {
@@ -1422,27 +1493,29 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1422
1493
  s += query.isOnlySelect ? '' : `:${ query.depth }`;
1423
1494
  return s;
1424
1495
  }
1425
- function selectAndMixin(name) {
1426
- return `${ select() }/mixin:${ _quoted(name) }`;
1496
+ function selectAndMixin( name ) {
1497
+ return `${ select() }/mixin:${ quoted(name) }`;
1427
1498
  }
1428
1499
  function element() {
1429
- return `/element:${ _quoted(elements.join('.')) }`;
1500
+ return `/element:${ quoted(elements.join('.')) }`;
1430
1501
  }
1431
- function param(name) {
1432
- return `/param:${ _quoted(name) }`;
1502
+ function param( name ) {
1503
+ return `/param:${ quoted(name) }`;
1433
1504
  }
1434
- function func(name) {
1435
- return `/function:${ _quoted(name) }`;
1505
+ function func( name ) {
1506
+ if (currentThing?.kind)
1507
+ return `/${ currentThing.kind }:${ quoted(name) }`;
1508
+ return `/action:${ quoted(name) }`;
1436
1509
  }
1437
- function elementAndEnum(name) {
1438
- return `${ element() }/enum:${ _quoted(name) }`;
1510
+ function elementAndEnum( name ) {
1511
+ return `${ element() }/enum:${ quoted(name) }`;
1439
1512
  }
1440
1513
 
1441
1514
  /**
1442
1515
  * Traverse rootQuery until targetQuery is found and count the depth,
1443
1516
  * check if targetQuery is only select in entity.
1444
1517
  */
1445
- function queryDepth(rootQuery, targetQuery) {
1518
+ function queryDepth( rootQuery, targetQuery ) {
1446
1519
  let targetQueryDepth = 1;
1447
1520
  let totalQueryDepth = 0;
1448
1521
 
@@ -1459,11 +1532,6 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1459
1532
  }
1460
1533
  }
1461
1534
 
1462
-
1463
- function _quoted( name ) {
1464
- return (name) ? `"${ name.replace( /"/g, '""' ) }"` : '<?>'; // sync ";
1465
- }
1466
-
1467
1535
  /**
1468
1536
  * Get the explanation string for the given message-id.
1469
1537
  * Ensure to have called hasMessageExplanation() before.
@@ -1473,9 +1541,9 @@ function _quoted( name ) {
1473
1541
  * @throws May throw an ENOENT error if the file cannot be found.
1474
1542
  * @see hasMessageExplanation()
1475
1543
  */
1476
- function explainMessage(messageId) {
1544
+ function explainMessage( messageId ) {
1477
1545
  messageId = oldMessageIds[messageId] || messageId;
1478
- const filename = path.join(__dirname, '..', '..', 'share', 'messages', `${messageId}.md`);
1546
+ const filename = path.join(__dirname, '..', '..', 'share', 'messages', `${ messageId }.md`);
1479
1547
  return fs.readFileSync(filename, 'utf8');
1480
1548
  }
1481
1549
 
@@ -1487,7 +1555,7 @@ function explainMessage(messageId) {
1487
1555
  * @param {string} messageId
1488
1556
  * @returns {boolean}
1489
1557
  */
1490
- function hasMessageExplanation(messageId) {
1558
+ function hasMessageExplanation( messageId ) {
1491
1559
  const id = oldMessageIds[messageId] || messageId || false;
1492
1560
  return id && _messageIdsWithExplanation.includes(id);
1493
1561
  }