@sap/cds-compiler 5.7.2 → 5.7.4

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 CHANGED
@@ -7,6 +7,14 @@
7
7
  Note: `beta` fixes, changes and features are usually not listed in this ChangeLog but [here](doc/CHANGELOG_BETA.md).
8
8
  The compiler behavior concerning `beta` features can change at any time without notice.
9
9
 
10
+ ## Version 5.7.4 - 2025-02-05
11
+
12
+ ### Fixed
13
+
14
+ - New CDL Parser (option `newParser: true`)
15
+ + Improve code completion
16
+ + Fix further edge cases in error recovery
17
+
10
18
  ## Version 5.7.2 - 2025-01-30
11
19
 
12
20
  ### Fixed
@@ -98,7 +98,7 @@ function stringOrRaw( val ) {
98
98
  */
99
99
  function combinedLocation( start, end ) {
100
100
  if (!start || !start.location)
101
- return end.location;
101
+ return end?.location;
102
102
  else if (!end || !end.location)
103
103
  return start.location;
104
104
  const loc = {
package/lib/base/model.js CHANGED
@@ -168,8 +168,6 @@ function forEachMember( construct, callback, target ) {
168
168
  function forEachMemberRecursively( construct, callback, target ) {
169
169
  forEachMember( construct, ( member, memberName, prop ) => {
170
170
  callback( member, memberName, prop );
171
- if (Array.isArray(member.$duplicates)) // redefinitions
172
- member.$duplicates.forEach( o => callback( o, memberName, prop ) );
173
171
  // Descend into nested members, too
174
172
  forEachMemberRecursively( member, callback );
175
173
  }, target);
@@ -177,15 +175,16 @@ function forEachMemberRecursively( construct, callback, target ) {
177
175
 
178
176
  // Apply function `callback` to all objects in dictionary `dict`, including all
179
177
  // duplicates (found under the same name). Function `callback` is called with
180
- // the following arguments: the object, the name, and -if it is a duplicate-
178
+ // the following arguments: the object, the name, and - if it is a duplicate -
181
179
  // the array index and the array containing all duplicates.
182
180
  function forEachGeneric( obj, prop, callback ) {
183
181
  const dict = obj[prop];
184
182
  for (const name in dict) {
185
183
  obj = dict[name];
184
+ const { $duplicates } = obj;
186
185
  callback( obj, name, prop );
187
- if (Array.isArray(obj.$duplicates)) // redefinitions
188
- obj.$duplicates.forEach( o => callback( o, name, prop ) );
186
+ if (Array.isArray($duplicates)) // redefinitions
187
+ $duplicates.forEach( o => callback( o, name, prop ) );
189
188
  }
190
189
  }
191
190
 
@@ -1155,6 +1155,9 @@ function define( model ) {
1155
1155
  setLink( elem, '_block', bl );
1156
1156
  const existing = parent[prop]?.[name];
1157
1157
  const add = construct !== parent && (!existing || elem.$inferred !== 'include');
1158
+ // don't dump with `entity T {}; extend T with { extend e {}; e {}; e {} };`:
1159
+ if (elem.$duplicates === true && add)
1160
+ elem.$duplicates = null;
1158
1161
  setMemberParent( elem, name, parent, add && prop );
1159
1162
  // console.log(message( null, elem.location, elem, {}, 'Info', 'INIT').toString())
1160
1163
  checkRedefinition( elem );
@@ -1459,7 +1459,7 @@ function resolve( model ) {
1459
1459
  type = getOrigin( type );
1460
1460
  const err = message( 'ref-undefined-enum', [ sym.location, assignment ],
1461
1461
  { id: sym.id, type } );
1462
- if (options.newParser)
1462
+ if (options.newParser || options.newparser)
1463
1463
  attachAndEmitValidNames( err, symbols );
1464
1464
  }
1465
1465
  }
@@ -1239,10 +1239,15 @@ limit:[683,455,,'queryOnLeft'],minus:'limit',order:'limit',union:'limit',except:
1239
1239
  'argumentsAndFilter',
1240
1240
  {'(':['c',712,,,,'prepareSpecialFunction'],'':731},
1241
1241
  {Id:['ciA',719,,'isNamedArg'],'':713},
1242
- {Id:[714,733],'#':'Id','(':'Id','*':'Id','+':'Id','-':'Id',':':'Id','?':'Id',Id_all:'Id',Number:'Id',String:'Id',QuotedLiteral:'Id','':730},
1242
+ {
1243
+ Id:[714,733],'#':'Id','(':'Id','*':'Id','+':'Id','-':'Id',':':'Id','?':'Id',Id_all:'Id',Number:'Id',String:'Id',QuotedLiteral:'Id',
1244
+ DeleteStarFromSet:['c',730],
1245
+ '':730
1246
+ },
1243
1247
  {',':['c',715,,,,'nextFunctionArgument'],'':716},
1244
1248
  {
1245
1249
  Id:[714,733],'#':'Id','(':'Id','*':'Id','+':'Id','-':'Id',':':'Id','?':'Id',not:'Id',case:'Id',cast:'Id',null:'Id',true:'Id',false:'Id',Id_all:'Id',Number:'Id',String:'Id',exists:'Id',QuotedLiteral:'Id',
1250
+ DeleteStarFromSet:['c',714],
1246
1251
  ')':['g',716,1],order:')',
1247
1252
  },
1248
1253
  {order:['ck',717],'':730},
@@ -3176,7 +3181,7 @@ case 457:switch(this.lk()){
3176
3181
  case'all':case'distinct':if(this.ck(458)){ quantifier = this.valueWithLocation(); }continue
3177
3182
  default:this.s=458;continue
3178
3183
  }
3179
- case 458:if(this.queryExpression(_={},455)){query=_.expr; if ($.expr.$parens || op.val !== $.expr.op.val || quantifier?.val !== $.expr.quantifier?.val) $.expr = { op, args: [$.expr], quantifier, location: { ...$.expr.location } };
3184
+ case 458:if(this.queryExpression(_={},455)){query=_.expr; if ($.expr.$parens || op.val !== $.expr.op?.val || quantifier?.val !== $.expr.quantifier?.val) $.expr = { op, args: [$.expr], quantifier, location: { ...$.expr.location } };
3180
3185
  quantifier = undefined;
3181
3186
  $.expr.args.push( query ); this.attachLocation( $.expr ); }continue
3182
3187
  case 459:switch(this.lk()){
@@ -3295,9 +3300,9 @@ default:this.gr([]);continue
3295
3300
  }
3296
3301
  case 490:if(this.mk(491,'join')){ if ($.expr?.join?.val !== 'cross' || $.expr.$parens) $.expr = { op: this.valueWithLocation(), join: this.valueWithLocation( undefined, join ), args: [ $.expr ] }; }continue
3297
3302
  case 491:switch(this.l()){
3298
- case'(':if(this.tableOrQueryParens(_={},489)){tab=_.expr; $.expr.args.push( this.taggedIfQuery( tab ) );
3303
+ case'(':if(this.tableOrQueryParens(_={},489)){tab=_.expr; const r = this.taggedIfQuery( tab ); if (r) $.expr.args.push( r );
3299
3304
  this.attachLocation( $.expr ); }continue
3300
- case'Id':if(this.fromRefWithOptAlias(_={},489)){tab=_.expr; $.expr.args.push( tab );
3305
+ case'Id':if(this.fromRefWithOptAlias(_={},489)){tab=_.expr; if (tab) $.expr.args.push( tab );
3301
3306
  this.attachLocation( $.expr ); }continue
3302
3307
  default:this.e();continue
3303
3308
  }
@@ -3594,7 +3599,7 @@ default:this.gi(584);continue
3594
3599
  case 575:if(this.mi(584,'ItemAlias')){ art.name = this.identAst(); }continue
3595
3600
  case 576:switch(this.lk()){
3596
3601
  case'over':if(this.ck(577)){ this.pushXprToken( expr.suffix = [] ); }continue
3597
- case'*':case'+':case'-':case'/':case'<':case'=':case'>':case'?':case'!=':case'<=':case'<>':case'>=':case'in':case'is':case'or':case'||':case'and':case'not':case'like':case'between':if(this.expression(_={ expr: expr },579,651)){e=_.expr; Object.assign( e.location, expr.location ); art.value = this.attachLocation( e ) }continue
3602
+ case'*':case'+':case'-':case'/':case'<':case'=':case'>':case'?':case'!=':case'<=':case'<>':case'>=':case'in':case'is':case'or':case'||':case'and':case'not':case'like':case'between':if(this.expression(_={ expr: expr },579,651)){e=_.expr; Object.assign( e.location || {}, expr.location ); art.value = this.attachLocation( e ) }continue
3598
3603
  case'as':this.ck(581);continue
3599
3604
  case'Id':if(this.ci(584,'ItemAlias')){this.nestedExpand(); art.name = this.fragileAlias( true ); }continue
3600
3605
  case'.':if(this.c(582)){ this.reportUnexpectedSpace( this.lb(), this.la().location, true );
@@ -3603,7 +3608,7 @@ default:if(this.gi(584)){this.nestedExpand(); this.classifyImplicitName( 'ItemIm
3603
3608
  }
3604
3609
  case 577:this.overClause({outer:expr.suffix},578);continue
3605
3610
  case 578:switch(this.lk()){
3606
- case'*':case'+':case'-':case'/':case'<':case'=':case'>':case'?':case'!=':case'<=':case'<>':case'>=':case'in':case'is':case'or':case'||':case'and':case'not':case'like':case'between':if(this.expression(_={ expr: expr },579,651)){e=_.expr; Object.assign( e.location, expr.location ); art.value = this.attachLocation( e ) }continue
3611
+ case'*':case'+':case'-':case'/':case'<':case'=':case'>':case'?':case'!=':case'<=':case'<>':case'>=':case'in':case'is':case'or':case'||':case'and':case'not':case'like':case'between':if(this.expression(_={ expr: expr },579,651)){e=_.expr; Object.assign( e.location || {}, expr.location ); art.value = this.attachLocation( e ) }continue
3607
3612
  default:this.s=579;continue
3608
3613
  }
3609
3614
  case 579:switch(this.lk()){
@@ -3916,7 +3921,9 @@ default:this.s=676;continue
3916
3921
  }
3917
3922
  case 675:if(this.expression(_={},676)){e=_.expr; $.expr.args?.push( e ); }continue
3918
3923
  case 676:this.s=661;{ this.attachLocation( $.expr ); }continue
3919
- default:return this.exit_()
3924
+ default:
3925
+ if (this.s == null) this.attachLocation( $.expr )
3926
+ return this.exit_()
3920
3927
  }
3921
3928
  }
3922
3929
  expressionOrQueryParens($,$next){
@@ -4046,6 +4053,7 @@ default:this.s=713;continue
4046
4053
  }
4047
4054
  case 713:switch(this.l()){
4048
4055
  case'Id':case'#':case'(':case'*':case'+':case'-':case':':case'?':case'Id_all':case'Number':case'String':case'QuotedLiteral':if(this.funcExpression(_={},714)){expr=_.expr; $.pathStep.args.push( expr ); }continue
4056
+ case'DeleteStarFromSet':this.c(730);continue
4049
4057
  default:this.s=730;continue
4050
4058
  }
4051
4059
  case 714:switch(this.l()){
@@ -4054,6 +4062,7 @@ default:this.s=716;continue
4054
4062
  }
4055
4063
  case 715:switch(this.lk()){
4056
4064
  case'Id':case'#':case'(':case'*':case'+':case'-':case':':case'?':case'not':case'case':case'cast':case'null':case'true':case'false':case'Id_all':case'Number':case'String':case'exists':case'QuotedLiteral':if(this.funcExpression(_={},714)){expr=_.expr; $.pathStep.args.push( expr ); }continue
4065
+ case'DeleteStarFromSet':this.c(714);continue
4057
4066
  case')':case'order':this.gP(716);continue
4058
4067
  default:this.ei();continue
4059
4068
  }
package/lib/main.d.ts CHANGED
@@ -172,7 +172,6 @@ declare namespace compiler {
172
172
  withLocations?: boolean|string
173
173
  /**
174
174
  * Use the new non-ANTLR based parser for compilation.
175
- * Experimental flag!
176
175
  *
177
176
  * @since v5.2.0
178
177
  */
@@ -171,7 +171,7 @@ class AstBuildingParser extends BaseParser {
171
171
  this.dynamic_.generic = spec ? spec[call.argPos] : specialFunctions[''][1];
172
172
  }
173
173
 
174
- lGenericIntroOrExpr( tryGenericIntro = true ) {
174
+ lGenericIntroOrExpr( _mode, tryGenericIntro = true ) {
175
175
  const { keyword, type } = this.la();
176
176
  // TODO: use lower-case in specialFunctions
177
177
  const text = typeof keyword === 'string' ? keyword.toUpperCase() : type;
@@ -180,37 +180,44 @@ class AstBuildingParser extends BaseParser {
180
180
  if (this.dynamic_.generic?.IN === 'separator')
181
181
  this.prec_ = PRECEDENCE_OF_IN_PREDICATE; // only expressions if `in` is separator
182
182
  if (generic !== 'expr')
183
- return (generic === 'intro') ? 'GenericIntro' : 'Id';
183
+ return (generic === 'intro') ? 'GenericIntro' : type;
184
+ // if both intro and expr: specialFunctions[fn][argPos][token] = 'expr'
184
185
  const next = this.tokens[this.tokenIdx + 1];
185
- if (next.type !== ',' && next.type !== ')' &&
186
+ if (next && next.type !== ',' && next.type !== ')' &&
186
187
  this.dynamic_.generic[next.keyword?.toUpperCase?.()] !== 'separator')
187
188
  return 'GenericIntro';
188
189
  }
189
- return (generic === 'expr') ? 'GenericExpr' : 'Id';
190
+ return (generic === 'expr') ? 'GenericExpr' : type;
190
191
  }
191
192
 
192
193
  lGenericExpr() {
193
- return this.lGenericIntroOrExpr( false );
194
+ return this.lGenericIntroOrExpr( null, false );
194
195
  }
195
196
 
196
- lGenericSeparator() { // TODO: { keyword, type } as arg ?
197
+ lGenericSeparator() {
197
198
  const { keyword, type } = this.la();
198
199
  // TODO: use lower-case in specialFunctions
199
200
  const text = typeof keyword === 'string' ? keyword.toUpperCase() : type;
200
201
  const generic = this.dynamic_.generic?.[text];
201
- return (generic === 'separator') ? 'GenericSeparator' : ',';
202
+ return (generic === 'separator') ? 'GenericSeparator' : type;
202
203
  }
203
204
 
204
205
  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 {
206
+ const token = parserTokens[tokenName];
207
+ // TODO: use lower-case in specialFunctions
208
+ const realTokens = token && this.dynamic_.generic?.[token];
209
+ if (realTokens) {
211
210
  for (const t of realTokens)
212
211
  super.addTokenToSet_( set, t.toLowerCase(), val, collectKeywordsOnly );
213
212
  }
213
+ else if (tokenName === 'DeleteStarFromSet') { // in rule `argumentsAndFilter`
214
+ // TODO: workaround for (`GenericExpr : Id_all | '*'`), see #13485.
215
+ // Works, since `DeleteStarFromSet` comes after `*` (length-sorted):
216
+ delete set['*'];
217
+ }
218
+ else {
219
+ super.addTokenToSet_( set, tokenName, val, collectKeywordsOnly );
220
+ }
214
221
  }
215
222
 
216
223
  inSelectItem( _test, arg ) { // only as action
@@ -731,14 +738,15 @@ class AstBuildingParser extends BaseParser {
731
738
  }
732
739
 
733
740
  taggedIfQuery( query ) {
734
- return (query.op && queryOps[query.op.val])
741
+ // attached actions are run even if rules ends prematurely → query can be
742
+ // undefined
743
+ return (query?.op && queryOps[query.op.val])
735
744
  ? { query, location: query.$parens?.at( -1 ) ?? query.location }
736
745
  : query;
737
746
  }
738
747
 
739
- addNamedArg( args, idToken, expr ) {
740
- expr.name = this.identAst( idToken );
741
- (args.args ?? args)[expr.name.id] = expr;
748
+ addNamedArg( pathItem, idToken, expr ) {
749
+ this.addDef( expr, pathItem, 'args', 0, this.identAst( idToken ) );
742
750
  }
743
751
 
744
752
  ixprAst( args ) {
@@ -1100,7 +1100,7 @@ queryExpression returns[ default expr = {} ] locals[ op, quantifier ]
1100
1100
  )
1101
1101
  query=queryExpression
1102
1102
  // with same op/quantifier: make left-assoc binary to nary:
1103
- { if ($expr.$parens || $op.val !== $expr.op.val || $quantifier?.val !== $expr.quantifier?.val) $expr = { op, args: [$expr], quantifier, location: { ...$.expr.location } }; } // TODO: ...$
1103
+ { if ($expr.$parens || $op.val !== $expr.op?.val || $quantifier?.val !== $expr.quantifier?.val) $expr = { op, args: [$expr], quantifier, location: { ...$.expr.location } }; } // TODO: ...$
1104
1104
  { $quantifier = undefined; }
1105
1105
  { $expr.args.push( $query ); this.attachLocation( $expr ); }
1106
1106
  )*
@@ -1169,9 +1169,9 @@ tableExpression returns[ default expr = {} ] // TableOrJoin
1169
1169
  (
1170
1170
  join=CROSS JOIN
1171
1171
  { if ($expr?.join?.val !== 'cross' || $expr.$parens) $expr = { op: this.valueWithLocation(), join: this.valueWithLocation( undefined, $join ), args: [ $expr ] }; }
1172
- ( tab=tableOrQueryParens { $expr.args.push( this.taggedIfQuery( $tab ) ); }
1172
+ ( tab=tableOrQueryParens { const r = this.taggedIfQuery( $tab ); if (r) $expr.args.push( r ); }
1173
1173
  { this.attachLocation( $expr ); }
1174
- | tab=fromRefWithOptAlias { $expr.args.push( $tab ); }
1174
+ | tab=fromRefWithOptAlias { if ($tab) $expr.args.push( $tab ); }
1175
1175
  { this.attachLocation( $expr ); }
1176
1176
  )
1177
1177
  |
@@ -1393,11 +1393,11 @@ selectItemDef[ columns ] locals[ art = new XsnArtifact() ]
1393
1393
  OVER { this.pushXprToken( $expr.suffix = [] ); }
1394
1394
  overClause[ $expr.suffix ]
1395
1395
  ( e=expression[ ...{ expr: $expr } ]<atAltStart>
1396
- { Object.assign( $e.location, $expr.location ); $art.value = this.attachLocation( $e )}
1396
+ { Object.assign( $e.location || {}, $expr.location ); $art.value = this.attachLocation( $e )}
1397
1397
  )?
1398
1398
  |
1399
1399
  e=expression[ ...{ expr: $expr } ]<atAltStart>
1400
- { Object.assign( $e.location, $expr.location ); $art.value = this.attachLocation( $e )}
1400
+ { Object.assign( $e.location || {}, $expr.location ); $art.value = this.attachLocation( $e )}
1401
1401
  )
1402
1402
  ( AS Id['ItemAlias'] { $art.name = this.identAst(); }
1403
1403
  | Id_restricted['ItemAlias'] { $art.name = this.fragileAlias( true ); }
@@ -1533,9 +1533,8 @@ valuePath returns[ default expr = { path: [] } ] locals[ pathItem ]
1533
1533
  )*
1534
1534
  ;
1535
1535
 
1536
- // TODO: ? params
1537
1536
  expression returns[ default expr = {} ]
1538
- //@finally{ if (!$expr?.$parens) this.attachLocation( $expr ); }
1537
+ @finally{ if (this.s == null) this.attachLocation( $expr ); }
1539
1538
  :
1540
1539
  (
1541
1540
  expressionOrQueryParens[ ...$ ]
@@ -1716,7 +1715,8 @@ options{ minTokensMatched = 1 }
1716
1715
  (
1717
1716
  ','<prepare=nextFunctionArgument>
1718
1717
  ( expr=funcExpression { $pathStep.args.push( $expr ); }
1719
- | <exitLoop> // <cond>: only before `)`
1718
+ | DeleteStarFromSet // Workaround for missing feature, see #13485, TODO
1719
+ | <exitLoop> // TODO <cond>: only before `)`
1720
1720
  )
1721
1721
  )*
1722
1722
  ( // ORDER BY in generic functions, e.g. `first_value(id order by name)`
@@ -1725,6 +1725,8 @@ options{ minTokensMatched = 1 }
1725
1725
  orderByClauseAsXpr[ $expr.args ]
1726
1726
  { this.attachLocation( $expr ); }
1727
1727
  )?
1728
+ |
1729
+ DeleteStarFromSet // Workaround for missing feature, see #13485, TODO
1728
1730
  )?
1729
1731
  |
1730
1732
  // TODO: if we want perfect CC and error recovery, `isNamedArg` would work
@@ -1772,6 +1774,7 @@ funcExpression returns[ default expr ] locals[ args ]
1772
1774
  { $expr = this.applyOpToken(); $args = $expr.args; }
1773
1775
  e=expression { $expr.args.push( $e ); }
1774
1776
  )
1777
+ // TODO: some <restrict=Id> here?
1775
1778
  ( options{ lookahead = lGenericSeparator; }
1776
1779
  :
1777
1780
  GenericSeparator
@@ -21,7 +21,7 @@ function parseCdl( source, filename = '<undefined>.cds',
21
21
  options = {}, messageFunctions = null,
22
22
  rule = 'cdl' ) {
23
23
  const rulespec = rules[rule];
24
- if (!options.newParser) // TODO: (options.newParser === false)
24
+ if (!options.newParser && !options.newparser)
25
25
  return parseWithAntlr( source, filename, options, messageFunctions, rulespec );
26
26
  const { CdlParser } = gen;
27
27
  if (CdlParser.tracingParser) // tracing → direct console output of message
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "5.7.2",
3
+ "version": "5.7.4",
4
4
  "description": "CDS (Core Data Services) compiler and backends",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "author": "SAP SE (https://www.sap.com)",