Socket
Socket
Sign inDemoInstall

@sap/cds-compiler

Package Overview
Dependencies
Maintainers
1
Versions
105
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sap/cds-compiler - npm Package Compare versions

Comparing version 3.9.4 to 4.0.0

share/messages/def-duplicate-autoexposed.md

34

bin/cdsc.js

@@ -33,3 +33,2 @@ #!/usr/bin/env node

const { term } = require('../lib/utils/term');
const { splitLines } = require('../lib/utils/file');
const { addLocalizationViews } = require('../lib/transform/localized');

@@ -509,3 +508,2 @@ const { availableBetaFlags } = require('../lib/base/model');

else if (options.noMessageContext) {
// TODO: currently, ‹↓› = "downgradable" is only shown here
messages.filter(msg => (messageLevels[msg.severity] <= options.warning))

@@ -515,26 +513,16 @@ .forEach(msg => log(main.messageString(msg, normalizeFilename, !!options.noMessageId, false, options.testMode && 'compile'))); // TODO: use module name

else {
// Contains file-contents that are split at '\n'. Try to avoid multiple `.split()` calls.
const splitCache = Object.create(null);
const sourceLines = (name) => {
if (!splitCache[name])
splitCache[name] = fileCache[name] ? splitLines(fileCache[name]) : fileCache;
return splitCache[name];
let hasAtLeastOneExplanation = false;
const config = {
normalizeFilename,
noMessageId: !!options.noMessageId,
hintExplanation: true,
color: options.color,
module: options.testMode && 'compile', // TODO: use module name
sourceMap: fileCache,
cwd: '',
};
let hasAtLeastOneExplanation = false;
messages.filter(msg => messageLevels[msg.severity] <= options.warning).forEach((msg) => {
hasAtLeastOneExplanation = hasAtLeastOneExplanation || main.hasMessageExplanation(msg.messageId);
const name = msg.$location && msg.$location.file;
const fullFilePath = name ? path.resolve('', name) : undefined;
log(main.messageStringMultiline(msg, {
normalizeFilename,
noMessageId: !!options.noMessageId,
withLineSpacer: true,
hintExplanation: true,
color: options.color,
}));
if (fullFilePath && msg.$location.line) {
// A message context only makes sense, if we have at least start line
const context = sourceLines(fullFilePath);
log(main.messageContext(context, msg, { color: options.color }));
}
log(main.messageStringMultiline(msg, config));
log(); // newline

@@ -541,0 +529,0 @@ });

@@ -7,7 +7,7 @@ #!/usr/bin/env node

// support for the CDS LSP server and to detect potential issues with
// corrupted, incomplete or errorneous CDL sources.
// corrupted, incomplete or erroneous CDL sources.
//
// The output could be used directly by some editors, e.g. Emacs. The
// capabilities supported at the moments is: complete - work in progress.
// Planned are: gotoDefinition, highlight (for syntax highighting).
// Planned are: gotoDefinition, highlight (for syntax highlighting).

@@ -172,3 +172,3 @@ /* eslint no-console:off */

const msg = (model.messages || model.options && model.options.messages).find(
m => m[prop] && m.location.line === line && m.location.col === col && m.location.file === frel
m => m[prop] && m.$location.line === line && m.$location.col === col && m.$location.file === frel
);

@@ -175,0 +175,0 @@ return msg && msg[prop];

@@ -10,12 +10,100 @@ # ChangeLog for cds compiler and backends

## Version 3.9.4 - 2023-06-07
## Version 4.0.0 - 2023-06-06
### Added
- Calculated elements "on-write" are now supported, e.g. `elem = field + 1 stored;` will be rendered in SQL
as `GENERATED ALWAYS AS`.
- compiler:
+ `returns` of actions and functions can now be annotated, e.g.
```cds
action A() returns @direct { … };
annotate A with returns {
@anno val;
}
```
+ It is now possible to use `*/` inside doc comments by escaping it `*\/`. Only this exact string can be escaped.
+ Functions `parse.expr` and `parse.cql` now support `group by`, `having`, `order by`, `limit`, and `offset` in infix filters.
+ In expressions, you can now use function names after the `new` keyword which do
not start with `st_`, if the name is followed by an open parenthesis.
### Changed
- API:
+ `messageContext()` is now deprecated; use `messageStringMultiline()` instead with `config.sourceMap`.
+ `messageString(err, config)` has a new signature; the old one is still supported for backward compatibility.
+ Option `docComment: false` now removes doc comments during compilation even for CSN.
If this option is not defined, doc comments are checked, but not put into CSN.
- compiler:
+ CSN: Specified elements of queries are now resolved and checked (previously ignored except for annotations).
Type properties (`length`, …) and some element properties are now taken from the specified elements
instead of relying only on the elements inferred by the compiler.
+ CSN: Compiler accepts CSN input with CSN version `0.1.0` (produced by cds-compiler version 1.10.0 and older)
only if the version attribute is properly set, i.e. `"version": {"csn": "0.1.0"}`.
+ CSN: Type properties (`length`, `precision`, `scale`, and `srid`) next to `elements` or `items` in CSN input
is now an error. Previously ignored.
+ An extension of the form `extend Def with { name = 42 };` is now represented in parsed CSN as
adding a calculated element.
+ `having` as the first word in an infix filter is now interpreted as keyword. Write `![having]`
to have it parsed as identifier.
+ If a definition overrides elements of an included definition, it is sorted according
to the included definition's element order. This is similar to how `*` works in projections.
It is no longer possible to overwrite a scalar element with a structure and vice versa.
+ Two included definitions cannot have the same element. The including entity must override the element.
+ If a type with properties precision and scale is extended, the `extend` statement must extend both properties.
+ `annotate E:elem with actions {};` is now a parser error and no longer a compiler error.
Only relevant if you use `parseCdl`-style CSN.
+ Annotations (and other properties) are now propagated to `returns` as well, if the returned artifact
is a non-entity, e.g. a type.
+ `annotate E with returns {…}` is now only applied to actions/functions. Previous compiler versions
autocorrected the `annotate` statements to apply them to an entity's elements.
+ Calculated elements can't refer to localized elements.
+ Table aliases can't be used in `extend Query with columns {…}` any longer. Use direct element references instead.
+ Table alias and mixin names can no longer start with `$`. Choose a different name. With this change
we avoid unexpected name resolution effects in combination with built-in `$`-variables.
+ A semicolon is now required after a type definition like `type T : many {} null`.
+ It is no longer possible to write `type of $self.‹elem›` to refer to the element `‹Def›.‹elem›`
where `‹Def›` is the main artifact where the type expression is embedded in. Replace by `type of <Def>:‹elem›`.
+ Message ID `duplicate-autoexposed` was changed to `def-duplicate-autoexposed`.
- Update OData vocabularies 'Common', 'UI'.
- to.sql:
+ Change default `length` for `cds.String` for all SQL dialects except `hana` to 255.
+ for the sql dialect `postgres`, the `ON DELETE RESTRICT` / `ON UPDATE RESTRICT` rules
are omitted from the referential constraint definitions.
- to.cdl:
+ If associations are used inside `items`, an error is emitted instead of rendering invalid CDL.
+ `items` inside `items`, where the outer one does not have a `type`, is now always an error,
because it can't be rendered in CDL
### Fixed
- compiler: `USING` empty files were incorrectly marked as "not found".
- compiler:
+ `parseCdl` CSN did not include correct `...` entries for annotations containing `... up to`
+ Type references inside calculated elements were not always correctly resolved.
+ `USING` empty files were incorrectly marked as "not found".
+ If an association was inside `items`, e.g. via type chains, the compiler crashes instead of emitting proper errors.
- Localized convenience views for projections (not views) did not have references rewritten.
This only affects CSN, the SQL result was correct.
- to.edm(x): Render correct EntitySetPath and annotation target path for actions/functions
with explicit binding parameter.
- Calculated elements in composition-of-aspect lost their `value` when generating composition targets.
- to.sql/to.hdi/for.odata: Foreign keys in ON-conditions were not always properly checked and flattened if explicit
`keys` were provided that reference structures.
- Extending bound actions with elements is not possible, but was not reported by the compiler and elements were silently lost.
- for.odata:
+ Don't propagate `@odata { Type, MaxLength, Precision, Scale }` from structured to flattened leaf elements.
+ Remove `type: null` attribute from element definitions referencing an undefined type via `type of`.
- to.edm(x):
+ Don't reject untyped properties that are API typed with a valid `@odata.Type` annotation.
+ Render correct `EntitySetPath` and annotation target path for actions/functions with explicit binding parameter.
- to.cdl: ParseCdl-style CSN containing annotations with `...` were not properly rendered.
### Removed
- NodeJs 14 is no longer supported.
- `CompileMessage` no longer has property `location`, which was deprecated in v2.1.0, but `$location`,
which is supported since v2.1.0
- "Smart type references" such as `Entity.myElement` instead of `Entity:myElement` are removed, because since
compiler v2, `Entity.myElement` could also be a definition, creating ambiguities.
- Element type references can no longer follow associations, i.e. `E:assoc.id` is not allowed.
## Version 3.9.2 - 2023-04-27

@@ -22,0 +110,0 @@

@@ -10,3 +10,8 @@ # CDS Compiler API Documentation

## Important Notes
The cds-compiler expects that `Object.prototype` is not polluted by enumerable
properties. Non-enumerable properties such as custom functions are fine.
[TypeDoc]: https://typedoc.org/

@@ -11,2 +11,18 @@ # ChangeLog of Beta Features for cdx compiler and backends

## Version 4.0.0 - 2023-06-06
### Removed `v4preview`
`v4preview` is now the default.
### Removed `calculatedElementsOnWrite`
It is now enabled by default. The feature is still "beta".
### Removed `ignoreAssocPublishingInUnion`
The behavior is now the default. If you still want to get an error message for
ignored published associations in unions, you can change the severity of message
`query-ignoring-assoc-in-union` to an error via `options.severities`.
## Version 3.9.2 - 2023-04-27

@@ -18,3 +34,3 @@

## Version 3.9.0 - 2023-04-XX
## Version 3.9.0 - 2023-04-20

@@ -21,0 +37,0 @@ ### Added `calculatedElementsOnWrite`

@@ -14,2 +14,30 @@ # ChangeLog of deprecated Features for cdx compiler and backends

## Version 4.0.0 - 2023-06-06
### Added `downgradableErrors`
Allow to change the severity of some errors which are meant to stay errors in v4.
### Added `includesNonShadowedFirst`
Use this flag to keep adding elements from included definitions first, example:
```cds
entity A { one: Integer; two: String(10); three: Integer; }
entity E : A { two: String(12); }
// v3: one, three, two
// v4: one, two, three
```
### Added `ignoreSpecifiedQueryElements`
Use this flag if you want to ignore a query's `elements`, except for annotations and doc comments.
cds-compiler v3 and earlier simply ignored a query element except for its annotations.
cds-compiler v4 resolves the element's type.
### Removed `autoCorrectOrderBySourceRefs`
Instead of this deprecated flag, you can downgrade error message `ref-deprecated-orderby`.
## Version 3.1.0 - 2022-08-04

@@ -16,0 +44,0 @@

@@ -8,3 +8,3 @@ {

// we actually do not extend airbnb-base, as it weakens some eslint:recommended rules
"extends": ["../../.eslintrc-ydkjsi.json", "plugin:jsdoc/recommended"],
"extends": ["plugin:jsdoc/recommended", "../../.eslintrc-ydkjsi.json"],
"parserOptions": {

@@ -11,0 +11,0 @@ "ecmaVersion": 2022,

@@ -167,11 +167,11 @@ /** @module API */

/**
* Transform a CSN like to.sql
* Transform a CSN like to.sql().
* Expects that internalOptions have been validated via prepareOptions.to.sql().
*
* @param {CSN.Model} csn Plain input CSN
* @param {SqlOptions} [options={}] Options
* @param {SqlOptions} internalOptions Options
* @returns {CSN.Model} CSN transformed like to.sql
* @private
*/
function forSql( csn, options = {} ) {
const internalOptions = prepareOptions.to.sql(options);
function csnForSql( csn, internalOptions ) {
internalOptions.transformation = 'sql';

@@ -182,3 +182,18 @@ const transformedCsn = forRelationalDB.transformForRelationalDBWithCsn(csn, internalOptions, 'to.sql');

}
/**
* Transform a CSN like to.sql(). Also performs options-checks.
* Pseudo-public version of csnForSql().
*
* @param {CSN.Model} csn Plain input CSN
* @param {SqlOptions} [options={}] Options
* @returns {CSN.Model} CSN transformed like to.sql
* @private
*/
function forSql( csn, options = {} ) {
const internalOptions = prepareOptions.to.sql(options);
return csnForSql(csn, internalOptions);
}
/**
* Transform a CSN like to.hdi

@@ -228,3 +243,3 @@ *

// we need the CSN for view sorting
const transformedCsn = forSql(csn, options);
const transformedCsn = csnForSql(csn, internalOptions);
const sqls = toSql.toSqlDdl(transformedCsn, internalOptions);

@@ -353,3 +368,4 @@

// Prepare after-image.
const afterImage = forSql(csn, internalOptions);
const afterImage = internalOptions.filterCsn
? diffFilter.csn(csnForSql(csn, internalOptions)) : csnForSql(csn, internalOptions);
// Compare both images.

@@ -851,4 +867,6 @@ const diff = modelCompare.compareModels(beforeImage || afterImage, afterImage, internalOptions);

const msg = info( 'api-recompiled-csn', location.emptyLocation('csn.json'), {}, 'CSN input had to be recompiled' );
if (options.internalMsg)
msg.error = err; // Attach original error
if (options.internalMsg || options.testMode)
msg.error = err; // Attach original error;
if (options.testMode) // Attach recompilation reason in testMode
msg.message += `\n ↳ cause: ${ err.message }`;

@@ -855,0 +873,0 @@ // next line to be replaced by CSN parser call which reads the CSN object

@@ -52,2 +52,4 @@ 'use strict';

const privateOptions = [
// Not callable via cdsc, keep private for now until we are sure that we want this
'filterCsn',
'lintMode',

@@ -54,0 +56,0 @@ 'fuzzyCsnError',

@@ -10,2 +10,3 @@ 'use strict';

super(`cds-compiler assertion failed: ${ message }`);
this.code = 'ERR_CDS_COMPILER_ASSERTION';
}

@@ -21,2 +22,3 @@ }

super(`cds-compiler model error: ${ message }`);
this.code = 'ERR_CDS_COMPILER_MODEL';
}

@@ -23,0 +25,0 @@ }

@@ -44,3 +44,3 @@ // Central registry for messages.

* - 'deprecated' = severity can only be changed with deprecated.downgradableErrors
* - 'v3' = currently like true, but should be 'deprecated' in v4
* - 'v4' = currently like true, but should be 'deprecated' in v5
*

@@ -56,2 +56,3 @@ * @type {Object<string, MessageConfig>}

'anno-invalid-sql-struct': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
'anno-invalid-sql-calc': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
'anno-invalid-sql-kind': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"

@@ -65,10 +66,12 @@ 'anno-invalid-sql-view': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"

'anno-undefined-param': { severity: 'Warning' },
'anno-unexpected-ellipsis': { severity: 'Error', configurableFor: 'v3' },
'anno-unexpected-ellipsis': { severity: 'Error', configurableFor: 'deprecated' },
'args-expected-named': { severity: 'Error', configurableFor: 'deprecated' }, // future --sloppy
'args-no-params': { severity: 'Error', configurableFor: 'deprecated' }, // future --sloppy
'args-undefined-param': { severity: 'Error', configurableFor: 'deprecated' }, // future --sloppy
'args-expecting-named': { severity: 'Error' },
'args-no-params': { severity: 'Error' },
'args-undefined-param': { severity: 'Error' },
'assoc-in-array': { severity: 'Error', configurableFor: 'deprecated' }, // not supported yet
'assoc-as-type': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: allow more, but not all
'name-invalid-dollar-alias': { severity: 'Error', configurableFor: true },
'type-invalid-items': { severity: 'Error' }, // not supported yet
'assoc-as-type': { severity: 'Error' }, // TODO: allow more, but not all
'def-unexpected-paramview-assoc': { severity: 'Error' },

@@ -80,23 +83,27 @@ 'def-unexpected-calcview-assoc': { severity: 'Error' },

'expr-no-filter': { severity: 'Error', configurableFor: 'deprecated' },
'def-duplicate-autoexposed': { severity: 'Error' },
'def-unexpected-default': { severity: 'Error', configurableFor: 'test' },
'expr-no-filter': { severity: 'Error' },
'empty-type': { severity: 'Info' }, // only still an error in old transformers
// TODO: rename to ref-expected-XYZ
'expected-type': { severity: 'Error' },
'ref-deprecated-orderby': { severity: 'Error', configurableFor: true },
'ref-expecting-type': { severity: 'Error' },
'ref-sloppy-type': { severity: 'Error' },
'ref-unexpected-self': { severity: 'Error' },
'ref-expecting-bare-aspect': { severity: 'Error' },
'type-unexpected-typeof': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: make it non-config
'type-unexpected-typeof': { severity: 'Error' },
'type-ignoring-argument': { severity: 'Error', configurableFor: true },
'type-expected-builtin': { severity: 'Error', configurableFor: true },
'expected-actionparam-type': { severity: 'Error' },
'type-expecting-service-target': { severity: 'Error', configurableFor: true },
'ref-expecting-action-param-type': { severity: 'Error' },
'ref-sloppy-actionparam-type': { severity: 'Error' },
'expected-event-type': { severity: 'Error' },
'ref-expecting-event-type': { severity: 'Error' }, // TODO: Test coverage
'ref-sloppy-event-type': { severity: 'Error' },
'expected-struct': { severity: 'Error' },
'expected-const': { severity: 'Error' },
'expected-entity': { severity: 'Error' },
'expected-source': { severity: 'Error' },
'expected-target': { severity: 'Error' },
'ref-expecting-struct': { severity: 'Error' },
'ref-expecting-const': { severity: 'Error' },
'ref-expecting-entity': { severity: 'Error' },
'ref-expecting-source': { severity: 'Error' },
'ref-expecting-target': { severity: 'Error' },
'ref-sloppy-target': { severity: 'Warning' },

@@ -110,3 +117,3 @@

'param-default': { severity: 'Error', configurableFor: 'deprecated' }, // not supported yet
'param-default': { severity: 'Error' }, // not supported yet

@@ -123,3 +130,3 @@ 'query-undefined-element': { severity: 'Error' },

'ref-autoexposed': { severity: 'Error', configurableFor: 'deprecated' },
'ref-unexpected-autoexposed': { severity: 'Error' },
// Published! Used in @sap/cds-lsp; if renamed, add to oldMessageIds and contact colleagues

@@ -131,3 +138,3 @@ 'ref-undefined-art': { severity: 'Error' },

'ref-unknown-var': { severity: 'Info', errorFor: [ 'to.hdbcds', 'to.sql', 'to.hdi', 'to.rename' ] },
'ref-obsolete-parameters': { severity: 'Error', configurableFor: true },
'ref-obsolete-parameters': { severity: 'Error', configurableFor: 'v4' },
// does not hurt us, but makes it tedious to detect parameter refs

@@ -144,16 +151,19 @@ 'ref-undefined-param': { severity: 'Error' },

'service-nested-context': { severity: 'Error', configurableFor: true }, // does not hurt compile, TODO
'service-nested-service': { severity: 'Error', configurableFor: 'deprecated' }, // not supported yet
'service-nested-service': { severity: 'Error' }, // not supported yet; TODO: configurableFor:'test'?
'expr-unexpected-operator': { severity: 'Error', configurableFor: true },
'syntax-deprecated-auto-union': { severity: 'Error', configurableFor: 'v4' },
// Published! Used in @sap/cds-lsp; if renamed, add to oldMessageIds and contact colleagues
// Also used by other projects that rely on double-quotes for delimited identifiers.
'syntax-deprecated-ident': { severity: 'Error', configurableFor: true },
'syntax-deprecated-property': { severity: 'Error', configurableFor: 'v4' }, // v0 prop
'syntax-deprecated-value': { severity: 'Error', configurableFor: 'v4' }, // v0 prop
// 'syntax-duplicate-annotate' came late with v3 - make it configurable as
// fallback, but then parse.cdl is not supposed to work correctly (it can
// then either issue an error or produce a CSN missing some annotations):
'syntax-duplicate-annotate': { severity: 'Error', configurableFor: 'v3' },
'syntax-duplicate-annotate': { severity: 'Error', configurableFor: 'deprecated' },
'syntax-duplicate-clause': { severity: 'Error', configurableFor: true },
'syntax-duplicate-equal-clause': { severity: 'Warning' },
'syntax-invalid-name': { severity: 'Error', configurableFor: 'v3' },
'syntax-invalid-name': { severity: 'Error', configurableFor: 'deprecated' },
'syntax-missing-as': { severity: 'Error', configurableFor: true },

@@ -163,5 +173,6 @@ 'syntax-unexpected-null': { severity: 'Error', configurableFor: true },

'syntax-unknown-escape': { severity: 'Error', configurableFor: true },
'syntax-unsupported-masked': { severity: 'Error', configurableFor: 'v3' },
'syntax-unsupported-masked': { severity: 'Error', configurableFor: 'deprecated' },
'syntax-unexpected-sql-clause': { severity: 'Error' }, // TODO: configurableFor:'tests'?
'type-managed-composition': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: non-config
'type-managed-composition': { severity: 'Error' },
'type-unsupported-precision-change': { severity: 'Error'},

@@ -174,4 +185,4 @@

'unexpected-keys-for-composition': { severity: 'Error' }, // TODO: more than 30 chars
'unmanaged-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing
'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
'unmanaged-as-key': { severity: 'Error' }, // is confusing
'composition-as-key': { severity: 'Error' }, // is confusing and not supported
'def-invalid-key-cardinality': { severity: 'Error' },

@@ -204,2 +215,3 @@ // Published! Used in @cap-js-community/odata-v2-adapter; if renamed, add to oldMessageIds

'odata-anno-def': { severity: 'Info', configurableFor: true },
'query-ignoring-assoc-in-union': { severity: 'Info' }
};

@@ -213,2 +225,4 @@

'old-anno-duplicate': 'anno-duplicate', // Example
'assoc-in-array': 'type-invalid-items',
'duplicate-autoexposed': 'def-duplicate-autoexposed',
});

@@ -280,7 +294,7 @@

},
'name-invalid-dollar-alias': { // Warning
std: 'An alias name starting with $(NAME) might shadow a special variable - replace by another name',
$tableAlias: 'A table alias name starting with $(NAME) might shadow a special variable - replace by another name',
$tableImplicit: 'The resulting table alias starts with $(NAME) and might shadow a special variable - specify another name with $(KEYWORD)',
mixin: 'A mixin name starting with $(NAME) might shadow a special variable - replace by another name',
'name-invalid-dollar-alias': {
std: 'An alias name starting with $(NAME) might shadow a special variable; replace by another name',
$tableAlias: 'A table alias name must not start with $(NAME); choose another name',
$tableImplicit: 'The resulting table alias starts with $(NAME); choose another name with $(KEYWORD)',
mixin: 'A mixin name starting with $(NAME) might shadow a special variable; replace by another name',
},

@@ -333,2 +347,6 @@

},
'syntax-invalid-source': {
std: 'The given source is invalid', // unused
'cdl-stackoverflow': 'The parser ran into a stack overflow. Does your CDS file contain too many nested artifacts?'
},
'syntax-missing-ellipsis': 'Expecting an array item $(NEWCODE) after an item with $(CODE)',

@@ -370,2 +388,7 @@ 'syntax-unexpected-ellipsis': {

// 'syntax-duplicate-wildcard'
'syntax-invalid-path-separator': {
std: 'Invalid reference path separator', // unused
dot: 'Use a $(NEWCODE), not a $(CODE) after the arguments or filter on an entity',
colon: 'Use a $(NEWCODE), not a $(CODE) between the element names in a reference',
},
// 'syntax-ignoring-doc-comment' (Info)

@@ -394,2 +417,5 @@ 'syntax-unexpected-reserved-word': '$(CODE) is a reserved word - write $(DELIMITED) instead if you want to use it as name',

},
'syntax-unexpected-sql-clause': {
std: 'Unexpected $(KEYWORD) clause for path filter',
},

@@ -402,3 +428,3 @@ // Syntax messages, CSN parser - default: Error ------------------------------

},
'syntax-deprecated-property': { // Warning
'syntax-deprecated-property': { // Configurable error
std: 'Deprecated property $(PROP)', // unused

@@ -408,5 +434,4 @@ 'zero': 'Deprecated CSN v0.1.0 property $(PROP) is ignored',

},
'syntax-deprecated-value': { // Warning
'syntax-deprecated-value': { // Configurable error
std: 'Deprecated representation of the value in property $(PROP)',
replace: 'Replace value in $(PROP) by $(VALUE)',
'zero-parens': 'Deprecated CSN v0.1.0 representation of expressions in parentheses',

@@ -475,5 +500,5 @@ 'zero-replace': 'Replace CSN v0.1.0 value in $(PROP) by $(VALUE)',

std: 'Can\'t include artifact with calculated element',
event: 'An event can\'t include an entity with calculated elements',
type: 'A type can\'t include an entity with calculated elements',
annotation: 'An annotation can\'t include an entity with calculated elements',
event: 'An event can\'t include an artifact with calculated elements',
type: 'A type can\'t include an artifact with calculated elements',
annotation: 'An annotation can\'t include an artifact with calculated elements',
},

@@ -507,2 +532,3 @@ 'def-unsupported-calc-elem': {

// location at erroneous reference (if possible)
'ref-deprecated-orderby': 'Replace source element reference $(ID) by $(NEWCODE); auto-corrected',
'ref-unexpected-self': 'Unexpected $(ID) reference; is valid only in ON-conditions',

@@ -515,3 +541,3 @@ 'ref-undefined-def': {

'ref-undefined-param': 'Entity $(ART) has no parameter $(ID)',
'ref-undefined-art': 'No artifact has been found with name $(NAME)',
'ref-undefined-art': 'No artifact has been found with name $(ART)',
// TODO: proposal 'No definition found for $(NAME)',

@@ -521,3 +547,5 @@ 'ref-undefined-element': {

element: 'Artifact $(ART) has no element $(MEMBER)',
target: 'Target entity $(ART) has no element $(ID)',
aspect: 'Element $(ID) has not been found in the anonymous target aspect',
query: 'The current query has no element $(ART)',
virtual: 'Element $(ART) has not been found. Use $(CODE) to add virtual elements in queries'

@@ -528,2 +556,3 @@ },

alias: 'Variable $(ID) has not been found. Use table alias $(ALIAS) to refer an element with the same name',
self: 'Variable $(ID) has not been found. Use $(ALIAS) to refer an element with the same name',
},

@@ -534,3 +563,3 @@ 'ref-unknown-var': {

'ref-unexpected-draft-enabled': 'Composition in draft-enabled entity can\'t lead to another entity with $(ANNO)',
'ref-rejected-on': {
'ref-rejected-on': { // TODO: currently not used - just remove?
std: 'Do not refer to a artefact like $(ID) in the explicit ON of a redirection', // Not used

@@ -568,16 +597,31 @@ mixin: 'Do not refer to a mixin like $(ID) in the explicit ON of a redirection',

std: 'Unexpected reference to calculated element',
on: 'Calculated elements can\'t be used in ON-conditions of unmanaged associations',
fkey: 'Calculated elements can\'t be used as foreign keys for managed associations',
on: 'Calculated elements (on-read) can\'t be used in ON-conditions of unmanaged associations',
fkey: 'Calculated elements (on-read) can\'t be used as foreign keys for managed associations',
},
'ref-unexpected-localized': {
std: 'Unexpected reference to localized element $(NAME)', // "std" currently unused
calc: 'Calculated elements can\'t refer to localized elements',
},
'ref-unexpected-navigation': {
std: 'Can\'t follow association $(ID) of path $(ELEMREF) in an ON-condition; only foreign keys can be referred to, but not $(NAME)',
unmanaged: 'Can\'t follow unmanaged association $(ID) of path $(ELEMREF) in an ON-condition; only foreign keys can be referred to',
unmanagedleaf: 'Unexpected unmanged association as final path step of $(ELEMREF) in an ON-condition',
std: 'Can\'t follow association $(ID) in path $(ELEMREF) in an ON-condition; only foreign keys can be referred to, but not $(NAME)',
unmanaged: 'Can\'t follow unmanaged association $(ID) in path $(ELEMREF) in an ON-condition',
unmanagedleaf: 'Unexpected unmanaged association as final path step of $(ELEMREF) in an ON-condition',
'calc-non-fk': 'Can\'t follow association $(ID) in path $(ELEMREF) in a stored calculated element; only foreign keys can be referred to, but not $(NAME)',
'calc-unmanaged': 'Can\'t follow unmanaged association $(ID) in path $(ELEMREF) in a stored calculated element',
},
'ref-unexpected-filter': {
std: 'Unexpected filter in path $(ELEMREF)', // unused
'on-condition': 'ON-conditions must not contain filters, step $(ID) of path $(ELEMREF)',
calc: 'Unexpected filter in path $(ELEMREF) of stored calculated element; only simple paths can be used here'
},
'ref-unexpected-args': {
std: 'Unexpected arguments in path $(ELEMREF)', // unused
'on-condition': 'ON-conditions must not contain parameters, step $(ID) of path $(ELEMREF)',
calc: 'Unexpected arguments in path $(ELEMREF) of stored calculated element; only simple paths can be used here'
},
'type-unexpected-typeof': {
std: 'Unexpected $(KEYWORD) for the type reference here',
type: 'Unexpected $(KEYWORD) in type of a type definition',
event: 'Unexpected $(KEYWORD) for the type of an event',
type: 'Unexpected $(KEYWORD) for the type of a type definition',
param: 'Unexpected $(KEYWORD) for the type of a parameter definition',

@@ -591,6 +635,7 @@ select: 'Unexpected $(KEYWORD) for type references in queries',

'type-unexpected-argument': {
std: 'Too many arguments for type $(ART)',
std: 'Too many arguments for type $(ART)', // we use config 'type-ignoring-argument' instead
type: 'Unexpected argument $(PROP) for type $(ART) with base type $(TYPE)',
builtin: 'Unexpected argument $(PROP) for type $(ART)',
'non-scalar': 'Only scalar types can have arguments',
// TODO: the following variants look like for an upcoming type-invalid-argument:
max: 'Expecting argument $(PROP) for type $(TYPE) to not exceed $(NUMBER)',

@@ -601,5 +646,12 @@ min: 'Expecting argument $(PROP) for type $(TYPE) to be greater than or equal to $(NUMBER)',

'type-invalid-items': {
std: 'Unexpected $(PROP)', // unused
nested: 'Unexpected $(PROP) inside $(PROP)',
assoc: 'Unexpected association inside $(PROP)',
comp: 'Unexpected composition inside $(PROP)',
},
'anno-builtin': 'Builtin types should not be annotated. Use custom type instead',
'anno-undefined-def': 'Artifact $(ART) has not been found',
'anno-undefined-art': 'No artifact has been found with name $(NAME)',
'anno-undefined-def': 'Artifact $(ART) has not been found', // TODO: ext-
'anno-undefined-art': 'No artifact has been found with name $(ART)',
'anno-undefined-element': {

@@ -653,2 +705,8 @@ std: 'Element $(NAME) has not been found',

},
'type-unexpected-many': {
std: 'Unexpected arrayed type', // unused variant
calc: 'An arrayed type can\'t be used for calculated elements',
'calc-implicit': 'An arrayed type can\'t be used for calculated elements (due to direct reference $(ELEMREF))',
'calc-cast': 'An arrayed type can\'t be used for calculated elements (via cast)',
},

@@ -698,13 +756,11 @@ 'def-missing-element': {

// TODO: rename to ref-expected-XYZ
'expected-actionparam-type': 'A type, an element, or a service entity is expected here',
'expected-const': 'A constant value is expected here',
'expected-context': 'A context or service is expected here',
'expected-event-type': 'A type, an element, an event, or a service entity is expected here',
'expected-entity': 'An entity, projection or view is expected here',
'expected-struct': 'A type, entity, aspect or event with direct elements is expected here',
'expected-type': 'A type or an element is expected here',
'ref-expecting-action-param-type': 'A type, an element, or a service entity is expected here',
'ref-expecting-const': 'A constant value is expected here',
'ref-expecting-event-type': 'A type, an element, an event, or a service entity is expected here',
'ref-expecting-entity': 'An entity, projection or view is expected here',
'ref-expecting-struct': 'A type, entity, aspect or event with direct elements is expected here',
'ref-expecting-type': 'A type or an element is expected here',
// TODO: text variant if the association does not start an entity
'expected-source': 'A query source must be an entity or an association',
'expected-target': 'An entity or an aspect is expected here',
'ref-expecting-source': 'A query source must be an entity or an association',
'ref-expecting-target': 'An entity or an aspect is expected here', // TODO: coverage
'extend-columns': 'Artifact $(ART) can\'t be extended with columns, only projections can',

@@ -728,2 +784,4 @@ 'extend-repeated-intralayer': 'Unstable element order due to repeated extensions in same layer',

},
'ext-missing-type-property': 'Type extension with property $(PROP) must also have property $(OTHERPROP) because $(ART) has both',
'ref-expected-scalar-type': {

@@ -753,2 +811,18 @@ std: 'Only scalar type definitions can be extended with type properties',

},
'query-mismatched-element': {
std: 'Specified element $(NAME) differs from inferred element in property $(PROP)',
type: 'Expected type of specified element $(NAME) to be the same as the inferred element\'s type',
typeName: 'Expected type $(TYPE) of specified element $(NAME) to be the same as the inferred element\'s type $(OTHERTYPE)',
missing: 'Specified element $(NAME) differs from inferred element: it is missing property $(PROP)',
extra: 'Specified element $(NAME) differs from inferred element: it has an additional property $(PROP)',
target: 'Expected target $(TARGET) of specified element $(NAME) to be the same as the inferred element\'s target $(ART)',
foreignKeys: 'Expected foreign keys of specified element $(NAME) to be the same as the inferred element\'s foreign keys',
prop: 'Value for $(PROP) of the specified element $(NAME) does not match the inferred element\'s value',
},
'query-unexpected-property': {
std: 'Unexpected property $(PROP) in the specified element $(NAME)',
calculatedElement: 'Unexpected property $(PROP) in the specified element $(NAME); calculated elements are not supported in queries',
},
'ref-sloppy-type': 'A type or an element is expected here',

@@ -875,3 +949,3 @@ 'ref-sloppy-actionparam-type': 'A type, an element, or a service entity is expected here',

// -----------------------------------------------------------------------------------
// All mesages of odata-anno-value below here MUST have $(ANNO) filled with msgctx.anno
// All messages of odata-anno-value below here MUST have $(ANNO) filled with msgctx.anno
// -----------------------------------------------------------------------------------

@@ -897,2 +971,7 @@ 'enum': 'Value $(VALUE) is not one out of $(RAWVALUES) for $(ANNO) of type $(TYPE)',

},
'query-ignoring-assoc-in-union': {
'managed': 'Ignoring managed association $(NAME) that is published in a UNION',
'std': 'Ignoring association $(NAME) that is published in a UNION'
}
}

@@ -905,7 +984,7 @@

* @property {MessageSeverity} severity Default severity for the message.
* @property {string[]|'deprecated'|'v3'|true} [configurableFor]
* @property {string[]|'deprecated'|'v4'|true} [configurableFor]
* Whether the error can be reclassified to a warning or lower.
* If not `true` then an array is expected with specified modules in which the error is downgradable.
* Only has an effect if default severity is 'Error'.
* 'deprecated': severity can only be changed with deprecated._downgradableErrors.
* 'deprecated': severity can only be changed with deprecated.downgradableErrors.
* TODO: Value `true` is temporary. Use an array instead.

@@ -912,0 +991,0 @@ * @property {string[]} [errorFor] Array of module names where the message shall be reclassified to an error.

@@ -15,5 +15,7 @@ // Functions and classes for syntax messages

const { getArtifactName } = require('../compiler/base');
const { cdlNewLineRegEx } = require('../language/textUtils');
const fs = require('fs');
const path = require('path');
const { inspect } = require('util')

@@ -45,8 +47,8 @@ // term instance for messages

* @param {string} moduleName
* @param {CSN.Options} options
* @returns {boolean}
*/
function hasNonDowngradableErrors( messages, moduleName, deprecatedDowngradable ) {
function hasNonDowngradableErrors( messages, moduleName, options ) {
return messages &&
messages.some( m => m.severity === 'Error' &&
!isDowngradable( m.messageId, moduleName, deprecatedDowngradable ));
messages.some( m => m.severity === 'Error' && !isDowngradable( m.messageId, moduleName, options ));
}

@@ -61,6 +63,6 @@

* @param {string} moduleName
* @param {boolean} deprecatedDowngradable
* @param {CSN.Options} options Options used to check for test mode and beta flags.
* @returns {boolean}
*/
function isDowngradable( messageId, moduleName, deprecatedDowngradable ) {
function isDowngradable( messageId, moduleName, options ) {
if (!messageId || !centralMessages[messageId])

@@ -80,3 +82,3 @@ return false;

? configurableFor.includes( moduleName )
: configurableFor && (configurableFor !== 'deprecated' || deprecatedDowngradable);
: configurableFor && (configurableFor !== 'deprecated' || isDeprecatedEnabled( options, 'downgradableErrors' ));
}

@@ -86,26 +88,19 @@

* Class for combined compiler errors. Additional members:
* `errors`: vector of errors (CompileMessage and errors from peg.js)
* `model`: the CSN model
* TODO: standard param order
* @class CompilationError
* @extends {Error}
* - `messages`: array of compiler messages (CompileMessage)
* - `model`: the CSN model
*/
class CompilationError extends Error {
/**
* Creates an instance of CompilationError.
* @param {array} messages vector of errors
* @param {CompileMessage[]} messages
* @param {XSN.Model} [model] the XSN model, only to be set with options.attachValidNames
* @param {string} [text] Text of the error
* @param {any} args Any args to pass to the super constructor
*
* @memberOf CompilationError
*/
constructor(messages, model, text, ...args) {
super( text || `CDS compilation failed\n${ messages.map( m => m.toString() ).join('\n') }`,
// @ts-ignore Error does not take more arguments according to TypeScript...
...args );
constructor(messages, model) {
// TODO(v5): Don't store stringified messages.
super( `CDS compilation failed\n${messages?.map( m => m.toString() ).join('\n') || ''}` );
/** @since v4.0.0 */
this.code = 'ERR_CDS_COMPILATION_FAILURE';
this.messages = messages;
/** @type {boolean} model */
this.hasBeenReported = false; // TODO: remove this bin/cdsc.js specifics
// TODO: remove this bin/cdsc.js specifics
Object.defineProperty( this, 'hasBeenReported', { value: false, configurable: true, writable: true, enumerable: false } );
// property `model` is only set with options.attachValidNames:

@@ -115,6 +110,10 @@ Object.defineProperty( this, 'model', { value: model || undefined, configurable: true } );

toString() { // does not really help -> set message
return this.message.includes('\n')
? this.message
: `${ this.message }\n${ this.messages.map( m => m.toString() ).join('\n') }`;
/**
* Called by `console.*()` functions in NodeJs. To avoid `err.messages` being
* printed using `util.inspect()`.
*
* @return {string}
*/
[inspect.custom]() {
return this.stack || this.message;
}

@@ -149,4 +148,3 @@

this.message = msg;
this.location = location;
this.$location = { ...this.location, address: undefined };
this.$location = { ...location, address: undefined };
this.validNames = null;

@@ -190,12 +188,8 @@ this.home = home; // semantic location, e.g. 'entity:"E"/element:"x"'

* @param {string} moduleName
* @param {boolean} deprecatedDowngradable
* @returns {MessageSeverity}
*/
function reclassifiedSeverity( msg, options, moduleName, deprecatedDowngradable ) {
function reclassifiedSeverity( msg, options, moduleName ) {
const spec = centralMessages[msg.messageId] || { severity: msg.severity, configurableFor: null, errorFor: null };
if (spec.severity === 'Error') {
const { configurableFor } = spec;
if (!(Array.isArray( configurableFor )
? configurableFor.includes( moduleName )
: configurableFor && (configurableFor !== 'deprecated' || deprecatedDowngradable)))
if (!isDowngradable(msg.messageId, moduleName, options))
return 'Error';

@@ -322,3 +316,2 @@ }

const hasMessageArray = !!options.messages;
const deprecatedDowngradable = isDeprecatedEnabled( options, '_downgradableErrors' );
/**

@@ -374,3 +367,3 @@ * Array of collected compiler messages. Only use it for debugging. Will not

_check$Consistency( id, moduleName, severity, texts, options );
msg.severity = reclassifiedSeverity(msg, options, moduleName, deprecatedDowngradable );
msg.severity = reclassifiedSeverity( msg, options, moduleName );
}

@@ -380,3 +373,3 @@

hasNewError = hasNewError || msg.severity === 'Error' &&
!(options.testMode && isDowngradable( msg.messageId, moduleName, deprecatedDowngradable ));
!(options.testMode && isDowngradable( msg.messageId, moduleName, options ));
if (!hasMessageArray)

@@ -424,3 +417,3 @@ console.error( messageString( msg ) );

* Normalize the given location. Location may be a CSN path, XSN/CSN location or an
* array of the form `[CSN.Location, user, suffix]`.
* array of the form `[CSN.Location, XSN user, suffix]`.
*

@@ -529,3 +522,3 @@ * @param {any} location

const hasError = options.testMode ? hasNonDowngradableErrors : hasErrors;
if (hasError( messages, moduleName, deprecatedDowngradable ))
if (hasError( messages, moduleName, options ))
throw new CompilationError( messages, options.attachValidNames && model );

@@ -542,3 +535,3 @@ }

if (msg.messageId && msg.severity !== 'Error') {
const severity = reclassifiedSeverity(msg, options, moduleName, deprecatedDowngradable);
const severity = reclassifiedSeverity( msg, options, moduleName );
if (severity !== msg.severity) {

@@ -549,3 +542,3 @@ msg.severity = severity;

hasNewError = hasNewError || severity === 'Error' &&
!(options.testMode && isDowngradable( msg.messageId, moduleName, deprecatedDowngradable ));
!(options.testMode && isDowngradable( msg.messageId, moduleName, options ));
}

@@ -639,3 +632,3 @@ }

// now try whether the message could be something less than an Error in the module due to user wishes
if (!isDowngradable( id, moduleName, true )) { // always an error in module
if (!isDowngradable(id, moduleName, { testMode: true, deprecated: { downgradableErrors: true } } )) { // always an error in module
if (severity !== 'Error')

@@ -675,3 +668,3 @@ throw new CompilerAssertion( `Inconsistent severity: Expecting "Error", not "${ severity }" for message ID "${ id }" in module "${ moduleName }"` );

const quote = { // could be an option in the future
double: p => `“${ p }”`, // for names, including annotation names (with preceeding `@`)
double: p => `“${ p }”`, // for names, including annotation names (with preceding `@`)
single: p => `‘${ p }’`, // for other things cited from or expected in the model

@@ -806,3 +799,3 @@ angle: p => `‹${ p }›`, // for tokens like ‹Identifier›, and similar

if (token.match( /^[A-Z][A-Z]/ )) // keyword
return quote.upper( token );
return keyword( token );
else if (token.match( /^[A-Z][a-z]/ )) // Number, Identifier, ...

@@ -824,8 +817,4 @@ return quote.angle( token );

return quoted(arg.ref.map((ref) => {
if (ref.id) {
// Indicate that the path has a filter.
if (ref.where)
return `${ ref.id }[…]`;
return ref.id;
}
if (ref.id)
return `${ ref.id }${ref.args ? '(…)' : ''}${ref.where ? '[…]' : ''}`;
return ref;

@@ -936,4 +925,9 @@ }).join('.'));

/**
* Return message string with location if present in compact form (i.e. one line)
* Return message string with location if present in compact form (i.e. one line).
*
* IMPORTANT:
* cds-compiler <v4 used following signature:
* `messageString( err, normalizeFilename, noMessageId, noHome, moduleName = undefined ) : string`
* This signature is still supported for backwards compatibility but is deprecated.
*
* Example:

@@ -943,16 +937,39 @@ * <source>.cds:3:11: Error message-id: Can't find type `nu` in this scope (in entity:“E”/element:“e”)

* @param {CompileMessage} err
* @param {boolean} [normalizeFilename]
* @param {boolean} [noMessageId]
* @param {boolean} [noHome]
*
* @param {object} [config = {}]
*
* @param {boolean} [config.normalizeFilename]
* If true, the file path will be normalized to use `/` as the path separator (instead of `\` on Windows).
*
* @param {boolean} [config.noMessageId]
* If true, will _not_ show the message ID (+ explanation hint) in the output.
*
* @param {boolean} [config.noHome]
* If true, will _not_ show message's semantic location.
*
* @param {string} [config.module]
* If set, downgradable error messages will get a '‹↓›' marker, depending on whether
* the message can be downgraded for the given module.
*
* @returns {string}
*/
function messageString( err, normalizeFilename, noMessageId, noHome, moduleName = undefined ) {
const location = (err.$location?.file ? `${ locationString( err.$location, normalizeFilename ) }: ` : '');
function messageString( err, config ) {
// backwards compatibility <v4
if (!config || typeof config === 'boolean' || arguments.length > 2) {
config = {
normalizeFilename: arguments[1],
noMessageId: arguments[2],
noHome: arguments[3],
module: arguments[4],
};
}
const location = (err.$location?.file ? `${ locationString( err.$location, config.normalizeFilename ) }: ` : '');
const severity = err.severity || 'Error';
const downgradable = severity === 'Error' && moduleName &&
isDowngradable(err.messageId, moduleName, false) ? '‹↓›' : '';
const downgradable = severity === 'Error' && config.module &&
isDowngradable(err.messageId, config.module, {}) ? '‹↓›' : '';
// even with noHome, print err.home if the location is weak
const home = !err.home || noHome && err.$location?.endLine ? '' : ` (in ${ err.home })`;
const home = !err.home || config.noHome && err.$location?.endLine ? '' : ` (in ${ err.home })`;
// TODO: the plan was with brackets = `Error[ref-undefined-def]`
const id = err.messageId && !noMessageId ? ` ${ err.messageId }` : '';
const id = err.messageId && !config.noMessageId ? ` ${ err.messageId }` : '';
return `${ location }${ severity }${ downgradable }${ id }: ${ err.message }${ home }`;

@@ -981,22 +998,44 @@ }

/**
* Returns a message string with file- and semantic location if present
* in multiline form.
* Returns a message string with file- and semantic location if present in multiline form
* with a source code snippet below that has highlights for the message's location.
* The message (+ message id) are colored according to their severity.
*
* Example:
* ```txt
* Error[message-id]: Can't find type `nu` in this scope
* |
* <source>.cds:3:11, at entity:“E”/element:“e”
* ```
* @param {CompileMessage} err
*
* @param {CompileMessage} err
* @param {object} [config = {}]
* @param {boolean} [config.normalizeFilename] Replace windows `\` with forward slashes `/`.
*
* @param {boolean} [config.normalizeFilename]
* If true, the file path will be normalized to use `/` as the path separator (instead of `\` on Windows).
*
* @param {boolean} [config.noMessageId]
* @param {boolean} [config.hintExplanation] If true, messages with explanations will get a "…" marker.
* @param {boolean} [config.withLineSpacer] If true, an additional line (with `|`) will be inserted between message and location.
* @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the severity. If false, no
* coloring will be used. If 'auto', we will decide based on certain factors such
* as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
* unset.
* If true, will _not_ show the message ID (+ explanation hint) in the output.
*
* @param {boolean} [config.hintExplanation]
* If true, messages with explanations will get a "…" marker.
*
* @param {string} [config.module]
* If set, downgradable error messages will get a '‹↓›' marker, depending on whether
* the message can be downgraded for the given module.
*
* @param {Record<string, string>} [config.sourceMap]
* A dictionary of filename<->source-code entries. You can pass the `fileCache` that is used
* by the compiler.
*
* @param {Record<string, number[]>} [config.sourceLineMap]
* A dictionary of filename<->source-newline-indices entries. Is used to extract source code
* snippets for message locations. If not set, will be set and filled by this function on-demand.
* An entry is an array of character/byte offsets to new-lines, for example sourceLineMap[1] is the
* end-newline for the second line.
*
* @param {string} [config.cwd]
* The current working directory (cwd) that was passed to the compiler.
* This value is only used if a source map is provided and relative paths needs to be
* resolved to absolute ones.
*
* @param {boolean | 'auto' | 'never' | 'always'} [config.color]
* If true/'always', ANSI escape codes will be used for coloring the severity. If false/'never',
* no coloring will be used. If 'auto', we will decide based on certain factors such
* as whether the shell is a TTY and whether the environment variable `NO_COLOR` is
* unset.
*
* @returns {string}

@@ -1008,11 +1047,17 @@ */

const explainHelp = (config.hintExplanation && hasMessageExplanation(err.messageId)) ? '…' : '';
const msgId = (err.messageId && !config.noMessageId) ? `[${ err.messageId }${ explainHelp }]` : '';
const home = !err.home ? '' : (`at ${ err.home }`);
const severity = err.severity || 'Error';
const downgradable = config.module && severity === 'Error' &&
isDowngradable(err.messageId, config.module, {}) ? '‹↓›' : '';
const msgId = (err.messageId && !config.noMessageId) ? `[${ err.messageId }${downgradable}${ explainHelp }]` : '';
let location = '';
if (err.$location && err.$location.file) {
let context = '';
if (err.$location?.file) {
location += locationString( err.$location, config.normalizeFilename );
if (home)
location += ', ';
context = _messageContext(err, config);
if (context !== '')
context = `\n${context}`;
}

@@ -1023,14 +1068,36 @@ else if (!home) {

let lineSpacer = '';
if (config.withLineSpacer) {
const additionalIndent = err.$location ? `${ err.$location.endLine || err.$location.line || 1 }`.length : 1;
lineSpacer = `\n ${ ' '.repeat( additionalIndent ) }|`;
const additionalIndent = err.$location ? `${ err.$location.endLine || err.$location.line || 1 }`.length : 1;
const lineSpacer = `\n ${ ' '.repeat( additionalIndent ) }|`;
return `${ colorTerm.severity(severity, severity + msgId) }: ${ err.message }${ lineSpacer }\n ${ location }${ home }${context}`;
}
/**
* Used by _messageContext() to create an array of line start offsets.
* Each entry in the returned array contains the offset for the start line,
* where the line is the index in the array.
*
* @param source
* @return {number[]}
* @private
*/
function _createSourceLineMap(source) {
const newlines = [ 0 ];
const re = new RegExp(cdlNewLineRegEx, 'g');
let line;
while((line = re.exec(source)) !== null) {
newlines.push(line.index + line[0].length);
}
newlines.push(source.length); // EOF marker
return `${ colorTerm.severity(severity, severity + msgId) }: ${ err.message }${ lineSpacer }\n ${ location }${ home }`;
return newlines;
}
/**
* Returns a context (code) string that is human readable (similar to rust's compiler)
* Returns a context (code) string that is human-readable (similar to rust's compiler).
*
* IMPORTANT: In case that `config.sourceMap[err.loc.file]` does not exist, this function
* uses `path.resolve()` to get the absolute filename.
*
* Example Output:

@@ -1041,30 +1108,40 @@ * |

*
* @param {string[]} sourceLines The source code split up into lines, e.g. by `splitLines(src)`
* from `lib/utils/file.js`
* @param {CompileMessage} err Error object containing all details like line, message, etc.
* @param {object} [config = {}]
* @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the `^`. If false, no
* coloring will be used. If 'auto', we will decide based on certain factors such
* as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
* unset.
* @param {object} [config = {}] See `messageStringMultiline()` for details.
*
* @returns {string}
* @private
*/
function messageContext( sourceLines, err, config ) {
colorTerm.changeColorMode(config ? config.color : 'auto');
function _messageContext( err, config ) {
const MAX_COL_LENGTH = 100;
const loc = err.$location;
if (!loc || !loc.line)
if (!loc || !loc.line || !loc.file || !config.sourceMap)
return '';
let filepath = config.sourceMap[loc.file]?.realname || loc.file;
if (!config.sourceMap[filepath])
filepath = path.resolve(config.cwd || '', filepath);
const source = config.sourceMap[filepath];
if (!source || source === true) // true: file exists, no further knowledge
return '';
if (!config.sourceLineMap)
config.sourceLineMap = Object.create(null);
if (!config.sourceLineMap[filepath])
config.sourceLineMap[filepath] = _createSourceLineMap(source);
const sourceLines = config.sourceLineMap[filepath];
// Lines are 1-based, we need 0-based ones for arrays
const startLine = loc.line - 1;
const endLine = loc.endLine ? loc.endLine - 1 : startLine;
const startLine = Math.min(sourceLines.length, loc.line - 1);
const endLine = Math.min(sourceLines.length, loc.endLine ? loc.endLine - 1 : startLine);
/** Only print N lines even if the error spans more lines. */
const maxLine = Math.min((startLine + 2), endLine);
// check that source lines exists
if (typeof sourceLines[startLine] !== 'string' || typeof sourceLines[endLine] !== 'string')
if (typeof sourceLines[startLine] !== 'number')
return '';
const digits = String(endLine + 1).length;

@@ -1082,5 +1159,2 @@ const severity = err.severity || 'Error';

/** Only print N lines even if the error spans more lines. */
const maxLine = Math.min((startLine + 2), endLine);
let msg = `${ indent }|\n`;

@@ -1091,3 +1165,4 @@

// Replaces tabs with 1 space
let sourceCode = sourceLines[line].replace(/\t/g, ' ');
let sourceCode = source.substring(sourceLines[line], sourceLines[line+1] || source.length).trimEnd();
sourceCode = sourceCode.replace(/\t/g, ' ');
if (sourceCode.length >= MAX_COL_LENGTH)

@@ -1111,3 +1186,3 @@ sourceCode = sourceCode.slice(0, MAX_COL_LENGTH);

// error spans more lines which we don't print
msg += `${ indent }| ...`;
msg += `${ indent }| …`;
}

@@ -1122,2 +1197,32 @@ else {

/**
* Returns a context (code) string that is human-readable (similar to rust's compiler)
*
* Example Output:
* |
* 3 | num * nu
* | ^^
*
* @param {string[]} sourceLines The source code split up into lines, e.g. by `splitLines(src)`
* from `lib/utils/file.js`
* @param {CompileMessage} err Error object containing all details like line, message, etc.
* @param {object} [config = {}]
* @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the `^`. If false, no
* coloring will be used. If 'auto', we will decide based on certain factors such
* as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
* unset.
* @returns {string}
*
* @deprecated Use `messageStringMultiline()` with `config.sourceMap` and `config.sourceLineMap` instead!
*/
function messageContext( sourceLines, err, config ) {
const loc = err.$location;
if (!loc || !loc.line|| !loc.file)
return '';
colorTerm.changeColorMode(config ? config.color : 'auto');
const sourceMap = { [err.$location.file]: sourceLines.join('\n') };
return _messageContext(err, { ...config, sourceMap });
}
/**
* Compare two messages `a` and `b`. Return 0 if they are equal, 1 if `a` is

@@ -1270,2 +1375,4 @@ * larger than `b`, and -1 if `a` is smaller than `b`. Messages without a location

return art;
if (art._user) // when providing a path item with filter as “user”
return homeName( art._user, absoluteOnly );
if (art._outer) // in items property

@@ -1479,4 +1586,4 @@ return homeName( art._outer, absoluteOnly );

}
else if (step === 'xpr' || step === 'ref' || step === 'as') {
break; // don't go into xprs, refs, aliases, etc.
else if (step === 'xpr' || step === 'ref' || step === 'as' || step === 'value') {
break; // don't go into xprs, refs, aliases, values, etc.
}

@@ -1709,3 +1816,2 @@ else if (step === 'returns') {

CompilationError,
isMessageDowngradable: isDowngradable,
explainMessage,

@@ -1712,0 +1818,0 @@ hasMessageExplanation,

@@ -25,21 +25,19 @@ 'use strict';

annotationExpressions: true,
toRename: true,
assocsWithParams: true,
toRename: true, // Removes once it's publicly documented
assocsWithParams: true, // beta, because runtimes don't support it, yet.
hanaAssocRealCardinality: true,
mapAssocToJoinCardinality: true,
ignoreAssocPublishingInUnion: true,
mapAssocToJoinCardinality: true, // only SAP HANA HEX engine supports it
enableUniversalCsn: true,
postgres: true,
aspectWithoutElements: true, // TODO: do not just remove beta flag - remove elements, too!
odataTerms: true,
optionalActionFunctionParameters: true,
calculatedElementsOnWrite: true,
optionalActionFunctionParameters: true, // not supported by runtime, yet.
// disabled by --beta-mode
nestedServices: false,
v4preview: false,
};
// Used by isDeprecatedEnabled() to check if any flag ist set.
const availableDeprecatedFlags = {
// the old ones starting with _, : false
autoCorrectOrderBySourceRefs: true,
downgradableErrors: true,
includesNonShadowedFirst: true,
eagerPersistenceForGeneratedEntities: true,

@@ -50,3 +48,3 @@ }

'createLocalizedViews',
'downgradableErrors',
// 'downgradableErrors', // re-added in v4
'generatedEntityNameWithUnderscore',

@@ -65,2 +63,4 @@ 'longAutoexposed',

'v1KeysForTemporal',
// do not add old deprecated flags which should not lead to an error:
// autoCorrectOrderBySourceRefs - just info would be ok
];

@@ -72,3 +72,3 @@

*
* Beta features cannot be used when options.deprecated is set.
* Beta features cannot be used when `options.deprecated` is set.
*

@@ -75,0 +75,0 @@ * A feature always needs to be provided - otherwise false will be returned.

{
// we actually do not extend airbnb-base, as it weakens some eslint:recommended rules
"extends": ["../../.eslintrc-ydkjsi.json", "plugin:jsdoc/recommended"],
"extends": ["plugin:jsdoc/recommended", "../../.eslintrc-ydkjsi.json"],
"plugins": [

@@ -5,0 +5,0 @@ "jsdoc"

@@ -15,3 +15,3 @@ 'use strict';

*/
function checkCoreMediaTypeAllowence( member ) {
function checkCoreMediaTypeAllowance( member ) {
const allowedCoreMediaTypes = {

@@ -71,3 +71,3 @@ 'cds.String': 1,

module.exports = {
checkCoreMediaTypeAllowence,
checkCoreMediaTypeAllowance,
checkAnalytics,

@@ -74,0 +74,0 @@ checkAtSapAnnotations,

@@ -156,3 +156,3 @@ 'use strict';

* 'items' break recursion as 'items' will turn into an NCLOB and the path
* prefix to 'items' can be flattend in the DB.
* prefix to 'items' can be flattened in the DB.
* In OData flat mode the first appearance of 'items' breaks out into structured

@@ -159,0 +159,0 @@ * mode producing (legal) recursive complex types.

@@ -31,3 +31,3 @@ // This is very similar to lib/model/enrichCsn - but the goal and the execution differ a bit:

type: simpleRef,
target: simpleRef,
target,
includes: simpleRef,

@@ -103,7 +103,6 @@ columns,

// eslint-disable-next-line jsdoc/require-jsdoc
function simpleRef( node, prop ) {
function simpleRef( node, prop, ref ) {
setProp(node, '$path', [ ...csnPath ]);
cleanupCallbacks.push(() => delete node.$path);
const ref = node[prop];
if (typeof ref === 'string') {

@@ -117,5 +116,15 @@ const art = artifactRef( ref, null );

else if (Array.isArray( ref )) {
// e.g. `includes: [ 'E' ]`, which gets a parallel `_includes`.
setProp(node, `_${ prop }`, ref.map( r => artifactRef( r, null ) ));
cleanupCallbacks.push(() => delete node[`_${ prop }`]);
}
else if (typeof ref === 'object') {
// e.g. type refs via `{ type: { ref: [ 'E', 'field' ] } }
standard(node, prop, ref);
const art = artifactRef( ref, null );
if (art) {
setProp(node, `_${ prop }`, art);
cleanupCallbacks.push(() => delete node[`_${ prop }`]);
}
}
}

@@ -158,4 +167,18 @@

}
/**
* A target is either an anonymous aspect (with elements, etc.) via gensrc or a reference.
*
* @param {object} parent
* @param {string} prop
* @param {any} node
*/
function target( parent, prop, node ) {
if (node?.elements) // e.g. via gensrc
standard(parent, prop, node);
else
simpleRef(parent, prop, node);
}
}
module.exports = enrichCsn;

@@ -77,2 +77,3 @@ 'use strict';

for (let j = 0; j < _links.length - 1; j++) {
let hasPathError = false;
const csnPath = path.concat([ 'on', i, 'ref', j ]);

@@ -92,21 +93,30 @@

this.error('ref-unexpected-navigation', csnPath, { '#': 'unmanaged', id, elemref });
hasPathError = true;
}
else {
// It's a managed association - access of the foreign keys is allowed
const nextRef = ref[j + 1].id || ref[j + 1];
if (!stepArt.keys.some(r => r.ref[0] === nextRef)) {
checkForeignKeyAccess(member.on[i], j, csnPath, (errorIndex) => {
this.error('ref-unexpected-navigation', csnPath, {
'#': 'std', id, elemref, name: nextRef,
'#': 'std', id, elemref, name: ref[errorIndex].id || ref[errorIndex],
});
}
hasPathError = true;
});
}
}
if (stepArt.virtual)
this.error(null, csnPath, { id, elemref }, 'Virtual elements can\'t be used in ON-conditions, step $(ID) of path $(ELEMREF)');
if (stepArt.virtual) {
this.error(null, csnPath, { id, elemref }, //
'Virtual elements can\'t be used in ON-conditions, step $(ID) of path $(ELEMREF)');
hasPathError = true;
}
if (ref[j].where) {
this.error('ref-unexpected-filter', csnPath, { '#': 'on-condition', id, elemref });
hasPathError = true;
}
if (ref[j].args) {
this.error('ref-unexpected-args', csnPath, { '#': 'on-condition', id, elemref });
hasPathError = true;
}
if (ref[j].where)
this.error(null, csnPath, { id, elemref }, 'ON-conditions must not contain filters, step $(ID) of path $(ELEMREF)');
if (ref[j].args)
this.error(null, csnPath, { id, elemref }, 'ON-conditions must not contain parameters, step $(ID) of path $(ELEMREF)');
if (hasPathError)
break; // avoid too many consequent errors
}

@@ -150,3 +160,48 @@

/**
* Ensure that only foreign keys of the association `parent.ref[refIndex]` are accessed in `parent.ref`.
* If a non-fk field is accessed, `callback` is invoked.
*
* @param {object} parent Object containing `ref` and `_links` from csnRefs.
* @param {number} refIndex Index of the to-be-checked association in `parent.ref`
* @param {CSN.Path} csnPath
* @param {(errorIndex: number) => void} callback Called if there are non-fk path steps. Argument is index in
* `parent.ref` that is faulty. If a fk-step is missing, `errorIndex` will be `> parent.ref.length`.
*/
function checkForeignKeyAccess( parent, refIndex, csnPath, callback ) {
const { ref, _links } = parent;
const assoc = _links[refIndex].art;
const next = ref[refIndex + 1].id || ref[refIndex + 1];
let possibleKeys = next && assoc.keys.filter(r => r.ref[0] === next);
if (!possibleKeys || possibleKeys.length === 0) {
callback(refIndex + 1);
}
else {
// For cases where `Association to T { struct.one, struct.two };` is used instead of `{ struct }`.
// Note: We know that `{ struct, struct.one }` is not possible, so no prefix check required.
let fkIndex = 0;
let success = false;
while (!success && possibleKeys.length > 0) {
const pathStep = ref[refIndex + fkIndex + 1].id || ref[refIndex + fkIndex + 1];
// Function is immediately executed, before next iteration of loop. Access is fine.
// eslint-disable-next-line no-loop-func
possibleKeys = possibleKeys.filter((r) => {
const result = r.ref[fkIndex] === pathStep;
if (result && r.ref.length - 1 === fkIndex)
success = true; // full fk matched
return result;
});
++fkIndex;
}
if (!success)
callback(refIndex + fkIndex);
}
}
/**
* Run the above validations also for mixins.

@@ -162,2 +217,2 @@ *

module.exports = { validateOnCondition, validateMixinOnCondition };
module.exports = { validateOnCondition, validateMixinOnCondition, checkForeignKeyAccess };

@@ -20,101 +20,116 @@ 'use strict';

function checkQueryForNoDBArtifacts( query ) {
/**
* Count the leaf-elements resulting from a given element.
*
* @param {CSN.Element} def Definition to check
* @returns {number} Number of leaf elements
*/
const leafCount = (def) => {
let c = 0;
if (!def)
return c;
if (def.elements) {
c += Object.values(def.elements).reduce((acc, e) => {
acc += leafCount(e);
return acc;
}, 0);
if (isPersistedOnDatabase(this.artifact) && !hasAnnotationValue(this.artifact, '@cds.persistence.table')) {
const generalQueryProperties = [ 'from', 'columns', 'where', 'groupBy', 'orderBy', 'having', 'limit' ];
for (const prop of generalQueryProperties) {
const queryPart = (query.SELECT || query.SET)[prop];
if (Array.isArray(queryPart)) {
for (const part of queryPart)
checkRef.call(this, part, prop === 'columns');
}
else if (typeof queryPart === 'object') {
checkRef.call(this, queryPart, prop === 'columns');
}
}
else if (def.keys) {
c += def.keys.reduce((acc, e) => {
acc += leafCount(e._art);
return acc;
}, 0);
}
else if (def.type) {
if (isBuiltinType(def.type) && !(def.target))
return 1;
c += leafCount(this.csn.definitions[def.type]);
}
}
}
/**
* Count the leaf-elements resulting from a given element.
*
* @param {CSN.Element} def Definition to check
* @returns {number} Number of leaf elements
*/
function leafCount( def ) {
let c = 0;
if (!def)
return c;
};
/**
* Check the given ref for usage of skipped/abstract assoc targets
*
* @param {object} obj CSN "thing" to check
* @param {boolean} inColumns True if the ref is part of a from
*/
const checkRef = (obj, inColumns) => {
if (!(obj && obj.ref) || !obj._links || obj.$scope === 'alias')
return;
if (def.elements) {
c += Object.values(def.elements).reduce((acc, e) => {
acc += leafCount.call(this, e);
return acc;
}, 0);
}
else if (def.keys) {
c += def.keys.reduce((acc, e) => {
acc += leafCount.call(this, e._art);
return acc;
}, 0);
}
else if (def.type) {
if (isBuiltinType(def.type) && !(def.target))
return 1;
c += leafCount.call(this, this.csn.definitions[def.type]);
}
return c;
}
const links = obj._links;
/**
* Check the given ref for usage of skipped/abstract assoc targets
*
* @param {object} obj CSN "thing" to check
* @param {boolean} inColumns True if the ref is part of a from
*/
function checkRef( obj, inColumns ) {
if (!(obj && obj.ref) || !obj._links || obj.$scope === 'alias')
return;
// Don't check the last element - to allow association publishing in columns
for (let i = 0; i < (inColumns ? links.length - 1 : links.length); i++) {
const link = links[i];
if (!link)
continue;
const links = obj._links;
const { art } = link;
if (!art)
continue;
// Don't check the last element - to allow association publishing in columns
for (let i = 0; i < (inColumns ? links.length - 1 : links.length); i++) {
const link = links[i];
if (!link)
continue;
const endArtifact = art.target ? this.csn.definitions[art.target] : art;
const pathStep = obj.ref[i].id ? obj.ref[i].id : obj.ref[i];
const name = art.target ? art.target : pathStep;
if (!isPersistedOnDatabase(endArtifact)) {
const nextElement = obj.ref[i + 1];
/**
* if we only navigate to foreign keys of the managed association in a view, we do not need to join,
* thus we can produce the view even if the target of the association is not persisted
*
* @param {CSN.Element} assoc association in ref
* @param {string} nextStep the ref step following the association
* @returns {boolean} true if no join will be generated
*/
const isJoinRelevant = (assoc, nextStep) => {
if (!assoc.keys)
return true;
const isExposedColumnAssocOrComposition = this.csnUtils.isAssocOrComposition(obj._art);
return !assoc.keys
.some(fk => fk.ref[0] === nextStep && !isExposedColumnAssocOrComposition);
};
if (isJoinRelevant(art, nextElement)) {
const cdsPersistenceSkipped = hasAnnotationValue(endArtifact, '@cds.persistence.skip');
this.error( null, obj.$path, {
id: pathStep, elemref: obj, name, '#': cdsPersistenceSkipped ? 'std' : 'abstract',
}, {
std: 'Unexpected “@cds.persistence.skip” annotation on association target $(NAME) of $(ID) in path $(ELEMREF)',
abstract: 'Unexpected “abstract” association target $(NAME) of $(ID) in path $(ELEMREF)',
} );
}
const { art } = link;
if (!art)
continue;
const endArtifact = art.target ? this.csn.definitions[art.target] : art;
const pathStep = obj.ref[i].id ? obj.ref[i].id : obj.ref[i];
const name = art.target ? art.target : pathStep;
if (!isPersistedOnDatabase(endArtifact)) {
const nextElement = obj.ref[i + 1];
/**
* if we only navigate to foreign keys of the managed association in a view, we do not need to join,
* thus we can produce the view even if the target of the association is not persisted
*
* @param {CSN.Element} assoc association in ref
* @param {string} nextStep the ref step following the association
* @returns {boolean} true if no join will be generated
*/
const isJoinRelevant = (assoc, nextStep) => {
if (!assoc.keys)
return true;
const isExposedColumnAssocOrComposition = this.csnUtils.isAssocOrComposition(obj._art.type);
return !assoc.keys
.some(fk => fk.ref[0] === nextStep && !isExposedColumnAssocOrComposition);
};
if (isJoinRelevant(art, nextElement)) {
const cdsPersistenceSkipped = hasAnnotationValue(endArtifact, '@cds.persistence.skip');
this.error( null, obj.$path, {
id: pathStep, elemref: obj, name, '#': cdsPersistenceSkipped ? 'std' : 'abstract',
}, {
std: 'Unexpected “@cds.persistence.skip” annotation on association target $(NAME) of $(ID) in path $(ELEMREF)',
abstract: 'Unexpected “abstract” association target $(NAME) of $(ID) in path $(ELEMREF)',
} );
}
// check managed association to have foreign keys array filled
if (art.keys && leafCount(art) === 0) {
this.error(null,
obj.$path,
{ id: pathStep, elemref: obj },
'Path step $(ID) of $(ELEMREF) has no foreign keys');
}
}
// check managed association to have foreign keys array filled
if (art.keys && leafCount.call(this, art) === 0) {
this.error(null,
obj.$path,
{ id: pathStep, elemref: obj },
'Path step $(ID) of $(ELEMREF) has no foreign keys');
}
if (art.on) {
for (let j = 0; j < art.on.length; j++) {
if (j < art.on.length - 2 && art.on[j].ref && art.on[j + 1] === '=' && art.on[j + 2].ref) {
const [ fwdAssoc, fwdPath ] = getForwardAssociation(pathStep, art.on[j], art.on[j + 2]);
if (fwdAssoc && fwdAssoc.keys && leafCount(fwdAssoc) === 0) {
this.error(null, obj.$path,
{ name: pathStep, elemref: obj, id: fwdPath },
'Path step $(NAME) of $(ELEMREF) is a $self comparison with $(ID) that has no foreign keys');
j += 2;
}
if (art.on) {
for (let j = 0; j < art.on.length; j++) {
if (j < art.on.length - 2 && art.on[j].ref && art.on[j + 1] === '=' && art.on[j + 2].ref) {
const [ fwdAssoc, fwdPath ] = getForwardAssociation(pathStep, art.on[j], art.on[j + 2]);
if (fwdAssoc && fwdAssoc.keys && leafCount.call(this, fwdAssoc) === 0) {
this.error(null, obj.$path,
{ name: pathStep, elemref: obj, id: fwdPath },
'Path step $(NAME) of $(ELEMREF) is a $self comparison with $(ID) that has no foreign keys');
j += 2;
}

@@ -124,16 +139,2 @@ }

}
};
if (isPersistedOnDatabase(this.artifact) && !hasAnnotationValue(this.artifact, '@cds.persistence.table')) {
const generalQueryProperties = [ 'from', 'columns', 'where', 'groupBy', 'orderBy', 'having', 'limit' ];
for (const prop of generalQueryProperties) {
const queryPart = (query.SELECT || query.SET)[prop];
if (Array.isArray(queryPart)) {
for (const part of queryPart)
checkRef(part, prop === 'columns');
}
else if (typeof queryPart === 'object') {
checkRef(queryPart, prop === 'columns');
}
}
}

@@ -140,0 +141,0 @@ }

@@ -25,2 +25,4 @@ 'use strict';

this.message('anno-invalid-sql-struct', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on structured elements' );
else if (member.value && !member.value.stored)
this.message('anno-invalid-sql-calc', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on calculated elements on read' );
else

@@ -27,0 +29,0 @@ checkValidAnnoValue(member, '@sql.append', path, this.error, this.options);

@@ -56,6 +56,6 @@ 'use strict';

// should only happen with csn input, not in cdl
// calculated elements may not have a .type (requires beta flag)
if (!member.value &&
// calculated elements on-read may not have a .type (requires beta flag)
if ((!member.value || member.value.stored) &&
!parent.projection && !parent.query && !hasArtifactTypeInformation(member)) {
errorAboutMissingType(this.error, path, memberName, true);
errorAboutMissingType(this.error, path, member, memberName, true);
return;

@@ -101,3 +101,3 @@ }

if (!hasArtifactTypeInformation(artifact)) {
errorAboutMissingType(this.error, path, artifactName);
errorAboutMissingType(this.error, path, artifact, artifactName);
return;

@@ -158,9 +158,15 @@ }

* @param {CSN.Path} path the path to the element or the artifact
* @param {CSN.Artifact} artifact Element or other member/definition.
* @param {string} name of the element or the artifact which is dubious
* @param {boolean} isElement indicates whether we are dealing with an element or an artifact
*/
function errorAboutMissingType( error, path, name, isElement = false ) {
error('check-proper-type', path, { art: name, '#': isElement ? 'elm' : 'std' }, {
function errorAboutMissingType( error, path, artifact, name,
isElement = false ) {
let variant = isElement ? 'elm' : 'std';
if (artifact.value?.stored)
variant = 'calc';
error('check-proper-type', path, { art: name, '#': variant }, {
std: 'Dubious type $(ART) without type information',
elm: 'Dubious element $(ART) without type information',
calc: 'A stored calculated element must have a type',
});

@@ -167,0 +173,0 @@ }

@@ -22,3 +22,3 @@ 'use strict';

const {
checkCoreMediaTypeAllowence, checkAnalytics,
checkCoreMediaTypeAllowance, checkAnalytics,
checkAtSapAnnotations, checkReadOnlyAndInsertOnly,

@@ -255,3 +255,3 @@ } = require('./annotationsOData');

forEachMemberRecursively(artifact, [
checkCoreMediaTypeAllowence.bind(that),
checkCoreMediaTypeAllowance.bind(that),
checkAnalytics.bind(that),

@@ -258,0 +258,0 @@ checkAtSapAnnotations.bind(that),

@@ -108,3 +108,2 @@ // Consistency checker on model (XSN = augmented CSN)

'$functions',
'$volatileFunctions',
'_sortedSources',

@@ -242,2 +241,3 @@ ],

enum$: { kind: true, enumerable: false, test: TODO },
typeProps$: { kind: true, enumerable: false, test: TODO },
actions: { kind: true, inherits: 'definitions' },

@@ -258,2 +258,3 @@ enum: { kind: true, inherits: 'definitions' },

'quantifier', 'orderBy', 'limit', 'name', '$parens', 'kind',
'_origin', // TODO tmp, see TODO in getOriginRaw()
'_parent', '_main', '_leadingQuery', '_effectiveType', '$effectiveSeqNo', // in FROM

@@ -365,4 +366,5 @@ ],

'args', '$syntax',
'where', 'cardinality',
'_artifact', '_navigation',
'where', 'groupBy', 'limit', 'orderBy', 'having',
'cardinality',
'_artifact', '_navigation', '_user',
'$inferred',

@@ -393,2 +395,3 @@ ],

$prefix: { test: isString }, // compiler-corrected path prefix
$extended: { test: TODO, kind: [ 'element', '$inline' ] }, // `extend … with columns`
$syntax: {

@@ -446,3 +449,3 @@ parser: true,

},
query: { requires: [ 'query', 'location' ] },
query: { requires: [ 'query', 'location' ], optional: [ 'stored' ] },
},

@@ -573,2 +576,3 @@ literal: { // TODO: check value against literal

_main: { kind: true, test: TODO },
_user: { kind: true, test: TODO },
_artifact: { test: TODO },

@@ -641,3 +645,3 @@ _navigation: { test: TODO },

test: isOneOf([
'', // constructed “super annotate” statement
'', // constructed “super annotate” statement, redirected user-provided target
// Uppercase values are used in logic, lowercase value are "just for us", i.e.

@@ -648,3 +652,2 @@ // debugging or to add properties such as $generated in Universal CSN.

'IMPLICIT',
'REDIRECTED',
'NULL', // from propagator

@@ -663,3 +666,3 @@ 'prop', // from propagator

'copy', // only used in rewriteCondition(): On-condition is copied
'duplicate-autoexposed', // just like `autoexposed`, but with `duplicate` error.
'def-duplicate-autoexposed', // just like `autoexposed`, but with `duplicate` error.
'expanded', // expanded elements, items, params

@@ -696,3 +699,2 @@ 'include', // through includes, e.g. `entity E : F {}`

$functions: { test: TODO },
$volatileFunctions: { test: TODO },
};

@@ -699,0 +701,0 @@ let _noSyntaxErrors = null;

@@ -436,2 +436,4 @@ // The builtin artifacts of CDS

model.$internal = { $frontend: '$internal' };
// namespace:"localized" reserved ---
model.definitions.localized = createNamespace( 'localized', 'reserved' );
return;

@@ -498,3 +500,3 @@

createMagicElements( art, magic.elements );
if (options.variableReplacements)
if (options.variableReplacements?.[name])
createMagicElements( art, options.variableReplacements[name] );

@@ -517,3 +519,7 @@ // setProp( art, '_effectiveType', art );

kind: 'builtin',
name: { id: n, element: `${ art.name.element }.${ n }` },
name: {
id: n,
absolute: art.name.absolute,
element: art.name.element ? `${ art.name.element }.${ n }` : n,
},
};

@@ -520,0 +526,0 @@ // Propagate this property so that it is available for sub-elements.

@@ -19,3 +19,5 @@ // Checks on XSN performed during compile()

const {
forEachGeneric, forEachDefinition, forEachMember, isBetaEnabled,
forEachGeneric,
forEachDefinition,
forEachMember,
} = require('../base/model');

@@ -461,3 +463,3 @@ const { CompilerAssertion } = require('../base/error');

error('ref-unexpected-virtual', [ key.location, elem ], { '#': 'fkey' });
else if (key._artifact?.$syntax === 'calc')
else if (key._artifact?.$syntax === 'calc' && !key._artifact.value.stored?.val)
error( 'ref-unexpected-calculated', [ key.location, elem ], { '#': 'fkey' } );

@@ -513,5 +515,2 @@ }

function checkElementIncludeOverride( def ) {
if (!isBetaEnabled(model.options, 'v4preview'))
return; // this is a v4 check only
for (const name in def.elements) {

@@ -596,3 +595,3 @@ const element = def.elements[name];

checkExpressionAssociationUsage(xpr, user, true);
if (xpr._artifact?.$syntax === 'calc')
if (xpr._artifact?.$syntax === 'calc' && !xpr._artifact.value.stored?.val)
error( 'ref-unexpected-calculated', [ xpr.location, user ], { '#': 'on' } );

@@ -619,11 +618,16 @@ });

if (xpr._artifact) { // we only need to check artifact references
const sourceLoc = xpr.path?.[xpr.path.length - 1].location || xpr.location;
checkExpressionNotVirtual(xpr, user);
if (isStructuredElement(xpr._artifact))
error('ref-unexpected-structured', [ xpr.location, elem ], { '#': 'expr' } );
// For inferred (e.g. included) calc elements, this error is already emitted at the origin.
// And users can't change structured to non-structured elements.
if (!elem.$inferred && isStructuredElement(xpr._artifact))
error('ref-unexpected-structured', [ sourceLoc, elem ], { '#': 'expr' } );
else if (xpr._artifact.target !== undefined)
error('ref-unexpected-assoc', [ xpr.location, elem ], { '#': 'expr' });
error('ref-unexpected-assoc', [ sourceLoc, elem ], { '#': 'expr' });
else if (xpr._artifact.localized?.val)
error('ref-unexpected-localized', [ sourceLoc, elem ], { '#': 'calc' });
}
});
// Calc elements must not refer to keys, because that may lead to another key
// in an SQL view, which is missing in OData.
// Calculated elements must not refer to keys, because that may lead to another
// key in an SQL view, which is missing in OData (for on-read).
// Following associations does not lead to this issue.

@@ -838,3 +842,3 @@ if (elem.value.path && isKeyElement(elem.value._artifact) &&

if (!elementDecl) {
warning(null, anno.location || anno.name.location,
warning(null, [ anno.location || anno.name.location, art ],
{ name: pathName(anno.name.path), anno: annoDecl.name.absolute },

@@ -853,7 +857,7 @@ 'Element $(NAME) not found for annotation $(ANNO)');

if (elementDecl.type && elementDecl.type._artifact.name.absolute) {
warning('anno-expecting-value', anno.location || anno.name.location,
warning('anno-expecting-value', [ anno.location || anno.name.location, art ],
{ '#': 'type', type: elementDecl.type._artifact });
}
else {
warning('anno-expecting-value', anno.location || anno.name.location,
warning('anno-expecting-value', [ anno.location || anno.name.location, art ],
{ '#': 'std', anno: anno.name.absolute });

@@ -866,3 +870,3 @@ }

// Value must be assignable to type
checkValueAssignableTo(anno, elementDecl, art);
checkValueAssignableTo(anno, anno, elementDecl, art);
}

@@ -873,3 +877,3 @@

// if not
function checkValueAssignableTo( value, elementDecl, art ) {
function checkValueAssignableTo( annoDef, value, elementDecl, art ) {
// FIXME: We currently do not have any element declaration that could match

@@ -880,3 +884,3 @@ // a 'path' value, so we simply leave those alone

const anno = annoDef.name.absolute;
const loc = [ value.location || value.name.location, art ];

@@ -888,3 +892,3 @@

if (value.literal !== 'array') {
warning(null, loc, {}, 'An array value is required here');
warning(null, loc, { anno }, 'An array value is required for annotation $(ANNO)');
return;

@@ -894,3 +898,3 @@ }

for (const valueItem of value.val)
checkValueAssignableTo(valueItem, elementDecl._effectiveType.items, art);
checkValueAssignableTo(value, valueItem, elementDecl._effectiveType.items, art);

@@ -903,3 +907,3 @@ return;

if (value.literal !== 'struct') {
warning(null, loc, {}, 'A struct value is required here');
warning(null, loc, { anno }, 'A struct value is required here for annotation $(ANNO)');
return;

@@ -915,25 +919,37 @@ }

if (value.literal !== 'string' && value.literal !== 'enum' &&
!elementDecl._effectiveType.enum)
warning(null, loc, { type }, 'A string value is required for type $(TYPE)');
!elementDecl._effectiveType.enum) {
warning(null, loc, { type, anno },
'A string value is required for type $(TYPE) for annotation $(ANNO)');
}
}
else if (builtins.isBinaryTypeName(type)) {
if (value.literal !== 'string' && value.literal !== 'x')
warning(null, loc, { type }, 'A hexadecimal string value is required for type $(TYPE)');
if (value.literal !== 'string' && value.literal !== 'x') {
warning(null, loc, { type, anno },
'A hexadecimal string value is required for type $(TYPE) for annotation $(ANNO)');
}
}
else if (builtins.isNumericTypeName(type)) {
if (value.literal !== 'number' && value.literal !== 'enum' &&
!elementDecl._effectiveType.enum)
warning(null, loc, { type }, 'A numerical value is required for type $(TYPE)');
!elementDecl._effectiveType.enum) {
warning(null, loc, { type, anno },
'A numerical value is required for type $(TYPE) for annotation $(ANNO)');
}
}
else if (builtins.isDateOrTimeTypeName(type)) {
if (value.literal !== 'date' && value.literal !== 'time' &&
value.literal !== 'timestamp' && value.literal !== 'string')
warning(null, loc, { type }, 'A date/time value or a string is required for type $(TYPE)');
value.literal !== 'timestamp' && value.literal !== 'string') {
warning(null, loc, { type, anno },
// eslint-disable-next-line max-len
'A date/time value or a string is required for type $(TYPE) for annotation $(ANNO)');
}
}
else if (builtins.isBooleanTypeName(type)) {
if (value.literal && value.literal !== 'boolean')
warning(null, loc, { type }, 'A boolean value is required for type $(TYPE)');
if (value.literal && value.literal !== 'boolean') {
warning(null, loc, { type, anno },
'A boolean value is required for type $(TYPE) for annotation $(ANNO)');
}
}
else if (builtins.isRelationTypeName(type) || builtins.isGeoTypeName(type)) {
warning(null, loc, { type }, 'Type $(TYPE) can\'t be assigned a value');
warning(null, loc, { type, anno },
'Type $(TYPE) can\'t be assigned a value for annotation $(ANNO)');
}

@@ -950,4 +966,4 @@ else if (!elementDecl._effectiveType.enum) {

if (!expectedEnum[value.sym.id]) {
// .. but no such constant
warning(null, loc, { id: `#${ value.sym.id }` }, 'Enum symbol $(ID) not found in enum');
// ... but no such constant
warning(null, loc, { id: `#${ value.sym.id }`, anno }, 'Enum symbol $(ID) not found in enum for annotation $(ANNO)');
}

@@ -957,3 +973,4 @@ }

// Enum symbol provided but not expected
warning(null, loc, { id: `#${ value.sym.id }`, type }, 'Can\'t use enum symbol $(ID) for non-enum type $(TYPE)');
warning(null, loc, { id: `#${ value.sym.id }`, type, anno },
'Can\'t use enum symbol $(ID) for non-enum type $(TYPE) for annotation $(ANNO)');
}

@@ -967,3 +984,3 @@ }

// ... and none of the valid enum symbols matches the value
warning(null, loc, {}, 'An enum value is required here');
warning(null, loc, { anno }, 'An enum value is required for annotation $(ANNO)');
}

@@ -970,0 +987,0 @@ }

@@ -126,3 +126,2 @@ // Compiler phase 1 = "define": transform dictionary of AST-like XSNs into XSN

forEachMember,
isBetaEnabled,
} = require('../base/model');

@@ -178,7 +177,2 @@ const shuffleGen = require('../base/shuffle');

} );
// During the definer, we can only resolve artifact references, i.e,
// after a `.`, we only search in the `_subArtifacts` dictionary:
model.$volatileFunctions.environment = function artifactsEnv( art ) {
return art._subArtifacts || Object.create(null);
};

@@ -338,2 +332,3 @@ let boundSelfParamType = true; // special `$self` for binding param must still be initialised

function addUsing( decl, src ) {
setLink( decl, '_block', src );
if (decl.usings) {

@@ -399,3 +394,3 @@ // e.g. `using {a,b} from 'file.cds'` -> recursive

setLink( ext, '_block', block );
const absolute = ext.name && resolveUncheckedPath( ext.name, 'extend', ext );
const absolute = ext.name && resolveUncheckedPath( ext.name, '_extensions', ext );
if (!absolute) // broken path

@@ -430,5 +425,7 @@ return;

function initExtension( parent ) {
forEachMember( parent, function init( sub ) {
forEachMember( parent, function initExtensionMember( sub, name, prop ) {
if (sub.kind !== 'extend' && sub.kind !== 'annotate')
return; // for defs inside, set somewhere else - TODO: rethink
if (prop === 'params' && name === '') // RETURNS
sub.name = { id: '', location: sub.location };
setLink( sub, '_block', parent._block );

@@ -520,3 +517,5 @@ setLink( sub, '_parent', parent );

}
else {
else if (!art.builtin) {
// TODO: better messages with definitions with the same name as builtin,
// especially if there is just one
error( 'duplicate-definition', [ art.name.location, art ], {

@@ -564,3 +563,3 @@ name: art.name.absolute,

if (!decl.$duplicates) { // do not have two duplicate messages
error( 'duplicate-using', [ decl.name.location, null ], { name }, // TODO: semantic
error( 'duplicate-using', [ decl.name.location, decl ], { name },
'Duplicate definition of top-level name $(NAME)' );

@@ -644,3 +643,2 @@ }

art.$tableAliases[selfname] = self;
setLink( art, '_$next', model.$magicVariables );
}

@@ -724,5 +722,3 @@

setLink( query, '_$next',
// if art is $tableAlias, set to embedding query
(!art._main || art.kind === 'select' || art.kind === '$join')
? art : art._parent ); // TODO: check with name resolution change
(art.kind === '$tableAlias' ? art._parent._$next : art) );
setLink( query, '_block', art._block );

@@ -831,3 +827,3 @@ query.kind = 'select';

if (table.name?.id[0] === '$' && table.name.$inferred !== '$internal') {
warning( 'name-invalid-dollar-alias', [ table.name.location, table ], {
message( 'name-invalid-dollar-alias', [ table.name.location, table ], {
'#': (table.name.$inferred ? '$tableImplicit' : '$tableAlias'),

@@ -854,2 +850,3 @@ name: '$',

function initExprForQuery( expr, query ) {
// TODO: use traverseExpr()
if (Array.isArray(expr)) { // TODO: old-style $parens ?

@@ -890,3 +887,3 @@ expr.forEach( e => initExprForQuery( e, query ) );

if (mixin.name.id[0] === '$') {
warning( 'name-invalid-dollar-alias', [ mixin.name.location, mixin ],
message( 'name-invalid-dollar-alias', [ mixin.name.location, mixin ],
{ '#': 'mixin', name: '$' } );

@@ -906,3 +903,3 @@ }

hasItems = true;
if (!columns) {
if (!columns) { // expand or inline
if (parent.value)

@@ -1075,2 +1072,3 @@ setLink( col, '_pathHead', parent ); // also set for '*' in expand/inline

// in extensions, extended enums are represented as elements
let firstEnum = null;
for (const n in obj.elements) {

@@ -1082,2 +1080,3 @@ const e = obj.elements[n];

e.kind = 'enum';
firstEnum = firstEnum || e;
}

@@ -1094,2 +1093,9 @@ else {

}
if (firstEnum && block.$frontend !== 'json') {
// Don't emit this message if `ext-unexpected-element` was already emitted.
// This message is similar to the one above. In v5/6, we could probably remove the warning
// and always emit the error.
warning( 'ext-expecting-enum', [ firstEnum.location, construct ],
{ code: 'extend … with enum' }, 'Use $(CODE) when extending enums' );
}
forEachGeneric( { enum: obj.elements }, 'enum', init );

@@ -1185,6 +1191,2 @@ }

}
if (elem.value.stored?.val === true && !isBetaEnabled(options, 'calculatedElementsOnWrite')) {
const loc = [ elem.value.stored.location, elem ];
message( 'def-unsupported-calc-elem', loc, { '#': 'on-write' } );
}
elem.$syntax = 'calc';

@@ -1210,4 +1212,7 @@ }

const type = first?.type || first?.items?.type; // this sequence = no derived type
if (type?.path?.length === 1 && type?.path[0]?.id === '$self') // TODO: no where: ?
const path = type?.path;
if (path?.length === 1 && path[0]?.id === '$self') { // TODO: no where: ?
setLink( type, '_artifact', boundSelfParamType );
setLink( path[0], '_artifact', boundSelfParamType );
}
}

@@ -1233,7 +1238,7 @@

if (prop === 'actions') {
error( 'unexpected-actions', [ location, construct ], {},
error( 'def-unexpected-actions', [ location, construct ], {},
'Actions and functions only exist top-level and for entities' );
}
else if (parent.kind === 'action' || parent.kind === 'function') {
error( 'extend-action', [ construct.location, construct ], { '#': parent.kind }, {
error( 'ext-unexpected-action', [ construct.location, construct ], { '#': parent.kind }, {
std: 'Actions and functions can\'t be extended, only annotated',

@@ -1276,3 +1281,3 @@ action: 'Actions can\'t be extended, only annotated',

else { // if (prop === 'enum') {
error( 'unexpected-enum', [ location, construct ], {},
error( 'def-unexpected-enum', [ location, construct ], {},
'Enum symbols can only be defined for types or typed constructs' );

@@ -1293,3 +1298,3 @@ }

return false;
const name = resolveUncheckedPath( target, 'compositionTarget', elem );
const name = resolveUncheckedPath( target, 'target', elem );
const aspect = name && model.definitions[name];

@@ -1296,0 +1301,0 @@ return aspect && (aspect.kind === 'aspect' || aspect.kind === 'type'); // type is sloppy

@@ -7,5 +7,6 @@ // Extend

const {
forEachInOrder, forEachDefinition,
forEachInOrder,
forEachDefinition,
forEachMember,
isBetaEnabled,
isDeprecatedEnabled,
} = require('../base/model');

@@ -25,2 +26,3 @@ const { dictAdd, pushToDict } = require('../base/dictionaries');

const layers = require('./moduleLayers');
const { CompilerAssertion } = require('../base/error');

@@ -34,3 +36,2 @@ const $location = Symbol.for('cds.$location');

function extend( model ) {
const { options } = model;
// Get simplified "resolve" functionality and the message function:

@@ -55,2 +56,4 @@ const {

const includesNonShadowedFirst = isDeprecatedEnabled(model.options, 'includesNonShadowedFirst');
sortModelSources();

@@ -85,4 +88,4 @@ const extensionsDict = Object.create(null); // TODO TMP

if (!art._main && !art._outer && art._extensions === undefined &&
art.name && // TODO: probably just a workaround, check with TODO in getOriginRaw()
art.kind !== 'namespace') {
// if (!art.name) console.log(art)
const { absolute } = art.name;

@@ -131,12 +134,12 @@ setLink( art, '_extensions', model.$collectedExtensions[absolute]?._extensions || null );

applyTypeExtensions( art, exts.srid, 'srid' );
checkPrecisionScaleExtension( art, exts );
delete art.$typeExts;
}
// TODO tmp: no proper XSN representation yet for annotate … with returns:
if (art.kind === 'annotate' && !art.returns &&
(extensionsMap.elements?.some( e => e.$syntax === 'returns' ) ||
extensionsMap.enum?.some( e => e.$syntax === 'returns' )))
if (art.kind === 'annotate' && !art.returns && extensionsMap.returns)
annotateCreate( art, '', art, 'returns' );
moveDictExtensions( art, extensionsMap, 'params' );
// moveReturnsExtensions( art, extensionsMap );
moveReturnsExtensions( art, extensionsMap );
const sub = art.returns || art.items || art.targetAspect?.elements && art.targetAspect;

@@ -149,4 +152,4 @@ if (sub) {

// care about 'ext-unexpected-returns' in a later change if we have XSN returns
pushToDict( sub, '_extensions', ...extensionsMap.elements || [] );
pushToDict( sub, '_extensions', ...extensionsMap.enum || [] );
pushToDict( sub, '_extensions', ...avoidRecursiveReturns(extensionsMap, 'elements'));
pushToDict( sub, '_extensions', ...avoidRecursiveReturns(extensionsMap, 'enum') );
}

@@ -162,2 +165,21 @@ else {

/**
* FIXME: Remove this workaround. This workaround avoids endless recursion for annotate statements
* that have both "returns" and "elements". The endless recursion happens due to the
* pushDict on `sub`. The `elements` extensions will point to the same extension on which
* `returns` exist.
*
* @param extensionsMap
* @param {string} prop
* @return {XSN.Extension[]}
*/
function avoidRecursiveReturns( extensionsMap, prop ) {
if (!extensionsMap[prop])
return [];
const exts = [];
for (const ext of extensionsMap[prop])
exts.push({ ...ext, returns: undefined });
return exts;
}
/**
* Create super annotate statements for remaining extensions

@@ -176,3 +198,3 @@ */

// TODO: delete again
// TODO: delete again - if not, what about extensions in contexts/services?
function setArtifactLinkForExtensions( source ) {

@@ -183,4 +205,6 @@ if (!source.extensions)

const { name } = ext;
if (name?.absolute && name._artifact === undefined) // no link set yet
resolvePath( name, ext.kind, ext ); // for LSP
if (name?.absolute && name._artifact === undefined) {
const refCtx = (name.absolute.startsWith( 'localized.' )) ? '_extensions' : ext.kind;
resolvePath( name, refCtx, ext ); // for LSP
}
}

@@ -223,3 +247,3 @@ }

for (const prop in ext) {
if (ext[prop] === undefined) // deleted propery
if (ext[prop] === undefined) // deleted property
continue;

@@ -371,2 +395,4 @@ // TODO: do this check nicer (after complete move to new extensions mechanism)

const { query } = art;
for (const col of ext.columns)
col.$extended = true;
if (!query?.from?.path)

@@ -547,2 +573,20 @@ error( 'extend-columns', [ ext.columns[$location], ext ], { art } );

/**
* If the target artifact has both precision and scale set, then extensions on it must also
* provide both to avoid user errors for subsequent `extend` statements.
*
* @param {XSN.Artifact} art
* @param {object} exts
*/
function checkPrecisionScaleExtension( art, exts ) {
if (art.precision && art.scale) {
if ((exts.precision || exts.scale) && !(exts.precision && exts.scale)) {
const missing = exts.precision ? 'scale' : 'precision';
const prop = exts.precision ? 'precision' : 'scale';
error( 'ext-missing-type-property', [ exts[prop].location, exts[prop] ],
{ art, prop, otherprop: missing } );
}
}
}
function allowsTypeArgument( art, prop ) {

@@ -564,4 +608,4 @@ const { parameters } = art._effectiveType;

const extDict = ext[extProp];
let dictCheck = (art.kind !== 'annotate'); // no check in super annotate statement
for (const name in extDict) {
let dictCheck = (art.kind !== 'annotate'); // no check in super annotate statement
const elemExt = extDict[name];

@@ -578,10 +622,25 @@ if (elemExt.kind !== 'annotate' && elemExt.kind !== 'extend') // TODO: specified elems

// function moveReturnsExtensions( art, extensionsMap ) {
// const artReturns = art.returns;
// const extensions = extensionsMap.returns;
// // TODO: artItem is null
// for (const ext of extensions)
// pushToDict( artReturns, '_extensions', ext.returns );
// }
function moveReturnsExtensions( art, extensionsMap ) {
if (!extensionsMap.returns)
return;
for (const ext of extensionsMap.returns) {
if (!art.returns && art.kind !== 'annotate') { // no check in super annotate statement
const variant = art.kind === 'action' || art.kind === 'function' ? art.kind : 'std';
warning( 'ext-unexpected-returns', [ ext.returns.location, ext ], {
'#': variant, keyword: 'returns',
}, {
std: 'Unexpected $(KEYWORD); only actions and functions have return parameters',
action: 'Unexpected $(KEYWORD) for action without return parameter',
// function without `returns` can happen via CSN input!
function: 'Unexpected $(KEYWORD) for function without return parameter',
});
}
// If `!art.returns`, then it could be CSN from SQL, where actions are replaced by dummies.
const elem = art.returns || annotateFor( art, 'params', '' );
setLink( ext.name, '_artifact', (elem.kind !== 'annotate' ? elem : null) );
pushToDict(elem, '_extensions', ext.returns);
}
}
function annotateFor( art, prop, name ) {

@@ -598,3 +657,3 @@ const base = annotateBase( art );

function annotateBase( art ) {
while (art._outer) // TOOD: think about anonymous target aspect
while (art._outer) // TODO: think about anonymous target aspect
art = art._outer;

@@ -635,15 +694,9 @@ if (art.kind === 'annotate')

for (const ext of extensions || []) {
if (ext.$syntax === 'returns') { // TODO tmp: no proper XSN representation
ext.$syntax = '$inside-returns';
delete ext.params;
}
else {
warning( 'ext-expected-returns', [ ext.name.location, ext ], {
'#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
}, {
std: 'Expected $(CODE)', // unused variant
action: 'Expected $(KEYWORD) when annotating action return structure, i.e. $(CODE)',
function: 'Expected $(KEYWORD) when annotating function return structure, i.e. $(CODE)',
} );
}
warning( 'ext-expecting-returns', [ ext.name.location, ext ], {
'#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
}, {
std: 'Expected $(CODE)', // unused variant
action: 'Expected $(KEYWORD) when annotating action return structure, i.e. $(CODE)',
function: 'Expected $(KEYWORD) when annotating function return structure, i.e. $(CODE)',
} );
}

@@ -686,8 +739,5 @@ }

break;
case 'actions':
warning( 'anno-unexpected-actions', [ location, ext._parent ], {},
'Actions and functions only exist top-level and for entities' );
break;
default:
// assert
if (model.options.testMode)
throw new CompilerAssertion(`Missing case for prop: ${ prop }`);
}

@@ -788,13 +838,8 @@ return false;

if (construct.kind === 'annotate' && art._block?.$frontend === 'cdl') {
if (construct.$syntax === 'returns' && art.kind !== 'action' && art.kind !== 'function' ) {
// `annotate ABC with returns {}` is handled just like `elements`. Warn if it is used
// for non-actions. We can't only check for !art.returns, because `action A();` is valid.
// `art._block` ensures that `art` is a defined def.
return;
// warning('ext-unexpected-returns', [ construct.name.location, construct ],
// { keyword: 'returns', meta: art.kind }, 'Unexpected $(KEYWORD) for $(META)');
if (construct.returns && art.kind !== 'action' && art.kind !== 'function' ) {
// See moveReturnsExtensions()
}
else if (construct.$syntax !== 'returns' &&
else if (!construct.returns &&
(art.kind === 'action' || art.kind === 'function') && construct.elements) {
warning('ext-expected-returns', [ construct.name.location, construct ], {
warning('ext-expecting-returns', [ construct.name.location, construct ], {
'#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',

@@ -1106,9 +1151,17 @@ }, {

ext[prop] = Object.create(null); // TODO: do not set actions property if there are none
let hasNewElement = false;
for (const ref of ext.includes) {
const template = ref._artifact; // already resolved
if (template) { // be robust
// eslint-disable-next-line no-loop-func
forEachInOrder( template, prop, ( origin, name ) => {
if (members && name in members)
return; // warning for overwritten element in checks.js
if (members && members[name]) {
if (!includesNonShadowedFirst && !ext[prop][name])
dictAdd( ext[prop], name, members[name] ); // to keep order
return;
}
hasNewElement = true;
const elem = linkToOrigin( origin, name, parent, prop, weakLocation( ref.location ) );
setLink( elem, '_block', origin._block );
if (!parent) // not yet set for EXTEND foo WITH bar => linkToOrigin() did not add it

@@ -1124,2 +1177,3 @@ dictAdd( ext[prop], name, elem );

// all usages in the expressions? Possibly just the first one?
// TODO: Unify with coding in extend.js
elem.value = Object.assign( { $inferred: 'include' }, copyExpr( origin.value ));

@@ -1133,7 +1187,15 @@ elem.$syntax = 'calc';

}
checkRedefinitionThroughIncludes( parent, prop );
// TODO: expand elements having direct elements (if needed)
if (members) {
if (!hasNewElement && members) {
ext[prop] = members;
}
else if (members) {
// TODO: expand elements having direct elements (if needed)
forEachInOrder( { [prop]: members }, prop, ( elem, name ) => {
dictAdd( ext[prop], name, elem );
// The element could have been added in the previous loop (includes) to keep
// the element order.
if (ext[prop][name] !== elem )
dictAdd( ext[prop], name, elem );
});

@@ -1155,11 +1217,4 @@ }

const includes = [ member, ...member.$duplicates ].map(dup => dup._origin._main);
if (isBetaEnabled(options, 'v4preview')) {
error( 'duplicate-definition', [ parent.name.location, member ],
{ '#': `include-${ prop }`, name, sorted_arts: includes } );
}
else {
// Error accidentally removed in v2/v3, therefore only a warning.
warning( 'ref-duplicate-include-member', [ parent.name.location, member ],
{ '#': prop, name, sorted_arts: includes } );
}
error( 'duplicate-definition', [ parent.name.location, member ],
{ '#': `include-${ prop }`, name, sorted_arts: includes } );
}

@@ -1166,0 +1221,0 @@ });

@@ -16,4 +16,4 @@ // Things which needs to done for parse.cdl after define()

const { CompilerAssertion } = require('../base/error');
const { forEachGeneric } = require('../base/model');
const { setLink, setArtifactLink } = require('./utils');

@@ -28,3 +28,2 @@

// Get simplified "resolve" functionality and the message function:
const { message, error } = model.$messageFunctions;
const {

@@ -46,3 +45,3 @@ resolveUncheckedPath,

for (const ext of late[name]._extensions) {
ext.name.absolute = resolveUncheckedPath( ext.name, 'extend', ext );
ext.name.absolute = resolveUncheckedPath( ext.name, '_extensions', ext );
// Initialize members and define annotations in sub-elements.

@@ -59,3 +58,3 @@ initMembers( ext, ext, ext._block, true );

// TODO: do a sort based on the location in case there were two extensions
// on the same definition?
// on the same definition? Yes: anno first outside, then inside service def
model.extensions = extensions;

@@ -156,2 +155,4 @@ model.extensions.forEach(ext => resolveTypesForParseCdl(ext, ext));

*
* TODO: this should basically be covered by a function of shared.js
*
* @param {object} artWithType

@@ -161,44 +162,12 @@ * @param {XSN.Artifact} user

function resolveTypeUnchecked( artWithType, user ) {
const root = artWithType.type.path && artWithType.type.path[0];
if (!root) // parse error
return;
// `scope` is only `typeOf` for `type of element` and not
// `type of Entity:element`. For the latter we can resolve the path
// without special treatment.
if (artWithType.type.scope !== 'typeOf') {
// elem: Type or elem: type of Artifact:elem
const name = resolveUncheckedPath( artWithType.type, 'type', user );
const type = name && model.definitions[name] || { name: { absolute: name } };
const name = resolveUncheckedPath( artWithType.type, 'type', user );
if (name) { // correct ref to main artifact
const type = model.definitions[name];
resolveTypeArgumentsUnchecked( artWithType, type, user );
}
else if (!user._main) {
error( 'ref-undefined-typeof', [ artWithType.type.location, user ], {},
'Current artifact has no element to refer to as type' );
else if (name === '' && artWithType.$typeArgs && model.options.testMode) {
// Ensure: parser does not allow type args with Main:elem/`type of`,
// extend … with type only with named type arguments
throw new CompilerAssertion( 'Unexpected type arguments for TYPE OF' );
}
else if (root.id === '$self' || root.id === '$projection') {
setArtifactLink( root, user._main );
}
else {
// For better error messages, check for invalid TYPE OFs similarly
// to how `resolveType()` does.
let struct = artWithType;
// `items` have no kind, but need to be skipped as well
while (struct.kind === 'element' || struct._outer?.items) {
if (struct._outer?.items)
struct = struct._outer;
else
struct = struct._parent;
}
if (struct.kind === 'select' || struct.kind === 'annotation' || struct !== user._main) {
message( 'type-unexpected-typeof', [ artWithType.type.location, user ],
{ keyword: 'type of', '#': struct.kind } );
return;
}
const fake = { name: { absolute: user.name.absolute } };
// to-csn just needs a fake element whose absolute name and _parent/_main links are correct
setLink( fake, '_parent', user._parent );
setLink( fake, '_main', user._main ); // value does not matter...
setArtifactLink( root, fake );
}
}

@@ -205,0 +174,0 @@ }

@@ -19,2 +19,3 @@ // Generate: localized data and managed compositions

isDirectComposition,
copyExpr,
} = require('./utils');

@@ -254,3 +255,4 @@

art._block.$withLocalized = true;
info( 'recalculated-text-entities', [ art.name.location, null ], {},
// no semantic loc: message only emitted once
info( 'def-unexpected-texts-entities', [ art.name.location, null ], {},
'Input CSN contains expansions for localized data' );

@@ -553,2 +555,3 @@ return textElems; // make compilation idempotent

// TODO: also do something special for TYPE OF inside `art`s own elements
// TODO: check for own - add test case with Type:elem (not TYPE OF elem)
name = resolveUncheckedPath( art.type, 'type', art );

@@ -599,3 +602,3 @@ art = name && model.definitions[name];

if (target && target.path)
target = resolvePath( origin.targetAspect, 'compositionTarget', origin );
target = resolvePath( origin.targetAspect, 'targetAspect', origin );
if (!target || !target.elements)

@@ -699,3 +702,2 @@ return;

const elements = Object.create(null);
const art = {

@@ -705,3 +707,3 @@ kind: 'entity',

location,
elements,
elements: Object.create(null),
$inferred: 'composition-entity',

@@ -761,3 +763,3 @@ };

function addProxyElements( proxyDict, elements, inferred, location, prefix = '', anno = '' ) {
// TODO: also use for includeMembers()?
// TODO: also use for includeMembers()? Both are similar. Combine?
for (const name in elements) {

@@ -767,2 +769,3 @@ const pname = `${ prefix }${ name }`;

const proxy = linkToOrigin( origin, pname, null, null, location || origin.location );
setLink( proxy, '_block', origin._block );
proxy.$inferred = inferred;

@@ -773,2 +776,10 @@ if (origin.masked)

proxy.key = Object.assign( { $inferred: 'include' }, origin.key );
if (origin.value && origin.$syntax === 'calc') {
// TODO: If paths become invalid in the new artifact, should we mark
// all usages in the expressions? Possibly just the first one?
// TODO: Unify with coding in extend.js
proxy.value = Object.assign( { $inferred: 'include' }, copyExpr( origin.value ));
proxy.$syntax = 'calc';
setLink( proxy, '_calcOrigin', origin._calcOrigin || origin );
}
if (anno)

@@ -775,0 +786,0 @@ setAnnotation( proxy, anno );

@@ -12,4 +12,2 @@ // Main XSN-based compiler functions

// e.g. `resolvePath` and similar will be attached to the XSN.
// - Functions which might be overwritten in a next sub module
// are added to `‹xsn›.$volatileFunctions`, currently just `environment`.

@@ -59,2 +57,3 @@ 'use strict';

super(...args);
this.code = 'ERR_CDS_COMPILER_INVOCATION';
this.errors = errs;

@@ -70,2 +69,3 @@ this.hasBeenReported = false;

super(...args);
this.code = 'ERR_CDS_COMPILER_ARGUMENT';
this.argument = arg;

@@ -416,4 +416,8 @@ }

function recompileX( csn, options ) {
// Explicitly set parseCdl to false because backends cannot handle it
options = { ...options, parseCdl: false, $recompile: true };
options = {
...options,
parseCdl: false, // Explicitly set parseCdl to false because backends cannot handle it
docComment: null, // Input is CSN: leave doc comments alone
$recompile: true,
};
// Reset csnFlavor: Use client style (default)

@@ -460,3 +464,2 @@ delete options.csnFlavor;

model.$functions = {};
model.$volatileFunctions = {};
fns( model ); // attach (mostly) paths functions

@@ -463,0 +466,0 @@ define( model );

@@ -80,3 +80,3 @@ // Kick-start: prepare to resolve all references

setLink( art, '_ancestors', 0 ); // avoid infloop with cyclic from
const name = resolveUncheckedPath( art.query.from, 'include', art ); // TODO: 'include'?
const name = resolveUncheckedPath( art.query.from, 'from', art );
art = name && model.definitions[name];

@@ -150,5 +150,6 @@ if (autoexposed)

const ref = def.extern;
const from = (topLevel ? def : src).fileDep;
const user = (topLevel ? def : src);
const from = user.fileDep;
if (art || !from || from.realname) // no error for non-existing ref with non-existing module
resolvePath( ref, 'global', def ); // TODO: consider FROM for validNames
resolvePath( ref, 'using', def ); // TODO: consider FROM for validNames
}

@@ -155,0 +156,0 @@ }

@@ -48,2 +48,3 @@ // Populate views with elements, elements with association targets, ...

} = require('./utils');
const { typeParameters } = require('./builtins');

@@ -53,2 +54,15 @@ const $inferred = Symbol.for('cds.$inferred');

/**
* These properties are copied from specified elements.
*/
const typePropertiesFromSpecifiedElements = {
// 'key' is special case, see setSpecifiedElementTypeProperties()
// TODO: Decide on behavior if an actual key does not have "key" property in specified elements,
// and another non-key is marked key in them.
// key: 'if-undefined',
default: 1,
notNull: 1,
localized: 1,
...typeParameters.expectedLiteralsFor,
};

@@ -69,3 +83,2 @@ // Export function of this file.

} = model.$functions;
model.$volatileFunctions.environment = environment;
Object.assign( model.$functions, {

@@ -75,2 +88,3 @@ effectiveType,

resolveType,
navigationEnv,
} );

@@ -120,13 +134,4 @@ // let depth = 100;

// Return effective search environment provided by artifact `art`, i.e. the
// `artifacts` or `elements` dictionary. For the latter, follow the `type`
// chain and resolve the association `target`. View elements are calculated
// on demand.
function environment( art, location, user, assocSpec ) {
const env = navigationEnv( art, location, user, assocSpec );
if (env === 0)
return 0;
return env && env.elements || Object.create(null);
}
// TODO: move setting dependencies and complaining about assocs in keys
// to resolvePath - then remove params: location, user
function navigationEnv( art, location, user, assocSpec ) {

@@ -140,3 +145,3 @@ // = effectiveType() on from-path, TODO: should actually already part of

type = effectiveType( type.items );
if (!type?.target)
if (!type?.target || assocSpec === 'targetAspectOnly')
return type;

@@ -224,8 +229,10 @@

art = populateArtifact( a, art ) || a;
setLink( a, '_effectiveType', art );
a.$effectiveSeqNo = ++effectiveSeqNo;
if (a.elements$ || a.enum$)
mergeSpecifiedElementsOrEnum( a );
setLink( a, '_effectiveType', art );
a.$effectiveSeqNo = ++effectiveSeqNo;
// console.log( 'ET-DO:', effectiveSeqNo, a?.kind, a?.name, a._extensions?.elements?.length )
extendArtifactAfter( a ); // after setting _effectiveType (for messages)
if (a.typeProps$)
setSpecifiedElementTypeProperties(a);
}

@@ -251,2 +258,5 @@ // console.log( 'ET-END:', art?.kind, art?.name )

}
else if (art.targetAspect) { // target aspect in aspect
return art;
}

@@ -324,4 +334,5 @@ // With properties to be calculated: ----------------------------------------

else {
// TODO: write checks for path in enum?
if (art.value?.path)
return resolvePath( art.value, 'expr', art, null );
return resolvePath( art.value, (art.$syntax === 'calc' ? 'calc' : 'expr'), art );
if (art.kind === 'select')

@@ -332,3 +343,3 @@ return getOrigin( dictFirst( art.$tableAliases ) );

// do not use navigationEnv(): it would always call effectiveType() on the
// source → we would would have a deeper callstack
// source → we would have a deeper callstack
const source = resolvePath( art, 'from', art._parent );

@@ -339,2 +350,3 @@ if (!source?._main)

// to call effectiveType() on the last assoc of a from ref:
// TODO: check this with test3/Queries/DollarSelf/CorruptedSource.err.cds
const assoc = effectiveType( source );

@@ -352,21 +364,2 @@ return resolvePath( assoc?.target, 'target', assoc );

user = user._outer;
if (ref.scope === 'typeOf') {
let struct = user;
while (struct.kind === 'element')
struct = struct._parent;
if (struct.kind === 'select' || struct.kind === 'annotation') {
// `type of` in annotation definitions can't work, because csn type refs
// always refer to definitions.
message( 'type-unexpected-typeof', [ ref.location, user ],
{ keyword: 'type of', '#': struct.kind } );
// we actually refer to an element in _combined; TODO: return null if
// not configurable; would produce illegal CSN with sub queries in FROM
}
else if (struct !== user._main) {
message( 'type-unexpected-typeof', [ ref.location, user ],
{ keyword: 'type of', '#': struct.kind } );
return setArtifactLink( ref, null );
}
return resolvePath( ref, 'typeOf', user );
}
if (user.kind === 'event')

@@ -540,4 +533,3 @@ return resolvePath( ref, 'eventType', user );

*
* We only copy annotations, since they are not part of `columns`,
* but only appear in `elements` in CSN.
* We only copy annotations.
*

@@ -551,2 +543,4 @@ * This is important to ensure re-compilability.

function mergeSpecifiedElementsOrEnum( art ) {
let wasAnnotated = false;
for (const id in (art.elements || art.enum)) {

@@ -560,3 +554,2 @@ const ielem = art.elements ? art.elements[id] : art.enum[id]; // inferred element

else {
let wasAnnotated = false;
for (const prop in selem) {

@@ -568,28 +561,36 @@ // just annotation assignments and doc comments for the moment

// may be lost during recompilation.
// TODO: Clarify: Should gensrc add this annotation to the column or as an
// annotate statement? Currently it's at the column.
ielem[prop].$priority = 'annotate';
wasAnnotated = true;
}
else if (typePropertiesFromSpecifiedElements[prop]) {
if (!ielem.typeProps$)
setLink(ielem, 'typeProps$', Object.create(null));
// Note: At this point in time, effectiveType() was likely not called on the
// element, yet. Setting it here, we can't compare it to it's value from _origin.
ielem.typeProps$[prop] = selem[prop];
}
}
if (wasAnnotated)
setExpandStatusAnnotate(art, 'annotate');
selem.$replacement = true;
if (selem.elements) {
if (selem.elements)
setLink(ielem, 'elements$', selem.elements);
delete selem.elements;
}
if (selem.enum) {
if (selem.enum)
setLink(ielem, 'enum$', selem.enum);
delete selem.enum;
}
}
}
if (wasAnnotated)
setExpandStatusAnnotate(art, 'annotate');
// TODO: We don't check enum$, yet! We first need to fix expansion for
// `cast(elem as EnumType)` (see #9421)
for (const id in art.elements$) {
const selem = art.elements$[id]; // specified element
if (!selem.$replacement) {
// console.log( 'QED:', art.name, art.kind, art.elements )
error( 'query-unspecified-element', [ selem.name.location, selem ], { id },
const specifiedElement = art.elements$[id];
// TODO: Custom kind?
specifiedElement.$isSpecifiedElement = true;
if (!specifiedElement.$replacement) {
const loc = [ specifiedElement.name.location, specifiedElement ];
error( 'query-unspecified-element', loc, { id },
'Element $(ID) does not result from the query' );

@@ -600,2 +601,20 @@ }

function setSpecifiedElementTypeProperties( art ) {
for (const prop in art.typeProps$) {
let o = art;
if (o._effectiveType !== 0) { // cyclic
while (!o[prop] && getOrigin(o))
o = getOrigin(o);
}
if (typePropertiesFromSpecifiedElements[prop] === 'if-undefined') {
if (!o[prop])
art[prop] = art.typeProps$[prop];
}
else if (!o[prop] || art.typeProps$[prop].val !== o[prop]?.val) {
art[prop] = art.typeProps$[prop];
}
}
}
function populateQuery( query ) {

@@ -653,3 +672,3 @@ if (query._combined || !query.from || !query.$tableAliases)

// need to ignore an explicit type, i.e. not getOrigin():
const assoc = resolvePath( elem.value, 'expr', elem, null );
const assoc = resolvePath( elem.value, 'expr', elem ); // TODO: extra 'column'?
if (!effectiveType( assoc )?.target)

@@ -748,4 +767,6 @@ return initFromColumns( elem, elem.expand );

if (!elem.type && elem.value?.type) { // top-level CAST( expr AS type )
if (!elem.target) // TODO: we might issue an error if there is a target
if (!elem.target) { // TODO: we might issue an error if there is a target
elem.type = { ...elem.value.type, $inferred: 'cast' };
// TODO: What about other direct properties in cast such as items/enum/...?
}
}

@@ -854,5 +875,8 @@ if (elem.foreignKeys) // REDIRECTED with explicit foreign keys

// if (envParent) console.log( 'CE:', envParent._origin, query );
return (envParent)
? environment( getOrigin( envParent ) ) // not the col with expand, but the referred
: userQuery( query )._combined;
if (!envParent)
return userQuery( query )._combined;
const env = navigationEnv( getOrigin( envParent ) );
if (env === 0)
return 0;
return env?.elements || Object.create(null);
}

@@ -926,2 +950,6 @@

return false;
// Specified elements could lead to warnings that seem unfixable by the user.
// TODO: Custom kind?
if (elem.$isSpecifiedElement)
return false;
const assocTarget = resolvePath( assoc.target, 'target', assoc );

@@ -933,3 +961,3 @@ let target = assocTarget;

return false; // error in target ref
const { location } = elem.value || elem.type || elem.name;
const { location } = elem.value || elem.type || elem.name || elem;
const service = (elem._main || elem)._service;

@@ -945,32 +973,5 @@ if (service && service !== target._service && assocIsToBeRedirected( elem )) {

return true;
const type = resolvePath( elem.type, 'type', elem ); // cds.Association or cds.Composition
const origin = {
kind: elem.kind, // necessary for rewrite, '$user-provided' would be best
name: elem.name,
type: { // TODO: necessary?
path: [ { id: type.name.absolute, location: elem.type.location } ],
scope: 'global',
location: elem.type.location,
$inferred: 'REDIRECTED',
},
target: elem.target,
$inferred: 'REDIRECTED',
location: elem.target.location,
};
setLink( elem, '_origin', origin );
setArtifactLink( elem.type, type );
setLink( origin, '_outer', elem );
setLink( origin, '_parent', elem._parent );
if (elem._main) // remark: the param `elem` can also be a type
setLink( origin, '_main', elem._main );
setLink( origin, '_effectiveType', origin );
setLink( origin, '_block', elem._block );
if (elem.foreignKeys) {
origin.foreignKeys = elem.foreignKeys;
delete elem.foreignKeys; // will be rewritten
}
if (elem.on) {
origin.on = elem.on;
delete elem.on; // will be rewritten
}
elem.target.$inferred = '';
setArtifactLink( elem.target, target );
return true;
}

@@ -1261,5 +1262,5 @@ if (target !== assocTarget)

}
error( 'duplicate-autoexposed', [ service.name.location, service ],
{ target, art: absolute },
'Name $(ART) of auto-exposed entity for $(TARGET) collides with other definition' );
message( 'def-duplicate-autoexposed', [ service.name.location, service ],
{ target, art: absolute },
'Name $(ART) of auto-exposed entity for $(TARGET) collides with other definition' );
info( null, [ target.name.location, target ],

@@ -1271,9 +1272,9 @@ { art: service },

const firstTarget = autoexposed.query.from._artifact;
error( 'duplicate-autoexposed', [ service.name.location, service ],
{ target: firstTarget, art: absolute },
'Name $(ART) of auto-exposed entity for $(TARGET) collides with other definition' );
message( 'def-duplicate-autoexposed', [ service.name.location, service ],
{ target: firstTarget, art: absolute },
'Name $(ART) of auto-exposed entity for $(TARGET) collides with other definition' );
info( null, [ firstTarget.name.location, firstTarget ],
{ art: service },
'Expose this (or the competing) entity explicitly in service $(ART)' );
autoexposed.$inferred = 'duplicate-autoexposed';
autoexposed.$inferred = 'def-duplicate-autoexposed';
return target;

@@ -1280,0 +1281,0 @@ }

@@ -16,3 +16,2 @@ // Propagate properties in XSN

isDeprecatedEnabled,
isBetaEnabled,
} = require( '../base/model');

@@ -47,4 +46,4 @@ const {

'@fiori.draft.enabled': onlyViaArtifact,
'@': annotation, // always except in 'items'
doc: annotation, // always except in 'items'
'@': annotation, // always except in 'items' (and parameters for entity return types)
doc: annotation, // always except in 'items' (and parameters for entity return types)
default: withKind, // always except in 'items'

@@ -79,3 +78,2 @@ virtual,

const { warning, throwWithError } = model.$messageFunctions;
const propagateToReturns = isBetaEnabled( options, 'v4preview' );

@@ -163,3 +161,3 @@ forEachDefinition( model, run );

// console.log('PROPS:',source&&source.name,'->',target.name)
const viaType = target.type && // TODO: falsy $inferred value instead 'cast'?
const viaType = target.type && // TODO: falsy $inferred value instead of 'cast'?
(!target.type.$inferred || target.type.$inferred === 'cast');

@@ -286,3 +284,3 @@ const keys = Object.keys( source );

if (!(from ? from[from.length - 1]._artifact : source)._main &&
!(propagateToReturns && target._parent && target._parent.returns === target))
!(target._parent && target._parent.returns === target))
annotation( prop, target, source );

@@ -292,4 +290,5 @@ }

function withKind( prop, target, source ) {
if (target.kind &&
(propagateToReturns || !target._parent || target._parent.returns !== target))
if (target.kind === 'param' && source.kind === 'entity')
return; // Don't propagate from entity types to parameters (+ return type).
if (target.kind)
always(prop, target, source); // not in 'items'

@@ -296,0 +295,0 @@ }

@@ -46,2 +46,3 @@ // Compiler phase "resolve": resolve all references

forEachInOrder,
isDeprecatedEnabled,
} = require('../base/model');

@@ -65,2 +66,3 @@ const { dictAdd } = require('../base/dictionaries');

traverseQueryPost,
traverseExpr,
} = require('./utils');

@@ -74,2 +76,6 @@

// TODO: make this part of specExpected in shared.js
// (standard: not possible on last if !ref.$expected → ref.scope: '$exists')
const expWithFilter = [ 'from', 'expand', 'inline' ];
// Export function of this file. Resolve type references in augmented CSN

@@ -93,3 +99,2 @@ // `model`. If the model has a property argument `messages`, do not throw

} = model.$functions;
const { environment } = model.$volatileFunctions;
Object.assign( model.$functions, {

@@ -99,3 +104,4 @@ resolveExpr,

/** @type {any} may also be a boolean */
const ignoreSpecifiedElements
= isDeprecatedEnabled(model.options, 'ignoreSpecifiedQueryElements');

@@ -105,7 +111,7 @@ return doResolve();

function doResolve() {
// Phase 1: check paths in usings has been moved to kick-start.js Phase 2:
// Phase 1: check paths in `usings` has been moved to kick-start.js Phase 2:
// calculate/init view elements & collect views in order:
// TODO: It might be that we need to call propagateKeyProps() and
// addImplicitForeignKeys() in populate.js, as we might need to know the
// foreign keys in populate.js (foreign key access w/o JOINs).
// addImplicitForeignKeys() in populate.js, as we might need to know the
// foreign keys in populate.js (foreign key access w/o JOINs).

@@ -152,3 +158,3 @@ // Phase 2+3: calculate keys along simple queries in collected views:

// TODO: or should we push elems with `expand` sibling to extra list for
// better messages? (Whatever that means exaclty.)
// better messages? (Whatever that means exactly.)
const nav = pathNavigation( elem.value );

@@ -400,5 +406,2 @@ const { path } = elem.value;

if (obj.target) {
// console.log(obj.name,obj._origin?.name,obj)
if (obj._origin && obj._origin.$inferred === 'REDIRECTED')
resolveTarget( art, obj._origin );
// console.log(error( 'test-target', [ obj.location, obj ],

@@ -417,11 +420,5 @@ // { target: obj.target, kind: obj.kind }, 'Target: $(TARGET), Kind $(KIND)'));

}
if (art.targetElement) { // in foreign keys
const target = parent && parent.target;
if (target && target._artifact) {
// we just look in target for the path
// TODO: also check that we do not follow associations? no args, no filter
resolvePath( art.targetElement, 'targetElement', art,
environment( target._artifact ), target._artifact );
}
}
if (art.targetElement) // in foreign keys
resolvePath( art.targetElement, 'targetElement', art );
// Resolve projections/views

@@ -448,3 +445,3 @@ // if (art.query)console.log( info( null, [art.query.location,art.query], 'VQ:' ).toString() );

resolveExpr( art.default, 'default', art );
resolveExpr( art.value, 'expr', art, undefined, art.expand || art.inline );
resolveExpr( art.value, (art.$syntax === 'calc' ? 'calc' : 'expr'), art );
if (art.type?.$inferred === 'cast')

@@ -460,9 +457,225 @@ inferTypePropertiesFromCast( art );

forEachMember( art, resolveRefs, art.targetAspect );
// After the resolving of foreign keys (and adding implicit ones):
if (obj.target?.$inferred === '')
checkRedirectedUserTarget( art );
if (!ignoreSpecifiedElements && art.elements$ && art.elements) {
for (const id in art.elements$) {
resolveRefs(art.elements$[id]);
checkSpecifiedElement(art.elements[id], art.elements$[id]);
}
}
/**
* Check whether the signature of the specified element matches that of the inferred one.
*
* TODO:
* - This function has a lot of quite similar code blocks; it should be refactored to
* combine them.
* - Some checks are not performed because of to.sql() backend "bugs", that affect the
* recompilation, such as flattening removing/not setting "key" where required.
*
* @param {XSN.Element} inferredElement
* @param {XSN.Element} specifiedElement
* @param {XSN.Element} user Only used for if specifiedElement is actually an `items`
*/
function checkSpecifiedElement( inferredElement, specifiedElement, user = specifiedElement ) {
if (!inferredElement || !specifiedElement)
return;
// Check explicit types: If either side has one, so must the other.
const sType = specifiedElement.type?._artifact;
const iType = getInferredPropFromOrigin('type')?._artifact || inferredElement;
// xor: could be missing a type;
// FIXME: The coding above returns incorrect iType for expand on associations
if (!specifiedElement.type && inferredElement.type) {
error('query-mismatched-element', [ specifiedElement.location, user ], {
'#': !specifiedElement.type ? 'missing' : 'extra', name: user.name.id, prop: 'type',
});
return;
}
// If specified type is `null`, type could not be resolved.
else if (sType && sType !== iType) {
const typeName = iType?.name && sType?.name ? 'typeName' : 'type';
error('query-mismatched-element', [
specifiedElement.type.location || specifiedElement.location, user,
], {
'#': typeName,
name: user.name.id,
type: sType,
othertype: iType,
});
return;
}
// This relies on (element) expansion! Check that both sides have the following properties.
// On the inferred side, they are likely expanded.
if (!hasXorPropMismatch('elements') && !hasXorPropMismatch('items') &&
!hasXorPropMismatch('target') && !hasXorPropMismatch('enum')) {
// Element are already traversed via elements$ merging.
// only check items, if the specified one is not expanded/inferred
if (specifiedElement.items && !specifiedElement.items.$inferred)
checkSpecifiedElement(inferredElement.items, specifiedElement.items, specifiedElement);
if (specifiedElement.target &&
specifiedElement.target._artifact !== inferredElement.target._artifact) {
error('query-mismatched-element', [
specifiedElement.target.location || specifiedElement.location, user,
], {
'#': 'target',
name: user.name.id,
target: specifiedElement.target,
art: inferredElement.target,
});
}
if (specifiedElement.foreignKeys) {
const sKeys = Object.keys(specifiedElement.foreignKeys);
/** @type {any} */
let iKeys = inferredElement;
if (inferredElement._effectiveType !== 0) {
while (iKeys._origin && !iKeys.foreignKeys)
iKeys = iKeys._origin;
}
iKeys = Object.keys(iKeys.foreignKeys || {});
if (sKeys.length !== iKeys.length || sKeys.some(key => !iKeys.includes(key))) {
error('query-mismatched-element', [
specifiedElement.foreignKeys.location || specifiedElement.location, user,
], {
'#': 'foreignKeys',
name: user.name.id,
target: specifiedElement.target,
art: inferredElement.target,
});
}
}
if (specifiedElement.virtual) {
const iVirtual = getInferredPropFromOrigin('virtual')?.val || false;
if (!specifiedElement.virtual.val !== !iVirtual) {
error('query-mismatched-element', [
specifiedElement.virtual.location || specifiedElement.location, user,
], {
'#': 'prop', prop: 'virtual', name: user.name.id,
});
}
}
// If cardinality is not specified, the compiler uses the inferred one.
if (specifiedElement.cardinality) {
const sCardinality = specifiedElement.cardinality;
const iCardinality = getInferredPropFromOrigin('cardinality');
if (!iCardinality) {
error('query-mismatched-element', [
sCardinality.location || specifiedElement.location, user,
], {
'#': 'extra',
prop: 'cardinality',
name: user.name.id,
});
}
else {
// Note: Cardinality does not have sourceMin (CSN "srcmin").
const props = {
targetMax: 'max',
targetMin: 'min',
sourceMax: 'src',
};
for (const prop in props) {
if (sCardinality[prop]?.val === iCardinality[prop]?.val)
continue;
error('query-mismatched-element', [
sCardinality[prop]?.location || sCardinality.location || specifiedElement.location,
user,
], {
// eslint-disable-next-line no-nested-ternary
'#': !sCardinality[prop] ? 'missing' : (iCardinality[prop] ? 'prop' : 'extra'),
prop: `cardinality.${ props[prop] }`,
name: user.name.id,
});
}
}
}
if (specifiedElement.value) {
error('query-unexpected-property', [
specifiedElement.value.location || specifiedElement.location, user,
], {
'#': 'calculatedElement', prop: 'value', name: user.name.id,
});
}
if (specifiedElement.key) { // TODO: `|| inferredElement.key?.val`, once to.sql is fixed
// TODO: Do not use _origin chain for key; has been propagated in propagateKeyProps().
const iKey = getInferredPropFromOrigin('key')?.val;
// If "key" is specified or truthy in the inferred element, the values must match.
if (!iKey !== !specifiedElement.key?.val) {
error('query-mismatched-element', [
specifiedElement.key?.location || specifiedElement.location, user,
], {
'#': specifiedElement.key ? 'prop' : 'missing', prop: 'key', name: user.name.id,
});
}
}
if (specifiedElement.enum) {
const iEnum = inferredElement.enum || inferredElement.value?.enum;
forEachGeneric(specifiedElement, 'enum', (sVal, name) => {
const iVal = iEnum[name];
if (!iVal) {
error('query-mismatched-element', [ specifiedElement.location, user ], {
'#': 'std',
name: user.name.id,
prop: 'enum',
});
}
// TODO: Get $expanded enum values
/* if (iVal.value?.val !== sVal.value?.val || iVal.value?.['#'] !== sVal.value?.['#']) {
error('query-mismatched-element', [ specifiedElement.location, user ], {
'#': 'std',
name: user.name.id,
prop: 'enum',
});
} */
});
}
}
function hasXorPropMismatch( prop ) {
// FIXME: `.value` check should be removed after #11183
// It appears the SQL backends expand a type in cast and add an `enum` property there.
// This property is directly in the `value` property, but not part of `inferredElement`
// which has a `type` property, but no `enum`.
if (!inferredElement[prop] !== !specifiedElement[prop] &&
!inferredElement.value?.[prop] !== !specifiedElement[prop]) {
error( 'query-mismatched-element', [ specifiedElement.location, specifiedElement ], {
'#': specifiedElement[prop] ? 'extra' : 'missing', name: user.name.id, prop,
});
return true;
}
return false;
}
function getInferredPropFromOrigin( prop ) {
// Inferred property via _origin chain (0 === circular).
let element = inferredElement;
if (element._effectiveType !== 0) {
while (getOrigin(element) && !element[prop])
element = getOrigin(element);
}
return element[prop];
}
}
// Set '@Core.Computed' in the Core Compiler to have it propagated...
if (art.kind !== 'element' || art['@Core.Computed'])
return;
if (art.virtual && art.virtual.val ||
if (art.virtual?.val ||
art.value &&
(!art.value._artifact || !art.value.path || // in localization view: _artifact, but no path
art.value.stored?.val || // calculated elements on-write are always computed
art.value._artifact.kind === 'builtin' || art.value._artifact.kind === 'param' )) {

@@ -507,7 +720,20 @@ art['@Core.Computed'] = {

else if (effectiveType(art)?.elements) {
if (art.type)
error( 'type-unexpected-structure', [ art.type.location, art ], { '#': 'calc' } );
else
error( 'ref-unexpected-structured', [ art.value.location, art ], { '#': 'expr' } );
// For inferred (e.g. included) calc elements, this error is already emitted at the origin.
if (!art.$inferred) {
if (art.type)
error( 'type-unexpected-structure', [ art.type.location, art ], { '#': 'calc' } );
else
error( 'ref-unexpected-structured', [ art.value.location, art ], { '#': 'expr' } );
}
}
else if (effectiveType(art)?.items) {
// For inferred (e.g. included) calc elements, this error is already emitted at the origin.
if (!art.$inferred) {
const isCast = art.type?.$inferred === 'cast';
error( 'type-unexpected-many', [ (art.type || art.value).location, art ], {
'#': (!art.type && 'calc-implicit') || (isCast && 'calc-cast') || 'calc',
elemref: art.type ? undefined : { ref: art.value.path },
} );
}
}
else {

@@ -582,3 +808,3 @@ const noTruthyAllowed = [ 'localized', 'key', 'virtual' ];

for (const col of query.$inlines)
resolveExpr( col.value, 'expr', col, undefined, true );
resolveExpr( col.value, 'expr', col );
// for (const col of query.$inlines)

@@ -591,13 +817,13 @@ // if (!col.value.path) throw new CompilerAssertion(col.name.element)

if (query.where)
resolveExpr( query.where, 'expr', query, query._combined );
resolveExpr( query.where, 'expr', query ); // TODO: extra 'where'?
if (query.groupBy)
resolveBy( query.groupBy, 'expr' );
resolveExpr( query.having, 'expr', query, query._combined );
resolveBy( query.groupBy, 'expr', 'expr' ); // TODO: extra 'groupBy'?
resolveExpr( query.having, 'expr', query ); // TODO: extra 'having' or 'where'?
if (query.$orderBy) // ORDER BY from UNION:
// TODO clarify: can I access the tab alias of outer queries? If not:
// 4th arg query._main instead query._parent.
resolveBy( query.$orderBy, 'order-by-union', query.elements, query._parent );
resolveBy( query.$orderBy, 'order-by-set-ref', 'order-by-set-expr' );
if (query.orderBy) { // ORDER BY
// search in `query.elements` after having checked table aliases of the current query
resolveBy( query.orderBy, 'order-by', query.elements );
resolveBy( query.orderBy, 'order-by-ref', 'order-by-expr' );
// TODO: disallow resulting element ref if in expression!

@@ -615,3 +841,3 @@ // Necessary to check it in the compiler as it might work with other semantics on DB!

if (join.on)
resolveExpr( join.on, 'expr', query, query._combined );
resolveExpr( join.on, 'joinOn', query );
// TODO: check restrictions according to join "query"

@@ -627,10 +853,10 @@ }

//
// This seem to be similar, but different in SQLite 3.22.0: ORDER BY seems
// This seems to be similar, but different in SQLite 3.22.0: ORDER BY seems
// to bind stronger than UNION (see <SQLite>/src/parse.y), and the name
// resolution seems to use select item aliases from all SELECTs of the
// UNION (see <SQLite>/test/tkt2822.test).
function resolveBy( array, mode, pathDict, q ) {
function resolveBy( array, refMode, exprMode ) {
for (const value of array ) {
if (value)
resolveExpr( value, mode, q || query, value.path && pathDict );
resolveExpr( value, (value.path ? refMode : exprMode), query );
}

@@ -641,6 +867,5 @@ }

function resolveTarget( art, obj ) {
if (art !== obj && obj.on && obj.$inferred !== 'REDIRECTED') {
message( 'assoc-in-array', [ obj.on.location, art ], {},
// TODO: also check parameter parent, two messages?
'An association can\'t be used for arrays or parameters' );
if (art !== obj && obj.on) {
// Unmanaged assoc inside items. Unmanaged assoc in param handled in resolveRefs()
message( 'type-invalid-items', [ obj.on.location, art ], { '#': 'assoc', prop: 'items' } );
setArtifactLink( obj.target, undefined );

@@ -673,15 +898,6 @@ return;

}
else if (obj.$inferred !== 'REDIRECTED') {
else {
// TODO: extra with $inferred (to avoid messages)?
// TODO: in the ON condition of an explicitly provided model entity
// which is going to be implicitly redirected, we can never navigate
// along associations, even not to the foreign keys (at least if they
// are renamed) - introduce extra 'expected' which inspects REDIRECTED
resolveExpr( obj.on, art.kind === 'mixin' ? 'mixin-on' : 'on', art );
}
else {
const elements = Object.create( art._parent.elements );
elements[art.name.id] = obj;
resolveExpr( obj.on, art.kind === 'mixin' ? 'mixin-on' : 'on', art, elements );
}
}

@@ -701,3 +917,3 @@ else if (art.kind === 'mixin') {

// redirected or explicit type cds.Association, ...
if (obj.$inferred === 'REDIRECTED' || obj.type?._artifact?.internal)
if (obj.type?._artifact?.internal)
addImplicitForeignKeys( art, obj, target );

@@ -713,2 +929,80 @@ }

function checkRedirectedUserTarget( art ) {
const issue = { target: art.target._artifact };
const tgtPath = art.target.path;
const modelTarget = tgtPath[tgtPath.length - 1]._artifact; // Array#at comes with node-16.6
// Check ON condition: no renamed target element
traverseExpr( art.on, 'on-check', art, (expr) => {
const { path } = expr;
if (!expr?._artifact || path?.length < 2 || issue['#'])
return; // no path or with error or already found issue
const head = (path[0]._navigation?.kind === '$self') ? 1 : 0;
if (path[head]._artifact === art)
checkAutoRedirectedPathItem( path[head + 1], modelTarget, issue );
} );
// Check explicit+implicit foreign keys: no renamed target element
const implicit = art.foreignKeys?.[$inferred];
forEachGeneric( art, 'foreignKeys', (fkey) => {
const { targetElement } = fkey;
if (targetElement._artifact && !issue['#'])
checkAutoRedirectedPathItem( targetElement.path[0], modelTarget, issue, implicit );
} );
// Check implicit foreign keys: same keys in same order
if (implicit && !issue['#']) {
const serviceKeys = keyElementNames( issue.target.elements );
const modelKeys = keyElementNames( modelTarget.elements );
if (modelKeys.length !== serviceKeys.length) {
issue.id = modelKeys.find( id => !serviceKeys.includes( id ));
issue['#'] = 'missing';
}
else if (!modelKeys.every( (id, index) => id === serviceKeys[index] )) {
issue['#'] = 'order';
}
}
if (issue['#']) {
/* eslint-disable max-len */
message( 'type-expecting-service-target', [ art.target.location, art ], issue, {
std: 'Expecting service entity $(TARGET)',
ref: 'Expecting service entity $(TARGET); its element $(ID) referred to at line $(LINE), column $(COL) is not from an element with the same name in the provided model target',
key: 'Expecting service entity $(TARGET); its key element $(ID) is not from a key element with the same name in the provided model target',
missing: 'Expecting service entity $(TARGET); it does not have the key element $(ID) of the provided model target',
order: 'Expecting service entity $(TARGET); its key elements are in a different order than those of the provided model target',
} );
/* eslint-enable max-len */
}
}
function keyElementNames( elements ) {
const names = [];
for (const name in elements) {
if (elements[name].key?.val)
names.push( name );
}
return names;
}
function checkAutoRedirectedPathItem( pathItem, modelTarget, issue, isKey = false ) {
if (!pathItem) // $self.assoc
return;
let targetElem = pathItem._artifact;
while (targetElem && targetElem._main !== modelTarget)
targetElem = directOrigin( targetElem );
if (targetElem?.name.id === pathItem.id && (!isKey || targetElem.key?.val))
return;
issue.id = pathItem.id;
issue.line = pathItem.location.line;
issue.col = pathItem.location.col;
issue['#'] = (isKey ? 'key' : 'ref');
}
function directOrigin( elem ) {
if (!elem._main.query || !elem._origin)
return elem._origin; // included element
const { path } = elem.value;
const kind = path[0]._navigation?.kind;
// TODO: expand/inline (also Alias.*)
return [ null, '$navElement', '$tableAlias' ][path.length] === (kind || true) && elem._origin;
}
function addImplicitForeignKeys( art, obj, target ) {

@@ -718,3 +1012,3 @@ obj.foreignKeys = Object.create(null);

if (elem.key && elem.key.val) {
const { location } = art.target;
const { location } = obj.target;
const key = {

@@ -729,2 +1023,3 @@ name: { location, id: elem.name.id, $inferred: 'keys' }, // more by setMemberParent()

dictAdd( obj.foreignKeys, name, key );
// the following should be done automatically, since we run resolveRefs after that
setArtifactLink( key.targetElement, elem );

@@ -936,21 +1231,11 @@ setArtifactLink( key.targetElement.path[0], elem );

function checkTypeArguments( artWithType, typeArt ) {
// Note: `_effectiveType` may point to `artWithType` itself, if the type is structured.
// Also: For enums, it points to the enum type, which is why this trick is needed.
// TODO(#8942): May not be necessary if effectiveType() is adapted. Furthermore, the enum
// trick may be removed if effectiveType() does not stop at enums.
// TODO: this is wrong - we must check typeArt.enum, not its effectiveType
// Note: `_effectiveType` point to `artWithType` itself, if it is an enum type,
// descend to the origin in this case.
// TODO: this function is not complete(!): parallel `elements` and `length`, … - rework function
const cyclic = new Set();
// TODO: check relationship with resolveTypeArgumentsUnchecked()
let effectiveTypeArt = effectiveType( typeArt );
while (effectiveTypeArt && effectiveTypeArt.enum && !cyclic.has(effectiveTypeArt)) {
cyclic.add(effectiveTypeArt);
const underlyingEnumType = getOrigin(effectiveTypeArt);
if (underlyingEnumType)
effectiveTypeArt = effectiveType(underlyingEnumType);
else
break;
}
while (effectiveTypeArt?.enum)
effectiveTypeArt = effectiveType( getOrigin( effectiveTypeArt ) );
if (!effectiveTypeArt)
return; // e.g. illegal definition references
return; // e.g. illegal definition references, cycles, ...

@@ -996,15 +1281,9 @@ const params = effectiveTypeArt.parameters &&

function resolveExpr( expr, expected, user, extDict, expandOrInline ) {
// TODO: when we have rewritten the resolvePath functions,
// define a traverseExpr() in ./utils.js
// TODO: extra "expected" 'expand'/'inline' instead o param `expandOrInline`
if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
return;
if (Array.isArray(expr)) {
expr.forEach( e => resolveExpr( e, expected, user, extDict ) );
return;
}
function resolveExpr( expr, exprCtx, user ) {
traverseExpr( expr, exprCtx, user, (e, c, u) => resolveExprItem( e, c, u ) );
}
function resolveExprItem( expr, expected, user ) {
if (expr.type) // e.g. cast( a as Integer )
resolveTypeExpr( expr, user );
resolveTypeExpr( expr, user._user || user );

@@ -1015,18 +1294,14 @@ if (expr.path) {

'An EXISTS predicate is not expected here' );
// We complain about the EXISTS before, as EXISTS subquery is also not
// supported (avoid that word if you do not want to get tickets when it
// will be supported), TODO: location of EXISTS
// We complain about the EXISTS before, as EXISTS subquery is also not supported
// TODO: location of EXISTS, TODO: really do this in define.js
expr.$expected = 'approved-exists'; // only complain once
}
if (expected instanceof Function) {
expected( expr, user, extDict );
return;
}
resolvePath( expr, expected, user, extDict );
resolvePath( expr, expected, user );
const last = !expandOrInline && expr.path[expr.path.length - 1];
const last = expr.$expected !== 'approved-exists' && expr.path[expr.path.length - 1];
for (const step of expr.path) {
if (step && (step.args || step.where || step.cardinality) &&
step._artifact && !Array.isArray( step._artifact ) )
resolveParamsAndWhere( step, expected, user, extDict, step === last );
resolveParamsAndWhere( step, expected, user, step === last );
// TODO: delete 4th arg
}

@@ -1044,11 +1319,5 @@ }

}
else if (expr.op && expr.args) {
const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
args.forEach( e => e && resolveExpr( e, e.$expected || expected, user, extDict ) );
}
if (expr.suffix)
expr.suffix.forEach( s => s && resolveExpr( s, expected, user, extDict ) );
}
function resolveParamsAndWhere( step, expected, user, extDict, isLast ) {
function resolveParamsAndWhere( step, expected, user, isLast ) {
const alias = (step._navigation?.kind === '$tableAlias') ? step._navigation : null;

@@ -1059,11 +1328,15 @@ const type = alias || effectiveType( step._artifact );

return;
const entity = (art.kind === 'entity') &&
(!isLast || [ 'from', 'exists', 'approved-exists' ].includes( expected )) && art;
const entity = art.kind === 'entity' &&
(!isLast || user.expand || user.inline || expWithFilter.includes( expected )) &&
art;
if (step.args)
resolveParams( step.args, art, entity, expected, user, extDict, step.location );
resolveParams( step.args, art, entity, expected, user, step.location );
if (entity) {
if (step.where)
resolveExpr( step.where, 'filter', user, environment( type ) );
if (step.where) {
setLink( step, '_user', user._user || user );
const inCalc = (expected === 'calc' || expected === 'calc-filter');
resolveExpr( step.where, (inCalc ? 'calc-filter' : 'filter'), step );
}
}
else if (step.where?.location || step.cardinality ) {
else if (step.where || step.cardinality ) {
const location = combinedLocation( step.where, step.cardinality );

@@ -1084,3 +1357,3 @@ let variant = alias ? 'tableAlias' : 'std';

function resolveParams( dict, art, entity, expected, user, extDict, stepLocation ) {
function resolveParams( dict, art, entity, expected, user, stepLocation ) {
if (!entity || !entity.params) {

@@ -1108,3 +1381,3 @@ let first = dict[Object.keys(dict)[0]];

for (const a of dict)
resolveExpr( a, exp, user, extDict );
resolveExpr( a, exp, user );
return;

@@ -1122,3 +1395,4 @@ }

}
resolveExpr( a, exp, user, extDict );
// TODO: Also for other parameters?
resolveExpr( a, (expected === 'from') ? 'param-only' : exp, user );
}

@@ -1125,0 +1399,0 @@ }

@@ -6,4 +6,4 @@ // Compiler functions and utilities shared across all phases

const { CompilerAssertion } = require('../base/error');
const { searchName } = require('../base/messages');
const { isDeprecatedEnabled } = require('../base/model');

@@ -15,8 +15,6 @@ const {

pathName,
userQuery,
definedViaCdl,
} = require('./utils');
function artifactsEnv( art ) {
return art._subArtifacts || Object.create(null);
}
/**

@@ -26,5 +24,5 @@ * Main export function of this file. Attach "resolve" functions shared for phase

*
* Before calling these functions, make sure that the following function
* in model.$volatileFunctions is set:
* - `environment`: a function which returns the search environment defined by
* Before calling `resolvePath`, make sure that the following function
* in model.$function is set:
* - `navigationEnv`: a function which returns the search environment defined by
* its argument, e.g. a function which returns the dictionary of subartifacts.

@@ -40,53 +38,214 @@ *

} = model.$messageFunctions;
const Functions = model.$functions;
const referenceSemantics = {
// global: ------------------------------------------------------------------
using: { // only used to produce error message
isMainRef: 'all',
lexical: null,
dynamic: modelDefinitions,
notFound: undefinedDefinition,
},
// only used for the main annotate/extend statements, not inner ones:
annotate: {
isMainRef: 'all',
lexical: userBlock,
dynamic: modelDefinitions,
notFound: undefinedForAnnotate,
},
extend: {
isMainRef: 'no-generated',
lexical: userBlock,
dynamic: modelDefinitions,
notFound: undefinedDefinition,
},
_extensions: {
isMainRef: 'all',
lexical: userBlock,
dynamic: modelDefinitions,
notFound: false, // without message
},
include: {
isMainRef: 'no-generated',
lexical: userBlock,
dynamic: modelBuiltinsOrDefinitions,
notFound: undefinedDefinition,
},
viewInclude: 'include', // TODO: do differently
target: {
isMainRef: 'no-autoexposed',
lexical: userBlock,
dynamic: modelBuiltinsOrDefinitions,
notFound: undefinedDefinition,
// special `scope`s for redirections:
global: () => ({ isMainRef: 'all', dynamic: modelDefinitions }),
},
targetAspect: { // TODO: do differently
isMainRef: 'no-autoexposed',
lexical: userBlock,
dynamic: modelBuiltinsOrDefinitions,
notFound: undefinedDefinition,
},
from: {
isMainRef: 'no-autoexposed',
lexical: userBlock,
dynamic: modelBuiltinsOrDefinitions,
notFound: undefinedDefinition,
navigation: environment,
},
type: {
isMainRef: 'no-autoexposed',
lexical: userBlock,
dynamic: modelBuiltinsOrDefinitions,
notFound: undefinedDefinition,
navigation: targetAspectOnly,
// special `scope`s for CDL parser - TYPE OF (TODO generated?), cds.Association:
typeOf: typeOfSemantics,
global: () => ({ isMainRef: 'no-autoexposed', dynamic: modelDefinitions }),
},
actionParamType: 'type', // TODO: do differently
eventType: 'type', // TODO: do differently
// element references without lexical scope (except $self/$projection): -----
targetElement: {
lexical: null,
dollar: false,
dynamic: targetElements,
notFound: undefinedTargetElement,
param: paramSemantics,
},
filter: {
lexical: justDollarSelf,
dollar: true,
dynamic: targetElements,
notFound: undefinedTargetElement,
param: paramSemantics,
},
'calc-filter': {
lexical: justDollarSelf,
dollar: true,
dynamic: targetElements,
notFound: undefinedTargetElement,
param: paramUnsupported,
},
default: {
lexical: null,
dollar: true,
dynamic: () => Object.create( null ),
notFound: undefinedVariable,
param: paramUnsupported,
},
// general element references -----------------------------------------------
expr: { // TODO: this is too general -> column
lexical: tableAliasesIfNotExtendAndSelf,
dollar: true,
dynamic: combinedSourcesOrParentElements,
notFound: undefinedSourceElement,
param: paramSemantics,
nestedColumn: () => ({ // in expand and inline
lexical: justDollarSelf,
dollar: true,
dynamic: nestedElements,
notFound: undefinedNestedElement,
param: paramSemantics,
}),
},
'param-only': {
lexical: null,
dollar: true,
dynamic: () => Object.create( null ),
notFound: undefinedVariable,
param: paramSemantics,
},
calc: {
lexical: justDollarSelf,
dollar: true,
dynamic: parentElements,
notFound: undefinedParentElement,
param: paramUnsupported,
},
joinOn: {
lexical: tableAliasesAndSelf,
dollar: true,
dynamic: combinedSourcesOrParentElements, // TODO: source alone...
notFound: undefinedSourceElement,
param: paramSemantics,
},
on: { // unmanaged assoc: outside query, redirected or new assoc in column
lexical: justDollarSelf,
allowBareSelf: true,
dollar: true,
dynamic: parentElements,
notFound: undefinedParentElement,
param: paramUnsupported,
nestedColumn: () => ({ // in expand and inline
lexical: justDollarSelf,
dollar: true,
dynamic: parentElements,
notFound: undefinedParentElement,
}),
},
'mixin-on': {
lexical: tableAliasesAndSelf,
allowBareSelf: true,
dollar: true,
dynamic: combinedSourcesOrParentElements,
notFound: undefinedSourceElement,
param: paramSemantics, // TODO: check that assocs containing param in ON is not published
},
'order-by-ref': {
lexical: tableAliasesAndSelf,
dollar: true,
dynamic: parentElements,
notFound: undefinedOrderByElement,
param: paramSemantics,
},
'order-by-expr': {
lexical: tableAliasesAndSelf,
dollar: true,
dynamic: combinedSourcesOrParentElements,
notFound: undefinedSourceElement,
param: paramSemantics,
},
'order-by-set-ref': {
lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
dollar: true,
dynamic: queryElements,
notFound: undefinedParentElement,
param: paramSemantics,
},
'order-by-set-expr': {
lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
dollar: true,
dynamic: () => Object.create( null ),
notFound: undefinedVariable,
param: paramSemantics,
},
};
// TODO: combine envFn and assoc ?
const specExpected = {
global: { // for using declaration
envFn: artifactsEnv,
artItemsCount: Number.MAX_SAFE_INTEGER,
useDefinitions: true,
global: 'definitions',
},
// TODO: re-check --------------------------------------------------------
annotation: { useDefinitions: true, noMessage: true, global: 'vocabularies' },
using: {}, // for using declaration
// TODO: artifact references ---------------------------------------------
extend: {
useDefinitions: true,
envFn: artifactsEnv,
artItemsCount: Number.MAX_SAFE_INTEGER,
},
extend: {},
// ref in top-level EXTEND
annotate: {
useDefinitions: true,
envFn: artifactsEnv,
artItemsCount: Number.MAX_SAFE_INTEGER,
undefinedDef: 'anno-undefined-def',
undefinedArt: 'anno-undefined-art',
allowAutoexposed: true, // TODO: think about Info/Warning
noMessageForLocalized: true, // TODO: should we issue a Debug message for code completion?
},
annotate: {},
_extensions: {},
type: { // TODO: more detailed later (e.g. for enum base type?)
envFn: artifactsEnv,
check: checkTypeRef,
expectedMsgId: 'expected-type',
expectedMsgId: 'ref-expecting-type',
sloppyMsgId: 'ref-sloppy-type',
deprecateSmart: true,
},
actionParamType: {
envFn: artifactsEnv,
check: checkActionParamTypeRef,
expectedMsgId: 'expected-actionparam-type',
expectedMsgId: 'ref-expecting-action-param-type',
sloppyMsgId: 'ref-sloppy-actionparam-type',
deprecateSmart: true,
},
eventType: {
envFn: artifactsEnv,
check: checkEventTypeRef,
expectedMsgId: 'expected-event-type',
expectedMsgId: 'ref-expecting-event-type',
sloppyMsgId: 'ref-sloppy-event-type',
deprecateSmart: true,
},
include: {
check: checkIncludesRef,
expectedMsgId: 'expected-struct',
envFn: artifactsEnv,
expectedMsgId: 'ref-expecting-struct',
},

@@ -96,53 +255,47 @@ viewInclude: {

expectedMsgId: 'ref-expecting-bare-aspect',
envFn: artifactsEnv,
},
target: {
check: checkEntityRef,
expectedMsgId: 'expected-entity',
expectedMsgId: 'ref-expecting-entity',
noDep: true,
envFn: artifactsEnv,
},
compositionTarget: {
targetAspect: {
check: checkTargetRef,
expectedMsgId: 'expected-target',
expectedMsgId: 'ref-expecting-target',
sloppyMsgId: 'ref-sloppy-target',
noDep: 'only-entity',
envFn: artifactsEnv,
},
from: {
envFn: artifactsEnv,
check: checkSourceRef,
expectedMsgId: 'expected-source',
expectedMsgId: 'ref-expecting-source',
assoc: 'from',
argsSpec: 'expr',
deprecateSmart: true,
},
// element references ----------------------------------------------------
// if we want to disallow assoc nav for TYPE, do not do it here
typeOf: { next: '_$next', dollar: true }, // TODO: disallow in var
// TODO: dep for (explicit+implicit!) foreign keys
targetElement: { next: '__none_', assoc: false, dollar: false },
// TODO: also check that we do not follow associations in foreign key? no args, no filter
targetElement: { assoc: false },
filter: {
next: '_$next', lexical: 'main', dollar: 'none', escape: 'param',
escape: 'param',
},
'calc-filter': {
escape: 'param',
},
default: {
next: '_$next',
dollar: true,
check: checkConstRef,
expectedMsgId: 'expected-const',
expectedMsgId: 'ref-expecting-const',
},
expr: { // in: from-on,
next: '_$next', dollar: true, escape: 'param', assoc: 'nav',
expr: {
escape: 'param', assoc: 'nav',
},
exists: { // same as expr
next: '_$next', dollar: true, escape: 'param', assoc: 'nav',
'param-only': {
escape: 'param',
},
'approved-exists': { // same as expr
next: '_$next', dollar: true, escape: 'param', assoc: 'nav',
calc: {
assoc: 'nav',
},
joinOn: { // ON condition for JOIN: should be different to 'expr'!
escape: 'param', assoc: 'nav',
},
on: { // TODO: there will also be a 'from-on' (see 'expr')
noAliasOrMixin: true, // TODO: some headReject or similar
next: '_$next', // TODO: lexical: ... how to find the (next) lexical environment
dollar: true,
rootEnv: 'elements', // the final environment for the path root
noDep: true, // do not set dependency for circular-check

@@ -153,16 +306,15 @@ allowSelf: true,

escape: 'param', // TODO: extra check that assocs containing param in ON is not published
next: '_$next', // TODO: lexical: ... how to find the (next) lexical environment
dollar: true,
noDep: true, // do not set dependency for circular-check
allowSelf: true,
}, // TODO: special assoc for only on user
rewrite: {
// ---------marker for getPathRoot replaced-----------
'order-by-ref': {
next: '_$next',
dollar: true,
escape: 'param',
noDep: true,
allowSelf: true,
rewrite: true,
}, // TODO: assertion that there is no next/escape used
'order-by': {
assoc: 'nav',
dynamic: 'query',
deprecatedSourceRefs: true,
},
'order-by-expr': {
next: '_$next',

@@ -172,5 +324,4 @@ dollar: true,

assoc: 'nav',
deprecatedSourceRefs: true,
},
'order-by-union': {
'order-by-set-ref': {
next: '_$next',

@@ -180,4 +331,13 @@ dollar: true,

noDep: true,
noExt: true,
dynamic: 'query',
lexical: 'next',
},
'order-by-set-expr': {
next: '_$next',
dollar: true,
escape: 'param',
noDep: true,
dynamic: false,
lexical: 'next',
},
// expr TODO: better - on condition for assoc, other on

@@ -187,7 +347,6 @@ // expr TODO: write dependency, but care for $self

check: checkConstRef,
expectedMsgId: 'expected-const',
expectedMsgId: 'ref-expecting-const',
},
};
const VolatileFns = model.$volatileFunctions;
Object.assign( model.$functions, {

@@ -279,29 +438,21 @@ resolveUncheckedPath,

// Used for collecting artifact extension, and annotation assignments.
function resolveUncheckedPath( ref, expected, user ) {
if (!ref.path || ref.path.broken) // incomplete type AST
//
// Return '' if the ref is good, but points to an element.
function resolveUncheckedPath( ref, refCtx, user ) {
const { path } = ref;
if (!path || path.broken) // incomplete type AST
return undefined;
if (ref._artifact)
return ref._artifact.name.absolute;
const spec = specExpected[expected];
let art = (ref.scope === 'global' || spec.global)
? getPathRoot( ref.path, spec, user, {}, model[spec.global || 'definitions'] )
: getPathRoot( ref.path, spec, user, user._block, null, true );
if (art === false) // redefinitions
art = ref.path[0]._artifact[0]; // array stored in head's _artifact
else if (!art)
return (spec.useDefinitions) ? pathName( ref.path ) : null;
// art can be using proxy...
if (ref.path.length > 1)
return `${ art.name.absolute }.${ pathName( ref.path.slice(1) ) }`;
return art.name.absolute;
}
function userQuery( user ) {
// TODO: we need _query links set by the definer
while (user._main) {
if (user.kind === 'select' || user.kind === '$join')
return user;
user = user._parent;
}
return null;
const semantics = referenceSemantics[refCtx];
let art = getPathRoot( ref, semantics, user );
if (ref.scope && ref.scope !== 'global')
return ''; // TYPE OF, Main:elem
if (Array.isArray( art ))
art = art[0];
if (!art)
return (semantics.notFound) ? art : pathName( path );
if (path.length === 1)
return art.name.absolute; // TODO: name.id
return `${ art.name.absolute }.${ pathName( ref.path.slice(1) ) }`;
}

@@ -313,3 +464,5 @@

// `user` to the found artifact if `user` is provided.
function resolvePath( ref, expected, user, extDict, msgArt ) {
function resolvePath( ref, expected, user ) {
const origUser = user;
user = user._user || user;
if (ref == null) // no references -> nothing to do

@@ -319,3 +472,5 @@ return undefined;

return ref._artifact;
if (!ref.path || ref.path.broken || !ref.path.length) {
const { path } = ref;
if (!path || path.broken || !path.length) {
// incomplete type AST or empty env (already reported)

@@ -325,127 +480,20 @@ return setArtifactLink( ref, undefined );

const s = referenceSemantics[expected]; // TODO: temp indirection
const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
const r = getPathRoot( ref, semantics, origUser );
const root = r && acceptPathRoot( r, ref, semantics, user );
if (!root)
return setArtifactLink( ref, root );
let spec = specExpected[expected];
const { path } = ref;
const head = path[0];
// message(null,head.location,{art:user,expected, id: head.id},
// 'Info','User $(ART), $(EXPECTED) $(ID)')
let env = user._block; // artifact references: block
if (ref.scope === 'param') {
if (!spec.escape) {
error( 'ref-unexpected-scope', [ ref.location, user ], { '#': 'std' } );
return setArtifactLink( ref, null );
}
if (user.$syntax === 'calc')
error('ref-unexpected-scope', [ ref.location, user ], { '#': 'calc' } );
if (!spec.escape)
throw new CompilerAssertion( 'getPathRoot() should have returned falsy val' );
spec = specExpected[spec.escape];
// In queries and query entities, the first lexical search environment
// are the parameters, otherwise the block. It is currently ensured that
// _block in queries is the same as _block of the query entity:
const lexical = (user._main || user).$tableAliases; // queries (but also query entities)
env = lexical && lexical.$parameters || user._block;
extDict = null; // let getPathRoot() choose it
}
else if (spec.next === '__none_') {
env = {};
}
else if (spec.next) { // TODO: combine spec.next / spec.lexical to spec.lexical
// TODO: SIMPLIFY this function
const query = (spec.lexical === 'main') ? user._main : userQuery( user );
// in path filter, just $magic (and $parameters)
env = (spec.lexical === 'from') ? query._parent : query || user._main || user;
// queries: first table aliases, then $magic - value refs: first $self, then $magic
if (!extDict && !spec.noExt) {
// TODO: change to name restriction for $joins, not own environments
extDict = query && spec.rootEnv !== 'elements' &&
// first step: only use _combined of real query - TODO:
// reject if not visible, but not allow more (!)
(query._combined || query._parent._combined) ||
VolatileFns.environment( user._main ? user._parent : user );
}
}
// 'global' for CSN later in value paths, CDL for Association/Composition:
let art = (ref.scope === 'global' || spec.global)
? getPathRoot( path, spec, user, {}, model[spec.global || 'definitions'] )
: getPathRoot( path, spec, user, env, extDict, msgArt || 0 );
if (!art) {
return setArtifactLink( ref, art );
}
else if (!spec.envFn && user._pathHead) {
if (art.kind === '$self') {
const headEnv = VolatileFns.environment( user._pathHead ) &&
user._pathHead._origin &&
VolatileFns.environment( user._pathHead._origin );
rejectBareSelf( spec, path, user, headEnv );
}
}
else if (art.kind === 'using') {
const def = model.definitions[art.name.absolute];
if (!def) {
// It could be that the artifact was removed and that the using-proxy needs to be reported.
// The check for $inferred is required to avoid consequential errors for cases such as:
// using unknown.abc;
// entity P as projection on abc; // <-- no consequential error here
if (art.$inferred === 'path-prefix') {
// head._artifact referred to the `using`. Remove the reference,
// so that getPathItem() below emits an error.
setArtifactLink( head, false );
setArtifactLink( ref, false );
}
else {
return setArtifactLink( ref, false );
}
}
else if (def.$duplicates) { // redefined art referenced by using proxy
return setArtifactLink( ref, false );
}
else {
setArtifactLink( head, def ); // we do not want to see the using
}
}
else if (art.kind === 'mixin') {
if (spec.noAliasOrMixin) {
// TODO: good enough for now - change later to not search for table aliases at all
signalNotFound( 'ref-rejected-on', [ head.location, user ], extDict && [ extDict ],
{ '#': 'mixin', id: head.id } );
// also set link on head?
return setArtifactLink( ref, false );
}
// console.log(message( null, art.location, art, {}, 'Info','MIX').toString())
setLink( head, '_navigation', art );
}
else if (art.kind === '$navElement') {
setLink( head, '_navigation', art );
setArtifactLink( head, art._origin );
// TODO: set art?
}
else if (art.kind === '$tableAlias' || art.kind === '$self') {
if (art.kind === '$self') {
rejectBareSelf( spec, path, user, extDict );
}
else if (spec.noAliasOrMixin) {
// TODO: good enough for now - change later to not search for table aliases at all
signalNotFound( 'ref-rejected-on', [ head.location, user ], extDict && [ extDict ],
{ '#': 'alias', id: head.id } );
// also set link on head?
return setArtifactLink( ref, false );
}
setLink( head, '_navigation', art );
setArtifactLink( head, art._origin ); // query source or leading query in FROM
// require('../model/revealInternalProperties').log(model, 'foo.bar.S.V1a')
if (!art._origin)
return setArtifactLink( ref, art._origin );
// if just table alias (with expand), mark `user` with `$noOrigin` to indicate
// that the corresponding entity should not be put as $origin into the CSN
if (path.length === 1 && user && art.kind === '$tableAlias')
user.$noOrigin = true;
}
// how many path items are for artifacts (rest: elements)
const artItemsCount = (typeof ref.scope === 'number')
? ref.scope || Number.MAX_SAFE_INTEGER
: spec.artItemsCount || 1;
// console.log(expected, ref.path.map(a=>a.id),artItemsCount)
art = getPathItem( path, spec, user, artItemsCount, !spec.envFn && user._pathHead && art);
let art = getPathItem( ref, semantics, spec, user );
if (!art)

@@ -461,3 +509,3 @@ return setArtifactLink( ref, art );

}
if (spec.check) {
if (spec.check && !ref.$expected) { // do not check with exists path (is already error)
const fail = spec.check( art, path );

@@ -484,3 +532,3 @@ if (fail === true) {

dependsOn( user, art._main, location );
VolatileFns.environment( art, location, user );
environment( art, location, user );
}

@@ -494,6 +542,2 @@ else if (art.kind !== 'select') { // no real dependency to bare $self

}
// Warning for CDL TYPE OF references without ':' or shifted ':'
if (spec.deprecateSmart && typeof ref.scope === 'number' &&
!(env.$frontend && env.$frontend !== 'cdl'))
deprecateSmart( ref, art, user );
// TODO: follow FROM here, see csnRef - fromRef

@@ -503,33 +547,2 @@ return setArtifactLink( ref, art );

function rejectBareSelf( spec, path, user, extDict ) {
if (path.length === 1 && !spec.allowSelf && !user.expand && !user.inline) {
const head = path[0];
// TODO: extra text variant for JOIN-ON (if we have an extra `expected`)
signalNotFound( 'ref-unexpected-self', [ head.location, user ], extDict && [ extDict ],
{ id: head.id } );
}
}
// Issue errors for "smart" element-in-artifact references
// without a colon; and errors for misplaced colons in references.
// This function likely disappears again in cds-compiler v2.x.
function deprecateSmart( ref, art, user ) {
const { path } = ref;
const scope = path.findIndex( i => i._artifact._main );
if (ref.scope) { // provided a ':' in the ref path
if (scope === ref.scope) // correctly between main artifact and element
return;
const item = path[ref.scope];
error( 'ref-unexpected-colon', [ item.location, user ], { id: item.id },
'Replace the colon before $(ID) by a dot' );
ref.scope = 0; // correct (otherwise CSN refs are wrong)
}
if (scope >= 0) { // we have a element-in-artifact reference
const item = path[scope];
error( 'ref-missing-colon', [ item.location, user ], { id: item.id },
'Replace the dot before $(ID) by a colon' );
ref.scope = scope; // no need to recalculate in to-csn.js
}
}
/**

@@ -547,2 +560,4 @@ * Resolve the type arguments of `artifact` according to the type `typeArtifact`.

*
* TODO: move to define.js (and probably rename), rewrite (consider syntax-unexpected-argument)
*
* @param {object} artifact

@@ -554,3 +569,3 @@ * @param {object} typeArtifact

let args = artifact.$typeArgs || [];
const parameters = typeArtifact.parameters || [];
const parameters = typeArtifact?.parameters || [];

@@ -568,3 +583,3 @@ if (parameters.length > 0) {

}
else if (args.length > 0 && !typeArtifact.builtin) {
else if (args.length > 0 && !typeArtifact?.builtin) {
// One or two arguments are interpreted as either length or precision/scale.

@@ -589,7 +604,10 @@ // For builtins, we know what arguments are expected, and we do not need this mapping.

if (args.length > 0) {
const loc = [ args[args.length - 1].location, user ];
if (typeArtifact.builtin)
const loc = [ args[0].location, user ];
if (typeArtifact?.builtin) {
message( 'type-ignoring-argument', loc, { art: typeArtifact } );
else
error( 'type-unexpected-argument', loc, { '#': 'std', art: typeArtifact });
}
else if (options.testMode) {
// Ensure: parser reports error and sets $typeArgs to undefined if more than 2 parameter
throw new CompilerAssertion( 'More than 2 type arguments set by parser' );
}
}

@@ -599,175 +617,61 @@ artifact.$typeArgs = undefined;

/**
* Return artifact or element referred by name `head`. The first environment
* we search in is `env`. If `unchecked` is equal to `true`, do not report an error
* if the artifact does not exist. Return a "fresh" artifact for
* non-existing external using references if `unchecked` is truthy.
*/
function getPathRoot( path, spec, user, env, extDict, msgArt ) {
function getPathRoot( { path, scope, location }, semantics, user ) {
// TODO: use string value of isMainRef?
const head = path[0];
if (!head || !head.id || !env)
if (!head || !head.id)
return undefined; // parse error
if (!spec.envFn && user._pathHead && head.id.charAt(0) !== '$') {
if (spec.rootEnv === 'elements') { // ON condition in expand/inline
let root = user._pathHead;
while (root.kind === '$inline')
root = root._parent;
return root;
}
VolatileFns.environment( user._pathHead ); // make sure _origin is set
return user._pathHead._origin;
// const { _origin } = user._pathHead;
// return (_origin && _origin.kind === '$tableAlias') ? _origin._origin : _origin;
}
// if (head.id === 'k') {console.log(Object.keys(user));
// throw new CompilerAssertion(JSON.stringify(user.name))}
// if head._artifact is set or is null then it was already computed once
if (head._artifact !== undefined)
return Array.isArray(head._artifact) ? false : head._artifact;
// console.log(pathName(path), !spec.next && !extDict &&
// (spec.useDefinitions || env.$frontend === 'json' || env))
if (!spec.next && !extDict) {
// CSN artifact paths are always fully qualified so we use
// model.definitions for the JSON frontend.
extDict = (spec.useDefinitions || env.$frontend && env.$frontend !== 'cdl')
? model.definitions
: model.$builtins;
}
const nodollar = !spec.dollar && spec.next;
const nextProp = spec.next || '_block';
for (let art = env; art; art = art[nextProp]) {
if (nodollar && !art._main) // $self stored in main.$tableAliases
break; // TODO: probably remove _$next link
const e = art.artifacts || art.$tableAliases || Object.create(null);
const r = e[head.id];
if (r && r.$inferred !== '$internal') {
if (Array.isArray(r)) { // redefinitions
setArtifactLink( head, r );
return false;
}
// if (head.$delimited && r.kind !== '$tableAlias' && r.kind !== 'mixin')
// TODO: warning for delimited special - or directly in parser
if (r.kind === '$parameters') {
if (!head.$delimited && path.length > 1) {
message( 'ref-obsolete-parameters', [ head.location, user ],
{ code: `$parameters.${ path[1].id }`, newcode: `:${ path[1].id }` },
'Obsolete $(CODE) - replace by $(NEWCODE)' );
// TODO: replace it in to-csn correspondingly !!!
return setArtifactLink( head, r );
}
}
else if (r.kind === '$self') {
// TODO: handle $delimited differently
// TODO: $projection only if not delimited _and_ length > 1
return setArtifactLink( head, r );
}
else if (r.kind !== '$tableAlias' || path.length > 1 || user.expand || user.inline) {
// except "real" table aliases (not $self) with path len 1
// TODO: $projection only if not delimited _and_ length > 1
return setArtifactLink( head, r );
}
return head._artifact;
const ruser = user._user || user; // TODO: nicer name if we keep this
// Handle expand/inline, `type of`, :param, global (internally for CDL):
if (user._pathHead && !semantics.isMainRef) // in expand/inline
semantics = semantics.nestedColumn();
if (typeof scope === 'string') { // typeOf, param, global
semantics = semantics?.[scope] && semantics[scope]( ruser, path, location );
if (!semantics) {
if (semantics == null)
throw new CompilerAssertion( `Scope ${ scope } is not expected here` );
return setArtifactLink( head, null );
}
}
if (extDict && (!spec.dollar || head.id[0] !== '$')) {
const r = extDict[head.id];
if (Array.isArray(r)) {
if (r[0].kind === '$navElement' && r.every( e => !e._parent.$duplicates )) {
// only complain about ambiguous source elements if we do not have
// duplicate table aliases, only mention non-ambiguous source elems
const uniqueNames = r.filter( e => !e.$duplicates);
if (uniqueNames.length) {
const names = uniqueNames.filter( e => e._parent.$inferred !== '$internal' )
.map( e => `${ e.name.alias }.${ e.name.element }` );
let variant = names.length === uniqueNames.length ? 'std' : 'few';
if (names.length === 0)
variant = 'none';
error( 'ref-ambiguous', [ head.location, user ], { '#': variant, id: head.id, names });
}
}
setArtifactLink( head, r );
return false;
}
else if (r) {
return setArtifactLink( head, r );
}
else if (spec.deprecatedSourceRefs && env._combined &&
isDeprecatedEnabled( options, 'autoCorrectOrderBySourceRefs' )) {
// User has provided a source element without table alias where a query
// element is expected. Possible on many DBs (and compiler v1), in CAP
// only with table alias. Auto-correct it if no duplicate.
// TODO: we could use that info also in messages when the deprecated flag is not set
const s = env._combined[head.id];
if (s && !Array.isArray(s)) {
path.$prefix = s.name.alias; // pushing it to path directly could be problematic
warning( null, [ head.location, user ],
{ id: head.id, newcode: `${ s.name.alias }.${ head.id }` },
'Replace source element reference $(ID) by $(NEWCODE); auto-corrected' );
return setArtifactLink( head, s );
}
}
}
if (spec.noMessage || msgArt === true && extDict === model.definitions)
return null;
const valid = [];
const valid = [];
for (let art = env; art; art = art[nextProp]) {
const e = art.artifacts || art.$tableAliases || Object.create(null);
valid.push( e );
}
if (extDict) {
const e = Object.create(null);
// the names of the external dictionary are valid, too, except duplicate
// navigation elements (for which you should use a table alias)
if (extDict !== model.definitions) {
for (const name in extDict) {
if (!spec.dollar || name[0] !== '$')
e[name] = extDict[name];
}
// Search in lexical environments, including $self/$projection:
const { isMainRef } = semantics;
const lexical = semantics.lexical?.( ruser ); // TODO: _pathHead?
if (lexical) {
const [ nextProp, dictProp ] = (isMainRef)
? [ '_block', 'artifacts' ]
: [ '_$next', '$tableAliases' ];
// let notApplicable = ...; // for table aliases in JOIN-ON and UNION order-by
for (let env = lexical; env; env = env[nextProp]) {
const dict = env[dictProp] || Object.create(null);
const r = dict[head.id];
if (acceptLexical( r, path, semantics, user ))
return setArtifactLink( head, r );
valid.push( dict );
}
else {
for (const name in extDict) {
if (!name.includes('.') && (!spec.dollar || name[0] !== '$'))
e[name] = extDict[name];
}
}
valid.push( e );
}
if (spec.next) { // value ref
// TODO: if not in query, specify where we search for elements and delete env.$msg
// TODO: also something special if it starts with '$'
if (msgArt) {
// TODO: we might mention both the "direct" and the "effective" type and
// always just mentioned one identifier as not found
signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
{ art: searchName( msgArt, head.id, 'element' ) } );
}
else if (head.id[0] === '$') {
const tableAlias = extDict[head.id]?._parent;
const alias = tableAlias?.kind === '$tableAlias' ? tableAlias.name?.alias : null;
signalNotFound( 'ref-undefined-var', [ head.location, user ], valid, {
'#': alias ? 'alias' : 'std',
alias,
id: head.id,
} );
}
else {
const isVirtual = (user.name?.id === head.id && user.virtual?.val);
const code = isVirtual ? 'virtual null as ‹name›' : '';
signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
{ art: head.id, '#': isVirtual ? 'virtual' : 'std', code } );
}
}
else if (env.$frontend && env.$frontend !== 'cdl' || spec.global) {
// IDE can inspect <model>.definitions - provide null for valid
if (!spec.noMessageForLocalized || !head.id.startsWith( 'localized.' )) {
signalNotFound( spec.undefinedDef || 'ref-undefined-def', [ head.location, user ],
valid, { art: head.id } );
}
}
else if (!spec.noMessageForLocalized || head.id !== 'localized') {
signalNotFound( spec.undefinedArt || 'ref-undefined-art', [ head.location, user ],
valid, { name: head.id } );
}
return setArtifactLink( head, null );
// Search in $special (ex $self/$projection) and dynamic environment:
const dynamicDict = semantics.dynamic( ruser, user._user && user._artifact );
if (!dynamicDict) // avoid consequential errors
return setArtifactLink( head, null );
const isVar = (semantics.dollar && head.id.charAt( 0 ) === '$');
const dict = (isVar) ? model.$magicVariables.artifacts : dynamicDict;
const r = dict[head.id];
if (r)
return setArtifactLink( head, r );
if (!semantics.dollar)
valid.push( dynamicDict );
else
valid.push( model.$magicVariables.artifacts, removeDollarNames( dynamicDict ) );
if (semantics.notFound === false)
return setArtifactLink( head, null );
// TODO: streamline function arguments (probably: user, path, semantics )
const undef = semantics.notFound( ruser, head, valid, dynamicDict,
!isMainRef && user._user && user._artifact, path );
return setArtifactLink( head, undef || null );
}

@@ -782,26 +686,31 @@

// (no _pathHead consultation for key prop and renaming support)
function getPathItem( path, spec, user, artItemsCount, headArt ) {
function getPathItem( ref, semantics, spec, user ) {
// let art = (headArt && headArt.kind === '$tableAlias') ? headArt._origin : headArt;
let art = headArt;
const { path } = ref;
let artItemsCount = 0;
const { isMainRef } = semantics;
if (isMainRef) {
artItemsCount = (typeof ref.scope === 'number' && ref.scope) ||
(ref.scope ? 1 : path.length);
}
let art = null;
let nav = spec.assoc !== '$keys' && null; // false for '$keys'
const last = path[path.length - 1];
// TODO: change elementsEnv via semantics for static versus dynamic assoc navigation
const elementsEnv = semantics.navigation || environment;
for (const item of path) {
--artItemsCount;
if (!item || !item.id) // incomplete AST due to parse error
if (!item?.id) // incomplete AST due to parse error
return undefined;
if (item._artifact) { // should be there on first path element (except with expand)
if (item._artifact) { // should be there on first path element
art = item._artifact;
if (Array.isArray(art))
return false;
if (art.$requireElementAccess && path.length === 1)
// Path with only one item, but we expect an element, e.g. `$at.from`.
signalMissingElementAccess(art, [ item.location, user ]);
continue;
}
const fn = (spec.envFn && artItemsCount >= 0) ? spec.envFn : VolatileFns.environment;
const env = fn( art, item.location, user, spec.assoc );
const sub = setArtifactLink( item, env?.[item.id] );
const prev = art;
const envFn = (artItemsCount >= 0) ? artifactsEnv : elementsEnv;
const env = envFn( art, item.location, user, spec.assoc );
art = setArtifactLink( item, env?.[item.id] );
if (!sub) {
if (!art) {
// element was not found in environment

@@ -811,52 +720,68 @@

// illegal-cycle error instead of reporting via `errorNotFound`.
if (art.$uncheckedElements) { // magic variable / replacement variable
if (prev.$uncheckedElements) { // magic variable / replacement variable
signalNotFound( 'ref-unknown-var', [ item.location, user ], [ env ],
{ id: pathName( path ) } );
}
else if (isMainRef && artItemsCount >= 0) { // artifact ref
if (semantics.notFound === false)
return null;
// TODO: streamline funtion arguments (probably: user, path, semantics, prev )
semantics.notFound( user, item, [ env ], null, prev, path );
}
else {
errorNotFound( item, env );
errorNotFound( item, env, prev, spec, user );
}
return null;
}
else if (Array.isArray(sub)) { // redefinitions
return false;
}
if (nav) { // we have already "pseudo-followed" a managed association
// We currently rely on the check that targetElement references do
// not (pseudo-) follow associations, otherwise potential redirection
// there had to be considered, too. Also, fk refs to sub elements in
// combinations with redirections of the target which directly access
// the potentially renamed sub elements would be really complex.
// With our restriction, no renaming must be considered for item.id.
setTargetReferenceKey( item.id, item );
}
// Now set an _navigation link for managed assocs in ON condition etc
else if (art && art.target && nav != null) {
// Find the original ref for sub and the original foreign key
// definition. This way, we do not need the foreign keys with
// rewritten target element path, which might not be available at
// this point (rewriteKeys in Resolver Phase 5). If we want to
// follow associations in foreign key definitions, rewriteKeys must
// be moved to the on-demand Resolver Phase 2.
let orig; // for the original target element
for (let o = sub; o; o = o.value && o.value._artifact) // TODO: or use _origin?
orig = o;
nav = (orig._effectiveType || orig).$keysNavigation;
setTargetReferenceKey( orig.name.id, item );
}
art = sub;
if (spec.envFn && !spec.allowAutoexposed && (!artItemsCount || item === last) &&
art && art.$inferred === 'autoexposed' && !user.$inferred) {
// TODO: what what about extra dependencies if we navigate along
// associations? See also extra args for environment()
nav = setSomeNavigationLinkForAssoc( nav, item, art, prev?.target, last, user );
// need to do that here, because we also need to disallow Service.AutoExposed:elem
if (isMainRef !== 'all' && artItemsCount === 0 &&
art.$inferred === 'autoexposed' && !user.$inferred) {
// Depending on the processing sequence, the following could be a
// simple 'ref-undefined-art'/'ref-undefined-def' - TODO: which we
// could "change" to this message at the end of compile():
message( 'ref-autoexposed', [ item.location, user ], { art },
// eslint-disable-next-line max-len
'An auto-exposed entity can\'t be referred to - expose entity $(ART) explicitly' );
error( 'ref-unexpected-autoexposed', [ item.location, user ], { art },
// eslint-disable-next-line max-len
'An auto-exposed entity can\'t be referred to - expose entity $(ART) explicitly' );
// return null;
}
}
// Final checks on artifact (could be done in an extra function)
if (art.$requireElementAccess) { // on some CDS variables
// Path with only one item, but we expect an element, e.g. `$at.from`.
signalMissingElementAccess(art, [ last.location, user ]);
}
return art;
}
function setTargetReferenceKey( id, item ) {
// To be analysed what it does exactly, see changes #3660, #3666:
function setSomeNavigationLinkForAssoc( nav, item, sub, target, last, user ) {
if (nav) { // we have already "pseudo-followed" a managed association
// We currently rely on the check that targetElement references do
// not (pseudo-) follow associations, otherwise potential redirection
// there had to be considered, too. Also, fk refs to sub elements in
// combinations with redirections of the target which directly access
// the potentially renamed sub elements would be really complex.
// With our restriction, no renaming must be considered for item.id.
setTargetReferenceKey( item.id );
}
// Now set an _navigation link for managed assocs in ON condition etc
else if (target && nav != null) {
// Find the original ref for sub and the original foreign key
// definition. This way, we do not need the foreign keys with
// rewritten target element path, which might not be available at
// this point (rewriteKeys in Resolver Phase 5). If we want to
// follow associations in foreign key definitions, rewriteKeys must
// be moved to the on-demand Resolver Phase 2.
let orig; // for the original target element
for (let o = sub; o; o = o.value && o.value._artifact) // TODO: or use _origin?
orig = o;
nav = (orig._effectiveType || orig).$keysNavigation;
setTargetReferenceKey( orig.name.id );
}
return nav;
function setTargetReferenceKey( id ) {
const node = nav && nav[id];

@@ -881,36 +806,369 @@ nav = null;

}
}
function errorNotFound( item, env ) {
if (!spec.next && artItemsCount >= 0) { // artifact ref
// TODO: better for FROM e.Assoc (even disallow for other refs)
const a = searchName( art, item.id, (spec.envFn || art._subArtifacts) && 'absolute' );
signalNotFound( spec.undefinedDef || 'ref-undefined-def', [ item.location, user ],
[ env ], { art: a } );
function errorNotFound( item, env, art, spec, user ) {
if (art.name && art.name.select && art.name.select > 1) {
// TODO: 'The current query has no element $(MEMBER)' with $self.MEMBER
// and 'The sub query for alias $(ALIAS) has no element $(MEMBER)'
// TODO: probably not extra messageId, but text variant
// TODO: views elements are proxies to query-0 elements, not the same
// TODO: better message text
signalNotFound( 'query-undefined-element', [ item.location, user ],
[ env ], { id: item.id } );
}
else if (art.kind === '$parameters') {
signalNotFound( 'ref-undefined-param', [ item.location, user ],
[ env ], { art: art._main, id: item.id } );
}
else {
const variant = art.kind === 'aspect' && !art.name && 'aspect';
signalNotFound( spec.undefinedDef || 'ref-undefined-element', [ item.location, user ],
[ env ], {
'#': variant,
art: (variant ? '' : searchName( art, item.id, 'element' )),
id: item.id,
} );
}
return null;
}
// Helper functions for resolve[Unchecked]Path, getPath{Root,Item}: -----------
function acceptLexical( art, path, semantics, user ) {
if (semantics.isMainRef || !art)
return !!art;
// Non-global lexical are table aliases, mixins and $self, $projection, $parameters,
// Do not accept a lonely table alias and `$projection`
// TODO: test table alias and mixin named `$projection`
if (path.length === 1 && !user.expand && !user.inline) { // accept lonely…
// allow mixins, and `up_` in anonymous target aspect:
if (art.kind !== '$self' || path[0].id !== '$self')
return art.kind === 'mixin' || art.kind === '$navElement';
return true;
}
// return !art.$internal && art;
return art.$inferred !== '$internal';
}
function acceptPathRoot( art, ref, semantics, user ) {
const { path } = ref;
const [ head ] = path;
if (Array.isArray( art ))
return getAmbiguousRefLink( art, head, user );
switch (art.kind) {
case 'using': {
const def = model.definitions[art.name.absolute];
if (!def)
return def;
if (def.$duplicates)
return false;
return setArtifactLink( head, def ); // we do not want to see the using
}
else if (art.name && art.name.select && art.name.select > 1) {
// TODO: 'The current query has no element $(MEMBER)' with $self.MEMBER
// and 'The sub query for alias $(ALIAS) has no element $(MEMBER)'
// TODO: probably not extra messageId, but text variant
// TODO: views elements are proxies to query-0 elements, not the same
// TODO: better message text
signalNotFound( 'query-undefined-element', [ item.location, user ],
[ env ], { id: item.id } );
case 'mixin': {
return setLink( head, '_navigation', art );
}
else if (art.kind === '$parameters') {
signalNotFound( 'ref-undefined-param', [ item.location, user ],
[ env ], { art: art._main, id: item.id } );
case '$navElement': {
if (head.id === user.$extended)
path.$prefix = user.$extended;
setLink( head, '_navigation', art );
return setArtifactLink( head, art._origin );
}
else {
const variant = art.kind === 'aspect' && !art.name && 'aspect';
signalNotFound( spec.undefinedDef || 'ref-undefined-element', [ item.location, user ],
[ env ], {
'#': variant,
art: (variant ? '' : searchName( art, item.id, 'element' )),
id: item.id,
} );
case '$self': // TODO: remove $projection from CC
case '$tableAlias': {
setLink( head, '_navigation', art );
setArtifactLink( head, art._origin ); // query source or leading query in FROM
if (!art._origin)
return art._origin;
// if just table alias (with expand), mark `user` with `$noOrigin` to indicate
// that the corresponding entity should not be put as $origin into the CSN.
// TODO: remove again, should be easy enough in to-csn without.
if (path.length === 1 && art.kind === '$tableAlias')
user.$noOrigin = true;
if (path.length === 1 && !semantics.allowBareSelf && !user.expand && !user.inline) {
// TODO: better ref-invalid-self
error( 'ref-unexpected-self', [ head.location, user ], { id: head.id } );
// TODO: reject bare $projection here (new message id, configurable)
// not really helpful to attach valid names here (would include $self)
}
return art;
}
return null;
case '$parameters': { // TODO: remove from CC
// TODO: if ref.scope='param' is handled, test that here, too ?
const { id } = path[1];
message( 'ref-obsolete-parameters', [ head.location, user ],
{ code: `$parameters.${ id }`, newcode: `:${ id }` },
'Obsolete $(CODE) - replace by $(NEWCODE)' );
// TODO: replace it in to-csn correspondingly, probably v5 or later in v4 ?
return art;
}
default:
return art;
}
}
function getAmbiguousRefLink( arr, head, user ) {
if (arr[0].kind !== '$navElement' || arr.some( e => e._parent.$duplicates ))
return false;
// only complain about ambiguous source elements if we do not have
// duplicate table aliases, only mention non-ambiguous source elems
const uniqueNames = arr.filter( e => !e.$duplicates);
if (uniqueNames.length) {
const names = uniqueNames.filter( e => e._parent.$inferred !== '$internal' )
.map( e => `${ e.name.alias }.${ e.name.element }` );
let variant = names.length === uniqueNames.length ? 'std' : 'few';
if (names.length === 0)
variant = 'none';
error( 'ref-ambiguous', [ head.location, user ], { '#': variant, id: head.id, names });
}
return false;
}
// Functions for the secondary reference semantics ----------------------------
function typeOfSemantics( user, [ head ] ) {
// `type of` is only allowed for (sub) elements of main artifacts
let struct = user;
while (struct.kind === 'element')
struct = struct._parent;
if (struct === user._main && struct.kind !== 'annotation')
return { dynamic: typeOfParentDict };
error( 'type-unexpected-typeof', [ head.location, user ],
{ keyword: 'type of', '#': struct.kind } );
return false;
}
function paramSemantics() {
return { dynamic: artifactParams, notFound: undefinedParam };
}
function paramUnsupported( user, _path, location ) {
error( 'ref-unexpected-scope', [ location, user ],
// why an extra text for calculated elements? or separate for all?
{ '#': (user.$syntax === 'calc' ? 'calc' : 'std') } );
return false;
}
// Functions for semantics.lexical: -------------------------------------------
function userBlock( user ) {
return definedViaCdl( user ) && user._block;
}
function justDollarSelf( user ) {
const query = userQuery( user );
if (!query)
return user._main || user;
// query.$tableAliases contains both aliases and $self/$projection
const aliases = query.$tableAliases;
const r = Object.create(null);
if (aliases.$self.kind === '$self')
r.$self = aliases.$self;
// TODO: disallow $projection for ON conditions all together
if (aliases.$projection?.kind === '$self')
r.$projection = aliases.$projection;
const { $parameters } = user._main.$tableAliases;
if ($parameters) // no need to test `kind`, just compiler-set “aliases”
r.$parameters = $parameters;
return { $tableAliases: r };
}
function tableAliasesAndSelf( user ) {
return userQuery( user ) || user._main || user;
}
function tableAliasesIfNotExtendAndSelf( user ) {
if (!user.$extended)
return tableAliasesAndSelf( user );
if (typeof user.$extended !== 'string') {
const aliases = userQuery( user ).$tableAliases;
user.$extended = Object.keys( aliases )[0];
}
return justDollarSelf( user );
}
// Functions called via semantics.dynamic: ------------------------------------
function modelDefinitions() {
return model.definitions;
}
function modelBuiltinsOrDefinitions( user ) {
return definedViaCdl( user ) ? model.$builtins : model.definitions;
}
function artifactParams( user ) {
const lexical = (user._main || user).$tableAliases;
// TODO: already report error here if no parameters?
return lexical?.$parameters?.elements || Object.create( null );
}
function typeOfParentDict( user ) {
// CDL produces the following XSN representation for `type of elem`:
// { path: [{ id: 'type of'}, { id: 'elem'}], scope: 'typeOf' }
return { 'type of': user._parent };
}
function targetElements( user, pathItemArtifact ) {
// const assoc = user._parent;
// const target = resolvePath( assoc.target, 'target', assoc );
return environment( pathItemArtifact || user._parent, null, null, null, true );
}
function combinedSourcesOrParentElements( user ) {
const query = userQuery( user );
if (!query)
return environment( user._main ? user._parent : user );
return query._combined; // TODO: do we need query._parent._combined ?
}
function parentElements( user ) {
return environment( user._main && user.kind !== 'select' ? user._parent : user );
}
function queryElements( user ) {
return environment( user );
}
function nestedElements( user ) {
Functions.navigationEnv( user._pathHead ); // set _origin
return environment( user._pathHead._origin );
}
function targetAspectOnly( prev ) {
let env = Functions.navigationEnv( prev, null, null, 'targetAspectOnly' );
while (env?.target && !env.targetAspect)
env = env._origin || env.type?._artifact;
if (env === 0)
return 0;
const target = env?.targetAspect;
if (target) {
if (target.elements)
return target.elements;
env = resolvePath( env.targetAspect, 'targetAspect', env );
}
return env?.elements || Object.create(null);
}
function artifactsEnv( art ) {
return art._subArtifacts || Object.create(null);
}
// Return effective search environment provided by artifact `art`, i.e. the
// `artifacts` or `elements` dictionary. For the latter, follow the `type`
// chain and resolve the association `target`. View elements are calculated
// on demand.
// TODO: what about location/user when called from getPath ?
// TODO: think of always acting as if falsyIfNone would be true
// (if not possible, move to second param position)
function environment( art, location, user, assocSpec, falsyIfNone ) {
const env = Functions.navigationEnv( art, location, user, assocSpec );
if (env === 0)
return 0;
return env?.elements || !falsyIfNone && Object.create(null);
}
// Functions called via semantics.notFound: -----------------------------------
function undefinedDefinition( user, item, valid, _dict, prev ) {
// in a CSN source or for `using`, only one env was tested (valid.length 1) :
const art = (!prev) ? item.id : searchName( prev, item.id, 'absolute' );
signalNotFound( (valid.length > 1 ? 'ref-undefined-art' : 'ref-undefined-def'),
[ item.location, user ], valid, { art } );
// TODO: improve text, use text variant for: "or builtin" or "definitions" or none
}
function undefinedForAnnotate( user, item, valid, _dict, prev ) {
// in a CSN source, only one env was tested (valid.length 1):
const art = (!prev) ? item.id : searchName( prev, item.id, 'absolute' );
signalNotFound( (valid.length > 1 ? 'anno-undefined-art' : 'anno-undefined-def'),
// TODO: ext-undefined-xyz
[ item.location, user ], valid, { art } );
}
function undefinedParam( user, head, valid ) {
// TODO: text variant if there are no parameters, or in artifactParameters()?
signalNotFound( 'ref-undefined-param', [ head.location, user ], valid,
{ art: user._main || user, id: head.id } );
}
function undefinedTargetElement( user, head, valid, _dict, pathItemArtifact ) {
// is only called if there is a target, targetElements() returns falsy otherwise
const { target } = pathItemArtifact?._effectiveType || user._parent;
// TODO: better with $refs in filter conditions
signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
{ '#': 'target', art: target, id: head.id } );
}
function undefinedVariable( user, head, valid ) {
// TODO: avoid message if we have already complained about `(exists …)`?
const { id } = head;
const isVar = id.charAt( 0 ) === '$' && id !== '$self';
signalNotFound( (isVar ? 'ref-undefined-var' : 'ref-expecting-const'),
[ head.location, user ],
valid, { '#': 'std', id } );
// TODO: use s/th better than 'ref-expecting-const'?
}
function undefinedSourceElement( user, head, valid, dynamicDict ) {
// TODO: we might mention both the "direct" and the "effective" type and
// always just mentioned one identifier as not found
const { id } = head;
if (id.charAt( 0 ) === '$') {
const tableAlias = dynamicDict[id]?._parent;
// TODO: probably better to pass param `semantics` and calculate dynamic dict explicitly
const alias = tableAlias?.kind === '$tableAlias' ? tableAlias.name?.alias : null;
// TODO: mention $self without query
signalNotFound( 'ref-undefined-var', [ head.location, user ], valid,
{ '#': (alias ? 'alias' : 'std'), alias, id } );
}
else {
const isVirtual = (user.name?.id === id && user.virtual?.val);
const code = 'virtual null as ‹name›';
signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
{ art: head.id, '#': isVirtual ? 'virtual' : 'std', code } );
}
}
function undefinedParentElement( user, head, valid, dynamicDict ) {
// TODO: we might mention both the "direct" and the "effective" type and
// always just mentioned one identifier as not found
const { id } = head;
if (id.charAt( 0 ) === '$') {
const queryOrMain = dynamicDict[id]?._parent;
const withSelf = queryOrMain && (!queryOrMain._main || queryOrMain?.kind === 'select');
signalNotFound( 'ref-undefined-var', [ head.location, user ], valid,
{ '#': (withSelf ? 'self' : 'std'), alias: '$self', id } );
}
else {
// TODO: extra msg like ref-rejected-on if elem found in source elements?
// also whether users wronly tried to refer to aliases/mixins?
const msgVar = userQuery( user ) ? 'query' : null;
// TODO: better with ON in expand if that is supported
signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
{ '#': msgVar, art: head.id } );
}
}
function undefinedOrderByElement( user, head, valid, dynamicDict, _art, path ) {
const { id } = head;
const src = id.charAt( 0 ) !== '$' && user._combined?.[id];
if (src && !Array.isArray(src)) {
path.$prefix = src.name.alias; // pushing it to path directly could be problematic
// configurable error:
signalNotFound( 'ref-deprecated-orderby', [ head.location, user ], valid,
{ id: head.id, newcode: `${ src.name.alias }.${ head.id }` } );
return src;
}
undefinedParentElement( user, head, valid, dynamicDict );
return null;
}
function undefinedNestedElement( user, head, valid ) {
// environment( user._pathHead ); // set _origin
const art = user._pathHead._origin;
// if (!art) console.log('UNE:',user,user._pathHead)
if (!art)
return; // no consequential error
// TODO: better message with $ref
signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
{ art: searchName( art, head.id, 'element' ) } );
// TODO: remove use of searchName() ?
}
// Low-level functions --------------------------------------------------------
/**

@@ -931,4 +1189,6 @@ * Make a "not found" error and optionally attach valid names.

if (valid) {
const user = Array.isArray( location ) && location[1];
err.validNames = (user && definedViaCdl( user )); // viaCdl -> '.'?
valid.reverse();
attachAndEmitValidNames(err, ...valid);
attachAndEmitValidNames( err, ...valid );
}

@@ -953,3 +1213,3 @@ }

}, Object.create(null));
attachAndEmitValidNames(err, valid);
attachAndEmitValidNames( err, valid );
}

@@ -965,2 +1225,3 @@

function attachAndEmitValidNames( msg, ...validDicts ) {
const viaCdl = msg.validNames; // TODO: move to argument list
if (!options.testMode && !options.attachValidNames)

@@ -972,6 +1233,8 @@ return;

for (const name of Object.keys( valid )) {
// ignore internal types such as cds.Association
if (valid[name].internal || valid[name].deprecated || valid[name].$inferred === '$internal')
continue;
msg.validNames[name] = valid[name];
const art = valid[name];
// ignore internal types such as cds.Association, ignore names with dot for
// CDL references to main artifacts:
if (!art.internal && !art.deprecated && art.$inferred !== '$internal' &&
(viaCdl ? art._main || !name.includes('.') : art.kind !== 'namespace'))
msg.validNames[name] = art;
}

@@ -981,7 +1244,12 @@

// no semantic location => either first of [loc, semantic loc] pair or just location.
const loc = msg.location[0] || msg.location;
const loc = msg.$location[0] || msg.$location;
const names = Object.keys(msg.validNames);
info( null, loc,
names.sort();
if (names.length > 22) {
names.length = 20;
names[20] = '…';
}
info( null, [ loc, null ],
{ '#': !names.length ? 'zero' : 'std' },
{ std: `Valid: ${ names.sort().join(', ') }`, zero: 'No valid names' });
{ std: `Valid: ${ names.join(', ') }`, zero: 'No valid names' });
}

@@ -991,4 +1259,13 @@ }

function removeDollarNames( dict ) {
const r = Object.create( null );
for (const name in dict) {
if (name.charAt( 0 ) !== '$')
r[name] = dict[name];
}
return r;
}
module.exports = {
fns,
};

@@ -20,2 +20,3 @@ // Tweak associations: rewrite keys and on conditions

setExpandStatus,
traverseExpr,
} = require('./utils');

@@ -35,9 +36,8 @@

getOrigin,
resolveExpr,
navigationEnv,
} = model.$functions;
const { environment } = model.$volatileFunctions;
// Phase 5: rewrite associations
model._entities.forEach( rewriteArtifact );
// Think hard whether an on condition rewrite can lead to a new cylcic
// Think hard whether an on condition rewrite can lead to a new cyclic
// dependency. If so, we need other messages anyway. TODO: probably do

@@ -318,3 +318,4 @@ // another cyclic check with testMode.js

if (!nav.tableAlias || nav.tableAlias.path) {
resolveExpr( cond, rewriteExpr, elem, nav.tableAlias );
traverseExpr( cond, 'rewrite-on', elem,
expr => rewriteExpr( expr, elem, nav.tableAlias ) );
}

@@ -458,2 +459,3 @@ else {

// we need to only rewrite the current element, not all sibling elements
// TODO: this will be different in v4
if (!elem._redirected)

@@ -487,4 +489,4 @@ return true;

}
const env = name && environment(elem);
elem = setArtifactLink( item, env && env[name] );
const env = name && navigationEnv(elem);
elem = setArtifactLink( item, env?.elements?.[name] );
if (elem && !Array.isArray(elem))

@@ -491,0 +493,0 @@ return elem;

@@ -445,2 +445,43 @@ // Simple compiler utility functions

function traverseExpr( expr, exprCtx, user, callback ) {
if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
return;
if (expr.path) {
callback( expr, exprCtx, user );
// TODO: move arguments and filter traversal to here
return;
}
else if (expr.type || expr.query) {
callback( expr, exprCtx, user );
}
if (expr.args) {
const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
// TODO: re-think $expected
args.forEach( e => traverseExpr( e, exprCtx, user, callback ) );
}
if (expr.suffix) // fn( … ) OVER …
expr.suffix.forEach( e => traverseExpr( e, exprCtx, user, callback ) );
}
function userQuery( user ) {
// TODO: we need _query links set by the definer
while (user._main) {
if (user.kind === 'select' || user.kind === '$join')
return user;
user = user._parent;
}
return null;
}
function definedViaCdl( art ) {
// return !!art._block?.artifacts;
// TODO: the above code would work when _block links are correctly set on
// members of duplicate extensions, see test3/Extensions/DuplicateExtend/. The
// following is a workaround to make at least ref to builtins work:
const { $frontend } = art._block || art;
return $frontend !== 'json' && $frontend !== '$internal';
}
module.exports = {

@@ -474,2 +515,5 @@ pushLink,

isDirectComposition,
traverseExpr,
userQuery,
definedViaCdl,
};

@@ -54,3 +54,3 @@ 'use strict';

options ] = initializeModel(csn, _options, messageFunctions, serviceNames);
const mergedVocabularies = translate.mergeOdataVocabularies(options, message);

@@ -408,3 +408,3 @@

// fetch all exising children names in a map
// fetch all existing children names in a map
const NamesInSchemaXRef = Schema._children.reduce((acc, cur) => {

@@ -441,3 +441,3 @@ const name = cur._edmAttributes.Name;

Each metadata document used to describe an OData service MUST define exactly one entity container.
This sentence expresses that an OData SERVICE must contain an entity container, but an EDMX is not required to have a container.

@@ -689,3 +689,3 @@ Therefore it is absolutely legal and necessary to remove an empty container from the IR!

Check for binding $self parameter. If available, use this parameter
instead of artifically created binding parameter (hasBindingParameter).
instead of artificially created binding parameter (hasBindingParameter).
The binding parameter remains in the CSN and is rendered as any other

@@ -812,4 +812,4 @@ parameter (including default value/not null/ etc) and acts as annotation carrier.

{
const defintion = schemaCsn.definitions[rt];
if(defintion && defintion.kind === 'entity')
const definition = schemaCsn.definitions[rt];
if(definition && definition.kind === 'entity')
{

@@ -891,28 +891,31 @@ functionImport.setEdmAttribute('EntitySet', rt.replace(schemaNamePrefix, ''));

const returns = action.returns.items || action.returns;
let type = returns.type;
if (type) {
collectUsedType(action.returns);
if (!isBuiltinType(type) && csn.definitions[type].kind !== 'entity' && csn.definitions[type].kind !== 'type') {
message('odata-spec-violation-returns', returnsLoc, { kind: action.kind, version: '2.0' });
}
else if(isBuiltinType(type)) {
type = edmUtils.mapCdsToEdmType(returns, messageFunctions, true);
if(type) {
const td = EdmPrimitiveTypeMap[type];
if(td && !td.v2) {
message('odata-spec-violation-type', returnsLoc,
let type = returns['@odata.Type'];
if(!type) {
type = returns.type;
if (type) {
collectUsedType(action.returns);
if (!isBuiltinType(type) && csn.definitions[type].kind !== 'entity' && csn.definitions[type].kind !== 'type') {
message('odata-spec-violation-returns', returnsLoc, { kind: action.kind, version: '2.0' });
}
else if(isBuiltinType(type)) {
type = edmUtils.mapCdsToEdmType(returns, messageFunctions, true);
if(type) {
const td = EdmPrimitiveTypeMap[type];
if(td && !td.v2) {
message('odata-spec-violation-type', returnsLoc,
{ type, version: '2.0', '#': 'incompatible' });
}
}
else {
message('odata-spec-violation-type-unknown', returnsLoc, { type });
}
}
else {
message('odata-spec-violation-type-unknown', returnsLoc, { type });
}
if(action.returns._isCollection)
type = `Collection(${type})`
}
if(action.returns._isCollection)
type = `Collection(${type})`
else {
// type is missing
message('odata-spec-violation-type', returnsLoc);
}
}
else {
// type is missing
message('odata-spec-violation-type', returnsLoc);
}
return type;

@@ -925,3 +928,3 @@ }

In V4 all this has been simplified very much, the only thing actually left over is
<ReferentialConstriant> that is then a sub element to <NavigationProperty>.
<ReferentialConstraint> that is then a sub element to <NavigationProperty>.
However, referential constraints are substantially different to its V2 counterpart,

@@ -1096,3 +1099,3 @@ so it is better to reimplement proper V4 construction of<NavigationProperty> in a separate

message('odata-spec-violation-type', pLoc,
{ type:edmType, version: (p.v4 ? '4.0' : '2.0'), '#': 'incompatible' });
{ type: edmType, version: (p.v4 ? '4.0' : '2.0'), '#': 'incompatible' });
EdmTypeFacetNames.forEach(name => {

@@ -1112,3 +1115,3 @@ const facet = EdmTypeFacetMap[name];

message('odata-spec-violation-type', pLoc,
{ type:edmType, name, version: (p.v4 ? '4.0' : '2.0'), '#': 'facet' });
{ type: edmType, name, version: (p.v4 ? '4.0' : '2.0'), '#': 'facet' });
}

@@ -1118,4 +1121,3 @@ });

else {
message('odata-spec-violation-type-unknown', pLoc,
{ type:edmType });
message('odata-spec-violation-type-unknown', pLoc, { type: edmType });
}

@@ -1122,0 +1124,0 @@ }

@@ -686,37 +686,40 @@ 'use strict'

edmUtils.addTypeFacets(this, this._scalarType);
// CDXCORE-245:
// map type to @odata.Type
// optionally add @odata { MaxLength, Precision, Scale, SRID } but only in combination with @odata.Type
const odataType = csn['@odata.Type'];
if(odataType)
{
const td = EdmPrimitiveTypeMap[odataType];
// If type is known, it must be available in the current version
// Reason: EDMX Importer may set `@odata.Type: 'Edm.DateTime'` on imported V2 services
// Not filtering out this incompatible type here in case of a V4 rendering would
// produce an unrecoverable error.
if(td && (td.v2 === this.v2 || td.v4 === this.v4)) {
this.setEdmAttribute(typeName, odataType);
EdmTypeFacetNames.forEach(facetName => {
const facet = EdmTypeFacetMap[facetName];
if(facet.remove) {
this.removeEdmAttribute(facetName);
this.removeEdmAttribute(facet.extra);
}
if(td[facetName] !== undefined &&
}
else {
this._edmAttributes[typeName] = typecsn.type;
}
}
// CDXCORE-245:
// map type to @odata.Type
// optionally add @odata { MaxLength, Precision, Scale, SRID }
// but only in combination with @odata.Type
// Allow to override type only on scalar and undefined types
if((this._scalarType || typecsn.type == null) && !csn.elements) {
const odataType = csn['@odata.Type'];
if(odataType) {
const td = EdmPrimitiveTypeMap[odataType];
// If type is known, it must be available in the current version
// Reason: EDMX Importer may set `@odata.Type: 'Edm.DateTime'` on imported V2 services
// Not filtering out this incompatible type here in case of a V4 rendering would
// produce an unrecoverable error.
if(td && (td.v2 === this.v2 || td.v4 === this.v4)) {
this.setEdmAttribute(typeName, odataType);
EdmTypeFacetNames.forEach(facetName => {
const facet = EdmTypeFacetMap[facetName];
if(facet.remove) {
this.removeEdmAttribute(facetName);
this.removeEdmAttribute(facet.extra);
}
if(td[facetName] !== undefined &&
(facet.v2 === this.v2 ||
facet.v4 === this.v4))
{
if(this.v2 && facetName === 'Scale' && csn['@odata.'+facetName] === 'variable')
this.setXml({ [facet.extra]: true });
else
if(this.v2 && facetName === 'Scale' && csn['@odata.'+facetName] === 'variable')
this.setXml({ [facet.extra]: true });
else
this.setEdmAttribute(facetName, csn['@odata.'+facetName]);
}
});
}
}
});
}
}
else {
this._edmAttributes[typeName] = typecsn.type;
}
}

@@ -738,3 +741,3 @@ }

// decorate for XML (not for Complex/EntityType)
if(this._isCollection)
if(this._isCollection && this._edmAttributes[typeName])
this._edmAttributes[typeName] = `Collection(${this._edmAttributes[typeName]})`

@@ -1132,3 +1135,3 @@ }

Only this special association may have an explicit ContainsTarget attribute.
See csn2edm.createParmeterizedEntityTypeAndSet() for details
See csn2edm.createEntityTypeAndSet() for details
2) ContainsTarget stems from the @odata.contained annotation

@@ -1135,0 +1138,0 @@ */

@@ -62,3 +62,2 @@ 'use strict';

}
mapAnnotationAssignment(element, struct, PDMSemantics());
}

@@ -84,24 +83,2 @@

// nested functions begin
function PDMSemantics()
{
/*
let dict = Object.create(null);
dict['@PDM.xxx1'] = [ '@sap.pdm-semantics' ];
dict['@PDM.xxx2'] = [ '@sap.pdm-propery' ];
dict['@PDM.xxx3'] = [ '@sap.pdm-display-sq-no' ];
dict['@PDM.xxx4'] = [ '@sap.pdm-record-identifier' ];
dict['@PDM.xxx5'] = [ '@sap.pdm-field-group' ];
dict['@PDM.xxx6'] = [ '@sap.pdm-mask-find-pattern' ];
dict['@PDM.xxx7'] = [ '@sap.pdm-mask-replacement-pattern' ];
dict['@PDM.xxx8'] = [ '@sap.deletable' ];
dict['@PDM.xxx8'] = [ '@sap.updatable' ];
// respect flattened annotation $value
Object.keys(dict).forEach(k => dict[k+'.$value'] = dict[k]);
*/
return Object.create(null);
}
function AnalyticalAnnotations()

@@ -108,0 +85,0 @@ {

@@ -27,3 +27,3 @@ 'use strict';

forEachDefinition(csn, [ attach$path, checkChainedArray ]);
forEachDefinition(csn, [ attach$path, checkProperArrayUsage ]);
checkNestedContextsAndServices();

@@ -41,3 +41,3 @@ throwWithError();

function checkChainedArray(def, defName) {
function checkProperArrayUsage(def, defName) {
if (!isMyServiceRequested(defName))

@@ -52,2 +52,7 @@ return;

if (constructType.items) {
if(constructType.items.target) {
const isComp = constructType.items.type === 'cds.Composition';
message('type-invalid-items', path, { '#': isComp ? 'comp' : 'assoc', prop: 'items' });
return;
}
if (constructType.items.items) {

@@ -54,0 +59,0 @@ message('chained-array-of', path);

@@ -77,6 +77,9 @@ 'use strict';

function isNavigable(assoc) {
return (assoc.target && (assoc['@odata.navigable'] == null || assoc['@odata.navigable']));
}
function isSingleton(entityCsn) {
const singleton = entityCsn['@odata.singleton'];
const hasNullable = entityCsn['@odata.singleton.nullable'] !== undefined && entityCsn['@odata.singleton.nullable'] !== null;
return singleton || ((singleton === undefined || singleton === null) && hasNullable);
const hasNullable = entityCsn['@odata.singleton.nullable'] != null;
return singleton || (singleton == null && hasNullable);
}

@@ -141,3 +144,3 @@

// Mark this association as backlink if $self appears exactly once
// to surpress edm:Association generation in V2 mode
// to suppress edm:Association generation in V2 mode
if(isBacklink) {

@@ -644,3 +647,3 @@ // establish partnership with origin assoc but only if this association is the first one

function isODataSimpleIdentifier(identifier){
// this regular expression reflects the specifiation from above
// this regular expression reflects the specification from above
const regex = /^[\p{Letter}\p{Nl}_][_\p{Letter}\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}]{0,127}$/gu

@@ -904,2 +907,3 @@ return identifier && identifier.match(regex);

isToMany,
isNavigable,
isSingleton,

@@ -906,0 +910,0 @@ isStructuredType,

@@ -36,2 +36,3 @@ // Transform XSN (augmented CSN) into CSN

let withLocations = false;
let withDocComments = false;
let structXpr = false;

@@ -58,3 +59,3 @@ let dictionaryPrototype = null;

id: n => n, // in path item
doc: value,
doc: docComment,
'@': anno,

@@ -164,3 +165,3 @@ virtual: value,

virtual: [ 'abstract' ], // abstract is compiler v1 CSN property
kind: [ 'annotate', 'extend', '$origin' ],
kind: [ 'annotate', 'extend', '$origin' ], // TODO: $origin better at the end? see addOrigin()
op: [ 'join', 'func', 'xpr' ], // TODO: 'func','xpr' into 'quantifier'? TODO: 'global'(scope)?

@@ -527,3 +528,3 @@ quantifier: [

return undefined;
if (node.target) {
if (node.target) { // TODO: use addOrigin() for this
csn.$origin = { target: (val.elements) ? standard( val ) : artifactRef( val, true ) };

@@ -543,9 +544,13 @@ return undefined;

function target( val, _csn, node ) {
if (gensrcFlavor && node._origin && node._origin.$inferred === 'REDIRECTED')
val = node._origin.target;
function target( val, csn, node ) {
if (val.elements)
return standard( val ); // elements in target (parse-cdl)
// Mention user-provided target in $origin if outside query entity:
if (val.$inferred === '' && universalCsn && !gensrcFlavor && !node._main?.query) {
if (!csn.$origin)
csn.$origin = {};
csn.$origin.target = artifactRef( val, '.path' ); // TODO: to addOrigin()
}
if (!universalCsn || gensrcFlavor || node.on)
return artifactRef( val, true );
return artifactRef( val, !gensrcFlavor || val.$inferred !== '' || '.path' );
const tref = artifactRef( val, true );

@@ -676,4 +681,3 @@ const proto = node.type && !node.type.$inferred ? node.type._artifact : node._origin;

if (xsn.kind && xsn.kind.charAt(0) !== '$' && xsn.kind !== 'select' &&
(!xsn.$inferred || !xsn._main) &&
xsn.$inferred !== 'REDIRECTED') { // TODO: also for 'select'
(!xsn.$inferred || !xsn._main)) { // TODO: also for 'select'
// Also include $location for elements in queries (if not via '*')

@@ -747,4 +751,2 @@ addLocation( xsn.name && xsn.name.location || loc, csn );

if (gensrcFlavor) {
if (node._origin?.$inferred === 'REDIRECTED')
dict = node._origin.foreignKeys;
if (dict[$inferred])

@@ -1060,4 +1062,2 @@ return;

function kind( k, csn, node ) {
if (node.$inferred === 'REDIRECTED')
return;
if (k === 'annotate' || k === 'extend') {

@@ -1088,3 +1088,3 @@ // We just use `name.absolute` because it is very likely a "constructed"

function type( node, csn, xsn ) {
function type( node ) {
if (!universalCsn)

@@ -1094,9 +1094,2 @@ return artifactRef( node, !node.$extra );

return undefined;
if (xsn._origin) {
if (xsn._origin.$inferred === 'REDIRECTED') { // auto-redirected user-provided target
const $origin = definition( xsn._origin );
if ($origin) // if not rendered as column
csn.$origin = $origin;
}
}
return artifactRef( node, !node.$extra );

@@ -1119,70 +1112,36 @@ }

// Works also on XSN directly coming from parser and with XSN from CDL->CSN transformation
const { path } = node;
if (terse && node._artifact && !node._artifact._main)
// Shortcut for many cases:
if (terse && node._artifact && !node._artifact._main && terse !== '.path')
return node._artifact.name.absolute;
let { path } = node;
if (!path)
return undefined; // TODO: complain with strict
else if (!path.length)
if (!path.length)
return [];
const link = path[0]._artifact; // XSN TODO: store double definitions differently
const root = Array.isArray(link) ? link[0] : link;
if (!root) { // XSN directly coming from the parser
if (strictMode && node.scope === 'typeOf')
throw new CompilerAssertion( `Unexpected TYPE OF in ${ locationString(node.location) }`);
return renderArtifactPath( node, path, terse, node.scope );
const head = path[0];
const root = head._artifact;
const id = root?.name.absolute;
const scope = node.scope || path.length;
if (typeof scope === 'number' && scope > 1) {
const item = path[scope - 1];
const name = item._artifact?.name;
const absolute = name?.absolute ||
`${ id || head.id }.${ path.slice( 1, scope ).map( i => i.id ).join('.') }`;
path = [ Object.assign( {}, item, { id: absolute } ), ...path.slice( scope ) ];
}
const { absolute } = root.name;
if (node.scope !== 'typeOf' && typeof node.scope !== 'number') {
// CSN input or generated in compiler (XSN TODO: remove scope:'global')
if (absolute === path[0].id) // normal case (no localization view)
return renderArtifactPath( node, path, terse );
// scope:param is not valid (and would be lost)
const head = Object.assign( {}, path[0], { id: absolute } );
return renderArtifactPath( node, [ head, ...path.slice(1) ], terse );
}
if (node.scope === 'typeOf') { // TYPE OF without ':' in path
// Root _artifact which is either element or main artifact for paths starting with $self.
// To make the CDL->CSN transformation simpler, the _artifact for first item could be
// a fake element with just a correct absolute name and _parent/_main links.
if (!root._main || root.kind === 'select') { // $self/$projection
// in query, only correct for leading query ->
// TODO: forbid TYPE OF elem / TYPE OF $self.elem in queries
return renderArtifactPath( node, [ { id: absolute }, ...path.slice(1) ], terse );
else if (scope === 'typeOf') { // TYPE OF without ':' in path
if (root) {
const structs = root.name.element?.split('.').map( n => ({ id: n }) );
// TODO: change (follow parents) if we introduce sparse names
path = [ { id }, ...(structs || []), ...path.slice(1) ];
}
const parent = root._parent;
const structs = parent.name.element ? parent.name.element.split('.') : [];
return extra( { ref: [ absolute, ...structs, ...path.map( pathItem ) ] }, node );
}
let { scope } = node;
if (!scope) { // no ':' in CDL path - try to be nice and guess it via links
const { length } = path;
for (; scope < length; ++scope) {
const art = path[scope]._artifact;
if (!art) {
scope = 0; // unsuccessful, not all path items have links
break;
}
if (art._main)
break; // successful, found first element
else if (strictMode) {
throw new CompilerAssertion( `Unexpected TYPE OF in ${ locationString(node.location) }`);
}
}
const head = Object.assign( {}, path[0], { id: absolute } );
return renderArtifactPath( node, [ head, ...path.slice(1) ], terse, scope );
}
function renderArtifactPath( node, path, terse, scope ) {
if (scope === 0) {
// try to find ':' position syntactically for FROM
scope = !terse && path.findIndex( i => i.where || i.args || i.cardinality) + 1 ||
path.length;
else if (root && id !== head.id) {
path = [ Object.assign( {}, head, { id } ), ...path.slice( 1 ) ];
}
if (typeof scope === 'number' && scope > 1) {
const item = path[scope - 1];
const name = item._artifact && item._artifact.name;
// In localization views, the _artifact link of `item` is important
const id = name && name.absolute ||
pathName( path.slice( 0, scope ) );
path = [ Object.assign( {}, item, { id } ), ...path.slice( scope ) ];
}
const ref = path.map( pathItem );

@@ -1197,2 +1156,6 @@ return (!terse || ref.length !== 1 || typeof ref[0] !== 'string')

!item.where &&
!item.groupBy &&
!item.having &&
!item.limit &&
!item.orderBy &&
!item.cardinality &&

@@ -1220,4 +1183,11 @@ !item.$extra &&

function docComment( doc ) {
// Value is `true` if options.docComment is falsey for CDL input.
if (withDocComments && doc?.val !== true)
return value( doc );
return undefined;
}
function value( node ) {
// "Short" value form, e.g. for annotation assignments
// "Short" value form, e.g. for annotation assignments
if (!node)

@@ -1258,7 +1228,8 @@ return true; // `@aBool` short for `@aBool: true`

return undefined;
// Enums can have values but if enums are extended, their kind is 'element'
if (node.kind === 'enum' || node.$syntax === 'enum') {
// Enums can have values but if enums are extended, their kind is 'element'.
// In v4, we don't check `node.$syntax === 'enum'` anymore.
if (node.kind === 'enum') {
Object.assign( csn, expression( v ) );
}
else if (node.$syntax === 'calc') { // TODO: || node._parent?.kind === 'extend'
else if (node.$syntax === 'calc' || node._parent?.kind === 'extend') {
const stored = v.stored ? { stored: value(v.stored) } : {};

@@ -1270,7 +1241,5 @@ return Object.assign( stored, expression( v ) );

function onCondition( cond, csn, node ) {
function onCondition( cond ) {
if (gensrcFlavor) {
if (node._origin && node._origin.$inferred === 'REDIRECTED')
cond = node._origin.on;
else if (cond.$inferred)
if (cond.$inferred)
return undefined;

@@ -1493,2 +1462,3 @@ }

set( 'virtual', col, elem );
// TODO if (!elem.key?.$specifiedElement)
set( 'key', col, elem );

@@ -1552,3 +1522,3 @@ const expr = expression( elem.value );

for (const prop of typeProperties)
set( prop, r.cast, node );
set(prop, r.cast, node);
return r;

@@ -1616,2 +1586,3 @@ }

withLocations = options.withLocations;
withDocComments = options.docComment !== false;
structXpr = options.structXpr;

@@ -1618,0 +1589,0 @@ projectionAsQuery = isDeprecatedEnabled( options, '_projectionAsQuery' );

@@ -14,3 +14,2 @@ // @ts-nocheck : Issues with Tokens on `this`, e.g. `this.DOT`.

const { CompileMessage } = require('../base/messages');
const { isBetaEnabled } = require('../base/model');
const errorStrategy = require('./errorStrategy');

@@ -34,7 +33,2 @@

class RewriteTypeTokenStream extends antlr4.CommonTokenStream {
constructor(lexer, v4newKeyword) {
super(lexer);
this.v4newKeyword = v4newKeyword;
}
LT( k ) {

@@ -61,3 +55,3 @@ const t = super.LT(k);

// TODO v4: rewrite token in grammar via `this.setLocalToken`
if (n?.type === this.Identifier && (this.v4newKeyword || /^st_/i.test( n.text ))) {
if (n?.type === this.Identifier) {
const o = super.LT(k + 2);

@@ -124,4 +118,3 @@ if (o?.type === this.PAREN)

const lexer = new Lexer( new antlr4.InputStream(source) );
const v4newKeyword = isBetaEnabled(options, 'v4preview');
const tokenStream = new RewriteTypeTokenStream(lexer, v4newKeyword);
const tokenStream = new RewriteTypeTokenStream(lexer);
/** @type {object} */

@@ -168,3 +161,15 @@ const parser = new Parser( tokenStream );

const rulespec = rules[rule];
const tree = rule && parser[rulespec.func]();
let tree;
try {
tree = rule && parser[rulespec.func]();
}
catch (e) {
if (e instanceof RangeError && e.message.match(/Maximum.*exceeded$/i)) {
messageFunctions.error('syntax-invalid-source', { file: filename },
{ '#': 'cdl-stackoverflow' } );
}
else {
throw e;
}
}
const ast = tree && tree[rulespec.returns] || {};

@@ -175,3 +180,2 @@ ast.options = options;

// Warn about unused doc-comments.
// Do not warn if docComments are explicitly disabled.

@@ -181,4 +185,4 @@ if (options.docComment !== false) {

if (token.type === parser.constructor.DocComment && !token.isUsed) {
messageFunctions.info( 'syntax-ignoring-doc-comment', parser.tokenLocation(token), {},
'Ignoring doc comment as it is not written at a defined position' );
messageFunctions.info('syntax-ignoring-doc-comment', parser.tokenLocation(token), {},
'Ignoring doc comment as it is not written at a defined position');
}

@@ -185,0 +189,0 @@ }

@@ -18,2 +18,8 @@ 'use strict';

*
* Notes on escape sequences:
* - For `*\/`, the `\` is removed.
* - Nothing else is escaped, meaning `\n` will be `\` and `n` and not a newline character.
* - _If requested_, we could parse the doc comment similar to multiline string literals, but
* via an option.
*
* @param {string} comment Raw comment, e.g. '/** comment ... '.

@@ -38,2 +44,3 @@ * Must be a valid doc comment.

.replace(/\*+\/$/, '')
.replace('*\\/', '*/') // escape sequence
.trim();

@@ -71,3 +78,6 @@ return isWhitespaceOrNewLineOnly(content) ? null : content;

const content = lines.slice(startIndex, endIndex).join('\n');
const content = lines
.slice(startIndex, endIndex)
.join('\n')
.replace('*\\/', '*/'); // escape sequence
return isWhitespaceOrNewLineOnly(content) ? null : content;

@@ -74,0 +84,0 @@ }

@@ -404,3 +404,4 @@ // Generic ANTLR parser class with AST-building functions

else if (prop === 'doc') {
if (def.doc) {
// With explicit docComment:false, we don't emit a warning.
if (def.doc && this.options.docComment !== false) {
this.warning( 'syntax-duplicate-doc-comment', def.doc.location, {},

@@ -650,9 +651,11 @@ 'Doc comment is overwritten by another one below' );

if (!this.options.docComment)
return;
if (node.doc) {
// With explicit docComment:false, we don't emit a warning.
if (node.doc && this.options.docComment !== false) {
this.warning( 'syntax-duplicate-doc-comment', node.doc.location, {},
'Doc comment is overwritten by another one below' );
}
node.doc = this.valueWithTokenLocation( parseDocComment( token.text ), token );
// Either store the doc comment or a marker that there is one.
const val = !this.options.docComment ? true : parseDocComment( token.text );
node.doc = this.valueWithTokenLocation( val, token );
}

@@ -1104,3 +1107,3 @@

if (!isBetaEnabled( this.options, 'aspectWithoutElements' )) {
this.error( null, [ art.name.location ], {},
this.error( null, [ art.name.location, null ], {},
'Aspects without elements are not supported, yet' );

@@ -1195,3 +1198,3 @@ }

function reportDuplicateClause( prop, errorneous, chosen, keywords ) {
function reportDuplicateClause( prop, erroneous, chosen, keywords ) {
// probably easier for message linters not to use (?:) for the message id...?

@@ -1204,6 +1207,6 @@ const args = {

};
if (errorneous.val === chosen.val)
this.warning( 'syntax-duplicate-equal-clause', errorneous.location, args );
if (erroneous.val === chosen.val)
this.warning( 'syntax-duplicate-equal-clause', erroneous.location, args );
else
this.message( 'syntax-duplicate-clause', errorneous.location, args );
this.message( 'syntax-duplicate-clause', erroneous.location, args );
}

@@ -1210,0 +1213,0 @@

@@ -290,2 +290,19 @@ // Official cds-compiler API.

}
/**
* If turned on, renders:
*
* - `$user.‹id|locale›` as `session_context( '$user.‹id|locale›' )`
* instead of requiring them to be set in `sqlOptions.variableReplacements`, and
* - `$at.‹from|to›` and `$valid.‹from|to›` as `session_context( '$valid.‹from|to›' )`
* instead of using `strftime(…)`.
*
* `sqlOptions.variableReplacements` takes precedence for `$user`. If `$user.id` is set,
* the compiler will not render a `session_context(…)` function, even if this option is set.
*
* Only works with sqlDialect 'sqlite'! Otherwise, it has no effect.
*
* @since 3.9.0
* @beta
*/
betterSqliteSessionVariables?: boolean
}

@@ -542,3 +559,3 @@

* Returns a message string with file- and semantic location if present in compact
* form (i.e. one line)
* form (i.e. one line).
*

@@ -554,2 +571,4 @@ * Example:

* @param noHome If true, the semantic location will _not_ be part of the string.
*
* @deprecated Use messageString(msg, config) instead.
*/

@@ -559,22 +578,94 @@ export function messageString(msg: CompileMessage, normalizeFilename?: boolean, noMessageId?: boolean, noHome?: boolean): string;

/**
* Returns a message string with file- and semantic location if present
* in multiline form.
* The error (+ message id) can colored according to their severity.
* Returns a message string with file- and semantic location if present in compact
* form (i.e. one line).
*
* Example:
* ```
* Error[message-id]: Can't find type `nu` in this scope
* |
* <source>.cds:3:11, at entity:“E”/element:“e”
* <source>.cds:3:11: Error message-id: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
* ```
*
* @param config.normalizeFilename If true, the file path will be normalized to use `/` as the path separator.
* @param config.noMessageId If true, no messages id (in brackets) will be shown.
* @param config.hintExplanation If true, messages with explanations will get a "…" marker, see {@link hasMessageExplanation}.
* @param config.withLineSpacer If true, an additional line (with `|`) will be inserted between message and location.
* @param config.color If true, ANSI escape codes will be used for coloring the severity. If false, no
* coloring will be used. If 'auto', we will decide based on certain factors such
* as whether the shell is a TTY and whether the environment variable `NO_COLOR` is
* unset.
* Example Usage:
* ```js
* const config = { normalizeFilename: false, noMessageId: true };
* console.log(messages.map(msg => compiler.messageString(msg, config)));
* ```
*
* @param config.normalizeFilename
* If true, the file path will be normalized to use `/` as the path separator (instead of `\` on Windows).
*
* @param config.noMessageId
* If true, will _not_ show the message ID (+ explanation hint) in the output.
*
* @param config.noHome
* If true, will _not_ show message's semantic location.
*
* @param config.module
* If set, downgradable error messages will get a '‹↓›' marker, depending on whether
* the message can be downgraded for the given module.
*/
export function messageString(msg: CompileMessage, config?: {
normalizeFilename?: boolean
noMessageId?: boolean
noHome?: boolean
module?: string
}): string;
/**
* Returns a message string with file- and semantic location if present in multiline form
* with a source code snippet below that has highlights for the message's location.
* The message (+ message id) are colored according to their severity.
*
* IMPORTANT: Argument `config` should be re-used by subsequent calls to this function,
* because it caches argument `config.sourceLineMap`.
*
* Example Output:
* ```
* Error[message-id]: Can't find type `nu` in this scope
* |
* <source>.cds:3:10, at entity:“E”/element:“e”
* |
* 3 | e : nu;
* | ^^
* ```
*
* Example Usage:
* ```js
* const config = { sourceMap: fileCache, cwd: '' };
* console.log(messages.map(msg => compiler.messageStringMultiline(msg, config)));
* ```
*
* @param config.normalizeFilename
* If true, the file path will be normalized to use `/` as the path separator (instead of `\` on Windows).
*
* @param config.noMessageId
* If true, will _not_ show the message ID (+ explanation hint) in the output.
*
* @param config.hintExplanation
* If true, messages with explanations will get a "…" marker, see {@link hasMessageExplanation}.
*
* @param config.module
* If set, downgradable error messages will get a '‹↓›' marker, depending on whether
* the message can be downgraded for the given module.
*
* @param config.sourceMap
* A dictionary of filename<->source-code entries. You can pass the fileCache that is used
* by the compiler.
*
* @param config.sourceLineMap
* A dictionary of filename<->source-newline-indices entries. Is used to extract source code
* snippets for message locations. If not set, will be set and filled by this function on-demand.
* An entry is an array of character/byte offsets to new-lines, for example sourceLineMap[1] is the
* end-newline for the second line.
*
* @param config.cwd
* The current working directory (cwd) that was passed to the compiler.
* This value is only used if a source map is provided and relative paths needs to be
* resolved to absolute ones.
*
* @param config.color
* If true/'always', ANSI escape codes will be used for coloring the severity. If false/'never',
* no coloring will be used. If 'auto', we will decide based on certain factors such
* as whether the shell is a TTY and whether the environment variable `NO_COLOR` is
* unset.
*/
export function messageStringMultiline(msg: CompileMessage, config?: {

@@ -584,4 +675,7 @@ normalizeFilename?: boolean

hintExplanation?: boolean
withLineSpacer?: boolean
color?: boolean | 'auto'
module?: string
sourceMap?: Record<string, string>
sourceLineMap?: Record<string, number[]>
cwd?: string
color?: boolean | 'auto' | 'always' | 'never'
}): string;

@@ -612,2 +706,4 @@

* unset.
*
* @deprecated Use {@link messageStringMultiline} with `config.sourceMap` and `config.sourceLineMap` instead!
*/

@@ -719,4 +815,4 @@ export function messageContext(sourceLines: string[], msg: CompileMessage, config?: {

* // '![OCCURRENCE]'
* to.cdl.smartId('myid')
* // 'myid'
* to.cdl.smartId('myId')
* // 'myId'
* ```

@@ -737,4 +833,4 @@ *

* // '![with ![brackets]]]'
* to.cdl.smartFunctionId('myfunction')
* // 'myfunction'
* to.cdl.smartFunctionId('myFunction')
* // 'myFunction'
* ```

@@ -753,4 +849,4 @@ *

* // '![with ![brackets]]]'
* to.cdl.delimitedId('myid')
* // '![myid]'
* to.cdl.delimitedId('myId')
* // '![myId]'
* ```

@@ -789,4 +885,4 @@ *

* // '"SELECT"'
* to.sql.smartId('myid', 'sqlite')
* // 'myid'
* to.sql.smartId('myId', 'sqlite')
* // 'myId'
* ```

@@ -807,4 +903,4 @@ *

* // '"with ""quotes"""'
* to.sql.smartFunctionId('myfunction', 'sqlite')
* // 'myfunction'
* to.sql.smartFunctionId('myFunction', 'sqlite')
* // 'myFunction'
* ```

@@ -824,4 +920,4 @@ *

* // '"with ""quotes"""'
* to.sql.delimitedId('myid', 'sqlite')
* // '"myid"'
* to.sql.delimitedId('myId', 'sqlite')
* // '"myId"'
* ```

@@ -1111,3 +1207,3 @@ *

* CDS Schema Notation. Not yet specified in this TypeScript declaration file.
* See <https://pages.github.tools.sap/cap/docs/cds/csn> for more.
* See <https://cap.cloud.sap/docs/cds/csn> for more.
*/

@@ -1118,3 +1214,3 @@ export type CSN = any;

* CDS Query Notation. Not yet specified in this TypeScript declaration file.
* See <https://pages.github.tools.sap/cap/docs/cds/cqn> for more.
* See <https://cap.cloud.sap/docs/cds/cqn> for more.
*/

@@ -1125,3 +1221,3 @@ export type CQN = any;

* CDS Expression Notation. Not yet specified in this TypeScript declaration file.
* See <https://pages.github.tools.sap/cap/docs/cds/cxn> for more.
* See <https://cap.cloud.sap/docs/cds/cxn> for more.
*/

@@ -1142,6 +1238,2 @@ export type CXN = any;

/**
* @deprecated Use `$location` instead.
*/
location: Location
/**
* Location information like file and line/column of the message.

@@ -1148,0 +1240,0 @@ */

@@ -95,3 +95,3 @@ // Main entry point for the CDS Compiler

messageString: (...args) => messages.messageString(...args),
messageStringMultiline: (...args) => messages.messageStringMultiline(...args),
messageStringMultiline: (err, config) => messages.messageStringMultiline(err, config),
messageContext: (...args) => messages.messageContext(...args),

@@ -98,0 +98,0 @@ explainMessage: (...args) => messages.explainMessage(...args),

@@ -96,3 +96,3 @@ // CSN functionality for resolving references

// (explicit and implicit) table alias names and `mixin` definitions of the
// current query and its parent queries (according to the query hiearchy).
// current query and its parent queries (according to the query hierarchy).
// 2. If the search according to (1) was not successful and the name starts

@@ -214,3 +214,3 @@ // with a `$`, we could consider the name to be a “magic” variable with

$init: { $initOnly: true },
type: { lexical: false, dynamic: 'global' }, // TODO: assoc: 'static', see Issue #8458
type: { lexical: false, dynamic: 'global', assoc: 'static' },
includes: { lexical: false, dynamic: 'global', assoc: 'static' }, // no elem ref anyway

@@ -261,2 +261,3 @@ target: { lexical: false, dynamic: 'global', assoc: 'static' }, // no elem ref anyway

};
artifactRef.from = fromArtifactRef;
return {

@@ -317,2 +318,4 @@ effectiveType,

env = effectiveType( env.items );
if (env.elements) // shortcut
return env;
const target = (staticAssoc ? targetAspect( env ) : env.target);

@@ -354,2 +357,10 @@ if (typeof target !== 'string')

function fromArtifactRef( ref ) {
// do not cache while there is second param
const art = artifactFromRef( ref, true );
if (art)
return art;
throw new ModelError( `Unknown artifact reference: ${ typeof ref !== 'string' ? JSON.stringify(ref.ref) : ref }` );
}
function artifactPathRef( ref ) {

@@ -360,3 +371,3 @@ const [ head, ...tail ] = ref.ref;

for (const elem of tail) {
const env = navigationEnv( art ); // TODO: second argument true, see Issue #8458
const env = navigationEnv( art, true );
art = env.elements[pathId( elem )];

@@ -367,3 +378,3 @@ }

function artifactFromRef( ref ) {
function artifactFromRef( ref, noLast ) {
const [ head, ...tail ] = ref.ref;

@@ -376,2 +387,4 @@ let art = csn.definitions[pathId( head )];

}
if (noLast) // TODO: delete that param
return art;
return navigationEnv( art );

@@ -637,5 +650,6 @@ }

if (semantics.dynamic === 'query')
if (semantics.dynamic === 'query') {
// TODO: for ON condition in expand, would need to use cached _element
return resolvePath( path, qcache.elements[head], null, 'query' );
}
for (const name in qcache.$aliases) {

@@ -679,2 +693,4 @@ const alias = qcache.$aliases[name];

setCache( path[i - 1], '_env', parent );
if (!parent.elements)
throw new ModelError( `${ parent.from ? 'Query ' : '' }elements not available: ${ Object.keys( parent ).join('+') }`);
art = parent.elements[pathId( path[i] )];

@@ -756,2 +772,4 @@ if (!art) {

columns.map( c => initColumnElement( c, qcache ) );
else if (columns && !elements)
throw new ModelError( `Query elements not available: ${ Object.keys( (index ? qcache._select : main) ).join('+') }`);
} );

@@ -1051,2 +1069,3 @@ return all;

// if we want to allow auto-redirect of user-provided target with renamed keys:
// (TODO: no, we do not allow that anymore)
refCtx = (refCtx === '$origin' && prop === 'keys') ? 'keys_origin' : prop;

@@ -1053,0 +1072,0 @@ }

@@ -104,3 +104,3 @@ 'use strict';

else if (query.SELECT.from.ref) {
let art = artifactRef(query.SELECT.from);
let art = artifactRef.from(query.SELECT.from);

@@ -135,3 +135,3 @@ if (art.target)

else if (arg.ref) {
const art = artifactRef(arg);
const art = artifactRef.from(arg);
elements = mergeElementsIntoMap(elements, art.elements, art.$location, arg.as || implicitAs(arg.ref), implicitAs(arg.ref) || arg.as);

@@ -546,6 +546,6 @@ }

else if (Array.isArray(callback)) {
callback.forEach(cb => cb(construct.returns, '', 'params', [ ...path, 'returns' ], construct));
callback.forEach(cb => cb(construct.returns, '', 'returns', [ ...path, 'returns' ], construct));
}
else {
callback(construct.returns, '', 'params', [ ...path, 'returns' ], construct);
callback(construct.returns, '', 'returns', [ ...path, 'returns' ], construct);
}

@@ -912,3 +912,3 @@ }

else if (typeof query.SELECT.from === 'string' || query.SELECT.from.ref)
handleDependency(artifactRef(query.SELECT.from), artifact, artifactName);
handleDependency(artifactRef.from(query.SELECT.from), artifact, artifactName);
}

@@ -928,3 +928,3 @@ }, [ 'definitions', artifactName, (artifact.projection ? 'projection' : 'query') ]);

else if (arg.ref)
handleDependency(artifactRef(arg), artifact, artifactName);
handleDependency(artifactRef.from(arg), artifact, artifactName);
}

@@ -1088,4 +1088,5 @@ }

* @param {boolean} [overwrite]
* @param {object} excludes
*/
function copyAnnotations( fromNode, toNode, overwrite = false ) {
function copyAnnotations( fromNode, toNode, overwrite = false, excludes = {} ) {
// Ignore if no toNode (in case of errors)

@@ -1098,3 +1099,3 @@ if (!toNode)

for (const anno of annotations) {
if (toNode[anno] === undefined || overwrite)
if ((toNode[anno] === undefined || overwrite) && !excludes[anno])
toNode[anno] = fromNode[anno];

@@ -1101,0 +1102,0 @@ }

@@ -250,3 +250,3 @@ // Make internal properties of the XSN / augmented CSN visible

function origin( node, parent ) {
if (!node || node.$inferred === 'REDIRECTED')
if (!node)
return reveal( node, parent );

@@ -315,14 +315,9 @@ return artifactIdentifier( node, parent );

if (node._outer) {
if (node.$inferred === 'REDIRECTED') {
outer = `/redirected${ outer }`;
}
else {
// eslint-disable-next-line no-nested-ternary
outer = (node._outer.items === node) ? `/items${ outer }`
// eslint-disable-next-line no-nested-ternary
outer = (node._outer.items === node) ? `/items${ outer }`
// eslint-disable-next-line no-nested-ternary
: (node._outer.returns === node) ? `/returns${ outer }`
// eslint-disable-next-line no-nested-ternary
: (node._outer.targetAspect === node) ? `/target${ outer }`
: `/returns/items${ outer }`;
}
: (node._outer.returns === node) ? `/returns${ outer }` // TODO returns now normal
// eslint-disable-next-line no-nested-ternary
: (node._outer.targetAspect === node) ? `/target${ outer }`
: `/returns/items${ outer }`;
node = node._outer;

@@ -329,0 +324,0 @@ }

@@ -177,3 +177,3 @@ 'use strict';

if(otherElement.notNull && element.notNull === undefined) {
element.$notNull = false; // Explictily set notNull to the implicit default so we render the correct ALTER
element.$notNull = false; // Explicitly set notNull to the implicit default so we render the correct ALTER
}

@@ -180,0 +180,0 @@ changedElementsDict[name] = changedElement(element, otherElement);

@@ -6,4 +6,4 @@ 'use strict';

const { forEach } = require('../../utils/objectUtils');
const { isPersistedAsTable } = require('../../model/csnUtils');
const { forEach, forEachValue, forEachKey } = require('../../utils/objectUtils');
const { isPersistedAsTable, applyTransformations } = require('../../model/csnUtils');

@@ -36,2 +36,3 @@ function isKey( element ) {

hana: getFilterObject('hana'),
csn: filterCsn,
};

@@ -103,1 +104,38 @@

}
/**
* Filter non-diff-relevant properties from a db-expanded CSN.
* Currently we filter:
* - annotations
*
* @param {CSN.Model} csn CSN to filter
* @returns {CSN.Model} Filtered input model
*/
function filterCsn( csn ) {
const annosToKeep = {
'@cds.persistence.skip': true,
'@cds.persistence.exists': true,
'@cds.persistence.table': true,
'@cds.persistence.name': true,
'@sql.append': true,
'@sql.prepend': true,
};
applyTransformations(csn, {
elements: (parent, prop, elements) => {
forEachValue(elements, (element) => {
forEachKey(element, (key) => {
if ((key.startsWith('@') && !annosToKeep[key]) || key === 'keys')
delete element[key];
});
});
},
}, [ (artifact) => {
forEachKey(artifact, (key) => {
if (key.startsWith('@') && !annosToKeep[key])
delete artifact[key];
});
} ]);
return csn;
}

@@ -107,5 +107,3 @@ 'use strict';

mapAssocToJoinCardinality
ignoreAssocPublishingInUnion
enableUniversalCsn
postgres
aspectWithoutElements

@@ -116,3 +114,2 @@ odataTerms

Valid values are:
autoCorrectOrderBySourceRefs
eagerPersistenceForGeneratedEntities

@@ -119,0 +116,0 @@ --fallback-parser <type> If the language cannot be deduced by the file's extensions, use this

@@ -142,2 +142,3 @@ // Common render functions for toCdl.js, toHdbcds.js and toSql.js

*
* @param {CSN.Model} csn
* @param {Function[]} killList Array to add cleanup functions to

@@ -175,3 +176,3 @@ */

*
*
* @param {CSN.Model} csn
* @param {string} parentName Name of the parent context

@@ -317,2 +318,21 @@ * @param {string} artifactName Name of the current context

/**
* Default lengths for CDS types.
*/
const sqlDefaultLengths = {
hana: {
'cds.String': 5000,
},
default: {
'cds.String': 255,
'cds.Binary': 5000,
},
};
function getDefaultTypeLengths( sqlDialect ) {
if (!sqlDefaultLengths[sqlDialect])
return sqlDefaultLengths.default;
return { ...sqlDefaultLengths.default, ...sqlDefaultLengths[sqlDialect] };
}
/**
* Get the element matching the column

@@ -473,5 +493,6 @@ *

* @param {ExpressionConfiguration} rendererBase
* @param {boolean} [adaptPath] If true, `env.path` will be adapted for lists and subExpr.
* @returns {ExpressionRenderer} Expression rendering utility
*/
function createExpressionRenderer( rendererBase ) {
function createExpressionRenderer( rendererBase, adaptPath = false ) {
const renderer = Object.create(rendererBase);

@@ -491,2 +512,3 @@ renderer.visitExpr = visitExpr;

renderObj.isNestedXpr = false;
renderObj.adaptPath = adaptPath;
return renderObj.visitExpr(x);

@@ -503,2 +525,3 @@ };

renderObj.isNestedXpr = true;
renderObj.adaptPath = adaptPath;
return renderObj.visitExpr(x);

@@ -528,3 +551,10 @@ };

// e.g. CSN for `(1=1 or 2=2) and 3=3`.
const tokens = x.map(item => this.renderSubExpr(item));
const tokens = x.map((item, i) => {
if (this.adaptPath) {
// We want to keep the prototype of the original env.
const env = Object.assign(Object.create(Object.getPrototypeOf(this.env || {})), this.env, { path: [ ...this.env.path, i ] });
return this.renderSubExpr(item, env);
}
return this.renderSubExpr(item);
});
return beautifyExprArray(tokens);

@@ -541,3 +571,10 @@ }

// Render as non-nested expr.
return `(${x.list.map(item => this.renderExpr(item)).join(', ')})`;
return `(${x.list.map((item, i) => {
if (this.adaptPath) {
// We want to keep the prototype of the original env.
const env = Object.assign(Object.create(Object.getPrototypeOf(this.env || {})), this.env, { path: [ ...this.env.path, 'list', i ] });
return this.renderExpr(item, env);
}
return this.renderExpr(item);
}).join(', ')})`;
}

@@ -592,2 +629,3 @@ else if (x.val !== undefined) {

withoutCast,
getDefaultTypeLengths,
};

@@ -27,3 +27,3 @@ 'use strict';

const elements = Object.entries(elementsObj)
.map(([ name, elt ]) => this.scopedFunctions.renderElement(artifactName, name, elt, duplicateChecker, null, alterEnv))
.map(([ name, elt ]) => this.scopedFunctions.renderElement(name, elt, duplicateChecker, null, alterEnv))
.filter(s => s !== '');

@@ -30,0 +30,0 @@

@@ -45,3 +45,3 @@ // Render functions for toSql.js

// omit 'RESTRICT' action for ON UPDATE / ON DELETE, because it interferes with deferred constraint check
if (sqlDialect === 'sqlite') {
if (sqlDialect === 'sqlite' || sqlDialect === 'postgres') {
if (constraint.onDelete === 'CASCADE' )

@@ -61,4 +61,4 @@ result += `${indent}ON DELETE ${constraint.onDelete}${onDeleteRemark}\n`;

}
// for sqlite, the DEFERRABLE keyword is required
result += `${indent}${sqlDialect === 'sqlite' ? 'DEFERRABLE ' : ''}INITIALLY DEFERRED`;
// for sqlite and postgreSQL, the DEFERRABLE keyword is required
result += `${indent}${sqlDialect === 'sqlite' || sqlDialect === 'postgres' ? 'DEFERRABLE ' : ''}INITIALLY DEFERRED`;
return result;

@@ -65,0 +65,0 @@ }

@@ -25,3 +25,3 @@ // Currently unused, but may become useful again if HDBCDS -> HDBTABLE

function beetween(expression, token, index){
function between(expression, token, index){
let start = index-1, end = index+4;

@@ -61,3 +61,3 @@ if(expression[index-1] === 'not'){

'!=': binarycomparison,
'between': beetween,
'between': between,
'like': like

@@ -64,0 +64,0 @@ }

{
"root": true,
"plugins": ["sonarjs", "jsdoc"],
"extends": ["../../../.eslintrc-ydkjsi.json", "plugin:sonarjs/recommended", "plugin:jsdoc/recommended"],
"extends": ["plugin:jsdoc/recommended", "../../../.eslintrc-ydkjsi.json", "plugin:sonarjs/recommended"],
"rules": {

@@ -6,0 +6,0 @@ "prefer-const": "error",

@@ -63,3 +63,3 @@ 'use strict';

(options.skipIgnore && node._ignore) ||
(options.skipStandard && options.skipStandard[_prop])
options.skipStandard?.[_prop]
)

@@ -141,3 +141,2 @@ return;

if (s && typeof s === 'object') {
csnPath.push( i );
if (options.drillRef) {

@@ -147,2 +146,3 @@ standard(_path, i, s);

else {
csnPath.push( i );
if (s.args)

@@ -152,4 +152,4 @@ standard( s, 'args', s.args );

standard( s, 'where', s.where );
csnPath.pop();
}
csnPath.pop();
}

@@ -156,0 +156,0 @@ } );

@@ -121,4 +121,7 @@ 'use strict';

*
* Or in other words: Allow using the foreign keys of managed associations in on-conditions
* Or in other words: Allow using the foreign keys of managed associations in
* on-conditions / calculated elements on-write.
*
* Expects that flattening has already been performed.
*
* @param {CSN.Artifact} artifact Artifact to check

@@ -134,2 +137,3 @@ * @param {string} artifactName Name of the artifact

if (links) {
let fkAlias = '';
// eslint-disable-next-line for-direction

@@ -142,15 +146,21 @@ for (let i = links.length - 1; i >= 0; i--) {

!ref.slice(i).some(refElement => typeof refElement !== 'string')) {
// We join the managed assoc with everything following it
const sourceElementName = ref.slice(i).join(pathDelimiter);
const source = findSource(links, i - 1) || artifact;
// allow specifying managed assoc on the source side
const fks = link.art.keys.filter(fk => ref[i] + pathDelimiter + fk.ref[0] === sourceElementName);
if (fks && fks.length >= 1) {
const fk = fks[0];
const managedAssocStepName = refOwner.ref[i];
const fkName = `${managedAssocStepName}${pathDelimiter}${fk.as}`;
if (source && source.elements[fkName])
refOwner.ref = [ ...ref.slice(0, i), fkName ];
const fkRef = ref[i + 1];
const fkName = (!fkAlias ? fkRef : `${fkRef}${pathDelimiter}${fkAlias}`);
const fks = link.art.keys.filter(key => key.ref[0] === fkName);
if (fks.length >= 1) { // after flattening, at most one FK will remain.
// `.as` is set for SQL, but not for OData -> fall back to implicit alias
fkAlias = fks[0].as || fks[0].ref[fks[0].ref.length - 1];
const source = findSource(links, i - 1) || artifact;
const managedAssocStepName = ref[i];
const newFkName = `${managedAssocStepName}${pathDelimiter}${fkAlias}`;
if (source?.elements[newFkName])
refOwner.ref = [ ...ref.slice(0, i), newFkName ];
}
}
else {
fkAlias = '';
// Ignore last path step and unmanaged associations.
// Structures should have been already flattened.
}
}

@@ -166,2 +176,4 @@ }

applyTransformationsOnNonDictionary(elem, 'on', transformer, {}, [ 'definitions', artifactName, 'elements', elemName ]);
else if (elem.value?.stored)
applyTransformationsOnNonDictionary(elem, 'value', transformer, {}, [ 'definitions', artifactName, 'elements', elemName ]);
}

@@ -168,0 +180,0 @@

@@ -62,3 +62,3 @@ 'use strict';

* Turn .expand/.inline into normal refs. @cds.persistence.skip .expand with to-many (and all transitive views).
* For such skipped things, error for usage of assoc pointing to them and and ignore publishing of assoc pointing to them.
* For such skipped things, error for usage of assoc pointing to them and ignore publishing of assoc pointing to them.
*/

@@ -70,3 +70,3 @@ function rewriteExpandInline() {

const entity = findAnEntity();
const toDummyfy = [];
const toDummify = [];

@@ -115,3 +115,3 @@ applyTransformations(csn, {

target: (parent, name, target, path) => {
if (toDummyfy.indexOf(target) !== -1) {
if (toDummify.indexOf(target) !== -1) {
publishing.push({

@@ -167,3 +167,3 @@ parent, name, target, path: [ ...path ],

const target = art.target ? art.target : pathStep;
if (toDummyfy.indexOf(target) !== -1) {
if (toDummify.indexOf(target) !== -1) {
error( null, obj.$path, {

@@ -181,3 +181,3 @@ id: pathStep, elemref: obj, name,

const target = art.target ? art.target : pathStep;
if (toDummyfy.indexOf(target) !== -1)
if (toDummify.indexOf(target) !== -1)
kill.push(i);

@@ -229,3 +229,3 @@ }

}
toDummyfy.push(n);
toDummify.push(n);
}

@@ -238,3 +238,3 @@ }

function dummyfy() {
for (const artifactName of [ ...new Set(toDummyfy) ])
for (const artifactName of [ ...new Set(toDummify) ])
csn.definitions[artifactName] = createDummyView(entity);

@@ -376,3 +376,4 @@ }

const thing = base[currentAlias[currentAlias.length - 1]];
if (current?._art?.value || thing?.value)
const value = current?._art?.value || thing?.value;
if (value && !value.stored)
error('query-unsupported-calc', current.$path || col.$path, { '#': 'inside' });

@@ -407,3 +408,3 @@ expanded.push(Object.assign({}, current, { ref: currentRef, as: currentAlias.join(pathDelimiter) } ));

*
* So anything that does not have a $self/$projection infron get's the so-far-traveled alias,
* So anything that does not have a $self/$projection infront gets the so-far-traveled alias,
* since after the transformation it will basically be in "top-level".

@@ -541,3 +542,3 @@ *

if (_art && isStructured(_art))
newThing.push(...expandRef(_art, col.ref, col.as, col.key || false, withAlias));
newThing.push(...expandRef(_art, col, withAlias));

@@ -565,12 +566,10 @@ else

* @param {CSN.Element} art
* @param {Array} ref
* @param {Array} alias
* @param {boolean} isKey True if the ref obj has property key: true
* @param {object} root Column, ref in order by, etc.
* @param {boolean} withAlias
* @returns {Array}
*/
function expandRef( art, ref, alias, isKey, withAlias ) {
function expandRef( art, root, withAlias ) {
const expanded = [];
/** @type {Array<[CSN.Element, any[], any[]]>} */
const stack = [ [ art, ref, [ alias || ref[ref.length - 1] ] ] ];
const stack = [ [ art, root.ref, [ root.as || implicitAs(root.ref) ] ] ];
while (stack.length > 0) {

@@ -583,3 +582,3 @@ const [ current, currentRef, currentAlias ] = stack.pop();

else {
const obj = { ref: currentRef };
const obj = { ...root, ...{ ref: currentRef } };
if (withAlias) {

@@ -590,6 +589,6 @@ const newAlias = currentAlias.join(pathDelimiter);

// alias was implicit - to later distinguish expanded s -> s.a from explicitly written s.a
if (alias === undefined)
if (root.as === undefined)
setProp(obj, '$implicitAlias', true);
}
if (isKey)
if (root.key)
obj.key = true;

@@ -596,0 +595,0 @@ expanded.push(obj);

@@ -93,6 +93,11 @@ 'use strict';

toFinalBaseType(parent, resolved, true);
if (parent.items) // items could have unresolved types
toFinalBaseType(parent.items, resolved, true);
// structured types might not have the child-types replaced.
// Drill down to ensure this.
if (parent.elements) {
const stack = [ parent.elements ];
let nextElements = parent.elements || parent.items?.elements;
if (nextElements) {
const stack = [ nextElements ];
while (stack.length > 0) {

@@ -103,8 +108,9 @@ const elements = stack.pop();

toFinalBaseType(e, resolved, true);
if (e.elements)
stack.push(e.elements);
nextElements = e.elements || e.items?.elements;
if (nextElements)
stack.push(nextElements);
}
}
}
const directLocalized = parent.localized || false;

@@ -114,16 +120,3 @@ if (!directLocalized && !options.toOdata)

}
// HANA/SQLite do not support array-of - turn into CLOB/Text
if (parent.items && !options.toOdata) {
parent.type = 'cds.LargeString';
delete parent.items;
}
},
// HANA/SQLite do not support array-of - turn into CLOB/Text
items: (parent) => {
// OData has no LargeString substitution and doesn't expand types under items
if (!options.toOdata) {
parent.type = 'cds.LargeString';
delete parent.items;
}
},
}, [ (definitions, artifactName, artifact) => {

@@ -526,3 +519,3 @@ // Replace events, actions and functions with simple dummies - they don't have effect on forRelationalDB stuff

// In OData backend there are no aliases assigned when the same as the ref
// TODO: remove the if after the new flattening in OData has been compleated
// TODO: remove the if after the new flattening in OData has been completed
assoc.keys[i].as = assoc.keys[i].ref[assoc.keys[i].ref.length - 1];

@@ -706,3 +699,3 @@ collector.push(assoc.keys[i]);

// unwind a derived type chain to a scalar type
while (finalElement.type && !isBuiltinType(finalElement.type)) {
while (finalElement?.type && !isBuiltinType(finalElement?.type)) {
finalTypeName = finalElement.type;

@@ -714,2 +707,5 @@ finalElement = csn.definitions[finalElement.type];

if (!finalElement)
return [];
if (finalElement.target && !finalElement.on) {

@@ -750,3 +746,3 @@ const hasKeys = !!finalElement.keys;

// we have reached a leaf element, create a foreign key
else if (finalElement && (finalElement.type == null || isBuiltinType(finalElement.type))) {
else if (finalElement.type == null || isBuiltinType(finalElement.type)) {
const newFk = Object.create(null);

@@ -753,0 +749,0 @@ for (const prop of [ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type' ]) {

@@ -7,7 +7,11 @@ 'use strict';

forEachDefinition, applyTransformationsOnNonDictionary, applyTransformationsOnDictionary, implicitAs, cloneCsnNonDict, getUtils,
forEachMemberRecursively,
} = require('../../model/csnUtils');
const { getBranches } = require('./flattening');
const { getColumnMap } = require('./views');
const { checkForeignKeyAccess } = require('../../checks/onConditions');
const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '$scope' ] };
/**

@@ -17,4 +21,2 @@ * Rewrite usage of calculated Elements into the expression itself.

*
* TODO: Calculated elements on-write (`stored: true`)
*
* @param {CSN.Model} csn

@@ -37,3 +39,3 @@ * @param {CSN.Options} options

}
else {
else if (artifact.elements) { // can happen with CSN input
rewriteInEntity(artifact);

@@ -50,4 +52,13 @@ entities.push({ artifact, artifactName });

ref: (_parent, _prop, ref, _path, root, index) => {
if (_parent._art && _parent._art.value) {
root[index] = _parent._art.value;
if (_parent._art?.value && !_parent._art.value.stored) {
if (_parent._art.value.ref) {
// Ensure that we don't break any navigation by only replacing the real element at the end
const leafLength = getLeafLength(_parent._links);
root[index].ref = [ ...root[index].ref.slice(0, -1 * leafLength), ..._parent._art.value.ref ];
setProp(root[index], '_links', [ ...root[index]._links.slice(0, leafLength), ..._parent._art.value._links ]);
setProp(root[index], '_art', _parent._art);
}
else {
root[index] = _parent._art.value;
}
// Note: Depends on A2J rejecting deeply nested filters

@@ -62,3 +73,7 @@ applyTransformationsOnNonDictionary(root, index, {

},
}, { drillRef: true }, [ 'definitions' ]);
}, {
drillRef: true,
// skip "type" to avoid going into type.ref
skipStandard: { type: 1 },
}, [ 'definitions' ]);
});

@@ -83,8 +98,88 @@

// Last pass, turn .value in tables into a simple val: 1 so we don't need to rewrite/flatten properly - will kill them later
/**
* Check that all paths in calculated elements-on write either access normal fields
* (structures are already rejected by the compiler) or access the foreign keys of
* associations. Non-fk fields must be rejected.
* Filters and parameters are not allowed.
*
* Note: This coding is similar to checks/onConditions.js, but does not check $self or other
* ON-condition related stuff.
*
* @param {object} parent
* @param {(string|object)[]} value
* @param {CSN.Path} csnPath
*/
function checkPathsInStoredCalcElement( parent, value, csnPath ) {
const { _links } = parent;
// If there is only one path step, it's been checked before.
for (let i = 0; i < value.length - 1; ++i) {
let hasPathError = false;
const step = value[i];
const stepArt = _links[i].art;
if (stepArt.target) {
const id = step.id || step;
if (stepArt.on) {
// It's an unmanaged association - traversal is always forbidden
error('ref-unexpected-navigation', csnPath, { '#': 'calc-unmanaged', id, elemref: parent });
hasPathError = true;
}
else {
// It's a managed association - access of the foreign keys is allowed
checkForeignKeyAccess(parent, i, csnPath, (errorIndex) => {
error('ref-unexpected-navigation', csnPath, {
'#': 'calc-non-fk', id, elemref: parent, name: value[errorIndex].id || value[errorIndex],
});
hasPathError = true;
});
}
}
if (typeof step === 'object') {
if (step.where) {
error('ref-unexpected-filter', csnPath, { '#': 'calc', elemref: parent });
hasPathError = true;
}
if (step.args) {
error('ref-unexpected-args', csnPath, { '#': 'calc', elemref: parent });
hasPathError = true;
}
}
if (hasPathError)
break; // avoid too many consequent errors
}
}
// Last pass, turn .value in tables into a simple 'val' so we don't need to rewrite/flatten properly - will kill them later
entities.forEach(({ artifact, artifactName }) => {
dummifyInEntity(artifact, [ 'definitions', artifactName ]);
forEachMemberRecursively({ elements: artifact.elements }, (element, elementName, _prop, path) => {
if (element.value?.stored) {
applyTransformationsOnNonDictionary(element, 'value', {
ref(parent, prop, value, csnPath) {
checkPathsInStoredCalcElement(parent, value, csnPath);
},
}, {}, path);
}
}, [ 'definitions', artifactName ]);
});
/**
* Get the length of the effective leaf - since structures are not flat yet it might be more than 1.
* Walk from the back until we find the first association/composition.
*
* @param {object[]} links
* @returns {number}
*/
function getLeafLength( links ) {
for (let i = links.length - 1; i >= 0; i--) {
const { art } = links[i];
if (art.target)
return links.length - i - 1;
}
return links.length;
}
/**
* Rewrite calculated-elements-columns in views/projections and replace them

@@ -182,7 +277,29 @@ * with their "root"-expression.

function rewriteInEntity( artifact ) {
let reorderElements = false;
applyTransformationsOnDictionary(artifact.elements, {
value: (parent, prop, value) => {
replaceValuesWithBaseValue(parent, value);
if (value.stored)
reorderElements = true;
replaceValuesWithBaseValue(parent);
},
});
// on-write must appear at the end of the elements. Order of the on-write between themselves
// should be as written.
if (reorderElements) {
const newElements = Object.create(null);
const onWrite = [];
for (const name in artifact.elements) {
const element = artifact.elements[name];
if (element.value?.stored)
onWrite.push(name);
else
newElements[name] = element;
}
// Add the on-write to the end
onWrite.forEach((name) => {
newElements[name] = artifact.elements[name];
});
artifact.elements = newElements;
}
}

@@ -195,10 +312,9 @@

*
* @param {object | Array} parent
* @param {object} value
* @param {object} parent
*/
function replaceValuesWithBaseValue( parent, value ) {
if (value.val && parent.value === value)
return;
function replaceValuesWithBaseValue( parent ) {
if (parent.value.val !== undefined)
return; // literal; no need to traverse
const stack = [ { parent, value } ];
const stack = [ { parent, value: parent.value } ];
while (stack.length > 0) {

@@ -220,21 +336,16 @@ const current = stack.pop();

}
else if (current.value.ref && current.value._art?.value) {
// TODO: Check for calculated elements on-write
else if (current.value.ref && current.value._art?.value && !current.value._art?.value.stored) {
const linksBase = current.value._links;
const refBase = current.value.ref;
const parentIndex = Array.isArray(current.parent) ? current.parent.indexOf(current.value) : -1;
replaceInRef(current.parent, current.value._art.value, current.isInXpr, refBase, linksBase, parentIndex);
stack.push(Object.assign(current, {
value: parentIndex > -1 ? current.parent[parentIndex] : current.parent.value,
refBase,
linksBase,
}));
const newValue = replaceInRef(current.value, current.value._art.value, current.isInXpr, refBase, linksBase);
const prop = Array.isArray(current.parent) ? current.parent.indexOf(current.value) : 'value';
if (prop === -1)
throw new CompilerAssertion('Calculated Elements: Value not in parent; should never happen!');
current.parent[prop] = newValue;
stack.push(Object.assign(current, { value: newValue, refBase, linksBase }));
}
// No need for cloning here, as we don't rewrite this further and will later on kill all the stuff anyway
else if (current.value.val) { // this is the base case - or a ref to a non-calculated element
else if (current.value.val) {
if (current.isInXpr) { // inside of expressions we directly need the val
current.parent.val = current.value.val;
delete current.parent.value;
delete current.parent.value; // TODO: current.parent could be an array!
}

@@ -255,3 +366,3 @@ else { // outside of expressions, i.e. as normal elements, we need it in a .value wrapper

*
* @param {object} parent
* @param {object} oldValue
* @param {object} newValue

@@ -261,9 +372,9 @@ * @param {boolean} isInXpr

* @param {Array} linksBase
* @param {number} indexInParent
* @returns {object|Array}
*/
function replaceInRef( parent, newValue, isInXpr, refBase, linksBase, indexInParent ) {
delete parent.ref;
const clone = {
value: cloneCsnNonDict({ value: newValue }, cloneCsnOptions).value,
};
function replaceInRef( oldValue, newValue, isInXpr, refBase, linksBase ) {
const clone = { value: cloneCsnNonDict({ value: newValue }, cloneCsnOptions).value };
if (oldValue.stored)
clone.value.stored = oldValue.stored;
const refPrefix = refBase.slice(0, -1);

@@ -282,20 +393,11 @@ const linksPrefix = linksBase.slice(0, -1);

}, {
// Do not rewrite refs inside of an association-where; avoids endless loop
// Do not rewrite refs inside an association-where; avoids endless loop
skipStandard: { where: true },
});
if (indexInParent > -1) // a .xpr in a .xpr
parent[indexInParent] = clone.value;
else
parent.value = clone.value;
}
else {
if (indexInParent > -1) // a .ref in a .xpr
parent[indexInParent] = clone.value;
else
parent.value = clone.value;
if (clone.value.ref && clone.value.ref[0] !== '$self' && clone.value.ref[0] !== '$projection' ) {
clone.value.ref = [ ...refPrefix, ...clone.value.ref ];
clone.value._links = [ ...linksPrefix, ...clone.value._links ]; // TODO: Make non-enum, increment idx
}
else if (clone.value.ref && clone.value.ref[0] !== '$self' && clone.value.ref[0] !== '$projection' ) {
clone.value.ref = [ ...refPrefix, ...clone.value.ref ];
clone.value._links = [ ...linksPrefix, ...clone.value._links ]; // TODO: Make non-enum, increment idx
}
return clone.value;
}

@@ -310,2 +412,3 @@

* @param {object} carrier The thing that will "carry" the columns - .SELECT or .projection
* @returns {Function[]} Cleanup callbacks that remove `_`-links.
*/

@@ -374,2 +477,3 @@ function calculateColumns( elements, carrier ) {

* @param {boolean} containsExpandInline
* @returns {Function[]} Cleanup callbacks that remove `_`-links.
*/

@@ -383,3 +487,3 @@ function makeAllCalculatedElementsExplicitColumns( elements, SELECT, containsExpandInline ) {

let starContainsCalculated = false;
let containsCalculated = false;
let containsCalcOnRead = false;
for (const name in elements) {

@@ -395,6 +499,8 @@ const originalRef = columnMap[name] && columnMap[name].ref || [ name ];

const branches = getBranches(element, name, effectiveType, pathDelimiter); // TODO: is our elements[name] really the root[name]?
if (hasCalculatedLeaf(branches)) {
containsCalculated = true;
if (hasCalcOnReadLeaf(branches)) {
containsCalcOnRead = true;
const columns = [];
for (const branchName in branches) {
const branch = branches[branchName];
const leafElement = branch.steps[branch.steps.length - 1];
if (columnMap[branchName]) { // Existing column - don't overwrite, we need $env!

@@ -406,3 +512,3 @@ columns.push(columnMap[branchName]);

const column = { ref: [ ...originalRef, ...branches[branchName].ref.slice(1) ], as: branchName };
setProp(column, '_element', element);
setProp(column, '_element', leafElement);
cleanupCallbacks.push(() => delete column._element);

@@ -429,6 +535,6 @@ columns.push(column);

if (containsExpandInline && containsCalculated) {
if (containsExpandInline && containsCalcOnRead) {
error('query-unsupported-calc', SELECT.$path, { '#': 'std' });
}
else if (containsCalculated) {
else if (containsCalcOnRead) {
const newColumns = [];

@@ -443,3 +549,2 @@ if (hasStar && !starContainsCalculated)

SELECT.columns = newColumns;

@@ -451,2 +556,4 @@ }

/**
* Returns true if any leaf node is a calculated element on-read.
* On-write behaves like regular elements, hence they do not count here.
*

@@ -456,7 +563,7 @@ * @param {object} branches

*/
function hasCalculatedLeaf( branches ) {
function hasCalcOnReadLeaf( branches ) {
for (const branchName in branches) {
const branch = branches[branchName].steps;
const leaf = branch[branch.length - 1];
if (hasValue(leaf))
if (hasOnReadValue(leaf))
return true;

@@ -475,3 +582,3 @@ }

*/
function hasValue( baseLeaf ) {
function hasOnReadValue( baseLeaf ) {
const visited = new WeakSet();

@@ -482,3 +589,3 @@ const stack = [ baseLeaf ];

if (!visited.has(leaf)) { // Don't re-process things
if (leaf.value)
if (leaf.value && !leaf.value.stored)
return true;

@@ -601,5 +708,5 @@ else if (leaf._art)

function processCalculatedElementsInEntities( csn ) {
forEachDefinition(csn, (artifact, artifactName) => {
forEachDefinition(csn, (artifact, definitionName) => {
if (artifact.kind === 'entity' && !(artifact.query || artifact.projection))
killInEntity(artifact, [ 'definitions', artifactName ]);
removeDummyValueInEntity(artifact, [ 'definitions', definitionName ]);
});

@@ -617,3 +724,3 @@ }

*/
function killInEntity( artifact, path ) {
function removeDummyValueInEntity( artifact, path ) {
applyTransformationsOnDictionary(artifact.elements, {

@@ -624,3 +731,3 @@ value: (parent, prop, value, p, root) => {

},
}, {}, path);
}, {}, path.concat( 'elements' ));
}

@@ -627,0 +734,0 @@

@@ -7,3 +7,2 @@ 'use strict';

const { implicitAs } = require('../../model/csnRefs');
const { isBetaEnabled } = require('../../base/model');
const { ModelError } = require('../../base/error');

@@ -212,7 +211,7 @@

if (isUnion(queryPath) && options.transformation === 'hdbcds') {
if (isBetaEnabled(options, 'ignoreAssocPublishingInUnion') && doA2J) {
if (doA2J) {
if (elem.keys)
info(null, queryPath, { name: elemName }, 'Managed association $(NAME), published in a UNION, will be ignored');
info('query-ignoring-assoc-in-union', queryPath, { name: elemName, '#': 'managed' });
else
info(null, queryPath, { name: elemName }, 'Association $(NAME), published in a UNION, will be ignored');
info('query-ignoring-assoc-in-union', queryPath, { name: elemName, '#': 'std' });

@@ -219,0 +218,0 @@ elem._ignore = true;

@@ -129,3 +129,3 @@ 'use strict';

// In contrast to EDM, the DB entity may have more than one technical keys but should have idealy exactly one key of type cds.UUID
// In contrast to EDM, the DB entity may have more than one technical keys but should have ideally exactly one key of type cds.UUID
if (keys.length !== 1)

@@ -187,16 +187,23 @@ warning(null, [ 'definitions', artifactName ], 'Entity annotated with “@odata.draft.enabled” should have exactly one key element');

const calcOnWriteElements = [];
// Copy all elements
for (const elemName in artifact.elements) {
const origElem = artifact.elements[elemName];
let elem;
if ((isDeprecatedEnabled(options, '_renderVirtualElements') && origElem.virtual) || !origElem.virtual)
elem = copyAndAddElement(origElem, draftsArtifact, draftsArtifactName, elemName)[elemName];
if (elem) {
// Remove "virtual" - cap/issues 4956
if (elem.virtual)
delete elem.virtual;
if (origElem.value?.stored) {
calcOnWriteElements.push(elemName);
}
else {
let elem;
if ((isDeprecatedEnabled(options, '_renderVirtualElements') && origElem.virtual) || !origElem.virtual)
elem = copyAndAddElement(origElem, draftsArtifact, draftsArtifactName, elemName)[elemName];
if (elem) {
// Remove "virtual" - cap/issues 4956
if (elem.virtual)
delete elem.virtual;
// explicitly set nullable if not key and not unmanaged association
if (!elem.key && !elem.on)
elem.notNull = false;
// explicitly set nullable if not key and not unmanaged association
if (!elem.key && !elem.on)
elem.notNull = false;
}
}

@@ -281,2 +288,4 @@ }

}
calcOnWriteElements.forEach(elemName => copyAndAddElement(artifact.elements[elemName], draftsArtifact, draftsArtifactName, elemName)[elemName]);
}

@@ -283,0 +292,0 @@

@@ -19,2 +19,6 @@ 'use strict';

*
* ATTENTION: generateDrafts propagates annotations from the draft nodes to the
* returns element of the draft actions. Shortcut/Convenience annotations
* are NOT replaced/expanded (eg. @label => @Common.Label).
*
* @param {CSN.Model} csn

@@ -21,0 +25,0 @@ * @param {CSN.Options} options

@@ -54,3 +54,3 @@ 'use strict';

// - Element must not be an 'array of' for OData V2 TODO: move to the validator
// (Linter Candiate, move as hard error into EdmPreproc on V2 generation)
// (Linter Candidate, move as hard error into EdmPreproc on V2 generation)
// - Perform checks for exposed non-abstract entities and views - check media type and

@@ -205,3 +205,3 @@ // key-ness (requires that containers have been identified) (Linter candidate, scenario check)

We should not remove $self prefixes in structured OData to not
interfer with path resolution
interfere with path resolution
*/

@@ -235,8 +235,9 @@ // This must be done before all the draft logic as all

forEachMemberRecursively(def, (member, memberName, propertyName) => {
if (memberName === '' && propertyName === 'params')
return; // ignore "returns" type
// Annotate elements, foreign keys, parameters, etc. with their DB names if requested
// Only these are actually required and don't annotate virtual elements in entities or types
// as they have no DB representation (although in views)
if (options.sqlMapping && typeof member === 'object' && !(member.kind === 'action' || member.kind === 'function') && propertyName !== 'enum' && (!member.virtual || def.query)) {
if (options.sqlMapping && typeof member === 'object' &&
!(member.kind === 'action' || member.kind === 'function') &&
!(propertyName === 'enum' || propertyName === 'returns') &&
(!member.virtual || def.query)) {
// If we have a 'preserved dotted name' (i.e. we are a result of flattening), use that for the @cds.persistence.name annotation

@@ -393,8 +394,8 @@ member['@cds.persistence.name'] = getElementDatabaseNameOf(member._flatElementNameWithDots || memberName, options.sqlMapping, 'hana'); // hana to allow "hdbcds"

// Apply default type facets to each type definition and every member
// But do not apply default string length 5000 (as in DB)
// But do not apply default string length (as in DB)
function setDefaultTypeFacets(def) {
addDefaultTypeFacets(def.items || def, false)
forEachMemberRecursively(def, m=>addDefaultTypeFacets(m.items || m, false));
addDefaultTypeFacets(def.items || def, null)
forEachMemberRecursively(def, m=>addDefaultTypeFacets(m.items || m, null));
if(def.returns)
addDefaultTypeFacets(def.returns.items || def.returns, false);
addDefaultTypeFacets(def.returns.items || def.returns, null);
}

@@ -416,3 +417,3 @@

ref: (node, prop, ref) => {
// remove leading $self when at the begining of a ref
// remove leading $self when at the beginning of a ref
if (ref.length > 1 && ref[0] === '$self')

@@ -419,0 +420,0 @@ node.ref.splice(0, 1);

@@ -34,2 +34,3 @@ 'use strict';

const { ModelError } = require('../base/error');
const { getDefaultTypeLengths } = require('../render/utils/common');

@@ -65,3 +66,3 @@ // By default: Do not process non-entities/views

* - (060) Users of primitive type 'UUID' (which is renamed to 'String' in 000) get length 36'.
* - (070) Default length 5000 is supplied for strings if not specified.
* - (070) Default length N is supplied for strings if not specified.
* - (080) Annotation definitions are ignored (note that annotation assignments are filtered out by toCdl).

@@ -118,2 +119,4 @@ * - (090) Compositions become associations.

const pathDelimiter = (options.sqlMapping === 'hdbcds') ? '.' : '_';
// There is also an explicit default length via options.defaultStringLength
const implicitDefaultLengths = getDefaultTypeLengths(options.sqlDialect);

@@ -166,2 +169,4 @@ let csnUtils;

rewriteCalculatedElementsInViews(csn, options, pathDelimiter, error);
// Needs to happen before tuple expansion, so the newly generated WHERE-conditions have it applied

@@ -191,4 +196,2 @@ handleExists(csn, options, error, inspectRef, initDefinition, dropDefinitionCache);

rewriteCalculatedElementsInViews(csn, options, pathDelimiter, error);
if(doA2J) {

@@ -260,5 +263,7 @@ // Expand a structured thing in: keys, columns, order by, group by

renamePrimitiveTypesAndUuid(val, node, key);
addDefaultTypeFacets(node);
addDefaultTypeFacets(node, implicitDefaultLengths);
},
// HANA/SQLite do not support array-of - turn into CLOB/Text
// no support for array-of - turn into CLOB/Text
// must be done after A2J or compiler checks could change
// (e.g. annotation def checks for arrayed types)
items: (val, node) => {

@@ -581,3 +586,3 @@ node.type = 'cds.LargeString';

forEachMemberRecursively(artifact, (member, memberName, property, path) => {
if (memberName === '' && property === 'params')
if (property === 'returns')
return; // ignore "returns" type

@@ -1156,3 +1161,3 @@ transformCommon(member, memberName, path);

flattenedIndex.push(',');
// ... then add the flattend element name as a single ref
// ... then add the flattened element name as a single ref
flattenedIndex.push({ ref: [ elem ] });

@@ -1159,0 +1164,0 @@ // ... then check if we have to propagate a 'asc'/'desc', omitting the last, which will be copied automatically

@@ -613,4 +613,6 @@ 'use strict';

notFound(name, index) {
if (!ignoreUnknownExtensions)
messageFunctions.message('anno-undefined-art', [ 'extensions', index ], { name })
if (!ignoreUnknownExtensions) {
messageFunctions.message('anno-undefined-art', [ 'extensions', index ],
{ art: name });
}
},

@@ -617,0 +619,0 @@ });

@@ -106,10 +106,10 @@ 'use strict';

const finalBaseType = csnUtils.getFinalTypeInfo(node.type);
if(!finalBaseType) {
if(finalBaseType == null) {
/*
type could not be resolved, set it to null
Today, all type refs must be resolvable,
input validations checkTypeDefinitionHasType, checkElementTypeDefinitionHasType
type could not be resolved, delete type property to be equal to a typeless element
definition. Today, all type refs must be resolvable, input validations
checkTypeDefinitionHasType, checkElementTypeDefinitionHasType
guarantee this. In the future this may change.
*/
node.type = null;
delete node.type;
}

@@ -116,0 +116,0 @@ else {

@@ -29,3 +29,3 @@ 'use strict';

*
* The input CSN may contain edges e(v_rs, v_dns) with v_r element of S_i (v_rs) and v_d not element
* The input CSN may contain edges e(v_rs, v_dns) with v_r element of S_i (v_rs) and v_d not element
* of S_i (v_dns).

@@ -54,3 +54,3 @@ *

* name(v_ds) = name(v_s) + '.' + fallbackschema + name(v_dns);
*
*
* @param {CSN.Model} csn

@@ -165,3 +165,3 @@ * @param {function} whatsMyServiceName

isKey = false;
// in case this was a named type and if the openess does not match the type definition
// in case this was a named type and if the openness does not match the type definition
// expose the type as a new one not changing the original definition.

@@ -168,0 +168,0 @@ if(elements && !!node['@open'] !== !!typeDef['@open'])

@@ -285,2 +285,5 @@ // @ts-nocheck

for(let n in xpr) {
// xpr could be an array with polluted prototype
if (!Object.hasOwnProperty.call(xpr, n))
continue;
const x = xpr[n];

@@ -287,0 +290,0 @@ const isAnno = n[0] === '@' && isSimpleAnnoValue(x);

@@ -69,6 +69,11 @@ 'use strict';

// Try to apply length, precision, scale from options if no type facet is set on the primitive types 'cds.String' or 'cds.Decimal'.
// If 'obj' has primitive type 'cds.String' and no length try to apply length from options if available or set to default 5000.
// if 'obj' has primitive type 'cds.Decimal' try to apply precision, scale from options if available.
function addDefaultTypeFacets(element, defStrLen5k=true) {
/**
* Try to apply length, precision, scale from options if no type facet is set on the primitive types 'cds.String' or 'cds.Decimal'.
* If 'obj' has primitive type 'cds.String' and no length try to apply length from options if available or set to default internalDefaultLengths[type].
* if 'obj' has primitive type 'cds.Decimal' try to apply precision, scale from options if available.
*
* @param {CSN.Element} element
* @param {null|object} [internalDefaultLengths] Either null (no implicit default) or an object `{ 'cds.String': N, 'cds.Binary': N }`.
* */
function addDefaultTypeFacets(element, internalDefaultLengths = null) {
if (!element || !element.type)

@@ -78,16 +83,18 @@ return;

if (element.type === 'cds.String' && element.length === undefined) {
if(options.defaultStringLength) {
if (options.defaultStringLength) {
element.length = options.defaultStringLength;
setProp(element, '$default', true);
}
else if(defStrLen5k)
element.length = 5000;
else if (internalDefaultLengths !== null) {
element.length = internalDefaultLengths[element.type];
}
}
if (element.type === 'cds.Binary' && element.length === undefined) {
if(options.defaultBinaryLength) {
if (options.defaultBinaryLength) {
element.length = options.defaultBinaryLength;
setProp(element, '$default', true);
}
else if(defStrLen5k)
element.length = 5000;
else if(internalDefaultLengths !== null) {
element.length = internalDefaultLengths[element.type];
}
}

@@ -278,3 +285,18 @@ /*

// if at all.
copyAnnotations(elem, flatElem, false);
// When flattening structured elements for OData don't propagate the odata.Type annotations
// as these would falsify the flattened elements. Type facets must be aligned with
// EdmTypeFacetMap defined in edm.js
const excludes = options.toOdata ?
{
'@odata.Type': 1,
'@odata.Scale': 1,
'@odata.Precision': 1,
'@odata.MaxLength': 1,
'@odata.SRID': 1,
'@odata.FixedLength': 1,
'@odata.Collation': 1,
'@odata.Unicode': 1,
} : {};
copyAnnotations(elem, flatElem, false, excludes);
// Copy selected type properties

@@ -419,10 +441,7 @@ const props = ['key', 'virtual', 'masked', 'viaAll'];

// if not more (in client style CSN).
if (typeRef.elements && !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds')) {
if (typeRef.elements && !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds'))
nodeWithType.elements = cloneCsnDictionary(typeRef.elements, options);
delete nodeWithType.type;
}
if (typeRef.items) {
else if (typeRef.items)
nodeWithType.items = cloneCsnNonDict(typeRef.items, options);
delete nodeWithType.type;
}
return;

@@ -457,3 +476,3 @@ }

// Transfer xrefs, that are redirected to the projection
// TODO: shall we remove the transfered elements from the original?
// TODO: shall we remove the transferred elements from the original?
// if (artElem._xref) {

@@ -478,3 +497,3 @@ // setProp(elem, '_xref', artElem._xref.filter(xref => xref.user && xref.user._main && xref.user._main._service == service));

'kind': 'entity',
projection: query.SELECT, // it is important that projetion and query refer to the same object!
projection: query.SELECT, // it is important that projection and query refer to the same object!
elements

@@ -758,2 +777,3 @@ };

action.returns = { type: returnTypeName };
// TODO: What about annotation propagation from return type to `returns`?
}

@@ -928,3 +948,3 @@

* Assigns unconditionally annotation to a node, which means it overwrites already existing annotation assignment.
* Overwritting is when the assignment differs from undefined and null, also when differs from the already set value.
* Overwriting is when the assignment differs from undefined and null, also when differs from the already set value.
* Setting new assignment results false as return value and overwriting - true.

@@ -1125,3 +1145,3 @@ *

const lhsIsVal = (lhs.val !== undefined);
// if ever rhs should be alowed to be a value uncomment this
// if ever rhs should be allowed to be a value uncomment this
const rhsIsVal = (rhs === 'null' /*|| rhs.val !== undefined*/);

@@ -1312,4 +1332,4 @@

function getType(art) {
const effart = effectiveType(art);
return Object.keys(effart).length ? effart : art.type;
const effArt = effectiveType(art);
return Object.keys(effArt).length ? effArt : art.type;
}

@@ -1316,0 +1336,0 @@

{
"root": true,
"plugins": ["sonarjs", "jsdoc"],
"extends": ["../../../.eslintrc-ydkjsi.json", "plugin:sonarjs/recommended", "plugin:jsdoc/recommended"],
"extends": ["plugin:jsdoc/recommended", "../../../.eslintrc-ydkjsi.json", "plugin:sonarjs/recommended"],
"rules": {

@@ -6,0 +6,0 @@ "prefer-const": "error",

@@ -30,3 +30,5 @@ 'use strict';

forEachMemberRecursively(artifact, (element) => {
if (element.value && !element.value?.ref) // calculated elements, but simple references are ignored
// Calculated elements, but simple references are ignored for on-read.
// casts() are also computed. In CSN, they appear next to a `.ref`.
if (element.value && (!element.value.ref || element.value.cast || element.value.stored))
setAnnotationIfNotDefined(element, '@Core.Computed', true);

@@ -99,3 +101,3 @@ }, path);

else if (base.ref) {
let artifact = artifactRef(base);
let artifact = artifactRef.from(base);
if (artifact.target)

@@ -150,3 +152,3 @@ artifact = artifactRef(artifact.target);

/**
* Return whether the given columns element needs to be marked with @Core.Computed.
* Returns true, if the given columns element needs to be annotated with @Core.Computed.
*

@@ -159,5 +161,5 @@ * @param {CSN.Column} column

(
column.xpr || column.list || column.func || column.val !== undefined || column.param ||
column.xpr || column.list || column.func || column.val !== undefined || column['#'] !== undefined || column.param ||
column.SELECT || column.SET ||
column.ref && [ '$at', '$valid', '$now', '$user', '$session' ].includes(column.ref[0])
column.ref && [ '$at', '$valid', '$now', '$user', '$session', '$parameters' ].includes(column.ref[0])
);

@@ -164,0 +166,0 @@ }

'use strict';
const { setProp, isBetaEnabled } = require('../../base/model');
const { setProp } = require('../../base/model');
const shuffleGen = require('../../base/shuffle');

@@ -26,3 +26,2 @@ const { setAnnotationIfNotDefined, makeClientCompatible } = require('./utils');

module.exports = (csn, options) => {
const propagateToReturns = isBetaEnabled( options, 'v4preview' );
const csnUtils = getUtils(csn, 'init-all');

@@ -120,3 +119,3 @@ const {

// the propagation above.
// Currently testMode-only for comparison against client CSN.
// Currently, testMode-only for comparison against client CSN.
if (options.testMode)

@@ -179,6 +178,3 @@ makeClientCompatible(csn);

},
type: ( parent, prop, type, path, grandParent, parentProp ) => {
// annos are not propagated to `returns` (<=v3) and `items`
if (parentProp === 'returns' && !propagateToReturns)
return;
type: ( parent, prop, type ) => {
const annotationsForBuiltinType = extensions[type];

@@ -294,4 +290,5 @@ Object.assign( parent, annotationsForBuiltinType );

forEachValue(params, (param) => {
const propagateToParams = typeof param.type === 'string' ? csn.definitions[param.type]?.kind !== 'entity' : true;
propagateMemberPropsFromOrigin(param, {
items: true, elements: true, enum: true, virtual: true,
'@': !propagateToParams, items: true, elements: true, enum: true, virtual: true,
});

@@ -301,3 +298,6 @@ });

returns: (parent, prop, returns) => {
propagateMemberPropsFromOrigin(returns, { items: true, '@': !propagateToReturns, elements: true });
// Only propagate to `returns` (return parameter) if return type is not an entity.
// If returns.type is an array, it is an element ref. If it's not found, it's likely builtin.
const propagateToParams = typeof returns.type === 'string' ? csn.definitions[returns.type]?.kind !== 'entity' : true;
propagateMemberPropsFromOrigin(returns, { '@': !propagateToParams, items: true, elements: true });
if (returns.target)

@@ -376,3 +376,3 @@ calculateForeignKeys(returns);

function skipMemberPropagation( origin ) {
// For empty members (`{}`), the origin was set in a previous call to `getOrigin(definition)`.
// For empty members (`{}`), the origin was set in a previous call to `getOrigin(definition)`.
return !origin;

@@ -528,3 +528,3 @@ }

const column = getColumn(element);
if (column) {
if (column?.ref) {
const mixin = query.SELECT.mixin[implicitAs(column.ref)] || {};

@@ -562,3 +562,3 @@ copyProperties(mixin, element, getMemberPropagationRuleFor);

const primarySourceRef = getQueryPrimarySource(target.query || target.projection);
const artRef = primarySourceRef ? artifactRef(primarySourceRef) : source;
const artRef = primarySourceRef ? artifactRef.from(primarySourceRef) : source;
if (!artRef.target)

@@ -565,0 +565,0 @@ target[prop] = source[prop];

{
"name": "@sap/cds-compiler",
"version": "3.9.4",
"version": "4.0.0",
"description": "CDS (Core Data Services) compiler and backends",

@@ -62,4 +62,4 @@ "homepage": "https://cap.cloud.sap/",

"engines": {
"node": ">=14"
"node": ">=16"
}
}
# Getting started
<!-- markdownlint-disable MD001 MD022 -->

@@ -5,0 +4,0 @@ ##### Table of Contents

@@ -7,3 +7,3 @@ {

"check-proper-type-of",
"duplicate-autoexposed",
"def-duplicate-autoexposed",
"extend-repeated-intralayer",

@@ -10,0 +10,0 @@ "extend-unrelated-layer",

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc