@sap/cds-compiler 3.0.0 → 3.0.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 +29 -9
- package/bin/cdsc.js +9 -16
- package/lib/api/main.js +92 -40
- package/lib/base/keywords.js +64 -1
- package/lib/base/message-registry.js +17 -1
- package/lib/base/messages.js +38 -28
- package/lib/base/optionProcessorHelper.js +53 -21
- package/lib/compiler/assert-consistency.js +1 -1
- package/lib/compiler/builtins.js +40 -1
- package/lib/compiler/define.js +4 -2
- package/lib/compiler/extend.js +4 -1
- package/lib/compiler/populate.js +3 -1
- package/lib/compiler/resolve.js +1 -4
- package/lib/compiler/shared.js +9 -0
- package/lib/compiler/utils.js +2 -2
- package/lib/edm/annotations/preprocessAnnotations.js +10 -11
- package/lib/edm/csn2edm.js +15 -14
- package/lib/edm/edm.js +13 -12
- package/lib/edm/edmPreprocessor.js +30 -33
- package/lib/edm/edmUtils.js +3 -39
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +3311 -3289
- package/lib/json/from-csn.js +17 -19
- package/lib/json/to-csn.js +3 -2
- package/lib/language/genericAntlrParser.js +42 -42
- package/lib/language/language.g4 +28 -17
- package/lib/model/csnRefs.js +1 -0
- package/lib/model/csnUtils.js +19 -8
- package/lib/model/revealInternalProperties.js +4 -1
- package/lib/optionProcessor.js +54 -38
- package/lib/render/toHdbcds.js +1 -1
- package/lib/render/toSql.js +7 -3
- package/lib/transform/forOdataNew.js +3 -3
- package/lib/transform/localized.js +15 -11
- package/lib/utils/file.js +28 -18
- package/package.json +2 -3
- package/share/messages/syntax-expected-integer.md +9 -8
package/lib/base/messages.js
CHANGED
|
@@ -435,7 +435,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
|
|
|
435
435
|
// CSN.Location (with line/endLine, col/endCol)
|
|
436
436
|
return [ location, location.home || null, null ]
|
|
437
437
|
|
|
438
|
-
const isCsnPath = (typeof location[0] === 'string');
|
|
438
|
+
const isCsnPath = (typeof location[0] === 'string'); // could be `definitions`, `extensions`, ....
|
|
439
439
|
if (isCsnPath) {
|
|
440
440
|
return [
|
|
441
441
|
searchForLocation( model, location ),
|
|
@@ -1174,7 +1174,7 @@ function homeName( art, absoluteOnly ) {
|
|
|
1174
1174
|
return null;
|
|
1175
1175
|
else if (art.kind === 'using')
|
|
1176
1176
|
return 'using:' + quoted( art.name.id );
|
|
1177
|
-
else if (art.kind === 'extend')
|
|
1177
|
+
else if (art.kind === 'extend' || art.kind === 'annotate')
|
|
1178
1178
|
return !absoluteOnly && homeNameForExtend ( art );
|
|
1179
1179
|
else if (art.name._artifact) // block, extend, annotate
|
|
1180
1180
|
return homeName( art.name._artifact, absoluteOnly ); // use corresponding definition
|
|
@@ -1189,35 +1189,41 @@ function homeName( art, absoluteOnly ) {
|
|
|
1189
1189
|
// The "home" for extensions is handled differently because `_artifact` is not
|
|
1190
1190
|
// set for unknown extensions and we could have nested extensions.
|
|
1191
1191
|
function homeNameForExtend( art ) {
|
|
1192
|
+
const kind = art.kind || 'extend';
|
|
1192
1193
|
// TODO: fix the following - do like in collectArtifactExtensions() or
|
|
1193
|
-
//
|
|
1194
|
-
const absoluteName =
|
|
1195
|
-
|
|
1194
|
+
// basically resolveUncheckedPath()
|
|
1195
|
+
const absoluteName = art.name.id ? art.name.id :
|
|
1196
|
+
(!art.name.element && art.name.absolute || art.name.path.map(s => s && s.id).join('.'));
|
|
1196
1197
|
|
|
1197
1198
|
// Surrounding parent may be another extension.
|
|
1198
1199
|
const parent = art._parent;
|
|
1199
1200
|
if (!parent)
|
|
1200
|
-
return '
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
return artName(parent) + '/' + quoted(absoluteName);
|
|
1206
|
-
|
|
1207
|
-
let extensionName;
|
|
1208
|
-
if (parentArt.enum || parentArt.elements) {
|
|
1209
|
-
const fakeArt = {
|
|
1210
|
-
kind: parentArt.enum ? 'enum' : 'element',
|
|
1211
|
-
name: { element: absoluteName }
|
|
1212
|
-
};
|
|
1213
|
-
extensionName = artName(fakeArt);
|
|
1201
|
+
return kind + ':' + quoted(absoluteName);
|
|
1202
|
+
|
|
1203
|
+
if (art.name.param && parent.params) {
|
|
1204
|
+
const fakeArt = { kind: 'param', name: { param: absoluteName } };
|
|
1205
|
+
return homeNameForExtend(parent) + '/' + artName(fakeArt);
|
|
1214
1206
|
}
|
|
1215
|
-
else {
|
|
1216
|
-
|
|
1207
|
+
else if (art.name.action && parent.actions) {
|
|
1208
|
+
const type = art.name._artifact?.kind || 'action';
|
|
1209
|
+
const fakeArt = { kind: type, name: { action: absoluteName }, _main: art.name._artifact?._main };
|
|
1210
|
+
return homeNameForExtend(parent) + '/' + artName(fakeArt);
|
|
1211
|
+
}
|
|
1212
|
+
else if (parent.enum || parent.elements || parent.returns?.elements) {
|
|
1213
|
+
// For enum, extensions may store them in `elements`, i.e. don't differ between enum/elements,
|
|
1214
|
+
// so we need to look at the parent artifact.
|
|
1215
|
+
// For `extend <art> with enum`, there is `enum`.
|
|
1216
|
+
const parentArt = parent.name?._artifact;
|
|
1217
|
+
const fakeKind = (parent.enum || parentArt?.enum) ? 'enum' : 'element';
|
|
1218
|
+
const fakeArt = { kind: fakeKind, name: { element: art.name.element } };
|
|
1219
|
+
let parentOfElementChain = parent;
|
|
1220
|
+
while (parentOfElementChain.name?.element && parentOfElementChain._parent)
|
|
1221
|
+
parentOfElementChain = parentOfElementChain._parent;
|
|
1222
|
+
|
|
1223
|
+
return homeNameForExtend(parentOfElementChain) + '/' + artName(fakeArt);
|
|
1217
1224
|
}
|
|
1218
|
-
//
|
|
1219
|
-
|
|
1220
|
-
return 'extend:' + artName(parentArt) + '/' + extensionName;
|
|
1225
|
+
// This case should not happen, but just in case
|
|
1226
|
+
return kind + ':' + artName(parent);
|
|
1221
1227
|
}
|
|
1222
1228
|
|
|
1223
1229
|
function constructSemanticLocationFromCsnPath(csnPath, model) {
|
|
@@ -1230,10 +1236,14 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
|
|
|
1230
1236
|
];
|
|
1231
1237
|
const queryProps = [ 'from', 'where', 'groupBy', 'having', 'orderBy', 'limit', 'offset' ];
|
|
1232
1238
|
|
|
1233
|
-
|
|
1234
|
-
csnPath
|
|
1235
|
-
|
|
1236
|
-
|
|
1239
|
+
if (csnPath[0] === 'extensions') {
|
|
1240
|
+
const ext = model.extensions && model.extensions[csnPath[1]] || {};
|
|
1241
|
+
if (ext.annotate)
|
|
1242
|
+
return 'annotate:' + quoted(ext.annotate);
|
|
1243
|
+
return 'extend:' + quoted(ext.extend);
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
let { query } = analyseCsnPath(csnPath, model, false);
|
|
1237
1247
|
|
|
1238
1248
|
// remove definitions
|
|
1239
1249
|
csnPath.shift();
|
|
@@ -153,36 +153,67 @@ function createOptionProcessor() {
|
|
|
153
153
|
* Internal: Define a general or command option.
|
|
154
154
|
* Throws if the option is already registered in the given command context.
|
|
155
155
|
* or in the given command.
|
|
156
|
+
*
|
|
156
157
|
* @private
|
|
157
158
|
* @see option()
|
|
158
159
|
*/
|
|
159
160
|
function _addOption(cmd, optString, validValues, options) {
|
|
160
|
-
const
|
|
161
|
-
Object.assign(
|
|
161
|
+
const cliOpt = _parseOptionString(optString, validValues);
|
|
162
|
+
Object.assign(cliOpt, options);
|
|
163
|
+
_addLongOption(cmd, cliOpt.longName, cliOpt);
|
|
164
|
+
_addShortOption(cmd, cliOpt.shortName, cliOpt);
|
|
165
|
+
|
|
166
|
+
for (const alias of cliOpt.aliases || []) {
|
|
167
|
+
const aliasOpt = Object.assign({ }, cliOpt, { isAlias: true });
|
|
168
|
+
_addLongOption(cmd, alias, aliasOpt); // use same camelName, etc. for alias
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return cmd;
|
|
172
|
+
}
|
|
162
173
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Internal: Add longName to the list of options.
|
|
176
|
+
* Throws if the option is already registered in the given command context.
|
|
177
|
+
* or in the given command.
|
|
178
|
+
* `longName` may differ from `opt.longName`, e.g. for aliases.
|
|
179
|
+
*
|
|
180
|
+
* @private
|
|
181
|
+
* @see _addOption()
|
|
182
|
+
*/
|
|
183
|
+
function _addLongOption(cmd, longName, opt) {
|
|
184
|
+
if (cmd.options[longName]) {
|
|
185
|
+
throw new Error(`Duplicate assignment for long option ${longName}`);
|
|
186
|
+
} else if (optionProcessor.options[longName]) {
|
|
166
187
|
// This path is only taken if optString is for commands
|
|
167
188
|
optionProcessor.optionClashes.push({
|
|
168
|
-
option:
|
|
169
|
-
description: `Command '${cmd.longName}' has option clash with general options for: ${
|
|
189
|
+
option: longName,
|
|
190
|
+
description: `Command '${cmd.longName}' has option clash with general options for: ${longName}`
|
|
170
191
|
});
|
|
171
192
|
}
|
|
172
|
-
cmd.options[
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
193
|
+
cmd.options[longName] = opt;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Internal: Add shortName to the list of options.
|
|
197
|
+
* Throws if the option is already registered in the given command context.
|
|
198
|
+
* or in the given command.
|
|
199
|
+
* `longName` may differ from `opt.longName`, e.g. for aliases.
|
|
200
|
+
*
|
|
201
|
+
* @private
|
|
202
|
+
* @see _addOption()
|
|
203
|
+
*/
|
|
204
|
+
function _addShortOption(cmd, shortName, opt) {
|
|
205
|
+
if (!shortName)
|
|
206
|
+
return;
|
|
207
|
+
if (cmd.options[shortName]) {
|
|
208
|
+
throw new Error(`Duplicate assignment for short option ${shortName}`);
|
|
209
|
+
} else if (optionProcessor.options[shortName]) {
|
|
210
|
+
// This path is only taken if optString is for commands
|
|
211
|
+
optionProcessor.optionClashes.push({
|
|
212
|
+
option: shortName,
|
|
213
|
+
description: `Command '${cmd.longName}' has option clash with general options for: ${shortName}`
|
|
214
|
+
});
|
|
184
215
|
}
|
|
185
|
-
|
|
216
|
+
cmd.options[shortName] = opt;
|
|
186
217
|
}
|
|
187
218
|
|
|
188
219
|
// Internal: Parse one command string like "F, toFoo". Return an object like this
|
|
@@ -275,7 +306,8 @@ function createOptionProcessor() {
|
|
|
275
306
|
shortName,
|
|
276
307
|
camelName: camelifyLongOption(longName),
|
|
277
308
|
param,
|
|
278
|
-
validValues
|
|
309
|
+
validValues,
|
|
310
|
+
isAlias: false, // default
|
|
279
311
|
}
|
|
280
312
|
}
|
|
281
313
|
|
|
@@ -585,7 +585,7 @@ function assertConsistency( model, stage ) {
|
|
|
585
585
|
// (it can contain the artifact itself with no/failed autoexposure):
|
|
586
586
|
_descendants: { kind: [ 'entity' ], test: isDictionary( isArray( TODO ) ) },
|
|
587
587
|
|
|
588
|
-
$errorReported: { parser: true, test:
|
|
588
|
+
$errorReported: { parser: true, kind: true, test: isString }, // to avoid duplicate messages
|
|
589
589
|
$duplicates: { parser: true, kind: true, test: TODO }, // array of arts or true
|
|
590
590
|
$extension: { kind: true, test: TODO }, // TODO: introduce $applied instead or $status
|
|
591
591
|
$inferred: { parser: true, kind: true, test: isString },
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The builtin artifacts of CDS
|
|
2
2
|
|
|
3
3
|
// TODO: split this file
|
|
4
|
-
// - in base/: common definitions
|
|
4
|
+
// - in base/: common definitions, datetime formats
|
|
5
5
|
// - in compiler/: XSN-specific
|
|
6
6
|
// - in ?: CSN-specific
|
|
7
7
|
|
|
@@ -193,6 +193,44 @@ const magicVariables = {
|
|
|
193
193
|
|
|
194
194
|
// see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP Cds via function
|
|
195
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Patterns for literal token tests and creation. The value is a map from the
|
|
198
|
+
* `prefix` argument of function `quotedliteral` to the following properties:
|
|
199
|
+
* - `test_msg`: error message which is issued if `test_fn` or `test_re` fail.
|
|
200
|
+
* - `test_fn`: function called with argument `value`, fails falsy return value
|
|
201
|
+
* - `test_re`: regular expression, fails if it does not match argument `value`
|
|
202
|
+
* - `unexpected_msg`: error message which is issued if `unexpected_char` matches
|
|
203
|
+
* - `unexpected_char`: regular expression matching an illegal character in `value`,
|
|
204
|
+
* the error location is only correct for a literal <prefix>'<value>'
|
|
205
|
+
* - `literal`: the value which is used instead of `prefix` in the AST
|
|
206
|
+
* TODO: we might do a range check (consider leap seconds, i.e. max value 60),
|
|
207
|
+
* but always allow Feb 29 (no leap year computation)
|
|
208
|
+
*/
|
|
209
|
+
const quotedLiteralPatterns = {
|
|
210
|
+
x: {
|
|
211
|
+
test_variant: 'uneven-hex',
|
|
212
|
+
test_fn: (str => Number.isInteger(str.length / 2)),
|
|
213
|
+
unexpected_variant: 'invalid-hex',
|
|
214
|
+
unexpected_char: /[^0-9a-f]/i,
|
|
215
|
+
json_type: 'string',
|
|
216
|
+
},
|
|
217
|
+
time: {
|
|
218
|
+
test_variant: 'time',
|
|
219
|
+
test_re: /^[0-9]{1,2}:[0-9]{1,2}(:[0-9]{1,2})?$/,
|
|
220
|
+
json_type: 'string',
|
|
221
|
+
},
|
|
222
|
+
date: {
|
|
223
|
+
test_variant: 'date',
|
|
224
|
+
test_re: /^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/,
|
|
225
|
+
json_type: 'string',
|
|
226
|
+
},
|
|
227
|
+
timestamp: {
|
|
228
|
+
test_variant: 'timestamp',
|
|
229
|
+
test_re: /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}(:[0-9]{2}(\.[0-9]{1,7})?)?$/,
|
|
230
|
+
json_type: 'string',
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
|
|
196
234
|
/** All types belong to one category. */
|
|
197
235
|
const typeCategories = {
|
|
198
236
|
string: [],
|
|
@@ -403,6 +441,7 @@ module.exports = {
|
|
|
403
441
|
typeParameters,
|
|
404
442
|
functionsWithoutParens,
|
|
405
443
|
specialFunctions,
|
|
444
|
+
quotedLiteralPatterns,
|
|
406
445
|
initBuiltins,
|
|
407
446
|
isInReservedNamespace,
|
|
408
447
|
isBuiltinType,
|
package/lib/compiler/define.js
CHANGED
|
@@ -390,14 +390,14 @@ function define( model ) {
|
|
|
390
390
|
setLink( vocab, '_block', block );
|
|
391
391
|
const { name } = vocab;
|
|
392
392
|
if (!name.absolute)
|
|
393
|
-
name.absolute = prefix + name.path
|
|
393
|
+
name.absolute = prefix + pathName( name.path );
|
|
394
394
|
dictAdd( model.vocabularies, name.absolute, vocab );
|
|
395
395
|
}
|
|
396
396
|
|
|
397
397
|
// Phase 2 ("init") --------------------------------------------------------
|
|
398
398
|
|
|
399
399
|
function checkRedefinition( art ) {
|
|
400
|
-
if (!art.$duplicates)
|
|
400
|
+
if (!art.$duplicates || art.$errorReported === 'syntax-duplicate-extend')
|
|
401
401
|
return;
|
|
402
402
|
if (art._main) {
|
|
403
403
|
error( 'duplicate-definition', [ art.name.location, art ], {
|
|
@@ -670,6 +670,8 @@ function define( model ) {
|
|
|
670
670
|
* @param {object} exprOrPathElement starts w/ an expr but then subelem from .path or .where.args
|
|
671
671
|
*/
|
|
672
672
|
function approveExistsInChildren(exprOrPathElement) {
|
|
673
|
+
if (!exprOrPathElement) // may be null in case of parse error
|
|
674
|
+
return;
|
|
673
675
|
if (exprOrPathElement.$expected === 'exists')
|
|
674
676
|
exprOrPathElement.$expected = 'approved-exists';
|
|
675
677
|
// Drill down
|
package/lib/compiler/extend.js
CHANGED
|
@@ -389,7 +389,10 @@ function extend( model ) {
|
|
|
389
389
|
for (const ext of exts) {
|
|
390
390
|
delete ext.name.path[0]._artifact; // get message for root
|
|
391
391
|
// TODO: make resolvePath('extend'/'annotate') ignore namespaces
|
|
392
|
-
|
|
392
|
+
// Don't try to apply annotations in the `localized.` namespace.
|
|
393
|
+
// That's done in `localized.js`.
|
|
394
|
+
if (!name.startsWith('localized.') &&
|
|
395
|
+
resolvePath( ext.name, ext.kind, ext )) { // should issue error/info
|
|
393
396
|
// should issue error for cds extensions (annotate ok)
|
|
394
397
|
if (art.kind === 'namespace') {
|
|
395
398
|
info( 'anno-namespace', [ ext.name.location, ext ], {},
|
package/lib/compiler/populate.js
CHANGED
|
@@ -246,7 +246,9 @@ function populate( model ) {
|
|
|
246
246
|
let struct = user;
|
|
247
247
|
while (struct.kind === 'element')
|
|
248
248
|
struct = struct._parent;
|
|
249
|
-
if (struct.kind === 'select') {
|
|
249
|
+
if (struct.kind === 'select' || struct.kind === 'annotation') {
|
|
250
|
+
// `type of` in annotation definitions can't work, because csn type refs
|
|
251
|
+
// always refer to definitions.
|
|
250
252
|
message( 'type-unexpected-typeof', [ ref.location, user ],
|
|
251
253
|
{ keyword: 'type of', '#': struct.kind } );
|
|
252
254
|
// we actually refer to an element in _combined; TODO: return null if
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -481,10 +481,7 @@ function resolve( model ) {
|
|
|
481
481
|
if (ext.$extension) // extension for known artifact -> already applied
|
|
482
482
|
return;
|
|
483
483
|
annotateMembers( ext );
|
|
484
|
-
|
|
485
|
-
if (prop.charAt(0) === '@')
|
|
486
|
-
chooseAssignment( prop, ext );
|
|
487
|
-
}
|
|
484
|
+
chooseAnnotationsInArtifact( ext );
|
|
488
485
|
}
|
|
489
486
|
|
|
490
487
|
/**
|
package/lib/compiler/shared.js
CHANGED
|
@@ -881,9 +881,18 @@ function fns( model ) {
|
|
|
881
881
|
info( 'anno-builtin', [ construct.name.location, construct ], {},
|
|
882
882
|
'Builtin types should not be annotated. Use custom type instead' );
|
|
883
883
|
}
|
|
884
|
+
else if (construct.$syntax === 'returns' && art._block && art.kind !== 'action' &&
|
|
885
|
+
art.kind !== 'function' ) {
|
|
886
|
+
// `annotate ABC with returns {}` is handled just like `elements`. Warn if it is used
|
|
887
|
+
// for non-actions. We can't only check for !art.returns, because `action A();` is valid.
|
|
888
|
+
// `art._block` ensures that `art` is a defined def.
|
|
889
|
+
warning('anno-unexpected-returns', [ construct.name.location, construct ],
|
|
890
|
+
{ keyword: 'returns', kind: art.kind }, 'Unexpected $(KEYWORD) for $(KIND)');
|
|
891
|
+
}
|
|
884
892
|
}
|
|
885
893
|
if (construct.doc)
|
|
886
894
|
art.doc = construct.doc; // e.g. through `extensions` array in CSN
|
|
895
|
+
|
|
887
896
|
// set _block (for layering) and $priority, shallow-copy from extension
|
|
888
897
|
// TODO: think of removing $priority, then
|
|
889
898
|
// no _block: define, _block: annotate/extend/edmx
|
package/lib/compiler/utils.js
CHANGED
|
@@ -188,8 +188,8 @@ function withAssociation( ref, test = testFunctionPlaceholder, alsoTestLast = fa
|
|
|
188
188
|
*
|
|
189
189
|
* @param {XSN.Path} path
|
|
190
190
|
*/
|
|
191
|
-
function pathName(path) {
|
|
192
|
-
return (path.broken) ?
|
|
191
|
+
function pathName( path ) {
|
|
192
|
+
return (path && !path.broken) ? path.map( id => id.id ).join('.') : '';
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const edmUtils = require('../edmUtils.js');
|
|
4
3
|
const { makeMessageFunction } = require('../../base/messages.js');
|
|
4
|
+
const { forEachDefinition } = require('../../model/csnUtils.js');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
/**************************************************************************************************
|
|
@@ -37,15 +37,14 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
37
37
|
let targetName = (typeof assoc.target === 'object') ? assoc.target.name : assoc.target;
|
|
38
38
|
let target = (typeof assoc.target === 'object') ? assoc.target : csn.definitions[assoc.target];
|
|
39
39
|
|
|
40
|
-
let keyNames = Object.keys(target.elements).filter(x => target.elements[x].key);
|
|
40
|
+
let keyNames = Object.keys(target.elements).filter(x => target.elements[x].key && !target.elements[x].target);
|
|
41
41
|
if (keyNames.length === 0) {
|
|
42
42
|
keyNames.push('MISSING');
|
|
43
43
|
warning(null, null, `in annotation preprocessing: target ${targetName} has no key`);
|
|
44
44
|
}
|
|
45
45
|
else if (keyNames.length > 1)
|
|
46
46
|
warning(null, null, `in annotation preprocessing: target ${targetName} has multiple key elements`);
|
|
47
|
-
|
|
48
|
-
// TODO: what happens if key of target is itself a managed association?
|
|
47
|
+
|
|
49
48
|
return keyNames[0];
|
|
50
49
|
}
|
|
51
50
|
|
|
@@ -58,15 +57,15 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
58
57
|
function resolveShortcuts() {
|
|
59
58
|
let art = null;
|
|
60
59
|
|
|
61
|
-
|
|
60
|
+
forEachDefinition(csn, (artifact, artifactName) => {
|
|
62
61
|
if(artifactName == serviceName || artifactName.startsWith(serviceName + '.')) {
|
|
63
62
|
art = artifactName;
|
|
64
63
|
handleAnnotations(artifactName, artifact);
|
|
65
|
-
|
|
64
|
+
artifact.elements && Object.entries(artifact.elements).forEach(([elementName, element]) => {
|
|
66
65
|
handleAnnotations(elementName, element);
|
|
67
66
|
});
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
artifact.actions && Object.values(artifact.actions).forEach(action => {
|
|
68
|
+
action.params && Object.entries(action.params).forEach(([paramName, param]) => {
|
|
70
69
|
handleAnnotations(paramName, param);
|
|
71
70
|
});
|
|
72
71
|
});
|
|
@@ -155,7 +154,7 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
155
154
|
return false;
|
|
156
155
|
}
|
|
157
156
|
let assoc = csn.definitions[art].elements[assocName];
|
|
158
|
-
if (!assoc || !
|
|
157
|
+
if (!assoc || !assoc.target) {
|
|
159
158
|
warning(null, null, `in annotation preprocessing/${aNameWithoutQualifier}: there is no association "${assocName}", ${ctx}`);
|
|
160
159
|
return false;
|
|
161
160
|
}
|
|
@@ -196,7 +195,7 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
196
195
|
// name of the element carrying the value help annotation
|
|
197
196
|
// if this is a managed assoc, use fk field instead (if there is a single one)
|
|
198
197
|
let localDataProp = carrierName.split('/').pop();
|
|
199
|
-
if (
|
|
198
|
+
if (carrier.target && carrier.on === undefined) {
|
|
200
199
|
localDataProp = localDataProp + fkSeparator + getKeyOfTargetOfManagedAssoc(carrier);
|
|
201
200
|
}
|
|
202
201
|
|
|
@@ -212,7 +211,7 @@ function preprocessAnnotations(csn, serviceName, options) {
|
|
|
212
211
|
// valueListProp: the (single) key field of the value list entity
|
|
213
212
|
// if no key or multiple keys -> warning
|
|
214
213
|
let valueListProp = null;
|
|
215
|
-
let keys = Object.keys(vlEntity.elements).filter( x => vlEntity.elements[x].key );
|
|
214
|
+
let keys = Object.keys(vlEntity.elements).filter( x => vlEntity.elements[x].key && !vlEntity.elements[x].target );
|
|
216
215
|
if (keys.length === 0) {
|
|
217
216
|
warning(null, null, `in annotation preprocessing/value help shortcut: entity "${enameFull}" has no key, ${ctx}`);
|
|
218
217
|
return false;
|
package/lib/edm/csn2edm.js
CHANGED
|
@@ -330,11 +330,12 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
330
330
|
Entity starts with 'localserviceNameized.' or ends with '_localized'
|
|
331
331
|
*/
|
|
332
332
|
edmUtils.foreach(schemaCsn.definitions,
|
|
333
|
-
a =>
|
|
333
|
+
a => a.kind === 'entity' && !a.abstract && a.name.startsWith(schemaNamePrefix),
|
|
334
334
|
createEntityTypeAndSet
|
|
335
335
|
);
|
|
336
336
|
// create unbound actions/functions
|
|
337
|
-
edmUtils.foreach(schemaCsn.definitions,
|
|
337
|
+
edmUtils.foreach(schemaCsn.definitions,
|
|
338
|
+
a => (a.kind === 'action' || a.kind === 'function') && a.name.startsWith(schemaNamePrefix),
|
|
338
339
|
(options.isV4()) ? createActionV4 : createActionV2);
|
|
339
340
|
|
|
340
341
|
// create the complex types
|
|
@@ -346,7 +347,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
346
347
|
{
|
|
347
348
|
edmUtils.foreach(schemaCsn.definitions,
|
|
348
349
|
artifact => edmUtils.isDerivedType(artifact) &&
|
|
349
|
-
!
|
|
350
|
+
!artifact.target &&
|
|
350
351
|
artifact.name.startsWith(schemaNamePrefix),
|
|
351
352
|
createTypeDefinition);
|
|
352
353
|
}
|
|
@@ -414,7 +415,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
414
415
|
if(p._edmAttributes.Name === EntityTypeName)
|
|
415
416
|
warning('odata-spec-violation-property-name', pLoc, { kind: entityCsn.kind });
|
|
416
417
|
|
|
417
|
-
if(options.isV2() && p._isCollection && !
|
|
418
|
+
if(options.isV2() && p._isCollection && !p._csn.target)
|
|
418
419
|
warning('odata-spec-violation-array', pLoc, { version: '2.0' });
|
|
419
420
|
|
|
420
421
|
if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
|
|
@@ -461,7 +462,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
461
462
|
}
|
|
462
463
|
|
|
463
464
|
// put actions behind entity types in Schema/EntityContainer
|
|
464
|
-
|
|
465
|
+
entityCsn.actions && Object.entries(entityCsn.actions).forEach(([ n, a ]) => {
|
|
465
466
|
(options.isV4()) ? createActionV4(a, n, entityCsn)
|
|
466
467
|
: createActionV2(a, n, entityCsn)
|
|
467
468
|
});
|
|
@@ -522,7 +523,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
522
523
|
}
|
|
523
524
|
|
|
524
525
|
// Parameter Nodes
|
|
525
|
-
|
|
526
|
+
actionCsn.params && Object.entries(actionCsn.params).forEach(([parameterName, parameterCsn]) => {
|
|
526
527
|
const p = new Edm.Parameter(v, { Name: parameterName }, parameterCsn );
|
|
527
528
|
const pLoc = [ ...loc, 'params', p._edmAttributes.Name ];
|
|
528
529
|
if(!edmUtils.isODataSimpleIdentifier(parameterName))
|
|
@@ -573,7 +574,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
573
574
|
if(rt) // add EntitySet attribute only if return type is an entity
|
|
574
575
|
{
|
|
575
576
|
const defintion = schemaCsn.definitions[rt];
|
|
576
|
-
if(defintion &&
|
|
577
|
+
if(defintion && defintion.kind === 'entity')
|
|
577
578
|
{
|
|
578
579
|
functionImport.setEdmAttribute('EntitySet', rt.replace(schemaNamePrefix, ''));
|
|
579
580
|
}
|
|
@@ -597,7 +598,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
597
598
|
// Binding Parameter: Primary Keys at first position in sequence, this is decisive!
|
|
598
599
|
// V2 XML: Nullable=false is set because we reuse the primary key property for the parameter
|
|
599
600
|
edmUtils.foreach(entityCsn.elements,
|
|
600
|
-
elementCsn => elementCsn.key && !
|
|
601
|
+
elementCsn => elementCsn.key && !elementCsn.target,
|
|
601
602
|
(elementCsn, elementName) => {
|
|
602
603
|
functionImport.append(new Edm.Parameter(v, { Name: elementName }, elementCsn, 'In' ));
|
|
603
604
|
}
|
|
@@ -605,7 +606,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
605
606
|
}
|
|
606
607
|
|
|
607
608
|
// is this still required?
|
|
608
|
-
|
|
609
|
+
Object.entries(actionCsn).forEach(([p, v]) => {
|
|
609
610
|
if (p.match(/^@sap\./))
|
|
610
611
|
functionImport.setXml( { ['sap:' + p.slice(5).replace(/\./g, '-')] : v });
|
|
611
612
|
});
|
|
@@ -613,7 +614,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
613
614
|
// V2 XML: Parameters that are not explicitly marked as Nullable or NotNullable in the CSN must become Nullable=true
|
|
614
615
|
// V2 XML spec does only mention default Nullable=true for Properties not for Parameters so omitting Nullable=true let
|
|
615
616
|
// the client assume that Nullable is false.... Correct Nullable Handling is done inside Parameter constructor
|
|
616
|
-
|
|
617
|
+
actionCsn.params && Object.entries(actionCsn.params).forEach(([parameterName, parameterCsn]) => {
|
|
617
618
|
const pLoc = [...loc, 'params', parameterName];
|
|
618
619
|
const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );
|
|
619
620
|
edmTypeCompatibilityCheck(param, pLoc);
|
|
@@ -683,12 +684,12 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
683
684
|
let hasStream = false;
|
|
684
685
|
const streamProps = [];
|
|
685
686
|
|
|
686
|
-
|
|
687
|
+
elementsCsn.elements && Object.entries(elementsCsn.elements).forEach(([elementName, elementCsn]) =>
|
|
687
688
|
{
|
|
688
689
|
if(elementCsn._edmParentCsn == undefined)
|
|
689
690
|
setProp(elementCsn, '_edmParentCsn', edmParentCsn);
|
|
690
691
|
|
|
691
|
-
if(
|
|
692
|
+
if(elementCsn.target) {
|
|
692
693
|
// Foreign keys are part of the generic elementCsn.elements property creation
|
|
693
694
|
|
|
694
695
|
// This is the V4 edmx:NavigationProperty
|
|
@@ -769,10 +770,10 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
769
770
|
message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
|
|
770
771
|
|
|
771
772
|
if(options.isV2()) {
|
|
772
|
-
if(p._isCollection && !
|
|
773
|
+
if(p._isCollection && !p._csn.target)
|
|
773
774
|
warning('odata-spec-violation-array', pLoc, { version: '2.0' });
|
|
774
775
|
|
|
775
|
-
if(
|
|
776
|
+
if(p._csn.target)
|
|
776
777
|
warning('odata-spec-violation-assoc', pLoc, { version: '2.0' });
|
|
777
778
|
}
|
|
778
779
|
});
|
package/lib/edm/edm.js
CHANGED
|
@@ -239,7 +239,7 @@ function getEdm(options, messageFunctions) {
|
|
|
239
239
|
if(csn)
|
|
240
240
|
{
|
|
241
241
|
const attr = (useSetAttributes ? csn._SetAttributes : csn);
|
|
242
|
-
|
|
242
|
+
attr && Object.entries(attr).forEach(([p, v]) => {
|
|
243
243
|
if (p.match(/^@sap./))
|
|
244
244
|
this.setXml( { ['sap:' + p.slice(5).replace(/\./g, '-')] : v } );
|
|
245
245
|
});
|
|
@@ -332,7 +332,7 @@ function getEdm(options, messageFunctions) {
|
|
|
332
332
|
if(what==='metadata' || what==='all')
|
|
333
333
|
{
|
|
334
334
|
xml += super.innerXML(indent);
|
|
335
|
-
|
|
335
|
+
this._actions && Object.values(this._actions).forEach(actionArray => {
|
|
336
336
|
actionArray.forEach(action => {
|
|
337
337
|
xml += action.toXML(indent, what) + '\n'; });
|
|
338
338
|
});
|
|
@@ -350,7 +350,7 @@ function getEdm(options, messageFunctions) {
|
|
|
350
350
|
// no $Namespace
|
|
351
351
|
toJSONattributes(json)
|
|
352
352
|
{
|
|
353
|
-
|
|
353
|
+
this._edmAttributes && Object.entries(this._edmAttributes).forEach(([p, v]) => {
|
|
354
354
|
if (p !== 'Name' && p !== 'Namespace')
|
|
355
355
|
json[p[0] === '@' ? p : '$' + p] = v;
|
|
356
356
|
});
|
|
@@ -371,7 +371,7 @@ function getEdm(options, messageFunctions) {
|
|
|
371
371
|
if(Object.keys(json_Annotations).length)
|
|
372
372
|
json['$Annotations'] = json_Annotations;
|
|
373
373
|
}
|
|
374
|
-
|
|
374
|
+
this._actions && Object.entries(this._actions).forEach(([actionName, actionArray]) => {
|
|
375
375
|
json[actionName] = [];
|
|
376
376
|
actionArray.forEach(action => {
|
|
377
377
|
json[actionName].push(action.toJSON());
|
|
@@ -737,7 +737,7 @@ function getEdm(options, messageFunctions) {
|
|
|
737
737
|
if(this._type !== 'Edm.String' && this._type) // Edm.String is default)
|
|
738
738
|
json['$'+this._typeName] = this._type;
|
|
739
739
|
|
|
740
|
-
|
|
740
|
+
this._edmAttributes && Object.entries(this._edmAttributes).forEach(([p, v]) => {
|
|
741
741
|
if (p !== 'Name' && p !== this._typeName
|
|
742
742
|
// remove this line if Nullable=true becomes default
|
|
743
743
|
&& !(p === 'Nullable' && v == false))
|
|
@@ -840,8 +840,8 @@ function getEdm(options, messageFunctions) {
|
|
|
840
840
|
super(v, attributes, csn);
|
|
841
841
|
|
|
842
842
|
// array of enum not yet allowed
|
|
843
|
-
|
|
844
|
-
|
|
843
|
+
const enumValues = /*(csn.items && csn.items.enum) ||*/ csn.enum;
|
|
844
|
+
enumValues && Object.entries(enumValues).forEach(([en, e]) => {
|
|
845
845
|
this.append(new Member(v, { Name: en, Value: e.val } ));
|
|
846
846
|
});
|
|
847
847
|
}
|
|
@@ -1177,9 +1177,10 @@ function getEdm(options, messageFunctions) {
|
|
|
1177
1177
|
_constraints = this._csn._constraints._partnerCsn._constraints;
|
|
1178
1178
|
[i,j] = [1,0];
|
|
1179
1179
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1180
|
+
_constraints.constraints && Object.values(_constraints.constraints).forEach(c =>
|
|
1181
|
+
this.append(new ReferentialConstraint(this._v,
|
|
1182
|
+
{ Property: c[i].join(options.pathDelimiter), ReferencedProperty: c[j].join(options.pathDelimiter) } ) )
|
|
1183
|
+
);
|
|
1183
1184
|
}
|
|
1184
1185
|
}
|
|
1185
1186
|
|
|
@@ -1502,7 +1503,7 @@ function getEdm(options, messageFunctions) {
|
|
|
1502
1503
|
}
|
|
1503
1504
|
toJSONattributes(json) {
|
|
1504
1505
|
super.toJSONattributes(json);
|
|
1505
|
-
|
|
1506
|
+
this._jsonOnlyAttributes && Object.entries(this._jsonOnlyAttributes).forEach(([p, v]) => {
|
|
1506
1507
|
json[p[0] === '@' ? p : '$' + p] = v;
|
|
1507
1508
|
});
|
|
1508
1509
|
return json;
|
|
@@ -1606,7 +1607,7 @@ function getEdm(options, messageFunctions) {
|
|
|
1606
1607
|
node._d = new Dependent(v, { Role: from } );
|
|
1607
1608
|
node._p = new Principal(v, { Role: to } );
|
|
1608
1609
|
|
|
1609
|
-
|
|
1610
|
+
c && Object.values(c).forEach(cv => {
|
|
1610
1611
|
node._d.append(new PropertyRef(v, cv[0].join(options.pathDelimiter)));
|
|
1611
1612
|
node._p.append(new PropertyRef(v, cv[1].join(options.pathDelimiter)));
|
|
1612
1613
|
});
|