@sap/cds-compiler 2.11.4 → 2.13.8
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 +159 -1
- package/bin/cds_update_identifiers.js +7 -7
- package/bin/cdsc.js +22 -23
- package/bin/cdsse.js +2 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +25 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +30 -63
- package/lib/api/options.js +5 -5
- package/lib/api/validate.js +0 -5
- package/lib/backends.js +15 -23
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +7 -17
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +52 -2
- package/lib/base/messages.js +16 -26
- package/lib/base/model.js +2 -62
- package/lib/base/optionProcessorHelper.js +246 -183
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +94 -0
- package/lib/checks/types.js +1 -1
- package/lib/checks/validator.js +12 -7
- package/lib/compiler/assert-consistency.js +10 -6
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/builtins.js +8 -6
- package/lib/compiler/checks.js +46 -12
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1103 -0
- package/lib/compiler/extend.js +983 -0
- package/lib/compiler/finalize-parse-cdl.js +231 -0
- package/lib/compiler/index.js +33 -14
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1226 -0
- package/lib/compiler/propagator.js +113 -47
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +76 -38
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +204 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -25
- package/lib/edm/annotations/preprocessAnnotations.js +3 -3
- package/lib/edm/csn2edm.js +10 -9
- package/lib/edm/edm.js +19 -20
- package/lib/edm/edmPreprocessor.js +166 -95
- package/lib/edm/edmUtils.js +127 -34
- package/lib/gen/Dictionary.json +92 -43
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -82
- package/lib/gen/languageLexer.interp +18 -1
- package/lib/gen/languageLexer.js +925 -847
- package/lib/gen/languageLexer.tokens +78 -74
- package/lib/gen/languageParser.js +5434 -4298
- package/lib/json/from-csn.js +59 -17
- package/lib/json/to-csn.js +143 -71
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +3 -3
- package/lib/language/genericAntlrParser.js +144 -54
- package/lib/language/language.g4 +424 -203
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +472 -61
- package/lib/main.js +38 -11
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +321 -204
- package/lib/model/csnUtils.js +224 -263
- package/lib/model/enrichCsn.js +97 -40
- package/lib/model/revealInternalProperties.js +27 -6
- package/lib/model/sortViews.js +2 -1
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +7 -6
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +36 -33
- package/lib/render/toCdl.js +174 -275
- package/lib/render/toHdbcds.js +201 -115
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +149 -75
- package/lib/render/utils/common.js +22 -8
- package/lib/render/utils/sql.js +10 -7
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +35 -12
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +187 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +61 -56
- package/lib/transform/db/expansion.js +50 -29
- package/lib/transform/db/flattening.js +552 -105
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +94 -28
- package/lib/transform/db/views.js +5 -4
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +9 -7
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +94 -801
- package/lib/transform/forOdataNew.js +22 -175
- package/lib/transform/localized.js +36 -32
- package/lib/transform/odata/generateForeignKeyElements.js +3 -3
- package/lib/transform/odata/referenceFlattener.js +95 -89
- package/lib/transform/odata/structureFlattener.js +1 -1
- package/lib/transform/odata/toFinalBaseType.js +86 -12
- package/lib/transform/odata/typesExposure.js +5 -5
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +47 -33
- package/lib/transform/translateAssocsToJoins.js +10 -27
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +170 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/file.js +2 -1
- package/lib/utils/objectUtils.js +30 -0
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2340
- package/lib/compiler/resolver.js +0 -2988
- package/lib/transform/universalCsnEnricher.js +0 -67
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// Things which needs to done for parse.cdl after define()
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const { forEachGeneric, forEachMember } = require('../base/model');
|
|
6
|
+
const { setLink, setArtifactLink } = require('./utils');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
// Used for resolving types in parseCdl mode.
|
|
10
|
+
// See resolveTypesForParseCdl().
|
|
11
|
+
const parseCdlSpeciallyHandledXsnProps = [ '$queries', 'mixin', 'columns', 'args', 'returns' ];
|
|
12
|
+
const parseCdlIgnoredXsnProps = [ 'location', 'query', '$tableAliases' ];
|
|
13
|
+
|
|
14
|
+
function finalizeParseCdl( model ) {
|
|
15
|
+
// Get simplified "resolve" functionality and the message function:
|
|
16
|
+
const { message, error } = model.$messageFunctions;
|
|
17
|
+
const {
|
|
18
|
+
resolveUncheckedPath,
|
|
19
|
+
resolveTypeArguments,
|
|
20
|
+
defineAnnotations,
|
|
21
|
+
initMembers,
|
|
22
|
+
extensionsDict,
|
|
23
|
+
} = model.$functions;
|
|
24
|
+
|
|
25
|
+
resolveTypesAndExtensionsForParseCdl();
|
|
26
|
+
return;
|
|
27
|
+
|
|
28
|
+
function resolveTypesAndExtensionsForParseCdl() {
|
|
29
|
+
if (!model.extensions)
|
|
30
|
+
model.extensions = [];
|
|
31
|
+
|
|
32
|
+
// TODO: probably better to loop over extensions of all sources (there is just one)
|
|
33
|
+
for (const name in extensionsDict) {
|
|
34
|
+
for (const ext of extensionsDict[name]) {
|
|
35
|
+
ext.name.absolute = resolveUncheckedPath( ext.name, 'extend', ext );
|
|
36
|
+
// Define annotations of this top-level extension
|
|
37
|
+
defineAnnotations( ext, ext, ext._block );
|
|
38
|
+
mergeAnnotatesForSameArtifact( ext );
|
|
39
|
+
// Initialize members and define annotations in sub-elements.
|
|
40
|
+
initMembers( ext, ext, ext._block, true );
|
|
41
|
+
model.extensions.push( ext );
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
forEachGeneric(model, 'definitions', art => resolveTypesForParseCdl(art, art));
|
|
46
|
+
forEachGeneric(model, 'vocabularies', art => resolveTypesForParseCdl(art, art));
|
|
47
|
+
if (model.extensions)
|
|
48
|
+
model.extensions.forEach(ext => resolveTypesForParseCdl(ext, ext));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolve all types in parseCdl mode for the given artifact.
|
|
53
|
+
* `main` refers to the current user/scope, e.g. top-level definition, element, function, etc.
|
|
54
|
+
*
|
|
55
|
+
* @param {*} artifact
|
|
56
|
+
* @param {XSN.Artifact} main
|
|
57
|
+
*/
|
|
58
|
+
function resolveTypesForParseCdl(artifact, main) {
|
|
59
|
+
if (!artifact || typeof artifact !== 'object')
|
|
60
|
+
return;
|
|
61
|
+
|
|
62
|
+
if (Array.isArray(artifact)) {
|
|
63
|
+
// e.g. `args` array
|
|
64
|
+
artifact.forEach(art => resolveTypesForParseCdl(art, main));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (artifact.kind === 'namespace' || artifact.kind === 'service' || artifact.kind === 'context')
|
|
69
|
+
// Services and context artifacts don't have any types.
|
|
70
|
+
return;
|
|
71
|
+
|
|
72
|
+
if (artifact.type)
|
|
73
|
+
resolveTypeUnchecked(artifact, main);
|
|
74
|
+
|
|
75
|
+
for (const include of artifact.includes || [])
|
|
76
|
+
resolveUncheckedPath(include, 'include', main);
|
|
77
|
+
|
|
78
|
+
if (artifact.target)
|
|
79
|
+
resolveUncheckedPath(artifact.target, 'target', main);
|
|
80
|
+
|
|
81
|
+
if (artifact.from) {
|
|
82
|
+
const { from } = artifact;
|
|
83
|
+
// Note: `_from` only contains sources necessary for calculating elements,
|
|
84
|
+
// not e.g. those from the `where exists` clause.
|
|
85
|
+
resolveUncheckedPath(from, 'from', main);
|
|
86
|
+
resolveTypesForParseCdl(from, main);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (artifact.targetAspect) {
|
|
90
|
+
if (artifact.targetAspect.path)
|
|
91
|
+
resolveUncheckedPath(artifact.targetAspect, 'target', main);
|
|
92
|
+
resolveTypesForParseCdl(artifact.targetAspect, main);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Recursively go through all XSN properties. There are a few that need to be
|
|
96
|
+
// handled specifically. Refer to the code below this loop for details.
|
|
97
|
+
for (const prop in artifact) {
|
|
98
|
+
if (parseCdlSpeciallyHandledXsnProps.includes(prop) || parseCdlIgnoredXsnProps.includes(prop))
|
|
99
|
+
continue;
|
|
100
|
+
|
|
101
|
+
// define.js (and initMembers()) initializes annotations. If there is a duplicate, the
|
|
102
|
+
// annotation is an array in XSN.
|
|
103
|
+
if (prop[0] === '@' && Array.isArray(artifact[prop]))
|
|
104
|
+
chooseAndReportDuplicateAnnotation(artifact, prop);
|
|
105
|
+
|
|
106
|
+
if (artifact[prop] && Object.getPrototypeOf(artifact[prop]) === null)
|
|
107
|
+
// Dictionary in XSN
|
|
108
|
+
forEachGeneric(artifact, prop, art => resolveTypesForParseCdl(art, art));
|
|
109
|
+
else
|
|
110
|
+
resolveTypesForParseCdl(artifact[prop], main);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// `$queries` has a flat structure that we can use instead of going through all `query`.
|
|
114
|
+
// For these query-related properties, we need to keep the reference to the artifact
|
|
115
|
+
// containing it. Otherwise some type's aren't properly resolved.
|
|
116
|
+
// TODO: If resolveTypeUnchecked is reworked, we may be able to simplify this coding.
|
|
117
|
+
(artifact.$queries || []).forEach( art => resolveTypesForParseCdl(art, artifact) );
|
|
118
|
+
(artifact.columns || []).forEach( art => resolveTypesForParseCdl(art, artifact) );
|
|
119
|
+
forEachGeneric( artifact, 'mixin', art => resolveTypesForParseCdl(art, artifact) );
|
|
120
|
+
|
|
121
|
+
// For better error messages for `type of`s in `returns`, we pass the object as the new main.
|
|
122
|
+
resolveTypesForParseCdl(artifact.returns, artifact.returns);
|
|
123
|
+
|
|
124
|
+
// Because `args` can be used in different contexts with different semantics,
|
|
125
|
+
// it needs to be handled specifically.
|
|
126
|
+
if (artifact.args && typeof artifact.args === 'object') {
|
|
127
|
+
if (Array.isArray(artifact.args)) {
|
|
128
|
+
// `args` may either be an array (e.g. query 'from' args) ...
|
|
129
|
+
artifact.args.forEach((from) => {
|
|
130
|
+
// ... and could be either inside a `from` ...
|
|
131
|
+
if (from && from.kind === '$tableAlias')
|
|
132
|
+
resolveUncheckedPath(from, 'from', from._main);
|
|
133
|
+
|
|
134
|
+
// ... or only params ...
|
|
135
|
+
resolveTypesForParseCdl(from, main);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// ... or dictionary (e.g. params)
|
|
140
|
+
forEachGeneric(artifact, 'args', obj => resolveTypesForParseCdl(obj, obj));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Resolves `artWithType.type` in an unchecked manner. Handles `type of` cases.
|
|
147
|
+
*
|
|
148
|
+
* @param {object} artWithType
|
|
149
|
+
* @param {XSN.Artifact} artifact
|
|
150
|
+
*/
|
|
151
|
+
function resolveTypeUnchecked(artWithType, artifact) {
|
|
152
|
+
if (!artWithType.type)
|
|
153
|
+
return;
|
|
154
|
+
const root = artWithType.type.path && artWithType.type.path[0];
|
|
155
|
+
if (!root) // parse error
|
|
156
|
+
return;
|
|
157
|
+
// `scope` is only `typeOf` for `type of element` and not
|
|
158
|
+
// `type of Entity:element`. For the latter we can resolve the path
|
|
159
|
+
// without special treatment.
|
|
160
|
+
if (artWithType.type.scope !== 'typeOf') {
|
|
161
|
+
// elem: Type or elem: type of Artifact:elem
|
|
162
|
+
const name = resolveUncheckedPath(artWithType.type, 'type', artifact);
|
|
163
|
+
const def = name && model.definitions[name];
|
|
164
|
+
if (def)
|
|
165
|
+
resolveTypeArguments( artWithType, def, artifact );
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
else if (!artifact._main) {
|
|
169
|
+
error( 'ref-undefined-typeof', [ artWithType.type.location, artifact ], {},
|
|
170
|
+
'Current artifact has no element to refer to as type' );
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
else if (root.id === '$self' || root.id === '$projection') {
|
|
174
|
+
setArtifactLink( root, artifact._main );
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
// For better error messages, check for invalid TYPE OFs similarly
|
|
178
|
+
// to how `resolveType()` does.
|
|
179
|
+
let struct = artWithType;
|
|
180
|
+
while (struct.kind === 'element')
|
|
181
|
+
struct = struct._parent;
|
|
182
|
+
if (struct.kind === 'select' || struct !== artifact._main) {
|
|
183
|
+
message( 'type-unexpected-typeof', [ artWithType.type.location, artifact ],
|
|
184
|
+
{ keyword: 'type of', '#': struct.kind } );
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const fake = { name: { absolute: artifact.name.absolute } };
|
|
189
|
+
// to-csn just needs a fake element whose absolute name and _parent/_main links are correct
|
|
190
|
+
setLink( fake, '_parent', artifact._parent );
|
|
191
|
+
setLink( fake, '_main', artifact._main ); // value does not matter...
|
|
192
|
+
setArtifactLink( root, fake );
|
|
193
|
+
}
|
|
194
|
+
resolveTypeArguments( artifact, {}, artifact ); // issue error for type args
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function chooseAndReportDuplicateAnnotation(artifact, annoName) {
|
|
198
|
+
for (const anno of artifact[annoName])
|
|
199
|
+
message( 'anno-duplicate', [ anno.name.location, artifact ], { anno: annoName } );
|
|
200
|
+
|
|
201
|
+
// Choose any annotation, doesn't matter because of the error above.
|
|
202
|
+
artifact[annoName] = artifact[annoName][0];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* For duplicate entries in `annotate {}` blocks, we de-duplicate the entries and merge them.
|
|
207
|
+
* Since it is allowed in "normal" compilations to have e.g.
|
|
208
|
+
* `annotate E with { @anno1 id, @anno2 id }`.
|
|
209
|
+
*
|
|
210
|
+
* @param {object} ext
|
|
211
|
+
*/
|
|
212
|
+
function mergeAnnotatesForSameArtifact( ext ) {
|
|
213
|
+
if (!ext || typeof ext !== 'object')
|
|
214
|
+
return;
|
|
215
|
+
|
|
216
|
+
forEachMember(ext, sub => mergeAnnotatesForSameArtifact(sub));
|
|
217
|
+
|
|
218
|
+
if (ext.$annotations && Array.isArray(ext.$duplicates)) {
|
|
219
|
+
const annotates = ext.$duplicates.filter(val => (val.kind === 'annotate'));
|
|
220
|
+
for (const dup of annotates) {
|
|
221
|
+
ext.$annotations.push(...dup.$annotations);
|
|
222
|
+
delete dup.$annotations;
|
|
223
|
+
}
|
|
224
|
+
ext.$duplicates = ext.$duplicates.filter(val => (val.kind !== 'annotate'));
|
|
225
|
+
if (ext.$duplicates.length === 0)
|
|
226
|
+
delete ext.$duplicates;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
module.exports = finalizeParseCdl;
|
package/lib/compiler/index.js
CHANGED
|
@@ -21,8 +21,13 @@ const parseCsn = require('../json/from-csn');
|
|
|
21
21
|
const assertConsistency = require('./assert-consistency');
|
|
22
22
|
const moduleLayers = require('./moduleLayers');
|
|
23
23
|
const { fns } = require('./shared');
|
|
24
|
-
const
|
|
25
|
-
const
|
|
24
|
+
const define = require('./define');
|
|
25
|
+
const finalizeParseCdl = require('./finalize-parse-cdl');
|
|
26
|
+
const extend = require('./extend');
|
|
27
|
+
const kickStart = require('./kick-start');
|
|
28
|
+
const populate = require('./populate');
|
|
29
|
+
const resolve = require('./resolve');
|
|
30
|
+
const tweakAssocs = require('./tweak-assocs');
|
|
26
31
|
const propagator = require('./propagator');
|
|
27
32
|
const check = require('./checks');
|
|
28
33
|
|
|
@@ -34,6 +39,8 @@ const { cdsFs } = require('../utils/file');
|
|
|
34
39
|
const fs = require('fs');
|
|
35
40
|
const path = require('path');
|
|
36
41
|
|
|
42
|
+
const csnExtensions = [ '.json', '.csn' ];
|
|
43
|
+
const cdlExtensions = [ '.cds', '.hdbcds', '.hdbdd', '.cdl' ];
|
|
37
44
|
|
|
38
45
|
// Class for command invocation errors. Additional members:
|
|
39
46
|
// `errors`: vector of errors (file IO or ArgumentError)
|
|
@@ -56,21 +63,21 @@ class ArgumentError extends Error {
|
|
|
56
63
|
|
|
57
64
|
/**
|
|
58
65
|
* Parse the given source with the correct parser based on the file name's
|
|
59
|
-
* extension. For example
|
|
60
|
-
*
|
|
66
|
+
* extension. For example uses CDL parser for `.cds` files.
|
|
67
|
+
* Respects the value of `options.fallbackParser`.
|
|
61
68
|
*
|
|
62
69
|
* @param {string} source Source code of the file.
|
|
63
70
|
* @param {string} filename Filename including its extension, e.g. "file.cds"
|
|
64
71
|
* @param {object} options Compile options
|
|
65
72
|
* @param {object} messageFunctions If not provided, parse errors will not lead to an exception
|
|
66
73
|
*/
|
|
67
|
-
function parseX( source, filename, options = {}, messageFunctions ) {
|
|
74
|
+
function parseX( source, filename, options = {}, messageFunctions = null ) {
|
|
68
75
|
if (!messageFunctions)
|
|
69
76
|
messageFunctions = createMessageFunctions( options, 'parse' );
|
|
70
77
|
const ext = path.extname( filename ).toLowerCase();
|
|
71
|
-
if (
|
|
78
|
+
if (csnExtensions.includes(ext) || options.fallbackParser === 'csn!')
|
|
72
79
|
return parseCsn.parse( source, filename, options, messageFunctions );
|
|
73
|
-
if (
|
|
80
|
+
if (cdlExtensions.includes(ext))
|
|
74
81
|
return parseLanguage( source, filename, options, messageFunctions );
|
|
75
82
|
if (options.fallbackParser === 'csn')
|
|
76
83
|
return parseCsn.parse( source, filename, options, messageFunctions );
|
|
@@ -89,8 +96,8 @@ function parseX( source, filename, options = {}, messageFunctions ) {
|
|
|
89
96
|
// Main function: Compile the sources from the files given by the array of
|
|
90
97
|
// `filenames`. As usual with the `fs` library, relative file names are
|
|
91
98
|
// relative to the working directory `process.cwd()`. With argument `dir`, the
|
|
92
|
-
// file names are relative to `process.cwd()+dir
|
|
93
|
-
// following properties:
|
|
99
|
+
// file names are relative to `process.cwd()+dir` (or just `dir` if it is absolute).
|
|
100
|
+
// Options can have the following properties:
|
|
94
101
|
// - Truthy `parseOnly`: stop compilation after parsing.
|
|
95
102
|
// - Truthy `lintMode`: do not do checks and propagation
|
|
96
103
|
// - many others - TODO
|
|
@@ -102,7 +109,7 @@ function parseX( source, filename, options = {}, messageFunctions ) {
|
|
|
102
109
|
//
|
|
103
110
|
// The promise is fulfilled if all files could be read and processed without
|
|
104
111
|
// errors. The fulfillment value is an augmented CSN (see
|
|
105
|
-
// ./compiler/
|
|
112
|
+
// ./compiler/define.js).
|
|
106
113
|
//
|
|
107
114
|
// If there are errors, the promise is rejected. If there was an invocation
|
|
108
115
|
// error (repeated filenames or if the file could not be read), the rejection
|
|
@@ -326,9 +333,10 @@ function compileSyncX( filenames, dir = '', options = {}, fileCache = Object.cre
|
|
|
326
333
|
/**
|
|
327
334
|
* Promise-less main functions: compile the given sources.
|
|
328
335
|
*
|
|
329
|
-
* Argument `sourcesDict` is a dictionary (it could actually be
|
|
330
|
-
* mapping filenames to either source texts (string) or
|
|
331
|
-
*
|
|
336
|
+
* Argument `sourcesDict` is a dictionary (it could actually be an ordinary object)
|
|
337
|
+
* mapping filenames to either source texts (string) or JS objects; the objects
|
|
338
|
+
* are usually CSNs, or XSNs (AST-like augmented CSNs) with option `$xsnObjects`.
|
|
339
|
+
* It could also be a simple string, which is then considered
|
|
332
340
|
* to be the source text of a file named `<stdin>.cds`.
|
|
333
341
|
*
|
|
334
342
|
* See function `compileX` for the meaning of the argument `options`. If there
|
|
@@ -356,9 +364,15 @@ function compileSourcesX( sourcesDict, options = {} ) {
|
|
|
356
364
|
ast.location = { file: filename };
|
|
357
365
|
assertConsistency( ast, options );
|
|
358
366
|
}
|
|
359
|
-
else {
|
|
367
|
+
else if (options.$xsnObjects) { // source is a XSN object with option $xsnObjects
|
|
360
368
|
sources[filename] = source;
|
|
361
369
|
}
|
|
370
|
+
else { // source is a CSN object
|
|
371
|
+
const ast = parseCsn.augment( source, filename, options, model.$messageFunctions );
|
|
372
|
+
sources[filename] = ast;
|
|
373
|
+
ast.location = { file: filename };
|
|
374
|
+
assertConsistency( ast, options );
|
|
375
|
+
}
|
|
362
376
|
}
|
|
363
377
|
moduleLayers.setLayers( sources );
|
|
364
378
|
|
|
@@ -420,10 +434,15 @@ function compileDoX( model ) {
|
|
|
420
434
|
// do not run the resolver in parse-cdl mode or we get duplicate annotations, etc.
|
|
421
435
|
// TODO: do not use this function for parseCdl anyway…
|
|
422
436
|
if (options.parseCdl) {
|
|
437
|
+
finalizeParseCdl( model );
|
|
423
438
|
throwWithError();
|
|
424
439
|
return model;
|
|
425
440
|
}
|
|
441
|
+
extend( model );
|
|
442
|
+
kickStart( model );
|
|
443
|
+
populate( model );
|
|
426
444
|
resolve( model );
|
|
445
|
+
tweakAssocs( model );
|
|
427
446
|
assertConsistency( model );
|
|
428
447
|
throwWithError();
|
|
429
448
|
if (options.lintMode)
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// Kick-start: prepare to resolve all references
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const { isBetaEnabled, forEachGeneric } = require('../base/model');
|
|
6
|
+
const { setLink, annotationVal, annotationIsFalse } = require('./utils');
|
|
7
|
+
|
|
8
|
+
function kickStart( model ) {
|
|
9
|
+
const { options } = model;
|
|
10
|
+
const { message } = model.$messageFunctions;
|
|
11
|
+
|
|
12
|
+
const { resolveUncheckedPath, resolvePath } = model.$functions;
|
|
13
|
+
Object.assign( model.$functions, { projectionAncestor } );
|
|
14
|
+
|
|
15
|
+
// Set _service link (sorted to set it on parent first). Could be set
|
|
16
|
+
// directly, but beware a namespace becoming a service later.
|
|
17
|
+
Object.keys( model.definitions ).sort().forEach( setAncestorsAndService );
|
|
18
|
+
forEachGeneric( model, 'definitions', postProcessArtifact );
|
|
19
|
+
|
|
20
|
+
forEachGeneric( model, 'sources', resolveUsings );
|
|
21
|
+
return;
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Set projection ancestors, and _service link for artifact with absolute name 'name':
|
|
26
|
+
* - not set: internal artifact
|
|
27
|
+
* - null: not within service
|
|
28
|
+
* - service: the artifact of the embedding service
|
|
29
|
+
* This function must be called ordered: parent first
|
|
30
|
+
*
|
|
31
|
+
* @param {string} name Artifact name
|
|
32
|
+
*/
|
|
33
|
+
function setAncestorsAndService( name ) {
|
|
34
|
+
const art = model.definitions[name];
|
|
35
|
+
if (!('_parent' in art))
|
|
36
|
+
return; // nothing to do for builtins and redefinitions
|
|
37
|
+
if (art._from && !('_ancestors' in art))
|
|
38
|
+
setProjectionAncestors( art );
|
|
39
|
+
|
|
40
|
+
let parent = art._parent;
|
|
41
|
+
if (parent === model.definitions.localized)
|
|
42
|
+
parent = model.definitions[name.substring( 'localized.'.length )];
|
|
43
|
+
const service = parent && (parent._service || parent.kind === 'service' && parent);
|
|
44
|
+
setLink( art, '_service', service );
|
|
45
|
+
if (!parent || !service)
|
|
46
|
+
return;
|
|
47
|
+
// To be removed when nested services are allowed
|
|
48
|
+
if (!isBetaEnabled(options, 'nestedServices') && art.kind === 'service') {
|
|
49
|
+
while (parent.kind !== 'service')
|
|
50
|
+
parent = parent._parent;
|
|
51
|
+
message( 'service-nested-service', [ art.name.location, art ], { art: parent },
|
|
52
|
+
'A service can\'t be nested within a service $(ART)' );
|
|
53
|
+
}
|
|
54
|
+
else if (art.kind === 'context') {
|
|
55
|
+
while (parent.kind !== 'service')
|
|
56
|
+
parent = parent._parent;
|
|
57
|
+
// TODO: remove this error
|
|
58
|
+
message( 'service-nested-context', [ art.name.location, art ], { art: parent },
|
|
59
|
+
'A context can\'t be nested within a service $(ART)' );
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function setProjectionAncestors( art ) {
|
|
64
|
+
// Must be run after processLocalizedData() as we could have a projection
|
|
65
|
+
// on a generated entity.
|
|
66
|
+
|
|
67
|
+
// TODO: do not do implicit redirection across services, i.e. Service2.E is
|
|
68
|
+
// no redirection target for E if Service2.E = projection on Service1.E and
|
|
69
|
+
// Service1.E = projection on E
|
|
70
|
+
const chain = [];
|
|
71
|
+
const autoexposed = art.$generated || annotationVal( art['@cds.autoexposed'] );
|
|
72
|
+
const preferredRedirectionTarget = annotationVal( art['@cds.redirection.target'] );
|
|
73
|
+
// no need to set preferredRedirectionTarget in the while loop as we would
|
|
74
|
+
// use the projection having @cds.redirection.target anyhow instead of
|
|
75
|
+
// `art` anyway (if we do the no-x-service-implicit-redirection TODO above)
|
|
76
|
+
while (art && !('_ancestors' in art) &&
|
|
77
|
+
art._from && art._from.length === 1 &&
|
|
78
|
+
(preferredRedirectionTarget || !annotationIsFalse( art['@cds.redirection.target'] ) ) &&
|
|
79
|
+
art.query.op && art.query.op.val === 'SELECT') {
|
|
80
|
+
chain.push( art );
|
|
81
|
+
setLink( art, '_ancestors', null ); // avoid infloop with cyclic from
|
|
82
|
+
const name = resolveUncheckedPath( art._from[0], 'include', art ); // TODO: 'include'?
|
|
83
|
+
art = name && projectionAncestor( model.definitions[name], art.params );
|
|
84
|
+
if (autoexposed)
|
|
85
|
+
break; // only direct projection for auto-exposed
|
|
86
|
+
}
|
|
87
|
+
let ancestors = art && (!autoexposed && art._ancestors || []);
|
|
88
|
+
chain.reverse();
|
|
89
|
+
for (const a of chain) {
|
|
90
|
+
ancestors = (ancestors ? [ ...ancestors, art ] : []);
|
|
91
|
+
setLink( a, '_ancestors', ancestors );
|
|
92
|
+
art = a;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Return argument `source` if entity `source` has parameters like `params`
|
|
97
|
+
// - same parameters, although `params` can contain a new optional one (with DEFAULT)
|
|
98
|
+
// - a parameter in `params` can be optional which is not in `source.params`, but not vice versa
|
|
99
|
+
// - exactly the same types (type argument do not matter)
|
|
100
|
+
function projectionAncestor( source, params ) {
|
|
101
|
+
if (!source)
|
|
102
|
+
return source;
|
|
103
|
+
if (!params) // proj has no params => ok if source has no params
|
|
104
|
+
return !source.params && source;
|
|
105
|
+
const sourceParams = source.params || Object.create(null);
|
|
106
|
+
for (const n in sourceParams) {
|
|
107
|
+
if (!(n in params)) // source param is not projection param
|
|
108
|
+
return null; // -> can't be used as implicit redirection target
|
|
109
|
+
}
|
|
110
|
+
for (const n in params) {
|
|
111
|
+
const pp = params[n];
|
|
112
|
+
const sp = sourceParams[n];
|
|
113
|
+
if (sp) {
|
|
114
|
+
if (sp.default && !pp.default) // param DEFAULT clause not supported yet
|
|
115
|
+
return null; // param is not optional anymore
|
|
116
|
+
const pt = pp.type && resolveUncheckedPath( pp.type, 'type', pp );
|
|
117
|
+
const st = sp.type && resolveUncheckedPath( sp.type, 'type', sp );
|
|
118
|
+
if ((pt || null) !== (st || null))
|
|
119
|
+
return null; // params have different type
|
|
120
|
+
}
|
|
121
|
+
else if (!pp.default) {
|
|
122
|
+
return null;
|
|
123
|
+
} // non-optional param in projection, but not source
|
|
124
|
+
}
|
|
125
|
+
return source;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function postProcessArtifact( art ) {
|
|
129
|
+
tagCompositionTargets( art );
|
|
130
|
+
if (art.$queries) {
|
|
131
|
+
for (const query of art.$queries) {
|
|
132
|
+
if (query.mixin)
|
|
133
|
+
forEachGeneric( query, 'mixin', tagCompositionTargets );
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (!art._ancestors || art.kind !== 'entity')
|
|
137
|
+
return; // redirections only to entities
|
|
138
|
+
const service = art._service;
|
|
139
|
+
if (!service)
|
|
140
|
+
return;
|
|
141
|
+
const sname = service.name.absolute;
|
|
142
|
+
art._ancestors.forEach( expose );
|
|
143
|
+
return;
|
|
144
|
+
|
|
145
|
+
function expose( ancestor ) {
|
|
146
|
+
if (ancestor._service === service)
|
|
147
|
+
return;
|
|
148
|
+
const desc = ancestor._descendants ||
|
|
149
|
+
setLink( ancestor, '_descendants', Object.create(null) );
|
|
150
|
+
if (!desc[sname])
|
|
151
|
+
desc[sname] = [ art ];
|
|
152
|
+
else
|
|
153
|
+
desc[sname].push( art );
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function tagCompositionTargets( elem ) {
|
|
158
|
+
const type = elem.type && elem.type.path;
|
|
159
|
+
if (elem.target && type && type[0] && type[0].id === 'cds.Composition') {
|
|
160
|
+
// A target aspect would have already moved to property `targetAspect` in
|
|
161
|
+
// define.js (hm... more something for kick-start.js...)
|
|
162
|
+
const target = resolvePath( elem.target, 'target', elem );
|
|
163
|
+
if (target)
|
|
164
|
+
model.$compositionTargets[target.name.absolute] = true;
|
|
165
|
+
}
|
|
166
|
+
forEachGeneric( elem, 'elements', tagCompositionTargets );
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Resolve the using declarations in `using`. Issue
|
|
170
|
+
// error message if the referenced artifact does not exist.
|
|
171
|
+
function resolveUsings( src, topLevel ) {
|
|
172
|
+
if (!src.usings)
|
|
173
|
+
return;
|
|
174
|
+
for (const def of src.usings) {
|
|
175
|
+
if (def.usings) // using {...}
|
|
176
|
+
resolveUsings( def );
|
|
177
|
+
if (!def.name || !def.name.absolute)
|
|
178
|
+
continue; // using {...}, parse error
|
|
179
|
+
const art = model.definitions[def.name.absolute];
|
|
180
|
+
if (art && art.$duplicates)
|
|
181
|
+
continue;
|
|
182
|
+
const ref = def.extern;
|
|
183
|
+
const from = (topLevel ? def : src).fileDep;
|
|
184
|
+
if (art || !from || from.realname) // no error for non-existing ref with non-existing module
|
|
185
|
+
resolvePath( ref, 'global', def ); // TODO: consider FROM for validNames
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = kickStart;
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
5
|
const detectCycles = require('./cycle-detector');
|
|
6
|
-
const {
|
|
6
|
+
const { setLink } = require('./utils');
|
|
7
7
|
|
|
8
8
|
function setLayers( sources ) {
|
|
9
9
|
// set dependencies
|
|
10
10
|
for (const name in sources) {
|
|
11
11
|
const ast = sources[name];
|
|
12
12
|
ast.realname = name;
|
|
13
|
-
|
|
13
|
+
setLink( ast, '_deps', [] );
|
|
14
14
|
for (const d of ast.dependencies || []) {
|
|
15
15
|
const art = sources[d.realname];
|
|
16
16
|
if (art)
|
|
@@ -24,7 +24,7 @@ function setLayers( sources ) {
|
|
|
24
24
|
// It is ensured that the representative is called last in SCC and that
|
|
25
25
|
// dependent SCCs are called first
|
|
26
26
|
function setExtends( node, representative, sccDeps = Object.create(null) ) {
|
|
27
|
-
|
|
27
|
+
setLink( node, '_layerRepresentative', representative );
|
|
28
28
|
if (layerRepresentative !== representative) {
|
|
29
29
|
layerRepresentative = representative;
|
|
30
30
|
++layerNumber;
|
|
@@ -40,7 +40,7 @@ function setLayers( sources ) {
|
|
|
40
40
|
if (node === representative) {
|
|
41
41
|
const exts = Object.keys( sccDeps ).map( name => sccDeps[name]._layerExtends );
|
|
42
42
|
Object.assign( sccDeps, ...exts );
|
|
43
|
-
|
|
43
|
+
setLink( representative, '_layerExtends', sccDeps );
|
|
44
44
|
// console.log ('SCC:', node.realname)
|
|
45
45
|
}
|
|
46
46
|
return sccDeps;
|