@sap/cds-compiler 2.12.0 → 2.15.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 +221 -15
- package/bin/cdsc.js +125 -50
- package/bin/cdsse.js +2 -2
- package/doc/CHANGELOG_BETA.md +13 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +47 -84
- package/lib/api/options.js +5 -6
- package/lib/api/validate.js +6 -11
- 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 +114 -18
- package/lib/base/messages.js +101 -90
- package/lib/base/model.js +2 -63
- package/lib/base/optionProcessorHelper.js +177 -123
- package/lib/checks/annotationsOData.js +12 -33
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +27 -26
- package/lib/checks/types.js +1 -1
- package/lib/checks/validator.js +6 -11
- package/lib/compiler/assert-consistency.js +6 -3
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +19 -6
- package/lib/compiler/checks.js +23 -60
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1151 -0
- package/lib/compiler/extend.js +1000 -0
- package/lib/compiler/finalize-parse-cdl.js +237 -0
- package/lib/compiler/index.js +107 -39
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1227 -0
- package/lib/compiler/propagator.js +114 -46
- package/lib/compiler/resolve.js +1521 -0
- package/lib/compiler/shared.js +126 -65
- package/lib/compiler/tweak-assocs.js +535 -0
- package/lib/compiler/utils.js +197 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -24
- package/lib/edm/annotations/preprocessAnnotations.js +2 -2
- package/lib/edm/csn2edm.js +219 -100
- package/lib/edm/edm.js +302 -230
- package/lib/edm/edmPreprocessor.js +554 -419
- package/lib/edm/edmUtils.js +138 -44
- package/lib/gen/Dictionary.json +100 -19
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -83
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +860 -833
- package/lib/gen/languageLexer.tokens +78 -75
- package/lib/gen/languageParser.js +5765 -4480
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +15 -3
- package/lib/json/to-csn.js +126 -68
- package/lib/language/docCommentParser.js +4 -4
- package/lib/language/genericAntlrParser.js +123 -5
- package/lib/language/language.g4 +355 -156
- package/lib/language/multiLineStringParser.js +5 -5
- package/lib/main.d.ts +486 -59
- package/lib/main.js +41 -9
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +252 -156
- package/lib/model/csnUtils.js +384 -297
- package/lib/model/enrichCsn.js +71 -29
- package/lib/model/revealInternalProperties.js +29 -8
- package/lib/model/sortViews.js +2 -1
- package/lib/modelCompare/compare.js +23 -18
- package/lib/optionProcessor.js +63 -26
- package/lib/render/manageConstraints.js +35 -32
- package/lib/render/toCdl.js +897 -947
- package/lib/render/toHdbcds.js +205 -257
- package/lib/render/toSql.js +264 -225
- package/lib/render/utils/common.js +136 -25
- package/lib/render/utils/sql.js +4 -3
- 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/db/.eslintrc.json +3 -1
- package/lib/transform/db/applyTransformations.js +35 -12
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +104 -306
- package/lib/transform/db/cdsPersistence.js +2 -2
- package/lib/transform/db/constraints.js +58 -53
- package/lib/transform/db/expansion.js +60 -33
- package/lib/transform/db/flattening.js +582 -104
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/transformExists.js +66 -13
- package/lib/transform/db/views.js +11 -7
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +6 -5
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +109 -208
- package/lib/transform/forOdataNew.js +59 -212
- package/lib/transform/localized.js +46 -26
- package/lib/transform/odata/toFinalBaseType.js +85 -11
- package/lib/transform/odata/typesExposure.js +147 -199
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +44 -33
- package/lib/transform/translateAssocsToJoins.js +3 -20
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +172 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +737 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/moduleResolve.js +13 -6
- package/lib/utils/objectUtils.js +30 -0
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/lib/compiler/definer.js +0 -2361
- package/lib/compiler/resolver.js +0 -3079
- package/lib/transform/odata/attachPath.js +0 -96
- package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
- package/lib/transform/odata/generateForeignKeyElements.js +0 -261
- package/lib/transform/odata/referenceFlattener.js +0 -290
- package/lib/transform/odata/sortByAssociationDependency.js +0 -105
- package/lib/transform/odata/structuralPath.js +0 -72
- package/lib/transform/odata/structureFlattener.js +0 -171
- package/lib/transform/universalCsnEnricher.js +0 -237
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
+
const { ModelError } = require('../../base/error');
|
|
6
|
+
|
|
5
7
|
const functionsWithoutParams = {
|
|
6
8
|
hana: {
|
|
7
9
|
CURRENT_CONNECTION: {},
|
|
@@ -19,8 +21,6 @@ const {
|
|
|
19
21
|
} = require('../../model/csnUtils');
|
|
20
22
|
|
|
21
23
|
const { implicitAs } = require('../../model/csnRefs');
|
|
22
|
-
const { isBetaEnabled } = require('../../base/model');
|
|
23
|
-
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Render the given function
|
|
@@ -38,7 +38,7 @@ function renderFunc( funcName, node, dialect, renderArgs) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* Checks
|
|
41
|
+
* Checks whether the given function is to be rendered without parentheses
|
|
42
42
|
*
|
|
43
43
|
* @param {object} node Content of the function
|
|
44
44
|
* @param {string} dialect One of 'hana', 'cap' or 'sqlite' - only 'hana' is relevant atm
|
|
@@ -282,12 +282,12 @@ const cdsToSqlTypes = {
|
|
|
282
282
|
};
|
|
283
283
|
|
|
284
284
|
/**
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
285
|
+
* Get the element matching the column
|
|
286
|
+
*
|
|
287
|
+
* @param {CSN.Elements} elements Elements of a query
|
|
288
|
+
* @param {CSN.Column} column Column from the same query
|
|
289
|
+
* @returns {CSN.Element}
|
|
290
|
+
*/
|
|
291
291
|
function findElement(elements, column) {
|
|
292
292
|
if (!elements)
|
|
293
293
|
return undefined;
|
|
@@ -332,7 +332,7 @@ function addIntermediateContexts(csn, killList) {
|
|
|
332
332
|
}
|
|
333
333
|
|
|
334
334
|
/**
|
|
335
|
-
* Check
|
|
335
|
+
* Check whether the given artifact or element has a comment that needs to be rendered.
|
|
336
336
|
* Things annotated with @cds.persistence.journal (for HANA SQL), should not get a comment.
|
|
337
337
|
*
|
|
338
338
|
* @param {CSN.Artifact} obj
|
|
@@ -343,16 +343,17 @@ function hasHanaComment(obj, options) {
|
|
|
343
343
|
return !options.disableHanaComments && typeof obj.doc === 'string';
|
|
344
344
|
}
|
|
345
345
|
/**
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
346
|
+
* Return the comment of the given artifact or element.
|
|
347
|
+
* Uses the first block (everything up to the first empty line (double \n)).
|
|
348
|
+
* Remove leading/trailing whitespace.
|
|
349
|
+
* Does not escape any characters.
|
|
350
|
+
*
|
|
351
|
+
* @param {CSN.Artifact|CSN.Element} obj
|
|
352
|
+
* @returns {string}
|
|
353
|
+
* @todo Warning/info to user?
|
|
354
|
+
*/
|
|
354
355
|
function getHanaComment(obj) {
|
|
355
|
-
return obj.doc.split('\n\n')[0].trim()
|
|
356
|
+
return obj.doc.split('\n\n')[0].trim();
|
|
356
357
|
}
|
|
357
358
|
|
|
358
359
|
/**
|
|
@@ -364,13 +365,122 @@ function getHanaComment(obj) {
|
|
|
364
365
|
* @returns {object} object with .front and .back
|
|
365
366
|
*/
|
|
366
367
|
function getSqlSnippets(options, obj) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
const back = obj['@sql.append'] ? ` ${obj['@sql.append']}` : '';
|
|
368
|
+
const front = obj['@sql.prepend'] ? `${obj['@sql.prepend']} ` : '';
|
|
369
|
+
const back = obj['@sql.append'] ? ` ${obj['@sql.append']}` : '';
|
|
370
370
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
371
|
+
return { front, back };
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* A function used to render a certain part of an expression object
|
|
376
|
+
*
|
|
377
|
+
* @callback renderPart
|
|
378
|
+
* @param {object||array} expression
|
|
379
|
+
* @param {CdlRenderEnvironment} env
|
|
380
|
+
* @this {{inline: Boolean, nestedExpr: Boolean}}
|
|
381
|
+
* @returns {string}
|
|
382
|
+
*/
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* The object containing the concrete rendering functions for the different parts
|
|
386
|
+
* of an expression
|
|
387
|
+
*
|
|
388
|
+
* @typedef {object} ExpressionConfiguration
|
|
389
|
+
* @property {(x: any) => string} finalize The final function to call on the expression(-string) before returning
|
|
390
|
+
* @property {renderPart} explicitTypeCast
|
|
391
|
+
* @property {renderPart} val
|
|
392
|
+
* @property {renderPart} enum
|
|
393
|
+
* @property {renderPart} ref
|
|
394
|
+
* @property {renderPart} aliasOnly
|
|
395
|
+
* @property {renderPart} windowFunction
|
|
396
|
+
* @property {renderPart} func
|
|
397
|
+
* @property {renderPart} xpr
|
|
398
|
+
* @property {renderPart} SELECT
|
|
399
|
+
* @property {renderPart} SET
|
|
400
|
+
*/
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Render an expression (including paths and values) or condition 'x'.
|
|
404
|
+
* (no trailing LF, don't indent if inline)
|
|
405
|
+
*
|
|
406
|
+
* @param {ExpressionConfiguration} renderer
|
|
407
|
+
* @returns {Function} Rendered expression
|
|
408
|
+
*/
|
|
409
|
+
function getExpressionRenderer(renderer) {
|
|
410
|
+
/**
|
|
411
|
+
* Render an expression (including paths and values) or condition 'x'.
|
|
412
|
+
* (no trailing LF, don't indent if inline)
|
|
413
|
+
*
|
|
414
|
+
* @todo Reuse this with toCdl
|
|
415
|
+
* @param {Array|object|string} expr Expression to render
|
|
416
|
+
* @param {object} env Render environment
|
|
417
|
+
* @param {boolean} [inline=true] Whether to render the expression inline
|
|
418
|
+
* @param {boolean} [nestedExpr=false] Whether to treat the expression as nested
|
|
419
|
+
* @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `nestedExpr === false`.
|
|
420
|
+
* Note: This is a hack for casts() inside groupBy.
|
|
421
|
+
* @returns {string} Rendered expression
|
|
422
|
+
*/
|
|
423
|
+
return function renderExpr(expr, env, inline = true, nestedExpr = false, alwaysRenderCast = false) {
|
|
424
|
+
// Compound expression
|
|
425
|
+
if (Array.isArray(expr)) {
|
|
426
|
+
const tokens = expr.map(item => renderExpr(item, env, inline, nestedExpr));
|
|
427
|
+
return beautifyExprArray(tokens);
|
|
428
|
+
}
|
|
429
|
+
else if (typeof expr === 'object' && expr !== null) {
|
|
430
|
+
if ((nestedExpr || alwaysRenderCast) && expr.cast && expr.cast.type && !expr.cast.target)
|
|
431
|
+
return renderer.explicitTypeCast.call({ inline, nestedExpr }, expr, env);
|
|
432
|
+
return renderExprObject(expr);
|
|
433
|
+
}
|
|
434
|
+
// Not a literal value but part of an operator, function etc - just leave as it is
|
|
435
|
+
// FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
|
|
436
|
+
return renderer.finalize.call({ inline, nestedExpr }, expr, env);
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Various special cases represented as objects
|
|
441
|
+
*
|
|
442
|
+
* @param {object} x Expression
|
|
443
|
+
* @returns {string} String representation of the expression
|
|
444
|
+
*/
|
|
445
|
+
function renderExprObject(x) {
|
|
446
|
+
if (x.list) { // TODO: Does this still exist?
|
|
447
|
+
return `(${x.list.map(item => renderExpr(item, env, inline, false)).join(', ')})`;
|
|
448
|
+
}
|
|
449
|
+
else if (x.val !== undefined) {
|
|
450
|
+
return renderer.val.call({ inline, nestedExpr }, x, env);
|
|
451
|
+
}
|
|
452
|
+
// Enum symbol
|
|
453
|
+
else if (x['#']) {
|
|
454
|
+
return renderer.enum.call({ inline, nestedExpr }, x, env);
|
|
455
|
+
}
|
|
456
|
+
// Reference: Array of path steps, possibly preceded by ':'
|
|
457
|
+
else if (x.ref) {
|
|
458
|
+
return renderer.ref.call({ inline, nestedExpr }, x, env);
|
|
459
|
+
}
|
|
460
|
+
// Function call, possibly with args (use '=>' for named args)
|
|
461
|
+
else if (x.func) {
|
|
462
|
+
if (x.xpr)
|
|
463
|
+
return renderer.windowFunction.call({ inline, nestedExpr }, x, env);
|
|
464
|
+
return renderer.func.call({ inline, nestedExpr }, x, env);
|
|
465
|
+
}
|
|
466
|
+
// Nested expression
|
|
467
|
+
else if (x.xpr) {
|
|
468
|
+
return renderer.xpr.call({ inline, nestedExpr }, x, env);
|
|
469
|
+
}
|
|
470
|
+
// Sub-select
|
|
471
|
+
else if (x.SELECT) {
|
|
472
|
+
return renderer.SELECT.call({ inline, nestedExpr }, x, env);
|
|
473
|
+
}
|
|
474
|
+
else if (x.SET) {
|
|
475
|
+
return renderer.SET.call({ inline, nestedExpr }, x, env);
|
|
476
|
+
}
|
|
477
|
+
else if (x.as && x.cast && x.cast.type && x.cast.target) {
|
|
478
|
+
return renderer.aliasOnly.call({ inline, nestedExpr }, x, env);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
|
|
482
|
+
}
|
|
483
|
+
};
|
|
374
484
|
}
|
|
375
485
|
|
|
376
486
|
/**
|
|
@@ -391,6 +501,7 @@ function getSqlSnippets(options, obj) {
|
|
|
391
501
|
|
|
392
502
|
module.exports = {
|
|
393
503
|
renderFunc,
|
|
504
|
+
getExpressionRenderer,
|
|
394
505
|
beautifyExprArray,
|
|
395
506
|
getNamespace,
|
|
396
507
|
getRealName,
|
package/lib/render/utils/sql.js
CHANGED
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
const { getResultingName } = require('../../model/csnUtils');
|
|
6
6
|
const { smartId, delimitedId } = require('../../sql-identifier');
|
|
7
|
+
const { ModelError } = require('../../base/error');
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Render a given referential constraint as part of a SQL CREATE TABLE statement, or as .hdbconstraint artefact.
|
|
10
11
|
*
|
|
11
12
|
* @param {CSN.ReferentialConstraint} constraint Content of the constraint
|
|
12
13
|
* @param {string} indent Indent to render the SQL with
|
|
13
|
-
* @param {boolean} toUpperCase
|
|
14
|
+
* @param {boolean} toUpperCase Whether to uppercase the identifier
|
|
14
15
|
* @param {CSN.Model} csn CSN
|
|
15
16
|
* @param {CSN.Options} options is needed for the naming mode and the sql dialect
|
|
16
17
|
* @param {boolean} [alterConstraint=false] whether the constraint should be rendered as part of an ALTER TABLE statement
|
|
@@ -121,7 +122,7 @@ function getIdentifierUtils(options) {
|
|
|
121
122
|
function prepareIdentifier(name) {
|
|
122
123
|
// Sanity check
|
|
123
124
|
if (options.toSql.dialect === 'sqlite' && options.toSql.names !== 'plain')
|
|
124
|
-
throw new
|
|
125
|
+
throw new ModelError(`Not expecting ${options.toSql.names} names for 'sqlite' dialect`);
|
|
125
126
|
|
|
126
127
|
|
|
127
128
|
switch (options.toSql.names) {
|
|
@@ -132,7 +133,7 @@ function getIdentifierUtils(options) {
|
|
|
132
133
|
case 'hdbcds':
|
|
133
134
|
return name;
|
|
134
135
|
default:
|
|
135
|
-
throw new
|
|
136
|
+
throw new ModelError(`No matching rendering found for naming mode ${options.toSql.names}`);
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
139
|
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const controlCharacters = /[\u{0000}-\u{001F}]/u;
|
|
4
|
+
const highSurrogate = /[\u{D800}-\u{DBFF}]/u;
|
|
5
|
+
const lowSurrogate = /[\u{DC00}-\u{DFFF}]/u;
|
|
6
|
+
// Either a high surrogate that is NOT followed by a low one or
|
|
7
|
+
// a low surrogate not preceded by a high one.
|
|
8
|
+
const unpairedSurrogate = /[^\u{D800}-\u{DBFF}][\u{DC00}-\u{DFFF}]|[\u{D800}-\u{DBFF}][^\u{DC00}-\u{DFFF}]/u;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns true if the string contains an unpaired unicode surrogate.
|
|
12
|
+
* See <https://en.wikipedia.org/wiki/UTF-16#U+D800_to_U+DFFF>.
|
|
13
|
+
* As a surrogate pair MUST consist of a high one followed by a low surrogate,
|
|
14
|
+
* an unpaired surrogate MUST be escaped.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} str
|
|
17
|
+
* @return {boolean}
|
|
18
|
+
*/
|
|
19
|
+
function hasUnpairedUnicodeSurrogate(str) {
|
|
20
|
+
return unpairedSurrogate.test(str);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Returns true if the string contains control characters such as LF or NUL.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} str
|
|
27
|
+
* @return {boolean}
|
|
28
|
+
*/
|
|
29
|
+
function hasControlCharacters(str) {
|
|
30
|
+
return controlCharacters.test(str);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Escape the given string according to the given specification in `escapes`.
|
|
35
|
+
*
|
|
36
|
+
* `escapes` is an object where the entries are either:
|
|
37
|
+
* - a mapping from character to string, e.g. `{ '"': '"' }`
|
|
38
|
+
* - `control: (codePoint) => str`
|
|
39
|
+
* A function that returns an escape sequence for the given control character.
|
|
40
|
+
* - `unpairedSurrogate: (codePoint) => str`
|
|
41
|
+
* A function that returns an escape sequence for the given unpaired unicode surrogate.
|
|
42
|
+
*
|
|
43
|
+
* Multi-character keys are not allowed.
|
|
44
|
+
*
|
|
45
|
+
* Character escapes take precedence over `control` and `unpairedSurrogate` escapes,
|
|
46
|
+
* i.e. if you do not want to encode LF (`\n`), add an explicit mapping for it, e.g.
|
|
47
|
+
* `{ '\n': '\n' }`.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* You can use `escapeString()` like this:
|
|
51
|
+
* ```js
|
|
52
|
+
* let escaped = escapeString(str, {
|
|
53
|
+
* '"': '\\"',
|
|
54
|
+
* control: (c) => `\\u{${c.toString(16)}}`;
|
|
55
|
+
* unpairedSurrogate: (c) => `\\u{${c.toString(16)}}`;
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @param {string} str
|
|
60
|
+
* @param {object} escapes
|
|
61
|
+
* @returns {string}
|
|
62
|
+
*/
|
|
63
|
+
function escapeString(str, escapes) {
|
|
64
|
+
const output = [];
|
|
65
|
+
|
|
66
|
+
for (let i = 0; i < str.length; ++i) {
|
|
67
|
+
const char = str[i];
|
|
68
|
+
|
|
69
|
+
if (char in escapes) {
|
|
70
|
+
output.push(escapes[char]);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Control Characters: C0
|
|
75
|
+
// See <https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Basic_ASCII_control_codes>
|
|
76
|
+
if (controlCharacters.test(char)) {
|
|
77
|
+
output.push(escapes.control ? escapes.control(char.codePointAt(0)) : char);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Unicode Surrogates
|
|
82
|
+
// These characters appear in _pairs_. A high surrogate must be followed by a low surrogate.
|
|
83
|
+
// If this is not the case, either needs to be encoded. This is also done by JSON.
|
|
84
|
+
// See also <https://docs.microsoft.com/en-us/globalization/encoding/surrogate-pairs>
|
|
85
|
+
if (highSurrogate.test(char)) {
|
|
86
|
+
if (i + 1 >= str.length || !lowSurrogate.test(str[i + 1])) {
|
|
87
|
+
output.push(escapes.unpairedSurrogate ? escapes.unpairedSurrogate(char.codePointAt(0)) : char);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
output.push(char);
|
|
91
|
+
++i;
|
|
92
|
+
output.push(str[i]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else if (lowSurrogate.test(char)) {
|
|
96
|
+
output.push(escapes.unpairedSurrogate ? escapes.unpairedSurrogate(char.codePointAt(0)) : char);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// unhandled / non-special character
|
|
100
|
+
output.push(char);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return output.join('');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = {
|
|
108
|
+
escapeString,
|
|
109
|
+
hasUnpairedUnicodeSurrogate,
|
|
110
|
+
hasControlCharacters,
|
|
111
|
+
};
|
package/lib/sql-identifier.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// an identifier such that the effective name (the one in the DB schema) is
|
|
10
10
|
// the same as the name used in CDS.
|
|
11
11
|
// - 'quoted' and 'hdbcds' for HANA only: similar to the non-provided previous
|
|
12
|
-
// mode, with different
|
|
12
|
+
// mode, with different adaptations to HANA CDS and XS (classic) restrictions.
|
|
13
13
|
|
|
14
14
|
// The main objective of this file is to support the 'plain' mode in a “smart”
|
|
15
15
|
// manner. If we would use the CDS name (after `.` to `_` replacements)
|
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
"prefer-template": "error",
|
|
9
9
|
"no-trailing-spaces": "error",
|
|
10
10
|
"template-curly-spacing":["error", "never"],
|
|
11
|
-
"complexity": ["warn",
|
|
11
|
+
"complexity": ["warn", 40],
|
|
12
12
|
"max-len": "off",
|
|
13
13
|
// Don't enforce stupid descriptions
|
|
14
14
|
"jsdoc/require-param-description": "off",
|
|
15
15
|
"jsdoc/require-returns-description": "off",
|
|
16
|
+
// Sometimes if-else's are more specific
|
|
17
|
+
"sonarjs/prefer-single-boolean-return": "off",
|
|
16
18
|
// Very whiny and nitpicky
|
|
17
19
|
"sonarjs/cognitive-complexity": "off",
|
|
18
20
|
// Does not recognize TS types
|
|
@@ -18,12 +18,11 @@ const { setProp } = require('../../base/model');
|
|
|
18
18
|
* @param {string} prop The property of parent to start at
|
|
19
19
|
* @param {object} customTransformers Map of prop to transform and function to apply
|
|
20
20
|
* @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
|
|
21
|
-
* @param {
|
|
22
|
-
* @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts, drillRef: boolean - whether to drill into infix/args
|
|
21
|
+
* @param {applyTransformationsOptions} [options={}]
|
|
23
22
|
* @param {CSN.Path} path Path to parent
|
|
24
23
|
* @returns {object} parent with transformations applied
|
|
25
24
|
*/
|
|
26
|
-
function applyTransformationsInternal(parent, prop, customTransformers, artifactTransformers,
|
|
25
|
+
function applyTransformationsInternal(parent, prop, customTransformers, artifactTransformers, options, path = []) {
|
|
27
26
|
const transformers = {
|
|
28
27
|
elements: dictionary,
|
|
29
28
|
definitions: dictionary,
|
|
@@ -32,6 +31,7 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
|
|
|
32
31
|
enum: dictionary,
|
|
33
32
|
mixin: dictionary,
|
|
34
33
|
ref: pathRef,
|
|
34
|
+
$origin: () => {}, // no-op
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
const csnPath = [ ...path ];
|
|
@@ -50,7 +50,12 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
|
|
|
50
50
|
* @param {object} node The value of node[_prop]
|
|
51
51
|
*/
|
|
52
52
|
function standard( _parent, _prop, node ) {
|
|
53
|
-
if (!node || typeof node !== 'object' ||
|
|
53
|
+
if (!node || typeof node !== 'object' ||
|
|
54
|
+
!{}.propertyIsEnumerable.call( _parent, _prop ) ||
|
|
55
|
+
(typeof _prop === 'string' && _prop.startsWith('@')) ||
|
|
56
|
+
(options.skipIgnore && node._ignore) ||
|
|
57
|
+
(options.skipStandard && options.skipStandard[_prop])
|
|
58
|
+
)
|
|
54
59
|
return;
|
|
55
60
|
|
|
56
61
|
csnPath.push( _prop );
|
|
@@ -80,7 +85,7 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
|
|
|
80
85
|
*/
|
|
81
86
|
function dictionary( node, _prop, dict ) {
|
|
82
87
|
// Allow skipping dicts like actions in forHanaNew
|
|
83
|
-
if (options.skipDict && options.skipDict[_prop])
|
|
88
|
+
if (options.skipDict && options.skipDict[_prop] || dict === null || dict === undefined) // with universal CSN, dicts might be null
|
|
84
89
|
return;
|
|
85
90
|
csnPath.push( _prop );
|
|
86
91
|
for (const name of Object.getOwnPropertyNames( dict ))
|
|
@@ -101,7 +106,10 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
|
|
|
101
106
|
function definitions( node, _prop, dict ) {
|
|
102
107
|
csnPath.push( _prop );
|
|
103
108
|
for (const name of Object.getOwnPropertyNames( dict )) {
|
|
104
|
-
const skip = options && options.
|
|
109
|
+
const skip = (options && options.allowArtifact && !options.allowArtifact(dict[name], name)) ||
|
|
110
|
+
(options && options.skipArtifact && options.skipArtifact(dict[name], name)) ||
|
|
111
|
+
(options && options.skip && options.skip.includes(dict[name].kind)) ||
|
|
112
|
+
false;
|
|
105
113
|
if (!skip) {
|
|
106
114
|
artifactTransformers.forEach(fn => fn(dict, name, dict[name]));
|
|
107
115
|
standard( dict, name, dict[name] );
|
|
@@ -152,13 +160,15 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
|
|
|
152
160
|
* @param {object} csn CSN to enrich in-place
|
|
153
161
|
* @param {object} customTransformers Map of _prop to transform and function to apply
|
|
154
162
|
* @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
|
|
155
|
-
* @param {
|
|
156
|
-
* @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts, drillRef: boolean - whether to drill into infix/args
|
|
163
|
+
* @param {applyTransformationsOptions} [options={}]
|
|
157
164
|
* @returns {object} CSN with transformations applied
|
|
158
165
|
*/
|
|
159
|
-
function applyTransformations( csn, customTransformers = {}, artifactTransformers = [],
|
|
166
|
+
function applyTransformations( csn, customTransformers = {}, artifactTransformers = [], options = { } ) {
|
|
167
|
+
if (options.skipIgnore === undefined)
|
|
168
|
+
options.skipIgnore = true;
|
|
169
|
+
|
|
160
170
|
if (csn && csn.definitions)
|
|
161
|
-
return applyTransformationsInternal(csn, 'definitions', customTransformers, artifactTransformers,
|
|
171
|
+
return applyTransformationsInternal(csn, 'definitions', customTransformers, artifactTransformers, options);
|
|
162
172
|
return csn;
|
|
163
173
|
}
|
|
164
174
|
|
|
@@ -176,14 +186,27 @@ function applyTransformations( csn, customTransformers = {}, artifactTransformer
|
|
|
176
186
|
* @param {object} parent The "parent" of which we transform a property of
|
|
177
187
|
* @param {string} prop The property of parent to start at
|
|
178
188
|
* @param {object} customTransformers Map of prop to transform and function to apply
|
|
189
|
+
* @param {applyTransformationsOptions} [options={}]
|
|
179
190
|
* @param {CSN.Path} path Path pointing to parent
|
|
180
191
|
* @returns {object} parent[prop] with transformations applied
|
|
181
192
|
*/
|
|
182
|
-
function applyTransformationsOnNonDictionary(parent, prop, customTransformers = {}, path = []) {
|
|
183
|
-
return applyTransformationsInternal(parent, prop, customTransformers, [],
|
|
193
|
+
function applyTransformationsOnNonDictionary(parent, prop, customTransformers = {}, options = {}, path = []) {
|
|
194
|
+
return applyTransformationsInternal(parent, prop, customTransformers, [], options, path)[prop];
|
|
184
195
|
}
|
|
185
196
|
|
|
186
197
|
module.exports = {
|
|
187
198
|
applyTransformations,
|
|
188
199
|
applyTransformationsOnNonDictionary,
|
|
189
200
|
};
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* @typedef {object} applyTransformationsOptions
|
|
205
|
+
* @property {(artifact, name) => boolean} [allowArtifact] to only allow certain artifacts
|
|
206
|
+
* @property {(artifact, name) => boolean} [skipArtifact] to skip certain artifacts
|
|
207
|
+
* @property {boolean} [drillRef] whether to drill into infix/args
|
|
208
|
+
* @property {string[]} [skip] skip definitions from certain kind
|
|
209
|
+
* @property {object} [skipStandard] stop drill-down on certain "standard" props
|
|
210
|
+
* @property {object} [skipDict] stop drill-down on certain "dictionary" props
|
|
211
|
+
* @property {boolean} [skipIgnore=true] Whether to skip _ignore elements or not
|
|
212
|
+
*/
|
|
@@ -158,7 +158,7 @@ function processAssertUnique(csn, options, error, info) {
|
|
|
158
158
|
function toRef(val) {
|
|
159
159
|
let ref = val.split('.');
|
|
160
160
|
const [ head, ...tail ] = ref;
|
|
161
|
-
if (
|
|
161
|
+
if (head === '$self' || head === '$projection')
|
|
162
162
|
ref = tail;
|
|
163
163
|
return {
|
|
164
164
|
ref: ref.map((ps) => {
|