Socket
Socket
Sign inDemoInstall

@sap/cds-compiler

Package Overview
Dependencies
Maintainers
1
Versions
106
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.0.2 to 3.1.0

doc/API.md

3

bin/.eslintrc.json

@@ -14,4 +14,5 @@ {

"radix": "off",
"no-shadow": "warn"
"no-shadow": "warn",
"global-require": "off"
}
}

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

toSql,
inspect,
};

@@ -429,2 +430,20 @@ const commandsWithoutCompilation = {

function inspect(model) {
const inspectModel = require('../lib/inspect');
if (options.statistics) {
const result = inspectModel.inspectModelStatistics(model, options);
if (result)
console.log(result);
}
if (options.propagation) {
const result = inspectModel.inspectPropagation(model, options, options.propagation);
if (result)
console.log(result);
}
return model;
}
// Display error messages in `err` resulting from a compilation. Also set

@@ -431,0 +450,0 @@ // process.exitCode - process.exit() will force the process to exit as quickly

@@ -10,2 +10,51 @@ # ChangeLog for cds compiler and backends

## Version 3.1.0 - 2022-08-04
### Added
- Extending an artifact with multiple includes in one extend statement is now possible:
`extend SomeEntity with FirstInclude, SecondInclude;`
- Aspects can now have actions and functions, similar to entities. Aspects can be extended by actions as well.
- `cdsc`:
- `toCsn` now supports `--with-locations` which adds a `$location` property to artifacts
- `toHana`/`toSql` now supports `--disable-hana-comments`, which disables rendering of doc-comments for HANA.
- to.hdi/sql/hdbcds: Support FK-access in `ORDER BY` and `GROUP BY`
- to.hdi.migration: Detect an implicit change from `not null` to `null` and render corresponding `ALTER`
### Changed
- compiler: If an unknown file extension is used but the file starts with
an opening curly brace (`{`), it will be parsed as CSN.
- to.edm(x): In V4 containment mode, pull up `@Capabilities` annotations from the containees to the root container (set)
and translate them into corresponding `@Capabilities.NavigationRestrictions`. If a `NavigationRestriction` is already available
for that containment path, capabilities are merged into this path. Capability annotation value paths are prefixed with
the navigation restriction path.
The capability 'pull up' has an effect on entity annotations only. `@Capabilities` assignments on compositions are not pulled
up but rendered to the association type which is important to enable dynamic capabilities on 'to-many' relations and to avoid
ambiguities in entity set capabilities.
- Update OData vocabularies 'Analytics', 'Capabilities', 'Common', 'Core', 'DataIntegration', 'Graph', 'PersonalData', 'UI', 'Validation'.
### Fixed
- Syntax of date/time literals are now checked against ISO 8601. If the format is invalid, a warning is emitted.
- The code completion directly after the `(` for functions with special syntax
now suggests all valid keywords, like for `extract` or `locate_regexpr`.
- compiler:
+ `cast(elem as EnumType)` crashed the compiler.
+ Annotations on sub-elements in query entities were lost during re-compilation.
+ An association's cardinality was lost for associations published in projections.
+ Annotations on indirect action parameters were lost in CSN flavor `gensrc`.
+ If a file's content starts with `{` and if neither file extension is known nor
`fallbackParser` is set, assume the source is CSN.
- all backends: references in `order by` _expressions_ are correctly resolved.
- to.edm(x):
+ Allow cross service references for unmanaged associations and improve warning message for muted associations.
+ Nested `@UI.TextArrangement` has precedence over `@TextArrangement` shortcut annotation for `@Common.Text`.
- to.hdi.migration:
+ Doc comments rendered the _full doc comment_ instead of only the first paragraph, as `to.hdi` does.
+ Respect option `disableHanaComments` when rendering the `ALTER` statements
- to.hdi/sql/hdbcds:
+ Check for invalid usages of `$self` and give helpful errors
+ Correctly resolve association-steps in the from-clause in conjunction with `exists`
## Version 3.0.2 - 2022-07-05

@@ -63,2 +112,18 @@

## Version 2.15.8 - 2022-08-02
### Fixed
- to.edm(x): Nested `@UI.TextArrangement` has precedence over `@TextArrangement` shortcut annotation for `@Common.Text`.
- to.hdi.migration:
+ Respect option `disableHanaComments` when rendering the `ALTER` statements
+ Doc comments rendered the _full doc comment_ instead of only the first paragraph, as `to.hdi` does.
- compiler: An association's cardinality was lost for associations published in projections.
## Version 2.15.6 - 2022-07-26
### Fixed
- Annotations on sub-elements were lost during re-compilation.
## Version 2.15.4 - 2022-06-09

@@ -65,0 +130,0 @@

@@ -11,4 +11,26 @@ # ChangeLog of Beta Features for cdx compiler and backends

## Version 3.0.0 - 2022-XX-YY
## Version 3.1.0 - 2022-08-04
### Added `optionalActionFunctionParameters`
- to.edm(x): Annotate optional function/action parameters with `@Core.OptionalParameter` for OData V4.
An action/function parameter is optional if
1) it is already annotated with `@Core.OptionalParameter` regardless of its definition.
2) it has a default value (including null), regardless of it's nullability
3) it has NO default value but is nullable (the implicit default value is null)
If a mandatory parameter (not null and no default value) appears after an optional
parameter, a warning is raised, Core.OptionalParameter requires that all optional
parameters appear rightmost.
### Added `odataOpenType`
- to.edm(x): Support annotation `@open` on entity and structured type level to declare the corresponding entity/complex type to
be `OpenType=true`. If an open structured type is declared closed (with a falsy annotation value), the corresponding EDM type
is closed as well and suffixed with `_closed` (or `_open` vice versa).
No further checks are performed on possibly open foreign or primary key types nor on eventually bucket elements to store the
additional data.
## Version 3.0.0 - 2022-06-23
### Removed `addTextsLanguageAssoc`

@@ -138,3 +160,3 @@

implicitly redirected when necessary.
See [below for details](#version-1300---20200612).
See [below for details](#version-1300---2020-06-12).

@@ -141,0 +163,0 @@ Nested array types (without intermediate structure types) are not supported.

@@ -14,6 +14,24 @@ # ChangeLog of deprecated Features for cdx compiler and backends

## Version 3.0.0 - 2022-XX-YY
## Version 3.1.0 - 2022-08-04
### Added `autoCorrectOrderBySourceRefs`
When this option is set, calling `compile` auto-corrects direct `order by`
source element references without table alias for SELECT queries by adding the
table alias to the `ref`.
Using this option might lead to surprising results when elements are added to
existing models: `order by` specifications might change their semantics without
any extra messages.
## Version 3.0.0 - 2022-06-23
Version 3 of the cds-compiler removes all v2 deprecated flags.
### Add `eagerPersistenceForGeneratedEntities`
If enabled, the old behavior regarding `@cds.persistence.skip` and `@cds.persistence.exists`
is restored, i.e. these annotations are not copied from parent to generated child entities, nor
is `@cds.persistence.exists` copied to localized convenience views.
### Removed `createLocalizedViews`

@@ -29,2 +47,4 @@

<!-- fully removed with 3.1.0 -->
### Removed `noInheritedAutoexposeViaComposition`

@@ -31,0 +51,0 @@

@@ -411,6 +411,6 @@ /** @module API */

/**
* From the given SQLs, create the the correct result structure.
* From the given SQLs, create the correct result structure.
*
* @param {object} hdbkinds
* @param {CSN.Model} afterImage
* @param {object} hdbkinds Object of hdbkinds (such as `hdbindex`) mapped to dictionary of artifacts.
* @param {CSN.Model} afterImage CSN, used to create correct file names in result structure.
* @returns {object[]} Array of objects, each having: name, suffix and sql

@@ -434,4 +434,4 @@ */

*
* @param {object} deletions
* @param {CSN.Model} beforeImage
* @param {object} deletions Dictionary of deletions, only keys are used.
* @param {CSN.Model} beforeImage CSN used to create correct file names in result structure.
* @returns {object[]} Array of objects, each having: name and suffix - only .hdbtable as suffix for now

@@ -447,4 +447,4 @@ */

*
* @param {object} migrations
* @param {CSN.Model} afterImage
* @param {object} migrations Dictionary of changesets (migrations).
* @param {CSN.Model} afterImage CSN used to create correct file names in result structure.
* @returns {object[]} Array of objects, each having: name, suffix and changeset.

@@ -451,0 +451,0 @@ */

@@ -60,5 +60,3 @@ 'use strict';

'disableHanaComments', // in case of issues with hana comment rendering
'dependentAutoexposed', // deprecated, no effect - TODO: safe to remove?
'longAutoexposed', // deprecated, no effect - TODO: safe to remove?
'localizedWithoutCoalesce', // deprecated version of 'localizedLanguageFallback',
'localizedWithoutCoalesce', // deprecated version of 'localizedLanguageFallback', TODO(v4): Remove option
];

@@ -163,2 +161,3 @@

},
overallOptions, // exported for testing
};

@@ -165,0 +164,0 @@

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

'ref-undefined-element': { severity: 'Error' },
'ref-unknown-var': { severity: 'Info' },
'ref-unknown-var': { severity: 'Info', errorFor: [ 'to.hdbcds', 'to.sql', 'to.hdi', 'to.rename' ] },
'ref-obsolete-parameters': { severity: 'Error', configurableFor: true }, // does not hurt us

@@ -135,2 +135,6 @@ 'ref-undefined-param': { severity: 'Error' },

// '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: true },
'syntax-expected-cardinality': { severity: 'Error' },

@@ -210,3 +214,3 @@ 'syntax-expected-length': { severity: 'Error' },

'api-invalid-option': {
std: 'Option $(NAME) is deprecated! Use SNAPI options instead',
std: 'Option $(NAME) is no longer supported! Use SNAPI options instead',
magicVars: 'Option “magicVars” is no longer supported! Use “variableReplacements” instead. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',

@@ -234,2 +238,6 @@ user: 'Option “variableReplacements” expects “$user” instead of “user”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',

'syntax-anno-ignored': {
std: 'Annotations can\'t be used at prefix references',
doc: 'Doc comments can\'t be used at prefix references',
},
'syntax-unexpected-ellipsis': {

@@ -283,3 +291,3 @@ std: 'Expected no more than one $(CODE)',

},
'syntax-duplicate-annotate': 'You shouldn\'t refer to $(NAME) repeatedly in the same annotate statement',
'syntax-duplicate-annotate': 'You can\'t refer to $(NAME) repeatedly with property $(PROP) in the same annotate statement',
'syntax-duplicate-extend': {

@@ -317,3 +325,3 @@ std: 'You can\'t define and refer to $(NAME) repeatedly in the same extend statement',

'ref-unknown-var': {
std: 'Replacement $(ID) not found'
std: 'No replacement found for special variable $(ID)'
},

@@ -476,3 +484,7 @@ 'ref-unexpected-draft-enabled': 'Composition in draft-enabled entity can\'t lead to another entity with $(ANNO)',

},
'odata-navigation': 'No OData navigation property generated, target $(TARGET) is outside of service $(SERVICE)'
'odata-navigation': {
std: 'No OData navigation property generated, target $(TARGET) is outside of service $(SERVICE)',
oncond: 'No OData navigation property generated for association with arbitrary ON condition and target $(TARGET) outside of service $(SERVICE)'
},
'odata-parameter-order': 'Unexpected mandatory after optional parameter',
}

@@ -479,0 +491,0 @@

@@ -11,3 +11,2 @@ // Functions and classes for syntax messages

const { centralMessages, centralMessageTexts, oldMessageIds } = require('./message-registry');
const { copyPropIfExist } = require('../utils/objectUtils');
const _messageIdsWithExplanation = require('../../share/messages/message-explanations.json').messages;

@@ -139,3 +138,3 @@ const { analyseCsnPath, traverseQuery } = require('../model/csnRefs');

this.location = location;
this.$location = dollarLocation( this.location );
this.$location = { ...this.location, address: undefined };
this.validNames = null;

@@ -157,29 +156,2 @@ if (home) // semantic location, e.g. 'entity:"E"/element:"x"'

/**
* Temporary v1 function to convert an "old-style" location to "new-style".
*
* @param {CSN.Location} location
* @return {CSN.Location}
* @todo Remove
*/
function dollarLocation( location ) {
const file = location && location.file || undefined;
if (!file)
return {};
const loc = {
file,
line: location.line,
col: location.col,
address: undefined,
};
copyPropIfExist(location, 'endLine', loc);
copyPropIfExist(location, 'endCol', loc);
// TODO:
// return {
// ...location,
// address: undefined,
// };
return loc;
}
const severitySpecs = {

@@ -263,10 +235,12 @@ error: { name: 'Error', level: 0 },

/**
* @todo This was copied from somewhere just to make CSN paths work.
* Find the nearest $location for the given CSN path in the model.
* If the path does not exist, the parent is used, and so on.
*
* @param {CSN.Model} model
* @param {CSN.Path} csnPath
* @returns {CSN.Location | null}
*/
function searchForLocation( model, csnPath ) {
function findNearestLocationForPath( model, csnPath ) {
if (!model)
return null;
// Don't display a location if we cannot find one!
let lastLocation = null;

@@ -445,3 +419,3 @@ /** @type {object} */

return [
searchForLocation( model, location ),
findNearestLocationForPath( model, location ),
constructSemanticLocationFromCsnPath( location, model ),

@@ -1094,5 +1068,6 @@ location[1] // location[0] is 'definitions'

function homeSortName( { home, messageId } ) {
return (!home)
? (messageId && /^(syntax|api)-/.test( messageId ) ? ' ' + messageId : '~')
: home.substring( home.indexOf(':') ); // i.e. starting with the ':', is always there
if (!home)
return (messageId && /^(syntax|api)-/.test( messageId ) ? ' ' + messageId : '~')
else
return home.substring( home.indexOf(':') ); // i.e. starting with the ':', is always there
}

@@ -1201,4 +1176,4 @@

// basically resolveUncheckedPath()
const absoluteName = art.name.id ? art.name.id :
(!art.name.element && art.name.absolute || art.name.path.map(s => s && s.id).join('.'));
const absoluteName = art.name.id != null ? art.name.id :
(!art.name.element && art.name.absolute || art.name.path && art.name.path.map(s => s && s.id).join('.'));

@@ -1393,4 +1368,8 @@ // Surrounding parent may be another extension.

result += `:${ _quoted(currentThing.as) }`;
else if (inRef)
result += `:${ _quoted(currentThing) }`;
else if (currentThing.ref)
result += `:${ _quoted(currentThing.ref.map(r => r.id ? r.id : r).join('.')) }`;
else
result += inRef ? `:${ _quoted(currentThing) }` : currentThing.ref ? `:${ _quoted(currentThing.ref.map(r => r.id ? r.id : r).join('.')) }` : '';
return'';

@@ -1397,0 +1376,0 @@ break;

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

nestedServices: false,
odataOpenType: true,
optionalActionFunctionParameters: true,
};

@@ -35,0 +37,0 @@

'use strict';
const { isBuiltinType } = require('../model/csnUtils');
const { isBetaEnabled } = require('../base/model');

@@ -55,9 +56,9 @@ // Only to be used with validator.js - a correct this value needs to be provided!

if (param.default || paramType.default) {
this.error('param-default', currPath, { '#': actKind },
{
std: 'Artifact parameters can\'t have a default value', // Not used
action: 'Action parameters can\'t have a default value',
function: 'Function parameters can\'t have a default value',
});
if (!isBetaEnabled(this.options, 'optionalActionFunctionParameters') && (param.default || paramType.default)) {
this.message('param-default', currPath, { '#': actKind },
{
std: 'Artifact parameters can\'t have a default value', // Not used
action: 'Action parameters can\'t have a default value',
function: 'Function parameters can\'t have a default value',
});
}

@@ -64,0 +65,0 @@

'use strict';
const { forEachGeneric } = require('../model/csnUtils');
const { forEachGeneric, applyTransformationsOnNonDictionary } = require('../model/csnUtils');

@@ -20,17 +20,99 @@ // Only to be used with validator.js - a correct this value needs to be provided!

return;
/**
* Check for a $self.<assoc> in columns etc. - since the $self.<assoc> references the "outside" view
* of the association, this is not allowed.
*
* @param {string} queryPart Part of the query that is being checked
* @returns {Function} Function as callback for applyTransformations
*/
function checkRefForInvalid$Self(queryPart) {
const signalError = (error, parent, type) => {
if (queryPart === 'columns') {
error(null, parent.$path,
{ name: parent.ref[0], type },
'Select items starting with $(NAME) must not contain path steps of type $(TYPE)');
}
else if (queryPart === 'orderBy') {
error(null, parent.$path,
{ id: queryPart, type },
'Items of the $(ID)-clause must not contain path steps of type $(TYPE)');
}
else {
error(null, parent.$path,
{ id: queryPart, name: parent.ref[0], type },
'Items of the $(ID)-clause starting with $(NAME) must not contain path steps of type $(TYPE)');
}
};
return function checkForInvalid$SelfInRef(parent) {
if (parent.ref && (parent.$scope === '$self' || parent.$scope === '$query')) {
const { _links } = parent;
for (let j = parent.$scope === '$self' ? 1 : 0; j < _links.length - 1; j++) {
if (_links[j].art.target) {
if (_links[j].art.on) {
// It's an unmanaged association - traversal is always forbidden
signalError(this.error, parent, _links[j].art.type);
}
else {
// It's a managed association - access of the foreign keys is allowed
const nextRef = parent.ref[j + 1].id || parent.ref[j + 1];
if (!_links[j].art.keys.some(r => r.ref[0] === nextRef))
signalError(this.error, parent, _links[j].art.type);
}
}
}
forEachGeneric(SELECT, 'columns', (selectItem) => {
if (selectItem.ref && (selectItem.ref[0] === '$self' || selectItem.ref[0] === '$projection')) {
const pathStepWithTarget = selectItem._links.slice(1).find(link => link.art.target);
if (pathStepWithTarget) {
this.error(null, selectItem.$path,
{ name: selectItem.ref[0], type: pathStepWithTarget.art.type },
'Select items starting with $(NAME) must not contain path steps of type $(TYPE)');
const last = _links[_links.length - 1];
if (last.art.target && last.art.on) {
// It's an unmanaged association - traversal is always forbidden
signalError(this.error, parent, last.art.type);
} // managed is okay, can be handled via tuple expansion
}
}
else if (this.options.transformation === 'hdbcds' && selectItem.xpr && selectItem.func) {
this.error(null, selectItem.$path,
'Window functions are not supported by SAP HANA CDS');
}
});
};
}
/**
* Check the given assoc filter for usage of $self - in an assoc-filter, you must only
* address things on the target side of the association, not from global scope.
*
* @param {object} parent
* @param {string} prop
* @param {Array} where
*/
function checkFilterForInvalid$Self(parent, prop, where) {
where.forEach((whereStep) => {
if (whereStep.ref && ( whereStep.ref[0] === '$projection' || whereStep.ref[0] === '$self')) {
this.error('expr-where-unexpected-self', whereStep.$path,
{ name: whereStep.ref[0] },
'Path steps inside of filters must not start with $(NAME)');
}
});
}
const aTCB = (parent, prop) => {
applyTransformationsOnNonDictionary(parent, prop, {
ref: checkRefForInvalid$Self(prop).bind(this),
where: checkFilterForInvalid$Self.bind(this),
}, { skipStandard: { on: true }, drillRef: true });
};
const transformers = {
columns: aTCB,
groupBy: aTCB,
orderBy: aTCB,
having: aTCB,
where: aTCB,
};
if (this.options.transformation === 'hdbcds') {
transformers.xpr = (parent) => {
if (parent.func) {
this.error(null, parent.$path,
'Window functions are not supported by SAP HANA CDS');
}
};
}
applyTransformationsOnNonDictionary(query, 'SELECT', transformers );
// .call() with 'this' to ensure we have access to the options

@@ -37,0 +119,0 @@ rejectManagedAssociationsAndStructuresForHdbcdsNames.call(this, SELECT, SELECT.$path);

'use strict';
const { getUtils, isBuiltinType, hasAnnotationValue } = require('../model/csnUtils');
const { getUtils, hasAnnotationValue } = require('../model/csnUtils');

@@ -58,3 +58,3 @@ // Only to be used with validator.js - a correct this value needs to be provided!

if (!hasArtifactTypeInformation(member)) {
warnAboutMissingType(this.error, path, memberName, true);
errorAboutMissingType(this.error, path, memberName, true);
return;

@@ -100,3 +100,3 @@ }

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

@@ -162,5 +162,4 @@ }

* @param {boolean} isElement indicates whether we are dealing with an element or an artifact
* @todo Rename, is an error not a warning
*/
function warnAboutMissingType(error, path, name, isElement = false) {
function errorAboutMissingType(error, path, name, isElement = false) {
error('check-proper-type', path, { art: name, '#': isElement ? 'elm' : 'std' }, {

@@ -180,8 +179,6 @@ std: 'Dubious type $(ART) without type information',

* @returns {boolean} indicates whether the artifact has type information
* @todo What is the point of isBuiltinType here if we check for artifact.type at the end?
*/
function hasArtifactTypeInformation(artifact) {
// When is what property set?
return isBuiltinType(artifact.type) || // => `Integer`
artifact.elements || // => `type A {}`
return artifact.elements || // => `type A {}`
artifact.items || // => `type A : array of Integer`

@@ -188,0 +185,0 @@ artifact.enum || // => `type A : Integer enum {}`, `type` also set

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

const nonexpandableStructuredInExpression = require('./nonexpandableStructured');
const unknownMagic = require('./unknownMagic');
const managedWithoutKeys = require('./managedWithoutKeys');

@@ -66,3 +65,3 @@ const {

const forHanaCsnValidators = [ nonexpandableStructuredInExpression, unknownMagic ];
const forHanaCsnValidators = [ nonexpandableStructuredInExpression ];
/**

@@ -69,0 +68,0 @@ * @type {Array<(query: CSN.Query, path: CSN.Path) => void>}

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

'$blocks',
'$newfeatures',
'$messageFunctions',

@@ -147,3 +146,2 @@ '$functions',

$frontend: { parser: true, test: isString, enum: [ 'cdl', 'json', 'xml' ] },
$newfeatures: { test: TODO }, // if new features have been used which break the old backends
messages: {

@@ -180,3 +178,2 @@ enumerable: () => true, // does not matter (non-enum std), enum in CSN/XML parser

},
_assocSources: { kind: true, test: TODO }, // just null: isArray( inDefinitions ) during resolve
$magicVariables: {

@@ -262,2 +259,3 @@ // $magicVariables contains "builtin" artifacts that differ from

'$tableAliases', 'kind', '_$next', '_combined', '$inlines', '_status',
'_extension', // for unapplied extensions
],

@@ -382,2 +380,3 @@ },

$parens: { parser: true, test: TODO },
$prefix: { test: isString }, // compiler-corrected path prefix
$syntax: {

@@ -389,3 +388,10 @@ parser: true,

value: {
optional: [ 'location', '$inferred', 'sort', 'nulls' ],
optional: [
'location', '$inferred', 'sort', 'nulls',
'param', 'scope', // for dynamic parameter '?'
// through cast() with enum through CSN->XSN
// TODO: re-check #9225, this should be directly in the query element,
// not inside value, no `enum` inside `cast`!
'elements', 'items', 'enum', '$expand', 'target',
],

@@ -448,3 +454,6 @@ kind: true,

inherits: 'value',
optional: [ 'name', '$duplicate', '$expected', 'args', 'suffix' ],
optional: [
'name', '$duplicate', '$expected', 'args', 'suffix',
'param', 'scope', // for dynamic parameter '?'
],
test: args,

@@ -466,3 +475,3 @@ },

},
$priority: { test: TODO },
$priority: { test: isOneOf([ undefined, false, 'extend', 'annotate' ]) },
$annotations: { parser: true, kind: true, test: TODO }, // deprecated, still there for cds-lsp

@@ -486,3 +495,3 @@ name: {

action: { test: isString },
param: { test: isString },
param: { test: TODO },
alias: { test: isString },

@@ -540,3 +549,2 @@ expectedKind: { kind: [ 'extend' ], inherits: 'kind' },

_artifact: { test: TODO },
_base: { test: TODO, kind: true },
_navigation: { test: TODO },

@@ -565,2 +573,3 @@ _effectiveType: { kind: true, test: TODO },

'limit', '_status',
'_extension', // for unapplied extensions
],

@@ -602,14 +611,50 @@ },

$extension: { kind: true, test: TODO }, // TODO: introduce $applied instead or $status
$inferred: { parser: true, kind: true, test: isString },
$inferred: {
parser: true,
kind: true,
test: isOneOf([
// Uppercase values are used in logic, lowercase value are "just for us", i.e.
// debugging or to add non-enumerable properties such as $generated in Universal CSN.
// However, that is no longer true. For example, `autoexposed` is used in populate.js
// as well.
'IMPLICIT',
'REDIRECTED',
'$autoElement', // for magicVars: $user is automatically changed to $user.id
'$generated', // compiler generated annotations, e.g. @Core.Computed
'*', // inferred from query wildcard
'as', // query alias name
'aspect-composition',
'autoexposed', // for auto-exposed entities (they can't be referred to)
'cast', // type from cast() function
'composition-entity',
'copy', // only used in rewriteCondition(): On-condition is copied
'duplicate-autoexposed', // just like `autoexposed`, but with `duplicate` error.
'expand-element', // expanded elements
'expand-param', // expanded params (difference to expand-element only for debugging)
'include', // through includes, e.g. `entity E : F {}`
'keys',
'localized', // e.g. compiler-generated elements for localized: `text` assoc, etc.
'localized-entity', // `.texts` entity
'nav', // only used for MASKED, TODO(v4): Remove
'none', // only used in ensureColumnName(): Used in object representing empty alias
'query', // inferred query properties, e.g. `key`
'rewrite', // on-conditions or FKeys are rewritten
]),
},
// Helper property for the XSN-to-CSN transformation, see function setExpandStatus():
// client, universal: render expanded elements? gensrc: produce annotate statements?
$expand: { kind: true, test: isString }, // TODO: rename it to $elementsExpand ?
// TODO: rename it to $elementsExpand ?
$expand: {
kind: true,
// See description of `setExpandStatus()` of in `lib/compiler/utils.js`.
test: isOneOf([ 'origin', 'annotate', 'target' ]),
},
$autoexpose: { kind: [ 'entity' ], test: isBoolean, also: [ null, 'Composition' ] },
$a2j: { kind: true, enumerable: true, test: TODO },
$extra: { parser: true, test: TODO }, // for unexpected properties in CSN
$withLocalized: { test: isBoolean },
$sources: { parser: true, test: isArray( isString ) },
$expected: { parser: true, test: isString },
$expected: { parser: true, test: isOneOf([ 'approved-exists', 'exists' ]) },
$messageFunctions: { test: TODO },

@@ -874,2 +919,9 @@ $functions: { test: TODO },

function isOneOf(values) {
return function isOneOfInner( node, parent, prop ) {
if (!values.includes(node))
throw new Error( `Unexpected value '${ node }', expected ${ JSON.stringify(values) }${ at( [ node, parent ], prop ) }` );
};
}
function isString( node, parent, prop, spec ) {

@@ -876,0 +928,0 @@ if (typeof node !== 'string')

@@ -19,3 +19,5 @@ // Base Definitions for the Core Compiler

service: { artifacts: true, normalized: 'namespace' },
entity: { elements: true, actions: true, params: () => false },
entity: {
elements: true, actions: true, params: () => false, include: true,
},
select: { normalized: 'select', elements: true },

@@ -27,5 +29,5 @@ $join: { normalized: 'select' },

$inline: { normalized: 'element' }, // column with inline property
event: { elements: true },
type: { elements: propExists, enum: propExists },
aspect: { elements: propExists },
event: { elements: true, include: true },
type: { elements: propExists, enum: propExists, include: true },
aspect: { elements: propExists, actions: true, include: true },
annotation: { elements: propExists, enum: propExists },

@@ -32,0 +34,0 @@ enum: { normalized: 'element' },

@@ -157,8 +157,10 @@ // The builtin artifacts of CDS

};
for (const generic of [ 'intro', 'expr', 'separator' ]) { // intro before expr!
for (const generic of [ 'intro', 'expr', 'separator' ]) {
// intro before expr: if both intro and expr, tag as 'expr'
for (const token of src[generic] || [])
tgt[token] = generic;
}
if (tgt.intro) // same token could be in both 'expr' and 'intro':
tgt.introMsg = src.introMsg || tgt.intro.filter( token => tgt[token] === 'intro' );
// As GenericIntro is always together with GenericExpr, only mention those
// which are not already proposed for GenericExpr:
tgt.introMsg = src.introMsg || tgt.intro.filter( token => tgt[token] === 'intro' );
return tgt;

@@ -200,5 +202,4 @@ }

* `prefix` argument of function `quotedliteral` to the following properties:
* - `test_msg`: error message which is issued if `test_fn` or `test_re` fail.
* - `test_msg`: error message which is issued if `test_fn` fails.
* - `test_fn`: function called with argument `value`, fails falsy return value
* - `test_re`: regular expression, fails if it does not match argument `value`
* - `unexpected_msg`: error message which is issued if `unexpected_char` matches

@@ -210,2 +211,4 @@ * - `unexpected_char`: regular expression matching an illegal character in `value`,

* but always allow Feb 29 (no leap year computation)
* Notes:
* - Dates/Times as defined in ISO 8601, see <https://en.wikipedia.org/wiki/ISO_8601>
*/

@@ -222,3 +225,7 @@ const quotedLiteralPatterns = {

test_variant: 'time',
test_re: /^[0-9]{1,2}:[0-9]{1,2}(:[0-9]{1,2})?$/,
test_fn: (x) => {
// Leading `T` allowed in ISO 8601.
const match = x.match( /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/ );
return match !== null && checkTime( match[1], match[2], match[3] );
},
json_type: 'string',

@@ -228,3 +235,6 @@ },

test_variant: 'date',
test_re: /^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/,
test_fn: (x) => {
const match = x.match( /^(-?\d{4})-(\d{1,2})-(\d{1,2})$/ );
return match !== null && checkDate( match[1], match[2], match[3] );
},
json_type: 'string',

@@ -234,3 +244,8 @@ },

test_variant: 'timestamp',
test_re: /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}(:[0-9]{2}(\.[0-9]{1,7})?)?$/,
test_fn: (x) => {
// eslint-disable-next-line max-len
const match = x.match( /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?$/ );
return match !== null && checkDate( match[1], match[2], match[3] ) &&
checkTime( match[4], match[5], match[6] );
},
json_type: 'string',

@@ -240,2 +255,37 @@ },

/**
* Check that the given date is within boundaries.
* We can't use Date.parse() since that also allows non-standard values (2022-02-31 for example).
* Checks according to ISO 8601.
*
* @returns {boolean} True if the date is valid.
*/
function checkDate(year, month, day) {
// Negative years are allowed
year = Math.abs(Number.parseInt(year, 10));
month = Number.parseInt(month, 10);
day = Number.parseInt(day, 10);
// If any is NaN, the condition will be false.
// Year 0 does not exist, but ISO 8601 allows it and defines it as 1 BC.
return !Number.isNaN(year) && month > 0 && month < 13 && day > 0 && day < 32;
}
/**
* Check that the given time is within boundaries.
* Checks according to ISO 8601.
*
* @returns {boolean} True if the date is valid.
*/
function checkTime(hour, minutes, seconds) {
hour = Number.parseInt(hour, 10);
minutes = Number.parseInt(minutes, 10);
seconds = seconds ? Number.parseInt(seconds, 10) : 0;
if (hour === 24) // allow 24:00:00 (ISO 8601 version earlier than 2019)
return minutes === 0 && seconds === 0;
// If any is NaN, the condition will be false.
return hour >= 0 && hour < 24 &&
minutes >= 0 && minutes < 60 &&
seconds >= 0 && seconds < 61; // we allow 60 for lead seconds
}
/** All types belong to one category. */

@@ -242,0 +292,0 @@ const typeCategories = {

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

// TODO: make this part of the the name resolution in the compiler
// TODO: make this part of the name resolution in the compiler
// Check that queries in 'art' do not contain unmanaged associations in GROUP BY or ORDER BY

@@ -420,0 +420,0 @@ function checkNoUnmanagedAssocsInGroupByOrderBy( query ) {

@@ -112,3 +112,3 @@ // Compiler phase "define": transform dictionary of AST-like CSNs into augmented CSN

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

@@ -151,2 +151,3 @@ dictAdd, dictAddArray, dictForEach, pushToDict,

resolveUncheckedPath,
checkAnnotate,
defineAnnotations,

@@ -202,2 +203,3 @@ } = model.$functions;

// Phase 1: ----------------------------------------------------------------
// Functions called from top-level: addSource()

@@ -207,3 +209,3 @@ /**

*
* @param {XSN.AST} src
* @param {XSN.SourceAst} src
*/

@@ -273,3 +275,3 @@ function addSource( src ) {

setLink( art, '_block', block );
// dictAdd might set $duplicates to true
// dictAdd might set $duplicates
dictAdd( model.definitions, absolute, art );

@@ -314,3 +316,3 @@ }

* @param {XSN.Using} decl Node to be expanded and added to `src`
* @param {XSN.AST} src
* @param {XSN.SourceAst} src
*/

@@ -404,5 +406,8 @@ function addUsing( decl, src ) {

// Phase 2 ("init") --------------------------------------------------------
// Functions called from top-level: initNamespaceAndUsing(), initArtifact(),
// initVocabulary()
function checkRedefinition( art ) {
if (!art.$duplicates || art.$errorReported === 'syntax-duplicate-extend')
if (!art.$duplicates || art.$errorReported === 'syntax-duplicate-extend' ||
art.$errorReported === 'syntax-duplicate-annotate')
return;

@@ -514,14 +519,2 @@ if (art._main) {

}
if (art.kind !== 'namespace' &&
isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' )) {
let p = parent;
while (p && kindProperties[p.kind].artifacts)
p = p._parent;
if (p) {
error( 'subartifacts-not-supported', [ art.name.location, art ],
{ art: p, prop: 'deprecated._generatedEntityNameWithUnderscore' },
// eslint-disable-next-line max-len
'With the option $(PROP), no sub artifact can be defined for a non-context/service $(ART)' );
}
}
setLink( art, '_parent', parent );

@@ -622,3 +615,13 @@ if (!parent._subArtifacts)

setLink( col, '_block', parent._block );
defineAnnotations( col, col, parent._block ); // TODO: complain with inline
defineAnnotations( col, col, parent._block );
if (col.inline) { // `@anno elem.{ * }` does not work
if (col.doc)
warning( 'syntax-anno-ignored', [ col.doc.location, col ], { '#': 'doc' } );
// col.$annotations no available for CSN input, have to search.
// Warning about first annotation should be enough to avoid spam.
const firstAnno = Object.keys(col).find(key => key.startsWith('@'));
if (firstAnno)
warning( 'syntax-anno-ignored', [ col[firstAnno].name.location, col ] );
}
// TODO: allow sub queries? at least in top-level expand without parallel ref

@@ -894,5 +897,3 @@ if (columns)

* Set property `_parent` for all elements in `parent` to `parent` and do so
* recursively for all sub elements. Also set the property
* `name.component` of the element with the help of argument `prefix`
* (which is basically the component name of the `parent` element plus a dot).
* recursively for all sub elements.
*/

@@ -1029,2 +1030,4 @@ // If not for extensions: construct === parent

checkRedefinition( elem );
if (elem.kind === 'annotate')
checkAnnotate( elem, elem );
defineAnnotations( elem, elem, bl );

@@ -1141,3 +1144,3 @@ initMembers( elem, elem, bl, initExtensions );

*
* @param {XSN.AST} src
* @param {XSN.SourceAst} src
*/

@@ -1144,0 +1147,0 @@ function initI18nFromSource( src ) {

@@ -36,2 +36,3 @@ // Extend, include, localized data and managed compositions

resolveUncheckedPath,
checkAnnotate,
defineAnnotations,

@@ -183,2 +184,6 @@ attachAndEmitValidNames,

checkDefinitions( ext, art, 'columns');
if (ext.includes)
applyIncludes( ext, art ); // emits error if `includes` is set
if (ext.kind === 'annotate')
checkAnnotate( ext, art );
defineAnnotations( ext, art, ext._block, ext.kind );

@@ -243,2 +248,4 @@ }

}
if (ext.kind === 'annotate')
checkAnnotate( ext, art );
defineAnnotations( ext, art, ext._block, ext.kind );

@@ -401,2 +408,3 @@ // TODO: do we allow to add elements with array of {...}? If yes, adapt

if (art.kind === 'namespace') {
// TODO: Emit error if namespace is extended by non-definitions.
info( 'anno-namespace', [ ext.name.location, ext ], {},

@@ -461,2 +469,8 @@ 'Namespaces can\'t be annotated' );

function applyIncludes( ext, art ) {
if (kindProperties[art.kind].include !== true) {
error('extend-unexpected-include', [ ext.includes[0]?.location, ext ], { kind: art.kind },
'Can\'t extend $(KIND) with includes');
return;
}
if (!art._ancestors)

@@ -523,5 +537,3 @@ setLink( art, '_ancestors', [] ); // recursive array of includes

const textsName = (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
? `${ art.name.absolute }_texts`
: `${ art.name.absolute }.texts`;
const textsName = `${ art.name.absolute }.texts`;
const textsEntity = model.definitions[textsName];

@@ -669,4 +681,2 @@ const localized = localizedData( art, textsEntity, fioriEnabled );

}
if (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
setLink( art, '_base', base );

@@ -844,5 +854,3 @@ dictAdd( art.elements, 'locale', locale );

return;
const entityName = (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
? `${ base.name.absolute }_${ elem.name.id }`
: `${ base.name.absolute }.${ elem.name.id }`;
const entityName = `${ base.name.absolute }.${ elem.name.id }`;
const entity = allowAspectComposition( target, elem, keys, entityName ) &&

@@ -979,4 +987,2 @@ createTargetEntity( target, elem, keys, entityName, base );

}
if (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
setLink( art, '_base', base._base || base );

@@ -983,0 +989,0 @@ dictAdd( art.elements, 'up_', up);

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

// define.js takes care that `target` is a ref and
// `targetAspect` is a structure.
if (artifact.target)
resolveUncheckedPath(artifact.target, 'target', main);
if (artifact.targetAspect)
resolveTypesForParseCdl(artifact.targetAspect, main);

@@ -97,8 +101,2 @@ if (artifact.from) {

if (artifact.targetAspect) {
if (artifact.targetAspect.path)
resolveUncheckedPath(artifact.targetAspect, 'target', main);
resolveTypesForParseCdl(artifact.targetAspect, main);
}
// Recursively go through all XSN properties. There are a few that need to be

@@ -163,4 +161,2 @@ // handled specifically. Refer to the code below this loop for details.

function resolveTypeUnchecked(artWithType, user) {
if (!artWithType.type)
return;
const root = artWithType.type.path && artWithType.type.path[0];

@@ -193,3 +189,3 @@ if (!root) // parse error

struct = struct._parent;
if (struct.kind === 'select' || struct !== user._main) {
if (struct.kind === 'select' || struct.kind === 'annotation' || struct !== user._main) {
message( 'type-unexpected-typeof', [ artWithType.type.location, user ],

@@ -196,0 +192,0 @@ { keyword: 'type of', '#': struct.kind } );

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

return parseLanguage( source, filename, options, messageFunctions );
if (source.startsWith('{')) // Source may be JSON.
return parseCsn.parse( source, filename, options, messageFunctions );

@@ -89,0 +91,0 @@ const model = { location: { file: filename } };

@@ -76,10 +76,4 @@ // Populate views with elements, elements with association targets, ...

// behavior depending on option `deprecated`:
const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
// TODO: we should get rid of noElementsExpansion soon; both
// beta.nestedProjections and beta.universalCsn do not work with it.
const scopedRedirections
= enableExpandElements &&
!isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ) &&
!isDeprecatedEnabled( options, '_shortAutoexposed' ) &&
= !isDeprecatedEnabled( options, '_shortAutoexposed' ) &&
!isDeprecatedEnabled( options, '_longAutoexposed' ) &&

@@ -113,2 +107,4 @@ !isDeprecatedEnabled( options, '_noInheritedAutoexposeViaComposition' ) &&

environment( art );
if (art.elements$)
mergeSpecifiedElements(art);
forEachMember( art, traverseElementEnvironments );

@@ -170,3 +166,3 @@ }

while (art && !('_effectiveType' in art) &&
(art.type || art._origin || art.value && art.value.path) &&
(art.type || art._origin || art.value?.path || art.value?.type) &&
// TODO: really stop at art.enum? See #8942

@@ -201,3 +197,3 @@ !art.target && !art.enum && !art.elements && !art.items) {

eType = effectiveType( eType._outer );
// collect the "latest" cardinality (calculate lazyly if necessary)
// collect the "latest" cardinality (calculate lazily if necessary)
let cardinality = art.cardinality ||

@@ -229,6 +225,8 @@ art._effectiveType && (() => getCardinality( art._effectiveType ));

return resolveType( art.type, art );
if (art.value?.type)
return resolveType( art.value.type, art );
// console.log( 'EXPR-IN', art.kind, refString(art.name) )
if (!art._main || !art.value || !art.value.path)
return undefined;
if (art._pathHead && art.value) {
if (art._pathHead && art.value.path) {
setLink( art, '_origin', resolvePath( art.value, 'expr', art, null ) );

@@ -303,3 +301,3 @@ return art._origin;

function expandItems( art, origin, eType ) {
if (!enableExpandElements || art.items)
if (art.items)
return false;

@@ -321,4 +319,2 @@ if (isInParents( art, eType )) {

function expandElements( art, struct, eType ) {
if (!enableExpandElements)
return false;
if (art.elements || art.kind === '$tableAlias' ||

@@ -357,3 +353,3 @@ // no element expansions for "non-proper" types like

function expandEnum( art, origin ) {
if (!enableExpandElements || art.enum)
if (art.enum)
return false;

@@ -438,4 +434,2 @@ const ref = art.type || art.value || art.name;

traverseQueryPost( view.query, null, populateQuery );
if (view.elements$) // specified elements
mergeSpecifiedElements( view );
if (!view.$entity) {

@@ -449,10 +443,21 @@ model._entities.push( view );

function mergeSpecifiedElements( view ) {
/**
* Merge _specified_ elements with _inferred_ elements in the given view/element,
* where specified elements can appear through CSN.
*
* We only copy annotations, since they are not part of `columns`,
* but only appear in `elements` in CSN.
*
* This is important to ensure re-compilability.
*
* @param art
*/
function mergeSpecifiedElements( art ) {
// Later we use specified elements as proxies to inferred of leading query
// (No, we probably do not.)
for (const id in view.elements) {
const ielem = view.elements[id]; // inferred element
const selem = view.elements$[id]; // specified element
for (const id in art.elements) {
const ielem = art.elements[id]; // inferred element
const selem = art.elements$[id]; // specified element
if (!selem) {
info( 'query-missing-element', [ ielem.name.location, view ], { id },
info( 'query-missing-element', [ ielem.name.location, art ], { id },
'Element $(ID) is missing in specified elements' );

@@ -467,6 +472,10 @@ }

selem.$replacement = true;
if (selem.elements) {
setLink(ielem, 'elements$', selem.elements);
delete selem.elements;
}
}
}
for (const id in view.elements$) {
const selem = view.elements$[id]; // specified element
for (const id in art.elements$) {
const selem = art.elements$[id]; // specified element
if (!selem.$replacement) {

@@ -488,4 +497,2 @@ error( 'query-unspecified-element', [ selem.name.location, selem ], { id },

initFromColumns( query, query.columns );
// TODO: already in definer: complain about EXCLUDING with no wildcard
// (would have been automatically with a good CDL syntax: `* without (...)`)
if (query.excludingDict) {

@@ -1054,4 +1061,2 @@ for (const name in query.excludingDict)

function definitionScope( art ) {
if (art._base) // with deprecated.generatedEntityNameWithUnderscore
return art._base;
let base = art;

@@ -1128,6 +1133,4 @@ while (art._parent) {

}
if (isDeprecatedEnabled( options, '_longAutoexposed' )) {
const dedot = isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' );
return `${ service.name.absolute }.${ dedot ? absolute.replace( /\./g, '_' ) : absolute }`;
}
if (isDeprecatedEnabled( options, '_longAutoexposed' ))
return `${ service.name.absolute }.${ absolute }`;
const base = definitionScope( target );

@@ -1134,0 +1137,0 @@ if (base === target)

@@ -1,3 +0,9 @@

//
// Propagate properties in XSN
// See also internalDoc/PropagatedCsn.md.
// As opposed to that document, the propagator here works on XSN, not CSN.
// We also do not deep-copy member dictionaries here, but create proxy members
// which get their properties via propagation: we use function `onlyViaParent`
// if that property would not be propagated otherwise.
'use strict';

@@ -14,2 +20,3 @@

// Note that propagation here is also used for deep-copying (function `onlyViaParent`)
function propagate( model ) {

@@ -60,3 +67,2 @@ const props = {

const { options } = model;
const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
// eslint-disable-next-line max-len

@@ -194,3 +200,3 @@ const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, '_oldVirtualNotNullPropagation' );

// We do not consider the $expand status, as elements are already expanded
// by the resolve(), and if not due to deprecated._noElementsExpansion
// by the resolve()
run( type );

@@ -319,7 +325,6 @@ return type[prop];

// usually considered expensive, except:
// - array of Entity (smooth upgrade: array of String(3), array of DerivedScalar)
// - array of Entity
const line = availableAtType( prop, target, source );
if (!line ||
line.type && line.type._artifact && line.type._artifact.kind === 'entity' ||
!line.elements && !line.enum && !line.items && !enableExpandElements)
line.type && line.type._artifact && line.type._artifact.kind === 'entity')
returns( prop, target, source, true );

@@ -326,0 +331,0 @@ }

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

resolvePath,
checkAnnotate,
defineAnnotations,

@@ -107,7 +108,2 @@ attachAndEmitValidNames,

// behavior depending on option `deprecated`:
const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
// TODO: we should get rid of noElementsExpansion soon; both
// beta.nestedProjections and beta.universalCsn do not work with it.
return doResolve();

@@ -319,3 +315,3 @@

const type = effectiveType( obj ); // make sure implicitly redirected target exists
if (!obj.items && type && type.items && enableExpandElements) {
if (!obj.items && type && type.items) {
// TODO: shouldn't be this part of populate.js ?

@@ -333,4 +329,3 @@ const items = {

obj = obj.items || obj; // the object which has type properties
if (enableExpandElements)
effectiveType(obj);
effectiveType(obj);
}

@@ -523,2 +518,4 @@ if (obj.type) { // TODO: && !obj.type.$inferred ?

if (art) {
if (art.kind === 'annotate')
checkAnnotate( ext, art );
defineAnnotations( ext, art, ext._block, ext.kind );

@@ -546,3 +543,3 @@ // eslint-disable-next-line no-shadow

if (!feature) {
warning( 'anno-unexpected-actions', [ ext.name.location, art ], {},
warning( 'anno-unexpected-actions', [ ext.name.location, art._parent || art ], {},
'Actions and functions only exist top-level and for entities' );

@@ -578,3 +575,3 @@ }

effectiveType( obj );
if (art._annotate.elements)
if (art._annotate.elements) // explicit $expand on aor needed
setExpandStatusAnnotate( aor, 'annotate' );

@@ -603,2 +600,4 @@ annotate( obj, 'element', 'elements', 'enum', art );

const dict = art._annotate[prop];
if (dict && art._annotate[prop])
setExpandStatusAnnotate( art, 'annotate' );
const env = obj[prop] || altProp && obj[altProp] || null;

@@ -626,3 +625,3 @@ for (const n in dict)

// see also expandElements()
if (!enableExpandElements || !effectiveType( action ))
if (!effectiveType( action ))
return;

@@ -968,3 +967,3 @@ const chain = [];

// search in `query.elements` after having checked table aliases of the current query
resolveBy( query.orderBy, 'expr', query.elements );
resolveBy( query.orderBy, 'order-by', query.elements );
// TODO: disallow resulting element ref if in expression!

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

resolveTypeArgumentsUnchecked( art, typeArt, user );
checkTypeArguments( art );
checkTypeArguments( art, typeArt );
}

@@ -1286,3 +1285,3 @@ }

*/
function checkTypeArguments( artWithType ) {
function checkTypeArguments( artWithType, typeArt ) {
// Note: `_effectiveType` may point to `artWithType` itself, if the type is structured.

@@ -1293,3 +1292,3 @@ // Also: For enums, it points to the enum type, which is why this trick is needed.

const cyclic = new Set();
let effectiveTypeArt = effectiveType( artWithType );
let effectiveTypeArt = effectiveType( typeArt );
while (effectiveTypeArt && effectiveTypeArt.enum && !cyclic.has(effectiveTypeArt)) {

@@ -1296,0 +1295,0 @@ cyclic.add(effectiveTypeArt);

@@ -8,2 +8,3 @@ // Compiler functions and utilities shared across all phases

const { dictAddArray } = require('../base/dictionaries');
const { isDeprecatedEnabled } = require('../base/model');

@@ -148,2 +149,9 @@ const {

}, // TODO: assertion that there is no next/escape used
'order-by': {
next: '_$next',
dollar: true,
escape: 'param',
assoc: 'nav',
deprecatedSourceRefs: true,
},
'order-by-union': {

@@ -165,2 +173,3 @@ next: '_$next', dollar: true, escape: 'param', noDep: true, noExt: true,

resolvePath,
checkAnnotate,
defineAnnotations,

@@ -608,2 +617,17 @@ attachAndEmitValidNames,

}
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 );
}
}
}

@@ -868,31 +892,34 @@ if (spec.noMessage || msgArt === true && extDict === model.definitions)

// Issue messages for annotations on namespaces and builtins
// (TODO: really here?, probably split main artifacts vs returns)
// see also lateExtensions() where similar messages are reported
function checkAnnotate( construct, art ) {
// Namespaces cannot be annotated in CSN but because they exist as XSN artifacts
// they can still be applied. Namespace annotations are extracted in to-csn.js
// In parseCdl mode USINGs and other unknown references are generated as
// namespaces which would lead to false positives.
// TODO: should this really be different to annotate-unknown?
if (art.kind === 'namespace') {
info( 'anno-namespace', [ construct.name.location, construct ], {},
'Namespaces can\'t be annotated' );
}
// Builtin annotations would also get lost. Same as for namespaces:
// extracted in to-csn.js
else if (art.builtin === true) {
info( 'anno-builtin', [ construct.name.location, construct ], {},
'Builtin types should not be annotated. Use custom type instead' );
}
else if (construct.$syntax === 'returns' && art._block && 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.
warning('anno-unexpected-returns', [ construct.name.location, construct ],
{ keyword: 'returns', kind: art.kind }, 'Unexpected $(KEYWORD) for $(KIND)');
}
}
// Set _block links for annotations (necessary for layering).
// Issue messages for annotations on namespaces and builtins (TODO: really here?)
// Also copy annotations from `construct` to `art` (TODO: separate that functionality).
function defineAnnotations( construct, art, block, priority = false ) {
if (!options.parseCdl && construct.kind === 'annotate') {
// Namespaces cannot be annotated in CSN but because they exist as XSN artifacts
// they can still be applied. Namespace annotations are extracted in to-csn.js
// In parseCdl mode USINGs and other unknown references are generated as
// namespaces which would lead to false positives.
// TODO: should this really be different to annotate-unknown?
if (art.kind === 'namespace') {
info( 'anno-namespace', [ construct.name.location, construct ], {},
'Namespaces can\'t be annotated' );
}
// Builtin annotations would also get lost. Same as for namespaces:
// extracted in to-csn.js
else if (art.builtin === true) {
info( 'anno-builtin', [ construct.name.location, construct ], {},
'Builtin types should not be annotated. Use custom type instead' );
}
else if (construct.$syntax === 'returns' && art._block && 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.
warning('anno-unexpected-returns', [ construct.name.location, construct ],
{ keyword: 'returns', kind: art.kind }, 'Unexpected $(KEYWORD) for $(KIND)');
}
}
if (construct.doc)

@@ -899,0 +926,0 @@ art.doc = construct.doc; // e.g. through `extensions` array in CSN

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

const {
isDeprecatedEnabled,
forEachDefinition,

@@ -30,3 +29,2 @@ forEachGeneric,

function tweakAssocs( model ) {
const { options } = model;
// Get shared functionality and the message function:

@@ -43,7 +41,2 @@ const {

// behavior depending on option `deprecated`:
const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
// TODO: we should get rid of noElementsExpansion soon; both
// beta.nestedProjections and beta.universalCsn do not work with it.
// Phase 5: rewrite associations

@@ -119,3 +112,3 @@ forEachDefinition( model, rewriteSimple );

const elem = element.items || element; // TODO v2: nested items
if (elem.elements && enableExpandElements)
if (elem.elements)
forEachGeneric( elem, 'elements', rewriteAssociationCheck );

@@ -214,3 +207,3 @@ if (!elem.target)

let elem = element.items || element; // TODO v2: nested items
if (elem.elements && enableExpandElements)
if (elem.elements)
forEachGeneric( elem, 'elements', rewriteAssociation );

@@ -294,3 +287,3 @@ if (!originTarget( elem ))

setExpandStatus( elem, 'target' );
if (enableExpandElements && elem._parent && elem._parent.kind === 'element') {
if (elem._parent && elem._parent.kind === 'element') {
// managed association as sub element not supported yet

@@ -378,3 +371,4 @@ error( null, [ elem.location, elem ], {},

return; // just $self
const elem = assoc._main.elements[item.id]; // corresponding elem in including structure
// corresponding elem in including structure
const elem = (assoc._main.items || assoc._main).elements[item.id];
if (!(Array.isArray(elem) || // no msg for redefs

@@ -381,0 +375,0 @@ elem === item._artifact || // redirection for explicit def

@@ -98,2 +98,10 @@ // Simple compiler utility functions

/**
* Set the member `elem` to have a _parent link to `parent` and a corresponding
* _main link. Also set the member's name accordingly, where argument `name`
* is most often the property `elem.name.id`.
*
* If argument `prop` is provided, add `elem` to the dictionary of that name,
* e.g. `elements`.
*/
function setMemberParent( elem, name, parent, prop ) {

@@ -111,5 +119,6 @@ if (prop) { // extension or structure include

setLink( elem, '_main', parent._main || parent );
const parentName = parent.name || parent._outer.name;
elem.name.absolute = parentName.absolute;
if (name == null)
const parentName = parent.name || parent._outer?.name;
if (parentName) // may not be available in e.g. cast() - TODO recheck (#9503)
elem.name.absolute = parentName.absolute;
if (!parentName || name == null)
return;

@@ -362,3 +371,3 @@ const normalized = kindProperties[elem.kind].normalized || elem.kind;

// - 'target': all expanded (sub) elements might only have new target/on, but
// no indivual annotations on any (sub) member
// no individual annotations on any (sub) member
// when set, traverse all parents where the value has been 'origin' before

@@ -365,0 +374,0 @@ // - 'annotate': at least one inferred (sub) member has an individual annotation,

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

forEachDefinition(csn, (artifact, artifactName) => {
if(artifactName == serviceName || artifactName.startsWith(serviceName + '.')) {
if(artifactName === serviceName || artifactName.startsWith(serviceName + '.')) {
art = artifactName;

@@ -168,3 +168,3 @@ handleAnnotations(artifactName, artifact);

// if both annotations are present, ignore 'entity' and raise a message
if (annoNames.map(x=>x.split('#')[0]).find(x=>(x=='@Common.ValueList.viaAssociation'))) {
if (annoNames.map(x=>x.split('#')[0]).find(x=>(x==='@Common.ValueList.viaAssociation'))) {
warning(null, null, `in annotation preprocessing/@Common.ValueList: 'entity' is ignored, as 'viaAssociation' is present, ${ctx}`);

@@ -301,4 +301,8 @@ return false;

// TODO should be flattened, but then alphabetical order is destroyed
let newTextAnno = { '$value': textAnno, '@UI.TextArrangement': value };
carrier['@Common.Text'] = newTextAnno;
// Do not overwrite existing nested annotation values, instead give existing
// nested annotation precedence and remove outer annotation (always)
if(!carrier['@Common.Text.@UI.TextArrangement'] && textAnno) {
carrier['@Common.Text'] = { '$value': textAnno, '@UI.TextArrangement': value };
}
delete carrier[aName];

@@ -305,0 +309,0 @@ }

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

const edmUtils = require('./edmUtils.js')
const { initializeModel, assignAnnotation } = require('./edmPreprocessor.js');
const { initializeModel } = require('./edmPreprocessor.js');
const translate = require('./annotations/genericTranslation.js');

@@ -330,3 +330,3 @@ const { setProp } = require('../base/model');

Do not create an entity set if:
V4 containment: _containerEntity is set and not equal with the artifact name
V4 containment: $containerNames is set and not equal with the artifact name
Entity starts with 'localserviceNameized.' or ends with '_localized'

@@ -438,3 +438,3 @@ */

attributes['m:HasStream'] = true;
assignAnnotation(entityCsn, '@Core.MediaType', hasStream);
edmUtils.assignAnnotation(entityCsn, '@Core.MediaType', hasStream);
}

@@ -441,0 +441,0 @@

@@ -6,2 +6,3 @@ 'use strict'

const { forEach } = require("../utils/objectUtils");
const { isBetaEnabled } = require('../base/model.js');

@@ -758,3 +759,10 @@ // facet definitions, optional could either be true or array of edm types

class ComplexType extends TypeBase { }
class ComplexType extends TypeBase {
constructor(v, details, csn) {
super(v, details, csn);
if(this.v4 && !!csn['@open'] && isBetaEnabled(options, 'odataOpenType')) {
this._edmAttributes['OpenType'] = true;
}
}
}
class EntityType extends ComplexType

@@ -761,0 +769,0 @@ {

@@ -30,4 +30,4 @@ 'use strict';

options.isV2 = function() { return this.v[0]; }
options.isV4 = function() { return this.v[1]; }
options.isV2 = () => v2;
options.isV4 = () => v4;

@@ -63,4 +63,4 @@ options.pathDelimiter = options.isStructFormat ? '/' : '_';

function isContainee(artifact) {
// if _containerEntity is present, it is guaranteed that it has at least one entry
return (artifact._containerEntity && (artifact._containerEntity.length > 1 || artifact._containerEntity[0] != artifact.name));
// if $containerNames is present, it is guaranteed that it has at least one entry
return (artifact.$containerNames && (artifact.$containerNames.length > 1 || artifact.$containerNames[0] != artifact.name));
}

@@ -758,3 +758,82 @@

function mergeIntoNavPropRestrictions(annoPrefix, assocPath, props, navPropRestrictions) {
let navPropEntry;
let hasEntry = false;
let newEntry = false;
hasEntry = !!(navPropEntry = navPropRestrictions.find(p =>
p.NavigationProperty && p.NavigationProperty['='] === assocPath));
if(!hasEntry) {
navPropEntry = { NavigationProperty: { '=': assocPath } };
}
const prop = annoPrefix.split('.')[1];
// Filter properties with prefix and reduce them into a new dictionary
const o = props.filter(p => p[0].startsWith(annoPrefix+'.')).reduce((a,c) => {
a[c[0].replace(annoPrefix+'.', '')] = c[1];
return a;
}, { });
// don't overwrite existing restrictions
if(!navPropEntry[prop]) {
// if dictionary has entries, add them to navPropEnty
if(Object.keys(o).length) {
// ReadRestrictions may have sub type ReadByKeyRestrictions { Description, LongDescription }
// chop annotations into dictionaries
if(annoPrefix === '@Capabilities.ReadRestrictions' &&
Object.keys(o).some(k => k.startsWith('ReadByKeyRestrictions.'))) {
const no = {};
Object.entries(o).forEach(([k,v]) => {
const [head, ...tail] = k.split('.');
if(head === 'ReadByKeyRestrictions' && tail.length) {
if(!no['ReadByKeyRestrictions'])
no['ReadByKeyRestrictions'] = {};
// Don't try to add entry into non object
if(typeof no['ReadByKeyRestrictions'] === 'object')
no['ReadByKeyRestrictions'][tail.join('.')] = v;
}
else {
no[k] = v;
}
});
navPropEntry[prop] = no;
}
else {
navPropEntry[prop] = o;
}
newEntry = true;
}
}
else {
// merge but don't overwrite into existing navprop
Object.entries(o).forEach(([k,v]) => {
if(!navPropEntry[prop][k])
navPropEntry[prop][k] = v;
});
}
if(newEntry && !hasEntry) {
navPropRestrictions.push(navPropEntry);
}
}
// Assign but not overwrite annotation
function assignAnnotation(node, name, value) {
if(value !== undefined &&
name !== undefined && name[0] === '@' &&
(node[name] === undefined || node[name] === null)) {
node[name] = value;
}
}
// Set non enumerable property if it doesn't exist yet
function assignProp(obj, prop, value) {
if(obj[prop] === undefined) {
setProp(obj, prop, value);
}
}
module.exports = {
assignAnnotation,
assignProp,
validateOptions,

@@ -780,3 +859,4 @@ intersect,

getSchemaPrefix,
getBaseName
getBaseName,
mergeIntoNavPropRestrictions
}

@@ -106,2 +106,3 @@ // CSN frontend - transform CSN into XSN

'target', 'elements', 'enum', 'items',
'cardinality', // for association publishing in views
'type', 'length', 'precision', 'scale', 'srid', 'localized', 'notNull',

@@ -232,3 +233,3 @@ 'keys', 'on', // only with 'target'

validKinds: [ 'action', 'function' ],
inKind: [ 'entity', 'annotate', 'extend' ],
inKind: [ 'entity', 'aspect', 'annotate', 'extend' ],
},

@@ -1292,3 +1293,3 @@ params: {

const p = quotedLiteralPatterns[lit];
if (p && (p.test_fn && !p.test_fn(csn.val) || p.test_re && !p.test_re.test(csn.val)))
if (p && p.test_fn && !p.test_fn(csn.val))
warning( 'syntax-invalid-literal', location(), { '#': p.test_variant } );

@@ -1295,0 +1296,0 @@ return lit;

@@ -130,5 +130,2 @@ // Transform XSN (augmented CSN) into CSN

location, // non-enumerable $location in CSN
$a2j: (e, csn) => { // on artifact level
Object.assign( csn, e );
},
$extra: (e, csn) => {

@@ -189,5 +186,7 @@ Object.assign( csn, e );

// sync with definition in from-csn.js:
// Note: Order here is also the property order in CSN.
const typeProperties = [
'target', 'elements', 'enum', 'items', // TODO: notNull?
'type', 'length', 'precision', 'scale', 'srid', 'localized',
'target', 'elements', 'enum', 'items',
'cardinality', // for association publishing in views
'type', 'length', 'precision', 'scale', 'srid', 'localized', // TODO: notNull?
'foreignKeys', 'on', // for explicit ON/keys with REDIRECTED

@@ -1281,4 +1280,7 @@ ];

if (node.path) {
const ref = node.path.map( pathItem );
if (node.path.$prefix)
ref.unshift( node.path.$prefix );
// we would need to consider node.global here if we introduce that
return extra( { ref: node.path.map( pathItem ) }, dollarExtraNode );
return extra( { ref }, dollarExtraNode );
}

@@ -1285,0 +1287,0 @@ if (node.literal) {

@@ -66,6 +66,9 @@ // Generic ANTLR parser class with AST-building functions

assignAnnotation,
addAnnotation,
checkExtensionDict,
handleExtension,
handleDuplicateExtension,
startLocation,
tokenLocation,
isMultiLineToken,
fixMultiLineTokenEndLocation,
valueWithTokenLocation,

@@ -194,8 +197,8 @@ previousTokenAtLocation,

function setLocalTokenForId( tokenNameMap ) {
const tokenName = tokenNameMap[ this._input.LT(2).text || '' ];
const ll1 = this.getCurrentToken();
if (ll1.type === this.constructor.Identifier || /^[a-zA-Z_]+$/.test( ll1.text )) {
const tokenName = tokenNameMap[ this._input.LT(2).text || '' ];
if (tokenName)
ll1.type = this.constructor[tokenName];
}
if (tokenName &&
(ll1.type === this.constructor.Identifier || /^[a-zA-Z_]+$/.test( ll1.text )))
ll1.type = this.constructor[tokenName];
return !!tokenName;
}

@@ -272,5 +275,2 @@

this.$genericKeywords = spec;
// currently, we only have 'TODO', i.e. a keyword which is alternative to expression
// TODO: If not just at the beginning, we need a stack for $genericKeywords,
// as we can have nested special functions
// @ts-ignore

@@ -314,11 +314,22 @@ const token = this.getCurrentToken() || { text: '' };

return art;
if (!art.location)
art.location = this.startLocation();
const { stop } = this._ctx;
art.location.endLine = stop.line;
art.location.endCol = stop.stop - stop.start + stop.column + 2; // after the last char (special for EOF?)
if (!art.location) {
art.location = this.tokenLocation(this._ctx.start, this._ctx.stop);
return art;
}
// The last token (this._ctx.stop) may be a multi-line string literal, in which
// case we can't rely on `this._ctx.stop.line`.
if (this.isMultiLineToken(this._ctx.stop)) {
this.fixMultiLineTokenEndLocation(this._ctx.stop, art.location);
} else {
const { stop } = this._ctx;
art.location.endLine = stop.line;
art.location.endCol = stop.stop - stop.start + stop.column + 2; // after the last char (special for EOF?)
}
return art;
}
function assignAnnotation( art, anno, prefix = '', iHaveVariant ) {
function assignAnnotation( art, anno, prefix = '', iHaveVariant = false ) {
const { name, $flatten } = anno;

@@ -351,15 +362,5 @@ const { path } = name;

name.absolute = absolute;
const prop = '@' + absolute;
const old = art[prop];
if (old && old.$inferred)
art[prop] = anno;
else
dictAddArray( art, prop, anno, (n, location, a) => {
this.error( 'syntax-duplicate-anno', [ location ], { anno: n },
'Duplicate assignment with $(ANNO)' );
a.$errorReported = 'syntax-duplicate-anno';
// do not report again later as anno-duplicate-xyz
} );
this.addAnnotation( art, '@' + absolute, anno );
}
if (!prefix) { // set deprecated $annnotations for cds-lsp
if (!prefix) { // set deprecated $annotations for cds-lsp
if (!art.$annotations)

@@ -372,2 +373,15 @@ art.$annotations = [];

function addAnnotation( art, prop, anno ) {
dictAddArray( art, prop, anno, (n, location, a) => {
// if we would make it a warning, we would still need to keep it an error
// with '...'; otherwise parse.cdl would have to split annotate statements
this.error( 'syntax-duplicate-anno', [ location ], { anno: n },
'Duplicate assignment with $(ANNO)' );
a.$errorReported = 'syntax-duplicate-anno';
// do not report again later as anno-duplicate-xyz
} );
}
const extensionDicts = { elements: true, enum: true, params: true, returns: true };
function checkExtensionDict( dict ) {

@@ -379,8 +393,30 @@ for (const name in dict) {

const numDefines = (def.kind === 'annotate')
? 0
: def.$duplicates.reduce( addOneForDefinition, addOneForDefinition( 0, def ) );
this.handleExtension( def, name, numDefines );
for (const dup of def.$duplicates)
this.handleExtension( dup, name, numDefines );
if (def.kind !== 'annotate') {
const numDefines =
def.$duplicates.reduce( addOneForDefinition, addOneForDefinition( 0, def ) );
this.handleDuplicateExtension( def, name, numDefines );
for (const dup of def.$duplicates)
this.handleDuplicateExtension( dup, name, numDefines );
continue;
}
// move annotations, 'doc' and 'elements' etc to main member
for (const dup of def.$duplicates) {
for (const prop of Object.keys( dup )) {
if (prop.charAt(0) === '@') {
this.addAnnotation( def, prop, dup[prop] )
}
else if (prop === 'doc') {
if (def.doc)
this.warning( 'syntax-duplicate-doc-comment', def.doc.location, {},
'Doc comment is overwritten by another one below' );
def.doc = dup.doc;
}
else if (extensionDicts[prop]) {
if (def[prop])
this.message( 'syntax-duplicate-annotate', [ def.name.location ], { name, prop } );
def[prop] = dup[prop]; // continuation semantics: last wins
}
}
}
def.$duplicates = null;
}

@@ -393,6 +429,11 @@ }

function handleExtension( ext, name, numDefines ) {
if (ext.kind === 'annotate')
this.warning( 'syntax-duplicate-annotate', [ ext.name.location ], { name } );
else if (ext.kind === 'extend')
/**
* Handle duplicate extensions. Does not handle `annotate`.
*
* @param {XSN.Extension} ext
* @param {string} name
* @param {number} numDefines
*/
function handleDuplicateExtension( ext, name, numDefines ) {
if (ext.kind === 'extend')
this.error( 'syntax-duplicate-extend', [ ext.name.location ],

@@ -445,27 +486,45 @@ { name, '#': (numDefines ? 'define' : 'extend') } );

// data if we know that it spans only one single line.
const isMultiLineToken = (
endToken.type === this.constructor.DocComment ||
endToken.type === this.constructor.String ||
endToken.type === this.constructor.UnterminatedLiteral
if (this.isMultiLineToken(token))
this.fixMultiLineTokenEndLocation(token, loc);
return loc;
}
function isMultiLineToken(token) {
return (
token.type === this.constructor.DocComment ||
token.type === this.constructor.String ||
token.type === this.constructor.UnterminatedLiteral
);
if (isMultiLineToken) {
// Count the number of newlines in the token.
const source = endToken.source[1].data;
let newLineCount = 0;
let lastNewlineIndex = endToken.start;
for (let i = endToken.start; i < endToken.stop; i++) {
// Note: We do NOT check for CR, LS, and PS (/[\r\u2028\u2029]/)
// because ANTLR only uses LF for line break detection.
if (source[i] === 10) { // code point of '\n'
newLineCount++;
lastNewlineIndex = i;
}
}
/**
* Adapt end location of `location` according to `token`, assuming that `token` is a multi-line
* token such as a multi-line string or doc comment.
*
* Sets `endLine`/`endCol`, respecting newline characters in the token.
*
* @param token
* @param {CSN.Location} location
*/
function fixMultiLineTokenEndLocation( token, location ) {
// Count the number of newlines in the token.
const source = token.source[1].data;
let newLineCount = 0;
let lastNewlineIndex = token.start;
for (let i = token.start; i < token.stop; i++) {
// Note: We do NOT check for CR, LS, and PS (/[\r\u2028\u2029]/)
// because ANTLR only uses LF for line break detection.
if (source[i] === 10) { // code point of '\n'
newLineCount++;
lastNewlineIndex = i;
}
if (newLineCount > 0) {
loc.endLine = endToken.line + newLineCount;
loc.endCol = endToken.stop - lastNewlineIndex + 1;
}
}
return loc;
if (newLineCount > 0) {
location.endLine = token.line + newLineCount;
location.endCol = token.stop - lastNewlineIndex + 1;
} else {
location.endLine = token.line;
location.endCol = token.stop - token.start + token.column + 2; // after the last char (special for EOF?)
}
}

@@ -543,4 +602,4 @@

if (node.doc) {
this.warning( 'syntax-duplicate-doc-comment', token, {},
'Repeated doc comment - previous doc is replaced' );
this.warning( 'syntax-duplicate-doc-comment', node.doc.location, {},
'Doc comment is overwritten by another one below' );
}

@@ -729,4 +788,3 @@ node.doc = this.valueWithTokenLocation( parseDocComment( token.text ), token );

if ((p.test_fn && !p.test_fn(val) || p.test_re && !p.test_re.test(val)) &&
!this.options.parseOnly)
if (p.test_fn && !p.test_fn(val) && !this.options.parseOnly)
this.warning( 'syntax-invalid-literal', location, { '#': p.test_variant } );

@@ -733,0 +791,0 @@

@@ -899,2 +899,3 @@ // Official cds-compiler API.

* @param definitionName Top-level definition name of the artifact.
* @since v2.14.0
*/

@@ -901,0 +902,0 @@ function isInReservedNamespace(definitionName: string): boolean;

@@ -14,3 +14,3 @@ // Miscellaneous CSN functions we put into our compiler API

* - the current CSN node, i.e. ‹parent node›.‹property name›
* - the the ‹parent node›
* - the ‹parent node›
* - the ‹property name› (might be useful if the same function is used for several props)

@@ -17,0 +17,0 @@ */

@@ -213,4 +213,7 @@ // CSN functionality for resolving references

// there are also 'on_join' and 'on_mixin' with default semantics
orderBy: { lexical: query => query, dynamic: 'query' },
orderBy_set: { lexical: query => query.$next, dynamic: 'query' }, // to outer SELECT (from UNION)
orderBy_ref: { lexical: query => query, dynamic: 'query' },
orderBy_expr: { lexical: query => query, dynamic: 'source' }, // ref in ORDER BY expression
orderBy_set_ref: { lexical: query => query.$next, dynamic: 'query' }, // to outer SELECT (from UNION)
// refs in ORDER BY expr in UNION not really allowed - only with table alias (of outer queries) or $self
orderBy_set_expr: { lexical: query => query.$next, dynamic: false },
// default: { lexical: query => query, dynamic: 'source' }

@@ -555,33 +558,35 @@ }

// now the dynamic environment: ------------------------------------------
if (semantics.dynamic === 'target') { // ref in keys
const target = assocTarget( parent, refCtx );
return resolvePath( path, target.elements[head], target, 'target' );
}
if (baseEnv) // ref-target (filter condition), expand, inline
return resolvePath( path, baseEnv.elements[head], baseEnv, semantics.dynamic );
if (!query) { // outside queries - TODO: items?
let art = parent.elements[head];
// Ref to up_ in anonymous aspect
if (!art && head === 'up_') {
const up = getCache( parent, '_parent' );
const target = up && typeof up.target === 'string' && csn.definitions[up.target];
if (target && target.elements) {
initDefinition( target );
art = target.elements.up_;
if (semantics.dynamic !== false) {
if (semantics.dynamic === 'target') { // ref in keys
const target = assocTarget( parent, refCtx );
return resolvePath( path, target.elements[head], target, 'target' );
}
if (baseEnv) // ref-target (filter condition), expand, inline
return resolvePath( path, baseEnv.elements[head], baseEnv, semantics.dynamic );
if (!query) { // outside queries - TODO: items?
let art = parent.elements[head];
// Ref to up_ in anonymous aspect
if (!art && head === 'up_') {
const up = getCache( parent, '_parent' );
const target = up && typeof up.target === 'string' && csn.definitions[up.target];
if (target && target.elements) {
initDefinition( target );
art = target.elements.up_;
}
}
return resolvePath( path, art, parent, 'parent' );
}
return resolvePath( path, art, parent, 'parent' );
}
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) {
const alias = qcache.$aliases[name];
const found = alias.elements[head];
if (found)
return resolvePath( path, found, alias._ref, 'source', name )
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) {
const alias = qcache.$aliases[name];
const found = alias.elements[head];
if (found)
return resolvePath( path, found, alias._ref, 'source', name )
}
}
// console.log(query.SELECT,qcache,qcache.$next,main)
throw new ModelError ( `Path item ${ 0 }=${ head } refers to nothing, refCtx: ${ refCtx }` );
throw new ModelError ( `Path item 0=${ head } refers to nothing, refCtx: ${ refCtx }` );
}

@@ -938,2 +943,11 @@

}
else if (refCtx === 'orderBy') {
const isSelect = isSelectQuery( query );
// use _query_ elements with direct refs (consider sub-optimal CSN,
// representation of the CAST function), otherwise source elements:
if (obj[prop].ref && !obj[prop].cast)
refCtx = (isSelect ? 'orderBy_ref' : 'orderBy_set_ref');
else
refCtx = (isSelect ? 'orderBy_expr' : 'orderBy_set_expr');
}
isName = false;

@@ -989,3 +1003,3 @@ }

else if (prop === 'orderBy') {
refCtx = (query.SET ? 'orderBy_set' : 'orderBy');
refCtx = 'orderBy';
}

@@ -1003,2 +1017,14 @@ else if (prop !== 'xpr') {

// A SELECT which is (unnecessarily) put into parentheses, the CSN
// representation uses SET without `op` and args of length 1:
function isSelectQuery( query ) {
while (query.SET) {
const { args } = query.SET;
if (args.length !== 1)
return false;
query = args[0];
}
return true;
}
module.exports = {

@@ -1005,0 +1031,0 @@ csnRefs,

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

* Resolve to the final type of a type, that means follow type chains, references, etc.
* Input is a type name, i.e. string, or type ref, i.e. `{ ref: [...] }`.
* Input is a fully qualified type name, i.e. string, or type ref, i.e. `{ ref: [...] }`.
*

@@ -503,4 +503,7 @@ * Returns `null` if the type can't be resolved or if the referenced element has no type,

*
* Caches type lookups. If the CSN changes drastically, you will need to re-call `csnUtils()`
* and use the newly returned `getFinalBaseTypeWithProps()`.
* Notes:
* - Caches type lookups. If the CSN changes drastically, you will need to re-call
* `csnUtils()` and use the newly returned `getFinalBaseTypeWithProps()`.
* - Does _not_ return the underlying type definition! It is an object with all relevant
* type properties collected while traversing the type chain!
*

@@ -522,3 +525,3 @@ * @param {string|object} type Type as string or type ref, i.e. `{ ref: [...] }`

// Delimiter chosen arbitrarily; just one that is rarely used.
const resolvedKey = (typeof type === 'object') ? `ref:${type.ref.join('\\')}` : `type:${type}`;
const resolvedKey = (typeof type === 'object') ? `ref[${type.ref.length}]:${type.ref.join('\\')}` : `type:${type}`;

@@ -792,6 +795,8 @@ if (finalBaseTypeCache[resolvedKey]) {

function executeCallbacks(o, name ) {
const p = iterateOptions.pathWithoutProp ? [ name ] : [ prop, name ];
if (Array.isArray(callback))
callback.forEach(cb => cb( o, name, prop, path.concat([prop, name]), construct ));
callback.forEach(cb => cb( o, name, prop, path.concat(p), construct ));
else
callback( o, name, prop, path.concat([prop, name]), construct )
callback( o, name, prop, path.concat(p), construct )
}

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

* will be applied.
* @todo Improve to also apply element annotations
*/

@@ -1296,0 +1300,0 @@ function applyAnnotationsFromExtensions(csn, config) {

@@ -61,3 +61,2 @@ // Make internal properties of the XSN / augmented CSN visible

* @param {string} [nameOrPath]
* @returns {string}
*/

@@ -303,4 +302,4 @@ function revealInternalProperties( model, nameOrPath ) {

if (node[$location])
r.push( { $location: locationString( node[$location] ) } );
return r;
r.push( { '[$location]': locationString( node[$location] ) } );
return (node.$prefix) ? [ { $prefix: node.$prefix }, ...node ] : r;
}

@@ -307,0 +306,0 @@

@@ -172,2 +172,5 @@ 'use strict';

// Type or parameters, e.g. association target, changed.
if(otherElement.notNull && element.notNull === undefined) {
element.$notNull = false; // Explictily set notNull to the implicit default so we render the correct ALTER
}
changedElementsDict[name] = changedElement(element, otherElement);

@@ -174,0 +177,0 @@ }

@@ -105,4 +105,7 @@ 'use strict';

ignoreAssocPublishingInUnion
odataOpenType
optionalActionFunctionParameters
--deprecated <list> Comma separated list of deprecated options.
Valid values are:
autoCorrectOrderBySourceRefs
eagerPersistenceForGeneratedEntities

@@ -142,2 +145,3 @@ --fallback-parser <type> If the language cannot be deduced by the file's extensions, use this

add / modify referential constraints.
inspect [options] <files...> (internal) Inspect the given CDS files.
`);

@@ -158,2 +162,3 @@

.option(' --assert-integrity-type <type>', ['RT', 'DB'], { ignoreCase: true })
.option(' --disable-hana-comments')
.help(`

@@ -193,2 +198,3 @@ Usage: cdsc toHana [options] <files...>

DB : Create database constraints for associations
--disable-hana-comments Disable rendering of doc comments as SAP HANA comments.
`);

@@ -270,2 +276,3 @@

.option(' --constraints-in-create-table')
.option(' --disable-hana-comments')
.help(`

@@ -318,2 +325,3 @@ Usage: cdsc toSql [options] <files...>

"ALTER TABLE ADD CONSTRAINT" statements
--disable-hana-comments Disable rendering of doc comments as SAP HANA comments.
`);

@@ -386,2 +394,3 @@

.option(' --with-localized')
.option(' --with-locations')
.help(`

@@ -401,2 +410,4 @@ Usage: cdsc toCsn [options] <files...>

universal: in development (BETA)
--with-locations Add $location to CSN artifacts. In contrast to \`--enrich-csn\`,
$location is an object with 'file', 'line' and 'col' properties.

@@ -455,4 +466,20 @@ Internal options (for testing only, may be changed/removed at any time)

optionProcessor.command('inspect')
.option('-h, --help')
.option(' --statistics')
.option(' --propagation <art>')
.positionalArgument('<files...>')
.help(`
Usage: cdsc inspect [options] <files...>
(internal): Inspect the CSN model compiled from the provided CDS files.
Options
-h, --help Show this help text
--statistics Print model statistics
--propagation <art> Show propagation sources for <art>
`);
module.exports = {
optionProcessor
};

@@ -289,4 +289,4 @@ // Common render functions for toCdl.js, toHdbcds.js and toSql.js

'cds.LargeBinary': 'bytea',
'cds.Binary': 'VARCHAR',
'cds.hana.BINARY': 'VARCHAR',
'cds.Binary': 'bytea',
'cds.hana.BINARY': 'bytea',
'cds.Double': 'double precision',

@@ -362,7 +362,6 @@ 'cds.hana.TINYINT': 'INTEGER',

* Remove leading/trailing whitespace.
* Does not escape any characters.
* Does not escape any characters, use e.g. `getEscapedHanaComment()` for HDBCDS.
*
* @param {CSN.Artifact|CSN.Element} obj
* @returns {string}
* @todo Warning/info to user?
*/

@@ -369,0 +368,0 @@ function getHanaComment(obj) {

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

*/
function getManagedAssocStepsInOnConditionFinalizer(csn, pathDelimiter) {
function getFKAccessFinalizer(csn, pathDelimiter) {
const {

@@ -116,3 +116,3 @@ inspectRef,

return handleManagedAssocStepsInOnCondition;
return handleManagedAssocSteps;

@@ -128,41 +128,49 @@ /**

*/
function handleManagedAssocStepsInOnCondition(artifact, artifactName) {
for (const elemName in artifact.elements) {
const elem = artifact.elements[elemName];
// The association is an unmanaged on
if (!elem.keys && elem.target && elem.on) {
applyTransformationsOnNonDictionary(elem, 'on', {
ref: (refOwner, prop, ref, path) => {
// [<assoc base>.]<managed assoc>.<field>
if (ref.length > 1) {
const { links } = inspectRef(path);
if (links) {
// eslint-disable-next-line for-direction
for (let i = links.length - 1; i >= 0; i--) {
const link = links[i];
// We found the latest managed assoc path step
if (link.art && link.art.target && link.art.keys &&
function handleManagedAssocSteps(artifact, artifactName) {
const transformer = {
ref: (refOwner, prop, ref, path) => {
// [<assoc base>.]<managed assoc>.<field>
if (ref.length > 1) {
const { links } = inspectRef(path);
if (links) {
// eslint-disable-next-line for-direction
for (let i = links.length - 1; i >= 0; i--) {
const link = links[i];
// We found the latest managed assoc path step
if (link.art && link.art.target && link.art.keys &&
// Doesn't work when ref-target (filter condition) or similar is used
!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 ];
}
}
// 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 ];
}
}
}
},
}, {}, [ 'definitions', artifactName, 'elements', elemName ]);
}
}
}
},
};
for (const elemName in artifact.elements) {
const elem = artifact.elements[elemName];
// The association is an unmanaged one
if (!elem.keys && elem.target && elem.on)
applyTransformationsOnNonDictionary(elem, 'on', transformer, {}, [ 'definitions', artifactName, 'elements', elemName ]);
}
if (artifact.query || artifact.projection) {
applyTransformationsOnNonDictionary(artifact, artifact.query ? 'query' : 'projection', {
orderBy: (parent, prop, thing, path) => applyTransformationsOnNonDictionary(parent, prop, transformer, {}, path),
groupBy: (parent, prop, thing, path) => applyTransformationsOnNonDictionary(parent, prop, transformer, {}, path),
}, {}, [ 'definitions', artifactName ]);
}
/**

@@ -189,3 +197,3 @@ * Find out where the managed association is

attachOnConditions,
getManagedAssocStepsInOnConditionFinalizer,
getFKAccessFinalizer,
};

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

* @param {CSN.Path} path
* @todo Why do we check for @cds.persistence.exists here if the parent-function only calls this for skip/abstract?
*/

@@ -84,0 +83,0 @@ function ignore(member, memberName, prop, path) {

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

function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOptions = {}) {
const typeCache = Object.create(null); // TODO: Argument as well?
/**

@@ -87,3 +86,3 @@ * Remove .localized from the element and any sub-elements

if (cast.type && !isBuiltinType(cast.type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(cast.type, path) && !isODataItems(cast.type)))
toFinalBaseType(parent.cast, resolved, true, typeCache);
toFinalBaseType(parent.cast, resolved, true);
},

@@ -95,3 +94,3 @@ // @ts-ignore

if (!isBuiltinType(type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(type, path) && !isODataItems(type))) {
toFinalBaseType(parent, resolved, true, typeCache);
toFinalBaseType(parent, resolved, true);
// structured types might not have the child-types replaced.

@@ -105,3 +104,3 @@ // Drill down to ensure this.

if (e.type && !isBuiltinType(e.type))
toFinalBaseType(e, resolved, true, typeCache);
toFinalBaseType(e, resolved, true);

@@ -108,0 +107,0 @@ if (e.elements)

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

const expr = walkCsnPath(csn, exprPath);
const queryBase = query.SELECT.from.ref ? (query.SELECT.from.as || query.SELECT.from.ref[0]) : null;
const queryBase = query.SELECT.from.ref ? (query.SELECT.from.as || query.SELECT.from.ref) : null;
const sources = getQuerySources(query.SELECT);

@@ -461,3 +461,3 @@

*
* For an ordinary unmanaged association, we do the the following for each part of the on-condition:
* For an ordinary unmanaged association, we do the following for each part of the on-condition:
* - target side: We prefix the real target and cut off the assoc-name from the ref

@@ -735,3 +735,3 @@ * - source side w/ leading $self: We remove the $self and add the source side entity/query source

*
* @param {string|null} queryBase
* @param {string | Array | null} queryBase
* @param {boolean} isPrefixedWithTableAlias

@@ -743,4 +743,6 @@ * @param {CSN.Column} current

function getBase(queryBase, isPrefixedWithTableAlias, current, path) {
if (queryBase)
return getRealName(csn, queryBase);
if (typeof queryBase === 'string') // alias
return queryBase;
else if (queryBase) // ref
return queryBase.length > 1 ? queryBase[queryBase.length - 1] : getRealName(csn, queryBase[0]);
else if (isPrefixedWithTableAlias)

@@ -747,0 +749,0 @@ return current.ref[0];

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

function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
const draftSuffix = isDeprecatedEnabled(options, '_generatedEntityNameWithUnderscore') ? '_drafts' : '.drafts';
const draftSuffix = '.drafts';
// All services of the model - needed for drafts

@@ -25,0 +25,0 @@ const allServices = getServiceNames(csn);

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

if (doA2J)
forEachDefinition(csn, associations.getManagedAssocStepsInOnConditionFinalizer(csn, pathDelimiter));
forEachDefinition(csn, associations.getFKAccessFinalizer(csn, pathDelimiter));
// Create convenience views for localized entities/views.
// To be done after getManagedAssocStepsInOnConditionFinalizer because associations are
// To be done after getFKAccessFinalizer because associations are
// handled and before handleDBChecks which removes the localized attribute.

@@ -396,2 +396,11 @@ // Association elements of localized convenience views do not have hidden properties

}
},
}
if(options.sqlDialect === 'postgres') {
killers.length = (parent) => {
if (parent.type === 'cds.Binary' || parent.type === 'cds.hana.BINARY') {
delete parent.length;
}
}

@@ -398,0 +407,0 @@ }

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

if(!structuredOData)
forEachDefinition(csn, associations.getManagedAssocStepsInOnConditionFinalizer(csn, '_'));
forEachDefinition(csn, associations.getFKAccessFinalizer(csn, '_'));

@@ -184,0 +184,0 @@ // structure flattener reports errors, further processing is not safe -> throw exception in case of errors

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

const { copyAnnotations } = require('../../model/csnUtils');
const { isBetaEnabled } = require('../../base/model.js');

@@ -96,7 +97,12 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackSchemaName, options, csnUtils, message) {

// as soon as we leave of the anonymous world,
// we're no longer in a key def => don't set notNull:true on named types
if (!isAnonymous && isKey)
isKey = false;
if (!isAnonymous) {
// as soon as we leave of the anonymous world,
// we're no longer in a key def => don't set notNull:true on named types
if(isKey)
isKey = false;
// in case this was a named type and if the openess does not match the type definition
// expose the type as a new one not changing the original definition.
if((!!node['@open'] !== !!typeDef['@open']) && isBetaEnabled(options, 'odataOpenType'))
fullQualifiedNewTypeName += node['@open'] ? '_open' : '_closed';
}
// check if that type is already defined

@@ -116,2 +122,5 @@ let newType = csn.definitions[fullQualifiedNewTypeName];

newType = createNewStructType(elements);
// if using node enforces open/closed, set it on type
if(node['@open'] !== undefined)
newType['@open'] = node['@open']
if (node.$location)

@@ -118,0 +127,0 @@ setProp(newType, '$location', node.$location);

@@ -28,3 +28,2 @@ // Custom resolve functionality for the CDS compiler

* - Preferred to a local installation? Not the node-way!
* - Why a global? The Umbrella could pass it as an option.
*

@@ -31,0 +30,0 @@ * @param {string} modulePath

{
"name": "@sap/cds-compiler",
"version": "3.0.2",
"version": "3.1.0",
"description": "CDS (Core Data Services) compiler and backends",

@@ -19,3 +19,3 @@ "homepage": "https://cap.cloud.sap/",

"gen": "node ./scripts/build.js && node scripts/genGrammarChecksum.js",
"xmakeBeforeInstall": "echo \"Due to binary mirror, use sqlite 5.0.8 explicitly\" && npm install --save --save-exact --no-package-lock sqlite3@5.0.8",
"xmakeBeforeInstall": "echo \"Due to binary mirror, use sqlite 5.0.11 explicitly\" && npm install --save --save-exact --no-package-lock sqlite3@5.0.11",
"xmakeAfterInstall": "npm run gen",

@@ -22,0 +22,0 @@ "xmakePrepareRelease": "echo \"$(node scripts/stripReadme.js README.md)\" > README.md && node scripts/assertSnapshotVersioning.js && node scripts/assertChangelog.js && node scripts/cleanup.js --remove-dev",

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

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