@sap/cds-compiler 2.11.4 → 2.12.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 (80) hide show
  1. package/CHANGELOG.md +58 -1
  2. package/bin/cds_update_identifiers.js +7 -7
  3. package/bin/cdsc.js +9 -10
  4. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  5. package/doc/CHANGELOG_BETA.md +12 -0
  6. package/lib/api/main.js +2 -0
  7. package/lib/api/options.js +2 -2
  8. package/lib/base/message-registry.js +31 -2
  9. package/lib/base/model.js +1 -0
  10. package/lib/base/optionProcessorHelper.js +97 -69
  11. package/lib/checks/.eslintrc.json +2 -0
  12. package/lib/checks/actionsFunctions.js +2 -1
  13. package/lib/checks/foreignKeys.js +4 -4
  14. package/lib/checks/managedInType.js +4 -4
  15. package/lib/checks/queryNoDbArtifacts.js +1 -3
  16. package/lib/checks/sql-snippets.js +93 -0
  17. package/lib/checks/validator.js +8 -0
  18. package/lib/compiler/assert-consistency.js +5 -3
  19. package/lib/compiler/base.js +0 -1
  20. package/lib/compiler/checks.js +32 -9
  21. package/lib/compiler/definer.js +25 -4
  22. package/lib/compiler/index.js +1 -1
  23. package/lib/compiler/propagator.js +3 -2
  24. package/lib/compiler/resolver.js +97 -6
  25. package/lib/compiler/shared.js +12 -1
  26. package/lib/compiler/utils.js +7 -0
  27. package/lib/edm/annotations/genericTranslation.js +34 -17
  28. package/lib/edm/annotations/preprocessAnnotations.js +1 -1
  29. package/lib/edm/csn2edm.js +1 -1
  30. package/lib/edm/edm.js +8 -8
  31. package/lib/edm/edmPreprocessor.js +30 -23
  32. package/lib/edm/edmUtils.js +11 -12
  33. package/lib/gen/Dictionary.json +82 -40
  34. package/lib/gen/language.checksum +1 -1
  35. package/lib/gen/language.interp +3 -1
  36. package/lib/gen/language.tokens +15 -14
  37. package/lib/gen/languageLexer.interp +9 -1
  38. package/lib/gen/languageLexer.js +830 -779
  39. package/lib/gen/languageLexer.tokens +7 -6
  40. package/lib/gen/languageParser.js +2401 -2282
  41. package/lib/json/from-csn.js +47 -16
  42. package/lib/json/to-csn.js +17 -5
  43. package/lib/language/antlrParser.js +3 -3
  44. package/lib/language/docCommentParser.js +1 -1
  45. package/lib/language/genericAntlrParser.js +68 -51
  46. package/lib/language/language.g4 +128 -74
  47. package/lib/language/multiLineStringParser.js +536 -0
  48. package/lib/main.d.ts +5 -3
  49. package/lib/main.js +3 -2
  50. package/lib/model/csnRefs.js +116 -68
  51. package/lib/model/csnUtils.js +40 -48
  52. package/lib/model/enrichCsn.js +30 -14
  53. package/lib/optionProcessor.js +3 -3
  54. package/lib/render/DuplicateChecker.js +1 -1
  55. package/lib/render/manageConstraints.js +1 -1
  56. package/lib/render/toCdl.js +193 -79
  57. package/lib/render/toHdbcds.js +179 -95
  58. package/lib/render/toRename.js +7 -10
  59. package/lib/render/toSql.js +57 -40
  60. package/lib/render/utils/common.js +24 -5
  61. package/lib/render/utils/sql.js +6 -4
  62. package/lib/transform/braceExpression.js +4 -2
  63. package/lib/transform/db/associations.js +389 -0
  64. package/lib/transform/db/cdsPersistence.js +150 -0
  65. package/lib/transform/db/constraints.js +6 -4
  66. package/lib/transform/db/draft.js +3 -2
  67. package/lib/transform/db/expansion.js +4 -5
  68. package/lib/transform/db/flattening.js +5 -6
  69. package/lib/transform/db/temporal.js +236 -0
  70. package/lib/transform/db/transformExists.js +36 -23
  71. package/lib/transform/forHanaNew.js +35 -626
  72. package/lib/transform/forOdataNew.js +5 -4
  73. package/lib/transform/localized.js +3 -14
  74. package/lib/transform/odata/generateForeignKeyElements.js +2 -2
  75. package/lib/transform/transformUtilsNew.js +13 -13
  76. package/lib/transform/translateAssocsToJoins.js +8 -8
  77. package/lib/transform/universalCsnEnricher.js +217 -47
  78. package/lib/utils/file.js +2 -1
  79. package/lib/utils/timetrace.js +8 -2
  80. package/package.json +1 -1
@@ -705,7 +705,7 @@ let csnFilename = '';
705
705
  let virtualLine = 1;
706
706
  /** @type {CSN.Location[]} */
707
707
  let dollarLocations = [];
708
- let arrayLvlCnt = 0;
708
+ let arrayLevelCount = 0;
709
709
 
710
710
  /**
711
711
  * @param {Object.<string, SchemaSpec>} specs
@@ -1164,6 +1164,12 @@ function symbol( id, spec, xsn ) { // for CSN property '#'
1164
1164
  xsn.sym = { id, location: location() };
1165
1165
  }
1166
1166
 
1167
+ // returns: false = no "...", true = "..." without UP TO, 'upTo' = "..." with UP TO
1168
+ function isEllipsis( val ) {
1169
+ return val && typeof val === 'object' && '...' in val && Object.keys(val).length === 1 &&
1170
+ (val['...'] === true || 'upTo');
1171
+ }
1172
+
1167
1173
  function annoValue( val, spec ) {
1168
1174
  if (val == null) // TODO: reject undefined
1169
1175
  return { val, literal: 'null', location: location() };
@@ -1171,22 +1177,39 @@ function annoValue( val, spec ) {
1171
1177
  if (lit !== 'object')
1172
1178
  return { val, literal: lit, location: location() };
1173
1179
  if (Array.isArray( val )) {
1174
- const ec = val.reduce((c, v) => ((v && v['...'] && Object.keys(v).length === 1) ? ++c : c), 0);
1175
- if (arrayLvlCnt === 0 && ec > 1) {
1176
- error( 'syntax-csn-duplicate-ellipsis', location(true), { code: '...' },
1177
- 'Expected no more than one $(CODE)' );
1180
+ let seenEllipsis = false;
1181
+ if (arrayLevelCount > 0) { // TODO: also inside structure (possible in CSN!)
1182
+ if (val.some( isEllipsis )) {
1183
+ error( 'syntax-csn-unexpected-ellipsis', location(true), { code: '...' },
1184
+ 'Unexpected $(CODE) in nested array' );
1185
+ }
1178
1186
  }
1179
- if (arrayLvlCnt > 0 && ec > 0) {
1180
- error( 'syntax-csn-unexpected-ellipsis', location(true), { code: '...' },
1181
- 'Unexpected $(CODE) in nested array' );
1187
+ else {
1188
+ for (const item of val) {
1189
+ if (seenEllipsis !== true) {
1190
+ seenEllipsis = isEllipsis( item ) || seenEllipsis;
1191
+ }
1192
+ else if (isEllipsis( item )) { // with or without UP TO
1193
+ // error position at the beginning of the array, but that is fine
1194
+ error( 'syntax-csn-duplicate-ellipsis', location(true), { code: '...' },
1195
+ 'Expected no more than one $(CODE)' );
1196
+ break;
1197
+ }
1198
+ }
1182
1199
  }
1183
- arrayLvlCnt++;
1200
+ arrayLevelCount++;
1184
1201
  const retval = {
1185
1202
  location: location(),
1186
1203
  val: arrayOf( annoValue )( val, spec ),
1187
1204
  literal: 'array',
1188
1205
  };
1189
- arrayLvlCnt--;
1206
+ arrayLevelCount--;
1207
+ if (seenEllipsis === 'upTo') {
1208
+ error( 'syntax-csn-expecting-ellipsis', location(true), // at closing bracket
1209
+ { code: '... up to', newcode: '...' },
1210
+ // TODO: should we be more CSN specific in the message?
1211
+ 'Expecting an array item $(NEWCODE) after an item with $(CODE)' );
1212
+ }
1190
1213
  return retval;
1191
1214
  }
1192
1215
  if (typeof val['#'] === 'string') {
@@ -1205,12 +1228,19 @@ function annoValue( val, spec ) {
1205
1228
  return refSplit( val['='], '=' );
1206
1229
  }
1207
1230
  }
1208
- else if (val['...'] && Object.keys(val).length === 1) {
1209
- return {
1231
+ else if (val['...'] && Object.keys( val ).length === 1) {
1232
+ // TODO: only if not nested - see error above
1233
+ ++virtualLine;
1234
+ const ell = val['...'];
1235
+ const r = {
1210
1236
  val: '...',
1211
1237
  literal: 'token',
1212
1238
  location: location(),
1213
1239
  };
1240
+ if (ell !== true)
1241
+ r.upTo = annoValue( ell, schema['@'] );
1242
+ ++virtualLine;
1243
+ return r;
1214
1244
  }
1215
1245
  const struct = Object.create(null);
1216
1246
  ++virtualLine;
@@ -1354,12 +1384,11 @@ function exprArgs( cond, spec, xsn, csn ) {
1354
1384
 
1355
1385
  function condition( cond, spec ) {
1356
1386
  const loc = location();
1357
- const x = {
1387
+ return {
1358
1388
  op: { val: 'xpr', location: loc },
1359
1389
  args: exprArgs( cond, spec ),
1360
1390
  location: loc,
1361
1391
  };
1362
- return x;
1363
1392
  }
1364
1393
 
1365
1394
  function vZeroValue( obj, spec, xsn ) {
@@ -1666,8 +1695,8 @@ function pushLocation( obj ) {
1666
1695
  error( 'syntax-csn-expected-object', location(true), { prop: '$location' } );
1667
1696
  }
1668
1697
  // hidden feature: string $location
1669
- const m = /:(\d+)(?::(\d+)(?:-[0-9-]+)?)?$/.exec( loc ); // extra - at end for .refloc
1670
- if (!m) {
1698
+ const m = /:(\d+)(?::(\d+))?$/.exec( loc ); // extra '^'s at end deliberately left out
1699
+ if (!m) { // without location or with '^'s: do not use
1671
1700
  dollarLocations.push( null );
1672
1701
  }
1673
1702
  else {
@@ -1710,8 +1739,10 @@ function toXsn( csn, filename, options, messageFunctions ) {
1710
1739
  csnFilename = filename;
1711
1740
  virtualLine = 1;
1712
1741
  dollarLocations = [];
1742
+ arrayLevelCount = 0;
1713
1743
  inExtensions = null;
1714
1744
  vocabInDefinitions = null;
1745
+
1715
1746
  const xsn = { $frontend: 'json' };
1716
1747
 
1717
1748
  // eslint-disable-next-line object-curly-newline
@@ -100,7 +100,7 @@ const transformers = {
100
100
  offset: expression,
101
101
  on: onCondition,
102
102
  // definitions, extensions, members ----------------------------------------
103
- returns: definition, // storing the return type of actions
103
+ returns, // storing the return type of actions
104
104
  notNull: value,
105
105
  default: expression,
106
106
  // targetElement: ignore, // special display of foreign key, renameTo: select
@@ -556,7 +556,7 @@ function sources( srcDict, csn ) {
556
556
  }
557
557
  }
558
558
 
559
- function attachAnnotations( annotate, prop, dict, inferred, returns = false ) {
559
+ function attachAnnotations( annotate, prop, dict, inferred, insideReturns = false ) {
560
560
  const annoDict = Object.create( dictionaryPrototype );
561
561
  for (const name in dict) {
562
562
  const elem = dict[name];
@@ -574,7 +574,7 @@ function attachAnnotations( annotate, prop, dict, inferred, returns = false ) {
574
574
  annoDict[name] = sub;
575
575
  }
576
576
  if (Object.keys( annoDict ).length) {
577
- if (returns)
577
+ if (insideReturns)
578
578
  annotate.returns = { elements: annoDict };
579
579
  else
580
580
  annotate[prop] = annoDict;
@@ -674,7 +674,8 @@ function enumDict( dict, csn, node ) {
674
674
  // no 'elements' with SELECT or inferred elements with gensrc;
675
675
  // hidden or visible 'elements' will be set in query()
676
676
  return undefined;
677
- if (universalCsn && node.type && !node.type.$inferred && node.$expand === 'annotate')
677
+ if (universalCsn && node.type && !node.type.$inferred && node.$expand === 'annotate' &&
678
+ node.type._artifact && !node.type._artifact.builtin)
678
679
  // derived type of enum type with individual annotations: also set $origin
679
680
  csn.$origin = originRef( node.type._artifact );
680
681
  return insertOrderDict( dict );
@@ -834,6 +835,14 @@ function foreignKeys( dict, csn, node ) {
834
835
  csn.keys = keys;
835
836
  }
836
837
 
838
+ function returns( art, csn, _node, prop ) {
839
+ // TODO: currently, the `returns` structure might just have been created by the propagator
840
+ // if that is the case, there should be no reason to store it in universal CSN
841
+ if (universalCsn && art.$inferred === 'proxy')
842
+ return undefined;
843
+ return definition( art, csn, _node, prop );
844
+ }
845
+
837
846
  function definition( art, _csn, _node, prop ) {
838
847
  if (!art || typeof art !== 'object')
839
848
  return undefined; // TODO: complain with strict
@@ -1163,7 +1172,7 @@ function value( node ) {
1163
1172
  if (node.literal === 'array')
1164
1173
  return node.val.map( value );
1165
1174
  if (node.literal === 'token' && node.val === '...')
1166
- return extra( { '...': true } );
1175
+ return extra( { '...': !node.upTo || value( node.upTo ) } );
1167
1176
  if (node.literal !== 'struct')
1168
1177
  // no val (undefined) as true only for annotation values (and struct elem values)
1169
1178
  return node.name && !('val' in node) || node.val;
@@ -1516,6 +1525,9 @@ function compactExpr( e ) { // TODO: options
1516
1525
  return e && expression( e, true );
1517
1526
  }
1518
1527
 
1528
+ /**
1529
+ * @param {CSN.Options} options
1530
+ */
1519
1531
  function initModuleVars( options = { csnFlavor: 'gensrc' } ) {
1520
1532
  gensrcFlavor = options.parseCdl || options.csnFlavor === 'gensrc' ||
1521
1533
  options.toCsn && options.toCsn.flavor === 'gensrc';
@@ -112,7 +112,7 @@ const rules = {
112
112
  expr: { func: 'conditionEOF', returns: 'cond' }, // yes, condition
113
113
  };
114
114
 
115
- function parse( source, filename = '<undefined>.cds', options = {}, messageFunctions, rule = 'cdl' ) {
115
+ function parse( source, filename = '<undefined>.cds', options = {}, messageFunctions = null, rule = 'cdl' ) {
116
116
  const lexer = new Lexer( new antlr4.InputStream(source) );
117
117
  const tokenStream = new RewriteTypeTokenStream(lexer);
118
118
  /** @type {object} */
@@ -166,8 +166,8 @@ function parse( source, filename = '<undefined>.cds', options = {}, messageFunct
166
166
  // Do not warn if docComments are explicitly disabled.
167
167
  if (options.docComment !== false) {
168
168
  for (const token of tokenStream.tokens) {
169
- if (token.channel === antlr4.Token.HIDDEN_CHANNEL && token.type === parser.constructor.DocComment && !token.isUsed) {
170
- messageFunctions.info('syntax-ignoring-doc-comment', parser.multiLineTokenLocation(token), {},
169
+ if (token.type === parser.constructor.DocComment && !token.isUsed) {
170
+ messageFunctions.info('syntax-ignoring-doc-comment', parser.tokenLocation(token), {},
171
171
  "Ignoring doc-comment as it does not belong to any artifact");
172
172
  }
173
173
  }
@@ -69,7 +69,7 @@ function parseDocComment(comment) {
69
69
  * @param {string} content
70
70
  */
71
71
  function isWhiteSpaceOnly(content) {
72
- return content.trim().length === 0;
72
+ return /^\s*$/.test(content);
73
73
  }
74
74
 
75
75
  /**
@@ -11,14 +11,17 @@ const { ATNState } = require('antlr4/atn/ATNState');
11
11
  const { dictAdd, dictAddArray } = require('../base/dictionaries');
12
12
  const locUtils = require('../base/location');
13
13
  const { parseDocComment } = require('./docCommentParser');
14
+ const { parseMultiLineStringLiteral } = require('./multiLineStringParser');
14
15
  const { functionsWithoutParens, specialFunctions } = require('../compiler/builtins');
15
16
 
16
17
 
17
18
  // Push message `msg` with location `loc` to array of errors:
18
19
  function _message( parser, severity, id, loc, ...args ) {
19
20
  const msg = parser.$messageFunctions[severity]; // set in antlrParser.js
20
- return msg( id,
21
- (loc instanceof antlr4.CommonToken) ? parser.tokenLocation(loc) : loc, ...args );
21
+ if (loc instanceof antlr4.CommonToken) {
22
+ loc = parser.tokenLocation(loc);
23
+ }
24
+ return msg( id, loc, ...args );
22
25
  }
23
26
 
24
27
  // Class which is to be used as grammar option with
@@ -49,7 +52,7 @@ GenericAntlrParser.prototype = Object.assign(
49
52
  attachLocation,
50
53
  startLocation,
51
54
  tokenLocation,
52
- multiLineTokenLocation,
55
+ valueWithTokenLocation,
53
56
  previousTokenAtLocation,
54
57
  combinedLocation,
55
58
  surroundByParens,
@@ -84,6 +87,7 @@ GenericAntlrParser.prototype = Object.assign(
84
87
  isStraightBefore,
85
88
  meltKeywordToIdentifier,
86
89
  prepareGenericKeywords,
90
+ parseMultiLineStringLiteral,
87
91
  constructor: GenericAntlrParser, // keep this last
88
92
  }
89
93
  );
@@ -290,62 +294,66 @@ function startLocation( token = this._ctx.start ) {
290
294
  *
291
295
  * @param {object} token
292
296
  * @param {object} endToken
293
- * @param {any} val
297
+ * @return {CSN.Location}
294
298
  */
295
- function tokenLocation( token, endToken, val ) {
299
+ function tokenLocation( token, endToken = null ) {
296
300
  if (!token)
297
301
  return undefined;
298
302
  if (!endToken) // including null
299
303
  endToken = token;
304
+
300
305
  /** @type {CSN.Location} */
301
- const r = {
306
+ const loc = {
302
307
  file: this.filename,
303
308
  line: token.line,
304
309
  col: token.column + 1,
305
- // we only have single-line tokens, except for docComments
310
+ // Default for single line tokens
306
311
  endLine: endToken.line,
307
312
  endCol: endToken.stop - endToken.start + endToken.column + 2, // after the last char (special for EOF?)
308
- };
309
- if (val !== undefined)
310
- return { location: r, val };
311
- return r;
313
+ }
314
+
315
+ // This check is done for performance reason. No need to access a token's
316
+ // data if we know that it spans only one single line.
317
+ const isMultiLineToken = (
318
+ endToken.type === this.constructor.DocComment ||
319
+ endToken.type === this.constructor.String ||
320
+ endToken.type === this.constructor.UnterminatedLiteral
321
+ );
322
+ if (isMultiLineToken) {
323
+ // Count the number of newlines in the token.
324
+ const source = endToken.source[1].data;
325
+ let newLineCount = 0;
326
+ let lastNewlineIndex = endToken.start;
327
+ for (let i = endToken.start; i < endToken.stop; i++) {
328
+ // Note: We do NOT check for CR, LS, and PS (/[\r\u2028\u2029]/)
329
+ // because ANTLR only uses LF for line break detection.
330
+ if (source[i] === 10) { // code point of '\n'
331
+ newLineCount++;
332
+ lastNewlineIndex = i;
333
+ }
334
+ }
335
+ if (newLineCount > 0) {
336
+ loc.endLine = endToken.line + newLineCount;
337
+ loc.endCol = endToken.stop - lastNewlineIndex + 1;
338
+ }
339
+ }
340
+
341
+ return loc;
312
342
  }
313
343
 
314
344
  /**
315
- * Return location of `token`. In contrast to `tokenLocation()`, this function
316
- * can handle multiline tokens.
345
+ * Return `val` with the location of `token`. If `endToken` is provided, use its end
346
+ * location as end location in the result.
347
+ *
348
+ * @param {object} startToken
349
+ * @param {object} endToken
350
+ * @param {any} val
317
351
  */
318
- function multiLineTokenLocation(token, val) {
319
- if (!token)
352
+ function valueWithTokenLocation( val, startToken, endToken = null ) {
353
+ if (!startToken)
320
354
  return undefined;
321
-
322
- // Count the number of newlines in the token.
323
- const source = token.source[1].data;
324
- let newLineCount = 0;
325
- let lastNewlineIndex = token.start;
326
- for (let i = token.start; i < token.stop; i++) {
327
- // Note: We do NOT check for CR, LS, and PS (/[\r\u2028\u2029]/)
328
- // because ANTLR only uses LF for line break detection.
329
- if (source[i] === 10) { // ASCII code for '\n'
330
- newLineCount++;
331
- lastNewlineIndex = i;
332
- }
333
- }
334
- if (newLineCount === 0)
335
- // endCol calculation below requires at least one newLine.
336
- return this.tokenLocation(token, token, val);
337
-
338
- /** @type {CSN.Location} */
339
- const r = {
340
- file: this.filename,
341
- line: token.line,
342
- col: token.column + 1,
343
- endLine: token.line + newLineCount,
344
- endCol: token.stop - lastNewlineIndex + 1, // after the last char (special for EOF?)
345
- };
346
- if (val !== undefined)
347
- return { location: r, val };
348
- return r;
355
+ const loc = this.tokenLocation( startToken, endToken );
356
+ return { location: loc, val };
349
357
  }
350
358
 
351
359
  function previousTokenAtLocation( location ) {
@@ -406,7 +414,7 @@ function docComment( node ) {
406
414
  this.warning( 'syntax-duplicate-doc-comment', token, {},
407
415
  'Repeated doc comment - previous doc is replaced' );
408
416
  }
409
- node.doc = this.multiLineTokenLocation( token, parseDocComment( token.text ) );
417
+ node.doc = this.valueWithTokenLocation( parseDocComment( token.text ), token );
410
418
  }
411
419
 
412
420
  // Classify token (identifier category) for implicit names,
@@ -507,7 +515,7 @@ function valuePathAst( ref ) {
507
515
  // If a '-' is directly before an unsigned number, consider it part of the number;
508
516
  // otherwise (including for '+'), represent it as extra unary prefix operator.
509
517
  function signedExpression( signToken, expr ) {
510
- const sign = this.tokenLocation( signToken, undefined, signToken.text );
518
+ const sign = this.valueWithTokenLocation( signToken.text, signToken );
511
519
  const nval =
512
520
  (signToken.text === '-' &&
513
521
  expr && // expr may be null if `-` rule can't be parsed
@@ -557,8 +565,16 @@ function numberLiteral( token, sign, text = token.text ) {
557
565
  function quotedLiteral( token, literal ) {
558
566
  /** @type {CSN.Location} */
559
567
  const location = this.tokenLocation( token );
560
- const pos = token.text.search( '\'' ) + 1; // pos of char after quote
561
- const val = token.text.slice( pos, -1 ).replace( /''/g, '\'' );
568
+ let pos;
569
+ let val;
570
+
571
+ if (token.text.startsWith('`')) {
572
+ val = this.parseMultiLineStringLiteral(token);
573
+ literal = 'string';
574
+ } else {
575
+ pos = token.text.search( '\'' ) + 1; // pos of char after quote
576
+ val = token.text.slice( pos, -1 ).replace( /''/g, '\'' );
577
+ }
562
578
 
563
579
  if (!literal)
564
580
  literal = token.text.slice( 0, pos - 1 ).toLowerCase();
@@ -588,6 +604,7 @@ function quotedLiteral( token, literal ) {
588
604
  };
589
605
 
590
606
  function atChar(i) {
607
+ // Is only used with single-line strings.
591
608
  return location.col + pos + i;
592
609
  }
593
610
  }
@@ -730,7 +747,7 @@ function assignProps( target, annos = [], props = null, location = null) {
730
747
  for (const key in props) {
731
748
  let val = props[key];
732
749
  if (val instanceof antlr4.CommonToken)
733
- val = this.tokenLocation( val, undefined, true);
750
+ val = this.valueWithTokenLocation( true, val);
734
751
  // only copy properties which are not undefined, null, {} or []
735
752
  if (val != null &&
736
753
  (typeof val !== 'object' ||
@@ -744,15 +761,15 @@ function assignProps( target, annos = [], props = null, location = null) {
744
761
 
745
762
  // Create AST node for prefix operator `op` and arguments `args`
746
763
  function createPrefixOp( token, args ) {
747
- const op = this.tokenLocation( token, undefined, token.text.toLowerCase() );
764
+ const op = this.valueWithTokenLocation( token.text.toLowerCase(), token );
748
765
  return { op, args, location: this.combinedLocation( op, args[args.length - 1] ) };
749
766
  }
750
767
 
751
768
  // Create AST node for binary operator `op` and arguments `args`
752
769
  function leftAssocBinaryOp( left, opToken, eToken, right, extraProp = 'quantifier' ) {
753
- const op = this.tokenLocation( opToken, undefined, opToken.text.toLowerCase() );
770
+ const op = this.valueWithTokenLocation( opToken.text.toLowerCase() , opToken);
754
771
  const extra = eToken
755
- ? this.tokenLocation( eToken, undefined, eToken.text.toLowerCase() )
772
+ ? this.valueWithTokenLocation( eToken.text.toLowerCase(), eToken )
756
773
  : undefined;
757
774
  if (!left.$parens &&
758
775
  (left.op && left.op.val) === (op && op.val) &&