@sap/cds-compiler
Advanced tools
Comparing version 1.42.2 to 1.43.0
@@ -128,3 +128,3 @@ #!/usr/bin/env node | ||
'dontRenderVirtualElements': true, | ||
'ignoreManagedAssocPublishingInUnion': true | ||
'ignoreAssocPublishingInUnion': true | ||
} | ||
@@ -131,0 +131,0 @@ } |
@@ -9,2 +9,25 @@ # ChangeLog for cdx compiler and backends | ||
## Version 1.43.0 - 2020-10-02 | ||
### Added | ||
- The magic variable `$session` is now supported. All element accesses are unchecked. | ||
- Reference paths as annotation values can now contain identifiers starting with `@`. | ||
### Changed | ||
- OData: | ||
+ Raise message level for illegal OData identifiers from warning to error. | ||
+ Update vocabularies 'Aggregation' and 'Common'. | ||
### Fixed | ||
- to.hdi/hdbcds/sql: Correctly process the elements of subqueries in localized view variants | ||
### Removed | ||
### Fixed | ||
- OData: put default value validation under `beta:odataDefaultValues` | ||
## Version 1.42.2 - 2020-09-29 | ||
@@ -11,0 +34,0 @@ |
@@ -10,9 +10,20 @@ # ChangeLog of Beta Features for cdx compiler and backends | ||
## Version 1.42.0 | ||
## Version 1.43.0 | ||
### Added `ignoreManagedAssocPublishingInUnion` | ||
### Changed `subElemRedirections` | ||
For `to.hdbcds`, with beta flag `ignoreManagedAssocPublishingInUnion` in conjunction with dialect | ||
`hanaJoins`, managed associations in UNIONs are replaced by their foreign keys and silently ignored | ||
When the beta option `subElemRedirections` is set to true, | ||
_all_ array (new!) and structure types are expanded when referenced: | ||
managed associations (and compositions to entities) in that array are | ||
implicitly redirected when necessary. | ||
See [below for details](#version-1300---20200612). | ||
Nested array types (without intermediate structure types) are not supported. | ||
### Added `ignoreAssocPublishingInUnion` | ||
For `to.hdbcds`, with beta flag `ignoreAssocPublishingInUnion` in conjunction with dialect | ||
`hanaJoins`, unmanaged associations in UNIONs are silently ignored and managed associations | ||
are replaced by their foreign keys and silently ignored | ||
## Version 1.36.0 - 2020-08-07 | ||
@@ -19,0 +30,0 @@ |
@@ -955,6 +955,10 @@ 'use strict'; | ||
// Return a set of options containing the defaults that would be applied by the backends. | ||
// Note that this only contains simple mergeable default values, not conditional defaults | ||
// that depend in any way on other options (e.g. toSql provides 'src' if neither 'src' nor | ||
// 'csn' is given: this is a conditional default). | ||
/** | ||
* Return a set of options containing the defaults that would be applied by the backends. | ||
* Note that this only contains simple mergeable default values, not conditional defaults | ||
* that depend in any way on other options (e.g. toSql provides 'src' if neither 'src' nor | ||
* 'csn' is given: this is a conditional default). | ||
* | ||
* @returns {CSN.Options} | ||
*/ | ||
function getDefaultBackendOptions() { | ||
@@ -961,0 +965,0 @@ return { |
'use strict'; | ||
// This file contains functions related to XSN/CSN-location objects. | ||
// This file contains functions related to XSN/CSN-location objects as well | ||
// as for semantic locations | ||
const { analyseCsnPath, traverseQuery } = require('../model/csnRefs'); | ||
/** | ||
@@ -17,3 +20,3 @@ * Create a location with location properties `filename` and `start` from | ||
start: start.location.start, | ||
end: end && end.location && end.location.end | ||
end: end && end.location && end.location.end, | ||
}; | ||
@@ -98,15 +101,234 @@ } | ||
const locations = [].concat( ...dict.map( objLocations ) ); | ||
const locations = [].concat( ...dict.map( _objLocations ) ); | ||
if (extraLocation) | ||
locations.push( extraLocation ); | ||
const min = locations.reduce( (a,b) => a.start.offset < b.start.offset ? a : b ); | ||
const max = locations.reduce( (a,b) => (a.end || a.start).offset > (b.end || b.start).offset ? a : b ); | ||
const min = locations.reduce( (a, b) => (a.start.offset < b.start.offset ? a : b) ); | ||
const max = locations.reduce( (a, b) => ((a.end || a.start).offset > (b.end || b.start).offset ? a : b) ); | ||
return { filename: min.filename, start: min.start, end: max.end }; | ||
} | ||
function objLocations( obj ) { | ||
function _objLocations( obj ) { | ||
return (obj instanceof Array) ? obj.map( o => o.location ) : [ obj.location ]; | ||
} | ||
function constructSemanticLocationFromCsnPath(csnPath, model) { | ||
// Copy because this function shift()s from the path. | ||
csnPath = [ ...csnPath ]; | ||
const csnDictionaries = [ | ||
'args', 'params', 'enum', 'mixin', 'elements', 'actions', 'definitions', | ||
]; | ||
const queryProps = [ 'from', 'where', 'groupBy', 'having', 'orderBy', 'limit', 'offset' ]; | ||
let { query } = analyseCsnPath( | ||
csnPath, | ||
model | ||
); | ||
// remove definitions | ||
csnPath.shift(); | ||
const artName = csnPath.shift(); | ||
let currentThing = model.definitions[artName]; | ||
let result = `${ (currentThing && currentThing.kind) ? currentThing.kind : 'artifact' }:${ _quoted(artName) }`; | ||
if (query) | ||
query = queryDepth(currentThing.query, query); | ||
const elements = []; | ||
let inCsnDict = false; | ||
let inElement = false; | ||
let inAction = false; | ||
let inParam = false; | ||
let inKeys = false; | ||
let inRef = false; | ||
let inEnum = false; | ||
let inQuery = false; | ||
let inColumn = false; | ||
let inMixin = false; | ||
let inItems = false; | ||
// for top level actions | ||
if (currentThing.kind === 'action') | ||
inAction = true; | ||
for (const [ index, step ] of csnPath.entries()) { | ||
currentThing = currentThing[step]; | ||
if (csnDictionaries.includes(step) && !inCsnDict) { | ||
inCsnDict = true; | ||
switch (step) { | ||
case 'elements': | ||
if (!inElement){ | ||
inElement = true; | ||
// do not print intermediate items | ||
inItems = false; | ||
} | ||
break; | ||
case 'actions': | ||
inAction = true; | ||
break; | ||
case 'params': | ||
inParam = true; | ||
break; | ||
case 'enum': | ||
inElement = false; | ||
inEnum = true; | ||
break; | ||
case 'mixin': | ||
inMixin = true; | ||
inQuery = false; | ||
break; | ||
default: | ||
if (inElement) { | ||
// close element | ||
result += element(); | ||
inElement = false; | ||
} | ||
} | ||
} | ||
else if ( inQuery ) { | ||
if (step === 'SELECT') { | ||
if (!csnPath[index + 1]) { | ||
result += select(); | ||
} | ||
else if (queryProps.includes(csnPath[index + 1]) && !csnPath[index + 2]) { | ||
const clause = csnPath[index + 1]; | ||
result += select(); | ||
result += `/${ clause }`; | ||
} | ||
} | ||
else if (step === 'columns') { | ||
result += select(); | ||
result += '/column'; | ||
inColumn = true; | ||
inQuery = false; | ||
} | ||
} | ||
else if ( inMixin ) { | ||
if (step === 'on') { | ||
result += '/on'; | ||
break; | ||
} | ||
else { | ||
result += selectAndMixin(step); | ||
} | ||
} | ||
else if (inEnum) { | ||
result += elementAndEnum(step); | ||
} | ||
else if (!inElement && step === 'query') { | ||
inQuery = true; | ||
} | ||
else if (inElement && step === 'keys') { | ||
// close element | ||
result += `${ element() }/key`; | ||
inElement = false; | ||
inKeys = true; | ||
} | ||
else if (inElement && step === 'on') { | ||
// close element | ||
result += `${ element() }/on`; | ||
inElement = false; | ||
break; | ||
} | ||
else if (inElement && step === 'items') { | ||
// this is an element called items | ||
if (csnPath[index - 1] === 'elements' && elements[elements.length - 1] !== 'elements') { | ||
elements.push(step); | ||
} | ||
else { | ||
inElement = false; | ||
inItems = true; | ||
} | ||
} | ||
else if (inElement && step === 'elements') { | ||
// this is an element called elements | ||
if (csnPath[index - 1] === 'elements') | ||
elements.push(step); | ||
} | ||
else if (inItems && step === 'elements') { | ||
inElement = true; | ||
inItems = false; | ||
} | ||
else if ( inKeys || inColumn) { | ||
if (typeof step === 'number') { | ||
if (currentThing.as) | ||
result += `:${ _quoted(currentThing.as) }`; | ||
else | ||
result += inRef ? `:${ _quoted(currentThing) }` : currentThing.ref ? `:${ _quoted(currentThing.ref.join('.')) }` : ''; | ||
break; | ||
} | ||
if ( step === 'ref') | ||
inRef = true; | ||
} | ||
else if (inAction && step === 'returns') { | ||
result += `/${ step }`; | ||
break; | ||
} | ||
else if (inCsnDict) { | ||
if (inElement) | ||
elements.push(step); | ||
else if (inParam) | ||
result += param(step); | ||
else if (inAction) | ||
result += func(step); | ||
inCsnDict = false; | ||
} | ||
} | ||
if ( inItems ) | ||
result += `${ element() }/items`; | ||
else if ( inElement ) | ||
result += element(); | ||
return result; | ||
function select() { | ||
let s = '/select'; | ||
s += query.isOnlySelect ? '' : `:${ query.depth }`; | ||
return s; | ||
} | ||
function selectAndMixin(name) { | ||
return `${ select() }/mixin:${ _quoted(name) }`; | ||
} | ||
function element() { | ||
return `/element:${ _quoted(elements.join('.')) }`; | ||
} | ||
function param(name) { | ||
return `/param:${ _quoted(name) }`; | ||
} | ||
function func(name) { | ||
return `/function:${ _quoted(name) }`; | ||
} | ||
function elementAndEnum(name) { | ||
return `${ element() }/enum:${ _quoted(name) }`; | ||
} | ||
/** | ||
* Traverse rootQuery until targetQuery is found and count the depth, | ||
* check if targetQuery is only select in entity. | ||
*/ | ||
function queryDepth(rootQuery, targetQuery) { | ||
let targetQueryDepth = 1; | ||
let totalQueryDepth = 0; | ||
let isFound = false; | ||
traverseQuery(rootQuery, null, (q, querySelect) => { | ||
if ( querySelect ) | ||
totalQueryDepth += 1; | ||
if ( querySelect && !isFound) | ||
targetQueryDepth += 1; | ||
if (q === targetQuery) | ||
isFound = true; | ||
}); | ||
return { depth: targetQueryDepth, isOnlySelect: totalQueryDepth === 1 }; | ||
} | ||
} | ||
function _quoted( name ) { | ||
return (name) ? `"${ name.replace( /"/g, '""' ) }"` : '<?>'; // sync "; | ||
} | ||
module.exports = { | ||
@@ -119,2 +341,3 @@ combinedLocation, | ||
dictLocation, | ||
constructSemanticLocationFromCsnPath, | ||
}; |
const { CompileMessage, DebugCompileMessage } = require('../base/messages'); | ||
const { analyseCsnPath, traverseQuery } = require('../model/csnRefs'); | ||
const { constructSemanticLocationFromCsnPath } = require('./location'); | ||
@@ -66,205 +66,7 @@ /** | ||
} | ||
return constructSemanticLocation([...csnPath], model); | ||
return constructSemanticLocationFromCsnPath(csnPath, model); | ||
} | ||
} | ||
function constructSemanticLocation(csnPath, model) { | ||
const csnDictionaries = [ | ||
'args', 'params', 'enum', 'mixin', 'elements', 'actions', 'definitions', | ||
]; | ||
let { query } = analyseCsnPath( | ||
csnPath, | ||
model | ||
); | ||
// remove definitions | ||
csnPath.shift(); | ||
const artName = csnPath.shift(); | ||
let currentThing = model.definitions[artName]; | ||
let result = ((currentThing && currentThing.kind) ? currentThing.kind : 'artifact') + ':' + quoted(artName); | ||
if(query) { | ||
query = queryDepth(currentThing.query, query); | ||
} | ||
let elements = []; | ||
let inCsnDict, inElement, inAction, inParam, inKeys, inRef, inEnum, inQuery, inColumn, inMixin, inItems = false; | ||
// for top level actions | ||
if(currentThing.kind === 'action') | ||
inAction = true; | ||
for(const [index, step] of csnPath.entries()){ | ||
currentThing = currentThing[step]; | ||
if(csnDictionaries.includes(step) && !inCsnDict) { | ||
inCsnDict = true; | ||
switch (step) { | ||
case 'elements': | ||
if(!inElement) { | ||
inElement = true; | ||
} | ||
break; | ||
case 'actions': | ||
inAction = true; | ||
break; | ||
case 'params': | ||
inParam = true; | ||
break; | ||
case 'enum': | ||
inElement = false; | ||
inEnum = true; | ||
break | ||
case 'mixin': | ||
inMixin = true; | ||
inQuery = false; | ||
break; | ||
default: | ||
if (inElement) { | ||
// close element | ||
result += element(); | ||
inElement = false; | ||
} | ||
} | ||
} | ||
else if( inQuery ){ | ||
if(step === 'SELECT') { | ||
if(!csnPath[index + 1]) { | ||
result += select(); | ||
} else if (['from', 'where', 'groupBy', 'having', 'orderBy', 'limit', 'offset'].includes(csnPath[index + 1]) && !csnPath[index + 2]) { | ||
let clause = csnPath[index + 1]; | ||
result += select(); | ||
result += '/' + clause; | ||
} | ||
} | ||
else if(step === 'columns'){ | ||
result += select(); | ||
result += '/column'; | ||
inColumn = true; | ||
inQuery = false; | ||
} | ||
} | ||
else if( inMixin ) { | ||
if(step === 'on') { | ||
result += '/on'; | ||
break; | ||
} else { | ||
result += selectAndMixin(step); | ||
} | ||
} | ||
else if(inEnum) { | ||
result += elementAndEnum(step); | ||
} | ||
else if(!inElement && step === 'query'){ | ||
inQuery = true; | ||
} | ||
else if(inElement && step === 'keys') { | ||
// close element | ||
result += element() + '/key'; | ||
inElement = false; | ||
inKeys = true; | ||
} | ||
else if(inElement && step === 'on') { | ||
// close element | ||
result += element() + '/on'; | ||
inElement = false; | ||
break; | ||
} | ||
else if(inElement && step === 'items') { | ||
// this is an element called items | ||
if(csnPath[index - 1] === 'elements' && elements[elements.length - 1] !== 'elements'){ | ||
elements.push(step); | ||
} | ||
else { | ||
inElement = false; | ||
inItems = true; | ||
} | ||
} | ||
else if(inElement && step === 'elements') { | ||
// this is an element called elements | ||
if(csnPath[index - 1] === 'elements'){ | ||
elements.push(step); | ||
} | ||
} | ||
else if(inItems && step === 'elements') { | ||
inElement = true; | ||
inItems = false; | ||
} | ||
else if( inKeys || inColumn){ | ||
if (typeof step === 'number') { | ||
if(currentThing.as) { | ||
result += ':' + quoted(currentThing.as); | ||
}else { | ||
result += inRef ? ':' + quoted(currentThing) : currentThing.ref ? ':' + quoted(currentThing.ref.join('.')) : ''; | ||
} | ||
break; | ||
} | ||
if( step === 'ref'){ | ||
inRef = true; | ||
} | ||
} | ||
else if(inAction && step === 'returns') { | ||
result += '/' + step; | ||
break; | ||
} | ||
else if(inCsnDict) { | ||
if (inElement) | ||
elements.push(step); | ||
else if(inParam){ | ||
result += param(step); | ||
} else if(inAction){ | ||
result += func(step); | ||
} | ||
inCsnDict = false; | ||
} | ||
} | ||
if( inItems ) result += element() + '/items'; | ||
else if( inElement ) result += element(); | ||
return result; | ||
function select() { | ||
let s = '/select'; | ||
s += query.isOnlySelect ? '' : ':' + query.depth; | ||
return s; | ||
} | ||
function selectAndMixin(name) { | ||
return `${select()}/mixin:${quoted(name)}`; | ||
} | ||
function element() { | ||
return `/element:${quoted(elements.join('.'))}`; | ||
} | ||
function param(name) { | ||
return `/param:${quoted(name)}`; | ||
} | ||
function func(name) { | ||
return `/function:${quoted(name)}`; | ||
} | ||
function elementAndEnum(name) { | ||
return `${element()}/enum:${quoted(name)}`; | ||
} | ||
/** | ||
* Traverse rootQuery until targetQuery is found and count the depth, | ||
* check if targetQuery is only select in entity. | ||
*/ | ||
function queryDepth (rootQuery, targetQuery) { | ||
let targetQueryDepth = 1; | ||
let totalQueryDepth = 0; | ||
let isFound = false; | ||
traverseQuery(rootQuery, null, function countSelect(q, select) { | ||
if( select ) totalQueryDepth += 1; | ||
if( select && !isFound) targetQueryDepth += 1; | ||
if(q === targetQuery) isFound = true; | ||
}); | ||
return { depth: targetQueryDepth, isOnlySelect: totalQueryDepth === 1 }; | ||
} | ||
} | ||
function quoted( name ) { | ||
return (name) ? '"' + name.replace( /"/g, '""' ) + '"' : '<?>'; // sync "; | ||
} | ||
module.exports = buildMessage; | ||
@@ -149,3 +149,5 @@ 'use strict'; | ||
let assocType = isComposition(elem.type) ? 'composition' : 'association'; | ||
signal(warning`The ${assocType} "${elem.name.id}" has cardinality "to many" but no ON-condition`, elem.location, undefined, isNoDb ? 'to-many-no-on-noDB' : 'to-many-no-on'); | ||
signal(warning`The ${assocType} "${elem.name.id}" has cardinality "to many" but no ON-condition`, | ||
elem.cardinality.location || elem.name.location, undefined, | ||
isNoDb ? 'to-many-no-on-noDB' : 'to-many-no-on'); | ||
} | ||
@@ -152,0 +154,0 @@ } |
'use strict'; | ||
const { isBetaEnabled } = require("../../base/model"); | ||
// Only to be used with validator.js - a correct this value needs to be provided! | ||
@@ -10,3 +12,7 @@ | ||
if(member.default && this.csn.options.toOdata) { | ||
// TODO: On removal of the beta flag, filter out all illegal OData default values that are legal in the database | ||
// These are all sorts of expressions, functions, $now and turn the message into appropriate warnings | ||
// Or leave them as errors and get through the spec meeting. | ||
if(member.default && this.csn.options.toOdata && isBetaEnabled(this.csn.options, 'odataDefaultValues')) { | ||
// unary minus is xpr: [ "-", { val: ... } ] | ||
@@ -23,3 +29,3 @@ let def = member.default; | ||
if(!(def.val !== undefined || def['#'])) { | ||
this.signal(this.error`Default value must be a simple value`, path); | ||
this.signal(this.error`Default value must be a simple value for OData exposure`, path); | ||
} | ||
@@ -26,0 +32,0 @@ } |
@@ -193,3 +193,3 @@ // Consistency checker on model (XSN = augmented CSN) | ||
requires: [ 'kind', 'name' ], | ||
optional: [ 'elements', '$autoElement', '_finalType', '_deps' ], | ||
optional: [ 'elements', '$autoElement', '$uncheckedElements', '_finalType', '_deps' ], | ||
schema: { | ||
@@ -199,2 +199,3 @@ kind: { test: isString, enum: [ 'builtin' ] }, | ||
$autoElement: { test: isString }, | ||
$uncheckedElements: { test: isBoolean }, | ||
// missing location for normal "elements" | ||
@@ -201,0 +202,0 @@ elements: { test: TODO }, |
@@ -54,2 +54,5 @@ // The builtin artifacts of CDS | ||
/** | ||
* Variables that have special meaning in CDL/CSN. | ||
*/ | ||
const magicVariables = { // in SQL-92 | ||
@@ -66,2 +69,3 @@ CURRENT_DATE: {}, | ||
elements: { id: {}, locale: {} }, | ||
// Allow shortcut in CDL: `$user` becomes `$user.id` in CSN. | ||
$autoElement: 'id', | ||
@@ -73,2 +77,7 @@ }, // CDS-specific, not part of SQL | ||
$now: {}, // Dito | ||
$session: { | ||
// In ABAP CDS session variables are accessed in a generic way via | ||
// the pseudo variable $session. | ||
$uncheckedElements: true, | ||
}, | ||
}; | ||
@@ -221,2 +230,4 @@ | ||
art.$autoElement = magic.$autoElement; | ||
if (magic.$uncheckedElements) | ||
art.$uncheckedElements = magic.$uncheckedElements; | ||
// setProp( art, '_finalType', art ); | ||
@@ -223,0 +234,0 @@ } |
@@ -615,2 +615,6 @@ // Compiler functions and utilities shared across all phases | ||
} | ||
else if (art.$uncheckedElements) { | ||
// do not check any elements of the path, e.g. $session | ||
return art; | ||
} | ||
else { | ||
@@ -617,0 +621,0 @@ const env = (spec.envFn || environment)( art, item.location, user, spec.assoc ); |
@@ -650,3 +650,3 @@ 'use strict'; | ||
if(!edmUtils.isSimpleIdentifier(identifier)){ | ||
message(signal.warning, context, | ||
message(signal.error, context, | ||
`OData annotation term "${identifier}" must consist of one or more dot separated simple identifiers (each starting with a letter or underscore, followed by at most 127 letters)`) | ||
@@ -664,3 +664,3 @@ } | ||
if (!edmUtils.isSimpleIdentifier(p[1])) { | ||
message(signal.warning, context, | ||
message(signal.error, context, | ||
`OData annotation qualifier "${p[1]}" must start with a letter or underscore, followed by at most 127 letters, underscores or digits`); | ||
@@ -667,0 +667,0 @@ } |
@@ -850,5 +850,7 @@ // @ts-nocheck | ||
} | ||
this[`Default${this.v4 ? 'Value' : ''}`] = ['cds.Boolean', 'cds.Binary', 'cds.LargeBinary', 'cds.Integer64', 'cds.Integer'].includes(csn.type) | ||
if(defVal !== undefined) { | ||
this[`Default${this.v4 ? 'Value' : ''}`] = ['cds.Boolean', 'cds.Binary', 'cds.LargeBinary', 'cds.Integer64', 'cds.Integer'].includes(csn.type) | ||
? defVal | ||
: edmUtils.escapeString(defVal); | ||
} | ||
} | ||
@@ -855,0 +857,0 @@ } |
@@ -354,4 +354,6 @@ 'use strict'; | ||
let structParent = def.items || def; | ||
// Iterate all struct elements | ||
forEachGeneric(def, 'elements', (element, elementName) => { | ||
forEachGeneric(structParent, 'elements', (element, elementName) => { | ||
initElement(element, elementName, def); | ||
@@ -361,3 +363,3 @@ | ||
signal( | ||
signal.warning`OData property name: "${elementName}" must start with a letter or underscore, followed by at most 127 letters, underscores or digits`, | ||
signal.error`OData property name: "${elementName}" must start with a letter or underscore, followed by at most 127 letters, underscores or digits`, | ||
['definitions', def.name, 'elements', elementName] | ||
@@ -837,3 +839,3 @@ ); | ||
function signalIllegalIdentifier(identifier, path, kind, msg) { | ||
signal(signal.warning`OData ${kind} ${identifier ? `: "${identifier}"` : ''} ${ msg ? msg : 'must start with a letter or underscore, followed by at most 127 letters, underscores or digits' }`, path); | ||
signal(signal.error`OData ${kind} ${identifier ? `: "${identifier}"` : ''} ${ msg ? msg : 'must start with a letter or underscore, followed by at most 127 letters, underscores or digits' }`, path); | ||
} | ||
@@ -840,0 +842,0 @@ |
@@ -352,3 +352,3 @@ 'use strict'; | ||
for(let fk of assocCsn.keys) { | ||
let realFk = assocCsn._parent.elements[fk.$generatedFieldName]; | ||
let realFk = assocCsn._parent.items ? assocCsn._parent.items.elements[fk.$generatedFieldName] : assocCsn._parent.elements[fk.$generatedFieldName]; | ||
let pk = assocCsn._target.elements[fk.ref[0]]; | ||
@@ -355,0 +355,0 @@ if(pk && pk.key && isConstraintCandidate(pk) && isConstraintCandidate(realFk)) |
@@ -1273,3 +1273,3 @@ // CSN frontend - transform CSN into XSN | ||
const keySpec = { dictionaryOf: translations, prop: langKey }; | ||
return dictionaryOf( translations )( val, keySpec, xsn, csn ); | ||
return dictionaryOf( translations )( val, keySpec ); | ||
} | ||
@@ -1276,0 +1276,0 @@ |
@@ -322,3 +322,3 @@ // Transform augmented CSN into compact "official" CSN | ||
for (const name of Object.keys( model.definitions ).sort()) { | ||
for (const name of Object.keys( model.definitions || {} ).sort()) { | ||
const art = model.definitions[name]; | ||
@@ -325,0 +325,0 @@ |
@@ -52,3 +52,3 @@ // Generic ANTLR parser class with AST-building functions | ||
setMaxCardinality, | ||
makeAnnotationIdentifier, | ||
pushIdent, | ||
handleComposition, | ||
@@ -339,2 +339,3 @@ hanaFlavorOnly, | ||
if (sign) { | ||
// TODO: warning for space in between | ||
let end = location.end; | ||
@@ -402,18 +403,25 @@ location = this.startLocation( sign ); | ||
function makeAnnotationIdentifier( at, identifier ) { | ||
if (!identifier) | ||
return identifier; | ||
const atLoc = { location: this.startLocation( at ) }; | ||
if (identifier.id) | ||
identifier.id = '@' + identifier.id; | ||
// `.stop` does not point to *after* the character as opposed to XSN locations. | ||
if (at.stop + 1 !== identifier.location.start.offset) { | ||
this.message("syntax-anno-space", identifier.location, {}, 'Error', | ||
'Expected identifier after \'@\' but found whitespace') | ||
function pushIdent( path, ident, prefix ) { | ||
if (!ident) { | ||
path.broken = true; | ||
} | ||
identifier.location = this.combinedLocation( atLoc, identifier ); | ||
return identifier; | ||
else if (!prefix) { | ||
path.push( ident ); | ||
} | ||
else { | ||
const { start, end } = this.tokenLocation( prefix ); | ||
if (end.line !== ident.location.start.line || // end.offset will disappear | ||
end.column !== ident.location.start.column) { | ||
const wsLocation = { | ||
filename: ident.location.filename, | ||
start: end, // ! | ||
end: { ...ident.location.start }, | ||
}; | ||
this.message( 'syntax-anno-space', wsLocation, {}, 'Error', // TODO: really Error? | ||
'Expected identifier after \'@\' but found whitespace' ); | ||
} | ||
ident.location.start = start; | ||
ident.id = prefix.text + ident.id; | ||
path.push( ident ); | ||
} | ||
} | ||
@@ -512,2 +520,3 @@ | ||
// Then this check can be removed | ||
// @ts-ignore `location` may exist on props even though it's an array. | ||
this.message( null, props.location || location || this.startLocation( this._ctx.start ), {}, | ||
@@ -514,0 +523,0 @@ 'Error', 'Remove the parentheses around the expression' ); |
@@ -13,3 +13,3 @@ 'use strict' | ||
* @callback genericCallback | ||
* @param {CSN.Member} art | ||
* @param {CSN.Artifact} art | ||
* @param {CSN.FQN} name Artifact Name | ||
@@ -147,3 +147,3 @@ * @param {string} prop Dictionary Property | ||
function isManagedAssociationElement(node) { | ||
return node.target !== undefined && node.on === undefined; | ||
return node.target !== undefined && node.on === undefined && node.keys; | ||
} | ||
@@ -409,3 +409,3 @@ | ||
* Deeply clone the given CSN model and return it. | ||
* @param {CSN.Model} csn | ||
* @param {object} csn | ||
*/ | ||
@@ -444,3 +444,3 @@ function cloneCsn(csn){ | ||
* @param {genericCallback|genericCallback[]} callback | ||
* @param {string[]} [path] | ||
* @param {CSN.Path} [path] | ||
* @param {boolean} [ignoreIgnore] | ||
@@ -657,3 +657,3 @@ */ | ||
* @param {CSN.Element} elementCsn | ||
* @param {CSN.Options & { isStructFormat: boolean}} options EDM specific options | ||
* @param {CSN.Options & CSN.OdataOptions} options EDM specific options | ||
*/ | ||
@@ -751,3 +751,3 @@ function isEdmPropertyRendered(elementCsn, options) { | ||
* @param {object} csn CSN to enrich in-place | ||
* @param {object} customTransformers Map of prop to transform and function to apply | ||
* @param {object} customTransformers Map of prop to transform and function to apply | ||
* @param {Function[]} artifactTransformers Transformations to run on the artifacts, like forEachDefinition | ||
@@ -754,0 +754,0 @@ * @param {Boolean} skipIgnore Wether to skip _ignore elements or not |
@@ -25,2 +25,3 @@ 'use strict'; | ||
forEachDefinition(csn, (def, definitionName) => { | ||
/** @type {CSN.Path} */ | ||
let root = ['definitions', definitionName]; | ||
@@ -27,0 +28,0 @@ forEachMemberRecursively(def, (element, elementName, _prop, subpath, parent) => { |
@@ -27,3 +27,3 @@ 'use strict'; | ||
if (def.kind === 'type') { | ||
forEachMember(def, (element, elementName, propertyName) => { | ||
forEachMember(def, (element, elementName, propertyName, path) => { | ||
if (propertyName === 'elements') { | ||
@@ -67,3 +67,3 @@ exposeStructTypeOf(element, `${defName}.${elementName}`, getServiceOfArtifact(defName, services), `${defName.replace(/\./g, '_')}_${elementName}`, structuredOData, path); | ||
// then expose T and assign it do the member. | ||
forEachMemberRecursively(def, (member, memberName) => { | ||
forEachMemberRecursively(def, (member, memberName, prop, path) => { | ||
// we do apply array of exposure logic on actions/functions | ||
@@ -76,5 +76,5 @@ // and on params and returns of action/function always, | ||
if (structuredOData) | ||
exposeArrayOfTypeOf(member, memberName, serviceName, `${defNameWithoutServiceName(defName, serviceName)}_${memberName}`); | ||
exposeArrayOfTypeOf(member, memberName, serviceName, `${defNameWithoutServiceName(defName, serviceName)}_${memberName}`, path); | ||
else if (options.toOdata.version === 'v4' && !isAction) { | ||
exposeArrayOfTypeOf(member, memberName, serviceName, `${defNameWithoutServiceName(defName, serviceName)}_${memberName}`); | ||
exposeArrayOfTypeOf(member, memberName, serviceName, `${defNameWithoutServiceName(defName, serviceName)}_${memberName}`, path); | ||
} | ||
@@ -231,3 +231,3 @@ } | ||
if (node.items && !node.type) { | ||
exposeStructTypeOf(node, memberName, service, artificialName, true, path); | ||
exposeStructTypeOf(node.items, memberName, service, artificialName, true, path.concat('items')); | ||
} | ||
@@ -244,3 +244,3 @@ // we can have both of the 'type' and 'items' in the cases: | ||
let typeId = `${service}.${node.type}`; | ||
let newType = exposeArrayedType(csnUtils.getFinalTypeDef(node.type), typeId); | ||
let newType = exposeArrayedType(node.items || csnUtils.getFinalTypeDef(node.type).items, typeId); | ||
// When we have in the model something like: | ||
@@ -258,3 +258,3 @@ // type Foo: array of Bar; type Bar: { qux: Integer }; | ||
function exposeArrayedType(typeDef, typeId) { | ||
function exposeArrayedType(items, typeId) { | ||
let newType = csn.definitions[typeId]; | ||
@@ -274,3 +274,3 @@ if (newType) { | ||
// copy over the items | ||
newType.items = cloneCsn(typeDef.items); | ||
newType.items = cloneCsn(items); | ||
csn.definitions[typeId] = newType; | ||
@@ -290,3 +290,3 @@ exposedStructTypes.push(typeId); | ||
if (csnUtils.isStructured(finalType)) { | ||
def.items.elements = cloneCsn(finalType.elements); | ||
if (!def.items.elements) def.items.elements = cloneCsn(finalType.elements); | ||
delete def.items.type; | ||
@@ -293,0 +293,0 @@ } |
@@ -39,4 +39,4 @@ const { | ||
* Return the definition name, without the prefixed service name | ||
* @param {String} name | ||
* @param {String} service | ||
* @param {string} name | ||
* @param {string} service | ||
*/ | ||
@@ -51,4 +51,4 @@ function defNameWithoutServiceName(name, service) { | ||
* name where the given artifact resides | ||
* @param {String} artName | ||
* @param {Array} services | ||
* @param {string} artName | ||
* @param {string[]} services | ||
*/ | ||
@@ -72,4 +72,4 @@ function getServiceOfArtifact(artName, services) { | ||
* the artifact is part of the service or not | ||
* @param {String} artName | ||
* @param {Array} services | ||
* @param {string} artName | ||
* @param {string[]} services | ||
*/ | ||
@@ -84,4 +84,4 @@ function isArtifactInSomeService(artName, services) { | ||
* the artifact is localized and part of the service | ||
* @param {String} artName | ||
* @param {Array} services | ||
* @param {string} artName | ||
* @param {string[]} services | ||
*/ | ||
@@ -88,0 +88,0 @@ function isLocalizedArtifactInService(artName, services) { |
@@ -558,3 +558,3 @@ 'use strict'; | ||
// Create the 'DRAFT.DraftAdministrativeData' entity | ||
let artifact = { | ||
const artifact = { | ||
kind: 'entity', | ||
@@ -561,0 +561,0 @@ elements: Object.create(null), |
@@ -17,3 +17,3 @@ // Util functions for operations usually used with files. | ||
module.exports = { | ||
splitLines | ||
} | ||
splitLines, | ||
}; |
@@ -1,22 +0,24 @@ | ||
/// | ||
/// This file is used for color output to stderr and stdout. | ||
/// Use `term.error`, `term.warn` and `term.info` as they use color output | ||
/// per default if the process runs in a TTY, i.e. stdout as well as | ||
/// stderr are TTYs. stderr/stdout are no TTYs if they (for example) | ||
/// are piped to another process or written to file: | ||
/// | ||
/// node myapp.js # stdout.isTTY: true, stderr.isTTY: true | ||
/// node myapp.js | cat # stdout.isTTY: undefined, stderr.isTTY: true | ||
/// node myapp.js |& cat # stdout.isTTY: undefined, stderr.isTTY: undefined | ||
/// node myapp.js > out.txt # stdout.isTTY: undefined, stderr.isTTY: true | ||
/// node myapp.js 2> out.txt # stdout.isTTY: true, stderr.isTTY: undefined | ||
/// | ||
// | ||
// This file is used for color output to stderr and stdout. | ||
// Use `term.error`, `term.warn` and `term.info` as they use color output | ||
// per default if the process runs in a TTY, i.e. stdout as well as | ||
// stderr are TTYs. stderr/stdout are no TTYs if they (for example) | ||
// are piped to another process or written to file: | ||
// | ||
// node myapp.js # stdout.isTTY: true, stderr.isTTY: true | ||
// node myapp.js | cat # stdout.isTTY: undefined, stderr.isTTY: true | ||
// node myapp.js |& cat # stdout.isTTY: undefined, stderr.isTTY: undefined | ||
// node myapp.js > out.txt # stdout.isTTY: undefined, stderr.isTTY: true | ||
// node myapp.js 2> out.txt # stdout.isTTY: true, stderr.isTTY: undefined | ||
// | ||
const stderrHasColor = process.stderr.isTTY | ||
const stdoutHasColor = process.stdout.isTTY | ||
'use strict'; | ||
let hasColor = stdoutHasColor && stderrHasColor | ||
const stderrHasColor = process.stderr.isTTY; | ||
const stdoutHasColor = process.stdout.isTTY; | ||
let hasColor = stdoutHasColor && stderrHasColor; | ||
module.exports.useColor = (mode) => { | ||
switch(mode) { | ||
switch (mode) { | ||
case false: | ||
@@ -31,3 +33,3 @@ case 'never': | ||
default: | ||
hasColor = stdoutHasColor && stderrHasColor | ||
hasColor = stdoutHasColor && stderrHasColor; | ||
break; | ||
@@ -38,3 +40,3 @@ } | ||
// https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences | ||
const t = module.exports.codes = { | ||
const t = { | ||
reset: '\x1b[0m', // Default | ||
@@ -48,17 +50,15 @@ bold: '\x1b[1m', // Bold/Bright | ||
cyan: '\x1b[36m', // Foreground Cyan | ||
} | ||
}; | ||
const as = module.exports.as = (codes, o) => { | ||
return hasColor ? (codes + o + t.reset) : ('' + o) | ||
} | ||
const as = (codes, o) => (hasColor ? (codes + o + t.reset) : (`${ o }`)); | ||
const asError = module.exports.error = o => as(t.red + t.bold, o) | ||
const asWarning = module.exports.warn = o => as(t.yellow, o) | ||
const asInfo = module.exports.info = o => as(t.green, o) | ||
const asHelp = module.exports.help = o => as(t.cyan, o) | ||
module.exports.underline = o => as(t.underline, o) | ||
module.exports.bold = o => as(t.bold, o) | ||
const asError = o => as(t.red + t.bold, o); | ||
const asWarning = o => as(t.yellow, o); | ||
const asInfo = o => as(t.green, o); | ||
const asHelp = o => as(t.cyan, o); | ||
module.exports.underline = o => as(t.underline, o); | ||
module.exports.bold = o => as(t.bold, o); | ||
module.exports.asSeverity = (severity, msg) => { | ||
switch((severity + '').toLowerCase()) { | ||
switch ((`${ severity }`).toLowerCase()) { | ||
case 'error': return asError(msg); | ||
@@ -71,2 +71,9 @@ case 'warning': return asWarning(msg); | ||
} | ||
} | ||
}; | ||
module.exports.codes = t; | ||
module.exports.as = as; | ||
module.exports.error = asError; | ||
module.exports.warn = asWarning; | ||
module.exports.info = asInfo; | ||
module.exports.help = asHelp; |
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
/** | ||
@@ -13,3 +15,3 @@ * A single TimeTrace encapsulates the runtime of a selected code frame. | ||
*/ | ||
constructor(id){ | ||
constructor(id) { | ||
let startTime; | ||
@@ -21,7 +23,7 @@ /** | ||
*/ | ||
this.start = function(indent){ | ||
this.start = function start(indent) { | ||
// eslint-disable-next-line no-console | ||
console.error(`${' '.repeat((indent)*2)}${id} started`); | ||
console.error(`${ ' '.repeat((indent) * 2) }${ id } started`); | ||
startTime = process.hrtime(); | ||
} | ||
}; | ||
@@ -33,10 +35,10 @@ /** | ||
*/ | ||
this.stop = function(indent){ | ||
this.stop = function stop(indent) { | ||
const endTime = process.hrtime(startTime); | ||
const base = `${' '.repeat(indent*2)}${id} took:`; | ||
// eslint-disable-next-line no-console | ||
console.error( `${base}${' '.repeat(60-base.length)} %ds %dms`, endTime[0], endTime[1] / 1000000); | ||
} | ||
const base = `${ ' '.repeat(indent * 2) }${ id } took:`; | ||
// eslint-disable-next-line no-console | ||
console.error( `${ base }${ ' '.repeat(60 - base.length) } %ds %dms`, endTime[0], endTime[1] / 1000000); | ||
}; | ||
} | ||
} | ||
} | ||
@@ -58,3 +60,3 @@ /** | ||
*/ | ||
constructor(){ | ||
constructor() { | ||
this.tracestack = []; | ||
@@ -70,10 +72,12 @@ } | ||
*/ | ||
start(id){ | ||
try{ | ||
start(id) { | ||
try { | ||
const b = new TimeTrace(id); | ||
this.tracestack.push(b); | ||
b.start(this.tracestack.length-1); | ||
} catch(e){ | ||
console.error(`Starting time trace with id ${id} failed: ${e}`) | ||
b.start(this.tracestack.length - 1); | ||
} | ||
catch (e) { | ||
// eslint-disable-next-line no-console | ||
console.error(`Starting time trace with id ${ id } failed: ${ e }`); | ||
} | ||
} | ||
@@ -87,12 +91,15 @@ | ||
*/ | ||
stop(){ | ||
stop() { | ||
try { | ||
const current = this.tracestack.pop(); | ||
current.stop(this.tracestack.length); | ||
} catch (e){ | ||
console.error(`Stopping time trace failed: ${e}`); | ||
} | ||
catch (e) { | ||
// eslint-disable-next-line no-console | ||
console.error(`Stopping time trace failed: ${ e }`); | ||
} | ||
} | ||
} | ||
module.exports = (process && process.env && process.env.CDSC_TIMETRACING !== undefined) ? new TimeTracer() : { start: () => {}, stop: () => {}}; | ||
const doTimeTrace = process && process.env && process.env.CDSC_TIMETRACING !== undefined; | ||
module.exports = doTimeTrace ? new TimeTracer() : { start: () => {}, stop: () => {} }; |
{ | ||
"name": "@sap/cds-compiler", | ||
"version": "1.42.2", | ||
"version": "1.43.0", | ||
"description": "CDS (Core Data Services) compiler and backends", | ||
@@ -5,0 +5,0 @@ "homepage": "https://cap.cloud.sap/", |
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 not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
3750042
126
71692