@sap/cds-compiler 3.4.2 → 3.5.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 +80 -0
- package/README.md +1 -0
- package/bin/cds_update_identifiers.js +5 -5
- package/bin/cdsc.js +15 -16
- package/bin/cdshi.js +19 -6
- package/doc/CHANGELOG_ARCHIVE.md +2 -2
- package/doc/CHANGELOG_BETA.md +9 -1
- package/doc/CHANGELOG_DEPRECATED.md +2 -0
- package/lib/api/main.js +61 -59
- package/lib/api/options.js +4 -2
- package/lib/api/validate.js +2 -2
- package/lib/base/cleanSymbols.js +2 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +6 -6
- package/lib/base/location.js +11 -12
- package/lib/base/message-registry.js +177 -58
- package/lib/base/messages.js +252 -180
- package/lib/base/model.js +14 -11
- package/lib/base/node-helpers.js +9 -10
- package/lib/base/optionProcessorHelper.js +138 -129
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +5 -5
- package/lib/checks/annotationsOData.js +4 -4
- package/lib/checks/arrayOfs.js +1 -1
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +3 -3
- package/lib/checks/defaultValues.js +3 -3
- package/lib/checks/elements.js +7 -7
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/foreignKeys.js +1 -1
- package/lib/checks/invalidTarget.js +4 -4
- package/lib/checks/managedInType.js +1 -1
- package/lib/checks/managedWithoutKeys.js +1 -1
- package/lib/checks/nonexpandableStructured.js +5 -3
- package/lib/checks/nullableKeys.js +1 -1
- package/lib/checks/onConditions.js +5 -6
- package/lib/checks/parameters.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -2
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +4 -4
- package/lib/checks/types.js +7 -7
- package/lib/checks/utils.js +4 -4
- package/lib/checks/validator.js +16 -13
- package/lib/compiler/.eslintrc.json +4 -1
- package/lib/compiler/assert-consistency.js +8 -7
- package/lib/compiler/builtins.js +14 -14
- package/lib/compiler/checks.js +123 -48
- package/lib/compiler/define.js +12 -13
- package/lib/compiler/extend.js +266 -60
- package/lib/compiler/finalize-parse-cdl.js +10 -5
- package/lib/compiler/index.js +17 -14
- package/lib/compiler/populate.js +14 -6
- package/lib/compiler/propagator.js +2 -0
- package/lib/compiler/resolve.js +2 -15
- package/lib/compiler/shared.js +27 -16
- package/lib/compiler/tweak-assocs.js +5 -6
- package/lib/compiler/utils.js +20 -0
- package/lib/edm/annotations/genericTranslation.js +604 -358
- package/lib/edm/annotations/preprocessAnnotations.js +39 -35
- package/lib/edm/csn2edm.js +275 -222
- package/lib/edm/edm.js +17 -3
- package/lib/edm/edmAnnoPreprocessor.js +6 -6
- package/lib/edm/edmInboundChecks.js +2 -2
- package/lib/edm/edmPreprocessor.js +107 -77
- package/lib/edm/edmUtils.js +44 -5
- package/lib/gen/Dictionary.json +210 -8
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +67 -63
- package/lib/gen/language.tokens +81 -81
- package/lib/gen/languageLexer.interp +4 -10
- package/lib/gen/languageLexer.js +854 -869
- package/lib/gen/languageLexer.tokens +79 -81
- package/lib/gen/languageParser.js +14309 -13832
- package/lib/inspect/inspectModelStatistics.js +2 -2
- package/lib/inspect/inspectPropagation.js +6 -6
- package/lib/inspect/inspectUtils.js +2 -2
- package/lib/json/from-csn.js +102 -55
- package/lib/json/to-csn.js +119 -198
- package/lib/language/antlrParser.js +5 -2
- package/lib/language/docCommentParser.js +6 -6
- package/lib/language/errorStrategy.js +43 -23
- package/lib/language/genericAntlrParser.js +113 -133
- package/lib/language/language.g4 +1550 -1506
- package/lib/language/multiLineStringParser.js +3 -3
- package/lib/language/textUtils.js +2 -2
- package/lib/main.js +3 -3
- package/lib/model/csnRefs.js +5 -0
- package/lib/model/csnUtils.js +130 -122
- package/lib/model/revealInternalProperties.js +1 -1
- package/lib/model/sortViews.js +4 -6
- package/lib/modelCompare/compare.js +2 -2
- package/lib/modelCompare/utils/.eslintrc.json +22 -0
- package/lib/modelCompare/utils/filter.js +100 -0
- package/lib/optionProcessor.js +5 -0
- package/lib/render/.eslintrc.json +1 -0
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +12 -12
- package/lib/render/toCdl.js +311 -276
- package/lib/render/toHdbcds.js +97 -94
- package/lib/render/toRename.js +5 -5
- package/lib/render/toSql.js +127 -223
- package/lib/render/utils/common.js +141 -108
- package/lib/render/utils/delta.js +227 -0
- package/lib/render/utils/sql.js +22 -6
- package/lib/render/utils/stringEscapes.js +3 -3
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +3 -3
- package/lib/transform/db/assertUnique.js +13 -12
- package/lib/transform/db/associations.js +5 -5
- package/lib/transform/db/cdsPersistence.js +10 -8
- package/lib/transform/db/constraints.js +14 -14
- package/lib/transform/db/expansion.js +20 -22
- package/lib/transform/db/flattening.js +24 -42
- package/lib/transform/db/groupByOrderBy.js +3 -3
- package/lib/transform/db/temporal.js +6 -6
- package/lib/transform/db/transformExists.js +23 -23
- package/lib/transform/db/views.js +16 -16
- package/lib/transform/draft/.eslintrc.json +1 -35
- package/lib/transform/draft/db.js +10 -10
- package/lib/transform/draft/odata.js +2 -2
- package/lib/transform/forOdataNew.js +8 -29
- package/lib/transform/forRelationalDB.js +16 -6
- package/lib/transform/localized.js +11 -10
- package/lib/transform/odata/toFinalBaseType.js +41 -27
- package/lib/transform/odata/typesExposure.js +113 -47
- package/lib/transform/parseExpr.js +209 -106
- package/lib/transform/transformUtilsNew.js +17 -10
- package/lib/transform/translateAssocsToJoins.js +24 -19
- package/lib/transform/universalCsn/coreComputed.js +10 -10
- package/lib/transform/universalCsn/universalCsnEnricher.js +26 -26
- package/lib/transform/universalCsn/utils.js +3 -3
- package/lib/utils/file.js +5 -5
- package/lib/utils/moduleResolve.js +13 -13
- package/lib/utils/objectUtils.js +6 -6
- package/lib/utils/term.js +5 -2
- package/lib/utils/timetrace.js +51 -24
- package/package.json +5 -8
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/message-explanations.json +1 -1
- package/share/messages/redirected-to-complex.md +4 -4
- package/share/messages/{syntax-expecting-integer.md → syntax-expecting-unsigned-int.md} +7 -4
- package/lib/modelCompare/filter.js +0 -83
|
@@ -55,8 +55,11 @@ function match( ttype ) {
|
|
|
55
55
|
const token = this.getCurrentToken();
|
|
56
56
|
if (token.type === identType || !keywordRegexp.test( token.text ))
|
|
57
57
|
return antlr4.Parser.prototype.match.call( this, ttype );
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
// This is very likely to be dead code: we do not use a simple Identifier
|
|
59
|
+
// without alternatives in the grammar. With alternatives, recoverInline() is
|
|
60
|
+
// the place to go. (But this code should work with a changed grammar…)
|
|
61
|
+
this.message( 'syntax-unexpected-reserved-word', token,
|
|
62
|
+
{ code: token.text, delimited: token.text } );
|
|
60
63
|
this._errHandler.reportMatch(this);
|
|
61
64
|
this.consume();
|
|
62
65
|
return token;
|
|
@@ -72,7 +75,6 @@ class KeywordErrorStrategy extends DefaultErrorStrategy {
|
|
|
72
75
|
super( ...args );
|
|
73
76
|
|
|
74
77
|
this._super = {
|
|
75
|
-
consumeUntil: super.consumeUntil,
|
|
76
78
|
recoverInline: super.recoverInline,
|
|
77
79
|
getExpectedTokens: super.getExpectedTokens,
|
|
78
80
|
};
|
|
@@ -89,6 +91,7 @@ Object.assign( KeywordErrorStrategy.prototype, {
|
|
|
89
91
|
reportIgnoredWith,
|
|
90
92
|
// getErrorRecoverySet,
|
|
91
93
|
consumeUntil,
|
|
94
|
+
consumeAndMarkUntil,
|
|
92
95
|
recoverInline,
|
|
93
96
|
getMissingSymbol,
|
|
94
97
|
getExpectedTokensForMessage,
|
|
@@ -137,16 +140,19 @@ function sync( recognizer ) {
|
|
|
137
140
|
}
|
|
138
141
|
|
|
139
142
|
// Expected token is identifier, current is (reserved) KEYWORD:
|
|
140
|
-
// TODO: do not use this if "close enough" (1 char diff
|
|
143
|
+
// TODO: do not use this if "close enough" (1 char diff or prefix)
|
|
144
|
+
// to a keyword in nextTokens
|
|
141
145
|
//
|
|
142
146
|
// NOTE: it is important to do this only if EPSILON is not in `nextTokens`,
|
|
143
|
-
// which means that we cannot bring the better special syntax-
|
|
147
|
+
// which means that we cannot bring the better special syntax-unexpected-reserved
|
|
144
148
|
// in all cases. Reason: high performance impact of the alternative,
|
|
145
149
|
// i.e. calling method Parser#isExpectedToken() = invoking the ATN
|
|
146
150
|
// interpreter to see behind EPSILON.
|
|
147
151
|
const identType = recognizer.constructor.Identifier;
|
|
148
152
|
if (keywordRegexp.test( token.text ) && nextTokens.contains( identType )) {
|
|
149
|
-
recognizer.message( 'syntax-
|
|
153
|
+
recognizer.message( 'syntax-unexpected-reserved-word', token,
|
|
154
|
+
{ code: token.text, delimited: token.text } );
|
|
155
|
+
// TODO: attach tokens like for 'syntax-unexpected-token'
|
|
150
156
|
token.type = identType; // make next ANTLR decision assume identifier
|
|
151
157
|
return;
|
|
152
158
|
}
|
|
@@ -210,11 +216,11 @@ function reportInputMismatch( recognizer, e, deadEnds ) {
|
|
|
210
216
|
const expecting = deadEnds !== true && // true: cannot compute expecting
|
|
211
217
|
this.getExpectedTokensForMessage( recognizer, e.offendingToken, deadEnds );
|
|
212
218
|
const offending = this.getTokenDisplay( e.offendingToken, recognizer );
|
|
219
|
+
e.offendingToken.$isSkipped = 'offending';
|
|
213
220
|
let err;
|
|
214
221
|
if (expecting && expecting.length) {
|
|
215
|
-
err = recognizer.error( 'syntax-
|
|
216
|
-
{ offending, expecting }
|
|
217
|
-
'Mismatched $(OFFENDING), expecting $(EXPECTING)' );
|
|
222
|
+
err = recognizer.error( 'syntax-unexpected-token', e.offendingToken,
|
|
223
|
+
{ offending, expecting } );
|
|
218
224
|
err.expectedTokens = expecting;
|
|
219
225
|
}
|
|
220
226
|
else { // should not really happen anymore... -> no messageId !
|
|
@@ -232,11 +238,12 @@ function reportUnwantedToken( recognizer ) {
|
|
|
232
238
|
this.beginErrorCondition(recognizer);
|
|
233
239
|
|
|
234
240
|
const token = recognizer.getCurrentToken();
|
|
241
|
+
token.$isSkipped = 'offending';
|
|
235
242
|
const expecting = this.getExpectedTokensForMessage( recognizer, token );
|
|
236
243
|
const offending = this.getTokenDisplay( token, recognizer );
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
'
|
|
244
|
+
// Just text variant, no other message id! Would depend on ANTLR-internals
|
|
245
|
+
const err = recognizer.error( 'syntax-unexpected-token', token,
|
|
246
|
+
{ '#': 'unwanted', offending, expecting } );
|
|
240
247
|
err.expectedTokens = expecting; // TODO: remove next token?
|
|
241
248
|
if (!recognizer.avoidErrorListeners) // with --trace-parser or --trace-parser-ambig
|
|
242
249
|
recognizer.notifyErrorListeners( err.message, token, err );
|
|
@@ -249,9 +256,11 @@ function reportMissingToken( recognizer ) {
|
|
|
249
256
|
this.beginErrorCondition(recognizer);
|
|
250
257
|
|
|
251
258
|
const token = recognizer.getCurrentToken();
|
|
259
|
+
token.$isSkipped = 'offending';
|
|
252
260
|
const expecting = this.getExpectedTokensForMessage( recognizer, token );
|
|
253
261
|
const offending = this.getTokenDisplay( token, recognizer );
|
|
254
262
|
// TODO: if non-reserved keyword will not been parsed as keyword, use Identifier for offending
|
|
263
|
+
// Hopefully not too ANTLR-specific, so extra message id is ok:
|
|
255
264
|
const err = recognizer.error( 'syntax-missing-token', token,
|
|
256
265
|
{ offending, expecting },
|
|
257
266
|
'Missing $(EXPECTING) before $(OFFENDING)' );
|
|
@@ -264,10 +273,10 @@ function reportIgnoredWith( recognizer, t ) {
|
|
|
264
273
|
const next = recognizer._interp.atn.states[recognizer.state].transitions[0].target;
|
|
265
274
|
recognizer.state = next.stateNumber; // previous match() does not set the state
|
|
266
275
|
const expecting = this.getExpectedTokensForMessage( recognizer, t );
|
|
267
|
-
const m = recognizer.warning( 'syntax-
|
|
268
|
-
{ offending: "';'", expecting },
|
|
276
|
+
const m = recognizer.warning( 'syntax-unexpected-semicolon', t,
|
|
277
|
+
{ offending: "';'", expecting, keyword: 'with' },
|
|
269
278
|
// eslint-disable-next-line max-len
|
|
270
|
-
'Unexpected $(OFFENDING), expecting $(EXPECTING) - ignored previous
|
|
279
|
+
'Unexpected $(OFFENDING), expecting $(EXPECTING) - ignored previous $(KEYWORD)' );
|
|
271
280
|
m.expectedTokens = expecting;
|
|
272
281
|
}
|
|
273
282
|
|
|
@@ -280,10 +289,10 @@ function consumeUntil( recognizer, set ) {
|
|
|
280
289
|
|
|
281
290
|
// let s=this.getTokenDisplay( recognizer.getCurrentToken(), recognizer );
|
|
282
291
|
if (SEMI < 1 || RBRACE < 1) {
|
|
283
|
-
this.
|
|
292
|
+
this.consumeAndMarkUntil( recognizer, set );
|
|
284
293
|
}
|
|
285
294
|
else if (set.contains(SEMI)) { // do not check for RBRACE here!
|
|
286
|
-
this.
|
|
295
|
+
this.consumeAndMarkUntil( recognizer, set );
|
|
287
296
|
// console.log('CONSUMED-ORIG:',s,this.getTokenDisplay( recognizer.getCurrentToken(),
|
|
288
297
|
// recognizer ),recognizer.getCurrentToken().line,intervalSetToArray( recognizer, set ));
|
|
289
298
|
}
|
|
@@ -295,9 +304,9 @@ function consumeUntil( recognizer, set ) {
|
|
|
295
304
|
stop.addOne( SEMI );
|
|
296
305
|
// I am not that sure whether to add RBRACE...
|
|
297
306
|
stop.addOne( RBRACE );
|
|
298
|
-
this.
|
|
299
|
-
|
|
300
|
-
|
|
307
|
+
this.consumeAndMarkUntil( recognizer, stop );
|
|
308
|
+
const ttype = recognizer.getTokenStream().LA(1);
|
|
309
|
+
if (ttype === SEMI || ttype === RBRACE && !set.contains(RBRACE)) {
|
|
301
310
|
recognizer.consume();
|
|
302
311
|
this.reportMatch(recognizer); // we know current token is correct
|
|
303
312
|
}
|
|
@@ -312,6 +321,15 @@ function consumeUntil( recognizer, set ) {
|
|
|
312
321
|
}
|
|
313
322
|
}
|
|
314
323
|
|
|
324
|
+
function consumeAndMarkUntil( recognizer, set ) {
|
|
325
|
+
let t = recognizer.getTokenStream().LT(1);
|
|
326
|
+
while (t.type !== antlr4.Token.EOF && !set.contains( t.type )) {
|
|
327
|
+
if (!t.$isSkipped)
|
|
328
|
+
t.$isSkipped = true;
|
|
329
|
+
recognizer.consume();
|
|
330
|
+
t = recognizer.getTokenStream().LT(1);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
315
333
|
|
|
316
334
|
// As the `match` function of the parser `recognizer` does not allow to check
|
|
317
335
|
// against a set of token types, the generated parser code checks against that
|
|
@@ -328,7 +346,9 @@ function recoverInline( recognizer ) {
|
|
|
328
346
|
if (!keywordRegexp.test( token.text ))
|
|
329
347
|
return this._super.recoverInline.call( this, recognizer );
|
|
330
348
|
|
|
331
|
-
|
|
349
|
+
// TODO: attach `Identifier` as valid name to message?
|
|
350
|
+
recognizer.message( 'syntax-unexpected-reserved-word', token,
|
|
351
|
+
{ code: token.text, delimited: token.text } );
|
|
332
352
|
this.reportMatch(recognizer); // we know current token is correct
|
|
333
353
|
recognizer.consume();
|
|
334
354
|
return token;
|
|
@@ -496,14 +516,14 @@ function getExpectedTokensForMessage( recognizer, offendingToken, deadEnds ) {
|
|
|
496
516
|
// expected.toString(recognizer.literalNames, recognizer.symbolicNames));
|
|
497
517
|
return intervalSetToArray( recognizer, expected );
|
|
498
518
|
|
|
499
|
-
function addSet(other) {
|
|
519
|
+
function addSet( other ) {
|
|
500
520
|
if (!other.contains( hideAltsType ))
|
|
501
521
|
origAddSet.call( this, other );
|
|
502
522
|
}
|
|
503
523
|
|
|
504
524
|
// Add an interval `v` to the IntervalSet `this`. If `v` contains the token
|
|
505
525
|
// type `Identifier`, do not add non-reserved keywords in `v`.
|
|
506
|
-
function addInterval(v) {
|
|
526
|
+
function addInterval( v ) {
|
|
507
527
|
if (v.stop <= identType) {
|
|
508
528
|
origAddInterval.call(this, v);
|
|
509
529
|
}
|
|
@@ -86,14 +86,15 @@ Object.assign(GenericAntlrParser.prototype, {
|
|
|
86
86
|
previousTokenAtLocation,
|
|
87
87
|
combinedLocation,
|
|
88
88
|
surroundByParens,
|
|
89
|
+
secureParens,
|
|
89
90
|
unaryOpForParens,
|
|
90
91
|
leftAssocBinaryOp,
|
|
91
92
|
classifyImplicitName,
|
|
93
|
+
warnIfColonFollows,
|
|
92
94
|
fragileAlias,
|
|
93
95
|
identAst,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
xprToken,
|
|
96
|
+
pushXprToken,
|
|
97
|
+
argsExpression,
|
|
97
98
|
valuePathAst,
|
|
98
99
|
signedExpression,
|
|
99
100
|
numberLiteral,
|
|
@@ -109,7 +110,6 @@ Object.assign(GenericAntlrParser.prototype, {
|
|
|
109
110
|
createArray,
|
|
110
111
|
finalizeDictOrArray,
|
|
111
112
|
createPrefixOp,
|
|
112
|
-
setOnce,
|
|
113
113
|
setMaxCardinality,
|
|
114
114
|
pushIdent,
|
|
115
115
|
handleComposition,
|
|
@@ -117,7 +117,6 @@ Object.assign(GenericAntlrParser.prototype, {
|
|
|
117
117
|
reportExpandInline,
|
|
118
118
|
checkTypeFacet,
|
|
119
119
|
csnParseOnly,
|
|
120
|
-
disallowElementExtension,
|
|
121
120
|
noAssignmentInSameLine,
|
|
122
121
|
noSemicolonHere,
|
|
123
122
|
setLocalToken,
|
|
@@ -217,29 +216,12 @@ function setLocalTokenForId( tokenNameMap ) {
|
|
|
217
216
|
// // throw new antlr4.error.InputMismatchException(this);
|
|
218
217
|
// }
|
|
219
218
|
|
|
220
|
-
/**
|
|
221
|
-
* For element extensions (`extend E:elem` syntax).
|
|
222
|
-
* If `elemName.path` is set, remove the last extension from `$outer` and
|
|
223
|
-
* emit an error that the extension is invalid.
|
|
224
|
-
*
|
|
225
|
-
* @param {object} elemName
|
|
226
|
-
* @param {object} outer
|
|
227
|
-
* @param {string} extensionVariant
|
|
228
|
-
*/
|
|
229
|
-
function disallowElementExtension(elemName, outer, extensionVariant) {
|
|
230
|
-
if (elemName.path) {
|
|
231
|
-
const loc = this.tokenLocation(this.getCurrentToken());
|
|
232
|
-
this.error( 'syntax-invalid-extend', loc, { kind: extensionVariant } );
|
|
233
|
-
// remove last, i.e. new extension
|
|
234
|
-
outer.extensions.pop();
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
219
|
function noAssignmentInSameLine() {
|
|
239
220
|
const t = this.getCurrentToken();
|
|
240
221
|
if (t.text === '@' && t.line <= this._input.LT(-1).line) {
|
|
241
|
-
this.warning( 'syntax-
|
|
242
|
-
|
|
222
|
+
this.warning( 'syntax-missing-newline', t, { anno: '‹anno›' }, // TODO: single quotes, @()
|
|
223
|
+
// eslint-disable-next-line max-len
|
|
224
|
+
'Add a newline before $(ANNO) to indicate that it belongs to the next statement' );
|
|
243
225
|
}
|
|
244
226
|
}
|
|
245
227
|
|
|
@@ -267,9 +249,15 @@ const genericTokenTypes = {
|
|
|
267
249
|
intro: 'GenericIntro',
|
|
268
250
|
};
|
|
269
251
|
|
|
270
|
-
|
|
252
|
+
/**
|
|
253
|
+
* @memberOf GenericAntlrParser
|
|
254
|
+
*
|
|
255
|
+
* @param pathItem
|
|
256
|
+
* @param [expected]
|
|
257
|
+
*/
|
|
258
|
+
function prepareGenericKeywords( pathItem, expected = null ) {
|
|
271
259
|
const length = pathItem?.args?.length || 0;
|
|
272
|
-
const argPos =
|
|
260
|
+
const argPos = length;
|
|
273
261
|
const func = pathItem?.id && specialFunctions[pathItem.id.toUpperCase()];
|
|
274
262
|
const spec = func && func[argPos] || specialFunctions[''][argPos ? 1 : 0];
|
|
275
263
|
this.$genericKeywords = spec;
|
|
@@ -492,10 +480,10 @@ function tokenLocation( token, endToken = null ) {
|
|
|
492
480
|
return loc;
|
|
493
481
|
}
|
|
494
482
|
|
|
495
|
-
function isMultiLineToken(token) {
|
|
483
|
+
function isMultiLineToken( token ) {
|
|
496
484
|
return (
|
|
497
485
|
token.type === this.constructor.DocComment ||
|
|
498
|
-
token.type === this.constructor.String ||
|
|
486
|
+
token.type === this.constructor.String || // TODO: do not check every string content
|
|
499
487
|
token.type === this.constructor.UnterminatedLiteral
|
|
500
488
|
);
|
|
501
489
|
}
|
|
@@ -565,6 +553,23 @@ function combinedLocation( start, end ) {
|
|
|
565
553
|
return locUtils.combinedLocation( start, end );
|
|
566
554
|
}
|
|
567
555
|
|
|
556
|
+
// make sure that the parens of `IN (…)` do not disappear:
|
|
557
|
+
function secureParens( expr ) {
|
|
558
|
+
const op = expr?.op?.val;
|
|
559
|
+
const $parens = expr?.$parens;
|
|
560
|
+
if (!$parens || expr.query || op && op !== 'call' && op !== 'cast')
|
|
561
|
+
return expr;
|
|
562
|
+
// ensure that references, literals and functions keep their surrounding parentheses
|
|
563
|
+
// (is for expressions the case anyway)
|
|
564
|
+
delete expr.$parens;
|
|
565
|
+
return {
|
|
566
|
+
op: { val: 'xpr', location: this.startLocation() },
|
|
567
|
+
args: [ expr ],
|
|
568
|
+
location: { ...expr.location },
|
|
569
|
+
$parens,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
|
|
568
573
|
function surroundByParens( expr, open, close, asQuery = false ) {
|
|
569
574
|
if (!expr)
|
|
570
575
|
return expr;
|
|
@@ -584,6 +589,17 @@ function unaryOpForParens( query, val ) {
|
|
|
584
589
|
return { op: { val, location }, location, args: [ query ] };
|
|
585
590
|
}
|
|
586
591
|
|
|
592
|
+
// ANTLR on some OS might corrupt non-ASCII chars for messages
|
|
593
|
+
function warnIfColonFollows( anno ) {
|
|
594
|
+
const t = this.getCurrentToken();
|
|
595
|
+
if (t.text === ':') {
|
|
596
|
+
this.warning( 'syntax-missing-parens', anno.name.location,
|
|
597
|
+
{ code: '@‹anno›', op: ':', newcode: '@(‹anno›…)' },
|
|
598
|
+
// eslint-disable-next-line max-len
|
|
599
|
+
'When $(CODE) is followed by $(OP), use $(NEWCODE) for annotation assignments at this position' );
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
587
603
|
// If the token before the current one is a doc comment (ignoring other tokens
|
|
588
604
|
// on the hidden channel), put its "cleaned-up" text as value of property `doc`
|
|
589
605
|
// of arg `node` (which could be an array). Complain if `doc` is already set.
|
|
@@ -611,9 +627,9 @@ function docComment( node ) {
|
|
|
611
627
|
|
|
612
628
|
// Classify token (identifier category) for implicit names,
|
|
613
629
|
// to be used in the empty alternative to AS <explicitName>.
|
|
614
|
-
function classifyImplicitName( category, ref ) {
|
|
630
|
+
function classifyImplicitName( category, ref, tokpos = 1 ) {
|
|
615
631
|
if (!ref || ref.path && this.getCurrentToken().text !== '.') {
|
|
616
|
-
const implicit = this._input.LT(-1);
|
|
632
|
+
const implicit = this._input.LT( tokpos - 1 || -1 );
|
|
617
633
|
if (implicit.isIdentifier)
|
|
618
634
|
implicit.isIdentifier = category;
|
|
619
635
|
}
|
|
@@ -623,11 +639,11 @@ function fragileAlias( ast, safe = false ) {
|
|
|
623
639
|
if (this.getCurrentToken().text === '.')
|
|
624
640
|
return ast;
|
|
625
641
|
if (safe || ast.$delimited || !/^[a-zA-Z][a-zA-Z_]+$/.test( ast.id )) {
|
|
626
|
-
this.warning( 'syntax-
|
|
642
|
+
this.warning( 'syntax-deprecated-auto-as', ast.location, { keyword: 'as' },
|
|
627
643
|
'Please add the keyword $(KEYWORD) in front of the alias name' );
|
|
628
644
|
}
|
|
629
645
|
else { // configurable error
|
|
630
|
-
this.message( 'syntax-
|
|
646
|
+
this.message( 'syntax-missing-as', ast.location, { keyword: 'as' },
|
|
631
647
|
'Please add the keyword $(KEYWORD) in front of the alias name' );
|
|
632
648
|
}
|
|
633
649
|
return ast;
|
|
@@ -642,10 +658,9 @@ function identAst( token, category, noTokenTypeCheck = false ) {
|
|
|
642
658
|
id = '';
|
|
643
659
|
if (token.text[0] === '!') {
|
|
644
660
|
id = id.slice( 2, -1 ).replace( /]]/g, ']' );
|
|
645
|
-
if (!id)
|
|
646
|
-
this.
|
|
647
|
-
|
|
648
|
-
}
|
|
661
|
+
if (!id)
|
|
662
|
+
this.message( 'syntax-invalid-name', token, {} );
|
|
663
|
+
|
|
649
664
|
// $delimited is used to complain about ![$self] and other magic vars usage;
|
|
650
665
|
// we might complain about that already here via @arg{category}
|
|
651
666
|
return { id, $delimited: true, location: this.tokenLocation( token ) };
|
|
@@ -655,8 +670,7 @@ function identAst( token, category, noTokenTypeCheck = false ) {
|
|
|
655
670
|
// delimited:
|
|
656
671
|
id = id.slice( 1, -1 ).replace( /""/g, '"' );
|
|
657
672
|
if (!id) {
|
|
658
|
-
this.
|
|
659
|
-
'Delimited identifier must contain at least one character' );
|
|
673
|
+
this.message( 'syntax-invalid-name', token, {} );
|
|
660
674
|
}
|
|
661
675
|
else {
|
|
662
676
|
this.message( 'syntax-deprecated-ident', token, { delimited: id },
|
|
@@ -666,33 +680,27 @@ function identAst( token, category, noTokenTypeCheck = false ) {
|
|
|
666
680
|
return { id, $delimited: true, location: this.tokenLocation( token ) };
|
|
667
681
|
}
|
|
668
682
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
args,
|
|
679
|
-
location,
|
|
680
|
-
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
function setLastAsXpr( args ) {
|
|
684
|
-
const pos = args.length - 1;
|
|
685
|
-
const last = args[pos];
|
|
686
|
-
if (!last || last?.op?.val === 'ixpr') // actuall an internal xpr, which is always flattened
|
|
687
|
-
return last;
|
|
688
|
-
const location = { ...last.location };
|
|
689
|
-
args[pos] = { op: { location, val: 'ixpr' }, args: [ last ], location };
|
|
690
|
-
return args[pos].args;
|
|
683
|
+
// only to be used in @after
|
|
684
|
+
// TODO: remove compatible stuff (A2J/checks use op: 'and'/'=')
|
|
685
|
+
function argsExpression( args, useFirstLocation, compatible = null ) {
|
|
686
|
+
// console.log('AE:',args);
|
|
687
|
+
if (args.length === 1)
|
|
688
|
+
return args[0];
|
|
689
|
+
const location = useFirstLocation && args[0] && { ...args[0].location };
|
|
690
|
+
const op = compatible && args.length === 3 && args[1].val === compatible && args[1];
|
|
691
|
+
const expr = (op)
|
|
692
|
+
? { op: { val: op.val, location: op.location }, args: [ args[0], args[2] ], location }
|
|
693
|
+
: { op: { val: 'ixpr', location: this.startLocation() }, args, location };
|
|
694
|
+
return this.attachLocation( expr );
|
|
691
695
|
}
|
|
692
696
|
|
|
693
|
-
function
|
|
697
|
+
function pushXprToken( args ) {
|
|
694
698
|
const token = this._input.LT(-1);
|
|
695
|
-
|
|
699
|
+
args.push( {
|
|
700
|
+
location: this.tokenLocation( token ),
|
|
701
|
+
val: token.text.toLowerCase(), // TODO: remove toLowerCase() ?
|
|
702
|
+
literal: 'token',
|
|
703
|
+
} );
|
|
696
704
|
}
|
|
697
705
|
|
|
698
706
|
function valuePathAst( ref ) {
|
|
@@ -705,7 +713,7 @@ function valuePathAst( ref ) {
|
|
|
705
713
|
const item = path.find( i => i.args && i.$syntax !== ':' );
|
|
706
714
|
if (!item)
|
|
707
715
|
return ref;
|
|
708
|
-
this.error( 'syntax-
|
|
716
|
+
this.error( 'syntax-unsupported-method', item.location, {},
|
|
709
717
|
'Methods in expressions are not supported yet' );
|
|
710
718
|
path.broken = true;
|
|
711
719
|
path.length = 1;
|
|
@@ -729,10 +737,11 @@ function valuePathAst( ref ) {
|
|
|
729
737
|
|
|
730
738
|
// If a '-' is directly before an unsigned number, consider it part of the number;
|
|
731
739
|
// otherwise (including for '+'), represent it as extra unary prefix operator.
|
|
732
|
-
function signedExpression(
|
|
733
|
-
|
|
740
|
+
function signedExpression( args, expr ) {
|
|
741
|
+
// if (args.length !== 1) throw Error()
|
|
742
|
+
const sign = args[0];
|
|
734
743
|
const nval
|
|
735
|
-
= (
|
|
744
|
+
= (sign.val === '-' &&
|
|
736
745
|
expr && // expr may be null if `-` rule can't be parsed
|
|
737
746
|
expr.literal === 'number' &&
|
|
738
747
|
sign.location.endLine === expr.location.line &&
|
|
@@ -740,11 +749,14 @@ function signedExpression( signToken, expr ) {
|
|
|
740
749
|
(typeof expr.val === 'number'
|
|
741
750
|
? expr.val >= 0 && -expr.val
|
|
742
751
|
: !expr.val.startsWith('-') && `-${ expr.val }`)) || false;
|
|
743
|
-
if (nval === false)
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
752
|
+
if (nval === false) {
|
|
753
|
+
args.push( expr );
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
expr.val = nval;
|
|
757
|
+
--expr.location.col;
|
|
758
|
+
args[0] = expr;
|
|
759
|
+
}
|
|
748
760
|
}
|
|
749
761
|
|
|
750
762
|
// Return AST for number token `token` with optional token `sign`. Represent
|
|
@@ -765,7 +777,7 @@ function numberLiteral( token, sign, text = token.text ) {
|
|
|
765
777
|
const num = Number.parseFloat( text || '0' ); // not Number.parseInt() !
|
|
766
778
|
if (!Number.isSafeInteger(num)) {
|
|
767
779
|
if (sign == null) {
|
|
768
|
-
this.error( 'syntax-expecting-
|
|
780
|
+
this.error( 'syntax-expecting-unsigned-int', token,
|
|
769
781
|
{ '#': !text.match(/^[0-9]*$/) ? 'normal' : 'unsafe' } );
|
|
770
782
|
}
|
|
771
783
|
|
|
@@ -819,7 +831,7 @@ function quotedLiteral( token, literal ) {
|
|
|
819
831
|
location,
|
|
820
832
|
};
|
|
821
833
|
|
|
822
|
-
function atChar(i) {
|
|
834
|
+
function atChar( i ) {
|
|
823
835
|
// Is only used with single-line strings.
|
|
824
836
|
return location.col + pos + i;
|
|
825
837
|
}
|
|
@@ -843,7 +855,7 @@ function pushIdent( path, ident, prefix ) {
|
|
|
843
855
|
endLine: ident.location.line,
|
|
844
856
|
endCol: ident.location.col,
|
|
845
857
|
};
|
|
846
|
-
this.error( 'syntax-
|
|
858
|
+
this.error( 'syntax-unexpected-space', wsLocation, {}, // TODO: really Error?
|
|
847
859
|
'Expected identifier after \'@\' but found whitespace' );
|
|
848
860
|
}
|
|
849
861
|
ident.location.line = tokenLoc.line;
|
|
@@ -900,15 +912,15 @@ function addDef( art, parent, env, kind, name ) {
|
|
|
900
912
|
dictAdd( parent[env], art.name.id, art, ( duplicateName, loc ) => {
|
|
901
913
|
// do not use function(), otherwise `this` is wrong:
|
|
902
914
|
if (kind === 0) {
|
|
903
|
-
this.error( 'duplicate-argument', loc, { name: duplicateName },
|
|
915
|
+
this.error( 'syntax-duplicate-argument', loc, { name: duplicateName },
|
|
904
916
|
'Duplicate value for parameter $(NAME)' );
|
|
905
917
|
}
|
|
906
918
|
else if (kind === '') {
|
|
907
|
-
this.error( 'duplicate-excluding', loc,
|
|
908
|
-
|
|
919
|
+
this.error( 'syntax-duplicate-excluding', loc,
|
|
920
|
+
{ name: duplicateName, keyword: 'excluding' } );
|
|
909
921
|
}
|
|
910
922
|
else {
|
|
911
|
-
this.error( 'duplicate-
|
|
923
|
+
this.error( 'syntax-duplicate-property', loc, { name: duplicateName },
|
|
912
924
|
'Duplicate value for structure property $(NAME)' );
|
|
913
925
|
}
|
|
914
926
|
} );
|
|
@@ -1026,33 +1038,14 @@ function leftAssocBinaryOp( left, opToken, eToken, right, extraProp = 'quantifie
|
|
|
1026
1038
|
return { op, args: [ left, right ], location: left.location };
|
|
1027
1039
|
}
|
|
1028
1040
|
|
|
1029
|
-
// Set property `prop` of `target` to value `value`. Issue error if that
|
|
1030
|
-
// property has been set before, while mentioning the keywords previously
|
|
1031
|
-
// provided (as arguments `tokens`).
|
|
1032
|
-
function setOnce( target, prop, value, ...tokens ) {
|
|
1033
|
-
const loc = this.tokenLocation( tokens[0], tokens[tokens.length - 1] );
|
|
1034
|
-
const prev = target[prop];
|
|
1035
|
-
if (prev) {
|
|
1036
|
-
this.error( 'syntax-repeated-option', loc, { option: prev.option },
|
|
1037
|
-
'Option $(OPTION) has already been specified' );
|
|
1038
|
-
}
|
|
1039
|
-
if (typeof value === 'boolean') {
|
|
1040
|
-
if (!value)
|
|
1041
|
-
loc.value = false;
|
|
1042
|
-
value = loc;
|
|
1043
|
-
}
|
|
1044
|
-
value.option = tokens.map( t => t.text.toUpperCase() ).join(' ');
|
|
1045
|
-
target[prop] = value;
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
1041
|
function setMaxCardinality( art, token, max ) {
|
|
1049
1042
|
const location = this.tokenLocation( token );
|
|
1050
1043
|
if (!art.cardinality) {
|
|
1051
1044
|
art.cardinality = { targetMax: Object.assign( { location }, max ), location };
|
|
1052
1045
|
}
|
|
1053
1046
|
else {
|
|
1054
|
-
this.warning( 'syntax-
|
|
1055
|
-
'The target cardinality has already been specified -
|
|
1047
|
+
this.warning( 'syntax-duplicate-cardinality', location, { keyword: token.text },
|
|
1048
|
+
'The target cardinality has already been specified - ignoring $(KEYWORD)' );
|
|
1056
1049
|
}
|
|
1057
1050
|
}
|
|
1058
1051
|
|
|
@@ -1069,33 +1062,20 @@ function handleComposition( cardinality, isComposition ) {
|
|
|
1069
1062
|
}
|
|
1070
1063
|
|
|
1071
1064
|
function associationInSelectItem( art ) {
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
art.name = art.value.path[0];
|
|
1080
|
-
}
|
|
1081
|
-
else {
|
|
1082
|
-
// Use alias if provided, i.e. ignore art.value.path.
|
|
1083
|
-
this.error( 'query-unexpected-alias', art.name.location, {},
|
|
1084
|
-
'Unexpected alias for association' );
|
|
1085
|
-
}
|
|
1086
|
-
delete art.value;
|
|
1087
|
-
}
|
|
1088
|
-
else {
|
|
1089
|
-
const loc = isPath ? art.value.path[1].location : art.value.location;
|
|
1090
|
-
// If neither path nor alias are present, `query-req-name` is emitted in `populate.js`.
|
|
1091
|
-
if (isPath || art.name) {
|
|
1092
|
-
this.error( 'query-expected-identifier', loc, { '#': 'assoc' } );
|
|
1093
|
-
if (isPath)
|
|
1094
|
-
art.name = art.value.path[art.value.path.length - 1];
|
|
1095
|
-
|
|
1065
|
+
const { value } = art;
|
|
1066
|
+
const path = value?.path;
|
|
1067
|
+
// we cannot compare "just one token before `:`" because there might be annos
|
|
1068
|
+
if (path && path.length === 1 && !art.name && !art.expand && !art.inline) {
|
|
1069
|
+
const name = value.path[0];
|
|
1070
|
+
if (path.length === 1 && !name.args && !name.cardinality && !name.where) {
|
|
1071
|
+
art.name = name;
|
|
1096
1072
|
delete art.value;
|
|
1073
|
+
return art;
|
|
1097
1074
|
}
|
|
1098
1075
|
}
|
|
1076
|
+
this.error( 'syntax-unexpected-assoc', this.getCurrentToken(), {},
|
|
1077
|
+
'Unexpected association definition in select item' );
|
|
1078
|
+
return {}; // result of the association rules are written into /dev/null
|
|
1099
1079
|
}
|
|
1100
1080
|
|
|
1101
1081
|
function reportExpandInline( column, isInline ) {
|
|
@@ -1108,8 +1088,8 @@ function reportExpandInline( column, isInline ) {
|
|
|
1108
1088
|
if (isInline && !name && this._input.LT(-1).type >= this.constructor.Identifier)
|
|
1109
1089
|
token = this._input.LT(2);
|
|
1110
1090
|
this.error( 'syntax-unexpected-nested-proj', token,
|
|
1111
|
-
{
|
|
1112
|
-
|
|
1091
|
+
{ code: isInline ? '.{ ‹inline› }' : '{ ‹expand› }' },
|
|
1092
|
+
'Unexpected $(CODE); nested projections can only be used after a reference' );
|
|
1113
1093
|
// continuation semantics:
|
|
1114
1094
|
// - add elements anyway (could lead to duplicate errors as usual)
|
|
1115
1095
|
// - no errors for refs inside expand/inline, but for refs in sibling expr
|
|
@@ -1117,25 +1097,25 @@ function reportExpandInline( column, isInline ) {
|
|
|
1117
1097
|
}
|
|
1118
1098
|
if (isInline && name) {
|
|
1119
1099
|
const location = this.tokenLocation( isInline, this._input.LT(-1) );
|
|
1120
|
-
this.error( 'syntax-unexpected-alias', location, {
|
|
1121
|
-
'Unexpected alias name before
|
|
1100
|
+
this.error( 'syntax-unexpected-alias', location, { code: '.{ ‹inline› }' },
|
|
1101
|
+
'Unexpected alias name before $(CODE)' );
|
|
1122
1102
|
// continuation semantics: ignore AS
|
|
1123
1103
|
}
|
|
1124
1104
|
}
|
|
1125
1105
|
|
|
1126
1106
|
function checkTypeFacet( art, argIdent ) {
|
|
1107
|
+
// TODO: use dictAddArray or dictAdd?
|
|
1127
1108
|
const { id } = argIdent;
|
|
1128
1109
|
if (id === 'length' || id === 'scale' || id === 'precision' || id === 'srid') {
|
|
1129
1110
|
if (art[id] !== undefined) {
|
|
1130
|
-
this.error( 'syntax-duplicate-argument', argIdent.location,
|
|
1131
|
-
{ '#': 'duplicate', code: id } );
|
|
1132
1111
|
this.error( 'syntax-duplicate-argument', art[id].location,
|
|
1133
|
-
{ '#': '
|
|
1112
|
+
{ '#': 'type', name: id } );
|
|
1113
|
+
// continuation semantics: use last
|
|
1134
1114
|
}
|
|
1135
1115
|
return true;
|
|
1136
1116
|
}
|
|
1137
|
-
this.error( 'syntax-
|
|
1138
|
-
|
|
1117
|
+
this.error( 'syntax-undefined-param', argIdent.location, { name: id },
|
|
1118
|
+
'There is no type parameter called $(NAME)');
|
|
1139
1119
|
return false;
|
|
1140
1120
|
}
|
|
1141
1121
|
|