@sap/cds-compiler 3.4.2 → 3.5.0

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 (143) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +1 -0
  3. package/bin/cds_update_identifiers.js +5 -5
  4. package/bin/cdsc.js +15 -16
  5. package/bin/cdshi.js +19 -6
  6. package/doc/CHANGELOG_ARCHIVE.md +2 -2
  7. package/doc/CHANGELOG_BETA.md +9 -1
  8. package/doc/CHANGELOG_DEPRECATED.md +2 -0
  9. package/lib/api/main.js +61 -59
  10. package/lib/api/options.js +4 -2
  11. package/lib/api/validate.js +2 -2
  12. package/lib/base/cleanSymbols.js +2 -3
  13. package/lib/base/dictionaries.js +6 -6
  14. package/lib/base/error.js +2 -2
  15. package/lib/base/keywords.js +6 -6
  16. package/lib/base/location.js +11 -12
  17. package/lib/base/message-registry.js +177 -58
  18. package/lib/base/messages.js +252 -180
  19. package/lib/base/model.js +14 -11
  20. package/lib/base/node-helpers.js +9 -10
  21. package/lib/base/optionProcessorHelper.js +138 -129
  22. package/lib/checks/.eslintrc.json +2 -0
  23. package/lib/checks/actionsFunctions.js +5 -5
  24. package/lib/checks/annotationsOData.js +4 -4
  25. package/lib/checks/arrayOfs.js +1 -1
  26. package/lib/checks/cdsPersistence.js +1 -1
  27. package/lib/checks/checkForTypes.js +3 -3
  28. package/lib/checks/defaultValues.js +3 -3
  29. package/lib/checks/elements.js +7 -7
  30. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  31. package/lib/checks/foreignKeys.js +1 -1
  32. package/lib/checks/invalidTarget.js +4 -4
  33. package/lib/checks/managedInType.js +1 -1
  34. package/lib/checks/managedWithoutKeys.js +1 -1
  35. package/lib/checks/nonexpandableStructured.js +5 -3
  36. package/lib/checks/nullableKeys.js +1 -1
  37. package/lib/checks/onConditions.js +5 -6
  38. package/lib/checks/parameters.js +1 -1
  39. package/lib/checks/queryNoDbArtifacts.js +2 -2
  40. package/lib/checks/selectItems.js +4 -4
  41. package/lib/checks/sql-snippets.js +4 -4
  42. package/lib/checks/types.js +7 -7
  43. package/lib/checks/utils.js +4 -4
  44. package/lib/checks/validator.js +16 -13
  45. package/lib/compiler/.eslintrc.json +4 -1
  46. package/lib/compiler/assert-consistency.js +8 -7
  47. package/lib/compiler/builtins.js +14 -14
  48. package/lib/compiler/checks.js +123 -48
  49. package/lib/compiler/define.js +12 -13
  50. package/lib/compiler/extend.js +266 -60
  51. package/lib/compiler/finalize-parse-cdl.js +10 -5
  52. package/lib/compiler/index.js +17 -14
  53. package/lib/compiler/populate.js +14 -6
  54. package/lib/compiler/propagator.js +2 -0
  55. package/lib/compiler/resolve.js +2 -15
  56. package/lib/compiler/shared.js +27 -16
  57. package/lib/compiler/tweak-assocs.js +5 -6
  58. package/lib/compiler/utils.js +20 -0
  59. package/lib/edm/annotations/genericTranslation.js +604 -358
  60. package/lib/edm/annotations/preprocessAnnotations.js +39 -35
  61. package/lib/edm/csn2edm.js +275 -222
  62. package/lib/edm/edm.js +17 -3
  63. package/lib/edm/edmAnnoPreprocessor.js +6 -6
  64. package/lib/edm/edmInboundChecks.js +2 -2
  65. package/lib/edm/edmPreprocessor.js +107 -77
  66. package/lib/edm/edmUtils.js +44 -5
  67. package/lib/gen/Dictionary.json +210 -8
  68. package/lib/gen/language.checksum +1 -1
  69. package/lib/gen/language.interp +67 -63
  70. package/lib/gen/language.tokens +81 -81
  71. package/lib/gen/languageLexer.interp +4 -10
  72. package/lib/gen/languageLexer.js +854 -869
  73. package/lib/gen/languageLexer.tokens +79 -81
  74. package/lib/gen/languageParser.js +14309 -13832
  75. package/lib/inspect/inspectModelStatistics.js +2 -2
  76. package/lib/inspect/inspectPropagation.js +6 -6
  77. package/lib/inspect/inspectUtils.js +2 -2
  78. package/lib/json/from-csn.js +102 -55
  79. package/lib/json/to-csn.js +119 -198
  80. package/lib/language/antlrParser.js +5 -2
  81. package/lib/language/docCommentParser.js +6 -6
  82. package/lib/language/errorStrategy.js +43 -23
  83. package/lib/language/genericAntlrParser.js +113 -133
  84. package/lib/language/language.g4 +1550 -1506
  85. package/lib/language/multiLineStringParser.js +3 -3
  86. package/lib/language/textUtils.js +2 -2
  87. package/lib/main.js +3 -3
  88. package/lib/model/csnRefs.js +5 -0
  89. package/lib/model/csnUtils.js +130 -122
  90. package/lib/model/revealInternalProperties.js +1 -1
  91. package/lib/model/sortViews.js +4 -6
  92. package/lib/modelCompare/compare.js +2 -2
  93. package/lib/modelCompare/utils/.eslintrc.json +22 -0
  94. package/lib/modelCompare/utils/filter.js +100 -0
  95. package/lib/optionProcessor.js +5 -0
  96. package/lib/render/.eslintrc.json +1 -0
  97. package/lib/render/DuplicateChecker.js +1 -1
  98. package/lib/render/manageConstraints.js +12 -12
  99. package/lib/render/toCdl.js +311 -276
  100. package/lib/render/toHdbcds.js +97 -94
  101. package/lib/render/toRename.js +5 -5
  102. package/lib/render/toSql.js +127 -223
  103. package/lib/render/utils/common.js +141 -108
  104. package/lib/render/utils/delta.js +227 -0
  105. package/lib/render/utils/sql.js +22 -6
  106. package/lib/render/utils/stringEscapes.js +3 -3
  107. package/lib/transform/db/.eslintrc.json +2 -0
  108. package/lib/transform/db/applyTransformations.js +3 -3
  109. package/lib/transform/db/assertUnique.js +13 -12
  110. package/lib/transform/db/associations.js +5 -5
  111. package/lib/transform/db/cdsPersistence.js +10 -8
  112. package/lib/transform/db/constraints.js +14 -14
  113. package/lib/transform/db/expansion.js +20 -22
  114. package/lib/transform/db/flattening.js +24 -42
  115. package/lib/transform/db/groupByOrderBy.js +3 -3
  116. package/lib/transform/db/temporal.js +6 -6
  117. package/lib/transform/db/transformExists.js +23 -23
  118. package/lib/transform/db/views.js +16 -16
  119. package/lib/transform/draft/.eslintrc.json +1 -35
  120. package/lib/transform/draft/db.js +10 -10
  121. package/lib/transform/draft/odata.js +2 -2
  122. package/lib/transform/forOdataNew.js +8 -29
  123. package/lib/transform/forRelationalDB.js +16 -6
  124. package/lib/transform/localized.js +11 -10
  125. package/lib/transform/odata/toFinalBaseType.js +41 -27
  126. package/lib/transform/odata/typesExposure.js +113 -47
  127. package/lib/transform/parseExpr.js +209 -106
  128. package/lib/transform/transformUtilsNew.js +17 -10
  129. package/lib/transform/translateAssocsToJoins.js +24 -19
  130. package/lib/transform/universalCsn/coreComputed.js +10 -10
  131. package/lib/transform/universalCsn/universalCsnEnricher.js +26 -26
  132. package/lib/transform/universalCsn/utils.js +3 -3
  133. package/lib/utils/file.js +5 -5
  134. package/lib/utils/moduleResolve.js +13 -13
  135. package/lib/utils/objectUtils.js +6 -6
  136. package/lib/utils/term.js +5 -2
  137. package/lib/utils/timetrace.js +51 -24
  138. package/package.json +5 -8
  139. package/share/messages/check-proper-type-of.md +1 -1
  140. package/share/messages/message-explanations.json +1 -1
  141. package/share/messages/redirected-to-complex.md +4 -4
  142. package/share/messages/{syntax-expecting-integer.md → syntax-expecting-unsigned-int.md} +7 -4
  143. package/lib/modelCompare/filter.js +0 -83
@@ -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,51 +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(', '),
658
- delimited: n => '![' + n + ']', // TODO: use quote.single around?
670
+ anno,
671
+ annos: transformManyWith( anno ),
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
- 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,
675
693
  art: transformArg,
676
694
  service: transformArg,
677
695
  sorted_arts: transformManyWith( transformArg, true ),
@@ -679,6 +697,7 @@ const paramsTransform = {
679
697
  source: transformArg,
680
698
  elemref: transformElementRef,
681
699
  type: transformArg,
700
+ othertype: transformArg,
682
701
  offending: tokenSymbol,
683
702
  op: quote.single,
684
703
  expecting: transformManyWith( tokenSymbol ),
@@ -687,13 +706,53 @@ const paramsTransform = {
687
706
  version: quote.single, // TODO delete: just use for OData $(VERSION), with version: 2.0
688
707
  };
689
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
+
690
749
  function ignoreTextTransform() {
691
750
  return null;
692
751
  }
693
752
 
694
753
  function transformManyWith( t, sorted ) {
695
754
  return function transformMany( many, r, args, texts ) {
696
- const prop = ['none','one','two'][ many.length ];
755
+ const prop = [ 'none', 'one', 'two' ][many.length];
697
756
  const names = many.map(t);
698
757
  if (sorted)
699
758
  names.sort();
@@ -707,7 +766,9 @@ function transformManyWith( t, sorted ) {
707
766
  }
708
767
 
709
768
  function quoted( name ) {
710
- return (name) ? quote.double( name ) : quote.angle( '?' ); // TODO: failure in --test-mode, then remove
769
+ if (typeof name === 'string')
770
+ return quote.double( name );
771
+ throw new CompilerAssertion( `Expecting a string, not ${ name }` );
711
772
  }
712
773
 
713
774
  function tokenSymbol( token ) {
@@ -715,21 +776,20 @@ function tokenSymbol( token ) {
715
776
  return quote.upper( token );
716
777
  else if (token.match( /^[A-Z][a-z]/ )) // Number, Identifier, ...
717
778
  return quote.angle( token );
718
- if (token.startsWith("'") && token.endsWith("'")) // operator token symbol
779
+ if (token.startsWith('\'') && token.endsWith('\'')) // operator token symbol
719
780
  return quote.single( token.slice( 1, -1 ));
720
781
  else if (token === '<EOF>')
721
782
  return quote.angle( 'EOF' );
722
- else
723
- return quote.single( token ); // should not happen
783
+ return quote.single( token ); // should not happen
724
784
  }
725
785
 
726
786
  /**
727
787
  * Transform an element reference (/path), e.g. on-condition path.
728
788
  */
729
- function transformElementRef(arg) {
789
+ function transformElementRef( arg ) {
730
790
  if (arg.ref) {
731
791
  // Can be used by CSN backends to create a simple path such as E:elem
732
- return quoted(arg.ref.map(ref => {
792
+ return quoted(arg.ref.map((ref) => {
733
793
  if (ref.id) {
734
794
  // Indicate that the path has a filter.
735
795
  if (ref.where)
@@ -754,16 +814,16 @@ function transformArg( arg, r, args, texts ) {
754
814
  if (arg.ref) {
755
815
  // Can be used by CSN backends to create a simple path such as E:elem
756
816
  if (arg.ref.length > 1)
757
- return quoted(arg.ref[0] + ':' + arg.ref.slice(1).join('.'));
817
+ return quoted(`${ arg.ref[0] }:${ arg.ref.slice(1).join('.') }`);
758
818
  return quoted(arg.ref);
759
819
  }
760
- let name = arg.name;
820
+ const { name } = arg;
761
821
  if (!name)
762
822
  return quoted( name );
763
- let prop = ['element','param','action','alias'].find( p => name[p] );
823
+ const prop = [ 'element', 'param', 'action', 'alias' ].find( p => name[p] );
764
824
  if (!prop || !texts[prop] )
765
825
  return shortArtName( arg );
766
- 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)
767
827
  r.member = quoted( name[prop] );
768
828
  return artName( arg, prop );
769
829
  }
@@ -778,7 +838,7 @@ function searchName( art, id, variant ) {
778
838
  if (!variant) {
779
839
  // used to mention the "effective" type in the message, not the
780
840
  // originally provided one (TODO: mention that in the message text)
781
- let type = art._effectiveType && art._effectiveType.kind !== 'undefined' ? art._effectiveType : art;
841
+ const type = art._effectiveType && art._effectiveType.kind !== 'undefined' ? art._effectiveType : art;
782
842
  if (type.elements) { // only mentioned elements
783
843
  art = type.target && type.target._artifact || type;
784
844
  variant = 'element';
@@ -787,42 +847,45 @@ function searchName( art, id, variant ) {
787
847
  variant = 'absolute';
788
848
  }
789
849
  }
790
- let prop = nameProp[variant] || variant;
791
- let name = Object.assign( { $variant: variant }, (art._artifact||art).name );
792
- 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 || '?';
793
853
  return { name, kind: art.kind };
794
854
  }
795
855
 
796
856
  function messageText( texts, params, transform ) {
797
857
  if (typeof texts === 'string')
798
858
  texts = { std: texts };
799
- let args = {};
800
- for (let p in params) {
801
- let t = transform && transform[p] || paramsTransform[p];
802
- args[p] = (t) ? t( params[p], args, params, texts ) : params[p];
859
+ const args = {};
860
+ for (const p in params) {
861
+ if (params[p] !== undefined) {
862
+ const t = transform && transform[p] || paramsTransform[p];
863
+ args[p] = (t) ? t( params[p], args, params, texts ) : params[p];
864
+ }
803
865
  }
804
- let variant = args['#'];
805
- return replaceInString( variant && texts[ variant ] || texts.std, args );
866
+ const variant = args['#'];
867
+ return replaceInString( variant && texts[variant] || texts.std, args );
806
868
  }
807
869
 
808
870
  function replaceInString( text, params ) {
809
871
  const usedParams = [ '#', '$reviewed' ];
810
- let pattern = /\$\(([A-Z_]+)\)/g;
811
- let parts = [];
872
+ const pattern = /\$\(([A-Z_]+)\)/g;
873
+ const parts = [];
812
874
  let start = 0;
813
875
  for (let p = pattern.exec( text ); p; p = pattern.exec( text )) {
814
- let prop = p[1].toLowerCase();
876
+ const prop = p[1].toLowerCase();
815
877
  parts.push( text.substring( start, p.index ),
816
878
  (prop in params ? params[prop] : p[0]) );
817
879
  usedParams.push(prop);
818
880
  start = pattern.lastIndex;
819
881
  }
820
882
  parts.push( text.substring( start ) );
821
- let remain = (params['#']) ? [] : Object.keys( params ).filter( n => !usedParams.includes(n) );
822
- return (remain.length)
823
- ? parts.join('') + '; ' +
824
- remain.map( n => n.toUpperCase() + ' = ' + params[n] ).join(', ')
825
- : 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('');
826
889
  }
827
890
 
828
891
  /**
@@ -830,6 +893,8 @@ function replaceInString( text, params ) {
830
893
  * @returns {CSN.Location}
831
894
  */
832
895
  function weakLocation( loc ) {
896
+ if (!loc)
897
+ return {};
833
898
  // no endLine/endCol
834
899
  return { file: loc.file, line: loc.line, col: loc.col };
835
900
  }
@@ -848,14 +913,14 @@ function weakLocation( loc ) {
848
913
  */
849
914
  function messageString( err, normalizeFilename, noMessageId, noHome ) {
850
915
  return (err.$location && err.$location.file
851
- ? locationString( err.$location, normalizeFilename ) + ': '
852
- : '') +
916
+ ? `${ locationString( err.$location, normalizeFilename ) }: `
917
+ : '') +
853
918
  (err.severity || 'Error') +
854
919
  // TODO: use [message-id]
855
- (err.messageId && !noMessageId ? ' ' + err.messageId + ': ' : ': ') +
920
+ (err.messageId && !noMessageId ? ` ${ err.messageId }: ` : ': ') +
856
921
  err.message +
857
922
  // even with noHome, print err.home if the location is weak
858
- (!err.home || noHome && err.$location && err.$location.endLine ? '' : ' (in ' + err.home + ')');
923
+ (!err.home || noHome && err.$location && err.$location.endLine ? '' : ` (in ${ err.home })`);
859
924
  }
860
925
 
861
926
  /**
@@ -865,11 +930,11 @@ function messageString( err, normalizeFilename, noMessageId, noHome ) {
865
930
  * @param {CompileMessage} msg
866
931
  * @returns {string} can be used to uniquely identify a message
867
932
  */
868
- function messageHash(msg) {
933
+ function messageHash( msg ) {
869
934
  // parser messages do not provide semantic location, therefore we need to use the file location
870
- if(!msg.home)
935
+ if (!msg.home)
871
936
  return messageString(msg);
872
- const copy = {...msg};
937
+ const copy = { ...msg };
873
938
  // Note: This is a hack. deduplicateMessages() would otherwise remove
874
939
  // all but one message about duplicated artifacts.
875
940
  if (!msg.messageId || !msg.messageId.includes('duplicate'))
@@ -905,25 +970,26 @@ function messageStringMultiline( err, config = {} ) {
905
970
 
906
971
  const explainHelp = (config.hintExplanation && hasMessageExplanation(err.messageId)) ? '…' : '';
907
972
  const msgId = (err.messageId && !config.noMessageId) ? `[${ err.messageId }${ explainHelp }]` : '';
908
- const home = !err.home ? '' : ('at ' + err.home);
973
+ const home = !err.home ? '' : (`at ${ err.home }`);
909
974
  const severity = err.severity || 'Error';
910
975
 
911
976
  let location = '';
912
977
  if (err.$location && err.$location.file) {
913
- location += locationString( err.$location, config.normalizeFilename )
978
+ location += locationString( err.$location, config.normalizeFilename );
914
979
  if (home)
915
- location += ', '
980
+ location += ', ';
981
+ }
982
+ else if (!home) {
983
+ return `${ colorTerm.severity(severity, severity + msgId) } ${ err.message }`;
916
984
  }
917
- else if (!home)
918
- return colorTerm.severity(severity, severity + msgId) + ' ' + err.message;
919
985
 
920
986
  let lineSpacer = '';
921
987
  if (config.withLineSpacer) {
922
- 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;
923
989
  lineSpacer = `\n ${ ' '.repeat( additionalIndent ) }|`;
924
990
  }
925
991
 
926
- 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 }`;
927
993
  }
928
994
 
929
995
  /**
@@ -944,23 +1010,23 @@ function messageStringMultiline( err, config = {} ) {
944
1010
  * unset.
945
1011
  * @returns {string}
946
1012
  */
947
- function messageContext(sourceLines, err, config) {
1013
+ function messageContext( sourceLines, err, config ) {
948
1014
  colorTerm.changeColorMode(config ? config.color : 'auto');
949
1015
  const MAX_COL_LENGTH = 100;
950
1016
 
951
1017
  const loc = err.$location;
952
- if (!loc || !loc.line) {
1018
+ if (!loc || !loc.line)
953
1019
  return '';
954
- }
1020
+
955
1021
 
956
1022
  // Lines are 1-based, we need 0-based ones for arrays
957
1023
  const startLine = loc.line - 1;
958
1024
  const endLine = loc.endLine ? loc.endLine - 1 : startLine;
959
1025
 
960
1026
  // check that source lines exists
961
- if (typeof sourceLines[startLine] !== 'string' || typeof sourceLines[endLine] !== 'string') {
1027
+ if (typeof sourceLines[startLine] !== 'string' || typeof sourceLines[endLine] !== 'string')
962
1028
  return '';
963
- }
1029
+
964
1030
 
965
1031
  const digits = String(endLine + 1).length;
966
1032
  const severity = err.severity || 'Error';
@@ -977,7 +1043,7 @@ function messageContext(sourceLines, err, config) {
977
1043
  /** Only print N lines even if the error spans more lines. */
978
1044
  const maxLine = Math.min((startLine + 2), endLine);
979
1045
 
980
- let msg = indent + '|\n';
1046
+ let msg = `${ indent }|\n`;
981
1047
 
982
1048
  // print source line(s)
983
1049
  for (let line = startLine; line <= maxLine; line++) {
@@ -986,8 +1052,8 @@ function messageContext(sourceLines, err, config) {
986
1052
  if (sourceCode.length >= MAX_COL_LENGTH)
987
1053
  sourceCode = sourceCode.slice(0, MAX_COL_LENGTH);
988
1054
  // Only prepend space if the line contains any sources.
989
- sourceCode = sourceCode.length ? ' ' + sourceCode : '';
990
- msg += ' ' + String(line + 1).padStart(digits, ' ') + ' |' + sourceCode + '\n';
1055
+ sourceCode = sourceCode.length ? ` ${ sourceCode }` : '';
1056
+ msg += ` ${ String(line + 1).padStart(digits, ' ') } |${ sourceCode }\n`;
991
1057
  }
992
1058
 
993
1059
  if (startLine === endLine && loc.col > 0) {
@@ -997,14 +1063,14 @@ function messageContext(sourceLines, err, config) {
997
1063
  // Indicate that the error is further to the right.
998
1064
  if (endColumn === MAX_COL_LENGTH)
999
1065
  highlighter = highlighter.replace(' ^', '..^');
1000
- msg += indent + '| ' + colorTerm.severity(severity, highlighter);
1001
-
1002
- } else if (maxLine !== endLine) {
1066
+ msg += `${ indent }| ${ colorTerm.severity(severity, highlighter) }`;
1067
+ }
1068
+ else if (maxLine !== endLine) {
1003
1069
  // error spans more lines which we don't print
1004
- msg += indent + '| ...';
1005
-
1006
- } else {
1007
- msg += indent + '|';
1070
+ msg += `${ indent }| ...`;
1071
+ }
1072
+ else {
1073
+ msg += `${ indent }|`;
1008
1074
  }
1009
1075
 
1010
1076
  return msg;
@@ -1033,13 +1099,14 @@ function compareMessage( a, b ) {
1033
1099
  // TODO: severities?
1034
1100
  c( a.message, b.message ) );
1035
1101
  }
1036
- else if (!aFile && !bFile)
1102
+ else if (!aFile && !bFile) {
1037
1103
  return ( c( homeSortName( a ), homeSortName( b ) ) ||
1038
1104
  c( a.message, b.message ) );
1039
- else if (!aFile)
1105
+ }
1106
+ else if (!aFile) {
1040
1107
  return (a.messageId && a.messageId.startsWith( 'api-' )) ? -1 : 1;
1041
- else
1042
- return (b.messageId && b.messageId.startsWith( 'api-' )) ? 1 : -1;
1108
+ }
1109
+ return (b.messageId && b.messageId.startsWith( 'api-' )) ? 1 : -1;
1043
1110
 
1044
1111
  function c( x, y ) {
1045
1112
  return (x === y) ? 0 : (x > y) ? 1 : -1;
@@ -1068,9 +1135,8 @@ function compareMessageSeverityAware( a, b ) {
1068
1135
  */
1069
1136
  function homeSortName( { home, messageId } ) {
1070
1137
  if (!home)
1071
- return (messageId && /^(syntax|api)-/.test( messageId ) ? ' ' + messageId : '~')
1072
- else
1073
- 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
1074
1140
  }
1075
1141
 
1076
1142
  /**
@@ -1087,22 +1153,24 @@ function homeSortName( { home, messageId } ) {
1087
1153
  * @param {CompileMessage[]} messages
1088
1154
  */
1089
1155
  function deduplicateMessages( messages ) {
1156
+ // sort messages to make it processing sequence independent which messages (with
1157
+ // which $location!) wins
1158
+ messages.sort(compareMessage);
1090
1159
  const seen = new Map();
1091
1160
  for (const msg of messages) {
1092
1161
  const hash = messageHash(msg);
1093
1162
 
1094
1163
  if (!seen.has(hash)) {
1095
1164
  seen.set(hash, msg);
1096
-
1097
- } else if (msg.$location) {
1165
+ }
1166
+ else if (msg.$location) {
1098
1167
  const existing = seen.get(hash);
1099
1168
  // If this messages has an end but the existing does not, then the new message is more precise.
1100
1169
  // If both messages do (or don't) have an endLine, then compare them based on their location.
1101
1170
  // Assume that a message is more precise if it comes later (i.e. may be included in the other).
1102
1171
  if (msg.$location.endLine && !existing.$location.endLine ||
1103
- (!msg.$location.endLine === !existing.$location.endLine && compareMessage(msg, existing) > 0)) {
1172
+ (!msg.$location.endLine === !existing.$location.endLine && compareMessage(msg, existing) > 0))
1104
1173
  seen.set(hash, msg);
1105
- }
1106
1174
  }
1107
1175
  }
1108
1176
 
@@ -1119,21 +1187,21 @@ function shortArtName( art ) {
1119
1187
  }
1120
1188
 
1121
1189
  function artName( art, omit ) {
1122
- let name = art.name;
1123
- let r = (name.absolute) ? [ quoted( name.absolute ) ] : [];
1190
+ const { name } = art;
1191
+ const r = (name.absolute) ? [ quoted( name.absolute ) ] : [];
1124
1192
  if (name.select && name.select > 1 || name.select != null && art.kind !== 'element') // Yes, omit select:1 for element - TODO: re-check
1125
1193
  r.push( (art.kind === 'extend' ? 'block:' : 'query:') + name.select ); // TODO: rename to 'select:1' and consider whether there are more selects
1126
1194
  if (name.action && omit !== 'action')
1127
- r.push( memberActionName(art) + ':' + quoted( name.action ) );
1195
+ r.push( `${ memberActionName(art) }:${ quoted( name.action ) }` );
1128
1196
  if (name.alias && art.kind !== '$self')
1129
- r.push( (art.kind === 'mixin' ? 'mixin:' : 'alias:') + quoted( name.alias ) )
1197
+ r.push( (art.kind === 'mixin' ? 'mixin:' : 'alias:') + quoted( name.alias ) );
1130
1198
  if (name.param != null && omit !== 'param')
1131
- r.push( name.param ? 'param:' + quoted( name.param ) : 'returns' ); // TODO: join
1199
+ r.push( name.param ? `param:${ quoted( name.param ) }` : 'returns' ); // TODO: join
1132
1200
  if (name.element && omit !== 'element')
1133
1201
  // r.push( `${ art.kind }: ${ quoted( name.element )}` ); or even better element:"assoc"/key:"i" same with enum
1134
1202
  r.push( (art.kind === 'enum' ? 'enum:' : 'element:') + quoted( name.element ) );
1135
1203
  if (art.kind === '$self')
1136
- 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
1137
1205
  return r.join('/');
1138
1206
  }
1139
1207
 
@@ -1155,9 +1223,9 @@ function homeName( art, absoluteOnly ) {
1155
1223
  else if (art.kind === 'source' || !art.name) // error reported in parser or on source level
1156
1224
  return null;
1157
1225
  else if (art.kind === 'using')
1158
- return 'using:' + quoted( art.name.id );
1226
+ return `using:${ quoted( art.name.id ) }`;
1159
1227
  else if (art.kind === 'extend' || art.kind === 'annotate')
1160
- return !absoluteOnly && homeNameForExtend ( art );
1228
+ return !absoluteOnly && homeNameForExtend( art );
1161
1229
  else if (art.name._artifact) // block, extend, annotate
1162
1230
  return homeName( art.name._artifact, absoluteOnly ); // use corresponding definition
1163
1231
  else if (absoluteOnly)
@@ -1165,7 +1233,7 @@ function homeName( art, absoluteOnly ) {
1165
1233
  let main = art._main || art;
1166
1234
  while (main._outer) // anonymous aspect
1167
1235
  main = main._outer._main;
1168
- return main.kind + ':' + artName( art );
1236
+ return `${ main.kind }:${ artName( art ) }`;
1169
1237
  }
1170
1238
 
1171
1239
  // The "home" for extensions is handled differently because `_artifact` is not
@@ -1174,24 +1242,24 @@ function homeNameForExtend( art ) {
1174
1242
  const kind = art.kind || 'extend';
1175
1243
  // TODO: fix the following - do like in collectArtifactExtensions() or
1176
1244
  // basically resolveUncheckedPath()
1177
- const absoluteName = art.name.id != null ? art.name.id :
1178
- (!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('.'));
1179
1247
 
1180
1248
  // Surrounding parent may be another extension.
1181
1249
  const parent = art._parent;
1182
1250
  if (!parent && art.name.absolute)
1183
- return kind + ':' + artName(removeBlock(art));
1251
+ return `${ kind }:${ artName(removeBlock(art)) }`;
1184
1252
  else if (!parent)
1185
- return kind + ':' + quoted(absoluteName);
1253
+ return `${ kind }:${ quoted(absoluteName) }`;
1186
1254
 
1187
1255
  if (art.name.param && parent.params) {
1188
1256
  const fakeArt = { kind: 'param', name: { param: absoluteName } };
1189
- return homeNameForExtend(parent) + '/' + artName(fakeArt);
1257
+ return `${ homeNameForExtend(parent) }/${ artName(fakeArt) }`;
1190
1258
  }
1191
1259
  else if (art.name.action && parent.actions) {
1192
1260
  const type = art.name._artifact?.kind || 'action';
1193
1261
  const fakeArt = { kind: type, name: { action: absoluteName }, _main: art.name._artifact?._main };
1194
- return homeNameForExtend(parent) + '/' + artName(fakeArt);
1262
+ return `${ homeNameForExtend(parent) }/${ artName(fakeArt) }`;
1195
1263
  }
1196
1264
  else if (parent.enum || parent.elements || parent.returns?.elements) {
1197
1265
  // For enum, extensions may store them in `elements`, i.e. don't differ between enum/elements,
@@ -1204,21 +1272,21 @@ function homeNameForExtend( art ) {
1204
1272
  while (parentOfElementChain.name?.element && parentOfElementChain._parent)
1205
1273
  parentOfElementChain = parentOfElementChain._parent;
1206
1274
 
1207
- return homeNameForExtend(parentOfElementChain) + '/' + artName(fakeArt);
1275
+ return `${ homeNameForExtend(parentOfElementChain) }/${ artName(fakeArt) }`;
1208
1276
  }
1209
1277
  // This case should not happen, but just in case
1210
- return kind + ':' + artName(parent);
1278
+ return `${ kind }:${ artName(parent) }`;
1211
1279
 
1212
1280
  /**
1213
1281
  * Remove `select` from the 'art's name to avoid `block:1` in artName().
1214
1282
  * @TODO: Refactor homeNameForExtend (and possibly artName) to get rid of this function
1215
- **/
1216
- function removeBlock(obj) {
1283
+ * */
1284
+ function removeBlock( obj ) {
1217
1285
  return { ...obj, name: { ...obj.name, select: null } };
1218
1286
  }
1219
1287
  }
1220
1288
 
1221
- function constructSemanticLocationFromCsnPath(csnPath, model) {
1289
+ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1222
1290
  if (!model)
1223
1291
  return null;
1224
1292
  // Copy because this function shift()s from the path.
@@ -1231,8 +1299,8 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1231
1299
  if (csnPath[0] === 'extensions') {
1232
1300
  const ext = model.extensions && model.extensions[csnPath[1]] || {};
1233
1301
  if (ext.annotate)
1234
- return 'annotate:' + quoted(ext.annotate);
1235
- return 'extend:' + quoted(ext.extend);
1302
+ return `annotate:${ quoted(ext.annotate) }`;
1303
+ return `extend:${ quoted(ext.extend) }`;
1236
1304
  }
1237
1305
 
1238
1306
  let { query } = analyseCsnPath(csnPath, model, false);
@@ -1245,7 +1313,7 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1245
1313
  ? currentThing.kind
1246
1314
  : (dictName === 'vocabularies'
1247
1315
  ? 'annotation'
1248
- : 'artifact') }:${ _quoted(artifactName) }`;
1316
+ : 'artifact') }:${ quoted(artifactName) }`;
1249
1317
 
1250
1318
  if (!currentThing)
1251
1319
  return result;
@@ -1265,6 +1333,7 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1265
1333
  let inColumn = false;
1266
1334
  let inMixin = false;
1267
1335
  let inItems = false;
1336
+ let currentAnno = null;
1268
1337
 
1269
1338
  // for top level actions
1270
1339
  if (currentThing.kind === 'action')
@@ -1275,7 +1344,7 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1275
1344
  inCsnDict = true;
1276
1345
  switch (step) {
1277
1346
  case 'elements':
1278
- if (!inElement){
1347
+ if (!inElement) {
1279
1348
  inElement = true;
1280
1349
  // do not print intermediate items
1281
1350
  inItems = false;
@@ -1378,13 +1447,13 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1378
1447
  else if ( inKeys || inColumn) {
1379
1448
  if (typeof step === 'number') {
1380
1449
  if (currentThing.as)
1381
- result += `:${ _quoted(currentThing.as) }`;
1450
+ result += `:${ quoted(currentThing.as) }`;
1382
1451
  else if (inRef)
1383
- result += `:${ _quoted(currentThing) }`;
1452
+ result += `:${ quoted(currentThing) }`;
1384
1453
  else if (currentThing.ref)
1385
- 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('.')) }`;
1386
1455
  else
1387
- return'';
1456
+ return '';
1388
1457
 
1389
1458
  break;
1390
1459
  }
@@ -1406,11 +1475,17 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1406
1475
 
1407
1476
  inCsnDict = false;
1408
1477
  }
1478
+ else if (step[0] === '@') {
1479
+ currentAnno = step;
1480
+ break; // we don't go inside annotation values
1481
+ }
1409
1482
  }
1410
1483
  if ( inItems )
1411
1484
  result += `${ element() }/items`;
1412
1485
  else if ( inElement )
1413
1486
  result += element();
1487
+ if ( currentAnno )
1488
+ result += `/${ quoted(currentAnno) }`;
1414
1489
  return result;
1415
1490
 
1416
1491
  function select() {
@@ -1418,27 +1493,29 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1418
1493
  s += query.isOnlySelect ? '' : `:${ query.depth }`;
1419
1494
  return s;
1420
1495
  }
1421
- function selectAndMixin(name) {
1422
- return `${ select() }/mixin:${ _quoted(name) }`;
1496
+ function selectAndMixin( name ) {
1497
+ return `${ select() }/mixin:${ quoted(name) }`;
1423
1498
  }
1424
1499
  function element() {
1425
- return `/element:${ _quoted(elements.join('.')) }`;
1500
+ return `/element:${ quoted(elements.join('.')) }`;
1426
1501
  }
1427
- function param(name) {
1428
- return `/param:${ _quoted(name) }`;
1502
+ function param( name ) {
1503
+ return `/param:${ quoted(name) }`;
1429
1504
  }
1430
- function func(name) {
1431
- return `/function:${ _quoted(name) }`;
1505
+ function func( name ) {
1506
+ if (currentThing?.kind)
1507
+ return `/${ currentThing.kind }:${ quoted(name) }`;
1508
+ return `/action:${ quoted(name) }`;
1432
1509
  }
1433
- function elementAndEnum(name) {
1434
- return `${ element() }/enum:${ _quoted(name) }`;
1510
+ function elementAndEnum( name ) {
1511
+ return `${ element() }/enum:${ quoted(name) }`;
1435
1512
  }
1436
1513
 
1437
1514
  /**
1438
1515
  * Traverse rootQuery until targetQuery is found and count the depth,
1439
1516
  * check if targetQuery is only select in entity.
1440
1517
  */
1441
- function queryDepth(rootQuery, targetQuery) {
1518
+ function queryDepth( rootQuery, targetQuery ) {
1442
1519
  let targetQueryDepth = 1;
1443
1520
  let totalQueryDepth = 0;
1444
1521
 
@@ -1455,11 +1532,6 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1455
1532
  }
1456
1533
  }
1457
1534
 
1458
-
1459
- function _quoted( name ) {
1460
- return (name) ? `"${ name.replace( /"/g, '""' ) }"` : '<?>'; // sync ";
1461
- }
1462
-
1463
1535
  /**
1464
1536
  * Get the explanation string for the given message-id.
1465
1537
  * Ensure to have called hasMessageExplanation() before.
@@ -1469,9 +1541,9 @@ function _quoted( name ) {
1469
1541
  * @throws May throw an ENOENT error if the file cannot be found.
1470
1542
  * @see hasMessageExplanation()
1471
1543
  */
1472
- function explainMessage(messageId) {
1544
+ function explainMessage( messageId ) {
1473
1545
  messageId = oldMessageIds[messageId] || messageId;
1474
- const filename = path.join(__dirname, '..', '..', 'share', 'messages', `${messageId}.md`);
1546
+ const filename = path.join(__dirname, '..', '..', 'share', 'messages', `${ messageId }.md`);
1475
1547
  return fs.readFileSync(filename, 'utf8');
1476
1548
  }
1477
1549
 
@@ -1483,7 +1555,7 @@ function explainMessage(messageId) {
1483
1555
  * @param {string} messageId
1484
1556
  * @returns {boolean}
1485
1557
  */
1486
- function hasMessageExplanation(messageId) {
1558
+ function hasMessageExplanation( messageId ) {
1487
1559
  const id = oldMessageIds[messageId] || messageId || false;
1488
1560
  return id && _messageIdsWithExplanation.includes(id);
1489
1561
  }