@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.
- package/CHANGELOG.md +39 -0
- package/bin/cdsc.js +5 -0
- package/bin/cdshi.js +8 -8
- package/doc/CHANGELOG_BETA.md +9 -4
- package/lib/api/validate.js +5 -0
- package/lib/base/message-registry.js +25 -1
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +0 -1
- package/lib/compiler/assert-consistency.js +2 -2
- package/lib/compiler/builtins.js +1 -1
- package/lib/compiler/checks.js +25 -6
- package/lib/compiler/define.js +24 -28
- package/lib/compiler/extend.js +11 -13
- package/lib/compiler/generate.js +3 -3
- package/lib/compiler/populate.js +13 -7
- package/lib/compiler/propagator.js +2 -2
- package/lib/compiler/resolve.js +58 -60
- package/lib/compiler/shared.js +5 -5
- package/lib/compiler/tweak-assocs.js +247 -34
- package/lib/compiler/utils.js +40 -32
- package/lib/compiler/xpr-rewrite.js +44 -58
- package/lib/edm/annotations/genericTranslation.js +4 -4
- package/lib/edm/csn2edm.js +2 -2
- package/lib/edm/edm.js +46 -21
- package/lib/edm/edmInboundChecks.js +0 -1
- package/lib/edm/edmPreprocessor.js +40 -27
- package/lib/edm/edmUtils.js +1 -1
- package/lib/gen/BaseParser.js +180 -122
- package/lib/gen/CdlParser.js +2226 -2170
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +3820 -3777
- package/lib/inspect/inspectPropagation.js +1 -1
- package/lib/json/from-csn.js +5 -3
- package/lib/json/to-csn.js +7 -10
- package/lib/language/antlrParser.js +38 -4
- package/lib/language/errorStrategy.js +1 -1
- package/lib/language/genericAntlrParser.js +4 -4
- package/lib/language/multiLineStringParser.js +1 -1
- package/lib/main.d.ts +23 -0
- package/lib/model/cloneCsn.js +22 -13
- package/lib/optionProcessor.js +7 -7
- package/lib/parsers/AstBuildingParser.js +155 -37
- package/lib/parsers/CdlGrammar.g4 +154 -81
- package/lib/parsers/Lexer.js +20 -10
- package/lib/render/toCdl.js +23 -18
- package/lib/transform/addTenantFields.js +4 -4
- package/lib/transform/db/rewriteCalculatedElements.js +11 -5
- package/lib/transform/db/transformExists.js +43 -18
- package/lib/transform/effective/main.js +1 -1
- package/lib/transform/forRelationalDB.js +8 -7
- package/lib/utils/moduleResolve.js +1 -1
- package/package.json +1 -1
- 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
|
}
|
package/lib/json/from-csn.js
CHANGED
|
@@ -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
|
-
|
|
2080
|
-
({
|
|
2079
|
+
|
|
2080
|
+
({
|
|
2081
|
+
message, error, warning, info,
|
|
2082
|
+
} = messageFunctions);
|
|
2081
2083
|
|
|
2082
2084
|
if (csnVersionZero) {
|
|
2083
2085
|
warning( 'syntax-deprecated-csn-version', location(true), {},
|
package/lib/json/to-csn.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
272
|
-
|
|
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.
|
package/lib/model/cloneCsn.js
CHANGED
|
@@ -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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
78
|
-
const sortDict =
|
|
79
|
-
|
|
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;
|
package/lib/optionProcessor.js
CHANGED
|
@@ -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(' --
|
|
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
|
-
--
|
|
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(' --
|
|
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
|
-
--
|
|
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(' --
|
|
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
|
-
--
|
|
499
|
-
will
|
|
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
|
-
|
|
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
|
-
*
|
|
307
|
+
* For annotations at the beginning of columns outside parentheses
|
|
209
308
|
*/
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
224
|
-
if (test)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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.
|
|
446
|
-
const {
|
|
447
|
-
if (
|
|
448
|
-
token.
|
|
449
|
-
return { token,
|
|
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.
|
|
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
|
-
|
|
762
|
-
return {
|
|
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
|
-
|
|
835
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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 }'`;
|