@sap/cds-compiler 3.5.4 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +56 -2
  2. package/bin/cdsc.js +14 -6
  3. package/doc/CHANGELOG_ARCHIVE.md +10 -10
  4. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  5. package/lib/api/main.js +32 -55
  6. package/lib/api/validate.js +5 -0
  7. package/lib/base/message-registry.js +104 -32
  8. package/lib/base/messages.js +277 -212
  9. package/lib/base/optionProcessorHelper.js +9 -2
  10. package/lib/base/shuffle.js +50 -0
  11. package/lib/checks/actionsFunctions.js +37 -20
  12. package/lib/checks/foreignKeys.js +13 -6
  13. package/lib/checks/nonexpandableStructured.js +1 -2
  14. package/lib/checks/onConditions.js +21 -19
  15. package/lib/checks/parameters.js +1 -1
  16. package/lib/checks/queryNoDbArtifacts.js +2 -0
  17. package/lib/checks/types.js +16 -22
  18. package/lib/compiler/assert-consistency.js +31 -28
  19. package/lib/compiler/builtins.js +20 -4
  20. package/lib/compiler/checks.js +72 -63
  21. package/lib/compiler/define.js +396 -314
  22. package/lib/compiler/extend.js +55 -49
  23. package/lib/compiler/index.js +5 -0
  24. package/lib/compiler/populate.js +28 -11
  25. package/lib/compiler/propagator.js +2 -1
  26. package/lib/compiler/resolve.js +28 -13
  27. package/lib/compiler/shared.js +15 -10
  28. package/lib/compiler/utils.js +7 -7
  29. package/lib/edm/annotations/genericTranslation.js +51 -46
  30. package/lib/edm/annotations/preprocessAnnotations.js +37 -40
  31. package/lib/edm/csn2edm.js +69 -21
  32. package/lib/edm/edm.js +2 -2
  33. package/lib/edm/edmInboundChecks.js +6 -8
  34. package/lib/edm/edmPreprocessor.js +88 -80
  35. package/lib/edm/edmUtils.js +6 -15
  36. package/lib/gen/Dictionary.json +81 -13
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +2 -1
  39. package/lib/gen/languageParser.js +4680 -4484
  40. package/lib/inspect/inspectModelStatistics.js +2 -1
  41. package/lib/inspect/inspectPropagation.js +2 -1
  42. package/lib/json/from-csn.js +131 -78
  43. package/lib/json/to-csn.js +39 -23
  44. package/lib/language/antlrParser.js +0 -3
  45. package/lib/language/docCommentParser.js +7 -3
  46. package/lib/language/errorStrategy.js +3 -2
  47. package/lib/language/genericAntlrParser.js +96 -41
  48. package/lib/language/language.g4 +112 -128
  49. package/lib/language/multiLineStringParser.js +2 -1
  50. package/lib/main.d.ts +115 -2
  51. package/lib/main.js +16 -3
  52. package/lib/model/csnRefs.js +3 -3
  53. package/lib/model/csnUtils.js +109 -179
  54. package/lib/model/enrichCsn.js +13 -8
  55. package/lib/model/revealInternalProperties.js +4 -3
  56. package/lib/optionProcessor.js +19 -3
  57. package/lib/render/manageConstraints.js +11 -15
  58. package/lib/render/toCdl.js +144 -47
  59. package/lib/render/toHdbcds.js +22 -22
  60. package/lib/render/toRename.js +3 -4
  61. package/lib/render/toSql.js +29 -20
  62. package/lib/render/utils/delta.js +3 -1
  63. package/lib/render/utils/sql.js +2 -14
  64. package/lib/transform/db/associations.js +6 -6
  65. package/lib/transform/db/cdsPersistence.js +3 -3
  66. package/lib/transform/db/constraints.js +4 -6
  67. package/lib/transform/db/expansion.js +4 -4
  68. package/lib/transform/db/flattening.js +12 -15
  69. package/lib/transform/db/temporal.js +4 -3
  70. package/lib/transform/db/transformExists.js +2 -1
  71. package/lib/transform/draft/db.js +7 -7
  72. package/lib/transform/forOdataNew.js +15 -4
  73. package/lib/transform/forRelationalDB.js +53 -39
  74. package/lib/transform/odata/toFinalBaseType.js +106 -82
  75. package/lib/transform/odata/typesExposure.js +26 -17
  76. package/lib/transform/odata/utils.js +1 -1
  77. package/lib/transform/parseExpr.js +1 -1
  78. package/lib/transform/transformUtilsNew.js +33 -10
  79. package/lib/transform/translateAssocsToJoins.js +8 -7
  80. package/lib/transform/universalCsn/coreComputed.js +7 -5
  81. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -4
  82. package/lib/utils/timetrace.js +2 -2
  83. package/package.json +1 -2
@@ -664,9 +664,9 @@ const quote = { // could be an option in the future
664
664
 
665
665
  const paramsTransform = {
666
666
  // simple convenience:
667
- name: quoted,
668
- id: quoted,
669
- alias: quoted,
667
+ name: quote.double,
668
+ id: quote.double,
669
+ alias: quote.double,
670
670
  anno,
671
671
  annos: transformManyWith( anno ),
672
672
  delimited: n => quote.single( `![${ n }]` ), // represent as delimited id (TODO: double-']')
@@ -688,6 +688,7 @@ const paramsTransform = {
688
688
  line: quote.direct,
689
689
  col: quote.direct,
690
690
  value,
691
+ rawvalue: quote.single,
691
692
  rawvalues: transformManyWith( quote.single ), // no 'double' quotes for strings
692
693
  othervalue: value,
693
694
  art: transformArg,
@@ -719,7 +720,7 @@ function value( val ) {
719
720
  return quote.single( val );
720
721
  }
721
722
  case 'string': {
722
- // TODO: should we also shorten the string if too long?
723
+ // TODO: should we also shorten the string if too long? TODO: false, true, null?
723
724
  return (!val ||
724
725
  Number.parseFloat( val ).toString() === val ||
725
726
  // with quotes (TODO: use `…` with escape chars):
@@ -768,7 +769,7 @@ function transformManyWith( t, sorted ) {
768
769
  function quoted( name ) {
769
770
  if (typeof name === 'string')
770
771
  return quote.double( name );
771
- throw new CompilerAssertion( `Expecting a string, not ${ name }` );
772
+ throw new CompilerAssertion( `Expecting a string, not ${ typeof name } (${ JSON.stringify(name) })` );
772
773
  }
773
774
 
774
775
  function tokenSymbol( token ) {
@@ -882,7 +883,7 @@ function replaceInString( text, params ) {
882
883
  parts.push( text.substring( start ) );
883
884
  const remain = (params['#']) ? [] : Object.keys( params ).filter( n => !usedParams.includes(n) );
884
885
  if (remain.length) {
885
- const remains = remain.map( n => `${ n.toUpperCase() } = ${ params[n] }` ).join(', ');
886
+ const remains = remain.map( n => `${ n.toUpperCase() } = ${ params[n] }` ).join(', ');
886
887
  return `${ parts.join('') }; ${ remains }`;
887
888
  }
888
889
  return parts.join('');
@@ -1197,9 +1198,15 @@ function artName( art, omit ) {
1197
1198
  r.push( (art.kind === 'mixin' ? 'mixin:' : 'alias:') + quoted( name.alias ) );
1198
1199
  if (name.param != null && omit !== 'param')
1199
1200
  r.push( name.param ? `param:${ quoted( name.param ) }` : 'returns' ); // TODO: join
1200
- if (name.element && omit !== 'element')
1201
- // r.push( `${ art.kind }: ${ quoted( name.element )}` ); or even better element:"assoc"/key:"i" same with enum
1202
- r.push( (art.kind === 'enum' ? 'enum:' : 'element:') + quoted( name.element ) );
1201
+
1202
+ if (name.element && omit !== 'element') {
1203
+ if (name.select != null && !art.$inferred)
1204
+ r.push( 'column:' + quoted( name.element ) );
1205
+ else
1206
+ // r.push( `${ art.kind }: ${ quoted( name.element )}` ); or even better element:"assoc"/key:"i" same with enum
1207
+ r.push( (art.kind === 'enum' ? 'enum:' : 'element:') + quoted( name.element ) );
1208
+ }
1209
+
1203
1210
  if (art.kind === '$self')
1204
1211
  r.push( `alias:${ quoted( name.alias ) }` ); // should be late due to $self in anonymous aspect
1205
1212
  return r.join('/');
@@ -1237,7 +1244,7 @@ function homeName( art, absoluteOnly ) {
1237
1244
  }
1238
1245
 
1239
1246
  // The "home" for extensions is handled differently because `_artifact` is not
1240
- // set for unknown extensions and we could have nested extensions.
1247
+ // set for unknown extensions, and we could have nested extensions.
1241
1248
  function homeNameForExtend( art ) {
1242
1249
  const kind = art.kind || 'extend';
1243
1250
  // TODO: fix the following - do like in collectArtifactExtensions() or
@@ -1286,252 +1293,308 @@ function homeNameForExtend( art ) {
1286
1293
  }
1287
1294
  }
1288
1295
 
1296
+ /**
1297
+ * Construct a semantic location for the given CSN path.
1298
+ * Works without a CSN model, but is less precise.
1299
+ *
1300
+ * Example:
1301
+ * path: ["definitions", "E", "elements", "s"],
1302
+ * result: entity:“E”/element:“s”
1303
+ *
1304
+ * @param {CSN.Model} model
1305
+ * @param {CSN.Options} options
1306
+ * @param {CSN.Path} csnPath
1307
+ * @return {string|null}
1308
+ */
1289
1309
  function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1290
1310
  if (!model)
1291
1311
  return null;
1292
- // Copy because this function shift()s from the path.
1293
- csnPath = [ ...csnPath ];
1312
+ let result = '';
1294
1313
  const csnDictionaries = [
1295
1314
  'args', 'params', 'enum', 'mixin', 'elements', 'actions', 'definitions',
1296
1315
  ];
1297
- const queryProps = [ 'from', 'where', 'groupBy', 'having', 'orderBy', 'limit', 'offset' ];
1316
+ // Properties that (currently) end the semantic location.
1317
+ const queryPropsLast = [ 'where', 'groupBy', 'having', 'orderBy', 'limit', 'offset' ];
1318
+
1319
+ let index = 0;
1320
+ /** @type {CSN.PathSegment} */
1321
+ let step = csnPath[index];
1322
+ let currentThing = model?.[step];
1323
+
1324
+ function next(steps = 1) {
1325
+ for (; steps > 0; --steps) {
1326
+ ++index;
1327
+ step = csnPath[index];
1328
+ currentThing = currentThing && currentThing[step];
1329
+ }
1330
+ }
1298
1331
 
1299
- if (csnPath[0] === 'extensions') {
1300
- const ext = model.extensions && model.extensions[csnPath[1]] || {};
1301
- if (ext.annotate)
1302
- return `annotate:${ quoted(ext.annotate) }`;
1303
- return `extend:${ quoted(ext.extend) }`;
1332
+ function peek(steps = 1) {
1333
+ return csnPath[index + steps];
1304
1334
  }
1305
1335
 
1306
- let { query } = analyseCsnPath(csnPath, model, false);
1336
+ // First step; always one of: -------------------------------------
1307
1337
 
1308
- const dictName = csnPath.shift();
1309
- const dict = model[dictName];
1310
- const artifactName = csnPath.shift();
1311
- let currentThing = dict ? dict[artifactName] : undefined;
1312
- let result = `${ (currentThing?.kind)
1313
- ? currentThing.kind
1314
- : (dictName === 'vocabularies'
1315
- ? 'annotation'
1316
- : 'artifact') }:${ quoted(artifactName) }`;
1338
+ if (step === 'definitions') {
1339
+ next(); // "definitions"
1340
+ const kind = currentThing?.kind || 'artifact';
1341
+ result += `${ kind }:${ quoted(step) }`;
1342
+ }
1343
+ else if (step === 'vocabularies') {
1344
+ next(); // dictionary name
1345
+ if (index < csnPath.length)
1346
+ result += `annotation:${ quoted(csnPath[index]) }`;
1347
+ else
1348
+ result += 'vocabularies';
1349
+ }
1350
+ else if (step === 'extensions') {
1351
+ next(); // "extensions"
1352
+ if (!currentThing) {
1353
+ result += `extension:${ step }`;
1354
+ }
1355
+ else {
1356
+ const name = currentThing.annotate || currentThing.extend;
1357
+ const kind = currentThing.annotate ? 'annotate' : 'extend';
1358
+ result += `${ kind }:${ quoted(name) }`;
1359
+ }
1360
+ }
1317
1361
 
1318
1362
  if (!currentThing)
1319
1363
  return result;
1320
1364
 
1321
- if (query)
1322
- query = queryDepth(currentThing.query || { SELECT: currentThing.projection }, query);
1323
-
1324
- const elements = [];
1325
- let inCsnDict = false;
1326
- let inElement = false;
1327
- let inAction = false;
1328
- let inParam = false;
1329
- let inKeys = false;
1330
- let inRef = false;
1331
- let inEnum = false;
1332
- let inQuery = false;
1333
- let inColumn = false;
1334
- let inMixin = false;
1335
- let inItems = false;
1336
- let currentAnno = null;
1337
-
1338
- // for top level actions
1339
- if (currentThing.kind === 'action')
1340
- inAction = true;
1341
- for (const [ index, step ] of csnPath.entries()) {
1342
- currentThing = currentThing[step];
1343
- if (csnDictionaries.includes(step) && !inCsnDict) {
1344
- inCsnDict = true;
1345
- switch (step) {
1346
- case 'elements':
1347
- if (!inElement) {
1348
- inElement = true;
1349
- // do not print intermediate items
1350
- inItems = false;
1351
- }
1352
- break;
1353
- case 'actions':
1354
- inAction = true;
1355
- break;
1356
- case 'params':
1357
- inParam = true;
1358
- break;
1359
- case 'enum':
1360
- inElement = false;
1361
- inEnum = true;
1362
- break;
1363
- case 'mixin':
1364
- inMixin = true;
1365
- inQuery = false;
1366
- break;
1367
- default:
1368
- if (inElement) {
1369
- // close element
1370
- result += element();
1371
- inElement = false;
1372
- }
1373
- }
1374
- }
1375
- else if ( inQuery ) {
1376
- if (step === 'SELECT') {
1377
- if (!csnPath[index + 1]) {
1378
- result += select();
1379
- }
1380
- // only print last query prop for paths like
1381
- // [... 'query', 'SELECT', 'from', 'SELECT', 'elements', 'struct'] -> select:2/element:"struct"
1382
- // no from in the semantic location in this case
1383
- else if (queryProps.includes(csnPath[index + 1]) && (!csnPath[index + 2] || query.isOnlySelect)) {
1384
- const clause = csnPath[index + 1];
1385
- result += select();
1386
- result += `/${ clause }`;
1365
+ let selectDepth = (csnPath[0] !== 'extensions') ? queryDepthForMessage(csnPath, model, currentThing) : null;
1366
+
1367
+ // Artifact ref -------------------------------------
1368
+
1369
+ next();
1370
+ while (index < csnPath.length) {
1371
+ if (step === 'elements' || step === 'items') {
1372
+ if (index >= csnPath.length - 1)
1373
+ break; // last segment
1374
+
1375
+ const elementHierarchy = [];
1376
+ while (index < (csnPath.length - 1) && (step === 'items' || step === 'elements')) {
1377
+ if (step === 'elements') {
1378
+ next();
1379
+ elementHierarchy.push(step);
1387
1380
  }
1381
+ next();
1388
1382
  }
1389
- else if (step === 'columns') {
1390
- result += select();
1391
- result += '/column';
1392
- inColumn = true;
1393
- inQuery = false;
1394
- }
1395
- else if (inElement) {
1396
- result += select();
1397
- elements.push(step);
1398
- inQuery = false;
1399
- }
1383
+ if (elementHierarchy.length > 0)
1384
+ result += `/element:${quoted(elementHierarchy.join('.'))}`;
1385
+ // no trailing /elements or /items
1386
+ continue;
1400
1387
  }
1401
- else if ( inMixin ) {
1402
- if (step === 'on') {
1403
- result += '/on';
1404
- break;
1388
+ else if (step === 'actions') {
1389
+ next(); // "actions"
1390
+ if (index < csnPath.length) {
1391
+ const kind = currentThing?.kind || 'action';
1392
+ result += `/${ kind }:${ quoted(csnPath[index]) }`;
1405
1393
  }
1406
- else {
1407
- result += selectAndMixin(step);
1394
+ else { // actions is last segment
1395
+ result += '/actions';
1408
1396
  }
1409
1397
  }
1410
- else if (inEnum) {
1411
- result += elementAndEnum(step);
1398
+ else if (step === 'params') {
1399
+ dictEntry('param');
1400
+ }
1401
+ else if (step === 'enum' || step === 'mixin') {
1402
+ dictEntry(step);
1412
1403
  }
1413
- else if (!inElement && step === 'query') {
1414
- inQuery = true;
1404
+ else if (step === 'cast') {
1405
+ // To shorten the location, only print 'cast' if it's not a
1406
+ // redirection target.
1407
+ if (csnPath[index+1] !== 'on' && csnPath[index+1] !== 'target') {
1408
+ result += '/cast';
1409
+ }
1415
1410
  }
1416
- else if (inElement && step === 'keys') {
1417
- // close element
1418
- result += `${ element() }/key`;
1419
- inElement = false;
1420
- inKeys = true;
1411
+ // @ts-ignore
1412
+ else if (queryPropsLast.includes(step)) {
1413
+ result += '/' + step;
1414
+ break; // always last step
1421
1415
  }
1422
- else if (inElement && step === 'on') {
1423
- // close element
1424
- result += `${ element() }/on`;
1425
- inElement = false;
1416
+ else if (step === 'on') {
1417
+ result += '/on';
1426
1418
  break;
1427
1419
  }
1428
- else if (inElement && step === 'items') {
1429
- // this is an element called items
1430
- if (csnPath[index - 1] === 'elements' && elements[elements.length - 1] !== 'elements') {
1431
- elements.push(step);
1432
- }
1433
- else {
1434
- inElement = false;
1435
- inItems = true;
1436
- }
1420
+ else if (step === 'target') {
1421
+ if (currentThing)
1422
+ result += '/target:' + quoted(currentThing);
1423
+ else
1424
+ result += '/target';
1425
+ break;
1437
1426
  }
1438
- else if (inElement && step === 'elements') {
1439
- // this is an element called elements
1440
- if (csnPath[index - 1] === 'elements')
1441
- elements.push(step);
1427
+ else if (step === 'xpr' || step === 'ref' || step === 'as') {
1428
+ break; // don't go into xprs, refs, aliases, etc.
1442
1429
  }
1443
- else if (inItems && step === 'elements') {
1444
- inElement = true;
1445
- inItems = false;
1430
+ else if (step === 'returns') {
1431
+ result += '/returns';
1446
1432
  }
1447
- else if ( inKeys || inColumn) {
1448
- if (typeof step === 'number') {
1449
- if (currentThing.as)
1450
- result += `:${ quoted(currentThing.as) }`;
1451
- else if (inRef)
1452
- result += `:${ quoted(currentThing) }`;
1453
- else if (currentThing.ref)
1454
- result += `:${ quoted(currentThing.ref.map(r => (r.id ? r.id : r)).join('.')) }`;
1455
- else
1456
- return '';
1457
-
1458
- break;
1459
- }
1460
- if ( step === 'ref')
1461
- inRef = true;
1433
+ else if (step === 'query' || step === 'projection') {
1434
+ if (!queryPath())
1435
+ break; // something failed
1436
+ continue;
1462
1437
  }
1463
- else if (inAction && step === 'returns') {
1464
- result += `/${ step }`;
1438
+ else if (step === 'cardinality') {
1439
+ // ignore; all messages pointing to cardinality already mention "cardinality".
1440
+ // Also, no annotations on cardinalities.
1465
1441
  break;
1466
1442
  }
1467
- else if (inCsnDict) {
1468
- if (inElement)
1469
- elements.push(step);
1470
- else if (inParam)
1471
- result += param(step);
1472
-
1473
- else if (inAction)
1474
- result += func(step);
1475
-
1476
- inCsnDict = false;
1443
+ else if (step === 'type') {
1444
+ break; // we don't go into types
1445
+ }
1446
+ else if (step === 'keys') { // e.g. association foreign keys
1447
+ next(); // "keys"
1448
+ if (index < csnPath.length) {
1449
+ const key = aliasOrReference();
1450
+ result += `/key:${ key ? quoted(key) : step }`;
1451
+ }
1452
+ break;
1477
1453
  }
1478
1454
  else if (step[0] === '@') {
1479
- currentAnno = step;
1480
- break; // we don't go inside annotation values
1455
+ // Annotations are always the last step.
1456
+ // Nothing comes after them, everything is user defined.
1457
+ result += `/${ quoted(csnPath[index]) }`;
1458
+ break;
1459
+ }
1460
+ else {
1461
+ if (options.testMode)
1462
+ throw new CompilerAssertion(`semantic location: Missing segment: ${ csnPath[index] } for path ${ JSON.stringify( csnPath) }`);
1463
+ break;
1481
1464
  }
1465
+ next();
1482
1466
  }
1483
- if ( inItems )
1484
- result += `${ element() }/items`;
1485
- else if ( inElement )
1486
- result += element();
1487
- if ( currentAnno )
1488
- result += `/${ quoted(currentAnno) }`;
1467
+
1489
1468
  return result;
1490
1469
 
1491
- function select() {
1492
- let s = '/select';
1493
- s += query.isOnlySelect ? '' : `:${ query.depth }`;
1494
- return s;
1495
- }
1496
- function selectAndMixin( name ) {
1497
- return `${ select() }/mixin:${ quoted(name) }`;
1498
- }
1499
- function element() {
1500
- return `/element:${ quoted(elements.join('.')) }`;
1501
- }
1502
- function param( name ) {
1503
- return `/param:${ quoted(name) }`;
1504
- }
1505
- function func( name ) {
1506
- if (currentThing?.kind)
1507
- return `/${ currentThing.kind }:${ quoted(name) }`;
1508
- return `/action:${ quoted(name) }`;
1509
- }
1510
- function elementAndEnum( name ) {
1511
- return `${ element() }/enum:${ quoted(name) }`;
1470
+ function dictEntry( prefix ) {
1471
+ next(); // dictionary name
1472
+ if (index < csnPath.length)
1473
+ result += `/${ prefix }:${ quoted(csnPath[index]) }`;
1512
1474
  }
1513
1475
 
1514
1476
  /**
1515
- * Traverse rootQuery until targetQuery is found and count the depth,
1516
- * check if targetQuery is only select in entity.
1477
+ * @return {string}
1517
1478
  */
1518
- function queryDepth( rootQuery, targetQuery ) {
1519
- let targetQueryDepth = 1;
1520
- let totalQueryDepth = 0;
1521
-
1522
- let isFound = false;
1523
- traverseQuery(rootQuery, null, null, (q, querySelect) => {
1524
- if ( querySelect )
1525
- totalQueryDepth += 1;
1526
- if ( querySelect && !isFound)
1527
- targetQueryDepth += 1;
1528
- if (q === targetQuery)
1529
- isFound = true;
1530
- });
1531
- return { depth: targetQueryDepth, isOnlySelect: totalQueryDepth === 1 };
1479
+ function currentRefName() {
1480
+ if (currentThing?.id)
1481
+ return currentThing?.id;
1482
+ else if (currentThing?.as)
1483
+ return currentThing?.as;
1484
+ else if (typeof currentThing === 'string')
1485
+ return currentThing;
1486
+ return undefined;
1487
+ }
1488
+
1489
+ function aliasOrReference() {
1490
+ if (!currentThing?.as && currentThing?.ref) {
1491
+ // Create implicit alias.
1492
+ const { ref } = currentThing;
1493
+ const name = ref[ref.length - 1]?.id || ref[ref.length - 1];
1494
+ if (csnPath[index + 1] === 'ref' && csnPath[index + 2] === ref.length - 1) {
1495
+ // if the next ref points to the implicit alias, consume it to avoid duplicate names.
1496
+ next(2);
1497
+ }
1498
+ return name;
1499
+ }
1500
+ return currentRefName();
1501
+ }
1502
+
1503
+ function queryPath() {
1504
+ next();
1505
+ while (index < csnPath.length) {
1506
+ if (step === 'SELECT' || step === 'SET') {
1507
+ if (selectDepth > 0) {
1508
+ // Once inside a SELECT, go to the "last" SELECT. Only print path steps after
1509
+ // the last SELECT, e.g. "columns".
1510
+ for (let j = csnPath.length - 1; j > index; --j) {
1511
+ // @ts-ignore
1512
+ if (csnPath[j] === 'SELECT' && !csnDictionaries.includes(csnPath[j-1])) {
1513
+ next(j - index); // found last SELECT
1514
+ break;
1515
+ }
1516
+ }
1517
+ result += `/select:${ selectDepth }`;
1518
+ } else {
1519
+ result += '/select';
1520
+ }
1521
+ }
1522
+ else if (step === 'from') {
1523
+ // Don't check for alias, possibly confuses users.
1524
+ result += '/from';
1525
+ }
1526
+ else if (step === 'columns') {
1527
+ next();
1528
+ if (index >= csnPath.length)
1529
+ continue; // no column name
1530
+
1531
+ let elementHierarchy = [];
1532
+
1533
+ // Concat column+expand/inline to get a name similar to elements.
1534
+ do {
1535
+ if (currentThing?.inline)
1536
+ elementHierarchy.push(quote.angle(step + 1));
1537
+ else if (currentThing === '*')
1538
+ elementHierarchy.push(quote.angle(currentThing));
1539
+ else
1540
+ elementHierarchy.push(aliasOrReference());
1541
+
1542
+ if (peek() === 'expand' || peek() === 'inline') {
1543
+ next(); // skip expand/inline
1544
+ next(); // go to next column
1545
+ } else {
1546
+ break;
1547
+ }
1548
+ } while (index < csnPath.length);
1549
+
1550
+ if (elementHierarchy.length > 0)
1551
+ result += `/column:${ quoted(elementHierarchy.join('.')) }`;
1552
+ }
1553
+ else if(step === 'args') {
1554
+ // Should only be reached for cases, where no SELECT in a union is picked.
1555
+ next(); // skip index
1556
+ }
1557
+ else {
1558
+ return true;
1559
+ }
1560
+ next();
1561
+ }
1562
+ return true;
1532
1563
  }
1533
1564
  }
1534
1565
 
1566
+ /**
1567
+ * Traverse the view's query until targetQuery is found and count the depth.
1568
+ * If there is only one or no query at all, returns `0` so that the semantic
1569
+ * location knows it does not need to print the query index.
1570
+ *
1571
+ * @param {CSN.Path} csnPath
1572
+ * @param {CSN.Model} model
1573
+ * @param {CSN.Artifact} view
1574
+ * @return {number}
1575
+ */
1576
+ function queryDepthForMessage( csnPath, model, view ) {
1577
+ const { query: targetQuery } = analyseCsnPath(csnPath, model, false);
1578
+ if (!targetQuery)
1579
+ return 0;
1580
+ const rootQuery = view.query || { SELECT: view.projection };
1581
+ let depth = 1;
1582
+ let totalDepth = 0;
1583
+ let isFound = false;
1584
+ traverseQuery(rootQuery, null, null, (q, querySelect) => {
1585
+ if (querySelect) {
1586
+ totalDepth += 1;
1587
+ if (!isFound)
1588
+ depth += 1;
1589
+ }
1590
+ if (q === targetQuery)
1591
+ isFound = true;
1592
+ });
1593
+ if (totalDepth > 1)
1594
+ return depth;
1595
+ return 0;
1596
+ }
1597
+
1535
1598
  /**
1536
1599
  * Get the explanation string for the given message-id.
1537
1600
  * Ensure to have called hasMessageExplanation() before.
@@ -1587,5 +1650,7 @@ module.exports = {
1587
1650
  explainMessage,
1588
1651
  hasMessageExplanation,
1589
1652
  messageIdsWithExplanation,
1653
+ // for tests only
1590
1654
  constructSemanticLocationFromCsnPath,
1655
+ homeName,
1591
1656
  };
@@ -641,8 +641,15 @@ function createOptionProcessor() {
641
641
  // hard-coded option dependencies (they disappear with command)
642
642
  return result;
643
643
 
644
- // Verify parameter value 'param' against option definition 'opt'. Return an error
645
- // string or false for an accepted param. Use 'prefix' when mentioning the option name.
644
+ /**
645
+ * Verify parameter value 'param' against option definition 'opt'. Return an error
646
+ * string or false for an accepted param. Use 'prefix' when mentioning the option name.
647
+ *
648
+ * @param param
649
+ * @param opt
650
+ * @param prefix
651
+ * @return {string|boolean}
652
+ */
646
653
  function verifyOptionParam( param, opt, prefix ) {
647
654
  if (opt.param) {
648
655
  // Parameter is required for this option
@@ -0,0 +1,50 @@
1
+ // Return shuffle functions using pseudo random functions
2
+
3
+ // By <https://github.com/bryc/code/blob/c97a26ad27a9f9d4f48cd3307fd8ee6f1772d4eb/jshash/PRNGs.md>:
4
+
5
+ // The random seed must be an integer between 0 and 4294967295 (or 1 and 4294967296 = 2**32)
6
+ function shuffleGen( seed ) {
7
+ return (Number.isSafeInteger( seed ) && seed > 0)
8
+ ? { shuffleArray, shuffleDict }
9
+ : { shuffleArray: a => a, shuffleDict: d => d };
10
+
11
+ function random() { // from function mulberry32() in doc above
12
+ seed = seed + 0x6D2B79F5 | 0;
13
+ let t = Math.imul( seed ^ seed >>> 15, 1 | seed );
14
+ t = t + Math.imul( t ^ t >>> 7, 61 | t ) ^ t;
15
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
16
+ }
17
+
18
+ /**
19
+ * Shuffles array in place.
20
+ * https://en.wikipedia.org/wiki/Fisher–Yates_shuffle#The_modern_algorithm
21
+ *
22
+ * @param {Array} array items An array containing the items.
23
+ */
24
+ function shuffleArray( array ) {
25
+ let i = array.length;
26
+ while (i > 1) {
27
+ const j = Math.floor( random() * i );
28
+ --i;
29
+ const temp = array[i];
30
+ array[i] = array[j];
31
+ array[j] = temp;
32
+ }
33
+ return array;
34
+ }
35
+
36
+ /**
37
+ * Return a shuffled version of `dict`.
38
+ */
39
+ function shuffleDict( dict ) {
40
+ if (!dict)
41
+ return dict;
42
+ const names = shuffleArray( Object.keys( dict ) );
43
+ const r = Object.create( null );
44
+ for (const n of names)
45
+ r[n] = dict[n];
46
+ return r;
47
+ }
48
+ }
49
+
50
+ module.exports = shuffleGen;