@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.
- package/CHANGELOG.md +43 -1
- package/bin/cdsse.js +4 -0
- package/bin/cdsv2m.js +2 -1
- package/doc/Versioning.md +4 -4
- package/lib/api/options.js +1 -0
- package/lib/base/builtins.js +2 -2
- package/lib/base/dictionaries.js +1 -2
- package/lib/base/keywords.js +3 -1
- package/lib/base/lazyload.js +1 -1
- package/lib/base/message-registry.js +170 -143
- package/lib/base/messages.js +69 -59
- package/lib/base/model.js +3 -3
- package/lib/base/node-helpers.js +17 -16
- package/lib/base/optionProcessorHelper.js +13 -14
- package/lib/base/shuffle.js +4 -1
- package/lib/checks/structuredAnnoExpressions.js +1 -1
- package/lib/compiler/assert-consistency.js +1 -1
- package/lib/compiler/builtins.js +4 -1
- package/lib/compiler/extend.js +20 -5
- package/lib/compiler/resolve.js +45 -9
- package/lib/compiler/shared.js +1 -0
- package/lib/edm/annotations/edmJson.js +3 -3
- package/lib/edm/annotations/genericTranslation.js +5 -1
- package/lib/edm/annotations/vocabularyDefinitions.js +8 -2
- package/lib/edm/edmUtils.js +2 -1
- package/lib/gen/BaseParser.js +142 -103
- package/lib/gen/CdlParser.js +2240 -2201
- package/lib/gen/Dictionary.json +185 -6
- package/lib/json/from-csn.js +2 -0
- package/lib/json/to-csn.js +13 -4
- package/lib/language/docCommentParser.js +11 -5
- package/lib/language/errorStrategy.js +3 -3
- package/lib/language/genericAntlrParser.js +2 -0
- package/lib/model/csnUtils.js +6 -1
- package/lib/optionProcessor.js +5 -1
- package/lib/parsers/AstBuildingParser.js +200 -86
- package/lib/parsers/CdlGrammar.g4 +142 -86
- package/lib/parsers/Lexer.js +5 -3
- package/lib/parsers/index.js +1 -1
- package/lib/render/toCdl.js +6 -5
- package/lib/render/toHdbcds.js +1 -1
- package/lib/render/toSql.js +5 -3
- package/lib/render/utils/common.js +19 -6
- package/lib/render/utils/standardDatabaseFunctions.js +576 -0
- package/lib/transform/addTenantFields.js +2 -1
- package/lib/transform/db/expansion.js +3 -0
- package/lib/transform/db/flattening.js +18 -77
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/rewriteCalculatedElements.js +14 -19
- package/lib/transform/db/temporal.js +2 -1
- package/lib/transform/forOdata.js +1 -1
- package/lib/transform/odata/adaptAnnotationRefs.js +79 -0
- package/lib/transform/odata/createForeignKeys.js +25 -9
- package/lib/transform/odata/flattening.js +11 -1
- package/lib/transform/transformUtils.js +20 -85
- package/package.json +2 -1
- 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
|
|
88
|
-
const
|
|
89
|
-
|
|
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
|
|
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
|
-
* - <
|
|
118
|
-
* - <
|
|
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
|
-
* - <
|
|
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 (
|
|
126
|
-
return
|
|
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
|
|
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
|
|
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
|
-
|
|
189
|
-
const realTokens = this.dynamic_.generic?.[parserTokens[tokenName]];
|
|
190
|
-
// TODO: avoid parserTokens dict, use lower-case in specialFunctions
|
|
191
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
260
|
+
return this.tokens[this.tokenIdx + 1]?.keyword === 'null' ||
|
|
219
261
|
this.precNone_( _test, prec );
|
|
220
262
|
}
|
|
221
263
|
|
|
222
264
|
isNamedArg() {
|
|
223
|
-
const
|
|
224
|
-
return 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$
|
|
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
|
-
//
|
|
282
|
+
// 'syntax-unexpected-extension': 'Unexpected $(KEYWORD) inside $(CODE) block',
|
|
241
283
|
const r = this.dynamic_.inExtension;
|
|
242
|
-
this.dynamic_.inExtension =
|
|
243
|
-
return
|
|
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 =
|
|
253
|
-
return
|
|
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 <
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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$
|
|
331
|
-
|
|
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
|
|
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
|
|
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]
|
|
485
|
+
return typeof this.dynamic_.arrayAnno[0] === 'string' && arg;
|
|
362
486
|
}
|
|
363
|
-
else { // orNotEmpty
|
|
364
|
-
return this.dynamic_.arrayAnno
|
|
487
|
+
else { // orNotEmpty -> anno value must not be empty struct
|
|
488
|
+
return !this.dynamic_.arrayAnno && this.lb().type === '{' && 'empty';
|
|
365
489
|
}
|
|
366
|
-
return
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
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
|
}
|