@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.
- package/CHANGELOG.md +58 -0
- package/README.md +1 -0
- package/bin/cds_update_identifiers.js +5 -5
- package/bin/cdsc.js +12 -12
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +9 -1
- package/doc/CHANGELOG_DEPRECATED.md +2 -0
- package/lib/api/main.js +58 -59
- package/lib/api/options.js +4 -2
- package/lib/api/validate.js +2 -2
- package/lib/base/cleanSymbols.js +2 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +6 -6
- package/lib/base/location.js +11 -12
- package/lib/base/message-registry.js +124 -28
- package/lib/base/messages.js +247 -179
- package/lib/base/model.js +14 -11
- package/lib/base/node-helpers.js +9 -10
- package/lib/base/optionProcessorHelper.js +138 -129
- package/lib/checks/actionsFunctions.js +5 -5
- package/lib/checks/annotationsOData.js +4 -4
- package/lib/checks/arrayOfs.js +1 -1
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +3 -3
- package/lib/checks/defaultValues.js +3 -3
- package/lib/checks/elements.js +7 -7
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/foreignKeys.js +1 -1
- package/lib/checks/invalidTarget.js +4 -4
- package/lib/checks/managedInType.js +1 -1
- package/lib/checks/managedWithoutKeys.js +1 -1
- package/lib/checks/nonexpandableStructured.js +5 -3
- package/lib/checks/nullableKeys.js +1 -1
- package/lib/checks/onConditions.js +5 -6
- package/lib/checks/parameters.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -2
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +4 -4
- package/lib/checks/types.js +7 -7
- package/lib/checks/utils.js +4 -4
- package/lib/checks/validator.js +16 -13
- package/lib/compiler/.eslintrc.json +1 -1
- package/lib/compiler/assert-consistency.js +0 -1
- package/lib/compiler/builtins.js +1 -1
- package/lib/compiler/checks.js +73 -15
- package/lib/compiler/define.js +3 -7
- package/lib/compiler/extend.js +212 -32
- package/lib/compiler/finalize-parse-cdl.js +7 -2
- package/lib/compiler/index.js +17 -14
- package/lib/compiler/populate.js +2 -5
- package/lib/compiler/propagator.js +2 -0
- package/lib/compiler/shared.js +23 -12
- package/lib/compiler/tweak-assocs.js +5 -6
- package/lib/compiler/utils.js +6 -0
- package/lib/edm/annotations/genericTranslation.js +553 -319
- package/lib/edm/annotations/preprocessAnnotations.js +39 -35
- package/lib/edm/csn2edm.js +88 -75
- package/lib/edm/edm.js +17 -3
- package/lib/edm/edmAnnoPreprocessor.js +5 -5
- package/lib/edm/edmPreprocessor.js +106 -76
- package/lib/edm/edmUtils.js +41 -2
- package/lib/gen/Dictionary.json +34 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +66 -63
- package/lib/gen/language.tokens +81 -81
- package/lib/gen/languageLexer.interp +4 -10
- package/lib/gen/languageLexer.js +854 -869
- package/lib/gen/languageLexer.tokens +79 -81
- package/lib/gen/languageParser.js +14360 -14146
- package/lib/inspect/inspectModelStatistics.js +2 -2
- package/lib/inspect/inspectPropagation.js +6 -6
- package/lib/inspect/inspectUtils.js +2 -2
- package/lib/json/from-csn.js +82 -40
- package/lib/json/to-csn.js +82 -157
- package/lib/language/.eslintrc.json +1 -4
- package/lib/language/genericAntlrParser.js +59 -38
- package/lib/language/language.g4 +1508 -1490
- package/lib/language/multiLineStringParser.js +1 -1
- package/lib/main.js +3 -3
- package/lib/model/csnUtils.js +130 -122
- package/lib/model/revealInternalProperties.js +1 -1
- package/lib/model/sortViews.js +4 -6
- package/lib/modelCompare/utils/filter.js +4 -3
- package/lib/optionProcessor.js +5 -0
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +12 -12
- package/lib/render/toCdl.js +225 -159
- package/lib/render/toHdbcds.js +63 -63
- package/lib/render/toRename.js +5 -5
- package/lib/render/toSql.js +55 -65
- package/lib/render/utils/common.js +20 -37
- package/lib/render/utils/delta.js +3 -3
- package/lib/render/utils/sql.js +22 -6
- package/lib/render/utils/stringEscapes.js +3 -3
- package/lib/transform/db/applyTransformations.js +3 -3
- package/lib/transform/db/assertUnique.js +13 -12
- package/lib/transform/db/associations.js +5 -5
- package/lib/transform/db/cdsPersistence.js +10 -8
- package/lib/transform/db/constraints.js +14 -14
- package/lib/transform/db/expansion.js +20 -22
- package/lib/transform/db/flattening.js +24 -42
- package/lib/transform/db/groupByOrderBy.js +3 -3
- package/lib/transform/db/temporal.js +6 -6
- package/lib/transform/db/transformExists.js +23 -23
- package/lib/transform/db/views.js +16 -16
- package/lib/transform/draft/db.js +10 -10
- package/lib/transform/draft/odata.js +2 -2
- package/lib/transform/forOdataNew.js +12 -40
- package/lib/transform/forRelationalDB.js +17 -7
- package/lib/transform/localized.js +2 -2
- package/lib/transform/odata/toFinalBaseType.js +41 -27
- package/lib/transform/odata/typesExposure.js +106 -62
- package/lib/transform/parseExpr.js +209 -106
- package/lib/transform/transformUtilsNew.js +2 -2
- package/lib/transform/translateAssocsToJoins.js +24 -19
- package/lib/transform/universalCsn/coreComputed.js +10 -10
- package/lib/transform/universalCsn/universalCsnEnricher.js +26 -26
- package/lib/transform/universalCsn/utils.js +3 -3
- package/lib/utils/file.js +5 -5
- package/lib/utils/moduleResolve.js +13 -13
- package/lib/utils/objectUtils.js +6 -6
- package/lib/utils/term.js +5 -2
- package/lib/utils/timetrace.js +51 -24
- package/package.json +5 -7
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/message-explanations.json +1 -1
- package/share/messages/redirected-to-complex.md +4 -4
- package/share/messages/{syntax-expecting-integer.md → syntax-expecting-unsigned-int.md} +7 -4
package/lib/base/messages.js
CHANGED
|
@@ -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(
|
|
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 ||
|
|
99
|
-
|
|
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
|
|
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
|
-
|
|
186
|
-
|
|
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
|
-
|
|
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,
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
}
|
|
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,
|
|
393
|
-
throw new CompilerAssertion(`Invalid argument type for ${ field }! Expected ${ type } but got ${ typeof
|
|
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(
|
|
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 +=
|
|
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
|
-
|
|
578
|
-
|
|
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
|
-
|
|
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}
|
|
644
|
+
* @param {string} val
|
|
631
645
|
* @private
|
|
632
646
|
*/
|
|
633
|
-
function _check$Texts( id, prop,
|
|
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] =
|
|
639
|
-
else if (expected !==
|
|
640
|
-
throw new CompilerAssertion( `Different texts for the same message ID. Expecting
|
|
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:
|
|
647
|
-
|
|
648
|
-
upper:
|
|
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
|
|
657
|
-
annos:
|
|
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.
|
|
667
|
-
|
|
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
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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'][
|
|
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(
|
|
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
|
-
|
|
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]
|
|
817
|
+
return quoted(`${ arg.ref[0] }:${ arg.ref.slice(1).join('.') }`);
|
|
761
818
|
return quoted(arg.ref);
|
|
762
819
|
}
|
|
763
|
-
|
|
820
|
+
const { name } = arg;
|
|
764
821
|
if (!name)
|
|
765
822
|
return quoted( name );
|
|
766
|
-
|
|
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[
|
|
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
|
-
|
|
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
|
-
|
|
794
|
-
|
|
795
|
-
name[prop] =
|
|
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
|
-
|
|
803
|
-
for (
|
|
804
|
-
|
|
805
|
-
|
|
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
|
-
|
|
809
|
-
return replaceInString( variant && texts[
|
|
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
|
-
|
|
815
|
-
|
|
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
|
-
|
|
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
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
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
|
-
|
|
856
|
-
|
|
916
|
+
? `${ locationString( err.$location, normalizeFilename ) }: `
|
|
917
|
+
: '') +
|
|
857
918
|
(err.severity || 'Error') +
|
|
858
919
|
// TODO: use [message-id]
|
|
859
|
-
(err.messageId && !noMessageId ?
|
|
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 ? '' :
|
|
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 ? '' : (
|
|
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
|
-
|
|
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)
|
|
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
|
|
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 ?
|
|
994
|
-
msg +=
|
|
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
|
|
1005
|
-
|
|
1006
|
-
|
|
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 +=
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
msg +=
|
|
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
|
-
|
|
1105
|
+
}
|
|
1106
|
+
else if (!aFile) {
|
|
1044
1107
|
return (a.messageId && a.messageId.startsWith( 'api-' )) ? -1 : 1;
|
|
1045
|
-
|
|
1046
|
-
|
|
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 ) ?
|
|
1076
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1127
|
-
|
|
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)
|
|
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 ?
|
|
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(
|
|
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
|
|
1226
|
+
return `using:${ quoted( art.name.id ) }`;
|
|
1163
1227
|
else if (art.kind === 'extend' || art.kind === 'annotate')
|
|
1164
|
-
return !absoluteOnly && homeNameForExtend
|
|
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
|
|
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
|
|
1251
|
+
return `${ kind }:${ artName(removeBlock(art)) }`;
|
|
1188
1252
|
else if (!parent)
|
|
1189
|
-
return kind
|
|
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)
|
|
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)
|
|
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)
|
|
1275
|
+
return `${ homeNameForExtend(parentOfElementChain) }/${ artName(fakeArt) }`;
|
|
1212
1276
|
}
|
|
1213
1277
|
// This case should not happen, but just in case
|
|
1214
|
-
return kind
|
|
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(
|
|
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
|
|
1239
|
-
return
|
|
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') }:${
|
|
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 += `:${
|
|
1450
|
+
result += `:${ quoted(currentThing.as) }`;
|
|
1386
1451
|
else if (inRef)
|
|
1387
|
-
result +=
|
|
1452
|
+
result += `:${ quoted(currentThing) }`;
|
|
1388
1453
|
else if (currentThing.ref)
|
|
1389
|
-
result += `:${
|
|
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:${
|
|
1496
|
+
function selectAndMixin( name ) {
|
|
1497
|
+
return `${ select() }/mixin:${ quoted(name) }`;
|
|
1427
1498
|
}
|
|
1428
1499
|
function element() {
|
|
1429
|
-
return `/element:${
|
|
1500
|
+
return `/element:${ quoted(elements.join('.')) }`;
|
|
1430
1501
|
}
|
|
1431
|
-
function param(name) {
|
|
1432
|
-
return `/param:${
|
|
1502
|
+
function param( name ) {
|
|
1503
|
+
return `/param:${ quoted(name) }`;
|
|
1433
1504
|
}
|
|
1434
|
-
function func(name) {
|
|
1435
|
-
|
|
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:${
|
|
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
|
}
|