@sap/cds-compiler 3.0.0 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +104 -9
  2. package/bin/.eslintrc.json +2 -1
  3. package/bin/cdsc.js +28 -16
  4. package/doc/API.md +11 -0
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +24 -2
  7. package/doc/CHANGELOG_DEPRECATED.md +21 -1
  8. package/lib/api/main.js +92 -40
  9. package/lib/api/options.js +2 -3
  10. package/lib/base/keywords.js +64 -1
  11. package/lib/base/message-registry.js +33 -5
  12. package/lib/base/messages.js +54 -65
  13. package/lib/base/model.js +2 -0
  14. package/lib/base/optionProcessorHelper.js +53 -21
  15. package/lib/checks/actionsFunctions.js +8 -7
  16. package/lib/checks/selectItems.js +96 -14
  17. package/lib/checks/types.js +5 -8
  18. package/lib/checks/validator.js +1 -2
  19. package/lib/compiler/assert-consistency.js +65 -13
  20. package/lib/compiler/base.js +6 -4
  21. package/lib/compiler/builtins.js +93 -4
  22. package/lib/compiler/checks.js +1 -1
  23. package/lib/compiler/define.js +28 -23
  24. package/lib/compiler/extend.js +20 -11
  25. package/lib/compiler/finalize-parse-cdl.js +5 -9
  26. package/lib/compiler/index.js +2 -0
  27. package/lib/compiler/populate.js +37 -32
  28. package/lib/compiler/propagator.js +11 -6
  29. package/lib/compiler/resolve.js +15 -19
  30. package/lib/compiler/shared.js +54 -18
  31. package/lib/compiler/tweak-assocs.js +5 -11
  32. package/lib/compiler/utils.js +15 -6
  33. package/lib/edm/annotations/genericTranslation.js +12 -2
  34. package/lib/edm/annotations/preprocessAnnotations.js +18 -15
  35. package/lib/edm/csn2edm.js +18 -17
  36. package/lib/edm/edm.js +22 -13
  37. package/lib/edm/edmAnnoPreprocessor.js +349 -0
  38. package/lib/edm/edmInboundChecks.js +85 -0
  39. package/lib/edm/edmPreprocessor.js +336 -665
  40. package/lib/edm/edmUtils.js +86 -45
  41. package/lib/gen/Dictionary.json +29 -9
  42. package/lib/gen/language.checksum +1 -1
  43. package/lib/gen/language.interp +1 -2
  44. package/lib/gen/languageLexer.js +3 -0
  45. package/lib/gen/languageParser.js +4332 -4496
  46. package/lib/inspect/.eslintrc.json +4 -0
  47. package/lib/inspect/index.js +14 -0
  48. package/lib/inspect/inspectModelStatistics.js +81 -0
  49. package/lib/inspect/inspectPropagation.js +189 -0
  50. package/lib/inspect/inspectUtils.js +44 -0
  51. package/lib/json/from-csn.js +19 -20
  52. package/lib/json/to-csn.js +11 -8
  53. package/lib/language/genericAntlrParser.js +150 -92
  54. package/lib/language/language.g4 +47 -74
  55. package/lib/main.d.ts +1 -0
  56. package/lib/model/api.js +1 -1
  57. package/lib/model/csnRefs.js +56 -29
  58. package/lib/model/csnUtils.js +29 -14
  59. package/lib/model/revealInternalProperties.js +6 -4
  60. package/lib/modelCompare/compare.js +3 -0
  61. package/lib/optionProcessor.js +81 -38
  62. package/lib/render/toCdl.js +57 -32
  63. package/lib/render/toHdbcds.js +1 -1
  64. package/lib/render/toSql.js +31 -11
  65. package/lib/render/utils/common.js +3 -4
  66. package/lib/transform/db/associations.js +43 -35
  67. package/lib/transform/db/cdsPersistence.js +0 -1
  68. package/lib/transform/db/flattening.js +3 -4
  69. package/lib/transform/db/transformExists.js +7 -5
  70. package/lib/transform/draft/db.js +1 -1
  71. package/lib/transform/forHanaNew.js +11 -2
  72. package/lib/transform/forOdataNew.js +4 -4
  73. package/lib/transform/localized.js +15 -11
  74. package/lib/transform/odata/typesExposure.js +14 -5
  75. package/lib/utils/file.js +28 -18
  76. package/lib/utils/moduleResolve.js +0 -1
  77. package/package.json +3 -4
  78. package/share/messages/syntax-expected-integer.md +9 -8
  79. package/lib/checks/unknownMagic.js +0 -41
@@ -190,7 +190,7 @@ module.exports = {
190
190
  // SAP HANA keywords, used for smart quoting in to-hdi.plain
191
191
  // Taken from https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/28bcd6af3eb6437892719f7c27a8a285.html
192
192
  // Better use keywords in ptime/query/parser/syntax/qp_keyword.cc minus those
193
- // in rule unreserved_keyword_column (=…_common - "CONSTRAINT") in
193
+ // in rule unreserved_keyword_column (=…_common - 'CONSTRAINT') in
194
194
  // ptime/query/parser/syntax/qp_gram.y of the HANA sources.
195
195
  hana: [
196
196
  'ABAPITAB',
@@ -209,12 +209,16 @@ module.exports = {
209
209
  'ABAP_STRING',
210
210
  'ABAP_TIME',
211
211
  'ABAP_XSTRING',
212
+ 'ABS',
213
+ 'ACOS',
212
214
  'ADD_DAYS',
213
215
  'ADD_MONTHS',
214
216
  'ADD_SECONDS',
215
217
  'ADD_YEARS',
218
+ 'ADJACENCY',
216
219
  'ADOPT',
217
220
  'ALL',
221
+ 'ALPHANUM',
218
222
  'ALTER',
219
223
  'ANALYTIC',
220
224
  'ANY',
@@ -222,7 +226,11 @@ module.exports = {
222
226
  'ARRAY',
223
227
  'ARRAY_AGG',
224
228
  'AS',
229
+ 'ASCII',
230
+ 'ASIN',
225
231
  'AT',
232
+ 'ATAN',
233
+ 'ATAN2',
226
234
  'AUTHORIZATION',
227
235
  'AUTO',
228
236
  'AVG',
@@ -240,6 +248,9 @@ module.exports = {
240
248
  'BIND_DOUBLE',
241
249
  'BIND_NCHAR',
242
250
  'BIND_REAL',
251
+ 'BINTEXT',
252
+ 'BINTOHEX',
253
+ 'BITAND',
243
254
  'BLOB',
244
255
  'BOOLEAN',
245
256
  'BOTH',
@@ -249,9 +260,25 @@ module.exports = {
249
260
  'BY',
250
261
  'CASE',
251
262
  'CAST',
263
+ 'CEIL',
264
+ 'CEILING',
265
+ 'CE_AGGREGATION',
252
266
  'CE_CALC',
267
+ 'CE_CALC_VIEW',
268
+ 'CE_COLUMN_TABLE',
269
+ 'CE_COMM2R',
270
+ 'CE_CONVERSION',
271
+ 'CE_FULL_OUTER_JOIN',
253
272
  'CE_JOIN',
273
+ 'CE_JOIN_VIEW',
274
+ 'CE_LEFT_OUTER_JOIN',
275
+ 'CE_MERGE',
276
+ 'CE_OLAP_VIEW',
277
+ 'CE_PARTITION',
254
278
  'CE_PROJECTION',
279
+ 'CE_RIGHT_OUTER_JOIN',
280
+ 'CE_UNION_ALL',
281
+ 'CE_VERTICAL_UNION',
255
282
  'CHAR',
256
283
  'CHARACTER',
257
284
  'CLOB',
@@ -262,8 +289,12 @@ module.exports = {
262
289
  'CONNECT',
263
290
  'CONSTANT',
264
291
  'CONSTRAINT',
292
+ 'COS',
293
+ 'COSH',
294
+ 'COT',
265
295
  'COUNT',
266
296
  'CROSS',
297
+ 'CS_ALPHANUM',
267
298
  'CS_DATE',
268
299
  'CS_DAYDATE',
269
300
  'CS_DECIMAL_FLOAT',
@@ -285,6 +316,7 @@ module.exports = {
285
316
  'CS_TEXT',
286
317
  'CS_TEXT_AE',
287
318
  'CS_TIME',
319
+ 'CS_ZONE',
288
320
  'CUBE',
289
321
  'CUME_DIST',
290
322
  'CURDATE',
@@ -313,6 +345,7 @@ module.exports = {
313
345
  'DAYOFWEEK',
314
346
  'DAYS_BETWEEN',
315
347
  'DDIC_ACCP',
348
+ 'DDIC_ALNM',
316
349
  'DDIC_CDAY',
317
350
  'DDIC_CHAR',
318
351
  'DDIC_CLNT',
@@ -373,12 +406,14 @@ module.exports = {
373
406
  'EXCEPTION',
374
407
  'EXEC',
375
408
  'EXISTS',
409
+ 'EXP',
376
410
  'EXTRACT',
377
411
  'FALSE',
378
412
  'FILTER',
379
413
  'FIRST_VALUE',
380
414
  'FLATTEN',
381
415
  'FLOAT',
416
+ 'FLOOR',
382
417
  'FOR',
383
418
  'FORCE_FIRST_PASSWORD_CHANGE',
384
419
  'FREESTYLESEARCHATTRIBUTE',
@@ -390,6 +425,7 @@ module.exports = {
390
425
  'GROUPING',
391
426
  'GROUPING_FILTER',
392
427
  'GROUPING_ID',
428
+ 'GROUP_SCORE',
393
429
  'HASANYPRIVILEGES',
394
430
  'HASSYSTEMPRIVILEGE',
395
431
  'HAVING',
@@ -403,6 +439,7 @@ module.exports = {
403
439
  'HIERARCHY_SIBLINGS',
404
440
  'HIERARCHY_SPANTREE',
405
441
  'HIERARCHY_TEMPORAL',
442
+ 'HIGHLIGHTED',
406
443
  'HILBERT',
407
444
  'HOST',
408
445
  'HOUR',
@@ -426,10 +463,12 @@ module.exports = {
426
463
  'JSON_TABLE',
427
464
  'JSON_VALUE',
428
465
  'LAG',
466
+ 'LANGUAGE',
429
467
  'LAST_DAY',
430
468
  'LAST_VALUE',
431
469
  'LATERAL',
432
470
  'LAYOUT',
471
+ 'LCASE',
433
472
  'LEAD',
434
473
  'LEADING',
435
474
  'LEAST',
@@ -439,10 +478,13 @@ module.exports = {
439
478
  'LENGTHB',
440
479
  'LEVELS',
441
480
  'LIMIT',
481
+ 'LN',
442
482
  'LOCATE',
443
483
  'LOCATE_REGEXPR',
484
+ 'LOG',
444
485
  'LONGDATE',
445
486
  'LOOP',
487
+ 'LOWER',
446
488
  'LPAD',
447
489
  'LTRIM',
448
490
  'MAP',
@@ -450,9 +492,11 @@ module.exports = {
450
492
  'MAP_REDUCE',
451
493
  'MAX',
452
494
  'MEASURES',
495
+ 'MIMETYPE',
453
496
  'MIN',
454
497
  'MINUS',
455
498
  'MINUTE',
499
+ 'MOD',
456
500
  'MONTH',
457
501
  'MULTIPARENT',
458
502
  'NATURAL',
@@ -486,9 +530,11 @@ module.exports = {
486
530
  'PERCENTILE_DISC',
487
531
  'PERCENT_RANK',
488
532
  'PLAIN',
533
+ 'POWER',
489
534
  'PRIOR',
490
535
  'PRODUCT',
491
536
  'RANGE',
537
+ 'RANGE_RESTRICTION',
492
538
  'RANK',
493
539
  'RAW',
494
540
  'RDICT',
@@ -525,11 +571,17 @@ module.exports = {
525
571
  'SESSION_CONTEXT',
526
572
  'SESSION_USER',
527
573
  'SET',
574
+ 'SHORTTEXT',
528
575
  'SIBLING',
576
+ 'SIGN',
577
+ 'SIN',
578
+ 'SINH',
529
579
  'SMALLDECIMAL',
530
580
  'SMALLINT',
581
+ 'SNIPPETS',
531
582
  'SOME',
532
583
  'SQL',
584
+ 'SQRT',
533
585
  'START',
534
586
  'STDDEV',
535
587
  'STRING',
@@ -537,6 +589,7 @@ module.exports = {
537
589
  'ST_ALPHASHAPEAGGR',
538
590
  'ST_ALPHASHAPEAREAAGGR',
539
591
  'ST_ALPHASHAPEEDGEAGGR',
592
+ 'ST_ASMVT',
540
593
  'ST_ASSVGAGGR',
541
594
  'ST_CIRCULARSTRING',
542
595
  'ST_CLUSTERCELL',
@@ -552,6 +605,7 @@ module.exports = {
552
605
  'ST_FROMTEXT',
553
606
  'ST_GEOMETRY',
554
607
  'ST_GEOMETRYCOLLECTION',
608
+ 'ST_GEOMFROMESRIJSON',
555
609
  'ST_GEOMFROMEWKB',
556
610
  'ST_GEOMFROMEWKT',
557
611
  'ST_GEOMFROMGEOHASH',
@@ -562,6 +616,8 @@ module.exports = {
562
616
  'ST_INTERSECTIONAGGR',
563
617
  'ST_LINESTRING',
564
618
  'ST_MAKELINE',
619
+ 'ST_MAKEPOLYGON',
620
+ 'ST_MEMORY_LOB',
565
621
  'ST_MULTILINESTRING',
566
622
  'ST_MULTIPOINT',
567
623
  'ST_MULTIPOLYGON',
@@ -586,9 +642,12 @@ module.exports = {
586
642
  'TABLE',
587
643
  'TABLES',
588
644
  'TABLESAMPLE',
645
+ 'TAN',
646
+ 'TANH',
589
647
  'TARGET',
590
648
  'TEMPORARY',
591
649
  'TEXT',
650
+ 'TEXT_FILTER',
592
651
  'THEN',
593
652
  'THREAD',
594
653
  'TIME',
@@ -614,6 +673,7 @@ module.exports = {
614
673
  'TO_CHAR',
615
674
  'TO_CLOB',
616
675
  'TO_DATE',
676
+ 'TO_DATS',
617
677
  'TO_DECIMAL',
618
678
  'TO_DOUBLE',
619
679
  'TO_INT',
@@ -642,9 +702,12 @@ module.exports = {
642
702
  'TRIGGER_UPDATE_COLUMN',
643
703
  'TRIM',
644
704
  'TRUE',
705
+ 'UCASE',
706
+ 'UNICODE',
645
707
  'UNION',
646
708
  'UNKNOWN',
647
709
  'UNNEST',
710
+ 'UPPER',
648
711
  'USER',
649
712
  'USING',
650
713
  'UTCTIMESTAMP',
@@ -114,7 +114,7 @@ const centralMessages = {
114
114
  'ref-undefined-def': { severity: 'Error' },
115
115
  'ref-undefined-var': { severity: 'Error' },
116
116
  'ref-undefined-element': { severity: 'Error' },
117
- 'ref-unknown-var': { severity: 'Info' },
117
+ 'ref-unknown-var': { severity: 'Info', errorFor: [ 'to.hdbcds', 'to.sql', 'to.hdi', 'to.rename' ] },
118
118
  'ref-obsolete-parameters': { severity: 'Error', configurableFor: true }, // does not hurt us
119
119
  'ref-undefined-param': { severity: 'Error' },
120
120
  'ref-rejected-on': { severity: 'Error' },
@@ -131,6 +131,10 @@ const centralMessages = {
131
131
  'service-nested-context': { severity: 'Error', configurableFor: true }, // does not hurt compile, TODO
132
132
  'service-nested-service': { severity: 'Error', configurableFor: 'deprecated' }, // not supported yet
133
133
 
134
+ // 'syntax-duplicate-annotate' came late with v3 - make it configurable as
135
+ // fallback, but then parse.cdl is not supposed to work correctly (it can
136
+ // then either issue an error or produce a CSN missing some annotations):
137
+ 'syntax-duplicate-annotate': { severity: 'Error', configurableFor: true },
134
138
  'syntax-expected-cardinality': { severity: 'Error' },
135
139
  'syntax-expected-length': { severity: 'Error' },
136
140
  'syntax-expected-translation': { severity: 'Error' },
@@ -176,6 +180,15 @@ const centralMessages = {
176
180
  // The keys will be added to `oldNames` of the new message, which is used for reclassification.
177
181
  const oldMessageIds = createDict({
178
182
  'old-anno-duplicate': 'anno-duplicate', // Example
183
+
184
+ // These IDs are used by large stakeholders. If we change them, we should
185
+ // be backward-compatible.
186
+ // 'redirected-to-complex': 'TODO',
187
+ // 'wildcard-excluding-one': 'TODO',
188
+ // 'wildcard-excluding-many': 'TODO',
189
+ // 'assoc-outside-service': 'TODO',
190
+ // 'redirected-to-same': 'TODO',
191
+ // 'query-navigate-many': 'TODO',
179
192
  });
180
193
 
181
194
  // Set up the old-to-new message ID mapping in the message registry.
@@ -196,8 +209,8 @@ for (const oldName in oldMessageIds) {
196
209
  // For messageIds, where no text has been provided via code (central def)
197
210
  const centralMessageTexts = {
198
211
  'api-invalid-option': {
199
- std: 'Option $(NAME) is deprecated! Use SNAPI options instead',
200
- magicVars: 'Option “magicVars” is deprecated! Use “variableReplacements” instead. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
212
+ std: 'Option $(NAME) is no longer supported! Use SNAPI options instead',
213
+ magicVars: 'Option “magicVars” is no longer supported! Use “variableReplacements” instead. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
201
214
  user: 'Option “variableReplacements” expects “$user” instead of “user”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
202
215
  locale: 'Option “variableReplacements” expects “$user.locale” instead of “locale”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
203
216
  'noDollar': 'Option “variableReplacements” does not know $(NAME). Did you forget a leading “$”?'
@@ -219,6 +232,10 @@ const centralMessageTexts = {
219
232
  'flatten-fkey-exists': 'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element',
220
233
  },
221
234
 
235
+ 'syntax-anno-ignored': {
236
+ std: 'Annotations can\'t be used at prefix references',
237
+ doc: 'Doc comments can\'t be used at prefix references',
238
+ },
222
239
  'syntax-unexpected-ellipsis': {
223
240
  std: 'Expected no more than one $(CODE)',
224
241
  'nested-array': 'Unexpected $(CODE) in nested array'
@@ -267,6 +284,12 @@ const centralMessageTexts = {
267
284
  unknown: 'Unknown argument $(CODE)',
268
285
  duplicate: 'Duplicate argument $(CODE)',
269
286
  },
287
+ 'syntax-duplicate-annotate': 'You can\'t refer to $(NAME) repeatedly with property $(PROP) in the same annotate statement',
288
+ 'syntax-duplicate-extend': {
289
+ std: 'You can\'t define and refer to $(NAME) repeatedly in the same extend statement',
290
+ define: 'You can\'t refer to $(NAME) in the same extend statement where it was defined',
291
+ extend: 'You can\'t refer to $(NAME) repeatedly in the same extend statement',
292
+ },
270
293
  'syntax-invalid-literal': {
271
294
  'std': 'Invalid literal',
272
295
  'uneven-hex': 'A binary literal must have an even number of characters',
@@ -294,7 +317,7 @@ const centralMessageTexts = {
294
317
  aspect: 'Element $(ID) has not been found in the anonymous target aspect'
295
318
  },
296
319
  'ref-unknown-var': {
297
- std: 'Replacement $(ID) not found'
320
+ std: 'No replacement found for special variable $(ID)'
298
321
  },
299
322
  'ref-unexpected-draft-enabled': 'Composition in draft-enabled entity can\'t lead to another entity with $(ANNO)',
300
323
  'ref-rejected-on': {
@@ -312,6 +335,7 @@ const centralMessageTexts = {
312
335
  event: 'Unexpected $(KEYWORD) for the type of an event',
313
336
  param: 'Unexpected $(KEYWORD) for the type of a parameter definition',
314
337
  select: 'Unexpected $(KEYWORD) for type references in queries',
338
+ annotation: '$(KEYWORD) can\'t be used in annotation definitions',
315
339
  },
316
340
 
317
341
  'type-missing-argument': 'Missing value for argument $(NAME) in reference to type $(ID)',
@@ -451,7 +475,11 @@ const centralMessageTexts = {
451
475
  std: 'Entity can\'t be created due to name collision with existing definition $(NAME)',
452
476
  proxy: 'No proxy entity created due to name collision with existing definition $(NAME) of kind $(KIND)'
453
477
  },
454
- 'odata-navigation': 'No OData navigation property generated, target $(TARGET) is outside of service $(SERVICE)'
478
+ 'odata-navigation': {
479
+ std: 'No OData navigation property generated, target $(TARGET) is outside of service $(SERVICE)',
480
+ oncond: 'No OData navigation property generated for association with arbitrary ON condition and target $(TARGET) outside of service $(SERVICE)'
481
+ },
482
+ 'odata-parameter-order': 'Unexpected mandatory after optional parameter',
455
483
  }
456
484
 
457
485
  /**
@@ -8,7 +8,6 @@ const { term } = require('../utils/term');
8
8
  const { locationString } = require('./location');
9
9
  const { isDeprecatedEnabled } = require('./model');
10
10
  const { centralMessages, centralMessageTexts, oldMessageIds } = require('./message-registry');
11
- const { copyPropIfExist } = require('../utils/objectUtils');
12
11
  const _messageIdsWithExplanation = require('../../share/messages/message-explanations.json').messages;
13
12
  const { analyseCsnPath, traverseQuery } = require('../model/csnRefs');
14
13
  const { CompilerAssertion } = require("./error");
@@ -135,7 +134,7 @@ class CompileMessage {
135
134
  constructor(location, msg, severity = 'Error', id = null, home = null, moduleName = null) {
136
135
  this.message = msg;
137
136
  this.location = location;
138
- this.$location = dollarLocation( this.location );
137
+ this.$location = { ...this.location, address: undefined };
139
138
  this.validNames = null;
140
139
  if (home) // semantic location, e.g. 'entity:"E"/element:"x"'
141
140
  this.home = home;
@@ -152,33 +151,6 @@ class CompileMessage {
152
151
  }
153
152
  }
154
153
 
155
- /**
156
- * Temporary v1 function to convert an "old-style" location to "new-style".
157
- *
158
- * @param {CSN.Location} location
159
- * @return {CSN.Location}
160
- * @todo Remove
161
- */
162
- function dollarLocation( location ) {
163
- const file = location && location.file || undefined;
164
- if (!file)
165
- return {};
166
- const loc = {
167
- file,
168
- line: location.line,
169
- col: location.col,
170
- address: undefined,
171
- };
172
- copyPropIfExist(location, 'endLine', loc);
173
- copyPropIfExist(location, 'endCol', loc);
174
- // TODO:
175
- // return {
176
- // ...location,
177
- // address: undefined,
178
- // };
179
- return loc;
180
- }
181
-
182
154
  const severitySpecs = {
183
155
  error: { name: 'Error', level: 0 },
184
156
  warning: { name: 'Warning', level: 1 },
@@ -257,14 +229,16 @@ function compareSeverities( a, b ) {
257
229
  }
258
230
 
259
231
  /**
260
- * @todo This was copied from somewhere just to make CSN paths work.
232
+ * Find the nearest $location for the given CSN path in the model.
233
+ * If the path does not exist, the parent is used, and so on.
234
+ *
261
235
  * @param {CSN.Model} model
262
236
  * @param {CSN.Path} csnPath
237
+ * @returns {CSN.Location | null}
263
238
  */
264
- function searchForLocation( model, csnPath ) {
239
+ function findNearestLocationForPath( model, csnPath ) {
265
240
  if (!model)
266
241
  return null;
267
- // Don't display a location if we cannot find one!
268
242
  let lastLocation = null;
269
243
  /** @type {object} */
270
244
  let currentStep = model;
@@ -435,10 +409,10 @@ function makeMessageFunction( model, options, moduleName = null ) {
435
409
  // CSN.Location (with line/endLine, col/endCol)
436
410
  return [ location, location.home || null, null ]
437
411
 
438
- const isCsnPath = (typeof location[0] === 'string');
412
+ const isCsnPath = (typeof location[0] === 'string'); // could be `definitions`, `extensions`, ....
439
413
  if (isCsnPath) {
440
414
  return [
441
- searchForLocation( model, location ),
415
+ findNearestLocationForPath( model, location ),
442
416
  constructSemanticLocationFromCsnPath( location, model ),
443
417
  location[1] // location[0] is 'definitions'
444
418
  ];
@@ -1086,9 +1060,10 @@ function compareMessageSeverityAware( a, b ) {
1086
1060
  * @param {CompileMessage} msg
1087
1061
  */
1088
1062
  function homeSortName( { home, messageId } ) {
1089
- return (!home)
1090
- ? (messageId && /^(syntax|api)-/.test( messageId ) ? ' ' + messageId : '~')
1091
- : home.substring( home.indexOf(':') ); // i.e. starting with the ':', is always there
1063
+ if (!home)
1064
+ return (messageId && /^(syntax|api)-/.test( messageId ) ? ' ' + messageId : '~')
1065
+ else
1066
+ return home.substring( home.indexOf(':') ); // i.e. starting with the ':', is always there
1092
1067
  }
1093
1068
 
1094
1069
  /**
@@ -1174,7 +1149,7 @@ function homeName( art, absoluteOnly ) {
1174
1149
  return null;
1175
1150
  else if (art.kind === 'using')
1176
1151
  return 'using:' + quoted( art.name.id );
1177
- else if (art.kind === 'extend')
1152
+ else if (art.kind === 'extend' || art.kind === 'annotate')
1178
1153
  return !absoluteOnly && homeNameForExtend ( art );
1179
1154
  else if (art.name._artifact) // block, extend, annotate
1180
1155
  return homeName( art.name._artifact, absoluteOnly ); // use corresponding definition
@@ -1189,35 +1164,41 @@ function homeName( art, absoluteOnly ) {
1189
1164
  // The "home" for extensions is handled differently because `_artifact` is not
1190
1165
  // set for unknown extensions and we could have nested extensions.
1191
1166
  function homeNameForExtend( art ) {
1167
+ const kind = art.kind || 'extend';
1192
1168
  // TODO: fix the following - do like in collectArtifactExtensions() or
1193
- // basically resolveUncheckedPath()
1194
- const absoluteName = (art.name.id ? art.name.id :
1195
- art.name.path.map(s => s && s.id).join('.'));
1169
+ // basically resolveUncheckedPath()
1170
+ const absoluteName = art.name.id != null ? art.name.id :
1171
+ (!art.name.element && art.name.absolute || art.name.path && art.name.path.map(s => s && s.id).join('.'));
1196
1172
 
1197
1173
  // Surrounding parent may be another extension.
1198
1174
  const parent = art._parent;
1199
1175
  if (!parent)
1200
- return 'extend:' + quoted(absoluteName);
1201
-
1202
- // And that extension's artifact could have been found.
1203
- const parentArt = parent.name && parent.name._artifact;
1204
- if (!parentArt)
1205
- return artName(parent) + '/' + quoted(absoluteName);
1206
-
1207
- let extensionName;
1208
- if (parentArt.enum || parentArt.elements) {
1209
- const fakeArt = {
1210
- kind: parentArt.enum ? 'enum' : 'element',
1211
- name: { element: absoluteName }
1212
- };
1213
- extensionName = artName(fakeArt);
1176
+ return kind + ':' + quoted(absoluteName);
1177
+
1178
+ if (art.name.param && parent.params) {
1179
+ const fakeArt = { kind: 'param', name: { param: absoluteName } };
1180
+ return homeNameForExtend(parent) + '/' + artName(fakeArt);
1214
1181
  }
1215
- else {
1216
- extensionName = 'extend:' + quoted(absoluteName);
1182
+ else if (art.name.action && parent.actions) {
1183
+ const type = art.name._artifact?.kind || 'action';
1184
+ const fakeArt = { kind: type, name: { action: absoluteName }, _main: art.name._artifact?._main };
1185
+ return homeNameForExtend(parent) + '/' + artName(fakeArt);
1217
1186
  }
1218
- // Even though the parent artifact was found, we use kind 'extend'
1219
- // to make it clear that we are inside an (element) extension.
1220
- return 'extend:' + artName(parentArt) + '/' + extensionName;
1187
+ else if (parent.enum || parent.elements || parent.returns?.elements) {
1188
+ // For enum, extensions may store them in `elements`, i.e. don't differ between enum/elements,
1189
+ // so we need to look at the parent artifact.
1190
+ // For `extend <art> with enum`, there is `enum`.
1191
+ const parentArt = parent.name?._artifact;
1192
+ const fakeKind = (parent.enum || parentArt?.enum) ? 'enum' : 'element';
1193
+ const fakeArt = { kind: fakeKind, name: { element: art.name.element } };
1194
+ let parentOfElementChain = parent;
1195
+ while (parentOfElementChain.name?.element && parentOfElementChain._parent)
1196
+ parentOfElementChain = parentOfElementChain._parent;
1197
+
1198
+ return homeNameForExtend(parentOfElementChain) + '/' + artName(fakeArt);
1199
+ }
1200
+ // This case should not happen, but just in case
1201
+ return kind + ':' + artName(parent);
1221
1202
  }
1222
1203
 
1223
1204
  function constructSemanticLocationFromCsnPath(csnPath, model) {
@@ -1230,10 +1211,14 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1230
1211
  ];
1231
1212
  const queryProps = [ 'from', 'where', 'groupBy', 'having', 'orderBy', 'limit', 'offset' ];
1232
1213
 
1233
- let { query } = analyseCsnPath(
1234
- csnPath,
1235
- model
1236
- );
1214
+ if (csnPath[0] === 'extensions') {
1215
+ const ext = model.extensions && model.extensions[csnPath[1]] || {};
1216
+ if (ext.annotate)
1217
+ return 'annotate:' + quoted(ext.annotate);
1218
+ return 'extend:' + quoted(ext.extend);
1219
+ }
1220
+
1221
+ let { query } = analyseCsnPath(csnPath, model, false);
1237
1222
 
1238
1223
  // remove definitions
1239
1224
  csnPath.shift();
@@ -1373,8 +1358,12 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1373
1358
  if (typeof step === 'number') {
1374
1359
  if (currentThing.as)
1375
1360
  result += `:${ _quoted(currentThing.as) }`;
1361
+ else if (inRef)
1362
+ result += `:${ _quoted(currentThing) }`;
1363
+ else if (currentThing.ref)
1364
+ result += `:${ _quoted(currentThing.ref.map(r => r.id ? r.id : r).join('.')) }`;
1376
1365
  else
1377
- result += inRef ? `:${ _quoted(currentThing) }` : currentThing.ref ? `:${ _quoted(currentThing.ref.map(r => r.id ? r.id : r).join('.')) }` : '';
1366
+ return'';
1378
1367
 
1379
1368
  break;
1380
1369
  }
package/lib/base/model.js CHANGED
@@ -30,6 +30,8 @@ const availableBetaFlags = {
30
30
  enableUniversalCsn: true,
31
31
  // disabled by --beta-mode
32
32
  nestedServices: false,
33
+ odataOpenType: true,
34
+ optionalActionFunctionParameters: true,
33
35
  };
34
36
 
35
37
  /**
@@ -153,36 +153,67 @@ function createOptionProcessor() {
153
153
  * Internal: Define a general or command option.
154
154
  * Throws if the option is already registered in the given command context.
155
155
  * or in the given command.
156
+ *
156
157
  * @private
157
158
  * @see option()
158
159
  */
159
160
  function _addOption(cmd, optString, validValues, options) {
160
- const opt = _parseOptionString(optString, validValues);
161
- Object.assign(opt, options);
161
+ const cliOpt = _parseOptionString(optString, validValues);
162
+ Object.assign(cliOpt, options);
163
+ _addLongOption(cmd, cliOpt.longName, cliOpt);
164
+ _addShortOption(cmd, cliOpt.shortName, cliOpt);
165
+
166
+ for (const alias of cliOpt.aliases || []) {
167
+ const aliasOpt = Object.assign({ }, cliOpt, { isAlias: true });
168
+ _addLongOption(cmd, alias, aliasOpt); // use same camelName, etc. for alias
169
+ }
170
+
171
+ return cmd;
172
+ }
162
173
 
163
- if (cmd.options[opt.longName]) {
164
- throw new Error(`Duplicate assignment for long option ${opt.longName}`);
165
- } else if (optionProcessor.options[opt.longName]) {
174
+ /**
175
+ * Internal: Add longName to the list of options.
176
+ * Throws if the option is already registered in the given command context.
177
+ * or in the given command.
178
+ * `longName` may differ from `opt.longName`, e.g. for aliases.
179
+ *
180
+ * @private
181
+ * @see _addOption()
182
+ */
183
+ function _addLongOption(cmd, longName, opt) {
184
+ if (cmd.options[longName]) {
185
+ throw new Error(`Duplicate assignment for long option ${longName}`);
186
+ } else if (optionProcessor.options[longName]) {
166
187
  // This path is only taken if optString is for commands
167
188
  optionProcessor.optionClashes.push({
168
- option: opt.longName,
169
- description: `Command '${cmd.longName}' has option clash with general options for: ${opt.longName}`
189
+ option: longName,
190
+ description: `Command '${cmd.longName}' has option clash with general options for: ${longName}`
170
191
  });
171
192
  }
172
- cmd.options[opt.longName] = opt;
173
- if (opt.shortName) {
174
- if (cmd.options[opt.shortName]) {
175
- throw new Error(`Duplicate assignment for short option ${opt.shortName}`);
176
- } else if (optionProcessor.options[opt.shortName]) {
177
- // This path is only taken if optString is for commands
178
- optionProcessor.optionClashes.push({
179
- option: opt.shortName,
180
- description: `Command '${cmd.longName}' has option clash with general options for: ${opt.shortName}`
181
- });
182
- }
183
- cmd.options[opt.shortName] = opt;
193
+ cmd.options[longName] = opt;
194
+ }
195
+ /**
196
+ * Internal: Add shortName to the list of options.
197
+ * Throws if the option is already registered in the given command context.
198
+ * or in the given command.
199
+ * `longName` may differ from `opt.longName`, e.g. for aliases.
200
+ *
201
+ * @private
202
+ * @see _addOption()
203
+ */
204
+ function _addShortOption(cmd, shortName, opt) {
205
+ if (!shortName)
206
+ return;
207
+ if (cmd.options[shortName]) {
208
+ throw new Error(`Duplicate assignment for short option ${shortName}`);
209
+ } else if (optionProcessor.options[shortName]) {
210
+ // This path is only taken if optString is for commands
211
+ optionProcessor.optionClashes.push({
212
+ option: shortName,
213
+ description: `Command '${cmd.longName}' has option clash with general options for: ${shortName}`
214
+ });
184
215
  }
185
- return cmd;
216
+ cmd.options[shortName] = opt;
186
217
  }
187
218
 
188
219
  // Internal: Parse one command string like "F, toFoo". Return an object like this
@@ -275,7 +306,8 @@ function createOptionProcessor() {
275
306
  shortName,
276
307
  camelName: camelifyLongOption(longName),
277
308
  param,
278
- validValues
309
+ validValues,
310
+ isAlias: false, // default
279
311
  }
280
312
  }
281
313