@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.
- package/CHANGELOG.md +80 -0
- package/README.md +1 -0
- package/bin/cds_update_identifiers.js +5 -5
- package/bin/cdsc.js +15 -16
- package/bin/cdshi.js +19 -6
- package/doc/CHANGELOG_ARCHIVE.md +2 -2
- package/doc/CHANGELOG_BETA.md +9 -1
- package/doc/CHANGELOG_DEPRECATED.md +2 -0
- package/lib/api/main.js +61 -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 +177 -58
- package/lib/base/messages.js +252 -180
- 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/.eslintrc.json +2 -0
- 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 +4 -1
- package/lib/compiler/assert-consistency.js +8 -7
- package/lib/compiler/builtins.js +14 -14
- package/lib/compiler/checks.js +123 -48
- package/lib/compiler/define.js +12 -13
- package/lib/compiler/extend.js +266 -60
- package/lib/compiler/finalize-parse-cdl.js +10 -5
- package/lib/compiler/index.js +17 -14
- package/lib/compiler/populate.js +14 -6
- package/lib/compiler/propagator.js +2 -0
- package/lib/compiler/resolve.js +2 -15
- package/lib/compiler/shared.js +27 -16
- package/lib/compiler/tweak-assocs.js +5 -6
- package/lib/compiler/utils.js +20 -0
- package/lib/edm/annotations/genericTranslation.js +604 -358
- package/lib/edm/annotations/preprocessAnnotations.js +39 -35
- package/lib/edm/csn2edm.js +275 -222
- package/lib/edm/edm.js +17 -3
- package/lib/edm/edmAnnoPreprocessor.js +6 -6
- package/lib/edm/edmInboundChecks.js +2 -2
- package/lib/edm/edmPreprocessor.js +107 -77
- package/lib/edm/edmUtils.js +44 -5
- package/lib/gen/Dictionary.json +210 -8
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +67 -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 +14309 -13832
- 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 +102 -55
- package/lib/json/to-csn.js +119 -198
- package/lib/language/antlrParser.js +5 -2
- package/lib/language/docCommentParser.js +6 -6
- package/lib/language/errorStrategy.js +43 -23
- package/lib/language/genericAntlrParser.js +113 -133
- package/lib/language/language.g4 +1550 -1506
- package/lib/language/multiLineStringParser.js +3 -3
- package/lib/language/textUtils.js +2 -2
- package/lib/main.js +3 -3
- package/lib/model/csnRefs.js +5 -0
- package/lib/model/csnUtils.js +130 -122
- package/lib/model/revealInternalProperties.js +1 -1
- package/lib/model/sortViews.js +4 -6
- package/lib/modelCompare/compare.js +2 -2
- package/lib/modelCompare/utils/.eslintrc.json +22 -0
- package/lib/modelCompare/utils/filter.js +100 -0
- package/lib/optionProcessor.js +5 -0
- package/lib/render/.eslintrc.json +1 -0
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +12 -12
- package/lib/render/toCdl.js +311 -276
- package/lib/render/toHdbcds.js +97 -94
- package/lib/render/toRename.js +5 -5
- package/lib/render/toSql.js +127 -223
- package/lib/render/utils/common.js +141 -108
- package/lib/render/utils/delta.js +227 -0
- package/lib/render/utils/sql.js +22 -6
- package/lib/render/utils/stringEscapes.js +3 -3
- package/lib/transform/db/.eslintrc.json +2 -0
- 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/.eslintrc.json +1 -35
- package/lib/transform/draft/db.js +10 -10
- package/lib/transform/draft/odata.js +2 -2
- package/lib/transform/forOdataNew.js +8 -29
- package/lib/transform/forRelationalDB.js +16 -6
- package/lib/transform/localized.js +11 -10
- package/lib/transform/odata/toFinalBaseType.js +41 -27
- package/lib/transform/odata/typesExposure.js +113 -47
- package/lib/transform/parseExpr.js +209 -106
- package/lib/transform/transformUtilsNew.js +17 -10
- 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 -8
- 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/modelCompare/filter.js +0 -83
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,51 +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:
|
|
658
|
-
delimited: n =>
|
|
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.
|
|
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
|
-
|
|
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'][
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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]
|
|
817
|
+
return quoted(`${ arg.ref[0] }:${ arg.ref.slice(1).join('.') }`);
|
|
758
818
|
return quoted(arg.ref);
|
|
759
819
|
}
|
|
760
|
-
|
|
820
|
+
const { name } = arg;
|
|
761
821
|
if (!name)
|
|
762
822
|
return quoted( name );
|
|
763
|
-
|
|
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[
|
|
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
|
-
|
|
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
|
-
|
|
791
|
-
|
|
792
|
-
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 || '?';
|
|
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
|
-
|
|
800
|
-
for (
|
|
801
|
-
|
|
802
|
-
|
|
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
|
-
|
|
805
|
-
return replaceInString( variant && texts[
|
|
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
|
-
|
|
811
|
-
|
|
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
|
-
|
|
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
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
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
|
-
|
|
852
|
-
|
|
916
|
+
? `${ locationString( err.$location, normalizeFilename ) }: `
|
|
917
|
+
: '') +
|
|
853
918
|
(err.severity || 'Error') +
|
|
854
919
|
// TODO: use [message-id]
|
|
855
|
-
(err.messageId && !noMessageId ?
|
|
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 ? '' :
|
|
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 ? '' : (
|
|
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
|
-
|
|
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)
|
|
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
|
|
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 ?
|
|
990
|
-
msg +=
|
|
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
|
|
1001
|
-
|
|
1002
|
-
|
|
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 +=
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
msg +=
|
|
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
|
-
|
|
1105
|
+
}
|
|
1106
|
+
else if (!aFile) {
|
|
1040
1107
|
return (a.messageId && a.messageId.startsWith( 'api-' )) ? -1 : 1;
|
|
1041
|
-
|
|
1042
|
-
|
|
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 ) ?
|
|
1072
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1123
|
-
|
|
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)
|
|
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 ?
|
|
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(
|
|
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
|
|
1226
|
+
return `using:${ quoted( art.name.id ) }`;
|
|
1159
1227
|
else if (art.kind === 'extend' || art.kind === 'annotate')
|
|
1160
|
-
return !absoluteOnly && homeNameForExtend
|
|
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
|
|
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
|
|
1251
|
+
return `${ kind }:${ artName(removeBlock(art)) }`;
|
|
1184
1252
|
else if (!parent)
|
|
1185
|
-
return kind
|
|
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)
|
|
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)
|
|
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)
|
|
1275
|
+
return `${ homeNameForExtend(parentOfElementChain) }/${ artName(fakeArt) }`;
|
|
1208
1276
|
}
|
|
1209
1277
|
// This case should not happen, but just in case
|
|
1210
|
-
return kind
|
|
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(
|
|
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
|
|
1235
|
-
return
|
|
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') }:${
|
|
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 += `:${
|
|
1450
|
+
result += `:${ quoted(currentThing.as) }`;
|
|
1382
1451
|
else if (inRef)
|
|
1383
|
-
result +=
|
|
1452
|
+
result += `:${ quoted(currentThing) }`;
|
|
1384
1453
|
else if (currentThing.ref)
|
|
1385
|
-
result += `:${
|
|
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:${
|
|
1496
|
+
function selectAndMixin( name ) {
|
|
1497
|
+
return `${ select() }/mixin:${ quoted(name) }`;
|
|
1423
1498
|
}
|
|
1424
1499
|
function element() {
|
|
1425
|
-
return `/element:${
|
|
1500
|
+
return `/element:${ quoted(elements.join('.')) }`;
|
|
1426
1501
|
}
|
|
1427
|
-
function param(name) {
|
|
1428
|
-
return `/param:${
|
|
1502
|
+
function param( name ) {
|
|
1503
|
+
return `/param:${ quoted(name) }`;
|
|
1429
1504
|
}
|
|
1430
|
-
function func(name) {
|
|
1431
|
-
|
|
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:${
|
|
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
|
}
|