@sap/cds-compiler 5.5.0 → 5.5.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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { csnPropertyOrder } = require('../json/to-csn');
4
4
  const { ModelError } = require('../base/error');
5
- const { setHidden } = require('../utils/objectUtils');
5
+ const { setHidden, hasNonEnumerable } = require('../utils/objectUtils');
6
6
  const { isAnnotationExpression } = require('../base/builtins');
7
7
 
8
8
  const csnDictionaries = {
@@ -110,10 +110,6 @@ function cloneCsn( csn, options, sort ) {
110
110
  return r;
111
111
  }
112
112
 
113
- function hasNonEnumerable( object, property ) {
114
- return Object.prototype.hasOwnProperty.call( object, property ) &&
115
- !Object.prototype.propertyIsEnumerable.call( object, property );
116
- }
117
113
 
118
114
  /**
119
115
  * Deeply clone the given CSN dictionary and return it.
@@ -71,11 +71,14 @@ class AstBuildingParser extends BaseParser {
71
71
  }
72
72
 
73
73
  expectingArray() {
74
+ const savedState = this.s;
75
+ this.s = this.errorState;
74
76
  let array = this.expectingArray_();
77
+ this.s = savedState;
75
78
  // compatibility: replace true+false by Boolean - TODO: delete
76
79
  if (array.includes( 'true' ))
77
80
  array = [ 'Boolean', ...array.filter( n => n !== 'true' && n !== 'false' ) ];
78
- return array.map( antlrName )
81
+ return array.map( tok => this.antlrName( tok ) )
79
82
  .sort( (a, b) => (tokenPrecedence(a) < tokenPrecedence(b) ? -1 : 1) );
80
83
  }
81
84
 
@@ -83,7 +86,7 @@ class AstBuildingParser extends BaseParser {
83
86
  const token = this.la();
84
87
  const expecting = this.expectingArray();
85
88
  const err = this.error( 'syntax-unexpected-token', token,
86
- { offending: antlrName( token ), expecting } );
89
+ { offending: this.antlrName( token ), expecting } );
87
90
  // No 'unwanted' variant, no 'syntax-missing-token'
88
91
  err.expectedTokens = expecting;
89
92
  }
@@ -318,10 +321,6 @@ class AstBuildingParser extends BaseParser {
318
321
  return true;
319
322
  }
320
323
 
321
- inExpandInline() { // not as <cond>
322
- this.dynamic_.inSelectItem = 'nested';
323
- }
324
-
325
324
  /**
326
325
  * `;` between statements is optional only after a `}` (ex braces of structure
327
326
  * values for annotations).
@@ -355,8 +354,11 @@ class AstBuildingParser extends BaseParser {
355
354
  const { arrayAnno } = this.dynamic_;
356
355
  if (!arrayAnno[0])
357
356
  return false;
358
- if (this.tokens[this.tokenIdx + 1]?.type === ',')
359
- arrayAnno[0] = false;
357
+ arrayAnno[0] = this.tokens[this.tokenIdx + 1]?.keyword;
358
+ }
359
+ else if (arg === 'bracket') {
360
+ // closing bracket not allowed if last `...` in array is with `up to
361
+ return typeof this.dynamic_.arrayAnno[0] !== 'string';
360
362
  }
361
363
  else { // orNotEmpty
362
364
  return this.dynamic_.arrayAnno || this.lb().type !== '{';
@@ -788,16 +790,20 @@ class AstBuildingParser extends BaseParser {
788
790
  }
789
791
  }
790
792
 
793
+ // TODO: can we remove `;`/EOF from the expected-set for `annotate Foo with ⎀`?
791
794
  checkWith( keyword ) {
792
795
  if (this.lb() !== keyword)
793
796
  return;
794
797
  const tok = this.la();
795
- if (this.docCommentIndex < tok.location.tokenIndex &&
796
- this.docCommentIndex > this.lb().location.tokenIndex)
798
+ const docTokenIndex = this.docCommentIndex &&
799
+ this.docComments[this.docCommentIndex - 1].location.tokenIndex;
800
+ if (docTokenIndex < tok.location.tokenIndex &&
801
+ docTokenIndex > this.lb().location.tokenIndex)
797
802
  return;
798
- const expecting = this.expectingArray(); // TODO: filter
803
+ // filter out what comes after current rule (no generic way necessary):
804
+ const expecting = this.expectingArray().filter( t => t !== '<EOF>' && t !== '\'}\'' );
799
805
  const msg = this.warning( 'syntax-unexpected-semicolon', tok,
800
- { offending: antlrName( tok ), expecting, keyword: 'with' },
806
+ { offending: this.antlrName( tok ), expecting, keyword: 'with' },
801
807
  // eslint-disable-next-line @stylistic/js/max-len
802
808
  'Unexpected $(OFFENDING), expecting $(EXPECTING) - ignored previous $(KEYWORD)' );
803
809
  msg.expectedTokens = expecting;
@@ -841,14 +847,15 @@ class AstBuildingParser extends BaseParser {
841
847
  }
842
848
 
843
849
  reportExpandInline( column, isInline ) {
850
+ // called before matching `{`
844
851
  const { name } = column;
845
852
  if (column.value && !column.value.path) {
846
- const token = this.la();
847
853
  // improve error location when using "inline" `.{…}` after ref (arguments and
848
854
  // filters not covered, not worth the effort); after an expression where
849
855
  // the last token is an identifier, not the `.` is wrong, but the `{`:
850
- // if (isInline && !name && this._input.LT(-1).type >= this.constructor.Identifier)
851
- // token = this._input.LT(2); -- TODO: still valid?
856
+ const token = (isInline && this.tokens[this.tokenIdx - 2].type !== 'Id')
857
+ ? this.lb()
858
+ : this.la();
852
859
  this.error( 'syntax-unexpected-nested-proj', token,
853
860
  { code: isInline ? '.{ ‹inline› }' : '{ ‹expand› }' },
854
861
  'Unexpected $(CODE); nested projections can only be used after a reference' );
@@ -1287,6 +1294,17 @@ class AstBuildingParser extends BaseParser {
1287
1294
  parent, 'elements', kind, seg );
1288
1295
  }
1289
1296
  }
1297
+
1298
+ // For compatibility with ANTLR-based parser:
1299
+ antlrName( type ) {
1300
+ if (typeof type !== 'string') {
1301
+ type = (!type.parsedAs && this.keywords[type.keyword ?? ''] != null ||
1302
+ type.parsedAs === 'keyword') && type.keyword || type.type;
1303
+ }
1304
+ if (/^[A-Z]+/.test( type ))// eslint-disable-next-line no-nested-ternary
1305
+ return (type === 'Id') ? 'Identifier' : (type === 'EOF') ? '<EOF>' : type;
1306
+ return (/^[a-z]+/.test( type )) ? type.toUpperCase() : `'${ type }'`;
1307
+ }
1290
1308
  }
1291
1309
 
1292
1310
  function addOneForDefinition( count, ext ) {
@@ -1309,15 +1327,6 @@ function relevantDigits( val ) {
1309
1327
  }
1310
1328
 
1311
1329
 
1312
- // For compatibility with ANTLR-based parser:
1313
- function antlrName( type ) {
1314
- if (typeof type !== 'string')
1315
- type = (!type.parsedAs || type.parsedAs === 'keyword') && type.keyword || type.type;
1316
- if (/^[A-Z]+/.test( type ))// eslint-disable-next-line no-nested-ternary
1317
- return (type === 'Id') ? 'Identifier' : (type === 'EOF') ? '<EOF>' : type;
1318
- return (/^[a-z]+/.test( type )) ? type.toUpperCase() : `'${ type }'`;
1319
- }
1320
-
1321
1330
  // Used for sorting in messages (TODO: make it part of messages.js?)
1322
1331
  const token1sort = {
1323
1332
  // 0: Identifier, Number, ...
@@ -208,7 +208,7 @@ aspectDef[ art, outer ]
208
208
  @finally{ this.attachLocation( $art ); }
209
209
  :
210
210
  ( ASPECT
211
- | <hide> ABSTRACT { this.warning( 'syntax-deprecated-abstract', this.lb().location ); }
211
+ | <hide> ABSTRACT { this.warning( 'syntax-deprecated-abstract', this.combineLocation( this.lb(), this.la() ) ); }
212
212
  ENTITY
213
213
  )
214
214
  name=namePath[ 'Type' ] // TODO: Type?
@@ -418,7 +418,7 @@ elementDef[ outer, art = undefined ]
418
418
  ( KEY { $art.key = this.valueWithLocation( true ); } )?
419
419
  ( <hide> MASKED { $art.masked = this.valueWithLocation( true ); }
420
420
  { this.message( 'syntax-unsupported-masked', this.lb(), { keyword: 'masked' } ); } )?
421
- ( ELEMENT { $art.$syntax = 'element'; } )?
421
+ ( <hide> ELEMENT { $art.$syntax = 'element'; } )?
422
422
  Id['Element'] <prepare=elementRestriction, arg=elem>
423
423
  { this.addDef( $art, $outer, 'elements', 'element', this.identAst() ); }
424
424
  { this.docComment( $art ); } annoAssignMid[ $art ]*
@@ -515,7 +515,7 @@ mixinElementDef[ outer ] locals[ art = new XsnArtifact() ]
515
515
  // Annotate and Extend: main definitions ----------------------------------------
516
516
 
517
517
  annotateArtifact[ art, outer ]
518
- @finally{ this.checkWith( $keyword ); this.attachLocation( $art ); }
518
+ @finally{ this.attachLocation( $art ); }
519
519
  :
520
520
  name=namePath[ 'Ext' ]
521
521
  ( // direct element annotation:
@@ -538,10 +538,11 @@ annotateArtifact[ art, outer ]
538
538
  annotateActionsBlock[ $art ]?
539
539
  )
540
540
  )
541
+ { this.checkWith( $keyword ); }
541
542
  ;
542
543
 
543
544
  extendArtifact[ art, outer ]
544
- @finally{ this.checkWith( $keyword ); this.attachLocation( $art ); }
545
+ @finally{ this.attachLocation( $art ); }
545
546
  :
546
547
  name=namePath[ 'Ext' ]
547
548
  ( // direct element annotation:
@@ -586,6 +587,7 @@ extendArtifact[ art, outer ]
586
587
  DEFINITIONS artifactsBlock[ $art, this.lb() ]
587
588
  )?
588
589
  )
590
+ { this.checkWith( $keyword ); }
589
591
  ;
590
592
 
591
593
  extendService[ art, outer ]
@@ -1115,6 +1117,8 @@ selectQuery returns[ default query = {} ]
1115
1117
  excludingClause[ $query ]?
1116
1118
  |
1117
1119
  ( ALL/DISTINCT { $query.quantifier = this.valueWithLocation(); } )?
1120
+ // TODO TOOL: move <prepare> to all branches if "simple", or with special <…,attach>
1121
+ {;} <prepare=inSelectItem, arg=sqlStyle>
1118
1122
  ( '*' { $query.columns = [ this.valueWithLocation() ]; }
1119
1123
  | selectItemDef[ ($query.columns = []) ]
1120
1124
  )
@@ -1692,7 +1696,7 @@ options{ minTokensMatched = 1 }
1692
1696
  )*
1693
1697
  )
1694
1698
  )
1695
- ')'
1699
+ ')' { this.finalizeDictOrArray( $pathStep.args ); }
1696
1700
  )?
1697
1701
  // TODO: not with function!
1698
1702
  cardinalityAndFilter[ ...$ ]?
@@ -1972,11 +1976,19 @@ annoValue returns[ default value = {} ]
1972
1976
  ( sub=annoValue { $value.val.push( $sub ) }
1973
1977
  |
1974
1978
  <cond=arrayAnno, arg=ellipsis> ellipsis='...'
1975
- ( UP TO upTo=annoValue | { $upTo = undefined; } )
1976
- { $value.val.push( { literal: 'token', val: '...', location: $ellipsis.location, upTo: $upTo } ); }
1979
+ ( UP TO upTo=annoValue
1980
+ { $value.val.push( { literal: 'token', val: '...', location: $ellipsis.location, upTo: $upTo } ); }
1981
+ | { $value.val.push( { literal: 'token', val: '...', location: $ellipsis.location } ); }
1982
+ )
1983
+ // TODO TOOL: if at last good state the command is ['g'],resume after the
1984
+ // gotos, do not execute its actions - ?
1985
+ // ( UP TO upTo=annoValue | { $upTo = undefined; } )
1986
+ // { $value.val.push( { literal: 'token', val: '...', location: $ellipsis.location, upTo: $upTo } ); }
1977
1987
  )
1978
1988
  ( ',' | <exitLoop> )
1979
1989
  )*
1990
+ // TODO TOOL: allow ( <cond=arrayAnno, arg=bracket> ']' )
1991
+ { this.ec( 'arrayAnno', 'bracket' ); }<always>
1980
1992
  ']'
1981
1993
  |
1982
1994
  '(' $value=condition ')' { $value.$tokenTexts = this.ruleTokensText(); }
@@ -83,6 +83,18 @@ function setHidden( obj, prop, val ) {
83
83
  } );
84
84
  }
85
85
 
86
+ /**
87
+ * Check if the given object has the property as non-enumerable
88
+ *
89
+ * @param {Object} object
90
+ * @param {string} propertyName
91
+ * @returns {boolean}
92
+ */
93
+ function hasNonEnumerable( object, propertyName ) {
94
+ return Object.prototype.hasOwnProperty.call( object, propertyName ) &&
95
+ !Object.prototype.propertyIsEnumerable.call( object, propertyName );
96
+ }
97
+
86
98
  module.exports = {
87
99
  copyPropIfExist,
88
100
  createDict,
@@ -90,4 +102,5 @@ module.exports = {
90
102
  forEachValue,
91
103
  forEachKey,
92
104
  setHidden,
105
+ hasNonEnumerable,
93
106
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "5.5.0",
3
+ "version": "5.5.2",
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)",