@sap/cds-compiler 5.5.2 → 5.7.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 (57) hide show
  1. package/CHANGELOG.md +43 -1
  2. package/bin/cdsse.js +4 -0
  3. package/bin/cdsv2m.js +2 -1
  4. package/doc/Versioning.md +4 -4
  5. package/lib/api/options.js +1 -0
  6. package/lib/base/builtins.js +2 -2
  7. package/lib/base/dictionaries.js +1 -2
  8. package/lib/base/keywords.js +3 -1
  9. package/lib/base/lazyload.js +1 -1
  10. package/lib/base/message-registry.js +170 -143
  11. package/lib/base/messages.js +69 -59
  12. package/lib/base/model.js +3 -3
  13. package/lib/base/node-helpers.js +17 -16
  14. package/lib/base/optionProcessorHelper.js +13 -14
  15. package/lib/base/shuffle.js +4 -1
  16. package/lib/checks/structuredAnnoExpressions.js +1 -1
  17. package/lib/compiler/assert-consistency.js +1 -1
  18. package/lib/compiler/builtins.js +4 -1
  19. package/lib/compiler/extend.js +20 -5
  20. package/lib/compiler/resolve.js +45 -9
  21. package/lib/compiler/shared.js +1 -0
  22. package/lib/edm/annotations/edmJson.js +3 -3
  23. package/lib/edm/annotations/genericTranslation.js +5 -1
  24. package/lib/edm/annotations/vocabularyDefinitions.js +8 -2
  25. package/lib/edm/edmUtils.js +2 -1
  26. package/lib/gen/BaseParser.js +142 -103
  27. package/lib/gen/CdlParser.js +2240 -2201
  28. package/lib/gen/Dictionary.json +185 -6
  29. package/lib/json/from-csn.js +2 -0
  30. package/lib/json/to-csn.js +13 -4
  31. package/lib/language/docCommentParser.js +11 -5
  32. package/lib/language/errorStrategy.js +3 -3
  33. package/lib/language/genericAntlrParser.js +2 -0
  34. package/lib/model/csnUtils.js +6 -1
  35. package/lib/optionProcessor.js +5 -1
  36. package/lib/parsers/AstBuildingParser.js +200 -86
  37. package/lib/parsers/CdlGrammar.g4 +142 -86
  38. package/lib/parsers/Lexer.js +5 -3
  39. package/lib/parsers/index.js +1 -1
  40. package/lib/render/toCdl.js +6 -5
  41. package/lib/render/toHdbcds.js +1 -1
  42. package/lib/render/toSql.js +5 -3
  43. package/lib/render/utils/common.js +19 -6
  44. package/lib/render/utils/standardDatabaseFunctions.js +576 -0
  45. package/lib/transform/addTenantFields.js +2 -1
  46. package/lib/transform/db/expansion.js +3 -0
  47. package/lib/transform/db/flattening.js +18 -77
  48. package/lib/transform/db/groupByOrderBy.js +2 -2
  49. package/lib/transform/db/rewriteCalculatedElements.js +14 -19
  50. package/lib/transform/db/temporal.js +2 -1
  51. package/lib/transform/forOdata.js +1 -1
  52. package/lib/transform/odata/adaptAnnotationRefs.js +79 -0
  53. package/lib/transform/odata/createForeignKeys.js +25 -9
  54. package/lib/transform/odata/flattening.js +11 -1
  55. package/lib/transform/transformUtils.js +20 -85
  56. package/package.json +2 -1
  57. package/bin/cds_update_annotations.js +0 -180
@@ -9,6 +9,7 @@ const { functionsWithoutParentheses } = require('./identifiers');
9
9
  const { pathName } = require('../compiler/utils');
10
10
  const { quotedLiteralPatterns, specialFunctions } = require('../compiler/builtins');
11
11
  const parserTokens = { // TODO: precompile into specialFunction
12
+ __proto__: null,
12
13
  GenericIntro: 'intro',
13
14
  GenericExpr: 'expr',
14
15
  GenericSeparator: 'separator',
@@ -36,10 +37,19 @@ const queryOps = {
36
37
  minus: 'query',
37
38
  };
38
39
 
40
+ const extensionsCode = {
41
+ __proto__: null,
42
+ definitions: 'extend … with definitions',
43
+ context: 'extend context',
44
+ service: 'extend service',
45
+ };
46
+
39
47
  const PRECEDENCE_OF_IN_PREDICATE = 10;
40
48
  const PRECEDENCE_OF_EQUAL = 10;
41
49
 
42
50
  class AstBuildingParser extends BaseParser {
51
+ leanConditions = { afterBrace: true };
52
+
43
53
  constructor( lexer, keywords, table, options, messageFunctions ) {
44
54
  super( lexer, keywords, table ); // lexer has file
45
55
  this.options = options;
@@ -84,12 +94,18 @@ class AstBuildingParser extends BaseParser {
84
94
 
85
95
  reportUnexpectedToken_() {
86
96
  const token = this.la();
87
- const expecting = this.expectingArray();
88
- const err = this.error( 'syntax-unexpected-token', token,
89
- { offending: this.antlrName( token ), expecting } );
97
+ const args = { offending: this.antlrName( token ), expecting: this.expectingArray() };
98
+ const errorMethod = this.conditionTokenIdx === this.tokenIdx &&
99
+ this[`${ this.conditionName }Error`];
100
+ let err = errorMethod && errorMethod.call( this, args, token );
101
+ // TODO: should we set the msg variant always? (→ no nestedExpandError necessary)
102
+ if (errorMethod && !err)
103
+ args['#'] ??= this.conditionName;
104
+ err ||= this.error( 'syntax-unexpected-token', token, args );
90
105
  // No 'unwanted' variant, no 'syntax-missing-token'
91
- err.expectedTokens = expecting;
106
+ err.expectedTokens = args.expecting;
92
107
  }
108
+
93
109
  reportReservedWord_() {
94
110
  const token = this.la();
95
111
  const err = this.message( 'syntax-unexpected-reserved-word', token,
@@ -98,13 +114,13 @@ class AstBuildingParser extends BaseParser {
98
114
  err.expectedTokens = this.expectingArray();
99
115
  }
100
116
 
101
- tableWithoutAs() {
117
+ tableWithoutAs() { // not used in <guard=…>, only called by other guard
102
118
  // TODO TOOL: if the tool properly creates `default: this.giR()`, this
103
119
  // condition method is most likely not necessary
104
120
  const { keyword } = this.la();
105
121
  // TODO: if necessary, we could allow some keywords, and just make sure that
106
122
  // all JOIN variants are still possible
107
- return !keyword || this.keywords[keyword] == null;
123
+ return keyword && this.keywords[keyword] != null;
108
124
  }
109
125
 
110
126
  /**
@@ -114,22 +130,22 @@ class AstBuildingParser extends BaseParser {
114
130
  * recursive call, it can finally turn to be both a query or expr/table
115
131
  * - <prepare=queryOnLeft, arg=‹SomeVal›>: make the current parentheses
116
132
  * context to be not a query anymore
117
- * - <cond=queryOnLeft> tests whether the expression on the left is a query
118
- * - <cond=queryOnLeft, arg=‹SomeVal›>: tests whether the expression on the
133
+ * - <guard=queryOnLeft> tests whether the expression on the left is a query
134
+ * - <guard=queryOnLeft, arg=‹SomeVal›>: tests whether the expression on the
119
135
  * left is a query, then make the current context to be not a query anymore
120
- * - <cond=queryOnLeft, arg=tableWithoutAs>: …after having checked
136
+ * - <guard=queryOnLeft, arg=tableWithoutAs>: …after having checked
121
137
  * whether the next token is no (reserved or unreserved) keyword
122
138
  */
123
139
  queryOnLeft( test, arg ) {
124
140
  if (arg === 'tableWithoutAs') {
125
- if (!this.tableWithoutAs())
126
- return false;
141
+ if (this.tableWithoutAs())
142
+ return true;
127
143
  }
128
144
  else if (!arg && !test) {
129
145
  // provide new dynamic parentheses context, except with direct
130
146
  // recursive call:
131
147
  if (this.inSameRule_( this.s, this.stack.at( -1 ).followState ))
132
- return true;
148
+ return false;
133
149
  this.dynamic_.parenthesesCtx = [ null ];
134
150
  this._tracePush( 'Parentheses()' );
135
151
  }
@@ -137,7 +153,7 @@ class AstBuildingParser extends BaseParser {
137
153
  const noQuery = parenthesesCtx?.[0];
138
154
  if (arg && parenthesesCtx)
139
155
  parenthesesCtx[0] = arg;
140
- return !noQuery;
156
+ return noQuery;
141
157
  }
142
158
 
143
159
  prepareSpecialFunction() {
@@ -185,14 +201,21 @@ class AstBuildingParser extends BaseParser {
185
201
  return (generic === 'separator') ? 'GenericSeparator' : ',';
186
202
  }
187
203
 
188
- translateParserToken_( tokenName ) {
189
- const realTokens = this.dynamic_.generic?.[parserTokens[tokenName]];
190
- // TODO: avoid parserTokens dict, use lower-case in specialFunctions
191
- return realTokens?.map( s => s.toLowerCase() ) ?? [ tokenName ];
204
+ addTokenToSet_( set, tokenName, val, collectKeywordsOnly ) {
205
+ const realTokens = parserTokens[tokenName] && this.dynamic_.generic?.[parserTokens[tokenName]];
206
+ // TODO: avoid 2nd parserTokens dict use, use lower-case in specialFunctions
207
+ if (!realTokens) {
208
+ super.addTokenToSet_( set, tokenName, val, collectKeywordsOnly );
209
+ }
210
+ else {
211
+ for (const t of realTokens)
212
+ super.addTokenToSet_( set, t.toLowerCase(), val, collectKeywordsOnly );
213
+ }
192
214
  }
193
215
 
194
216
  inSelectItem( _test, arg ) { // only as action
195
- this.dynamic_.inSelectItem = arg;
217
+ this.dynamic_.inSelectItem = arg ||
218
+ (this.tokens[this.tokenIdx - 2].type === '.' ? 'inline' : 'expand');
196
219
  }
197
220
 
198
221
  /**
@@ -200,28 +223,47 @@ class AstBuildingParser extends BaseParser {
200
223
  * (also inside sub queries in those, which will be rejected later anyway)
201
224
  */
202
225
  modifierRestriction() {
203
- return this.dynamic_.inSelectItem !== 'nested';
226
+ const { inSelectItem } = this.dynamic_;
227
+ // TODO: really reject for top-level "inline"?
228
+ return inSelectItem === 'expand' || inSelectItem === 'inline';
229
+ }
230
+ modifierRestrictionError( args, offending ) {
231
+ return this.error( 'syntax-unexpected-modifier', offending, args,
232
+ // TODO: we would have text variant for expand or inline,
233
+ // but we probably allow `key` in nested top-level inline
234
+ 'Unexpected $(OFFENDING) in nested expand/inline, expecting $(EXPECTING)' );
204
235
  }
205
236
 
206
237
  isDotForPath() { // see also inSelectItem
238
+ // TODO: also consider whether we are in the <prefer>ed `valuePath` branch
207
239
  if (this.dynamic_.inSelectItem == null)
208
- return true;
240
+ return false;
209
241
  // TODO: it would be best to set this.dynamic_.inSelectItem to null in filters
210
242
  // (as <prepare>)
211
243
  const next = this.tokens[this.tokenIdx + 1]?.type;
212
- return next !== '*' && next !== '{';
244
+ return next === '*' || next === '{';
245
+ }
246
+
247
+ notAfterEntityArgOrFilter( mode ) { // TODO: for <hide>
248
+ if (mode !== 'M')
249
+ return false;
250
+ const { type } = this.lb();
251
+ if (type !== ')' && type !== ']')
252
+ return false;
253
+ const { followState } = this.stack.at( -1 );
254
+ return this.table[followState][':'];
213
255
  }
214
256
 
215
257
  // <prec=10, postfix=once> + test that the next token is not `null`; TODO: code
216
258
  // completion for `… default 3 not ~;` → currently just `null` but hey
217
259
  isNegatedRelation( _test, prec ) {
218
- return this.tokens[this.tokenIdx + 1]?.keyword !== 'null' &&
260
+ return this.tokens[this.tokenIdx + 1]?.keyword === 'null' ||
219
261
  this.precNone_( _test, prec );
220
262
  }
221
263
 
222
264
  isNamedArg() {
223
- const { type } = this.tokens[this.tokenIdx + 1];
224
- return type === ':' || type === '=>';
265
+ const type = this.tokens[this.tokenIdx + 1]?.type;
266
+ return type !== ':' && type !== '=>';
225
267
  }
226
268
 
227
269
  /**
@@ -229,28 +271,66 @@ class AstBuildingParser extends BaseParser {
229
271
  * `namespace`
230
272
  */
231
273
  namespaceRestriction() {
232
- return ++this.topLevel$ < 1;
274
+ return ++this.topLevel$ > 0;
233
275
  }
234
276
 
235
277
  /**
236
278
  * `extend`/`annotate` is forbidden inside `extend … with definitions` and
237
- * variants
279
+ * variants. TODO: combine with `vocabularyRestriction`.
238
280
  */
239
281
  extensionRestriction() {
240
- // TODO: use `syntax-unexpected-extension` as message
282
+ // 'syntax-unexpected-extension': 'Unexpected $(KEYWORD) inside $(CODE) block',
241
283
  const r = this.dynamic_.inExtension;
242
- this.dynamic_.inExtension = true;
243
- return !r;
284
+ this.dynamic_.inExtension = this.tokenIdx + 1;
285
+ return r;
286
+ }
287
+ extensionRestrictionError( args, token ) {
288
+ const extendIdx = this.conditionFailure;
289
+ const variant = this.tokens[extendIdx + 1]?.type === 'Id' &&
290
+ this.tokens[extendIdx].keyword;
291
+ args.code = extensionsCode[variant] || extensionsCode.definitions;
292
+ args['#'] = 'new-parser';
293
+ return this.error( 'syntax-unexpected-extension', token, args );
244
294
  }
245
295
 
246
296
  /**
247
- * `annotation` def is only allowed top-level
297
+ * `annotation` def is only allowed top-level. TODO: combine with `extensionRestriction`
248
298
  */
249
299
  vocabularyRestriction( test ) {
250
- // TODO: use `syntax-unexpected-vocabulary` as message
251
300
  if (!test)
252
- this.dynamic_.inBlock = true;
253
- return !this.dynamic_.inBlock;
301
+ this.dynamic_.inBlock = this.tokenIdx;
302
+ return this.dynamic_.inBlock ?? this.dynamic_.inExtension;
303
+ }
304
+ vocabularyRestrictionError( args, token ) {
305
+ const extendIdx = this.conditionFailure;
306
+ args['#'] = `${ this.tokens[extendIdx - 1].keyword }-new`;
307
+ return this.error( 'syntax-unexpected-vocabulary', token, args );
308
+ }
309
+
310
+ /**
311
+ * Restrictions according to the expression of a select column.
312
+ * Currently only to restrict it to a single `Id` for published associations.
313
+ * No extra syntax-unexpected-assoc for failure.
314
+ */
315
+ columnExpr( mode, arg ) {
316
+ if (mode)
317
+ return !this.columnExpr$;
318
+ if (arg)
319
+ this.columnExpr$ = this.tokenIdx;
320
+ else if (this.columnExpr$ !== this.tokenIdx - 1 ||
321
+ this.lb().type !== 'Id' ||
322
+ [ 'true', 'false', 'null' ].includes( this.lb().keyword ) )
323
+ this.columnExpr$ = null;
324
+ return true;
325
+ }
326
+
327
+ nestedExpand( mode ) {
328
+ if (!mode)
329
+ this.nestedExpand$ = this.tokenIdx;
330
+ return this.nestedExpand$ !== this.tokenIdx;
331
+ }
332
+ nestedExpandError() {
333
+ // This is intentionally left empty
254
334
  }
255
335
 
256
336
  /**
@@ -270,7 +350,7 @@ class AstBuildingParser extends BaseParser {
270
350
  * now disallow annotation assignments after `= calcExpr`,
271
351
  * ignore doc comment after having called `typeExpression`
272
352
  *
273
- * Called as <cond=…>:
353
+ * Called as <guard=…>:
274
354
  *
275
355
  * - <…, arg=default> in `typeExpression` and `typeProperties`
276
356
  * is `default` allowed? If used, disallow calc and further DEFAULT
@@ -292,14 +372,14 @@ class AstBuildingParser extends BaseParser {
292
372
  let { elementCtx } = this.dynamic_;
293
373
  if (test) {
294
374
  if (elementCtx?.[0] === arg)
295
- return false;
375
+ return arg;
296
376
  if (!elementCtx) { // with type, param, or annotation defs
297
377
  // eslint-disable-next-line no-multi-assign
298
378
  elementCtx = this.dynamic_.elementCtx = [ null, false, false ];
299
379
  }
300
380
  if (arg === 'default') {
301
381
  if (elementCtx[1])
302
- return false;
382
+ return true;
303
383
  elementCtx[1] = true;
304
384
  elementCtx[0] = 'calc';
305
385
  this.prec_ = PRECEDENCE_OF_EQUAL; // only expressions for DEFAULT expr
@@ -307,7 +387,7 @@ class AstBuildingParser extends BaseParser {
307
387
  else if (arg === 'notNull') {
308
388
  if (elementCtx[2]) {
309
389
  if (this.la().keyword !== elementCtx[2] || test === 'M') // TODO v6: always error
310
- return false; // error if different nullibility specification
390
+ return true; // error if different nullibility specification
311
391
  }
312
392
  elementCtx[2] = this.la().keyword;
313
393
  }
@@ -318,17 +398,61 @@ class AstBuildingParser extends BaseParser {
318
398
  else if (elementCtx) {
319
399
  elementCtx[0] = arg;
320
400
  }
321
- return true;
401
+ return false;
402
+ }
403
+ elementRestrictionError( args, token ) {
404
+ if (this.conditionFailure !== 'calc')
405
+ return null;
406
+ args.keyword = 'default';
407
+ // TODO: investigate why 'null', '@' are not in the expected-set
408
+ // TODO: simplified version for predictions, such that ops are in expected-set ?
409
+ // TODO: also test `default 3 null = 4`
410
+ return this.error( 'syntax-unexpected-calc', token, args,
411
+ 'Unexpected $(OFFENDING) after $(KEYWORD) clause, expecting $(EXPECTING)' );
412
+ }
413
+
414
+ noRepeatedCardinality( mode ) {
415
+ if (this.tokens[this.tokenIdx - 2]?.type !== ']')
416
+ return false;
417
+ if (mode === 'M')
418
+ return true;
419
+ // currently just warning if same cardinality provided twice
420
+ const same = { one: '1', many: '*' }[this.la().keyword];
421
+ return this.tokens[this.tokenIdx - 3]?.text !== same;
422
+ }
423
+ noRepeatedCardinalityError( args ) {
424
+ let openIdx = this.tokenIdx - 2;
425
+ while (this.tokens[--openIdx].type !== '[')
426
+ ;
427
+ args.location = this.tokens[openIdx].location;
428
+ args.code = '[…]';
322
429
  }
323
430
 
324
431
  /**
325
432
  * `;` between statements is optional only after a `}` (ex braces of structure
326
- * values for annotations).
433
+ * values for annotations and foreign key specifications).
434
+ *
435
+ * Beware: mentioned in leanConditions, i.e. executed in predictions!
327
436
  */
328
- afterBrace( test ) {
329
- if (!test)
330
- this.afterBrace$ = this.tokenIdx;
331
- return this.afterBrace$ === this.tokenIdx;
437
+ afterBrace( test, arg ) {
438
+ if (!test) {
439
+ this.afterBrace$
440
+ = (!arg || this.afterBrace$ > 0 && this.tokens[this.afterBrace$ - 1].keyword === arg)
441
+ ? this.tokenIdx
442
+ : -1;
443
+ }
444
+ // TODO TOOL: the following test belongs to the BaseParser.js:
445
+ if (this.conditionTokenIdx === this.tokenIdx && // tested on same
446
+ this.conditionStackLength == null && // after error recover
447
+ test !== 'M')
448
+ return false;
449
+ // Strange optional `;` after PROJECTION ON source: the rule exit prediction
450
+ // for fromRefWithOptAlias etc now checks C(afterBrace):
451
+ if (test === 'E' && this.afterBrace$ > 0 &&
452
+ this.tokens[this.afterBrace$ - 1]?.keyword === 'projection' &&
453
+ this.tokens[this.afterBrace$].keyword === 'on')
454
+ return false;
455
+ return this.afterBrace$ !== this.tokenIdx;
332
456
  }
333
457
 
334
458
  /**
@@ -337,8 +461,8 @@ class AstBuildingParser extends BaseParser {
337
461
  annoInSameLine( test ) {
338
462
  if (!test)
339
463
  this.dynamic_.safeAnno = true;
340
- return this.dynamic_.safeAnno ||
341
- this.lb().location.line === this.la().location.line;
464
+ return !this.dynamic_.safeAnno &&
465
+ this.lb().location.line !== this.la().location.line;
342
466
  }
343
467
 
344
468
  /**
@@ -353,21 +477,30 @@ class AstBuildingParser extends BaseParser {
353
477
  else if (arg === 'ellipsis') { // on '...'
354
478
  const { arrayAnno } = this.dynamic_;
355
479
  if (!arrayAnno[0])
356
- return false;
480
+ return arrayAnno[0] == null ? 'duplicate' : arg;
357
481
  arrayAnno[0] = this.tokens[this.tokenIdx + 1]?.keyword;
358
482
  }
359
- else if (arg === 'bracket') {
483
+ else if (arg === 'bracket') { // syntax-invalid-ellipsis
360
484
  // closing bracket not allowed if last `...` in array is with `up to
361
- return typeof this.dynamic_.arrayAnno[0] !== 'string';
485
+ return typeof this.dynamic_.arrayAnno[0] === 'string' && arg;
362
486
  }
363
- else { // orNotEmpty
364
- return this.dynamic_.arrayAnno || this.lb().type !== '{';
487
+ else { // orNotEmpty -> anno value must not be empty struct
488
+ return !this.dynamic_.arrayAnno && this.lb().type === '{' && 'empty';
365
489
  }
366
- return true;
490
+ return false;
491
+ }
492
+ arrayAnnoError( args, token ) {
493
+ if (this.conditionFailure === 'duplicate') {
494
+ args['#'] = 'std'; // normal syntax-unexpected-token
495
+ return null;
496
+ }
497
+ args.code = '...';
498
+ args['#'] = this.conditionFailure;
499
+ return this.error( 'syntax-invalid-anno', token, args );
367
500
  }
368
501
 
369
502
  beforeColon() {
370
- return this.tokens[this.tokenIdx + 1]?.text === ':';
503
+ return this.tokens[this.tokenIdx + 1]?.text !== ':';
371
504
  }
372
505
 
373
506
  // Space handling etc, locations ----------------------------------------------
@@ -583,12 +716,9 @@ class AstBuildingParser extends BaseParser {
583
716
  const tokenIndex = ref?.path.at(-1)?.location.tokenIndex;
584
717
  const token = this.prevTokenWithIndex( tokenIndex ) ?? this.tokens[this.tokenIdx - 1];
585
718
  const { parsedAs } = token;
586
- if (parsedAs && parsedAs !== 'token' && parsedAs !== 'keyword') {
719
+ if (parsedAs && parsedAs !== 'token' && parsedAs !== 'keyword')
587
720
  token.parsedAs = category;
588
- return { token, parsedAs };
589
- }
590
721
  }
591
- return null;
592
722
  }
593
723
 
594
724
  taggedIfQuery( query ) {
@@ -838,7 +968,7 @@ class AstBuildingParser extends BaseParser {
838
968
  // TODO: `literal` needed?
839
969
  if (art.cardinality) {
840
970
  this.reportDuplicateClause( 'cardinality', targetMax, art.cardinality.targetMax,
841
- card.keyword, true );
971
+ card.keyword );
842
972
  }
843
973
  else {
844
974
  art.cardinality = { targetMax, location: targetMax.location };
@@ -846,9 +976,9 @@ class AstBuildingParser extends BaseParser {
846
976
  return target;
847
977
  }
848
978
 
979
+ // see also <guard=nestedExpand>
849
980
  reportExpandInline( column, isInline ) {
850
981
  // called before matching `{`
851
- const { name } = column;
852
982
  if (column.value && !column.value.path) {
853
983
  // improve error location when using "inline" `.{…}` after ref (arguments and
854
984
  // filters not covered, not worth the effort); after an expression where
@@ -864,18 +994,9 @@ class AstBuildingParser extends BaseParser {
864
994
  // - no errors for refs inside expand/inline, but for refs in sibling expr
865
995
  // - think about: reference to these (sub) elements from other view
866
996
  }
867
- if (isInline && name) {
868
- const alias = this.tokens[this.tokenIdx - 2];
869
- const location = (isInline === true)
870
- ? alias.location
871
- : this.combineLocation( isInline, alias );
872
- this.error( 'syntax-unexpected-alias', location, { code: '.{ ‹inline› }' },
873
- 'Unexpected alias name before $(CODE)' );
874
- // continuation semantics: ignore AS
875
- }
876
997
  }
877
998
 
878
- reportDuplicateClause( prop, erroneous, chosen, code, literalValIfNotEq ) {
999
+ reportDuplicateClause( prop, erroneous, chosen, code ) {
879
1000
  // probably easier for message linters not to use (?:) for the message id...?
880
1001
  const args = {
881
1002
  '#': prop,
@@ -887,11 +1008,7 @@ class AstBuildingParser extends BaseParser {
887
1008
  // TODO v6: duplicate clause = error, independently whether it is the same
888
1009
  this.warning( 'syntax-duplicate-equal-clause', erroneous.location, args );
889
1010
  }
890
- else if (prop !== 'notNull') { // already via guard in grammar
891
- if (literalValIfNotEq)
892
- args.code = chosen.val;
893
- this.message( 'syntax-duplicate-clause', erroneous.location, args );
894
- }
1011
+ // TODO extra msg text 'syntax-duplicate-clause' for noRepeatedCardinality()
895
1012
  }
896
1013
 
897
1014
  setTypeFacet( art, name, value ) {
@@ -1090,20 +1207,16 @@ class AstBuildingParser extends BaseParser {
1090
1207
  };
1091
1208
  }
1092
1209
 
1210
+ // no extra message syntax-unexpected-assoc for guard failure
1093
1211
  associationInSelectItem( art ) {
1212
+ if (art.name)
1213
+ return;
1094
1214
  this.classifyImplicitName( 'ItemAssoc', art.value );
1095
1215
  const path = art.value?.path;
1096
- // we cannot compare "just one token before `:`" because there might be annos
1097
- if (path && path.length === 1 && !art.name && !art.expand && !art.inline) {
1098
- const name = path[0];
1099
- if (path.length === 1 && !name.args && !name.cardinality && !name.where) {
1100
- art.name = name;
1101
- delete art.value;
1102
- return;
1103
- }
1216
+ if (path?.length) {
1217
+ art.name = path.at( -1 ); // usually length 1, but make it also work during error recovery
1218
+ delete art.value;
1104
1219
  }
1105
- this.error( 'syntax-unexpected-assoc', this.la(), {},
1106
- 'Unexpected association definition in select item' );
1107
1220
  }
1108
1221
 
1109
1222
  // must be in action directly after having parsed '{', '(`, or a keyword before
@@ -1313,15 +1426,16 @@ function addOneForDefinition( count, ext ) {
1313
1426
 
1314
1427
  // Significant digits (before exponent) without leading and trailing zeros
1315
1428
  function relevantDigits( val ) {
1429
+ // eslint-disable-next-line sonarjs/slow-regex
1430
+ val = val.replace( /e.+$/i, '' ); // this regex has no newlines -> is not slow
1431
+
1316
1432
  const init = /^[-+0.]+/g; // global flag to have lastIndex
1317
1433
  const zeros = /[0.]+/g;
1318
1434
  if (init.test( val )) // sets init.lastIndex
1319
1435
  zeros.lastIndex = init.lastIndex;
1320
1436
 
1321
1437
  let r;
1322
- while ((r = zeros.exec( val )) != null &&
1323
- zeros.lastIndex < val.length &&
1324
- val.charAt( zeros.lastIndex ).toLowerCase() !== 'e')
1438
+ while ((r = zeros.exec( val )) != null && zeros.lastIndex < val.length)
1325
1439
  ;
1326
1440
  return val.slice( init.lastIndex, r?.index ).replace( /\./, '' );
1327
1441
  }