@sap/cds-compiler 5.2.0 → 5.3.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 (54) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/bin/cdsc.js +5 -0
  3. package/bin/cdshi.js +8 -8
  4. package/doc/CHANGELOG_BETA.md +9 -4
  5. package/lib/api/validate.js +5 -0
  6. package/lib/base/message-registry.js +25 -1
  7. package/lib/base/messages.js +1 -1
  8. package/lib/base/model.js +0 -1
  9. package/lib/compiler/assert-consistency.js +2 -2
  10. package/lib/compiler/builtins.js +1 -1
  11. package/lib/compiler/checks.js +25 -6
  12. package/lib/compiler/define.js +24 -28
  13. package/lib/compiler/extend.js +11 -13
  14. package/lib/compiler/generate.js +3 -3
  15. package/lib/compiler/populate.js +13 -7
  16. package/lib/compiler/propagator.js +2 -2
  17. package/lib/compiler/resolve.js +58 -60
  18. package/lib/compiler/shared.js +5 -5
  19. package/lib/compiler/tweak-assocs.js +247 -34
  20. package/lib/compiler/utils.js +40 -32
  21. package/lib/compiler/xpr-rewrite.js +44 -58
  22. package/lib/edm/annotations/genericTranslation.js +4 -4
  23. package/lib/edm/csn2edm.js +2 -2
  24. package/lib/edm/edm.js +46 -21
  25. package/lib/edm/edmInboundChecks.js +0 -1
  26. package/lib/edm/edmPreprocessor.js +40 -27
  27. package/lib/edm/edmUtils.js +1 -1
  28. package/lib/gen/BaseParser.js +180 -122
  29. package/lib/gen/CdlParser.js +2226 -2170
  30. package/lib/gen/language.checksum +1 -1
  31. package/lib/gen/language.interp +1 -1
  32. package/lib/gen/languageParser.js +3820 -3777
  33. package/lib/inspect/inspectPropagation.js +1 -1
  34. package/lib/json/from-csn.js +5 -3
  35. package/lib/json/to-csn.js +7 -10
  36. package/lib/language/antlrParser.js +38 -4
  37. package/lib/language/errorStrategy.js +1 -1
  38. package/lib/language/genericAntlrParser.js +4 -4
  39. package/lib/language/multiLineStringParser.js +1 -1
  40. package/lib/main.d.ts +23 -0
  41. package/lib/model/cloneCsn.js +22 -13
  42. package/lib/optionProcessor.js +7 -7
  43. package/lib/parsers/AstBuildingParser.js +155 -37
  44. package/lib/parsers/CdlGrammar.g4 +154 -81
  45. package/lib/parsers/Lexer.js +20 -10
  46. package/lib/render/toCdl.js +23 -18
  47. package/lib/transform/addTenantFields.js +4 -4
  48. package/lib/transform/db/rewriteCalculatedElements.js +11 -5
  49. package/lib/transform/db/transformExists.js +43 -18
  50. package/lib/transform/effective/main.js +1 -1
  51. package/lib/transform/forRelationalDB.js +8 -7
  52. package/lib/utils/moduleResolve.js +1 -1
  53. package/package.json +1 -1
  54. package/share/messages/redirected-to-complex.md +6 -3
@@ -36,7 +36,7 @@ function inspectPropagation( xsn, options, artifactName ) {
36
36
 
37
37
  if (!artifactXsn) {
38
38
  error(null, null, { name: artifactName },
39
- // eslint-disable-next-line max-len
39
+ // eslint-disable-next-line @stylistic/js/max-len
40
40
  'Artifact $(NAME) not found, only top-level artifacts and their elements are supported for now');
41
41
  return null;
42
42
  }
@@ -1134,7 +1134,7 @@ function elementsDict( def, spec, xsn ) {
1134
1134
  return elements;
1135
1135
  warning( 'syntax-expecting-returns', elements[$location],
1136
1136
  { prop: 'elements', parentprop: 'returns' },
1137
- // eslint-disable-next-line max-len
1137
+ // eslint-disable-next-line @stylistic/js/max-len
1138
1138
  'Expecting property $(PROP) to be put into an object for property $(PARENTPROP) when annotating action return structures' );
1139
1139
  xsn.returns = { kind: 'annotate', elements, location: elements[$location] };
1140
1140
  return undefined;
@@ -2076,8 +2076,10 @@ function toXsn( csn, filename, options, messageFunctions ) {
2076
2076
 
2077
2077
  const xsn = new XsnSource( 'json' ); // TODO: 'csn'? LSP does not use $frontend
2078
2078
 
2079
- // eslint-disable-next-line object-curly-newline
2080
- ({ message, error, warning, info } = messageFunctions);
2079
+
2080
+ ({
2081
+ message, error, warning, info,
2082
+ } = messageFunctions);
2081
2083
 
2082
2084
  if (csnVersionZero) {
2083
2085
  warning( 'syntax-deprecated-csn-version', location(true), {},
@@ -123,7 +123,7 @@ const transformers = {
123
123
  value: enumValueOrCalc, // do not list for select items as elements
124
124
  query,
125
125
  elements,
126
- actions, // TODO: just normal dictionary
126
+ actions: sortedDict, // TODO: just normal dictionary
127
127
  returns, // storing the return type of actions
128
128
  // special: top-level, cardinality -----------------------------------------
129
129
  sources,
@@ -609,6 +609,10 @@ function addLocation( loc, csn ) {
609
609
  // is often not the reason for an error or warning. So we gain little benefit for
610
610
  // two more properties. It is also an indication that the location is not exact.
611
611
  const val = { file: loc.file, line: loc.line, col: loc.col };
612
+ if (withLocations === 'withEndPosition' && loc.endLine) {
613
+ val.endLine = loc.endLine;
614
+ val.endCol = loc.endCol;
615
+ }
612
616
  Object.defineProperty( csn, '$location', {
613
617
  value: val, configurable: true, writable: true, enumerable: withLocations,
614
618
  } );
@@ -621,18 +625,11 @@ function insertOrderDict( dict ) {
621
625
  return dictionary( dict, keys );
622
626
  }
623
627
 
624
- function sortedDict( dict ) {
628
+ function sortedDict( dict, _csn, _node, prop ) {
625
629
  const keys = Object.keys( dict );
626
630
  if (strictMode)
627
631
  keys.sort();
628
- return dictionary( dict, keys );
629
- }
630
-
631
- function actions( dict, _csn, node ) {
632
- const keys = Object.keys( dict );
633
- if (strictMode && node.kind === 'annotate')
634
- keys.sort(); // TODO: always sort with --test-mode ?
635
- return dictionary( dict, keys, 'actions' );
632
+ return dictionary( dict, keys, prop );
636
633
  }
637
634
 
638
635
  function params( dict ) {
@@ -20,6 +20,7 @@ const Lexer = require('../gen/languageLexer').default;
20
20
  const CdlLexer = require( '../parsers/Lexer' );
21
21
  const CdlParser = require( '../gen/CdlParser' );
22
22
  const { createMessageFunctions } = require( '../base/messages' );
23
+ const { CompilerAssertion } = require( '../base/error' );
23
24
 
24
25
  // Error listener used for ANTLR4-generated parser
25
26
  class ErrorListener extends antlr4.error.ErrorListener {
@@ -224,10 +225,34 @@ function parseWithNewParser( source, filename, options, messageFunctions, rule )
224
225
  const lexer = new CdlLexer( filename, source );
225
226
  const parser = new CdlParser( lexer, options, messageFunctions ).init();
226
227
  parser.filename = filename; // LSP compatibility
227
- parser.tokenStream = parser; // LSP compatibility: object with property `tokens`
228
228
 
229
+ const { parseListener, attachTokens } = options;
230
+ if (parseListener || attachTokens) {
231
+ const combined = [];
232
+ const { tokens, comments, docComments } = parser;
233
+ const length = tokens.length + comments.length + docComments.length;
234
+ let tokenIdx = 0;
235
+ let commentIdx = 0;
236
+ let docCommentIdx = 0;
237
+ for (let index = 0; index < length; ++index) {
238
+ if (tokens[tokenIdx].location.tokenIndex === index) // EOF has largest tokenIndex
239
+ combined.push( tokens[tokenIdx++] );
240
+ else if (comments[commentIdx]?.location.tokenIndex === index)
241
+ combined.push( comments[commentIdx++] );
242
+ else
243
+ combined.push( docComments[docCommentIdx++] );
244
+ }
245
+ if (!combined.at( -1 ))
246
+ throw new CompilerAssertion( 'Invalid values for `tokenIndex`' );
247
+ for (const tok of combined)
248
+ tok.start = lexer.characterPos( tok.location.line, tok.location.col );
249
+
250
+ parser._input = { tokens: combined, lexer }; // lexer for characterPos() in cdshi.js
251
+ parser.getTokenStream = function getTokenStream() {
252
+ return this._input;
253
+ };
254
+ }
229
255
  // LSP feature: provide parse listener with ANTLR-like context:
230
- const { parseListener } = options;
231
256
  if (parseListener) {
232
257
  // TODO LSP: we could also call different listener methods: then LSP could
233
258
  // have dedicated methods for ANTLR-based and new parser
@@ -252,6 +277,14 @@ function parseWithNewParser( source, filename, options, messageFunctions, rule )
252
277
  parseListener.exitEveryRule( $ctx );
253
278
  CdlParser.prototype.exit_.apply( this, args );
254
279
  };
280
+ parser.c = function c( ...args ) { // consume
281
+ CdlParser.prototype.c.apply( this, args );
282
+ parseListener.visitTerminal( { symbol: this.lb() } );
283
+ };
284
+ parser.skipToken_ = function skipToken_( ...args ) { // skip token in error recovery
285
+ CdlParser.prototype.skipToken_.apply( this, args ); // = `++this.tokenIdx`
286
+ parseListener.visitErrorNode( { symbol: this.lb() } );
287
+ };
255
288
  }
256
289
  const result = {};
257
290
  const rulespec = rules[rule];
@@ -268,8 +301,9 @@ function parseWithNewParser( source, filename, options, messageFunctions, rule )
268
301
  }
269
302
  }
270
303
  const ast = result[rulespec?.returns] || (rule === 'cdl' ? new XsnSource( 'cdl' ) : {} );
271
- if (options.attachTokens === true || options.attachTokens === filename)
272
- ast.tokenStream = parser; // with property tokens
304
+ ast.options = options;
305
+ if (attachTokens === true || attachTokens === filename)
306
+ ast.tokenStream = parser._input;
273
307
  return ast;
274
308
  }
275
309
 
@@ -337,7 +337,7 @@ function reportIgnoredWith( recognizer, t ) {
337
337
  const expecting = this.getExpectedTokensForMessage( recognizer, t );
338
338
  const m = recognizer.warning( 'syntax-unexpected-semicolon', t,
339
339
  { offending: "';'", expecting, keyword: 'with' },
340
- // eslint-disable-next-line max-len
340
+ // eslint-disable-next-line @stylistic/js/max-len
341
341
  'Unexpected $(OFFENDING), expecting $(EXPECTING) - ignored previous $(KEYWORD)' );
342
342
  m.expectedTokens = expecting;
343
343
  }
@@ -280,7 +280,7 @@ function noAssignmentInSameLine() {
280
280
  if (t.text === '@' && t.line <= this._input.LT(-1).line) {
281
281
  // TODO: use 'syntax-missing-newline'
282
282
  this.warning( 'syntax-missing-semicolon', t, { code: ';' },
283
- // eslint-disable-next-line max-len
283
+ // eslint-disable-next-line @stylistic/js/max-len
284
284
  'Add a $(CODE) and/or newline before the annotation assignment to indicate that it belongs to the next statement' );
285
285
  }
286
286
  }
@@ -685,7 +685,7 @@ function warnIfColonFollows( anno ) {
685
685
  if (t.text === ':') {
686
686
  this.warning( 'syntax-missing-parens', anno.name.location,
687
687
  { code: '@‹anno›', op: ':', newcode: '@(‹anno›…)' },
688
- // eslint-disable-next-line max-len
688
+ // eslint-disable-next-line @stylistic/js/max-len
689
689
  'When $(CODE) is followed by $(OP), use $(NEWCODE) for annotation assignments at this position' );
690
690
  }
691
691
  }
@@ -796,7 +796,7 @@ function identAst( token, category, noTokenTypeCheck = false ) {
796
796
  }
797
797
  else {
798
798
  this.message( 'syntax-deprecated-ident', token, { delimited: id },
799
- // eslint-disable-next-line max-len
799
+ // eslint-disable-next-line @stylistic/js/max-len
800
800
  'Deprecated delimited identifier syntax, use $(DELIMITED) - strings are delimited by single quotes' );
801
801
  }
802
802
  const ast = { id, $delimited: true, location: this.tokenLocation( token ) };
@@ -1049,7 +1049,7 @@ function assignAnnotationValue( anno, value ) {
1049
1049
  {
1050
1050
  std: 'Annotation number $(RAWVALUE) is put as $(VALUE) into the CSN',
1051
1051
  rounded: 'Annotation number $(RAWVALUE) is rounded to $(VALUE)',
1052
- // eslint-disable-next-line max-len
1052
+ // eslint-disable-next-line @stylistic/js/max-len
1053
1053
  infinite: 'Annotation value $(RAWVALUE) is infinite as number and put as string into the CSN',
1054
1054
  } );
1055
1055
  }
@@ -77,7 +77,7 @@ class MultiLineStringParser {
77
77
  this.str = token.text; // Copy because .text is a getter
78
78
 
79
79
  if (this.str[0] !== '`' || this.str[this.str.length - 1] !== '`')
80
- // eslint-disable-next-line max-len
80
+ // eslint-disable-next-line @stylistic/js/max-len
81
81
  throw new CompilerAssertion('Invalid multi-line string sequence: Require string to be surrounded by back-ticks!');
82
82
 
83
83
  this.output = [];
package/lib/main.d.ts CHANGED
@@ -154,6 +154,29 @@ declare namespace compiler {
154
154
  * @since v2.12.1
155
155
  */
156
156
  $xsnObjects?: boolean
157
+ /**
158
+ * If `true`, the CSN will have an enumerable property `$locations`.
159
+ * with values for `line` and `col`, i.e. there is only a start position,
160
+ * but no end position.
161
+ *
162
+ * If `false`, the property will be non-enumerable, i.e. it won't be
163
+ * serialized when using `JSON.stringify()`.
164
+ *
165
+ * With value `"withEndPosition"`, the property will be enumerable and
166
+ * will contain values for the end-position. Other string values
167
+ * are not allowed. This value was introduced in v5.3.0.
168
+ *
169
+ * $location is not set on all artifacts, and it only indicates the position
170
+ * of the _name_ of the artifact.
171
+ */
172
+ withLocations?: boolean|string
173
+ /**
174
+ * Use the new non-ANTLR based parser for compilation.
175
+ * Experimental flag!
176
+ *
177
+ * @since v5.2.0
178
+ */
179
+ newParser?: boolean
157
180
  /**
158
181
  * Internal option for LSP only!
159
182
  * If set, each AST gets a `tokenStream` property containing all lexed tokens.
@@ -5,16 +5,23 @@ const { ModelError } = require('../base/error');
5
5
  const { setHidden } = require('../utils/objectUtils');
6
6
  const { isAnnotationExpression } = require('../base/builtins');
7
7
 
8
- const csnDictionaries = [
9
- 'args',
10
- 'params',
11
- 'enum',
12
- 'mixin',
13
- 'elements',
14
- 'actions',
15
- 'definitions',
16
- 'vocabularies',
17
- ];
8
+ const csnDictionaries = {
9
+ __proto__: null,
10
+ args: 1,
11
+ params: 1,
12
+ enum: 1,
13
+ mixin: 1,
14
+ elements: 1,
15
+ actions: 1,
16
+ definitions: 1,
17
+ vocabularies: 1,
18
+ };
19
+
20
+ const sortedCsnDictionaries = {
21
+ __proto__: null,
22
+ definitions: 1,
23
+ actions: 1,
24
+ };
18
25
 
19
26
  function shallowCopy( val, _options, _sort ) {
20
27
  return val;
@@ -74,9 +81,9 @@ function cloneCsn( csn, options, sort ) {
74
81
  else if (!val || typeof val !== 'object') {
75
82
  r[n] = val;
76
83
  }
77
- else if (csnDictionaries.includes(n) && !Array.isArray(val)) {
78
- const sortDict = n === 'definitions' &&
79
- (!options || options.testMode || options.testSortCsn);
84
+ else if (csnDictionaries[n] && !Array.isArray(val)) {
85
+ const sortDict = (!options || options.testMode || options.testSortCsn) &&
86
+ sortedCsnDictionaries[n];
80
87
  // Array check for property `args` which may either be a dictionary or an array.
81
88
  r[n] = cloneCsnDict(val, options, sort, sortDict);
82
89
  }
@@ -122,6 +129,8 @@ function hasNonEnumerable( object, property ) {
122
129
  * @param {object} csn
123
130
  * @param {CSN.Options} options Only cloneOptions.dictionaryPrototype is
124
131
  * used and cloneOptions are passed to sortCsn().
132
+ * @param {boolean} sortProps Whether to sort CSN properties.
133
+ * @param {boolean} sortDict Whether to sort CSN dictionary entries.
125
134
  */
126
135
  function cloneCsnDict( csn, options, sortProps, sortDict ) {
127
136
  const proto = options?.dictionaryPrototype;
@@ -254,7 +254,7 @@ optionProcessor.command('O, toOdata')
254
254
  .option('-f, --odata-format <format>', { valid: ['flat', 'structured'] })
255
255
  .option('-n, --sql-mapping <style>', { valid: ['plain', 'quoted', 'hdbcds'], aliases: [ '--names' ] })
256
256
  .option('-s, --service-names <list>')
257
- .option(' --fewer-localized-views')
257
+ .option(' --transitive-localized-views')
258
258
  .help(`
259
259
  Usage: cdsc toOdata [options] <files...>
260
260
 
@@ -293,7 +293,7 @@ optionProcessor.command('O, toOdata')
293
293
  source (like "quoted", but using element names with dots)
294
294
  -s, --service-names <list> List of comma-separated service names to be rendered
295
295
  (default) empty, all services are rendered
296
- --fewer-localized-views If set, the backends will not create localized convenience views for
296
+ --transitive-localized-views If set, the backends will create localized convenience views for
297
297
  those views, that only have an association to a localized entity/view.
298
298
  `);
299
299
 
@@ -338,7 +338,7 @@ optionProcessor.command('Q, toSql')
338
338
  .option(' --disable-hana-comments')
339
339
  .option(' --generated-by-comment')
340
340
  .option(' --better-sqlite-session-variables <bool>')
341
- .option(' --fewer-localized-views')
341
+ .option(' --transitive-localized-views')
342
342
  .option(' --with-hana-associations <bool>', { valid: [ 'true', 'false' ] })
343
343
  .help(`
344
344
  Usage: cdsc toSql [options] <files...>
@@ -396,7 +396,7 @@ optionProcessor.command('Q, toSql')
396
396
  active if sqlDialect is \`sqlite\`:
397
397
  true : (default) Render better-sqlite session_context(…)
398
398
  false : Render session variables as string literals, used e.g. with sqlite3 driver
399
- --fewer-localized-views If set, the backends will not create localized convenience views for
399
+ --transitive-localized-views If set, the backends will create localized convenience views for
400
400
  those views, that only have an association to a localized entity/view.
401
401
  --with-hana-associations <bool>
402
402
  Enable and disable rendering of "WITH ASSOCIATIONS" for sqlDialect 'hana'.
@@ -478,7 +478,7 @@ optionProcessor.command('toCsn')
478
478
  .option(' --with-localized')
479
479
  .option(' --with-locations')
480
480
  .option(' --struct-xpr')
481
- .option(' --fewer-localized-views')
481
+ .option(' --transitive-localized-views')
482
482
  .help(`
483
483
  Usage: cdsc toCsn [options] <files...>
484
484
 
@@ -495,8 +495,8 @@ optionProcessor.command('toCsn')
495
495
  universal: in development (BETA)
496
496
  --with-locations Add $location to CSN artifacts. In contrast to \`--enrich-csn\`,
497
497
  $location is an object with 'file', 'line' and 'col' properties.
498
- --fewer-localized-views If --with-locations and this option are set, the backends
499
- will not create localized convenience views for those views,
498
+ --transitive-localized-views If --with-locations and this option are set, the backends
499
+ will create localized convenience views for those views,
500
500
  that only have an association to a localized entity/view.
501
501
 
502
502
  Internal options (for testing only, may be changed/removed at any time)
@@ -37,6 +37,7 @@ const queryOps = {
37
37
  };
38
38
 
39
39
  const PRECEDENCE_OF_IN_PREDICATE = 10;
40
+ const PRECEDENCE_OF_EQUAL = 10;
40
41
 
41
42
  class AstBuildingParser extends BaseParser {
42
43
  constructor( lexer, keywords, table, options, messageFunctions ) {
@@ -45,6 +46,7 @@ class AstBuildingParser extends BaseParser {
45
46
  this.$messageFunctions = messageFunctions;
46
47
  this.docComments = [];
47
48
  this.docCommentIndex = 0;
49
+ this.comments = [];
48
50
 
49
51
  this.afterBrace$ = -1;
50
52
  this.topLevel$ = -1;
@@ -165,6 +167,10 @@ class AstBuildingParser extends BaseParser {
165
167
  }
166
168
 
167
169
  isDotForPath() {
170
+ if (this.dynamic_.inSelectItem == null)
171
+ return true;
172
+ // false for outer select item, true for inner; TODO: it would be best to set
173
+ // this.dynamic_.inSelectItem to null in filters
168
174
  const next = this.tokens[this.tokenIdx + 1].type;
169
175
  return next !== '*' && next !== '{';
170
176
  }
@@ -190,10 +196,103 @@ class AstBuildingParser extends BaseParser {
190
196
  * `namespace` is forbidden after a definitions/extend or after previous
191
197
  * `namespace`
192
198
  */
193
- fileSection() {
199
+ namespaceRestriction() {
194
200
  return ++this.topLevel$ < 1;
195
201
  }
196
202
 
203
+ /**
204
+ * `extend`/`annotate` is forbidden inside `extend … with definitions` and
205
+ * variants
206
+ */
207
+ extensionRestriction() {
208
+ // TODO: use `syntax-unexpected-extension` as message
209
+ const r = this.dynamic_.inExtension;
210
+ this.dynamic_.inExtension = true;
211
+ return !r;
212
+ }
213
+
214
+ /**
215
+ * `annotation` def is only allowed top-level
216
+ */
217
+ vocabularyRestriction( test ) {
218
+ // TODO: use `syntax-unexpected-vocabulary` as message
219
+ if (!test)
220
+ this.dynamic_.inBlock = true;
221
+ return !this.dynamic_.inBlock;
222
+ }
223
+
224
+ /**
225
+ * Prepare element restrictions and check validility of final anno assignments.
226
+ * TODO TOOL: `arg` for actions/conditions
227
+ *
228
+ * Called in rule `elementDef` at the following places:
229
+ * - <prepare> after `:` (before calling `typeExpression`):
230
+ * disallow `= calcExpr` and final annotation assignments
231
+ * without further <prepare=calcOrDefaultRestriction>
232
+ * - <prepare> in empty alternative to type expression:
233
+ * allow `= calcExpr` and final annotation assignments
234
+ * - <cond> before final annotation assignments: allowed?
235
+ *
236
+ * Called in rule `returnsSpec`:
237
+ * - <prepare> after `returns`: disallow `default`.
238
+ */
239
+ elementRestriction( test ) {
240
+ if (!test) { // after `:` for typeExpression, or without type
241
+ const withoutType = this.lb().type !== ':';
242
+ const afterReturns = this.lb().keyword === 'returns';
243
+ this.dynamic_.elementCtx = [ withoutType, withoutType, afterReturns ];
244
+ }
245
+ // or before final annotation assignments
246
+ return this.dynamic_.elementCtx?.[1];
247
+ }
248
+
249
+ /**
250
+ * Prepare `= calcExpr` restriction and check whether it can be used.
251
+ *
252
+ * Called at the following places:
253
+ * - <prepare> before (optionally) calling rule `nullabilityAndDefault`,
254
+ * except for managed associations/compositions:
255
+ * allow `= calcExpr`, allow final annotation assignments if not after `}`
256
+ * (TODO: should we allow `String @anno:{ … } not null @MoreAnnos`?)
257
+ * - <cond> before `default`: disallow calc expr (+ restrict default expr),
258
+ * allowed? (not for `returns`)
259
+ * - <cond> before `=` for calc expressions: allowed?
260
+ *
261
+ * To have any effect, <prepare=elementRestriction> must have been called.
262
+ */
263
+ calcOrDefaultRestriction( test, arg ) {
264
+ const { elementCtx } = this.dynamic_;
265
+ if (!test) { // at beginning of rule `nullabilityAndDefault`
266
+ if (!elementCtx)
267
+ return true;
268
+ elementCtx[0] = !arg; // using `= expr` is ok (except for assoc)
269
+ elementCtx[1] = this.lb().type !== '}'; // allow final annos not after block
270
+ }
271
+ else if (this.l() === '=') { // <cond> before `= calcExpression`
272
+ return elementCtx?.[0];
273
+ }
274
+ else { // <cond> before `default`
275
+ if (elementCtx)
276
+ elementCtx[0] = false;
277
+ this.prec_ = PRECEDENCE_OF_EQUAL; // only expressions for DEFAULT expr
278
+ }
279
+ return !elementCtx?.[2]; // default allowed?
280
+ }
281
+
282
+ inExpandInline() { // not as <cond>
283
+ this.dynamic_.inSelectItem = 'nested';
284
+ }
285
+
286
+ /**
287
+ * `virtual` and `key` cannot be used inside expand/inline
288
+ * (also inside sub queries in those, which will be rejected later anyway)
289
+ */
290
+ notInExpandInline( test ) {
291
+ if (!test)
292
+ this.dynamic_.inSelectItem = true;
293
+ return this.dynamic_.inSelectItem !== 'nested';
294
+ }
295
+
197
296
  /**
198
297
  * `;` between statements is optional only after a `}` (ex braces of structure
199
298
  * values for annotations).
@@ -205,29 +304,31 @@ class AstBuildingParser extends BaseParser {
205
304
  }
206
305
 
207
306
  /**
208
- * Annotation assignments at the end of (element) refs are allowed.
307
+ * For annotations at the beginning of columns outside parentheses
209
308
  */
210
- allowFinalAnnoAssign() {
211
- // TODO: do properly with type expression
212
- return this.afterBrace$ !== this.tokenIdx;
213
- }
214
-
215
- // TOOL Runtime TODO: provide proto-linked dynamicContext
216
- inSameLine() {
217
- return this.lb().location.line === this.la().location.line;
309
+ annoInSameLine( test ) {
310
+ if (!test)
311
+ this.dynamic_.safeAnno = true;
312
+ return this.dynamic_.safeAnno ||
313
+ this.lb().location.line === this.la().location.line;
218
314
  }
219
315
 
220
316
  /**
221
- * `...` can appear in the top-level array value only.
317
+ * `...` can appear in the top-level array value only and not after `...`
318
+ * without `up to`.
222
319
  */
223
- annoTopValue( test ) {
224
- if (test)
225
- return !this.stack.at( -1 ).$annoTopValue;
226
- if (this.stack.at( -2 )?.$annoTopValue)
227
- this.stack.at( -1 ).$annoTopValue = 'inner';
228
- else if (this.lb().type === '[')
229
- this.stack.at( -1 ).$annoTopValue = 'array';
230
- return null;
320
+ ellipsisRestriction( test ) {
321
+ if (!test) {
322
+ this.dynamic_.arrayAnno = [ !this.dynamic_.arrayAnno ];
323
+ }
324
+ else { // on '...'
325
+ const { arrayAnno } = this.dynamic_;
326
+ if (!arrayAnno[0])
327
+ return false;
328
+ if (this.tokens[this.tokenIdx + 1]?.type === ',')
329
+ arrayAnno[0] = false;
330
+ }
331
+ return true;
231
332
  }
232
333
 
233
334
  beforeColon() {
@@ -255,7 +356,7 @@ class AstBuildingParser extends BaseParser {
255
356
  if (this.l() === ':') {
256
357
  this.warning( 'syntax-missing-parens', anno.name,
257
358
  { code: '@‹anno›', op: ':', newcode: '@(‹anno›…)' },
258
- // eslint-disable-next-line max-len
359
+ // eslint-disable-next-line @stylistic/js/max-len
259
360
  'When $(CODE) is followed by $(OP), use $(NEWCODE) for annotation assignments at this position' );
260
361
  }
261
362
  }
@@ -264,7 +365,7 @@ class AstBuildingParser extends BaseParser {
264
365
  const next = this.la();
265
366
  if (next.text === '@' && next.line <= this.lb().endLine) {
266
367
  this.warning( 'syntax-missing-semicolon', next, { code: ';' },
267
- // eslint-disable-next-line max-len
368
+ // eslint-disable-next-line @stylistic/js/max-len
268
369
  'Add a $(CODE) and/or newline before the annotation assignment to indicate that it belongs to the next statement' );
269
370
  }
270
371
  }
@@ -403,7 +504,7 @@ class AstBuildingParser extends BaseParser {
403
504
  }
404
505
  else if (text.charAt(0) !== '!') {
405
506
  this.message( 'syntax-deprecated-ident', location, { delimited: id },
406
- // eslint-disable-next-line max-len
507
+ // eslint-disable-next-line @stylistic/js/max-len
407
508
  'Deprecated delimited identifier syntax, use $(DELIMITED) - strings are delimited by single quotes' );
408
509
  }
409
510
  }
@@ -442,11 +543,11 @@ class AstBuildingParser extends BaseParser {
442
543
  classifyImplicitName( category, ref ) {
443
544
  if (!ref || ref.path) {
444
545
  const tokenIndex = ref?.path[ref.path.length - 1]?.location.tokenIndex;
445
- const token = this.tokens[tokenIndex ?? this.tokenIdx - 1];
446
- const { parsed } = token;
447
- if (parsed && parsed !== 'token' && parsed !== 'keyword') {
448
- token.parsed = category;
449
- return { token, parsed };
546
+ const token = this.prevTokenWithIndex( tokenIndex ) ?? this.tokens[this.tokenIdx - 1];
547
+ const { parsedAs } = token;
548
+ if (parsedAs && parsedAs !== 'token' && parsedAs !== 'keyword') {
549
+ token.parsedAs = category;
550
+ return { token, parsedAs };
450
551
  }
451
552
  }
452
553
  return null;
@@ -600,7 +701,7 @@ class AstBuildingParser extends BaseParser {
600
701
  {
601
702
  std: 'Annotation number $(RAWVALUE) is put as $(VALUE) into the CSN',
602
703
  rounded: 'Annotation number $(RAWVALUE) is rounded to $(VALUE)',
603
- // eslint-disable-next-line max-len
704
+ // eslint-disable-next-line @stylistic/js/max-len
604
705
  infinite: 'Annotation value $(RAWVALUE) is infinite as number and put as string into the CSN',
605
706
  } );
606
707
  }
@@ -639,11 +740,10 @@ class AstBuildingParser extends BaseParser {
639
740
  else { // next doc comment between previous & current token
640
741
  // With explicit docComment:false, we don't emit a warning.
641
742
  if (art.doc && this.options.docComment !== false) {
642
- this.docComments[art.doc.location.tokenIndex].parsed = '';
643
743
  this.warning( 'syntax-duplicate-doc-comment', art.doc, {},
644
744
  'Doc comment is overwritten by another one below' );
645
745
  }
646
- token.parsed = 'doc';
746
+ token.parsedAs = 'doc';
647
747
  const val = !this.options.docComment || parseDocComment( token.text );
648
748
  art.doc = { val, location: token.location };
649
749
  }
@@ -758,8 +858,24 @@ class AstBuildingParser extends BaseParser {
758
858
  // TODO: also define method `combineWith` in Location
759
859
  combineLocation( { location: start }, { location: end } = this.lb() ) {
760
860
  const { file, line, col } = start;
761
- // eslint-disable-next-line object-curly-newline
762
- return { file, line, col, endLine: end.endLine, endCol: end.endCol };
861
+
862
+ return {
863
+ file, line, col, endLine: end.endLine, endCol: end.endCol,
864
+ };
865
+ }
866
+
867
+ // `tokenIndex` is index in “combined” token array (parsing-relevant, doc
868
+ // comments, comments) → cannot be used directly
869
+ prevTokenWithIndex( tokenIndex ) {
870
+ if (tokenIndex != null) {
871
+ let { tokenIdx } = this;
872
+ while (--tokenIdx >= 0) {
873
+ const token = this.tokens[tokenIdx];
874
+ if (token.location.tokenIndex === tokenIndex)
875
+ return token;
876
+ }
877
+ }
878
+ return null;
763
879
  }
764
880
 
765
881
  // TODO: rename to `valAst`
@@ -831,8 +947,10 @@ class AstBuildingParser extends BaseParser {
831
947
  : path[0].$delimited || !functionsWithoutParens.includes( id.toUpperCase() ))
832
948
  return this.attachLocation( ref );
833
949
 
834
- if (location.tokenIndex != null)
835
- this.tokens[location.tokenIndex].parsed = 'func';
950
+ const funcToken = this.prevTokenWithIndex( location.tokenIndex );
951
+ // TODO: we could have an opt(?) parameter funcToken for speed-up (passing this.lr())
952
+ if (funcToken)
953
+ funcToken.parsedAs = 'func';
836
954
  // TODO: XSN representation of functions is a bit strange - rework
837
955
  const op = { location, val: 'call' };
838
956
  return this.attachLocation( { op, func: ref, args } );
@@ -876,7 +994,7 @@ class AstBuildingParser extends BaseParser {
876
994
  literal: 'token',
877
995
  });
878
996
  }
879
- this.tokens[method.location.tokenIndex].parsed = 'func';
997
+ // this.prevTokenWithIndex( method.location.tokenIndex ).parsedAs = 'func';
880
998
  const func = {
881
999
  op: { location: method.location, val: 'call' },
882
1000
  func: { path: [ method ] },
@@ -906,7 +1024,7 @@ class AstBuildingParser extends BaseParser {
906
1024
  return;
907
1025
  }
908
1026
  }
909
- this.error( 'syntax-unexpected-assoc', this.getCurrentToken(), {},
1027
+ this.error( 'syntax-unexpected-assoc', this.la(), {},
910
1028
  'Unexpected association definition in select item' );
911
1029
  }
912
1030
 
@@ -1120,7 +1238,7 @@ function relevantDigits( val ) {
1120
1238
  // For compatibility with ANTLR-based parser:
1121
1239
  function antlrName( type ) {
1122
1240
  if (typeof type !== 'string')
1123
- type = (!type.parsed || type.parsed === 'keyword') && type.keyword || type.type;
1241
+ type = (!type.parsedAs || type.parsedAs === 'keyword') && type.keyword || type.type;
1124
1242
  if (/^[A-Z]+/.test( type ))// eslint-disable-next-line no-nested-ternary
1125
1243
  return (type === 'Id') ? 'Identifier' : (type === 'EOF') ? '<EOF>' : type;
1126
1244
  return (/^[a-z]+/.test( type )) ? type.toUpperCase() : `'${ type }'`;