@sap/cds-compiler 2.11.2 → 2.11.4
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 +23 -2
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +3 -1
- package/bin/cdsc.js +8 -1
- package/bin/cdsv2m.js +3 -2
- package/lib/api/main.js +2 -16
- package/lib/api/options.js +3 -2
- package/lib/api/validate.js +7 -1
- package/lib/backends.js +3 -5
- package/lib/base/keywords.js +3 -2
- package/lib/base/message-registry.js +24 -8
- package/lib/base/messages.js +15 -9
- package/lib/base/optionProcessorHelper.js +1 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/unknownMagic.js +1 -1
- package/lib/compiler/assert-consistency.js +2 -2
- package/lib/compiler/builtins.js +34 -15
- package/lib/compiler/definer.js +8 -17
- package/lib/compiler/index.js +13 -25
- package/lib/compiler/resolver.js +89 -23
- package/lib/compiler/shared.js +25 -28
- package/lib/compiler/utils.js +11 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/json/to-csn.js +60 -14
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +2 -1
- package/lib/language/language.g4 +6 -3
- package/lib/main.d.ts +79 -1
- package/lib/model/csnRefs.js +11 -4
- package/lib/model/csnUtils.js +2 -107
- package/lib/model/enrichCsn.js +33 -35
- package/lib/model/revealInternalProperties.js +5 -4
- package/lib/model/sortViews.js +8 -1
- package/lib/optionProcessor.js +5 -1
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/toHdbcds.js +2 -7
- package/lib/render/toSql.js +16 -11
- package/lib/transform/db/applyTransformations.js +189 -0
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/transformExists.js +9 -0
- package/lib/transform/db/views.js +89 -42
- package/lib/transform/forHanaNew.js +34 -12
- package/lib/transform/translateAssocsToJoins.js +3 -3
- package/lib/utils/file.js +6 -2
- package/package.json +1 -1
- package/lib/transform/db/helpers.js +0 -58
package/lib/compiler/index.js
CHANGED
|
@@ -26,7 +26,6 @@ const resolve = require('./resolver');
|
|
|
26
26
|
const propagator = require('./propagator');
|
|
27
27
|
const check = require('./checks');
|
|
28
28
|
|
|
29
|
-
|
|
30
29
|
const { emptyWeakLocation } = require('../base/location');
|
|
31
30
|
const { createMessageFunctions, deduplicateMessages } = require('../base/messages');
|
|
32
31
|
const { promiseAllDoNotRejectImmediately } = require('../base/node-helpers');
|
|
@@ -144,36 +143,25 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
|
|
|
144
143
|
});
|
|
145
144
|
|
|
146
145
|
// Read file `filename` and parse its content, return messages
|
|
147
|
-
function readAndParse( filename ) {
|
|
146
|
+
async function readAndParse( filename ) {
|
|
147
|
+
const { sources } = a;
|
|
148
148
|
if ( filename === false ) // module which has not been found
|
|
149
149
|
return [];
|
|
150
|
-
const rel =
|
|
150
|
+
const rel = sources[filename] || path.relative( dir, filename );
|
|
151
151
|
if (typeof rel === 'object') // already parsed
|
|
152
152
|
return []; // no further dependency processing
|
|
153
153
|
// no parallel readAndParse with same resolved filename should read the file,
|
|
154
|
-
// also ensure deterministic sequence in
|
|
155
|
-
|
|
154
|
+
// also ensure deterministic sequence in sources:
|
|
155
|
+
sources[filename] = { location: { file: rel } };
|
|
156
156
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
a.sources[filename] = ast;
|
|
166
|
-
ast.location = { file: rel };
|
|
167
|
-
ast.dirname = path.dirname( filename );
|
|
168
|
-
assertConsistency( ast, options );
|
|
169
|
-
fulfill( ast );
|
|
170
|
-
}
|
|
171
|
-
catch (e) {
|
|
172
|
-
reject( e );
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
});
|
|
157
|
+
const source = await cdsFs( fileCache, options.traceFs ).readFileAsync( filename, 'utf8' );
|
|
158
|
+
const ast = parseX( source, rel, options, model.$messageFunctions );
|
|
159
|
+
sources[filename] = ast;
|
|
160
|
+
ast.location = { file: rel };
|
|
161
|
+
ast.dirname = path.dirname( filename );
|
|
162
|
+
assertConsistency( ast, options );
|
|
163
|
+
|
|
164
|
+
return ast;
|
|
177
165
|
}
|
|
178
166
|
|
|
179
167
|
// Combine the parse results (if there are not file IO errors)
|
package/lib/compiler/resolver.js
CHANGED
|
@@ -52,6 +52,7 @@ const { kindProperties } = require('./base');
|
|
|
52
52
|
const {
|
|
53
53
|
pushLink,
|
|
54
54
|
setLink,
|
|
55
|
+
annotationVal,
|
|
55
56
|
augmentPath,
|
|
56
57
|
splitIntoPath,
|
|
57
58
|
linkToOrigin,
|
|
@@ -359,13 +360,13 @@ function resolve( model ) {
|
|
|
359
360
|
while (struct.kind === 'element')
|
|
360
361
|
struct = struct._parent;
|
|
361
362
|
if (struct.kind === 'select') {
|
|
362
|
-
message( '
|
|
363
|
+
message( 'type-unexpected-typeof', [ ref.location, user ],
|
|
363
364
|
{ keyword: 'type of', '#': struct.kind } );
|
|
364
365
|
// we actually refer to an element in _combined; TODO: return null if
|
|
365
366
|
// not configurable; would produce illegal CSN with sub queries in FROM
|
|
366
367
|
}
|
|
367
368
|
else if (struct !== user._main) {
|
|
368
|
-
message( '
|
|
369
|
+
message( 'type-unexpected-typeof', [ ref.location, user ],
|
|
369
370
|
{ keyword: 'type of', '#': struct.kind } );
|
|
370
371
|
return setProp( ref, '_artifact', null );
|
|
371
372
|
}
|
|
@@ -413,6 +414,7 @@ function resolve( model ) {
|
|
|
413
414
|
for (const view of resolveChain.reverse()) {
|
|
414
415
|
if (view._status !== '_query' ) { // not already resolved
|
|
415
416
|
setProp( view, '_status', '_query' );
|
|
417
|
+
// must be run in order “sub query in FROM first”:
|
|
416
418
|
traverseQueryPost( view.query, null, populateQuery );
|
|
417
419
|
if (view.elements$) // specified elements
|
|
418
420
|
mergeSpecifiedElements( view );
|
|
@@ -719,35 +721,69 @@ function resolve( model ) {
|
|
|
719
721
|
else if (exposed.length === 1) {
|
|
720
722
|
target = exposed[0];
|
|
721
723
|
}
|
|
722
|
-
else {
|
|
723
|
-
|
|
724
|
-
|
|
724
|
+
else if (elem === assoc) {
|
|
725
|
+
// `assoc: Association to ModelEntity`: user-provided target is to be auto-redirected
|
|
726
|
+
warning( 'type-ambiguous-target',
|
|
727
|
+
[ elem.target.location, elem ],
|
|
725
728
|
{
|
|
726
729
|
target,
|
|
727
|
-
|
|
728
|
-
art: definitionScope( target ),
|
|
730
|
+
// art: definitionScope( target ), - TODO extra debug info in message
|
|
729
731
|
sorted_arts: exposed,
|
|
730
|
-
'#': ( elemScope !== true ? 'std' : 'scoped' ),
|
|
731
732
|
}, {
|
|
732
733
|
// eslint-disable-next-line max-len
|
|
733
|
-
std: '
|
|
734
|
+
std: 'Replace target $(TARGET) by one of $(SORTED_ARTS); can\'t auto-redirect this association if multiple projections exist in this service',
|
|
734
735
|
// eslint-disable-next-line max-len
|
|
735
|
-
|
|
736
|
+
two: 'Replace target $(TARGET) by $(SORTED_ARTS) or $(SECOND); can\'t auto-redirect this association if multiple projections exist in this service',
|
|
736
737
|
});
|
|
738
|
+
// continuation semantics: no auto-redirection
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
// referred (and probably inferred) assoc (without a user-provided target at that place)
|
|
742
|
+
// HINT: consider bin/cdsv2m.js when changing the following message text
|
|
743
|
+
// No grouped and sub messages yet (TODO v3): mention at all target places with all assocs
|
|
744
|
+
const withAnno = annotationVal( exposed[0]['@cds.redirection.target'] );
|
|
745
|
+
for (const proj of exposed) {
|
|
746
|
+
// TODO: def-ambiguous-target (just v3, as the current is infamous and used in options),
|
|
747
|
+
message( 'redirected-implicitly-ambiguous',
|
|
748
|
+
[ weakLocation( proj.location ), proj ],
|
|
749
|
+
{
|
|
750
|
+
'#': withAnno && 'justOne',
|
|
751
|
+
target,
|
|
752
|
+
art: elem,
|
|
753
|
+
// art: definitionScope( target ), - TODO extra debug info in message
|
|
754
|
+
anno: 'cds.redirection.target',
|
|
755
|
+
sorted_arts: exposed,
|
|
756
|
+
}, {
|
|
757
|
+
// eslint-disable-next-line max-len
|
|
758
|
+
std: 'Add $(ANNO) to one of $(SORTED_ARTS) to select the entity as redirection target for $(TARGET) in this service; can\'t auto-redirect $(ART) otherwise',
|
|
759
|
+
// eslint-disable-next-line max-len
|
|
760
|
+
two: 'Add $(ANNO) to either $(SORTED_ARTS) or $(SECOND) to select the entity as redirection target for $(TARGET) in this service; can\'t auto-redirect $(ART) otherwise',
|
|
761
|
+
// eslint-disable-next-line max-len
|
|
762
|
+
justOne: 'Remove $(ANNO) from all but one of $(SORTED_ARTS) to have a unique redirection target for $(TARGET) in this service; can\'t auto-redirect $(ART) otherwise',
|
|
763
|
+
} );
|
|
764
|
+
}
|
|
737
765
|
// continuation semantics: no implicit redirections
|
|
738
766
|
}
|
|
739
767
|
}
|
|
740
768
|
if (elem.target) { // redirection for Association to / Composition of
|
|
741
769
|
if (elem.target._artifact === target) // no change (due to no implicit redirection)
|
|
742
770
|
return true;
|
|
771
|
+
const type = resolvePath( elem.type, 'type', elem ); // cds.Association or cds.Composition
|
|
743
772
|
const origin = {
|
|
744
|
-
kind: elem.kind,
|
|
773
|
+
kind: elem.kind, // necessary for rewrite, '$user-provided' would be best
|
|
745
774
|
name: elem.name,
|
|
775
|
+
type: {
|
|
776
|
+
path: [ { id: type.name.absolute, location: elem.type.location } ],
|
|
777
|
+
scope: 'global',
|
|
778
|
+
location: elem.type.location,
|
|
779
|
+
},
|
|
746
780
|
target: elem.target,
|
|
747
781
|
$inferred: 'REDIRECTED',
|
|
748
782
|
location: elem.target.location,
|
|
749
783
|
};
|
|
750
784
|
setLink( elem, origin, '_origin' );
|
|
785
|
+
setLink( elem.type, type, '_artifact' );
|
|
786
|
+
setLink( origin, elem, '_outer' );
|
|
751
787
|
setLink( origin, elem._parent, '_parent' );
|
|
752
788
|
if (elem._main) // remark: the param `elem` can also be a type
|
|
753
789
|
setLink( origin, elem._main, '_main' );
|
|
@@ -755,11 +791,11 @@ function resolve( model ) {
|
|
|
755
791
|
setLink( origin, elem._block, '_block' );
|
|
756
792
|
if (elem.foreignKeys) {
|
|
757
793
|
origin.foreignKeys = elem.foreignKeys;
|
|
758
|
-
delete elem.foreignKeys;
|
|
794
|
+
delete elem.foreignKeys; // will be rewritten
|
|
759
795
|
}
|
|
760
796
|
if (elem.on) {
|
|
761
797
|
origin.on = elem.on;
|
|
762
|
-
delete elem.on;
|
|
798
|
+
delete elem.on; // will be rewritten
|
|
763
799
|
}
|
|
764
800
|
}
|
|
765
801
|
elem.target = {
|
|
@@ -818,10 +854,7 @@ function resolve( model ) {
|
|
|
818
854
|
target._descendants[service.name.absolute] ||
|
|
819
855
|
[],
|
|
820
856
|
elemScope, target );
|
|
821
|
-
const preferred = descendants.filter( ( d )
|
|
822
|
-
const anno = d['@cds.redirection.target'];
|
|
823
|
-
return anno && (anno.val === undefined || anno.val );
|
|
824
|
-
} );
|
|
857
|
+
const preferred = descendants.filter( d => annotationVal( d['@cds.redirection.target'] ) );
|
|
825
858
|
const exposed = preferred.length ? preferred : descendants;
|
|
826
859
|
if (exposed.length < 2)
|
|
827
860
|
return exposed || [];
|
|
@@ -860,7 +893,7 @@ function resolve( model ) {
|
|
|
860
893
|
// Need to filter out auto-exposed, otherwise the behavior is
|
|
861
894
|
// processing-order dependent (not storing the autoexposed in
|
|
862
895
|
// _descendents would only be an alternative w/o recompilation)
|
|
863
|
-
return descendants.filter( d => !d['@cds.autoexposed'] );
|
|
896
|
+
return descendants.filter( d => !annotationVal( d['@cds.autoexposed'] ) );
|
|
864
897
|
}
|
|
865
898
|
// try scope as target first, even if it has @cds.redirection.target: false
|
|
866
899
|
if (isDirectProjection( elemScope, target ))
|
|
@@ -991,6 +1024,7 @@ function resolve( model ) {
|
|
|
991
1024
|
$inferred: 'autoexposed',
|
|
992
1025
|
'@cds.autoexposed': {
|
|
993
1026
|
name: { path: [ { id: 'cds.autoexposed', location } ], location },
|
|
1027
|
+
$inferred: 'autoexposed',
|
|
994
1028
|
},
|
|
995
1029
|
};
|
|
996
1030
|
// TODO: do we need to tag the generated entity with elemScope = 'auto'?
|
|
@@ -2059,6 +2093,7 @@ function resolve( model ) {
|
|
|
2059
2093
|
dependsOnSilent(art, key);
|
|
2060
2094
|
}
|
|
2061
2095
|
});
|
|
2096
|
+
obj.foreignKeys[$inferred] = 'keys';
|
|
2062
2097
|
}
|
|
2063
2098
|
|
|
2064
2099
|
function addForeignKeyNavigations( art ) {
|
|
@@ -2227,8 +2262,6 @@ function resolve( model ) {
|
|
|
2227
2262
|
// Only top-level queries and sub queries in FROM
|
|
2228
2263
|
|
|
2229
2264
|
function rewriteSimple( art ) {
|
|
2230
|
-
// If we have a proper seperation of view elements and elements of the
|
|
2231
|
-
// primary query, we can delete this function.
|
|
2232
2265
|
// return;
|
|
2233
2266
|
if (!art.includes && !art.query) {
|
|
2234
2267
|
// console.log(message( null, art.location, art, {target:art._target},
|
|
@@ -2241,7 +2274,7 @@ function resolve( model ) {
|
|
|
2241
2274
|
}
|
|
2242
2275
|
|
|
2243
2276
|
function rewriteView( view ) {
|
|
2244
|
-
|
|
2277
|
+
traverseQueryExtra( view, ( query ) => {
|
|
2245
2278
|
forEachGeneric( query, 'elements', rewriteAssociation );
|
|
2246
2279
|
} );
|
|
2247
2280
|
if (view.includes) // entities with structure includes:
|
|
@@ -2249,6 +2282,7 @@ function resolve( model ) {
|
|
|
2249
2282
|
}
|
|
2250
2283
|
|
|
2251
2284
|
// Check explicit ON / keys with REDIRECTED TO
|
|
2285
|
+
// TODO: run on all queries, but this is potentially incompatible
|
|
2252
2286
|
function rewriteViewCheck( view ) {
|
|
2253
2287
|
traverseQueryPost( view.query, false, ( query ) => {
|
|
2254
2288
|
forEachGeneric( query, 'elements', rewriteAssociationCheck );
|
|
@@ -2474,7 +2508,7 @@ function resolve( model ) {
|
|
|
2474
2508
|
resolveExpr( cond, rewriteExpr, elem, nav.tableAlias );
|
|
2475
2509
|
}
|
|
2476
2510
|
else {
|
|
2477
|
-
// TODO: support that
|
|
2511
|
+
// TODO: support that, now that the ON condition is rewritten in the right order
|
|
2478
2512
|
error( null, [ elem.value.location, elem ],
|
|
2479
2513
|
'Selecting unmanaged associations from a sub query is not supported' );
|
|
2480
2514
|
}
|
|
@@ -2880,8 +2914,10 @@ function navProjection( navigation, preferred ) {
|
|
|
2880
2914
|
: navigation._projections[0] || null;
|
|
2881
2915
|
}
|
|
2882
2916
|
|
|
2883
|
-
// Query tree post-order traversal - called for everything which
|
|
2917
|
+
// Query tree post-order traversal - called for everything which contributes to the query
|
|
2918
|
+
// i.e. is necessary to calculate the elements of the query
|
|
2884
2919
|
// except "real ones": operands of UNION etc, JOIN with ON, and sub queries in FROM
|
|
2920
|
+
// NOTE: does not run on non-referred sub queries! Consider using ‹main›.$queries instead!
|
|
2885
2921
|
function traverseQueryPost( query, simpleOnly, callback ) {
|
|
2886
2922
|
if (!query) // parser error
|
|
2887
2923
|
return;
|
|
@@ -2919,4 +2955,34 @@ function traverseQueryPost( query, simpleOnly, callback ) {
|
|
|
2919
2955
|
// else: with parse error (`select from <EOF>`, `select distinct from;`)
|
|
2920
2956
|
}
|
|
2921
2957
|
|
|
2958
|
+
// Call callback on all queries in dependency order, i.e. starting with query Q
|
|
2959
|
+
// 1. sub queries in FROM sources of Q
|
|
2960
|
+
// 2. Q itself, except if non-referred query, but with right UNION parts
|
|
2961
|
+
// 3. sub queries in ON in FROM of Q
|
|
2962
|
+
// 4. sub queries in columns, WHERE, HAVING
|
|
2963
|
+
function traverseQueryExtra( main, callback ) {
|
|
2964
|
+
if (!main.$queries)
|
|
2965
|
+
return;
|
|
2966
|
+
// with a top-level UNION, $queries[0] is just the left
|
|
2967
|
+
traverseQueryPost( main.query, false, (q) => { // also with right of UNION (to be compatible)
|
|
2968
|
+
setProp( q, '_status', 'extra' );
|
|
2969
|
+
callback( q );
|
|
2970
|
+
} );
|
|
2971
|
+
for (const query of main.$queries.slice(1)) {
|
|
2972
|
+
if (query._status === 'extra' || query._parent.kind === '$tableAlias')
|
|
2973
|
+
continue; // if parent is alias, query is FROM source -> run by traverseQueryPost
|
|
2974
|
+
// we are now in the top-level (parent is entity) or a non-referred query (parent is query)
|
|
2975
|
+
setProp( query, '_status', 'extra' ); // do not call callback() in non-referred query
|
|
2976
|
+
// console.log( 'A:', query.name,query._status)
|
|
2977
|
+
traverseQueryPost( query, null, (q) => {
|
|
2978
|
+
if (q._status !== 'extra') {
|
|
2979
|
+
// console.log( 'T:', q.name)
|
|
2980
|
+
setProp( q, '_status', 'extra' );
|
|
2981
|
+
callback( q );
|
|
2982
|
+
}
|
|
2983
|
+
// else console.log( 'E:', q.name)
|
|
2984
|
+
} );
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2922
2988
|
module.exports = resolve;
|
package/lib/compiler/shared.js
CHANGED
|
@@ -7,7 +7,7 @@ const { searchName } = require('../base/messages');
|
|
|
7
7
|
const { dictAddArray } = require('../base/dictionaries');
|
|
8
8
|
const { setProp } = require('../base/model');
|
|
9
9
|
|
|
10
|
-
const { setLink, dependsOn } = require('./utils');
|
|
10
|
+
const { setLink, dependsOn, pathName } = require('./utils');
|
|
11
11
|
|
|
12
12
|
function artifactsEnv( art ) {
|
|
13
13
|
return art._subArtifacts || Object.create(null);
|
|
@@ -611,29 +611,24 @@ function fns( model ) {
|
|
|
611
611
|
|
|
612
612
|
const fn = (spec.envFn && artItemsCount >= 0) ? spec.envFn : VolatileFns.environment;
|
|
613
613
|
const env = fn( art, item.location, user, spec.assoc );
|
|
614
|
-
|
|
615
|
-
// do not check any elements of the path, e.g. $session - but still don't return path-head
|
|
616
|
-
if (art && art.$uncheckedElements) {
|
|
617
|
-
if (env && env[item.id]) // something like $user.id/$user.locale
|
|
618
|
-
return env[item.id];
|
|
619
|
-
|
|
620
|
-
// $user.foo - build our own valid path step obj
|
|
621
|
-
// Important: Don't directly modify item!
|
|
622
|
-
const obj = {
|
|
623
|
-
location: item.location,
|
|
624
|
-
kind: 'builtin',
|
|
625
|
-
name: { id: item.id, element: path.map(p => p.id).join('.') },
|
|
626
|
-
};
|
|
627
|
-
setLink(obj, art, '_parent');
|
|
628
|
-
return obj;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
614
|
const sub = setLink( item, env && env[item.id] );
|
|
632
615
|
|
|
633
|
-
if (!sub)
|
|
634
|
-
|
|
635
|
-
|
|
616
|
+
if (!sub) {
|
|
617
|
+
// element was not found in environment
|
|
618
|
+
if (sub === 0)
|
|
619
|
+
return 0;
|
|
620
|
+
if (art.$uncheckedElements) { // magic variable / replacement variable
|
|
621
|
+
signalNotFound( 'ref-unknown-var', [ item.location, user ], [ env ],
|
|
622
|
+
{ id: path.map(n => n.id).join('.') } );
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
errorNotFound( item, env );
|
|
626
|
+
}
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
else if (Array.isArray(sub)) { // redefinitions
|
|
636
630
|
return false;
|
|
631
|
+
}
|
|
637
632
|
|
|
638
633
|
if (nav) { // we have already "pseudo-followed" a managed association
|
|
639
634
|
// We currently rely on the check that targetElement references do
|
|
@@ -715,7 +710,7 @@ function fns( model ) {
|
|
|
715
710
|
{ param: 'Entity $(ART) has no parameter $(MEMBER)' } );
|
|
716
711
|
}
|
|
717
712
|
else {
|
|
718
|
-
signalNotFound( 'ref-undefined-element', [ item.location, user ],
|
|
713
|
+
signalNotFound( spec.undefinedDef || 'ref-undefined-element', [ item.location, user ],
|
|
719
714
|
[ env ], { art: searchName( art, item.id, 'element' ) } );
|
|
720
715
|
}
|
|
721
716
|
return null;
|
|
@@ -814,7 +809,7 @@ function fns( model ) {
|
|
|
814
809
|
setProp( a, '_block', block );
|
|
815
810
|
a.$priority = priority;
|
|
816
811
|
if (construct !== art)
|
|
817
|
-
|
|
812
|
+
addAnnotation( art, annoProp, a );
|
|
818
813
|
}
|
|
819
814
|
}
|
|
820
815
|
}
|
|
@@ -865,15 +860,17 @@ function fns( model ) {
|
|
|
865
860
|
// TODO: _parent, _main is set later (if we have ElementRef), or do we
|
|
866
861
|
// set _artifact?
|
|
867
862
|
anno.$priority = priority;
|
|
868
|
-
|
|
863
|
+
addAnnotation( art, annoProp, anno );
|
|
869
864
|
}
|
|
870
865
|
}
|
|
871
866
|
}
|
|
872
867
|
|
|
873
|
-
//
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
868
|
+
// Add annotation to definition - overwriting $inferred annos
|
|
869
|
+
function addAnnotation( art, annoProp, anno ) {
|
|
870
|
+
const old = art[annoProp];
|
|
871
|
+
if (old && old.$inferred)
|
|
872
|
+
delete art[annoProp];
|
|
873
|
+
dictAddArray( art, annoProp, anno );
|
|
877
874
|
}
|
|
878
875
|
|
|
879
876
|
module.exports = {
|
package/lib/compiler/utils.js
CHANGED
|
@@ -181,6 +181,16 @@ function withAssociation( ref, test = testFunctionPlaceholder, alsoTestLast = fa
|
|
|
181
181
|
return false;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Return string 'A.B.C' for parsed source `A.B.C` (is vector of ids with
|
|
186
|
+
* locations).
|
|
187
|
+
*
|
|
188
|
+
* @param {XSN.Path} path
|
|
189
|
+
*/
|
|
190
|
+
function pathName(path) {
|
|
191
|
+
return (path.broken) ? '' : path.map( id => id.id ).join('.');
|
|
192
|
+
}
|
|
193
|
+
|
|
184
194
|
/**
|
|
185
195
|
* Generates an XSN path out of the given name. Path segments are delimited by a dot.
|
|
186
196
|
* Each segment will have the given location assigned.
|
|
@@ -215,6 +225,7 @@ module.exports = {
|
|
|
215
225
|
setMemberParent,
|
|
216
226
|
storeExtension,
|
|
217
227
|
withAssociation,
|
|
228
|
+
pathName,
|
|
218
229
|
augmentPath,
|
|
219
230
|
splitIntoPath,
|
|
220
231
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
cde5224056abde61cc2eb0b1b30bf694
|
package/lib/json/to-csn.js
CHANGED
|
@@ -37,6 +37,14 @@ let dictionaryPrototype = null;
|
|
|
37
37
|
// stored with symbols as keys, as we do not want to disallow any key name:
|
|
38
38
|
const $inferred = Symbol.for('cds.$inferred');
|
|
39
39
|
|
|
40
|
+
// XSN $inferred values mapped to Universal CSN $generated values:
|
|
41
|
+
const inferredAsGenerated = {
|
|
42
|
+
autoexposed: 'exposed',
|
|
43
|
+
'localized-entity': 'localized',
|
|
44
|
+
localized: 'localized', // on elements (texts, localized)
|
|
45
|
+
'composition-entity': 'composed', // ('aspect-composition' on element not in CSN)
|
|
46
|
+
};
|
|
47
|
+
|
|
40
48
|
// IMPORTANT: the order of these properties determine the order of properties
|
|
41
49
|
// in the resulting CSN !!! Also check const `csnPropertyNames`.
|
|
42
50
|
const transformers = {
|
|
@@ -66,7 +74,7 @@ const transformers = {
|
|
|
66
74
|
precision: value,
|
|
67
75
|
scale: value,
|
|
68
76
|
srid: value,
|
|
69
|
-
cardinality
|
|
77
|
+
cardinality, // also in pathItem: after 'id', before 'where'
|
|
70
78
|
targetAspect,
|
|
71
79
|
target,
|
|
72
80
|
foreignKeys,
|
|
@@ -609,10 +617,21 @@ function set( prop, csn, node ) {
|
|
|
609
617
|
}
|
|
610
618
|
|
|
611
619
|
function targetAspect( val, csn, node ) {
|
|
620
|
+
if (universalCsn) {
|
|
621
|
+
if (val.$inferred)
|
|
622
|
+
return undefined;
|
|
623
|
+
if (node.target) {
|
|
624
|
+
csn.$origin = { type: 'cds.Composition' };
|
|
625
|
+
if (node.cardinality)
|
|
626
|
+
csn.$origin.cardinality = standard( node.cardinality );
|
|
627
|
+
csn.$origin.target = (val.elements) ? standard( val ) : artifactRef( val, true );
|
|
628
|
+
return undefined;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
612
631
|
const ta = (val.elements)
|
|
613
632
|
? addLocation( val.location, standard( val ) )
|
|
614
633
|
: artifactRef( val, true );
|
|
615
|
-
if (!gensrcFlavor || node.target && !node.target.$inferred)
|
|
634
|
+
if (!gensrcFlavor && !universalCsn || node.target && !node.target.$inferred)
|
|
616
635
|
return ta;
|
|
617
636
|
// For compatibility, put aspect in 'target' with parse.cdl and csn flavor 'gensrc'
|
|
618
637
|
csn.target = ta;
|
|
@@ -662,12 +681,7 @@ function enumDict( dict, csn, node ) {
|
|
|
662
681
|
}
|
|
663
682
|
|
|
664
683
|
function enumerableQueryElements( select ) {
|
|
665
|
-
|
|
666
|
-
return false;
|
|
667
|
-
if (select.orderBy || select.$orderBy)
|
|
668
|
-
return true;
|
|
669
|
-
const alias = select._parent;
|
|
670
|
-
return alias.query && (alias.query._leadingQuery || alias.query) === select;
|
|
684
|
+
return (universalCsn && select !== select._main._leadingQuery);
|
|
671
685
|
}
|
|
672
686
|
|
|
673
687
|
// Should we render the elements? (and items?)
|
|
@@ -746,7 +760,8 @@ function ignore() { /* no-op: ignore property */ }
|
|
|
746
760
|
|
|
747
761
|
function location( loc, csn, xsn ) {
|
|
748
762
|
if (xsn.kind && xsn.kind.charAt(0) !== '$' && xsn.kind !== 'select' &&
|
|
749
|
-
(!xsn.$inferred || !xsn._main)
|
|
763
|
+
(!xsn.$inferred || !xsn._main) &&
|
|
764
|
+
xsn.$inferred !== 'REDIRECTED') { // TODO: also for 'select'
|
|
750
765
|
// Also include $location for elements in queries (if not via '*')
|
|
751
766
|
addLocation( xsn.name && xsn.name.location || loc, csn );
|
|
752
767
|
}
|
|
@@ -802,8 +817,10 @@ function dictionary( dict, keys, prop ) {
|
|
|
802
817
|
}
|
|
803
818
|
|
|
804
819
|
function foreignKeys( dict, csn, node ) {
|
|
805
|
-
if (universalCsn
|
|
806
|
-
|
|
820
|
+
if (universalCsn) {
|
|
821
|
+
if (!target( node.target, csn, node ) || dict[$inferred] === 'keys')
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
807
824
|
if (gensrcFlavor && node._origin && node._origin.$inferred === 'REDIRECTED')
|
|
808
825
|
dict = node._origin.foreignKeys;
|
|
809
826
|
const keys = [];
|
|
@@ -836,8 +853,8 @@ function definition( art, _csn, _node, prop ) {
|
|
|
836
853
|
delete c.elements;
|
|
837
854
|
c.returns = { elements: elems };
|
|
838
855
|
}
|
|
839
|
-
|
|
840
|
-
|
|
856
|
+
// precondition already fulfilled: art.kind !== 'key'
|
|
857
|
+
addOrigin( c, art, art._origin );
|
|
841
858
|
return c;
|
|
842
859
|
}
|
|
843
860
|
|
|
@@ -979,6 +996,8 @@ function originRef( art, user ) {
|
|
|
979
996
|
}
|
|
980
997
|
|
|
981
998
|
function kind( k, csn, node ) {
|
|
999
|
+
if (node.$inferred === 'REDIRECTED')
|
|
1000
|
+
return;
|
|
982
1001
|
if (k === 'annotate' || k === 'extend') {
|
|
983
1002
|
// We just use `name.absolute` because it is very likely a "constructed"
|
|
984
1003
|
// extensions. The CSN parser must produce name.path like for other refs.
|
|
@@ -987,20 +1006,47 @@ function kind( k, csn, node ) {
|
|
|
987
1006
|
else if (k === 'extend')
|
|
988
1007
|
csn.kind = k;
|
|
989
1008
|
}
|
|
1009
|
+
else if (k === 'action' && node._main && universalCsn && node.$inferred) {
|
|
1010
|
+
// Universal CSN: do not mention kind: 'action' on expanded action
|
|
1011
|
+
}
|
|
990
1012
|
else if (![
|
|
991
1013
|
'element', 'key', 'param', 'enum', 'select', '$join',
|
|
992
1014
|
'$tableAlias', 'annotation', 'mixin',
|
|
993
1015
|
].includes(k)) {
|
|
994
1016
|
csn.kind = k;
|
|
995
1017
|
}
|
|
1018
|
+
const generated = universalCsn && inferredAsGenerated[node.$inferred];
|
|
1019
|
+
if (typeof generated === 'string')
|
|
1020
|
+
csn.$generated = generated;
|
|
996
1021
|
}
|
|
997
1022
|
|
|
998
1023
|
function type( node, csn, xsn ) {
|
|
999
|
-
if (universalCsn
|
|
1024
|
+
if (!universalCsn)
|
|
1025
|
+
return artifactRef( node, !node.$extra );
|
|
1026
|
+
if (node.$inferred)
|
|
1027
|
+
return undefined;
|
|
1028
|
+
if (xsn._origin) {
|
|
1029
|
+
if (xsn._origin.$inferred === 'REDIRECTED') { // auto-redirected user-provided target
|
|
1030
|
+
csn.$origin = definition( xsn._origin );
|
|
1031
|
+
return undefined;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
else if ( xsn.targetAspect && xsn.target ) {
|
|
1035
|
+
// type moved to $origin: { type: … }
|
|
1000
1036
|
return undefined;
|
|
1037
|
+
}
|
|
1001
1038
|
return artifactRef( node, !node.$extra );
|
|
1002
1039
|
}
|
|
1003
1040
|
|
|
1041
|
+
function cardinality( node, csn, xsn ) {
|
|
1042
|
+
if (!universalCsn)
|
|
1043
|
+
return standard( node );
|
|
1044
|
+
// cardinality might be moved to $origin: { cardinality: … }
|
|
1045
|
+
if (node.$inferred || xsn.targetAspect && !xsn.targetAspect.$inferred && xsn.target)
|
|
1046
|
+
return undefined;
|
|
1047
|
+
return standard( node );
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1004
1050
|
function artifactRef( node, terse ) {
|
|
1005
1051
|
// When called as transformer function, a CSN node is provided as argument
|
|
1006
1052
|
// for `terse`, i.e. it is usually truthy, except for FROM
|
|
@@ -114,9 +114,10 @@ function sync( recognizer ) {
|
|
|
114
114
|
}
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
|
-
// TODO: expected token is identifier, current is KEYWORD
|
|
118
117
|
|
|
119
118
|
if (nextTokens.contains(antlr4.Token.EPSILON)) {
|
|
119
|
+
// when exiting a (innermost) rule, remember the state to make
|
|
120
|
+
// getExpectedTokensForMessage() calculate the full "expected set"
|
|
120
121
|
if (recognizer.$nextTokensToken !== token) {
|
|
121
122
|
// console.log('SET:',token.type,recognizer.state,recognizer.$nextTokensToken && recognizer.$nextTokensToken.type)
|
|
122
123
|
recognizer.$nextTokensToken = token;
|
|
@@ -126,13 +127,29 @@ function sync( recognizer ) {
|
|
|
126
127
|
return;
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
// Expected token is identifier, current is (reserved) KEYWORD:
|
|
131
|
+
// TODO: do not use this if "close enough" (1 char diff) to a keyword in nextTokens
|
|
132
|
+
//
|
|
133
|
+
// NOTE: it is important to do this only if EPSILON is not in `nextTokens`,
|
|
134
|
+
// which means that we cannot bring the better special syntax-fragile-ident
|
|
135
|
+
// in all cases. Reason: high performance impact of the alternative,
|
|
136
|
+
// i.e. calling method Parser#isExpectedToken() = invoking the ATN
|
|
137
|
+
// interpreter to see behind EPSILON.
|
|
138
|
+
const identType = recognizer.constructor.Identifier;
|
|
139
|
+
if (keywordRegexp.test( token.text ) && nextTokens.contains( identType )) {
|
|
140
|
+
recognizer.message( 'syntax-fragile-ident', token, { id: token.text, delimited: token.text },
|
|
141
|
+
'$(ID) is a reserved name here - write $(DELIMITED) instead if you want to use it' );
|
|
142
|
+
token.type = identType; // make next ANTLR decision assume identifier
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
129
146
|
if (recognizer._ctx._sync === 'nop')
|
|
130
147
|
return;
|
|
131
148
|
switch (s.stateType) {
|
|
132
|
-
case ATNState.BLOCK_START:
|
|
133
|
-
case ATNState.STAR_BLOCK_START:
|
|
134
|
-
case ATNState.PLUS_BLOCK_START:
|
|
135
|
-
case ATNState.STAR_LOOP_ENTRY:
|
|
149
|
+
case ATNState.BLOCK_START: // 3
|
|
150
|
+
case ATNState.STAR_BLOCK_START: // 5
|
|
151
|
+
case ATNState.PLUS_BLOCK_START: // 4
|
|
152
|
+
case ATNState.STAR_LOOP_ENTRY: // 10
|
|
136
153
|
// report error and recover if possible
|
|
137
154
|
if ( token.text !== '}' && // do not just delete a '}'
|
|
138
155
|
this.singleTokenDeletion(recognizer) !== null) { // also calls reportUnwantedToken
|
|
@@ -145,8 +162,8 @@ function sync( recognizer ) {
|
|
|
145
162
|
}
|
|
146
163
|
throw new InputMismatchException(recognizer);
|
|
147
164
|
|
|
148
|
-
case ATNState.PLUS_LOOP_BACK:
|
|
149
|
-
case ATNState.STAR_LOOP_BACK: {
|
|
165
|
+
case ATNState.PLUS_LOOP_BACK: // 11
|
|
166
|
+
case ATNState.STAR_LOOP_BACK: { // 9
|
|
150
167
|
// TODO: do not delete a '}'
|
|
151
168
|
this.reportUnwantedToken(recognizer);
|
|
152
169
|
const expecting = new IntervalSet.IntervalSet();
|
|
@@ -425,7 +442,8 @@ function getExpectedTokensForMessage( recognizer, offendingToken, deadEnds ) {
|
|
|
425
442
|
}
|
|
426
443
|
else if (offendingToken && recognizer.$nextTokensContext &&
|
|
427
444
|
offendingToken === recognizer.$nextTokensToken) {
|
|
428
|
-
//
|
|
445
|
+
// Before exiting a rule, we had a state (via sync()) with a bigger
|
|
446
|
+
// "expecting set" for the same token
|
|
429
447
|
ll1._LOOK( atn.states[recognizer.$nextTokensState], null,
|
|
430
448
|
predictionContext( atn, recognizer.$nextTokensContext ),
|
|
431
449
|
expected, lookBusy, calledRules, true, true );
|
|
@@ -320,11 +320,12 @@ function multiLineTokenLocation(token, val) {
|
|
|
320
320
|
return undefined;
|
|
321
321
|
|
|
322
322
|
// Count the number of newlines in the token.
|
|
323
|
-
// TODO: I want to avoid a substring, that's why I don't use RegEx here
|
|
324
323
|
const source = token.source[1].data;
|
|
325
324
|
let newLineCount = 0;
|
|
326
325
|
let lastNewlineIndex = token.start;
|
|
327
326
|
for (let i = token.start; i < token.stop; i++) {
|
|
327
|
+
// Note: We do NOT check for CR, LS, and PS (/[\r\u2028\u2029]/)
|
|
328
|
+
// because ANTLR only uses LF for line break detection.
|
|
328
329
|
if (source[i] === 10) { // ASCII code for '\n'
|
|
329
330
|
newLineCount++;
|
|
330
331
|
lastNewlineIndex = i;
|