@sap/cds-compiler
Advanced tools
Comparing version 1.13.4 to 1.15.0
@@ -16,2 +16,4 @@ #!/usr/bin/env node | ||
const compiler = require('../lib/main'); | ||
const { compactModel } = require('../lib/json/to-csn'); | ||
const { toOdataWithCsn, toHanaWithCsn } = require('../lib/backends'); | ||
var util = require('util'); | ||
@@ -130,3 +132,21 @@ var fs = require('fs'); | ||
} | ||
compiler.compile( args, undefined, options ) | ||
// special case when we want to call a transformer directly | ||
// without compiling before that (for now only available for toOdata, toHana, toSql) | ||
// works only with combination of --beta-mode in the transformers | ||
if (options.skipCompile && ['toOdata', 'toHana', 'toSql'].includes(command)) { | ||
if (args.length !== 1) | ||
throw new Error(`--skip-compile option works with only one file`); | ||
let csn = ''; | ||
try { | ||
csn = JSON.parse(fs.readFileSync(args[0])); | ||
} catch (e) { | ||
throw new Error(`${args[0]} must be a valid json file`); | ||
} | ||
options.betaMode = true; | ||
csn.options = options; | ||
commands[command](csn); | ||
} else { | ||
compiler.compile( args, undefined, options ) | ||
.then( commands[command] ) | ||
@@ -136,2 +156,4 @@ .then( displayMessages, displayErrors ) | ||
} | ||
if (unknownOptions.length) { | ||
@@ -163,3 +185,3 @@ let out = process.stdout; | ||
function toHana( model ) { | ||
let hanaResult = compiler.toHana(model); | ||
let hanaResult = options.newTransformers ? toHanaWithCsn(compactModel(model, options), options) : compiler.toHana(model); | ||
for (let name in hanaResult.hdbcds) { | ||
@@ -177,3 +199,3 @@ writeToFileOrDisplay(options.out, name + '.hdbcds', hanaResult.hdbcds[name]); | ||
function toOdata( model ) { | ||
let odataResult = compiler.toOdata(model); | ||
let odataResult = options.newTransformers ? toOdataWithCsn(compactModel(model, options), options) : compiler.toOdata(model); | ||
for (let serviceName in odataResult.services) { | ||
@@ -277,4 +299,2 @@ // <service>_metadata.xml (metadata) | ||
console.error( util.inspect( reveal( err.model ), false, null )); | ||
else if (options.internalMsg) | ||
console.error( util.inspect( reveal( err.model ).messages, { depth: null, maxArrayLength: null} )); | ||
else | ||
@@ -302,3 +322,5 @@ displayMessages( err.model, err.errors ); | ||
for (let msg of messages) { | ||
if (messageLevels[ msg.severity ] <= options.warning) | ||
if (options.internalMsg) | ||
console.error( util.inspect( msg, { depth: null, maxArrayLength: null} ) ); | ||
else if (messageLevels[ msg.severity ] <= options.warning) | ||
console.error( compiler.messageString( msg, normalizeFilename, !options.showMessageId, !options.testMode ) ); | ||
@@ -321,3 +343,3 @@ } | ||
} | ||
else { | ||
else if (!options.lintMode && !options.internalMsg) { | ||
writeToFileOrDisplay(options.out, name + '.json', compiler.toCsn(model), true); | ||
@@ -333,2 +355,4 @@ } | ||
function writeToFileOrDisplay(dir, filename, content, omitHeadline = false) { | ||
if (options.lintMode || options.internalMsg) | ||
return; | ||
filename = filename.replace(/[:/\\]/g, '_'); | ||
@@ -335,0 +359,0 @@ if (!(content instanceof String || typeof content == 'string')) { |
@@ -16,3 +16,3 @@ #!/usr/bin/env node | ||
const commands = { | ||
complete | ||
complete, lint | ||
} | ||
@@ -65,3 +65,3 @@ | ||
let fname = path.resolve( '', file ); | ||
compiler.compile( [file], '', { attachValidNames: true, betaMode: true, newRedirectImpl: true } , { [fname]: src } ) | ||
compiler.compile( [file], '', { attachValidNames: true, lintMode: true, betaMode: true } , { [fname]: src } ) | ||
.then( ident, ident ); | ||
@@ -84,2 +84,20 @@ } | ||
function lint( err, buf ) { | ||
if (err) | ||
return usage( err ); | ||
let fname = path.resolve( '', file ); | ||
compiler.compile( [file], '', { lintMode: true, betaMode: true } , { [fname]: buf } ) | ||
.then( display, display ); | ||
return true; | ||
function display( xsnOrErr ) { | ||
let messages = xsnOrErr.messages || xsnOrErr.errors; | ||
if (!messages) | ||
return usage( xsnOrErr ); | ||
for (let msg of messages) | ||
console.log( compiler.messageString( msg ) ); | ||
return true; | ||
} | ||
} | ||
function tokensAt( buf, offset, symbol ) { | ||
@@ -86,0 +104,0 @@ let src = buf.substring( 0, offset ) + '≠' + buf.substring( offset ); |
@@ -8,2 +8,3 @@ 'use strict'; | ||
const { transformForHana } = require('./transform/forHana'); | ||
const { transformForHanaWithCsn } = require('./transform/forHanaNew'); | ||
const { compact, compactSorted } = require('./json/compactor') | ||
@@ -15,4 +16,5 @@ const { compactModel } = require('./json/to-csn') | ||
const { transform4odata, getServiceNames } = require('./transform/forOdata'); | ||
const { transform4odataWithCsn } = require('./transform/forOdataNew'); | ||
const csn2edm = require('./edm/csn2edm'); | ||
const { mergeOptions, verifyModelSignature } = require('./model/modelUtils'); | ||
const { mergeOptions } = require('./model/modelUtils'); | ||
const { transformTntExtensions } = require('./transform/tntSpecific'); | ||
@@ -86,5 +88,2 @@ const alerts = require('./base/alerts'); | ||
// Verify model signature (if enabled in options) | ||
verifyModelSignature(model, 'toHana'); | ||
// Prepare model for HANA (transferring the options to forHana, and setting 'dialect' to 'hana', because 'toHana' is only used for that) | ||
@@ -110,2 +109,62 @@ let forHanaAugmented = transformForHana(model, mergeOptions(options, { forHana: { dialect: 'hana' } }, { forHana : options.toHana } )); | ||
// The twin of the toHana function but using CSN as a model | ||
function toHanaWithCsn(model, options) { | ||
// In case of API usage the options are in the 'options' argument | ||
// put the OData specific options under the 'options.toHana' wrapper | ||
// and leave the rest under 'options' | ||
if (options && !options.toHana) { | ||
_wrapRelevantOptionsForCmd(options, 'toHana'); | ||
} | ||
// Provide defaults and merge options with those from model | ||
options = mergeOptions({ toHana : getDefaultBackendOptions().toHana }, model.options, options); | ||
// Provide something to generate if nothing else was given (conditional default) | ||
if (!options.toHana.src && !options.toHana.csn) { | ||
options.toHana.src = true; | ||
} | ||
// Backward compatibility for old naming modes | ||
// FIXME: Remove after a few releases | ||
const { warning, signal } = alerts(model); | ||
if (options.toHana.names == 'flat') { | ||
signal(warning`Option "{ toHana.names: 'flat' }" is deprecated, use "{ toHana.names: 'plain' }" instead`); | ||
options.toHana.names = 'plain'; | ||
} | ||
else if (options.toHana.names == 'deep') { | ||
signal(warning`Option "{ toHana.names: 'deep' }" is deprecated, use "{ toHana.names: 'quoted' }" instead`); | ||
options.toHana.names = 'quoted'; | ||
} | ||
// Verify options | ||
optionProcessor.verifyOptions(options, 'toHana').map(complaint => signal(warning`${complaint}`)); | ||
// Special case: For naming variant 'hdbcds' in combination with 'toHana' (and only there!), 'forHana' | ||
// must leave namespaces, structs and associations alone. | ||
if (options.toHana.names == 'hdbcds') { | ||
options = mergeOptions(options, { forHana : { keepNamespaces: true, keepStructsAssocs: true } }); | ||
} | ||
// Prepare model for HANA (transferring the options to forHana, and setting 'dialect' to 'hana', because 'toHana' is only used for that) | ||
let forHanaAugmented = transformForHanaWithCsn(model, mergeOptions(options, { forHana: { dialect: 'hana' } }, { forHana : options.toHana } )); | ||
// Assemble result | ||
let result = {}; | ||
if (options.toHana.src) { | ||
/* eslint no-console:off */ | ||
console.log('### toCdsSource() not yet working on compact CSN'); | ||
//result.hdbcds = toCdsSource(forHanaAugmented); | ||
} | ||
if (options.toHana.csn) { | ||
result._augmentedCsn = forHanaAugmented; | ||
result.csn = options.newCsn === false ? compactSorted(forHanaAugmented) : compactModel(forHanaAugmented); | ||
} | ||
// Transfer warnings (errors would have resulted in an exception before we come here) | ||
if (forHanaAugmented.messages && forHanaAugmented.messages.length > 0) { | ||
result.messages = forHanaAugmented.messages; | ||
} | ||
return result; | ||
} | ||
// ----------- toOdata ----------- | ||
@@ -190,3 +249,3 @@ | ||
signal(warning`Option "{ toOdata.names: 'deep' }" is deprecated, use "{ toOdata.names: 'quoted' }" instead`); | ||
options.toOdata.names = 'deep'; | ||
options.toOdata.names = 'quoted'; | ||
} | ||
@@ -197,5 +256,2 @@ | ||
// Verify model signature (if enabled in options) | ||
verifyModelSignature(model, 'toOdata'); | ||
// Perform extra-magic for TNT if requested | ||
@@ -259,2 +315,91 @@ if ((model.options && model.options.tntFlavor) || options.tntFlavor) { | ||
// Generate ODATA for CSN `model` using `options`. | ||
// The twin of the toOdata function but using CSN | ||
function toOdataWithCsn(model, options) { | ||
const { error, warning, signal } = alerts(model); | ||
// In case of API usage the options are in the 'options' argument | ||
// put the OData specific options under the 'options.toOdata' wrapper | ||
// and leave the rest under 'options' | ||
if (options && !options.toOdata) { | ||
_wrapRelevantOptionsForCmd(options, 'toOdata'); | ||
} | ||
// Provide defaults and merge options with those from model | ||
options = mergeOptions({ toOdata : getDefaultBackendOptions().toOdata }, model.options, options); | ||
// Provide something to generate if nothing else was given (conditional default) | ||
if (!options.toOdata.xml && !options.toOdata.json && !options.toOdata.csn) { | ||
options.toOdata.xml = true; | ||
} | ||
if (!options.toOdata.separate && !options.toOdata.combined) { | ||
options.toOdata.combined = true; | ||
} | ||
// Backward compatibility for old naming modes | ||
// FIXME: Remove after a few releases | ||
if (options.toOdata.names == 'flat') { | ||
signal(warning`Option "{ toOdata.names: 'flat' }" is deprecated, use "{ toOdata.names: 'plain' }" instead`); | ||
options.toOdata.names = 'plain'; | ||
} | ||
else if (options.toOdata.names == 'deep') { | ||
signal(warning`Option "{ toOdata.names: 'deep' }" is deprecated, use "{ toOdata.names: 'quoted' }" instead`); | ||
options.toOdata.names = 'deep'; | ||
} | ||
// Verify options | ||
optionProcessor.verifyOptions(options, 'toOdata').map(complaint => signal(warning`${complaint}`)); | ||
// Perform extra-magic for TNT if requested | ||
if ((model.options && model.options.tntFlavor) || options.tntFlavor) { | ||
model = transformTntExtensions(model, options); | ||
} | ||
// Prepare model for ODATA processing | ||
let forOdataCSN = transform4odataWithCsn(model, options); | ||
// Assemble result object | ||
let result = { | ||
services: Object.create(null), | ||
messages: model.messages, | ||
} | ||
if (options.toOdata.csn) { | ||
result.csn = forOdataCSN; | ||
} | ||
// Create annotations and metadata once per service | ||
if (options.toOdata.xml || options.toOdata.json) { | ||
// Compact the model | ||
setProp(forOdataCSN, 'messages', forOdataCSN.messages); | ||
for (let serviceName of getServiceNames(model)) { | ||
// FIXME: Unify handling of version and tntFlavor (use original options) | ||
let l_edm = csn2edm(forOdataCSN, serviceName, options); | ||
result.services[serviceName] = {}; | ||
if (options.toOdata.xml) { | ||
if (options.toOdata.separate) { | ||
result.services[serviceName].annotations = l_edm.toXML('annotations'); | ||
result.services[serviceName].metadata = l_edm.toXML('metadata'); | ||
} | ||
if (options.toOdata.combined) { | ||
result.services[serviceName].combined = l_edm.toXML('all'); | ||
} | ||
} | ||
if (options.toOdata.json) { | ||
// JSON output is not available for ODATA V2 | ||
if (options.toOdata.version == 'v2') { | ||
signal(error`ODATA JSON output is not available for ODATA V2`); | ||
} | ||
// FIXME: Why only metadata_json - isn't this rather a 'combined_json' ? If so, rename it! | ||
result.services[serviceName].metadata_json = l_edm.toJSON(); | ||
} | ||
} | ||
} | ||
// Transfer warnings (errors would have resulted in an exception before we come here) | ||
if (forOdataCSN.messages && forOdataCSN.messages.length > 0) { | ||
result.messages = forOdataCSN.messages; | ||
} | ||
return result; | ||
} | ||
// Generate edmx for given 'service' based on 'csn' (new-style compact, already prepared for OData) | ||
@@ -309,4 +454,2 @@ // using 'options' | ||
optionProcessor.verifyOptions(options, 'toCdl').map(complaint => signal(warning`${complaint}`)); | ||
// Verify model signature (if enabled in options) | ||
verifyModelSignature(model, 'toCdl'); | ||
return toCdsSource(model, options); | ||
@@ -363,4 +506,2 @@ } | ||
optionProcessor.verifyOptions(options, 'toSwagger').map(complaint => signal(warning`${complaint}`)); | ||
// Verify model signature (if enabled in options) | ||
verifyModelSignature(model, 'toSwagger'); | ||
// Actual implementation | ||
@@ -442,5 +583,2 @@ return csnToSwagger(model, options); | ||
// Verify model signature (if enabled in options) | ||
verifyModelSignature(model, 'toSql'); | ||
// FIXME: Currently, '--to-sql' implies transformation for HANA (transferring the options to forHana) | ||
@@ -571,5 +709,2 @@ let forHanaOptions = options.toSql; | ||
// Verify model signature (if enabled in options) | ||
verifyModelSignature(model, 'toRename'); | ||
// Requires beta mode | ||
@@ -633,8 +768,4 @@ if (!options.betaMode) { | ||
// Verify model signature (if enabled in options) | ||
verifyModelSignature(model, 'toCsn'); | ||
if (options.toCsn.flavor && options.newCsn === false) { | ||
signal(error`CSN in "gensrc" flavor can only be generated as new-style CSN`); | ||
// FIXME: CompilationError? Why at all? | ||
if (options.toCsn.gensrc && !options.newCsn === false) { | ||
signal(error`CSN in "gensrc" flavor can only be generated as new-style CSN (option "newCsn" required)`); | ||
throw new CompilationError(sortMessages(model.messages), model); | ||
@@ -689,3 +820,5 @@ } | ||
toHana, | ||
toHanaWithCsn, | ||
toOdata, | ||
toOdataWithCsn, | ||
preparedCsnToEdmx, | ||
@@ -692,0 +825,0 @@ preparedCsnToEdm, |
@@ -39,6 +39,8 @@ // Implementation of alerts | ||
// | ||
function alerts(model) { | ||
function alerts(model, options = model.options || {}) { | ||
// If 'model' does not have a 'messages' array yet (may happen for plain CSNs), create one | ||
model.messages = model.messages || []; | ||
let collected = model.messages; | ||
let collected = options.messages || model.messages || | ||
Object.defineProperty( model, 'messages', | ||
{ value: [], configurable: true, writable: true } ) | ||
.messages; | ||
@@ -45,0 +47,0 @@ return { |
@@ -14,5 +14,23 @@ // Deep copy an object structure | ||
let map = new WeakMap(); | ||
return clone(obj); | ||
let copyStack = []; | ||
let result = copy(obj); | ||
function clone(obj) { | ||
while (copyStack.length) { | ||
let entry = copyStack.pop(); | ||
let {src, target, prop} = entry; | ||
// copy element | ||
let newObj = copy(src[prop]); | ||
// store reference to copy in target property | ||
let desc = (src && typeof src === 'object') ? Object.getOwnPropertyDescriptor(src, prop) : null; | ||
if (desc && desc.enumerable === false && desc.get === undefined && desc.set === undefined) { | ||
desc.value = newObj; | ||
Object.defineProperty(target, prop, desc); | ||
} else { | ||
target[prop] = newObj; | ||
} | ||
} | ||
return result; | ||
function copy(obj) { | ||
let newObj; | ||
@@ -32,12 +50,14 @@ if (typeof obj !== 'object' || obj === null) // return primitive type, note that typeof null === 'object' | ||
map.set(obj, newObj); | ||
let props = Object.getOwnPropertyNames(obj); // we clone only own properties, not inherited one's | ||
for (let p of props) { | ||
let pd = Object.getOwnPropertyDescriptor(obj, p); | ||
if (pd && pd.enumerable === false && pd.get === undefined && pd.set === undefined ) { | ||
pd.value = clone(obj[p]); | ||
Object.defineProperty(newObj, p, pd); | ||
// loop over all properties and add them to copyStack | ||
// in reverse order to keep same order in copied object | ||
let propertiesToPush = []; | ||
for (let propName of Object.getOwnPropertyNames(obj)) { // we clone only own properties, not inherited one's | ||
if (propName === 'location' && Object.getPrototypeOf(obj)) { | ||
// performance optimization: only link location instead of copying the object | ||
newObj.location = obj.location; | ||
} else { | ||
propertiesToPush.push({ src: obj, prop: propName, target: newObj }); | ||
} | ||
else | ||
newObj[p] = clone(obj[p]); | ||
} | ||
copyStack.push(...propertiesToPush.reverse()); | ||
return newObj; | ||
@@ -47,2 +67,2 @@ } | ||
module.exports = deepCopy; | ||
module.exports = deepCopy; |
@@ -12,3 +12,4 @@ // Functions for dictionaries (Objects without prototype) | ||
var found = dict[name]; | ||
if (!found) { | ||
if (!found || found.builtin) { // do not replace a builtin definition | ||
// XSN TODO: store duplicate definitions in prop $duplicates, do not use array (except $combined?) | ||
dict[name] = entry; // also ok if array (redefined) | ||
@@ -15,0 +16,0 @@ return entry; |
@@ -109,3 +109,3 @@ // Functions and classes for syntax messages | ||
* @param {any} msg The message text | ||
* @param {string} [severity='Error'] Severity: Info, Warning, Error | ||
* @param {string} [severity='Error'] Severity: Debug, Info, Warning, Error | ||
* @param {any} id The ID of the message - visible as property messageId | ||
@@ -134,8 +134,10 @@ * @param {any} home | ||
function handleMessages( model ) { | ||
if (model.messages && model.messages.length) { | ||
model.messages.sort( compareMessage ); | ||
if (hasErrors( model.messages )) | ||
throw new CompilationError( model.messages, model ); | ||
function handleMessages( model, options = model.options || {} ) { | ||
let messages = options.messages || model.messages; | ||
if (messages && messages.length) { | ||
messages.sort( compareMessage ); | ||
if (hasErrors( messages )) | ||
throw new CompilationError( messages, model ); | ||
} | ||
return model; | ||
} | ||
@@ -157,6 +159,11 @@ | ||
function getMessageFunction( model ) { | ||
if (!model.messages) | ||
model.messages = []; | ||
let config = model.options && model.options.severities || {}; | ||
// Return message function to issue errors, warnings, info and debug messages. | ||
// Messages are put into the `messages` property of argument `options` or `model`. | ||
// If those do not exist, define a non-enumerable property `messages` in `model`. | ||
function getMessageFunction( model, options = model.options || {} ) { | ||
let messages = options.messages || model.messages || | ||
Object.defineProperty( model, 'messages', | ||
{ value: [], configurable: true, writable: true } ) | ||
.messages; | ||
let config = options.severities || {}; | ||
@@ -174,3 +181,3 @@ return function message( id, location, home, params = {}, severity = undefined, texts = undefined ) { | ||
(typeof home === 'string' ? home : homeName(home)) ); | ||
model.messages.push( msg ); | ||
messages.push( msg ); | ||
return msg; | ||
@@ -177,0 +184,0 @@ } |
@@ -38,4 +38,4 @@ // | ||
function forEachMemberRecursively( construct, callback ) { | ||
forEachMember( construct, ( member, memberName ) => { | ||
callback( member, memberName ); | ||
forEachMember( construct, ( member, memberName, prop ) => { | ||
callback( member, memberName, prop ); | ||
// Descend into nested members, too | ||
@@ -42,0 +42,0 @@ forEachMemberRecursively( member, callback ); |
@@ -107,3 +107,7 @@ 'use strict'; | ||
if(artifactName.absolute && artifactName.element) { | ||
let argTarget = model.definitions[artifactName.absolute].elements[artifactName.element]; | ||
let targetArtifact = model.definitions[artifactName.absolute]; | ||
if (targetArtifact instanceof Array) { | ||
return; | ||
} | ||
let argTarget = targetArtifact.elements[artifactName.element]; | ||
//the check is valid for unmanaged associations | ||
@@ -110,0 +114,0 @@ //TODO clarify if the full resolved path to the target field should consist of managed associations or just the first |
@@ -111,7 +111,2 @@ 'use strict'; | ||
// exception: they can live in cds.foundation | ||
if (art.kind != 'namespace' && | ||
(art.name.absolute == 'cds' || | ||
(art.name.absolute.match(/^cds\./) && !art.name.absolute.match(/^cds\.foundation\./)))) { | ||
signal(error`The namespace "cds" is reserved for CDS builtins`, art.name.location); | ||
} | ||
if (options.toHana) { | ||
@@ -118,0 +113,0 @@ checkNotEmptyOrOnlyVirtualElems(art, model); |
@@ -69,3 +69,3 @@ // Consistency checker on model (XSN = augmented CSN) | ||
const { locationString } = require('../base/messages'); | ||
const { locationString, hasErrors } = require('../base/messages'); | ||
const { queryOps } = require('../base/model'); | ||
@@ -85,4 +85,4 @@ | ||
':model': { // top-level from compiler | ||
requires: ['messages','options','definitions','sources'], | ||
optional: ['extensions','version','$version','meta','$magicVariables','$builtins','$internal','$compositionTargets','_entities','$entity'] // version without --test-mode | ||
requires: ['options','definitions','sources'], | ||
optional: ['messages','extensions','version','$version','meta','$magicVariables','$builtins','$internal','$compositionTargets','_entities','$entity'] // version without --test-mode | ||
}, | ||
@@ -126,3 +126,6 @@ ':parser': { // top-level from parser | ||
$frontend: { parser: true, test: isString, enum: ['cdl','json','xml'] }, | ||
messages: { test: isArray( TODO ) }, // TODO: check message object | ||
messages: { | ||
enumerable: () => true, // does not matter (non-enum std), enum in CSN/XML parser | ||
test: isArray( TODO ) | ||
}, // TODO: check message object | ||
options: { test: TODO }, // TODO: check option object | ||
@@ -200,3 +203,4 @@ definitions: { | ||
] | ||
} | ||
}, | ||
none: { optional: () => true } // parse error | ||
}, | ||
@@ -264,3 +268,3 @@ from: { | ||
kind: { | ||
isRequired: () => true, // required even with parse errors | ||
isRequired: !stageParser && (() => true), // required to be set by Core Compiler even with parse errors | ||
test: isString, | ||
@@ -441,17 +445,4 @@ enum: [ | ||
function noSyntaxErrors() { | ||
if (_noSyntaxErrors != null) | ||
return _noSyntaxErrors; | ||
// TODO: currently, we only have errors - use hasErrors() later | ||
if (stageParser || !model.sources) { | ||
_noSyntaxErrors = !(model.messages && model.messages.length); | ||
return _noSyntaxErrors; | ||
} | ||
for (let file in model.sources) { | ||
let s = model.sources[file]; | ||
if (s.messages && model.messages.length) { | ||
_noSyntaxErrors = false; | ||
return _noSyntaxErrors; | ||
} | ||
} | ||
_noSyntaxErrors = true; | ||
if (_noSyntaxErrors == null) | ||
_noSyntaxErrors = !hasErrors( options.messages || model.messages ); // TODO: check messageId? | ||
return _noSyntaxErrors; | ||
@@ -472,3 +463,5 @@ } | ||
let enumerable = ('enumerable' in spec) ? spec.enumerable : char !== '_'; | ||
if (parent.propertyIsEnumerable(prop) !== enumerable) | ||
if (enumerable instanceof Function | ||
? !enumerable( parent, prop ) | ||
: parent.propertyIsEnumerable(prop) !== enumerable) | ||
throw new Error( `Unexpected enumerability ${!enumerable}${at( [node, parent], prop )}` ); | ||
@@ -475,0 +468,0 @@ } |
@@ -82,3 +82,3 @@ // The builtin artifacts of CDS | ||
setMagicVariables( magicVariables ); | ||
let cds = createNamespace('cds'); | ||
let cds = createNamespace( 'cds', 'reserved' ); | ||
// setProp( model.definitions, 'cds', cds ); | ||
@@ -89,3 +89,3 @@ model.definitions.cds = cds; // not setProp - oData | ||
// namespace to store localized table views | ||
let localized = createNamespace('localized'); | ||
let localized = createNamespace( 'localized', true ); | ||
model.definitions.localized = localized; | ||
@@ -102,3 +102,3 @@ } | ||
function createNamespace( name ) { | ||
function createNamespace( name, builtin ) { | ||
return { | ||
@@ -109,2 +109,3 @@ kind: 'namespace', | ||
artifacts: Object.create(null), | ||
builtin, | ||
location: createDummyLocation() | ||
@@ -111,0 +112,0 @@ }; |
@@ -90,2 +90,3 @@ // Compiler phase "define": transform dictionary of AST-like CSNs into augmented CSN | ||
var initBuiltins = require('./builtins'); | ||
const { addBoolAnnotationTo } = require('../model/modelUtils'); | ||
@@ -160,4 +161,4 @@ // Export function of this file. Transform argument `sources` = dictionary of | ||
forEachGeneric( src, 'definitions', function( art, name ) { | ||
if (!art.kind) // TODO: should be done by augmentor | ||
art.kind = 'type'; | ||
if (!art.kind) // Wrong CSN input: | ||
art.kind = 'type'; // ...use default as defined for CSN (TODO: or extra to avoid further errors?) | ||
let dot = name.lastIndexOf('.'); | ||
@@ -187,5 +188,6 @@ if (dot > name.indexOf('::')) // there is a dot (after '::') | ||
let builtin = model.$builtins[ last.id ]; | ||
if (builtin && !builtin.internal) { | ||
message( 'ref-shadowed-builtin', src.namespace.location, null, // no home artifact | ||
{ id: last.id, art: src.namespace, code: `using ${builtin.name.absolute};` }, | ||
let absolute = namespace.name.absolute; | ||
if (builtin && !builtin.internal && absolute !== 'cds') { | ||
message( 'ref-shadowed-builtin', namespace.location, null, // no home artifact | ||
{ id: last.id, art: namespace, code: `using ${builtin.name.absolute};` }, | ||
'Warning', '$(ID) now refers to $(ART) - consider $(CODE)' ); | ||
@@ -198,3 +200,2 @@ } | ||
}; | ||
let absolute = namespace.name.absolute; | ||
block.artifacts[ last.id ] = { // assert( last.id === namespace.name.id ) | ||
@@ -213,5 +214,7 @@ kind: 'using', // TODO: or store namespace directly? | ||
function namespaceContext( path, prefix = '', parent, declLocation ) { | ||
// declLocation also means: check reserved cds on last item | ||
let absolute; | ||
if (path.broken) | ||
return parent; | ||
let check = declLocation && path[ path.length-1 ]; | ||
for (let item of path) { | ||
@@ -236,3 +239,3 @@ let id = item.id; | ||
} | ||
addToDefinitions( context, absolute ); | ||
addToDefinitions( context, absolute, undefined, undefined, item !== check ); | ||
setProp( context, '_parent', parent || null ); | ||
@@ -249,4 +252,3 @@ } | ||
// another artifact has already been defined with the same absolute name. | ||
// TODO: do our own check for reserved names in compiler !!! | ||
function addToDefinitions( art, absolute = art.name.absolute, prefix, parent ) { | ||
function addToDefinitions( art, absolute = art.name.absolute, prefix, parent, noReservedCheck ) { | ||
let context = reuse(); | ||
@@ -271,9 +273,13 @@ //console.log(context?'Reuse':'Def',art.name,prefix ,parent&&parent.name) | ||
} | ||
if (absolute === 'cds') { | ||
// TODO: move all 'cds' prefix checks into compiler | ||
message( null, art.name.location, parent, | ||
if (!noReservedCheck && | ||
(absolute === 'cds' || | ||
absolute.match(/^cds\./) && !absolute.match(/^cds\.foundation(\.|$)/))) { | ||
message( null, art.name.location, null, | ||
`The namespace "cds" is reserved for CDS builtins` ); | ||
} | ||
else | ||
addToDict( model.definitions, absolute, art ); | ||
else if (!noReservedCheck && absolute === 'localized') { | ||
message( null, art.name.location, null, | ||
`The namespace "localized" is reserved for localized convenience views` ); | ||
} | ||
addToDict( model.definitions, absolute, art ); | ||
return art; | ||
@@ -305,3 +311,3 @@ | ||
let path = contextName.substring(colons).split('.').map( id => ({ id, location }) ); | ||
context = namespaceContext( path, contextName.substring(0,colons), undefined, location ); | ||
context = namespaceContext( path, contextName.substring(0,colons) ); | ||
} | ||
@@ -441,5 +447,6 @@ if (context instanceof Array || !context.kind) // no proper construct | ||
art.$queries = []; | ||
art.queries = []; | ||
art.queries = []; // TODO: remove | ||
setProp( art, '_leadingQuery', initQueryExpression( art, art.query ) ); | ||
setProp( art._leadingQuery, '_$next', art ); | ||
if (art._leadingQuery) // null in case of parser error | ||
setProp( art._leadingQuery, '_$next', art ); | ||
// resolve parameters and actions: | ||
@@ -555,6 +562,3 @@ initMembers( art, art, block ); // before setting art.elements! | ||
if (query.on) { | ||
addQuery(); | ||
// TODO: use first tabalias name on the right side of the join (if much | ||
// easier: last of left side) as 'param' in name of this "query" (do | ||
// not use that for user msg, only for --raw-output) | ||
addQuery( parents[0].name.query ); | ||
initSubQuery( query ); | ||
@@ -565,6 +569,14 @@ parents = [...parents, query]; | ||
initQueryExpression( art, tab, parents ); | ||
if (query.name) { // with ON (after processing args to get the $tableAliases | ||
let aliases = Object.keys( query.$tableAliases || {} ); | ||
// Use first tabalias name on the right side of the join to name the | ||
// (internal) query, should only be relevant for --raw-output, not for | ||
// user messages or references | ||
query.name.param = aliases[1] || aliases[0] || '<unknown>'; | ||
} | ||
} | ||
else if (query.from) { // select | ||
addQuery(); | ||
query._main.$queries.push( query ); // TODO: set number with it | ||
addQuery( (art._main||art).$queries.length ); | ||
// TODO: 1-based if view elems are proxies to leading queries elements | ||
query._main.$queries.push( query ); | ||
if (parents.length) | ||
@@ -604,3 +616,3 @@ addAlias( {}, query ); | ||
setProp( query, '_leadingQuery', leading ); | ||
if (query.orderBy) { | ||
if (leading && query.orderBy) { | ||
if (leading.$orderBy) | ||
@@ -668,3 +680,3 @@ leading.$orderBy.push( ...query.orderBy ); | ||
function addQuery() { | ||
function addQuery( name ) { | ||
setProp( query, '_$next', art ); | ||
@@ -675,3 +687,3 @@ setProp( query, '_block', art._block ); | ||
query.name = { location: query.location }; | ||
setMemberParent( query, art.queries.length, art ); | ||
setMemberParent( query, name, art ); | ||
art.queries.push( query ); | ||
@@ -736,2 +748,4 @@ query.$tableAliases = Object.create( null ); // table aliases and mixin definitions | ||
function init ( elem, name, prop ) { | ||
if (!elem.kind) // wrong CSN input | ||
elem.kind = dictKinds[ prop ]; | ||
if (!elem.name) { | ||
@@ -771,2 +785,5 @@ let ref = elem.targetElement || elem.kind === 'element' && elem.value; | ||
return false; | ||
const names = Object.getOwnPropertyNames( dict ); | ||
if (!names.length) | ||
return false; | ||
let feature = kindProperties[ parent.kind ][prop]; | ||
@@ -776,3 +793,3 @@ if (feature && | ||
return true; | ||
let location = dictLocation( dict ); | ||
let location = dictLocation( names.map( name => dict[name] ) ); | ||
if (prop === 'actions') { | ||
@@ -946,3 +963,3 @@ message( 'unexpected-actions', location, {}, construct, 'Error', | ||
} | ||
else if (!options.lintMode) { | ||
else { | ||
// If not lint-mode, complain about unused extensions, i.e. those | ||
@@ -1126,22 +1143,4 @@ // which do not point to a valid artifact | ||
if(ext.columns) { // extend projection - copy columns | ||
let loc = ext.location; | ||
let query = art.query; | ||
let name = ext.name.path.map(X => X.id).join('/'); | ||
if(query.from == undefined) { | ||
message( 'ext-proj-from-one', loc, null, { name, source:query.op.val }, 'Error', | ||
'Can not extend projection $(NAME) on $(SOURCE) as source' ); | ||
} else if(query.from.length!==1) { | ||
message( 'ext-proj-from-one', loc, null, { name }, 'Error', | ||
'Can not extend projection $(NAME) having more than one sources' ); | ||
} else { | ||
if(query.columns==undefined) | ||
query.columns=[]; | ||
for (let columnName in ext.columns) { | ||
let column = ext.columns[ columnName ]; | ||
setProp(column,'_block', ext._block); | ||
query.columns.push(column) | ||
} | ||
} | ||
} // extend projection | ||
if (ext.columns) // extend projection | ||
extendColumns( ext, art ); | ||
} | ||
@@ -1163,2 +1162,28 @@ if (elemExtensions.length > 1) | ||
// Copy columns for EXTEND PROJECTION | ||
function extendColumns( ext, art ) { | ||
// TODO: consider reportUnstableExtensions | ||
let location = ext.location; | ||
let query = art.query; | ||
if (!query) { | ||
if (art.kind !== 'annotate') | ||
message( 'extend-columns', location, ext, { art }, 'Error', | ||
'Artifact $(ART) cannot be extended with columns, only projections can' ); | ||
return; | ||
} | ||
if (!query.from || query.from.length !==1 || !query.from[0].path) { | ||
message( 'extend-columns', location, ext, { art }, 'Error', | ||
'Artifact $(ART) cannot be extended with columns, only projections can' ); | ||
} | ||
else { | ||
if (!query.columns) | ||
query.columns = [ { location, val: '*' } ]; | ||
for (let column of ext.columns) { | ||
setProp( column,'_block', ext._block ); | ||
query.columns.push(column) | ||
} | ||
} | ||
} | ||
function reportUnstableExtensions( extensions ) { | ||
@@ -1349,2 +1374,3 @@ // Report 'Warning: Unstable element order due to repeated extensions'. | ||
} | ||
addBoolAnnotationTo('@odata.draft.enabled', false, art); | ||
let locale = { | ||
@@ -1351,0 +1377,0 @@ name: { location, id: 'locale' }, |
@@ -193,3 +193,3 @@ // | ||
function returns( prop, target, source, ok ) { | ||
function returns( prop, target, source, ok, deleteProp ) { | ||
// TODO: remove returns in XSN | ||
@@ -201,2 +201,5 @@ if (ok || target.$inferred === 'proxy' || target.$inferred === 'include' ) { | ||
setProp( target[prop], '_outer', target._outer || target ); // for setMemberParent | ||
if (deleteProp) { | ||
delete target[deleteProp]; | ||
} | ||
} | ||
@@ -212,3 +215,3 @@ } | ||
!line.elements && !line.enum && !line.items ) | ||
returns( prop, target, source, !options.hanaFlavor ); | ||
returns( prop, target, source, !options.hanaFlavor, 'type'); | ||
} | ||
@@ -215,0 +218,0 @@ } |
@@ -623,7 +623,6 @@ // Compiler functions and utilities shared across all phases | ||
elem.name.absolute = elem._main.name.absolute; | ||
let normalized = kindProperties[ elem.kind ].normalized || elem.kind; | ||
[ 'element', 'alias', 'query', 'param', 'action' ].forEach( function( kind ) { | ||
// console.log(elem.kind || elem) | ||
let normalized = kindProperties[ elem.kind ].normalized || elem.kind; | ||
if (normalized === kind) { | ||
elem.name[kind] = (parent.name[kind] != null) ? parent.name[kind] + '.' + name : name; | ||
elem.name[kind] = (parent.name[kind] != null && kind !== 'query') ? parent.name[kind] + '.' + name : name; | ||
} | ||
@@ -630,0 +629,0 @@ else if (parent.name[kind] != null) { |
@@ -44,7 +44,7 @@ { | ||
"Authorization.SecuritySchemes": { | ||
"Type": "Collection(Auth.SecurityScheme)", | ||
"Type": "Collection(Authorization.SecurityScheme)", | ||
"AppliesTo": "EntityContainer" | ||
}, | ||
"Authorization.Authorizations": { | ||
"Type": "Collection(Auth.Authorization)", | ||
"Type": "Collection(Authorization.Authorization)", | ||
"AppliesTo": "EntityContainer" | ||
@@ -631,2 +631,6 @@ }, | ||
}, | ||
"UI.ConnectedFields": { | ||
"Type": "UI.ConnectedFieldsType", | ||
"AppliesTo": "EntityType" | ||
}, | ||
"UI.GeoLocations": { | ||
@@ -740,2 +744,6 @@ "Type": "Collection(UI.GeoLocationType)", | ||
}, | ||
"UI.DataFieldDefault": { | ||
"Type": "UI.DataFieldAbstract", | ||
"AppliesTo": "Property" | ||
}, | ||
"UI.Criticality": { | ||
@@ -877,3 +885,3 @@ "Type": "UI.CriticalityType", | ||
"Properties": { | ||
"Scopes": "Collection(Auth.AuthorizationScope)", | ||
"Scopes": "Collection(Authorization.AuthorizationScope)", | ||
"RefreshUrl": "Edm.String", | ||
@@ -889,3 +897,3 @@ "Name": "Edm.String", | ||
"TokenUrl": "Edm.String", | ||
"Scopes": "Collection(Auth.AuthorizationScope)", | ||
"Scopes": "Collection(Authorization.AuthorizationScope)", | ||
"RefreshUrl": "Edm.String", | ||
@@ -901,3 +909,3 @@ "Name": "Edm.String", | ||
"AuthorizationUrl": "Edm.String", | ||
"Scopes": "Collection(Auth.AuthorizationScope)", | ||
"Scopes": "Collection(Authorization.AuthorizationScope)", | ||
"RefreshUrl": "Edm.String", | ||
@@ -913,3 +921,3 @@ "Name": "Edm.String", | ||
"TokenUrl": "Edm.String", | ||
"Scopes": "Collection(Auth.AuthorizationScope)", | ||
"Scopes": "Collection(Authorization.AuthorizationScope)", | ||
"RefreshUrl": "Edm.String", | ||
@@ -926,3 +934,3 @@ "Name": "Edm.String", | ||
"TokenUrl": "Edm.String", | ||
"Scopes": "Collection(Auth.AuthorizationScope)", | ||
"Scopes": "Collection(Authorization.AuthorizationScope)", | ||
"RefreshUrl": "Edm.String", | ||
@@ -945,3 +953,3 @@ "Name": "Edm.String", | ||
"KeyName": "Edm.String", | ||
"Location": "Auth.KeyLocation", | ||
"Location": "Authorization.KeyLocation", | ||
"Name": "Edm.String", | ||
@@ -1619,4 +1627,4 @@ "Description": "Edm.String" | ||
"TypeNamePlural": "Edm.String", | ||
"Title": "UI.DataField", | ||
"Description": "UI.DataField", | ||
"Title": "UI.DataFieldAbstract", | ||
"Description": "UI.DataFieldAbstract", | ||
"ImageUrl": "Edm.String", | ||
@@ -1689,2 +1697,3 @@ "TypeImageUrl": "Edm.String" | ||
"Criticality": "UI.CriticalityType", | ||
"CriticalityLabels": "Edm.AnnotationPath", | ||
"CriticalityCalculation": "UI.CriticalityCalculationType", | ||
@@ -1752,2 +1761,3 @@ "Trend": "UI.TrendType", | ||
"DataPoint": "UI.DataPointType", | ||
"AdditionalDataPoints": "Collection(UI.DataPointType)", | ||
"Detail": "UI.KPIDetailType" | ||
@@ -1754,0 +1764,0 @@ } |
@@ -31,3 +31,3 @@ const parseXml = require('./xmlParserWithLocations'); | ||
*/ | ||
function edmx2csn(src, filename) { | ||
function edmx2csn(src, filename, options) { | ||
let source = parseXml(src, filename); | ||
@@ -41,2 +41,5 @@ | ||
forEachAnnotationsTag(source); | ||
// The EDMX parser do not use the normal message function: | ||
if (options.messages && csn.messages) | ||
options.messages.push( ...csn.messages ); | ||
return csn; | ||
@@ -43,0 +46,0 @@ |
@@ -140,3 +140,2 @@ 'use strict'; | ||
} | ||
if (object.kind == "action" || object.kind == "function") { | ||
@@ -251,3 +250,2 @@ handleAction(objName, object, null); | ||
let element = object.elements[elemName]; | ||
// determine the name of the target in the resulting edm | ||
@@ -331,8 +329,11 @@ // for non-assoc element, this simply is "<objectName>/<elementName>" | ||
let params = []; | ||
if (bindingParam) params.push(bindingParam); | ||
if (bindingParam) { | ||
params.push(action['@cds.odata.bindingparameter.collection'] ? 'Collection(' + bindingParam + ')' : bindingParam); | ||
} | ||
if (action.kind === 'function') { | ||
for (let n in action.params) { | ||
let p = action.params[n]; | ||
let otype = p.type.startsWith('cds.') ? glue.mapCdsToEdmType(p.type, false /*is only called for v4*/) : p.type; | ||
params.push(otype); | ||
let mapType = (type) => type.startsWith('cds.') ? glue.mapCdsToEdmType(type, false /*is only called for v4*/) : type; | ||
let isArrayType = !p.type && p.items && p.items.type; | ||
params.push(isArrayType ? 'Collection(' + mapType(p.items.type) + ')' : mapType(p.type)); | ||
} | ||
@@ -354,2 +355,6 @@ } | ||
// - have a value other than null | ||
if(carrier['@cds.api.ignore']) { | ||
return; | ||
} | ||
let annoNames = Object.keys(carrier).filter( x => x.substr(0,1) == "@" ); | ||
@@ -356,0 +361,0 @@ let nullWhitelist = [ '@Core.OperationAvailable' ]; |
@@ -226,3 +226,2 @@ 'use strict'; | ||
let aNameWithoutQualifier = aName.split("#")[0]; | ||
let a = carrier[aName]; | ||
@@ -232,11 +231,29 @@ //for warning messages | ||
// Always - draft annotations, value is action name | ||
// - v2: prefix with entity name | ||
// - prefix with service name | ||
draftAnnotations(carrier, aName, aNameWithoutQualifier); | ||
// Always - FixedValueListShortcut | ||
// expand shortcut form of ValueList annotation | ||
fixedValueListShortCut(carrier, aNameWithoutQualifier, ctx); | ||
// Always - TextArrangementReordering | ||
// convert @Common.TextArrangement annotation that is on same level as Text annotation into a nested annotation | ||
// TnT only: also accept @UI.TextArrangement | ||
textArrangementReordering(carrier, aName, aNameWithoutQualifier, ctx); | ||
// TNT - SubstitutingFKeysForAssocs | ||
substituteFKeysForAssocsTNT(carrier, aName, aNameWithoutQualifier); | ||
} | ||
// inner functions | ||
function draftAnnotations(carrier, aName, aNameWithoutQualifier) { | ||
if ((carrier.kind === 'entity' || carrier.kind === 'view') && | ||
(aNameWithoutQualifier == "@Common.DraftRoot.PreparationAction" || | ||
aNameWithoutQualifier == "@Common.DraftRoot.ActivationAction" || | ||
aNameWithoutQualifier == "@Common.DraftRoot.EditAction" || | ||
aNameWithoutQualifier == "@Common.DraftNode.PreparationAction") | ||
) { | ||
aNameWithoutQualifier == "@Common.DraftRoot.ActivationAction" || | ||
aNameWithoutQualifier == "@Common.DraftRoot.EditAction" || | ||
aNameWithoutQualifier == "@Common.DraftNode.PreparationAction") | ||
) { | ||
let value = carrier[aName]; | ||
@@ -255,5 +272,5 @@ // prefix with service name, if not already done | ||
} | ||
} | ||
// Always - FixedValueListShortcut | ||
// expand shortcut form of ValueList annotation | ||
function fixedValueListShortCut(carrier, aNameWithoutQualifier, ctx) { | ||
if (aNameWithoutQualifier == "@Common.ValueList.entity" || | ||
@@ -336,2 +353,11 @@ aNameWithoutQualifier == "@Common.ValueList.viaAssociation") { | ||
// if this carrier is a generated foreign key field and the association is marked @cds.api.ignore | ||
// rename the localDataProp to be 'assocName/key' | ||
if(carrier['@cds.api.ignore']) { | ||
let assocName = carrier['@odata.foreignKey4']; | ||
if(assocName && options.isV4()) { | ||
localDataProp = localDataProp.replace(assocName+fkSeparator, assocName+'/'); | ||
} | ||
} | ||
// valueListProp: the (single) key field of the value list entity | ||
@@ -417,6 +443,5 @@ // if no key or multiple keys -> warning | ||
} | ||
} | ||
// Always - TextArrangementReordering | ||
// convert @Common.TextArrangement annotation that is on same level as Text annotation into a nested annotation | ||
// TnT only: also accept @UI.TextArrangement | ||
function textArrangementReordering(carrier, aName, aNameWithoutQualifier, ctx) { | ||
let tntTextArrangement = options && options.tntFlavor && !options.tntFlavor.skipAnnosTextArrangementReordering; | ||
@@ -445,10 +470,12 @@ if (aNameWithoutQualifier == "@Common.TextArrangement" || | ||
} | ||
} | ||
// TNT - SubstitutingFKeysForAssocs | ||
function substituteFKeysForAssocsTNT(carrier, aName, aNameWithoutQualifier) { | ||
if (options && options.tntFlavor && !options.tntFlavor.skipAnnosSubstitutingFKeysForAssocs) { | ||
// replace association by fk field, mind nested annotatinos | ||
let annotationValue = carrier[aName]; | ||
if (aNameWithoutQualifier == "@UI.LineItem" || aNameWithoutQualifier == "@UI.LineItem.$value" || | ||
aNameWithoutQualifier == "@UI.Identification" || aNameWithoutQualifier == "@UI.Identification.$value" || | ||
aNameWithoutQualifier == "@UI.FieldGroup" || aNameWithoutQualifier == "@UI.FieldGroup.$value") { | ||
for (let ae of a) { | ||
for (let ae of annotationValue) { | ||
if (ae["Value"] && ae["Value"]["="]) { | ||
@@ -462,3 +489,3 @@ replaceManagedAssocByFK(ae["Value"]); | ||
if (aNameWithoutQualifier == "@UI.SelectionFields") { | ||
for (let ae of a) { | ||
for (let ae of annotationValue) { | ||
if ("=" in ae) { | ||
@@ -470,19 +497,19 @@ replaceManagedAssocByFK(ae); | ||
} | ||
} | ||
function replaceManagedAssocByFK(ae) { | ||
let path = ae["="]; | ||
let steps = path.split('.'); | ||
let ent = carrier; | ||
for (let i in steps) { | ||
if (!ent || ent.kind != 'entity') return; | ||
let el = ent.elements[steps[i]]; | ||
if (el && glue.isAssociation(el)) { | ||
if (i < steps.length-1) { | ||
ent = getTargetOfAssoc(el); | ||
} | ||
else { //last step | ||
if (!el.onCond && !el.on) { // only for managed | ||
ae["="] += fkSeparator + getKeyOfTargetOfManagedAssoc(el); | ||
function replaceManagedAssocByFK(ae) { | ||
let path = ae["="]; | ||
let steps = path.split('.'); | ||
let ent = carrier; | ||
for (let i in steps) { | ||
if (!ent || ent.kind != 'entity') return; | ||
let el = ent.elements[steps[i]]; | ||
if (el && glue.isAssociation(el)) { | ||
if (i < steps.length-1) { | ||
ent = getTargetOfAssoc(el); | ||
} | ||
else { //last step | ||
if (!el.onCond && !el.on) { // only for managed | ||
ae["="] += fkSeparator + getKeyOfTargetOfManagedAssoc(el); | ||
} | ||
} | ||
} | ||
@@ -489,0 +516,0 @@ } |
@@ -13,3 +13,3 @@ 'use strict'; | ||
const alerts = require('../base/alerts'); | ||
const { CompilationError, hasErrors, sortMessages } = require('../base/messages'); | ||
const { handleMessages } = require('../base/messages'); | ||
const { setProp } = require('../base/model'); | ||
@@ -49,4 +49,4 @@ | ||
let serviceCsn = glue.initializeModel(model, options); | ||
if(serviceCsn == undefined) | ||
let serviceCsn = glue.initializeModel(model, options, signal, warning); | ||
if(serviceCsn === undefined) | ||
signal(error`No Service found in model`); | ||
@@ -99,14 +99,21 @@ | ||
// fetch all exising children names in a map | ||
let NamesInSchemaXRef = Schema._children.reduce((acc, cur) => { | ||
if(acc[cur.Name] === undefined) { | ||
acc[cur.Name] = [ cur ]; | ||
} else { | ||
acc[cur.Name].push(cur); | ||
} | ||
return acc; | ||
}, {} ); | ||
navigationProperties.forEach(np => { | ||
/* | ||
if(np._csn._partnerCsn.length > 1) { | ||
if(options.tntFlavor) { | ||
signal(warning`NavigationProperty "${np._csn._parent.name}/${np.Name}" has more then one partner: ${np._csn._partnerCsn.map(csn=>'"'+csn._parent.name+'/'+csn.Name+'"').join(', ')}`); | ||
} | ||
else { | ||
signal(error`NavigationProperty "${np._csn._parent.name}/${np.Name}" has more then one partner: ${np._csn._partnerCsn.map(csn=>'"'+csn._parent.name+'/'+csn.Name+'"').join(', ')}`); | ||
} | ||
signal(warning`NavigationProperty "${np._csn._parent.name}/${np.Name}" has more then one partner: ${np._csn._partnerCsn.map(csn=>'"'+csn._parent.name+'/'+csn.Name+'"').join(', ')}`); | ||
} | ||
*/ | ||
if(options.isV4()) { | ||
// V4: No referential constraints for Containment Relationships | ||
if(!np.isContainment()) | ||
if(!np.isContainment() && !np.isToMany()) | ||
np.addReferentialConstraintNodes(); | ||
@@ -120,2 +127,7 @@ } | ||
for(let name in NamesInSchemaXRef) { | ||
if(NamesInSchemaXRef[name].length > 1) { | ||
signal(error`Duplicate name "${name}" in Namespace "${Schema.Namespace}"`); | ||
} | ||
} | ||
if(Schema._ec._children.length == 0) | ||
@@ -125,6 +137,3 @@ signal(error`EntityContainer must contain at least one EntitySet`); | ||
// Throw up if we have errors | ||
if (hasErrors(model.messages)) { | ||
throw new CompilationError(sortMessages(model.messages), model); | ||
} | ||
handleMessages(model, options); | ||
return edm | ||
@@ -146,2 +155,5 @@ | ||
if(properties.length === 0) { | ||
signal(error`EntityType "${serviceName}/${EntityTypeName}" has no properties`); | ||
} | ||
// construct EntityType attributes | ||
@@ -198,3 +210,3 @@ let attributes = { Name : EntityTypeName }; | ||
let bpType = actionCsn['@cds.odata.bindingparameter.collection'] ? | ||
'$Collection('+fullQualified(entityCsn.name)+')' : fullQualified(entityCsn.name); | ||
'Collection('+fullQualified(entityCsn.name)+')' : fullQualified(entityCsn.name); | ||
// Binding Parameter: 'in' at first position in sequence, this is decisive! | ||
@@ -209,3 +221,3 @@ actionNode.append(new Edm.Parameter(v, { Name: bpName, Type: bpType }, {} )); | ||
let rt = actionCsn.returns && (actionCsn.returns.type || actionCsn.returns.items.type); | ||
let rt = actionCsn.returns && ((actionCsn.returns.items && actionCsn.returns.items.type) || actionCsn.returns.type); | ||
if(rt) // add EntitySet attribute only if return type is a non abstract entity | ||
@@ -254,3 +266,3 @@ { | ||
let rt = actionCsn.returns && (actionCsn.returns.type || actionCsn.returns.items.type); | ||
let rt = actionCsn.returns && ((actionCsn.returns.items && actionCsn.returns.items.type) || actionCsn.returns.type); | ||
if(rt) // add EntitySet attribute only if return type is an entity | ||
@@ -310,3 +322,3 @@ { | ||
// it is safe to assume that either type or items.type are set | ||
let type = action.returns && (action.returns.type || action.returns.items.type); | ||
let type = action.returns && ((action.returns.items && action.returns.items.type) || action.returns.type); | ||
if(type && type.startsWith('cds.')) | ||
@@ -422,5 +434,10 @@ type = glue.mapCdsToEdmType(type, options.isV2()); | ||
{ | ||
let constraints = navigationProperty._referentialConstraints; | ||
let constraints = navigationProperty._csn._constraints; | ||
let parentName = navigationProperty._csn._parent.name.replace(namespace, ''); | ||
let assocName = parentName + NAVPROP_TRENNER + navigationProperty.Name.replace(VALUELIST_NAVPROP_PREFIX, ''); | ||
let plainAssocName = parentName + NAVPROP_TRENNER + navigationProperty.Name.replace(VALUELIST_NAVPROP_PREFIX, ''); | ||
let assocName = plainAssocName; | ||
let i = 1; | ||
while(NamesInSchemaXRef[assocName] !== undefined) { | ||
assocName = plainAssocName + '_' + i++; | ||
} | ||
@@ -482,7 +499,13 @@ let fromRole = parentName; | ||
parentName = forwardAssocCsn._parent.name.replace(namespace, ''); | ||
assocName = parentName + NAVPROP_TRENNER + forwardAssocCsn.name.replace(VALUELIST_NAVPROP_PREFIX, ''); | ||
assocName = plainAssocName = parentName + NAVPROP_TRENNER + forwardAssocCsn.name.replace(VALUELIST_NAVPROP_PREFIX, ''); | ||
i = 1; | ||
while(NamesInSchemaXRef[assocName] !== undefined && !(NamesInSchemaXRef[assocName][0] instanceof Edm.Association)) { | ||
assocName = plainAssocName + '_' + i++; | ||
} | ||
navigationProperty.Relationship = fullQualified(assocName) | ||
reuseAssoc = !!forwardAssocCsn._NavigationProperty; | ||
constraints = navigationProperty.getReferentialConstraints(forwardAssocCsn); | ||
constraints = glue.getReferentialConstraints(forwardAssocCsn, signal, warning); | ||
constraints._multiplicity = glue.determineMultiplicity(forwardAssocCsn); | ||
} | ||
@@ -518,6 +541,11 @@ | ||
[ toRole, fullQualified(toEntityType) ], | ||
constraints.multiplicity ); | ||
constraints._multiplicity ); | ||
if(NamesInSchemaXRef[assocName] === undefined) { | ||
NamesInSchemaXRef[assocName] = [ navigationProperty._edmAssociation ]; | ||
} | ||
else { | ||
navigationProperty._edmAssociation.push(navigationProperty._edmAssociation); | ||
} | ||
// Add ReferentialConstraints if any | ||
if(Object.keys(constraints.constraints).length > 0) { | ||
if(!navigationProperty._isCollection && Object.keys(constraints.constraints).length > 0) { | ||
// A managed composition is treated as association | ||
@@ -524,0 +552,0 @@ if(navigationProperty._csn.type == 'cds.Composition' && (navigationProperty._csn.on || navigationProperty._csn.onCond)) { |
@@ -533,3 +533,3 @@ 'use strict' | ||
{ | ||
let type = csn.type || csn.items.type; | ||
let type = (csn.items && csn.items.type) || csn.type; | ||
if(type && type.startsWith('cds.')) | ||
@@ -847,14 +847,17 @@ type = glue.mapCdsToEdmType(type, false, false); | ||
{ | ||
NavigationProperty.DOLLAR_SELF = '$self' | ||
super(v, attributes, csn); | ||
super(v, attributes, csn); | ||
let [src, tgt] = glue.determineMultiplicity(csn._constraints._originAssocCsn || csn); | ||
csn._constraints._multiplicity = csn._constraints._originAssocCsn ? [tgt, src] : [src, tgt]; | ||
this.set( { | ||
_type: attributes.Type, | ||
_isCollection: glue.isToMany(csn), | ||
_referentialConstraints: this.getReferentialConstraints(), | ||
_targetCsn: csn.target } ); | ||
_isCollection: this.isToMany(), | ||
_targetCsn: csn.target | ||
} ); | ||
if (this.v4) | ||
{ | ||
// either csn has multiplicity or we have to use the multiplicity of the backlink | ||
if(this._isCollection || this._referentialConstraints.multiplicity[1] == '*') { | ||
if(this._isCollection) { | ||
this.Type = `Collection(${attributes.Type})` | ||
@@ -872,2 +875,7 @@ // attribute Nullable is not allowed in combination with Collection (see Spec) | ||
let partner = (csn._partnerCsn.length > 0) ? csn._partnerCsn[0] : csn._constraints._originAssocCsn; | ||
if(partner && partner['@odata.navigable'] !== false) { | ||
this.Partner = partner.name; | ||
} | ||
/* | ||
@@ -884,6 +892,2 @@ 1) If this navigation property belongs to an EntityType for a parameterized entity | ||
this.ContainsTarget = true; | ||
// if backlink has established partner before this underlying NavProp is created, use it | ||
if(csn._partnerCsn.length > 0) | ||
this.Partner = csn._partnerCsn[0].name; | ||
} | ||
@@ -916,2 +920,5 @@ | ||
} | ||
isToMany() { | ||
return (this._isCollection || this._csn._constraints._multiplicity[1] == '*'); | ||
} | ||
@@ -963,266 +970,5 @@ toJSONattributes(json) | ||
{ | ||
glue.forAll(this._referentialConstraints.constraints, | ||
glue.forAll(this._csn._constraints.constraints, | ||
c => this.append(new ReferentialConstraint(this._v, { Property: c[0], ReferencedProperty: c[1] } ) ) ); | ||
} | ||
getReferentialConstraints(assocCsn = undefined) | ||
{ | ||
let result = { multiplicity: [ ], constraints: Object.create(null), selfs: [], termCount: 0 }; | ||
assocCsn = assocCsn || this._csn; | ||
if(assocCsn.on) | ||
{ | ||
// fill constraint array with [prop, depProp] | ||
getExpressionArguments(assocCsn.on); | ||
// for all $self conditions, fill constraints of partner (if any) | ||
let isBacklink = result.selfs.length == 1 && result.termCount == 1; | ||
/* example for originalTarget: | ||
entity E (with parameters) { | ||
... keys and all the stuff ... | ||
toE: association to E; | ||
back: association to E on back.toE = $self | ||
} | ||
toE target 'E' is redirected to 'EParameters' (must be as the new parameter list is required) | ||
back target 'E' is also redirected to 'EParameters' (otherwise backlink would fail) | ||
ON Condition back.toE => parter=toE cannot be resolved in EParameters, originalTarget 'E' is | ||
required for that | ||
*/ | ||
result.selfs.forEach(partner => { | ||
let originAssocCsn = assocCsn.target.elements[partner]; | ||
if(originAssocCsn == undefined && assocCsn.originalTarget) | ||
originAssocCsn = assocCsn.originalTarget.elements[partner]; | ||
if(originAssocCsn == undefined) | ||
throw Error('Could not find forward association "' + assocCsn.target.name + '.' + partner + | ||
' for backlink association "' + assocCsn._parent.name + '.' + assocCsn.name + '", maybe forward is not published in service?'); | ||
// let originAssocCsn = assocCsn.target.elements[partner] || assocCsn.originalTarget.elements[partner]; | ||
if(glue.isAssociationOrComposition(originAssocCsn)) { | ||
// if the assoc is marked as primary key, add all its foreign keys as constraint | ||
// as they are primary keys of the other entity as well | ||
if(!assocCsn.target.isParamEntity && originAssocCsn.key) { | ||
if(originAssocCsn.keys) { | ||
for(let fk of originAssocCsn.keys) { | ||
let c = [ fk.ref[0], fk.$generatedFieldName ]; | ||
result.constraints[c] = c; | ||
} | ||
} | ||
} | ||
// Mark this association as backlink if $self appears exactly once | ||
// to surpress edm:Association generation in V2 mode | ||
if(isBacklink) { | ||
result._originAssocCsn = originAssocCsn; | ||
// store this backlink at forward association | ||
originAssocCsn._partnerCsn.push(assocCsn); | ||
// V4 Set the Partner attribute with the name of the opposite | ||
// association to the two corresponding csn's and to this NavProp | ||
// (but only if originAssoc is navigable (undefined !== false) still evaluates to true) | ||
if(this.v4 && originAssocCsn['@odata.navigable'] !== false) | ||
{ | ||
// set the Partner attribute to this (backlink) NavProp | ||
this.Partner = originAssocCsn.name; | ||
// if the other NavProp has been created already, set NavProp.Partner | ||
// if not, Partner will be set during creation of other NavProp | ||
if(originAssocCsn._NavigationProperty) | ||
originAssocCsn._NavigationProperty.Partner = assocCsn.name; | ||
} | ||
} | ||
} | ||
else { | ||
/* | ||
entity E { | ||
key id : Integer; | ||
toMe: associaton to E on toMe.id = $self; }; | ||
*/ | ||
throw Error('Backlink association element is not an association or composition: "' + originAssocCsn.name); | ||
} | ||
}); | ||
if(!assocCsn.target.isParamEntity) { | ||
// Header is composed of Items => Cds.Composition: Header is principal => use header's primary keys | ||
let dependentEntity = assocCsn._parent; | ||
let principalEntity = assocCsn.target; | ||
if(assocCsn.type == 'cds.Composition') { | ||
principalEntity = assocCsn._parent; | ||
dependentEntity = assocCsn.target; | ||
// Swap the constraint elements to be correct on Composition [principal, dependent] => [dependent, principal] | ||
Object.keys(result.constraints).forEach(cn => { | ||
result.constraints[cn] = [ result.constraints[cn][1], result.constraints[cn][0] ] } ); | ||
} | ||
// Remove all arget elements that are not key in the principal entity | ||
// and all elements that annotated with '@cds.api.ignore' | ||
glue.foreach(result.constraints, | ||
c => { | ||
let fk = dependentEntity.elements[c[0]]; | ||
let pk = principalEntity.key[c[1]]; | ||
return !(pk && fk && !(pk['@cds.api.ignore'] || fk['@cds.api.ignore'])); | ||
}, | ||
(c, cn) => { delete result.constraints[cn]; } ); | ||
} | ||
} | ||
// Handle managed association, a managed composition is treated as association | ||
else | ||
{ | ||
// If FK is key in target => constraint | ||
// Don't consider primary key associations (fks become keys on the source entity) as | ||
// this would impose a constraint against the target. | ||
// Filter out all elements that annotated with '@cds.api.ignore' | ||
// FIXME: If path is something structured, perform a path resolution (or use augmented CSN) | ||
if(!assocCsn.target.isParamEntity && assocCsn.keys) { | ||
for(let fk of assocCsn.keys) { | ||
let realFk = assocCsn._parent.elements[fk.$generatedFieldName]; | ||
let pk = assocCsn.target.elements[fk.ref[0]].key; | ||
if(pk && !(pk['@cds.api.ignore'] || realFk['@cds.api.ignore'])) | ||
{ | ||
let c = [ fk.$generatedFieldName, fk.ref[0] ]; | ||
result.constraints[c] = c; | ||
} | ||
} | ||
} | ||
} | ||
// If this association points to a redirected Parameter EntityType, do not calculate any constraints, | ||
// continue with multiplicity | ||
if(assocCsn.target.isParamEntity) | ||
{ | ||
result.constraints = Object.create(null); | ||
} | ||
determineMultiplicity(result._originAssocCsn); | ||
return result; | ||
// nested functions | ||
function getExpressionArguments(expr) | ||
{ | ||
let allowedTokens = [ '=', 'and', '(', ')' ]; | ||
if(expr && Array.isArray(expr)) | ||
// if some returns true, this term is not usable as a constraint term | ||
if(!expr.some(isNotAConstraintTerm)) | ||
expr.forEach(fillConstraints) | ||
// return true if token is not one of '=', 'and', '(', ')' or object | ||
function isNotAConstraintTerm(tok) | ||
{ | ||
if(tok.xpr) | ||
return tok.xpr.some(isNotAConstraintTerm); | ||
if(Array.isArray(tok)) | ||
return tok.some(isNotAConstraintTerm); | ||
return !(typeof tok === 'object' && tok != null || allowedTokens.includes(tok)); | ||
} | ||
// fill constraints object with [dependent, principal] pairs and collect all forward assocs for $self terms | ||
function fillConstraints(arg, pos) | ||
{ | ||
if(arg.xpr) | ||
arg.xpr.map(fillConstraints); | ||
else if(pos > 0 && pos < expr.length) | ||
{ | ||
let lhs = expr[pos-1]; | ||
let rhs = expr[pos+1]; | ||
if(['='].includes(arg)) | ||
{ | ||
result.termCount++; | ||
if(lhs.ref && rhs.ref) // ref is a path | ||
{ | ||
lhs = lhs.ref; | ||
rhs = rhs.ref; | ||
// if exactly one operand starts with the prefix then this is potentially a constraint | ||
if((lhs[0] === assocCsn.name && rhs[0] !== assocCsn.name) || | ||
(lhs[0] !== assocCsn.name && rhs[0] === assocCsn.name)) | ||
{ | ||
// order is always [ property, referencedProperty ] | ||
//backlink [ self, assocName ] | ||
let c; | ||
if(lhs[0] === assocCsn.name) | ||
c = [rhs[0], lhs[1]]; | ||
else | ||
c = [lhs[0], rhs[1]]; | ||
// do we have a $self id? | ||
// if so, store partner in selfs array | ||
if(c[0] === NavigationProperty.DOLLAR_SELF) | ||
result.selfs.push(c[1]); | ||
else | ||
result.constraints[c] = c; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
function determineMultiplicity(originCsn) | ||
{ | ||
/* | ||
=> SRC Cardinality | ||
CDS => EDM | ||
------------ | ||
undef => '*' // CDS default mapping for associations | ||
1 => 0..1 // CDS default mapping for compositions | ||
n => '*' | ||
n/a => 1 // not expressable | ||
* => * | ||
=> TGT Cardinality | ||
CDS => EDM | ||
------------ | ||
undef => 0..1 // CDS default mapping for associations | ||
0..1 => 0..1 | ||
1 => 0..1 | ||
1 not null => 1 (targetMin=1 is set by transform/toOdata.js) | ||
1..1 => 1 | ||
0..m => '*' // CDS default mapping for compositions | ||
m => '*' | ||
1..n => '*' | ||
n..m => '*' | ||
* => '*' | ||
*/ | ||
let csn = originCsn || assocCsn; | ||
/* new csn: | ||
src, min, max | ||
*/ | ||
if(!csn.cardinality) | ||
csn.cardinality = Object.create(null); | ||
// set missing defaults | ||
// A managed composition is treated as an ordinary association | ||
/* | ||
if(csn.type == 'cds.Composition' && csn.on || csn.onCond) { | ||
if(!csn.cardinality.src) | ||
csn.cardinality.src = '1'; | ||
if(!csn.cardinality.min) | ||
csn.cardinality.min = 0; | ||
if(!csn.cardinality.max) | ||
csn.cardinality.max = '*'; | ||
} | ||
else */ | ||
{ | ||
if(!csn.cardinality.src) | ||
csn.cardinality.src = '*'; | ||
if(!csn.cardinality.min) | ||
csn.cardinality.min = 0; | ||
if(!csn.cardinality.max) | ||
csn.cardinality.max = 1; | ||
} | ||
let srcCardinality = (csn.cardinality.src == 1) ? '0..1' : '*'; | ||
let tgtCardinality = (csn.cardinality.max > 1 || csn.cardinality.max == '*') ? '*' : | ||
(csn.cardinality.min == 1) ? '1' : '0..1'; | ||
// if originCsn was provided, reverse multiplicity for backlink | ||
result.multiplicity = originCsn ? [tgtCardinality, srcCardinality] : [srcCardinality, tgtCardinality]; | ||
} | ||
} | ||
} | ||
@@ -1229,0 +975,0 @@ |
@@ -127,3 +127,3 @@ 'use strict'; | ||
// FIXME: Should move to some more ODATA-specific location | ||
function initializeModel(model, options) | ||
function initializeModel(model, options, signal, warning) | ||
{ | ||
@@ -158,2 +158,4 @@ if(options == undefined) | ||
foreach(model.definitions, isStructuredArtifact, initializeAssociation); | ||
foreach(model.definitions, isStructuredArtifact, initializeConstraints); | ||
return service; | ||
@@ -251,12 +253,8 @@ | ||
// Only collect @cds.valid.xy in beta-mode -> Only enable temporal in beta-mode | ||
if(options.betaMode){ | ||
if(element['@cds.valid.key']) { | ||
validKey.push(element); | ||
} | ||
if(element['@cds.valid.from']) { | ||
validFrom.push(element); | ||
} | ||
if(element['@cds.valid.key']) { | ||
validKey.push(element); | ||
} | ||
if(element['@cds.valid.from']) { | ||
validFrom.push(element); | ||
} | ||
// Collect keys | ||
@@ -286,4 +284,5 @@ if (element.key) { | ||
}); | ||
struct['@Core.AlternateKeys'] = altKeys; | ||
keys = Object.create(null); | ||
if(struct['@Core.AlternateKeys'] === undefined) { | ||
struct['@Core.AlternateKeys'] = altKeys; | ||
} keys = Object.create(null); | ||
validKey.forEach(e => { | ||
@@ -296,2 +295,3 @@ e.key = true; | ||
validFrom.forEach(e => { | ||
e.key = true; | ||
keys[e.name] = e; | ||
@@ -340,3 +340,3 @@ }); | ||
// target to <Type>Parameters entity (see initializeParameterizedEntityOrView() above) | ||
// Preserve the original target for backlinks (see edm.js NavigationProperty.getReferentialConstraints() | ||
// Preserve the original target for backlinks (getReferentialConstraints() | ||
// for details | ||
@@ -361,2 +361,30 @@ if(element.target._containerEntity && element.target.params) { | ||
function initializeConstraints(struct) { | ||
foreach(struct.elements, isAssociationOrComposition, element => { | ||
if (element._ignore) return; | ||
element._constraints = getReferentialConstraints(element, signal, warning); | ||
// only in V2 we must set the target cardinality of the backlink to the forward: | ||
if(element._constraints._originAssocCsn && element.cardinality && element.cardinality.max) { | ||
if(element._constraints._originAssocCsn.cardinality) { | ||
if(element._constraints._originAssocCsn.cardinality.src) { | ||
let srcMult = (element._constraints._originAssocCsn.cardinality.src == 1) ? '0..1' : '*'; | ||
let newMult = (element.cardinality.max > 1) ? '*' : '0..1'; | ||
if(options.isV2() && srcMult != newMult) { | ||
// Association 'E_toF': Multiplicity of Role='E' defined to '*', conflicting with target multiplicity '0..1' from | ||
signal(warning`Source cardinality "${element._constraints._originAssocCsn.cardinality.src}" of "${element._constraints._originAssocCsn._parent.name}/${element._constraints._originAssocCsn.name}" conflicts with target cardinality "${element.cardinality.max}" of association "${element._parent.name}/${element.name}"`); | ||
} | ||
} | ||
else { | ||
element._constraints._originAssocCsn.cardinality.src = element.cardinality.max; | ||
} | ||
} | ||
else { | ||
element._constraints._originAssocCsn.cardinality = { src: element.cardinality.max }; | ||
} | ||
} | ||
}); | ||
} | ||
/* | ||
@@ -403,2 +431,252 @@ If an element is annotated with @Common.ValueList.entity, an additional | ||
function getReferentialConstraints(assocCsn, signal, warning) | ||
{ | ||
let result = { constraints: Object.create(null), selfs: [], termCount: 0 }; | ||
if(assocCsn.on) | ||
{ | ||
// fill constraint array with [prop, depProp] | ||
getExpressionArguments(assocCsn.on); | ||
// for all $self conditions, fill constraints of partner (if any) | ||
let isBacklink = result.selfs.length == 1 && result.termCount == 1; | ||
/* example for originalTarget: | ||
entity E (with parameters) { | ||
... keys and all the stuff ... | ||
toE: association to E; | ||
back: association to E on back.toE = $self | ||
} | ||
toE target 'E' is redirected to 'EParameters' (must be as the new parameter list is required) | ||
back target 'E' is also redirected to 'EParameters' (otherwise backlink would fail) | ||
ON Condition back.toE => parter=toE cannot be resolved in EParameters, originalTarget 'E' is | ||
required for that | ||
*/ | ||
result.selfs.forEach(partner => { | ||
let originAssocCsn = assocCsn.target.elements[partner]; | ||
if(originAssocCsn == undefined && assocCsn.originalTarget) | ||
originAssocCsn = assocCsn.originalTarget.elements[partner]; | ||
if(originAssocCsn === undefined) { | ||
signal(warning`Cannot resolve backlink to ${assocCsn.target.name}/${partner}" from "${assocCsn._parent.name}/${assocCsn.name}"`); | ||
} | ||
else | ||
{ | ||
if(originAssocCsn.target != assocCsn._parent) { | ||
signal(warning`"${assocCsn._parent.name}/${assocCsn.name}" references "${originAssocCsn._parent.name}/${partner}" in $self ON condition with target "${originAssocCsn.target.name}"`); | ||
} | ||
// let originAssocCsn = assocCsn.target.elements[partner] || assocCsn.originalTarget.elements[partner]; | ||
if(isAssociationOrComposition(originAssocCsn)) { | ||
// if the assoc is marked as primary key, add all its foreign keys as constraint | ||
// as they are primary keys of the other entity as well | ||
if(!assocCsn.target.isParamEntity && originAssocCsn.key) { | ||
if(originAssocCsn.keys) { | ||
for(let fk of originAssocCsn.keys) { | ||
let c = [ fk.ref[0], fk.$generatedFieldName ]; | ||
result.constraints[c] = c; | ||
} | ||
} | ||
} | ||
// Mark this association as backlink if $self appears exactly once | ||
// to surpress edm:Association generation in V2 mode | ||
if(isBacklink) { | ||
// use first backlink as partner | ||
if(originAssocCsn._partnerCsn.length == 0) { | ||
result._originAssocCsn = originAssocCsn; | ||
} | ||
else { | ||
isBacklink = false; | ||
} | ||
// collect all backlinks at forward association | ||
originAssocCsn._partnerCsn.push(assocCsn); | ||
} | ||
} | ||
else { | ||
/* | ||
entity E { | ||
key id : Integer; | ||
toMe: associaton to E on toMe.id = $self; }; | ||
*/ | ||
throw Error('Backlink association element is not an association or composition: "' + originAssocCsn.name); | ||
} | ||
} | ||
}); | ||
if(!assocCsn.target.isParamEntity) { | ||
// Header is composed of Items => Cds.Composition: Header is principal => use header's primary keys | ||
let dependentEntity = assocCsn._parent; | ||
let principalEntity = assocCsn.target; | ||
if(assocCsn.type == 'cds.Composition') { | ||
principalEntity = assocCsn._parent; | ||
dependentEntity = assocCsn.target; | ||
// Swap the constraint elements to be correct on Composition [principal, dependent] => [dependent, principal] | ||
Object.keys(result.constraints).forEach(cn => { | ||
result.constraints[cn] = [ result.constraints[cn][1], result.constraints[cn][0] ] } ); | ||
} | ||
// Remove all arget elements that are not key in the principal entity | ||
// and all elements that annotated with '@cds.api.ignore' | ||
foreach(result.constraints, | ||
c => { | ||
let fk = dependentEntity.elements[c[0]]; | ||
let pk = principalEntity.key[c[1]]; | ||
return !(pk && fk && !(pk['@cds.api.ignore'] || fk['@cds.api.ignore'])); | ||
}, | ||
(c, cn) => { delete result.constraints[cn]; } ); | ||
} | ||
} | ||
// Handle managed association, a managed composition is treated as association | ||
else | ||
{ | ||
// If FK is key in target => constraint | ||
// Don't consider primary key associations (fks become keys on the source entity) as | ||
// this would impose a constraint against the target. | ||
// Filter out all elements that annotated with '@cds.api.ignore' | ||
// FIXME: If path is something structured, perform a path resolution (or use augmented CSN) | ||
if(!assocCsn.target.isParamEntity && assocCsn.keys) { | ||
for(let fk of assocCsn.keys) { | ||
let realFk = assocCsn._parent.elements[fk.$generatedFieldName]; | ||
let pk = assocCsn.target.elements[fk.ref[0]].key; | ||
if(pk && !(pk['@cds.api.ignore'] || realFk['@cds.api.ignore'])) | ||
{ | ||
let c = [ fk.$generatedFieldName, fk.ref[0] ]; | ||
result.constraints[c] = c; | ||
} | ||
} | ||
} | ||
} | ||
// If this association points to a redirected Parameter EntityType, do not calculate any constraints, | ||
// continue with multiplicity | ||
if(assocCsn.target.isParamEntity) | ||
{ | ||
result.constraints = Object.create(null); | ||
} | ||
return result; | ||
// nested functions | ||
function getExpressionArguments(expr) | ||
{ | ||
let allowedTokens = [ '=', 'and', '(', ')' ]; | ||
if(expr && Array.isArray(expr)) | ||
// if some returns true, this term is not usable as a constraint term | ||
if(!expr.some(isNotAConstraintTerm)) | ||
expr.forEach(fillConstraints) | ||
// return true if token is not one of '=', 'and', '(', ')' or object | ||
function isNotAConstraintTerm(tok) | ||
{ | ||
if(tok.xpr) | ||
return tok.xpr.some(isNotAConstraintTerm); | ||
if(Array.isArray(tok)) | ||
return tok.some(isNotAConstraintTerm); | ||
return !(typeof tok === 'object' && tok != null || allowedTokens.includes(tok)); | ||
} | ||
// fill constraints object with [dependent, principal] pairs and collect all forward assocs for $self terms | ||
function fillConstraints(arg, pos) | ||
{ | ||
if(arg.xpr) | ||
arg.xpr.map(fillConstraints); | ||
else if(pos > 0 && pos < expr.length) | ||
{ | ||
let lhs = expr[pos-1]; | ||
let rhs = expr[pos+1]; | ||
if(['='].includes(arg)) | ||
{ | ||
result.termCount++; | ||
if(lhs.ref && rhs.ref) // ref is a path | ||
{ | ||
lhs = lhs.ref; | ||
rhs = rhs.ref; | ||
// if exactly one operand starts with the prefix then this is potentially a constraint | ||
if((lhs[0] === assocCsn.name && rhs[0] !== assocCsn.name) || | ||
(lhs[0] !== assocCsn.name && rhs[0] === assocCsn.name)) | ||
{ | ||
// order is always [ property, referencedProperty ] | ||
//backlink [ self, assocName ] | ||
let c; | ||
if(lhs[0] === assocCsn.name) | ||
c = [rhs[0], lhs[1]]; | ||
else | ||
c = [lhs[0], rhs[1]]; | ||
// do we have a $self id? | ||
// if so, store partner in selfs array | ||
if(c[0] === '$self') | ||
result.selfs.push(c[1]); | ||
else | ||
result.constraints[c] = c; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
function determineMultiplicity(csn) | ||
{ | ||
/* | ||
=> SRC Cardinality | ||
CDS => EDM | ||
------------ | ||
undef => '*' // CDS default mapping for associations | ||
1 => 0..1 // CDS default mapping for compositions | ||
n => '*' | ||
n/a => 1 // not expressable | ||
* => * | ||
=> TGT Cardinality | ||
CDS => EDM | ||
------------ | ||
undef => 0..1 // CDS default mapping for associations | ||
0..1 => 0..1 | ||
1 => 0..1 | ||
1 not null => 1 (targetMin=1 is set by transform/toOdata.js) | ||
1..1 => 1 | ||
0..m => '*' // CDS default mapping for compositions | ||
m => '*' | ||
1..n => '*' | ||
n..m => '*' | ||
* => '*' | ||
*/ | ||
/* new csn: | ||
src, min, max | ||
*/ | ||
if(!csn.cardinality) | ||
csn.cardinality = Object.create(null); | ||
// set missing defaults | ||
// A managed composition is treated as an ordinary association | ||
/* | ||
if(csn.type == 'cds.Composition' && csn.on || csn.onCond) { | ||
if(!csn.cardinality.src) | ||
csn.cardinality.src = '1'; | ||
if(!csn.cardinality.min) | ||
csn.cardinality.min = 0; | ||
if(!csn.cardinality.max) | ||
csn.cardinality.max = '*'; | ||
} | ||
else */ | ||
{ | ||
if(!csn.cardinality.src) | ||
csn.cardinality.src = '*'; | ||
if(!csn.cardinality.min) | ||
csn.cardinality.min = 0; | ||
if(!csn.cardinality.max) | ||
csn.cardinality.max = 1; | ||
} | ||
let srcCardinality = (csn.cardinality.src == 1) ? '0..1' : '*'; | ||
let tgtCardinality = (csn.cardinality.max > 1 || csn.cardinality.max == '*') ? '*' : | ||
(csn.cardinality.min == 1) ? '1' : '0..1'; | ||
return [srcCardinality, tgtCardinality]; | ||
} | ||
function mapCdsToEdmType(cdsType, isV2=false, isMediaType=false) | ||
@@ -495,4 +773,6 @@ { | ||
initializeModel, | ||
getReferentialConstraints, | ||
determineMultiplicity, | ||
mapCdsToEdmType, | ||
addTypeFacets | ||
} |
@@ -28,10 +28,27 @@ // Generated from JSON.g4 by ANTLR 4.7.1 | ||
function unquote(s) { | ||
return JSON.parse(s); // unquote for free | ||
function unquoteNumber(s) { | ||
return JSON.parse(s); // unquote | ||
} | ||
function unquoteString(s) { | ||
if(s.startsWith("'") && s.endsWith("'")) { | ||
let ss = s.substring(1,s.length-1); | ||
ss = ss.replace(/\\/g, '\\\\') | ||
ss = ss.replace(/\"/g, '\\"') | ||
s = '"' + ss + '"'; | ||
} | ||
if(!s.startsWith('"') && !s.endsWith('"') && !isCharNumber(s[0])) { // plain string | ||
s = '"' + s + '"'; | ||
} | ||
return JSON.parse(s); // unquote | ||
function isCharNumber(c) { | ||
return c >= '0' && c <= '9'; | ||
} | ||
} | ||
var serializedATN = ["\u0003\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964", | ||
"\u0002\u0010\u00a5\b\u0001\u0004\u0002\t\u0002\u0004\u0003\t\u0003\u0004", | ||
"\u0002\u0010\u00ed\b\u0001\u0004\u0002\t\u0002\u0004\u0003\t\u0003\u0004", | ||
"\u0004\t\u0004\u0004\u0005\t\u0005\u0004\u0006\t\u0006\u0004\u0007\t", | ||
@@ -41,100 +58,151 @@ "\u0007\u0004\b\t\b\u0004\t\t\t\u0004\n\t\n\u0004\u000b\t\u000b\u0004", | ||
"\t\u0010\u0004\u0011\t\u0011\u0004\u0012\t\u0012\u0004\u0013\t\u0013", | ||
"\u0004\u0014\t\u0014\u0003\u0002\u0003\u0002\u0003\u0003\u0003\u0003", | ||
"\u0003\u0004\u0003\u0004\u0003\u0005\u0003\u0005\u0003\u0006\u0003\u0006", | ||
"\u0003\u0007\u0003\u0007\u0003\b\u0003\b\u0003\b\u0003\b\u0003\b\u0003", | ||
"\t\u0003\t\u0003\t\u0003\t\u0003\t\u0003\t\u0003\n\u0003\n\u0003\n\u0003", | ||
"\n\u0003\n\u0003\u000b\u0003\u000b\u0003\u000b\u0007\u000bI\n\u000b", | ||
"\f\u000b\u000e\u000bL\u000b\u000b\u0003\u000b\u0003\u000b\u0003\f\u0003", | ||
"\f\u0003\f\u0005\fS\n\f\u0003\r\u0003\r\u0003\r\u0003\r\u0003\r\u0003", | ||
"\r\u0003\u000e\u0003\u000e\u0003\u000f\u0005\u000f^\n\u000f\u0003\u000f", | ||
"\u0003\u000f\u0003\u000f\u0006\u000fc\n\u000f\r\u000f\u000e\u000fd\u0003", | ||
"\u000f\u0005\u000fh\n\u000f\u0003\u000f\u0005\u000fk\n\u000f\u0003\u000f", | ||
"\u0003\u000f\u0003\u000f\u0003\u000f\u0005\u000fq\n\u000f\u0003\u000f", | ||
"\u0005\u000ft\n\u000f\u0003\u0010\u0003\u0010\u0003\u0010\u0007\u0010", | ||
"y\n\u0010\f\u0010\u000e\u0010|\u000b\u0010\u0005\u0010~\n\u0010\u0003", | ||
"\u0011\u0003\u0011\u0005\u0011\u0082\n\u0011\u0003\u0011\u0003\u0011", | ||
"\u0003\u0012\u0006\u0012\u0087\n\u0012\r\u0012\u000e\u0012\u0088\u0003", | ||
"\u0012\u0003\u0012\u0003\u0013\u0003\u0013\u0003\u0013\u0003\u0013\u0007", | ||
"\u0013\u0091\n\u0013\f\u0013\u000e\u0013\u0094\u000b\u0013\u0003\u0013", | ||
"\u0003\u0013\u0003\u0013\u0003\u0013\u0003\u0013\u0003\u0014\u0003\u0014", | ||
"\u0003\u0014\u0003\u0014\u0007\u0014\u009f\n\u0014\f\u0014\u000e\u0014", | ||
"\u00a2\u000b\u0014\u0003\u0014\u0003\u0014\u0003\u0092\u0002\u0015\u0003", | ||
"\u0003\u0005\u0004\u0007\u0005\t\u0006\u000b\u0007\r\b\u000f\t\u0011", | ||
"\n\u0013\u000b\u0015\f\u0017\u0002\u0019\u0002\u001b\u0002\u001d\r\u001f", | ||
"\u0002!\u0002#\u000e%\u000f\'\u0010\u0003\u0002\u000b\u0004\u0002$$", | ||
"^^\n\u0002$$11^^ddhhppttvv\u0005\u00022;CHch\u0003\u00022;\u0003\u0002", | ||
"3;\u0004\u0002GGgg\u0004\u0002--//\u0005\u0002\u000b\f\u000f\u000f\"", | ||
"\"\u0004\u0002\f\f\u000f\u000f\u0002\u00af\u0002\u0003\u0003\u0002\u0002", | ||
"\u0002\u0002\u0005\u0003\u0002\u0002\u0002\u0002\u0007\u0003\u0002\u0002", | ||
"\u0002\u0002\t\u0003\u0002\u0002\u0002\u0002\u000b\u0003\u0002\u0002", | ||
"\u0002\u0002\r\u0003\u0002\u0002\u0002\u0002\u000f\u0003\u0002\u0002", | ||
"\u0002\u0002\u0011\u0003\u0002\u0002\u0002\u0002\u0013\u0003\u0002\u0002", | ||
"\u0002\u0002\u0015\u0003\u0002\u0002\u0002\u0002\u001d\u0003\u0002\u0002", | ||
"\u0002\u0002#\u0003\u0002\u0002\u0002\u0002%\u0003\u0002\u0002\u0002", | ||
"\u0002\'\u0003\u0002\u0002\u0002\u0003)\u0003\u0002\u0002\u0002\u0005", | ||
"+\u0003\u0002\u0002\u0002\u0007-\u0003\u0002\u0002\u0002\t/\u0003\u0002", | ||
"\u0002\u0002\u000b1\u0003\u0002\u0002\u0002\r3\u0003\u0002\u0002\u0002", | ||
"\u000f5\u0003\u0002\u0002\u0002\u0011:\u0003\u0002\u0002\u0002\u0013", | ||
"@\u0003\u0002\u0002\u0002\u0015E\u0003\u0002\u0002\u0002\u0017O\u0003", | ||
"\u0002\u0002\u0002\u0019T\u0003\u0002\u0002\u0002\u001bZ\u0003\u0002", | ||
"\u0002\u0002\u001ds\u0003\u0002\u0002\u0002\u001f}\u0003\u0002\u0002", | ||
"\u0002!\u007f\u0003\u0002\u0002\u0002#\u0086\u0003\u0002\u0002\u0002", | ||
"%\u008c\u0003\u0002\u0002\u0002\'\u009a\u0003\u0002\u0002\u0002)*\u0007", | ||
"}\u0002\u0002*\u0004\u0003\u0002\u0002\u0002+,\u0007.\u0002\u0002,\u0006", | ||
"\u0003\u0002\u0002\u0002-.\u0007\u007f\u0002\u0002.\b\u0003\u0002\u0002", | ||
"\u0002/0\u0007<\u0002\u00020\n\u0003\u0002\u0002\u000212\u0007]\u0002", | ||
"\u00022\f\u0003\u0002\u0002\u000234\u0007_\u0002\u00024\u000e\u0003", | ||
"\u0002\u0002\u000256\u0007v\u0002\u000267\u0007t\u0002\u000278\u0007", | ||
"w\u0002\u000289\u0007g\u0002\u00029\u0010\u0003\u0002\u0002\u0002:;", | ||
"\u0007h\u0002\u0002;<\u0007c\u0002\u0002<=\u0007n\u0002\u0002=>\u0007", | ||
"u\u0002\u0002>?\u0007g\u0002\u0002?\u0012\u0003\u0002\u0002\u0002@A", | ||
"\u0007p\u0002\u0002AB\u0007w\u0002\u0002BC\u0007n\u0002\u0002CD\u0007", | ||
"n\u0002\u0002D\u0014\u0003\u0002\u0002\u0002EJ\u0007$\u0002\u0002FI", | ||
"\u0005\u0017\f\u0002GI\n\u0002\u0002\u0002HF\u0003\u0002\u0002\u0002", | ||
"HG\u0003\u0002\u0002\u0002IL\u0003\u0002\u0002\u0002JH\u0003\u0002\u0002", | ||
"\u0002JK\u0003\u0002\u0002\u0002KM\u0003\u0002\u0002\u0002LJ\u0003\u0002", | ||
"\u0002\u0002MN\u0007$\u0002\u0002N\u0016\u0003\u0002\u0002\u0002OR\u0007", | ||
"^\u0002\u0002PS\t\u0003\u0002\u0002QS\u0005\u0019\r\u0002RP\u0003\u0002", | ||
"\u0002\u0002RQ\u0003\u0002\u0002\u0002S\u0018\u0003\u0002\u0002\u0002", | ||
"TU\u0007w\u0002\u0002UV\u0005\u001b\u000e\u0002VW\u0005\u001b\u000e", | ||
"\u0002WX\u0005\u001b\u000e\u0002XY\u0005\u001b\u000e\u0002Y\u001a\u0003", | ||
"\u0002\u0002\u0002Z[\t\u0004\u0002\u0002[\u001c\u0003\u0002\u0002\u0002", | ||
"\\^\u0007/\u0002\u0002]\\\u0003\u0002\u0002\u0002]^\u0003\u0002\u0002", | ||
"\u0002^_\u0003\u0002\u0002\u0002_`\u0005\u001f\u0010\u0002`b\u00070", | ||
"\u0002\u0002ac\t\u0005\u0002\u0002ba\u0003\u0002\u0002\u0002cd\u0003", | ||
"\u0002\u0002\u0002db\u0003\u0002\u0002\u0002de\u0003\u0002\u0002\u0002", | ||
"eg\u0003\u0002\u0002\u0002fh\u0005!\u0011\u0002gf\u0003\u0002\u0002", | ||
"\u0002gh\u0003\u0002\u0002\u0002ht\u0003\u0002\u0002\u0002ik\u0007/", | ||
"\u0002\u0002ji\u0003\u0002\u0002\u0002jk\u0003\u0002\u0002\u0002kl\u0003", | ||
"\u0002\u0002\u0002lm\u0005\u001f\u0010\u0002mn\u0005!\u0011\u0002nt", | ||
"\u0003\u0002\u0002\u0002oq\u0007/\u0002\u0002po\u0003\u0002\u0002\u0002", | ||
"pq\u0003\u0002\u0002\u0002qr\u0003\u0002\u0002\u0002rt\u0005\u001f\u0010", | ||
"\u0002s]\u0003\u0002\u0002\u0002sj\u0003\u0002\u0002\u0002sp\u0003\u0002", | ||
"\u0002\u0002t\u001e\u0003\u0002\u0002\u0002u~\u00072\u0002\u0002vz\t", | ||
"\u0006\u0002\u0002wy\t\u0005\u0002\u0002xw\u0003\u0002\u0002\u0002y", | ||
"|\u0003\u0002\u0002\u0002zx\u0003\u0002\u0002\u0002z{\u0003\u0002\u0002", | ||
"\u0002{~\u0003\u0002\u0002\u0002|z\u0003\u0002\u0002\u0002}u\u0003\u0002", | ||
"\u0002\u0002}v\u0003\u0002\u0002\u0002~ \u0003\u0002\u0002\u0002\u007f", | ||
"\u0081\t\u0007\u0002\u0002\u0080\u0082\t\b\u0002\u0002\u0081\u0080\u0003", | ||
"\u0002\u0002\u0002\u0081\u0082\u0003\u0002\u0002\u0002\u0082\u0083\u0003", | ||
"\u0002\u0002\u0002\u0083\u0084\u0005\u001f\u0010\u0002\u0084\"\u0003", | ||
"\u0002\u0002\u0002\u0085\u0087\t\t\u0002\u0002\u0086\u0085\u0003\u0002", | ||
"\u0002\u0002\u0087\u0088\u0003\u0002\u0002\u0002\u0088\u0086\u0003\u0002", | ||
"\u0002\u0002\u0088\u0089\u0003\u0002\u0002\u0002\u0089\u008a\u0003\u0002", | ||
"\u0002\u0002\u008a\u008b\b\u0012\u0002\u0002\u008b$\u0003\u0002\u0002", | ||
"\u0002\u008c\u008d\u00071\u0002\u0002\u008d\u008e\u0007,\u0002\u0002", | ||
"\u008e\u0092\u0003\u0002\u0002\u0002\u008f\u0091\u000b\u0002\u0002\u0002", | ||
"\u0090\u008f\u0003\u0002\u0002\u0002\u0091\u0094\u0003\u0002\u0002\u0002", | ||
"\u0092\u0093\u0003\u0002\u0002\u0002\u0092\u0090\u0003\u0002\u0002\u0002", | ||
"\u0093\u0095\u0003\u0002\u0002\u0002\u0094\u0092\u0003\u0002\u0002\u0002", | ||
"\u0095\u0096\u0007,\u0002\u0002\u0096\u0097\u00071\u0002\u0002\u0097", | ||
"\u0098\u0003\u0002\u0002\u0002\u0098\u0099\b\u0013\u0002\u0002\u0099", | ||
"&\u0003\u0002\u0002\u0002\u009a\u009b\u00071\u0002\u0002\u009b\u009c", | ||
"\u00071\u0002\u0002\u009c\u00a0\u0003\u0002\u0002\u0002\u009d\u009f", | ||
"\n\n\u0002\u0002\u009e\u009d\u0003\u0002\u0002\u0002\u009f\u00a2\u0003", | ||
"\u0002\u0002\u0002\u00a0\u009e\u0003\u0002\u0002\u0002\u00a0\u00a1\u0003", | ||
"\u0002\u0002\u0002\u00a1\u00a3\u0003\u0002\u0002\u0002\u00a2\u00a0\u0003", | ||
"\u0002\u0002\u0002\u00a3\u00a4\b\u0014\u0002\u0002\u00a4(\u0003\u0002", | ||
"\u0002\u0002\u0012\u0002HJR]dgjpsz}\u0081\u0088\u0092\u00a0\u0003\b", | ||
"\u0002\u0002"].join(""); | ||
"\u0004\u0014\t\u0014\u0004\u0015\t\u0015\u0004\u0016\t\u0016\u0004\u0017", | ||
"\t\u0017\u0004\u0018\t\u0018\u0004\u0019\t\u0019\u0004\u001a\t\u001a", | ||
"\u0004\u001b\t\u001b\u0004\u001c\t\u001c\u0004\u001d\t\u001d\u0004\u001e", | ||
"\t\u001e\u0004\u001f\t\u001f\u0004 \t \u0003\u0002\u0003\u0002\u0003", | ||
"\u0003\u0003\u0003\u0003\u0004\u0003\u0004\u0003\u0005\u0003\u0005\u0003", | ||
"\u0006\u0003\u0006\u0003\u0007\u0003\u0007\u0003\b\u0003\b\u0003\b\u0003", | ||
"\b\u0003\b\u0003\t\u0003\t\u0003\t\u0003\t\u0003\t\u0003\t\u0003\n\u0003", | ||
"\n\u0003\n\u0003\n\u0003\n\u0003\u000b\u0003\u000b\u0003\u000b\u0005", | ||
"\u000ba\n\u000b\u0003\f\u0003\f\u0003\f\u0003\f\u0007\fg\n\f\f\f\u000e", | ||
"\fj\u000b\f\u0003\r\u0003\r\u0003\r\u0003\r\u0003\r\u0003\r\u0005\r", | ||
"r\n\r\u0003\u000e\u0003\u000e\u0003\u000f\u0003\u000f\u0003\u0010\u0003", | ||
"\u0010\u0003\u0011\u0003\u0011\u0003\u0012\u0003\u0012\u0003\u0013\u0003", | ||
"\u0013\u0003\u0014\u0003\u0014\u0003\u0015\u0003\u0015\u0003\u0016\u0003", | ||
"\u0016\u0003\u0016\u0007\u0016\u0087\n\u0016\f\u0016\u000e\u0016\u008a", | ||
"\u000b\u0016\u0003\u0016\u0003\u0016\u0003\u0017\u0003\u0017\u0003\u0017", | ||
"\u0007\u0017\u0091\n\u0017\f\u0017\u000e\u0017\u0094\u000b\u0017\u0003", | ||
"\u0017\u0003\u0017\u0003\u0018\u0003\u0018\u0003\u0018\u0005\u0018\u009b", | ||
"\n\u0018\u0003\u0019\u0003\u0019\u0003\u0019\u0003\u0019\u0003\u0019", | ||
"\u0003\u0019\u0003\u001a\u0003\u001a\u0003\u001b\u0005\u001b\u00a6\n", | ||
"\u001b\u0003\u001b\u0003\u001b\u0003\u001b\u0006\u001b\u00ab\n\u001b", | ||
"\r\u001b\u000e\u001b\u00ac\u0003\u001b\u0005\u001b\u00b0\n\u001b\u0003", | ||
"\u001b\u0005\u001b\u00b3\n\u001b\u0003\u001b\u0003\u001b\u0003\u001b", | ||
"\u0003\u001b\u0005\u001b\u00b9\n\u001b\u0003\u001b\u0005\u001b\u00bc", | ||
"\n\u001b\u0003\u001c\u0003\u001c\u0003\u001c\u0007\u001c\u00c1\n\u001c", | ||
"\f\u001c\u000e\u001c\u00c4\u000b\u001c\u0005\u001c\u00c6\n\u001c\u0003", | ||
"\u001d\u0003\u001d\u0005\u001d\u00ca\n\u001d\u0003\u001d\u0003\u001d", | ||
"\u0003\u001e\u0006\u001e\u00cf\n\u001e\r\u001e\u000e\u001e\u00d0\u0003", | ||
"\u001e\u0003\u001e\u0003\u001f\u0003\u001f\u0003\u001f\u0003\u001f\u0007", | ||
"\u001f\u00d9\n\u001f\f\u001f\u000e\u001f\u00dc\u000b\u001f\u0003\u001f", | ||
"\u0003\u001f\u0003\u001f\u0003\u001f\u0003\u001f\u0003 \u0003 \u0003", | ||
" \u0003 \u0007 \u00e7\n \f \u000e \u00ea\u000b \u0003 \u0003 \u0003", | ||
"\u00da\u0002!\u0003\u0003\u0005\u0004\u0007\u0005\t\u0006\u000b\u0007", | ||
"\r\b\u000f\t\u0011\n\u0013\u000b\u0015\f\u0017\u0002\u0019\u0002\u001b", | ||
"\u0002\u001d\u0002\u001f\u0002!\u0002#\u0002%\u0002\'\u0002)\u0002+", | ||
"\u0002-\u0002/\u00021\u00023\u00025\r7\u00029\u0002;\u000e=\u000f?\u0010", | ||
"\u0003\u0002\u000e\u0007\u0002##%(--`a\u0080\u0080\u0004\u0002C\\c|", | ||
"\u0003\u00022;\u0004\u0002))^^\u0004\u0002$$^^\n\u0002$$11^^ddhhppt", | ||
"tvv\u0005\u00022;CHch\u0003\u00023;\u0004\u0002GGgg\u0004\u0002--//", | ||
"\u0005\u0002\u000b\f\u000f\u000f\"\"\u0004\u0002\f\f\u000f\u000f\u0002", | ||
"\u00f7\u0002\u0003\u0003\u0002\u0002\u0002\u0002\u0005\u0003\u0002\u0002", | ||
"\u0002\u0002\u0007\u0003\u0002\u0002\u0002\u0002\t\u0003\u0002\u0002", | ||
"\u0002\u0002\u000b\u0003\u0002\u0002\u0002\u0002\r\u0003\u0002\u0002", | ||
"\u0002\u0002\u000f\u0003\u0002\u0002\u0002\u0002\u0011\u0003\u0002\u0002", | ||
"\u0002\u0002\u0013\u0003\u0002\u0002\u0002\u0002\u0015\u0003\u0002\u0002", | ||
"\u0002\u00025\u0003\u0002\u0002\u0002\u0002;\u0003\u0002\u0002\u0002", | ||
"\u0002=\u0003\u0002\u0002\u0002\u0002?\u0003\u0002\u0002\u0002\u0003", | ||
"A\u0003\u0002\u0002\u0002\u0005C\u0003\u0002\u0002\u0002\u0007E\u0003", | ||
"\u0002\u0002\u0002\tG\u0003\u0002\u0002\u0002\u000bI\u0003\u0002\u0002", | ||
"\u0002\rK\u0003\u0002\u0002\u0002\u000fM\u0003\u0002\u0002\u0002\u0011", | ||
"R\u0003\u0002\u0002\u0002\u0013X\u0003\u0002\u0002\u0002\u0015`\u0003", | ||
"\u0002\u0002\u0002\u0017b\u0003\u0002\u0002\u0002\u0019q\u0003\u0002", | ||
"\u0002\u0002\u001bs\u0003\u0002\u0002\u0002\u001du\u0003\u0002\u0002", | ||
"\u0002\u001fw\u0003\u0002\u0002\u0002!y\u0003\u0002\u0002\u0002#{\u0003", | ||
"\u0002\u0002\u0002%}\u0003\u0002\u0002\u0002\'\u007f\u0003\u0002\u0002", | ||
"\u0002)\u0081\u0003\u0002\u0002\u0002+\u0083\u0003\u0002\u0002\u0002", | ||
"-\u008d\u0003\u0002\u0002\u0002/\u0097\u0003\u0002\u0002\u00021\u009c", | ||
"\u0003\u0002\u0002\u00023\u00a2\u0003\u0002\u0002\u00025\u00bb\u0003", | ||
"\u0002\u0002\u00027\u00c5\u0003\u0002\u0002\u00029\u00c7\u0003\u0002", | ||
"\u0002\u0002;\u00ce\u0003\u0002\u0002\u0002=\u00d4\u0003\u0002\u0002", | ||
"\u0002?\u00e2\u0003\u0002\u0002\u0002AB\u0007}\u0002\u0002B\u0004\u0003", | ||
"\u0002\u0002\u0002CD\u0007.\u0002\u0002D\u0006\u0003\u0002\u0002\u0002", | ||
"EF\u0007\u007f\u0002\u0002F\b\u0003\u0002\u0002\u0002GH\u0007<\u0002", | ||
"\u0002H\n\u0003\u0002\u0002\u0002IJ\u0007]\u0002\u0002J\f\u0003\u0002", | ||
"\u0002\u0002KL\u0007_\u0002\u0002L\u000e\u0003\u0002\u0002\u0002MN\u0007", | ||
"v\u0002\u0002NO\u0007t\u0002\u0002OP\u0007w\u0002\u0002PQ\u0007g\u0002", | ||
"\u0002Q\u0010\u0003\u0002\u0002\u0002RS\u0007h\u0002\u0002ST\u0007c", | ||
"\u0002\u0002TU\u0007n\u0002\u0002UV\u0007u\u0002\u0002VW\u0007g\u0002", | ||
"\u0002W\u0012\u0003\u0002\u0002\u0002XY\u0007p\u0002\u0002YZ\u0007w", | ||
"\u0002\u0002Z[\u0007n\u0002\u0002[\\\u0007n\u0002\u0002\\\u0014\u0003", | ||
"\u0002\u0002\u0002]a\u0005+\u0016\u0002^a\u0005-\u0017\u0002_a\u0005", | ||
"\u0017\f\u0002`]\u0003\u0002\u0002\u0002`^\u0003\u0002\u0002\u0002`", | ||
"_\u0003\u0002\u0002\u0002a\u0016\u0003\u0002\u0002\u0002bh\u0005\u0019", | ||
"\r\u0002cg\u0005\u0019\r\u0002dg\u0005)\u0015\u0002eg\u0005%\u0013\u0002", | ||
"fc\u0003\u0002\u0002\u0002fd\u0003\u0002\u0002\u0002fe\u0003\u0002\u0002", | ||
"\u0002gj\u0003\u0002\u0002\u0002hf\u0003\u0002\u0002\u0002hi\u0003\u0002", | ||
"\u0002\u0002i\u0018\u0003\u0002\u0002\u0002jh\u0003\u0002\u0002\u0002", | ||
"kr\u0005\u001d\u000f\u0002lr\u0005\u001b\u000e\u0002mr\u0005\u001f\u0010", | ||
"\u0002nr\u0005!\u0011\u0002or\u0005#\u0012\u0002pr\u0005\'\u0014\u0002", | ||
"qk\u0003\u0002\u0002\u0002ql\u0003\u0002\u0002\u0002qm\u0003\u0002\u0002", | ||
"\u0002qn\u0003\u0002\u0002\u0002qo\u0003\u0002\u0002\u0002qp\u0003\u0002", | ||
"\u0002\u0002r\u001a\u0003\u0002\u0002\u0002st\t\u0002\u0002\u0002t\u001c", | ||
"\u0003\u0002\u0002\u0002uv\t\u0003\u0002\u0002v\u001e\u0003\u0002\u0002", | ||
"\u0002wx\u0007,\u0002\u0002x \u0003\u0002\u0002\u0002yz\u0007A\u0002", | ||
"\u0002z\"\u0003\u0002\u0002\u0002{|\u00070\u0002\u0002|$\u0003\u0002", | ||
"\u0002\u0002}~\u0007/\u0002\u0002~&\u0003\u0002\u0002\u0002\u007f\u0080", | ||
"\u0007B\u0002\u0002\u0080(\u0003\u0002\u0002\u0002\u0081\u0082\t\u0004", | ||
"\u0002\u0002\u0082*\u0003\u0002\u0002\u0002\u0083\u0088\u0007)\u0002", | ||
"\u0002\u0084\u0087\u0005/\u0018\u0002\u0085\u0087\n\u0005\u0002\u0002", | ||
"\u0086\u0084\u0003\u0002\u0002\u0002\u0086\u0085\u0003\u0002\u0002\u0002", | ||
"\u0087\u008a\u0003\u0002\u0002\u0002\u0088\u0086\u0003\u0002\u0002\u0002", | ||
"\u0088\u0089\u0003\u0002\u0002\u0002\u0089\u008b\u0003\u0002\u0002\u0002", | ||
"\u008a\u0088\u0003\u0002\u0002\u0002\u008b\u008c\u0007)\u0002\u0002", | ||
"\u008c,\u0003\u0002\u0002\u0002\u008d\u0092\u0007$\u0002\u0002\u008e", | ||
"\u0091\u0005/\u0018\u0002\u008f\u0091\n\u0006\u0002\u0002\u0090\u008e", | ||
"\u0003\u0002\u0002\u0002\u0090\u008f\u0003\u0002\u0002\u0002\u0091\u0094", | ||
"\u0003\u0002\u0002\u0002\u0092\u0090\u0003\u0002\u0002\u0002\u0092\u0093", | ||
"\u0003\u0002\u0002\u0002\u0093\u0095\u0003\u0002\u0002\u0002\u0094\u0092", | ||
"\u0003\u0002\u0002\u0002\u0095\u0096\u0007$\u0002\u0002\u0096.\u0003", | ||
"\u0002\u0002\u0002\u0097\u009a\u0007^\u0002\u0002\u0098\u009b\t\u0007", | ||
"\u0002\u0002\u0099\u009b\u00051\u0019\u0002\u009a\u0098\u0003\u0002", | ||
"\u0002\u0002\u009a\u0099\u0003\u0002\u0002\u0002\u009b0\u0003\u0002", | ||
"\u0002\u0002\u009c\u009d\u0007w\u0002\u0002\u009d\u009e\u00053\u001a", | ||
"\u0002\u009e\u009f\u00053\u001a\u0002\u009f\u00a0\u00053\u001a\u0002", | ||
"\u00a0\u00a1\u00053\u001a\u0002\u00a12\u0003\u0002\u0002\u0002\u00a2", | ||
"\u00a3\t\b\u0002\u0002\u00a34\u0003\u0002\u0002\u0002\u00a4\u00a6\u0007", | ||
"/\u0002\u0002\u00a5\u00a4\u0003\u0002\u0002\u0002\u00a5\u00a6\u0003", | ||
"\u0002\u0002\u0002\u00a6\u00a7\u0003\u0002\u0002\u0002\u00a7\u00a8\u0005", | ||
"7\u001c\u0002\u00a8\u00aa\u00070\u0002\u0002\u00a9\u00ab\t\u0004\u0002", | ||
"\u0002\u00aa\u00a9\u0003\u0002\u0002\u0002\u00ab\u00ac\u0003\u0002\u0002", | ||
"\u0002\u00ac\u00aa\u0003\u0002\u0002\u0002\u00ac\u00ad\u0003\u0002\u0002", | ||
"\u0002\u00ad\u00af\u0003\u0002\u0002\u0002\u00ae\u00b0\u00059\u001d", | ||
"\u0002\u00af\u00ae\u0003\u0002\u0002\u0002\u00af\u00b0\u0003\u0002\u0002", | ||
"\u0002\u00b0\u00bc\u0003\u0002\u0002\u0002\u00b1\u00b3\u0007/\u0002", | ||
"\u0002\u00b2\u00b1\u0003\u0002\u0002\u0002\u00b2\u00b3\u0003\u0002\u0002", | ||
"\u0002\u00b3\u00b4\u0003\u0002\u0002\u0002\u00b4\u00b5\u00057\u001c", | ||
"\u0002\u00b5\u00b6\u00059\u001d\u0002\u00b6\u00bc\u0003\u0002\u0002", | ||
"\u0002\u00b7\u00b9\u0007/\u0002\u0002\u00b8\u00b7\u0003\u0002\u0002", | ||
"\u0002\u00b8\u00b9\u0003\u0002\u0002\u0002\u00b9\u00ba\u0003\u0002\u0002", | ||
"\u0002\u00ba\u00bc\u00057\u001c\u0002\u00bb\u00a5\u0003\u0002\u0002", | ||
"\u0002\u00bb\u00b2\u0003\u0002\u0002\u0002\u00bb\u00b8\u0003\u0002\u0002", | ||
"\u0002\u00bc6\u0003\u0002\u0002\u0002\u00bd\u00c6\u00072\u0002\u0002", | ||
"\u00be\u00c2\t\t\u0002\u0002\u00bf\u00c1\t\u0004\u0002\u0002\u00c0\u00bf", | ||
"\u0003\u0002\u0002\u0002\u00c1\u00c4\u0003\u0002\u0002\u0002\u00c2\u00c0", | ||
"\u0003\u0002\u0002\u0002\u00c2\u00c3\u0003\u0002\u0002\u0002\u00c3\u00c6", | ||
"\u0003\u0002\u0002\u0002\u00c4\u00c2\u0003\u0002\u0002\u0002\u00c5\u00bd", | ||
"\u0003\u0002\u0002\u0002\u00c5\u00be\u0003\u0002\u0002\u0002\u00c68", | ||
"\u0003\u0002\u0002\u0002\u00c7\u00c9\t\n\u0002\u0002\u00c8\u00ca\t\u000b", | ||
"\u0002\u0002\u00c9\u00c8\u0003\u0002\u0002\u0002\u00c9\u00ca\u0003\u0002", | ||
"\u0002\u0002\u00ca\u00cb\u0003\u0002\u0002\u0002\u00cb\u00cc\u00057", | ||
"\u001c\u0002\u00cc:\u0003\u0002\u0002\u0002\u00cd\u00cf\t\f\u0002\u0002", | ||
"\u00ce\u00cd\u0003\u0002\u0002\u0002\u00cf\u00d0\u0003\u0002\u0002\u0002", | ||
"\u00d0\u00ce\u0003\u0002\u0002\u0002\u00d0\u00d1\u0003\u0002\u0002\u0002", | ||
"\u00d1\u00d2\u0003\u0002\u0002\u0002\u00d2\u00d3\b\u001e\u0002\u0002", | ||
"\u00d3<\u0003\u0002\u0002\u0002\u00d4\u00d5\u00071\u0002\u0002\u00d5", | ||
"\u00d6\u0007,\u0002\u0002\u00d6\u00da\u0003\u0002\u0002\u0002\u00d7", | ||
"\u00d9\u000b\u0002\u0002\u0002\u00d8\u00d7\u0003\u0002\u0002\u0002\u00d9", | ||
"\u00dc\u0003\u0002\u0002\u0002\u00da\u00db\u0003\u0002\u0002\u0002\u00da", | ||
"\u00d8\u0003\u0002\u0002\u0002\u00db\u00dd\u0003\u0002\u0002\u0002\u00dc", | ||
"\u00da\u0003\u0002\u0002\u0002\u00dd\u00de\u0007,\u0002\u0002\u00de", | ||
"\u00df\u00071\u0002\u0002\u00df\u00e0\u0003\u0002\u0002\u0002\u00e0", | ||
"\u00e1\b\u001f\u0002\u0002\u00e1>\u0003\u0002\u0002\u0002\u00e2\u00e3", | ||
"\u00071\u0002\u0002\u00e3\u00e4\u00071\u0002\u0002\u00e4\u00e8\u0003", | ||
"\u0002\u0002\u0002\u00e5\u00e7\n\r\u0002\u0002\u00e6\u00e5\u0003\u0002", | ||
"\u0002\u0002\u00e7\u00ea\u0003\u0002\u0002\u0002\u00e8\u00e6\u0003\u0002", | ||
"\u0002\u0002\u00e8\u00e9\u0003\u0002\u0002\u0002\u00e9\u00eb\u0003\u0002", | ||
"\u0002\u0002\u00ea\u00e8\u0003\u0002\u0002\u0002\u00eb\u00ec\b \u0002", | ||
"\u0002\u00ec@\u0003\u0002\u0002\u0002\u0018\u0002`fhq\u0086\u0088\u0090", | ||
"\u0092\u009a\u00a5\u00ac\u00af\u00b2\u00b8\u00bb\u00c2\u00c5\u00c9\u00d0", | ||
"\u00da\u00e8\u0003\b\u0002\u0002"].join(""); | ||
@@ -191,5 +259,7 @@ | ||
"T__5", "T__6", "T__7", "T__8", "STRING", | ||
"ESC", "UNICODE", "HEX", "NUMBER", "INT", | ||
"EXP", "WHITESPACE", "MULTI_LINE_COMMENT", | ||
"SINGLE_LINE_COMMENT" ]; | ||
"ASTRING", "STRSYM", "SPECHAR", "LETTER", | ||
"STAR", "QUESTION", "DOT", "MINUS", "AtSIGN", | ||
"DIGIT", "Q1STRING", "Q2STRING", "ESC", | ||
"UNICODE", "HEX", "NUMBER", "INT", "EXP", | ||
"WHITESPACE", "MULTI_LINE_COMMENT", "SINGLE_LINE_COMMENT" ]; | ||
@@ -196,0 +266,0 @@ JSONLexer.prototype.grammarFileName = "JSON.g4"; |
@@ -27,54 +27,75 @@ // Generated from JSON.g4 by ANTLR 4.7.1 | ||
function unquote(s) { | ||
return JSON.parse(s); // unquote for free | ||
function unquoteNumber(s) { | ||
return JSON.parse(s); // unquote | ||
} | ||
function unquoteString(s) { | ||
if(s.startsWith("'") && s.endsWith("'")) { | ||
let ss = s.substring(1,s.length-1); | ||
ss = ss.replace(/\\/g, '\\\\') | ||
ss = ss.replace(/\"/g, '\\"') | ||
s = '"' + ss + '"'; | ||
} | ||
if(!s.startsWith('"') && !s.endsWith('"') && !isCharNumber(s[0])) { // plain string | ||
s = '"' + s + '"'; | ||
} | ||
return JSON.parse(s); // unquote | ||
function isCharNumber(c) { | ||
return c >= '0' && c <= '9'; | ||
} | ||
} | ||
var grammarFileName = "JSON.g4"; | ||
var serializedATN = ["\u0003\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964", | ||
"\u0003\u0010H\u0004\u0002\t\u0002\u0004\u0003\t\u0003\u0004\u0004\t", | ||
"\u0003\u0010N\u0004\u0002\t\u0002\u0004\u0003\t\u0003\u0004\u0004\t", | ||
"\u0004\u0004\u0005\t\u0005\u0004\u0006\t\u0006\u0003\u0002\u0003\u0002", | ||
"\u0005\u0002\u000f\n\u0002\u0003\u0003\u0003\u0003\u0003\u0003\u0003", | ||
"\u0003\u0007\u0003\u0015\n\u0003\f\u0003\u000e\u0003\u0018\u000b\u0003", | ||
"\u0003\u0003\u0003\u0003\u0003\u0003\u0003\u0003\u0005\u0003\u001e\n", | ||
"\u0003\u0003\u0004\u0003\u0004\u0003\u0004\u0003\u0004\u0003\u0004\u0003", | ||
"\u0004\u0003\u0005\u0003\u0005\u0003\u0005\u0003\u0005\u0003\u0005\u0003", | ||
"\u0005\u0003\u0005\u0003\u0005\u0003\u0005\u0007\u0005/\n\u0005\f\u0005", | ||
"\u000e\u00052\u000b\u0005\u0003\u0005\u0003\u0005\u0003\u0005\u0003", | ||
"\u0005\u0005\u00058\n\u0005\u0003\u0006\u0003\u0006\u0003\u0006\u0003", | ||
"\u0006\u0003\u0006\u0003\u0006\u0003\u0006\u0003\u0006\u0003\u0006\u0003", | ||
"\u0006\u0003\u0006\u0003\u0006\u0005\u0006F\n\u0006\u0003\u0006\u0002", | ||
"\u0002\u0007\u0002\u0004\u0006\b\n\u0002\u0002\u0002M\u0002\u000e\u0003", | ||
"\u0002\u0002\u0002\u0004\u001d\u0003\u0002\u0002\u0002\u0006\u001f\u0003", | ||
"\u0002\u0002\u0002\b7\u0003\u0002\u0002\u0002\nE\u0003\u0002\u0002\u0002", | ||
"\f\u000f\u0005\u0004\u0003\u0002\r\u000f\u0005\b\u0005\u0002\u000e\f", | ||
"\u0003\u0002\u0002\u0002\u000e\r\u0003\u0002\u0002\u0002\u000f\u0003", | ||
"\u0003\u0002\u0002\u0002\u0010\u0011\u0007\u0003\u0002\u0002\u0011\u0016", | ||
"\u0005\u0006\u0004\u0002\u0012\u0013\u0007\u0004\u0002\u0002\u0013\u0015", | ||
"\u0005\u0006\u0004\u0002\u0014\u0012\u0003\u0002\u0002\u0002\u0015\u0018", | ||
"\u0003\u0002\u0002\u0002\u0016\u0014\u0003\u0002\u0002\u0002\u0016\u0017", | ||
"\u0003\u0002\u0002\u0002\u0017\u0019\u0003\u0002\u0002\u0002\u0018\u0016", | ||
"\u0003\u0002\u0002\u0002\u0019\u001a\u0007\u0005\u0002\u0002\u001a\u001e", | ||
"\u0003\u0002\u0002\u0002\u001b\u001c\u0007\u0003\u0002\u0002\u001c\u001e", | ||
"\u0007\u0005\u0002\u0002\u001d\u0010\u0003\u0002\u0002\u0002\u001d\u001b", | ||
"\u0003\u0002\u0002\u0002\u001e\u0005\u0003\u0002\u0002\u0002\u001f ", | ||
"\u0007\f\u0002\u0002 !\b\u0004\u0001\u0002!\"\u0007\u0006\u0002\u0002", | ||
"\"#\u0005\n\u0006\u0002#$\b\u0004\u0001\u0002$\u0007\u0003\u0002\u0002", | ||
"\u0002%&\u0007\u0007\u0002\u0002&\'\b\u0005\u0001\u0002\'(\u0005\n\u0006", | ||
"\u0002(0\b\u0005\u0001\u0002)*\u0007\u0004\u0002\u0002*+\b\u0005\u0001", | ||
"\u0002+,\u0005\n\u0006\u0002,-\b\u0005\u0001\u0002-/\u0003\u0002\u0002", | ||
"\u0002.)\u0003\u0002\u0002\u0002/2\u0003\u0002\u0002\u00020.\u0003\u0002", | ||
"\u0002\u000201\u0003\u0002\u0002\u000213\u0003\u0002\u0002\u000220\u0003", | ||
"\u0002\u0002\u000234\u0007\b\u0002\u000248\u0003\u0002\u0002\u00025", | ||
"6\u0007\u0007\u0002\u000268\u0007\b\u0002\u00027%\u0003\u0002\u0002", | ||
"\u000275\u0003\u0002\u0002\u00028\t\u0003\u0002\u0002\u00029:\u0007", | ||
"\f\u0002\u0002:F\b\u0006\u0001\u0002;<\u0007\r\u0002\u0002<F\b\u0006", | ||
"\u0001\u0002=F\u0005\u0004\u0003\u0002>F\u0005\b\u0005\u0002?@\u0007", | ||
"\t\u0002\u0002@F\b\u0006\u0001\u0002AB\u0007\n\u0002\u0002BF\b\u0006", | ||
"\u0001\u0002CD\u0007\u000b\u0002\u0002DF\b\u0006\u0001\u0002E9\u0003", | ||
"\u0002\u0002\u0002E;\u0003\u0002\u0002\u0002E=\u0003\u0002\u0002\u0002", | ||
"E>\u0003\u0002\u0002\u0002E?\u0003\u0002\u0002\u0002EA\u0003\u0002\u0002", | ||
"\u0002EC\u0003\u0002\u0002\u0002F\u000b\u0003\u0002\u0002\u0002\b\u000e", | ||
"\u0016\u001d07E"].join(""); | ||
"\u0003\u0003\u0005\u0003\u001b\n\u0003\u0003\u0003\u0003\u0003\u0003", | ||
"\u0003\u0003\u0003\u0005\u0003!\n\u0003\u0003\u0004\u0003\u0004\u0003", | ||
"\u0004\u0003\u0004\u0003\u0004\u0003\u0004\u0003\u0005\u0003\u0005\u0003", | ||
"\u0005\u0003\u0005\u0003\u0005\u0003\u0005\u0003\u0005\u0003\u0005\u0003", | ||
"\u0005\u0007\u00052\n\u0005\f\u0005\u000e\u00055\u000b\u0005\u0003\u0005", | ||
"\u0005\u00058\n\u0005\u0003\u0005\u0003\u0005\u0003\u0005\u0003\u0005", | ||
"\u0005\u0005>\n\u0005\u0003\u0006\u0003\u0006\u0003\u0006\u0003\u0006", | ||
"\u0003\u0006\u0003\u0006\u0003\u0006\u0003\u0006\u0003\u0006\u0003\u0006", | ||
"\u0003\u0006\u0003\u0006\u0005\u0006L\n\u0006\u0003\u0006\u0002\u0002", | ||
"\u0007\u0002\u0004\u0006\b\n\u0002\u0002\u0002U\u0002\u000e\u0003\u0002", | ||
"\u0002\u0002\u0004 \u0003\u0002\u0002\u0002\u0006\"\u0003\u0002\u0002", | ||
"\u0002\b=\u0003\u0002\u0002\u0002\nK\u0003\u0002\u0002\u0002\f\u000f", | ||
"\u0005\u0004\u0003\u0002\r\u000f\u0005\b\u0005\u0002\u000e\f\u0003\u0002", | ||
"\u0002\u0002\u000e\r\u0003\u0002\u0002\u0002\u000f\u0003\u0003\u0002", | ||
"\u0002\u0002\u0010\u0011\u0007\u0003\u0002\u0002\u0011\u0016\u0005\u0006", | ||
"\u0004\u0002\u0012\u0013\u0007\u0004\u0002\u0002\u0013\u0015\u0005\u0006", | ||
"\u0004\u0002\u0014\u0012\u0003\u0002\u0002\u0002\u0015\u0018\u0003\u0002", | ||
"\u0002\u0002\u0016\u0014\u0003\u0002\u0002\u0002\u0016\u0017\u0003\u0002", | ||
"\u0002\u0002\u0017\u001a\u0003\u0002\u0002\u0002\u0018\u0016\u0003\u0002", | ||
"\u0002\u0002\u0019\u001b\u0007\u0004\u0002\u0002\u001a\u0019\u0003\u0002", | ||
"\u0002\u0002\u001a\u001b\u0003\u0002\u0002\u0002\u001b\u001c\u0003\u0002", | ||
"\u0002\u0002\u001c\u001d\u0007\u0005\u0002\u0002\u001d!\u0003\u0002", | ||
"\u0002\u0002\u001e\u001f\u0007\u0003\u0002\u0002\u001f!\u0007\u0005", | ||
"\u0002\u0002 \u0010\u0003\u0002\u0002\u0002 \u001e\u0003\u0002\u0002", | ||
"\u0002!\u0005\u0003\u0002\u0002\u0002\"#\u0007\f\u0002\u0002#$\b\u0004", | ||
"\u0001\u0002$%\u0007\u0006\u0002\u0002%&\u0005\n\u0006\u0002&\'\b\u0004", | ||
"\u0001\u0002\'\u0007\u0003\u0002\u0002\u0002()\u0007\u0007\u0002\u0002", | ||
")*\b\u0005\u0001\u0002*+\u0005\n\u0006\u0002+3\b\u0005\u0001\u0002,", | ||
"-\u0007\u0004\u0002\u0002-.\b\u0005\u0001\u0002./\u0005\n\u0006\u0002", | ||
"/0\b\u0005\u0001\u000202\u0003\u0002\u0002\u00021,\u0003\u0002\u0002", | ||
"\u000225\u0003\u0002\u0002\u000231\u0003\u0002\u0002\u000234\u0003\u0002", | ||
"\u0002\u000247\u0003\u0002\u0002\u000253\u0003\u0002\u0002\u000268\u0007", | ||
"\u0004\u0002\u000276\u0003\u0002\u0002\u000278\u0003\u0002\u0002\u0002", | ||
"89\u0003\u0002\u0002\u00029:\u0007\b\u0002\u0002:>\u0003\u0002\u0002", | ||
"\u0002;<\u0007\u0007\u0002\u0002<>\u0007\b\u0002\u0002=(\u0003\u0002", | ||
"\u0002\u0002=;\u0003\u0002\u0002\u0002>\t\u0003\u0002\u0002\u0002?@", | ||
"\u0007\r\u0002\u0002@L\b\u0006\u0001\u0002AB\u0007\f\u0002\u0002BL\b", | ||
"\u0006\u0001\u0002CL\u0005\u0004\u0003\u0002DL\u0005\b\u0005\u0002E", | ||
"F\u0007\t\u0002\u0002FL\b\u0006\u0001\u0002GH\u0007\n\u0002\u0002HL", | ||
"\b\u0006\u0001\u0002IJ\u0007\u000b\u0002\u0002JL\b\u0006\u0001\u0002", | ||
"K?\u0003\u0002\u0002\u0002KA\u0003\u0002\u0002\u0002KC\u0003\u0002\u0002", | ||
"\u0002KD\u0003\u0002\u0002\u0002KE\u0003\u0002\u0002\u0002KG\u0003\u0002", | ||
"\u0002\u0002KI\u0003\u0002\u0002\u0002L\u000b\u0003\u0002\u0002\u0002", | ||
"\n\u000e\u0016\u001a 37=K"].join(""); | ||
@@ -243,5 +264,5 @@ | ||
try { | ||
this.state = 27; | ||
this.state = 30; | ||
this._errHandler.sync(this); | ||
var la_ = this._interp.adaptivePredict(this._input,2,this._ctx); | ||
var la_ = this._interp.adaptivePredict(this._input,3,this._ctx); | ||
switch(la_) { | ||
@@ -256,13 +277,24 @@ case 1: | ||
this._errHandler.sync(this); | ||
_la = this._input.LA(1); | ||
while(_la===JSONParser.T__1) { | ||
this.state = 16; | ||
this.match(JSONParser.T__1); | ||
this.state = 17; | ||
this.pair(localctx.ret.value); | ||
var _alt = this._interp.adaptivePredict(this._input,1,this._ctx) | ||
while(_alt!=2 && _alt!=antlr4.atn.ATN.INVALID_ALT_NUMBER) { | ||
if(_alt===1) { | ||
this.state = 16; | ||
this.match(JSONParser.T__1); | ||
this.state = 17; | ||
this.pair(localctx.ret.value); | ||
} | ||
this.state = 22; | ||
this._errHandler.sync(this); | ||
_la = this._input.LA(1); | ||
_alt = this._interp.adaptivePredict(this._input,1,this._ctx); | ||
} | ||
this.state = 23; | ||
this.state = 24; | ||
this._errHandler.sync(this); | ||
_la = this._input.LA(1); | ||
if(_la===JSONParser.T__1) { | ||
this.state = 23; | ||
this.match(JSONParser.T__1); | ||
} | ||
this.state = 26; | ||
this.match(JSONParser.T__2); | ||
@@ -273,5 +305,5 @@ break; | ||
this.enterOuterAlt(localctx, 2); | ||
this.state = 25; | ||
this.state = 28; | ||
this.match(JSONParser.T__0); | ||
this.state = 26; | ||
this.state = 29; | ||
this.match(JSONParser.T__2); | ||
@@ -337,10 +369,10 @@ break; | ||
this.enterOuterAlt(localctx, 1); | ||
this.state = 29; | ||
this.state = 32; | ||
localctx._STRING = this.match(JSONParser.STRING); | ||
this.state = 31; | ||
this.state = 34; | ||
this.match(JSONParser.T__3); | ||
this.state = 32; | ||
this.state = 35; | ||
this.value(localctx.val); | ||
localctx.ret[unquote((localctx._STRING===null ? null : localctx._STRING.text))]=localctx.val | ||
localctx.ret[unquoteString((localctx._STRING===null ? null : localctx._STRING.text))]=localctx.val | ||
this._ctx.stop = this._input.LT(-1); | ||
@@ -404,29 +436,40 @@ getLoc(localctx,localctx.val) | ||
try { | ||
this.state = 53; | ||
this.state = 59; | ||
this._errHandler.sync(this); | ||
var la_ = this._interp.adaptivePredict(this._input,4,this._ctx); | ||
var la_ = this._interp.adaptivePredict(this._input,6,this._ctx); | ||
switch(la_) { | ||
case 1: | ||
this.enterOuterAlt(localctx, 1); | ||
this.state = 35; | ||
this.state = 38; | ||
this.match(JSONParser.T__4); | ||
localctx.val={} | ||
this.state = 37; | ||
this.state = 40; | ||
this.value(localctx.val); | ||
localctx.ret.value[ID++]=localctx.val | ||
this.state = 46; | ||
this.state = 49; | ||
this._errHandler.sync(this); | ||
var _alt = this._interp.adaptivePredict(this._input,4,this._ctx) | ||
while(_alt!=2 && _alt!=antlr4.atn.ATN.INVALID_ALT_NUMBER) { | ||
if(_alt===1) { | ||
this.state = 42; | ||
this.match(JSONParser.T__1); | ||
localctx.val={} | ||
this.state = 44; | ||
this.value(localctx.val); | ||
localctx.ret.value[ID++]=localctx.val | ||
} | ||
this.state = 51; | ||
this._errHandler.sync(this); | ||
_alt = this._interp.adaptivePredict(this._input,4,this._ctx); | ||
} | ||
this.state = 53; | ||
this._errHandler.sync(this); | ||
_la = this._input.LA(1); | ||
while(_la===JSONParser.T__1) { | ||
this.state = 39; | ||
if(_la===JSONParser.T__1) { | ||
this.state = 52; | ||
this.match(JSONParser.T__1); | ||
localctx.val={} | ||
this.state = 41; | ||
this.value(localctx.val); | ||
localctx.ret.value[ID++]=localctx.val | ||
this.state = 48; | ||
this._errHandler.sync(this); | ||
_la = this._input.LA(1); | ||
} | ||
this.state = 49; | ||
this.state = 55; | ||
this.match(JSONParser.T__5); | ||
@@ -437,5 +480,5 @@ break; | ||
this.enterOuterAlt(localctx, 2); | ||
this.state = 51; | ||
this.state = 57; | ||
this.match(JSONParser.T__4); | ||
this.state = 52; | ||
this.state = 58; | ||
this.match(JSONParser.T__5); | ||
@@ -472,4 +515,4 @@ break; | ||
this.ret = null | ||
this._NUMBER = null; // Token | ||
this._STRING = null; // Token | ||
this._NUMBER = null; // Token | ||
this.ret = ret || null; | ||
@@ -482,2 +525,6 @@ return this; | ||
ValueContext.prototype.NUMBER = function() { | ||
return this.getToken(JSONParser.NUMBER, 0); | ||
}; | ||
ValueContext.prototype.STRING = function() { | ||
@@ -487,6 +534,2 @@ return this.getToken(JSONParser.STRING, 0); | ||
ValueContext.prototype.NUMBER = function() { | ||
return this.getToken(JSONParser.NUMBER, 0); | ||
}; | ||
ValueContext.prototype.obj = function() { | ||
@@ -511,20 +554,20 @@ return this.getTypedRuleContext(ObjContext,0); | ||
try { | ||
this.state = 67; | ||
this.state = 73; | ||
this._errHandler.sync(this); | ||
switch(this._input.LA(1)) { | ||
case JSONParser.STRING: | ||
case JSONParser.NUMBER: | ||
this.enterOuterAlt(localctx, 1); | ||
this.state = 55; | ||
localctx._STRING = this.match(JSONParser.STRING); | ||
localctx.ret.type="string"; localctx.ret.value=unquote((localctx._STRING===null ? null : localctx._STRING.text)); | ||
this.state = 61; | ||
localctx._NUMBER = this.match(JSONParser.NUMBER); | ||
localctx.ret.type="number"; localctx.ret.value=unquoteNumber((localctx._NUMBER===null ? null : localctx._NUMBER.text)); | ||
break; | ||
case JSONParser.NUMBER: | ||
case JSONParser.STRING: | ||
this.enterOuterAlt(localctx, 2); | ||
this.state = 57; | ||
localctx._NUMBER = this.match(JSONParser.NUMBER); | ||
localctx.ret.type="number"; localctx.ret.value=unquote((localctx._NUMBER===null ? null : localctx._NUMBER.text)); | ||
this.state = 63; | ||
localctx._STRING = this.match(JSONParser.STRING); | ||
localctx.ret.type="string"; localctx.ret.value=unquoteString((localctx._STRING===null ? null : localctx._STRING.text)); | ||
break; | ||
case JSONParser.T__0: | ||
this.enterOuterAlt(localctx, 3); | ||
this.state = 59; | ||
this.state = 65; | ||
this.obj(localctx.ret); | ||
@@ -534,3 +577,3 @@ break; | ||
this.enterOuterAlt(localctx, 4); | ||
this.state = 60; | ||
this.state = 66; | ||
this.array(localctx.ret); | ||
@@ -540,3 +583,3 @@ break; | ||
this.enterOuterAlt(localctx, 5); | ||
this.state = 61; | ||
this.state = 67; | ||
this.match(JSONParser.T__6); | ||
@@ -547,3 +590,3 @@ localctx.ret.type="boolean"; localctx.ret.value=true; | ||
this.enterOuterAlt(localctx, 6); | ||
this.state = 63; | ||
this.state = 69; | ||
this.match(JSONParser.T__7); | ||
@@ -554,3 +597,3 @@ localctx.ret.type="boolean"; localctx.ret.value=false; | ||
this.enterOuterAlt(localctx, 7); | ||
this.state = 65; | ||
this.state = 71; | ||
this.match(JSONParser.T__8); | ||
@@ -557,0 +600,0 @@ localctx.ret.type="null"; localctx.ret.value=null; |
// contains query relevant augmentor functions | ||
let W = require("./walker"); | ||
let udict = require("../transform/udict"); | ||
@@ -239,3 +240,3 @@ // creates a new instance which export all functions | ||
function augmentViewArgs(args,path) { | ||
return W.dmap(args, (arg,obj) => { | ||
return udict.dmap(args, (obj,arg) => { | ||
if(obj.param === true) | ||
@@ -394,3 +395,3 @@ return { | ||
if(typeof val.args == "object" && Array.isArray(val.args)==false) { | ||
r.namedArgs=W.dmap(val.args, (N,O) => { | ||
r.namedArgs=udict.dmap(val.args, (O,N) => { | ||
return newValue(O.val,aPath.concat(N), N, U.WILO_FULL, undefined, U.WILO_FIRST); | ||
@@ -397,0 +398,0 @@ }) |
@@ -290,3 +290,6 @@ // Transform augmented CSN into compact "official" CSN | ||
function compactSorted( ...args ) { | ||
let model = compact( ...args ); | ||
return sortCsn(compact( ...args )); | ||
} | ||
function sortCsn(model) { | ||
if (model.definitions) { | ||
@@ -357,2 +360,3 @@ let result = Object.create(null); | ||
getCompactors, | ||
sortCsn, | ||
}; |
@@ -32,2 +32,5 @@ /** | ||
delete model["locations"]; | ||
// The CSN parser does not use the normal message function: | ||
if (options.messages && model.messages) | ||
options.messages.push( ...model.messages ); | ||
return model; | ||
@@ -34,0 +37,0 @@ } |
@@ -78,2 +78,3 @@ // Transform augmented CSN into compact "official" CSN | ||
messages: ignore, // consider compactQuery / compactExpr | ||
options: ignore, | ||
sourceMax: renameTo( 'src', value ), // TODO XSN: rename? | ||
@@ -88,4 +89,4 @@ targetMin: renameTo( 'min', value ), | ||
_ignore: a => a, // not yet obsolete - still required by toHana (FIXME: maybe rename to $ignore, or use an annotation instead?) | ||
_ignoreMasked: standard, // FIXME: prop starting with _ is link and non-enumerable | ||
_isToContainer: standard, // FIXME: prop starting with _ is link and non-enumerable | ||
_ignoreMasked: b => b, // FIXME: prop starting with _ is link and non-enumerable | ||
_isToContainer: b => b, // FIXME: prop starting with _ is link and non-enumerable | ||
location, // -> $location | ||
@@ -97,2 +98,3 @@ $extra: (e, csn) => { Object.assign( csn, e ); }, | ||
blocks: ignore, // FIXME: make it $blocks | ||
builtin: ignore, // XSN: $builtin, "cds" namespace probably exposed by current transformers | ||
queries: ignore, // FIXME: make it $queries (flat) | ||
@@ -99,0 +101,0 @@ typeArguments: ignore, // FIXME: make it $typeArgs |
function Context(options) { | ||
return { | ||
options, | ||
messages:[] | ||
messages:[], | ||
toRemove:[] | ||
} | ||
@@ -6,0 +7,0 @@ } |
@@ -85,2 +85,14 @@ let W = require("../walker") | ||
// remove marked for deletion | ||
ctx.toRemove.forEach(P => { | ||
let node = csn; | ||
P.forEach((N,I) => { | ||
if(node===undefined) return; | ||
if(I===P.length-1) { // last node in path? | ||
delete node[N]; // delete the property | ||
} else { | ||
node = node[N]; | ||
} | ||
}) | ||
}) | ||
} | ||
@@ -130,2 +142,7 @@ | ||
function rmv(ctx,p) { | ||
if(ctx && ctx.toRemove) | ||
ctx.toRemove.push(p.filter(X => X[0]!=='{')) | ||
} | ||
module.exports = { | ||
@@ -138,2 +155,3 @@ checkTypeOf, | ||
fuzzy, | ||
rmv | ||
} |
@@ -34,2 +34,20 @@ /** | ||
/** | ||
* Callback of the forEach function called for each node it walks | ||
* @callback forEachCallback | ||
* @param {string} name of the node | ||
* @param {object} node | ||
*/ | ||
/** | ||
* Loops over all elements in an object and calls the specified callback(key,obj) | ||
* @param {object} obj | ||
* @param {forEachCallback} callback | ||
*/ | ||
function forEach(obj, callback) { | ||
for(var key in obj) { | ||
callback(key, obj[key]); | ||
} | ||
} | ||
/** | ||
* Callback of the walk function | ||
@@ -310,92 +328,4 @@ * @callback walkCallback | ||
/** | ||
* Callback of the forEach function called for each node it walks | ||
* @callback forEachCallback | ||
* @param {string} name of the node | ||
* @param {object} node | ||
*/ | ||
/** | ||
* Loops over all elements in an object and calls the specified callback(key,obj) | ||
* @param {object} obj | ||
* @param {forEachCallback} callback | ||
*/ | ||
function forEach(obj, callback) { | ||
for(var key in obj) { | ||
callback(key, obj[key]); | ||
} | ||
} | ||
/** | ||
* Callback of the forEachObject function called for each node it walks | ||
* @callback forEachObjectCallback | ||
* @param {string} name of the node | ||
* @param {object} node | ||
* @return false stops looping | ||
*/ | ||
/** | ||
* Loops over all object-elements in an object and calls the specified callback | ||
* @param {object} obj | ||
* @param {forEachObjectCallback} callback | ||
*/ | ||
function forEachObject(obj, callback) { | ||
for(var key in obj) { | ||
let iobj = obj[key]; | ||
if(isObject(iobj)) { | ||
if(callback(key, iobj)===false) | ||
break; //early exit | ||
} | ||
} | ||
} | ||
/** | ||
* Callback of the forEachProp function called for each leaf it walks | ||
* @callback forEachPropCallback | ||
* @param {string} name of the node | ||
* @param {object} leaf | ||
* @return false stops looping | ||
*/ | ||
/** | ||
* Loops over all leafs in an object and calls the specified callback | ||
* @param {object} obj | ||
* @param {forEachPropCallback} callback | ||
*/ | ||
function forEachProp(obj, callback) { | ||
for(var key in obj) { | ||
let iobj = obj[key]; | ||
if(!isObject(iobj)) { | ||
if(callback(key, iobj)===false) | ||
break; //early exit | ||
} | ||
} | ||
} | ||
/** | ||
* Callback of the dmap function called for each leaf it walks | ||
* @callback dmapCallback | ||
* @param {string} name of the node | ||
* @param {object} leaf | ||
* @return resulting object | ||
*/ | ||
/** | ||
* Transforms all elements of a dictionary into another. | ||
* Loops over all elements in an object and calls the specified callback. | ||
* The callback function returns the new representation of the passed element. | ||
* @param {object} obj | ||
* @param {dmapCallback} callback | ||
* @return resulting dictionary | ||
*/ | ||
function dmap(obj, callback) { | ||
let R = Object.create(null); | ||
for(var key in obj) { | ||
let iobj = obj[key]; | ||
R[key] = callback(key,iobj); | ||
} | ||
return R; | ||
} | ||
/** | ||
* Callback of the walkNodesEx function to obtain the next node to walk | ||
@@ -481,6 +411,3 @@ * @callback getNextElements | ||
module.exports = { | ||
dmap, | ||
forEach, | ||
forEachObject, | ||
forEachProp, | ||
walk, | ||
@@ -487,0 +414,0 @@ walkNodesEx, |
@@ -94,3 +94,3 @@ // Wrapper around generated ANTLR parser | ||
parser.options = options; | ||
parser.$message = getMessageFunction( parser ); // sets parser.messages | ||
parser.$message = getMessageFunction( parser, options ); // sets parser.messages | ||
@@ -132,6 +132,10 @@ initTokenRewrite( parser, tokenStream ); | ||
var ast = tree && tree[ rulespec.returns ] || {}; | ||
ast.options = options; | ||
if (rulespec.frontend) | ||
ast.$frontend = rulespec.frontend; | ||
ast.messages = parser.messages; | ||
if (parser.messages) { | ||
Object.defineProperty( ast, 'messages', | ||
{ value: parser.messages, configurable: true, writable: true } ); | ||
} | ||
if (options.attachTokens === true || options.attachTokens === filename) | ||
@@ -138,0 +142,0 @@ ast.tokenStream = tokenStream; |
@@ -17,3 +17,2 @@ // Main entry point for the Research Vanilla CDS Compiler | ||
const backends = require('./backends'); | ||
const { signModel } = require('./model/modelUtils'); | ||
@@ -78,4 +77,5 @@ // The compiler version (taken from package.json) | ||
let ext = path.extname( filename ).toLowerCase(); | ||
if (ext === '.xml') | ||
if (ext === '.xml') { | ||
return emdx2csn( source, filename, options ); | ||
} | ||
else if (['.json', '.csn'].includes(ext)) { | ||
@@ -90,3 +90,3 @@ let parseCSNfromJson = require("./json/from-json"); | ||
let model = {}; | ||
const message = getMessageFunction( model ); | ||
const message = getMessageFunction( model, options ); | ||
message( 'file-unknown-ext', | ||
@@ -113,4 +113,3 @@ { filename, start: { offset: 0, line: 1, column: 1 } }, null, | ||
// - Truthy `parseOnly`: stop compilation after parsing. | ||
// - Truthy `lintMode`: do not report errors for using directives pointing to | ||
// non-existing artifacts. | ||
// - Truthy `lintMode`: do not do checks and propagation | ||
// - many others - TODO | ||
@@ -146,5 +145,5 @@ | ||
var messagesArray = [[]]; | ||
const message = getMessageFunction({ messages: messagesArray[0] }); | ||
let model = { sources: a.sources, options }; | ||
const message = getMessageFunction( model ); | ||
const parseOptions = optionsWithMessages( options, model ); | ||
var all = promiseAllDoNotRejectImmediately( a.files.map(readAndParse) ); | ||
@@ -160,15 +159,12 @@ | ||
}); | ||
if (!options.lintMode && !options.parseOnly) | ||
if (!options.parseOnly) | ||
all = all.then( readDependencies ); | ||
return all.then( function() { | ||
moduleLayers.setLayers( a.sources ); | ||
for (let name in a.sources) | ||
messagesArray.push( a.sources[name].messages || [] ); | ||
if (options.collectSources) | ||
return collect(); | ||
return compileDo( a.sources, messagesArray, options, a.fileContentDict ); | ||
return compileDo( model, a.fileContentDict ); | ||
}); | ||
function collect() { | ||
let model = { messages: [].concat( ...messagesArray ) }; | ||
handleMessages( model ); | ||
@@ -204,7 +200,7 @@ let dependencies = Object.create(null); | ||
a.fileContentDict[filename] = source; | ||
let ast = parse( source, rel, options ); | ||
let ast = parse( source, rel, parseOptions ); | ||
a.sources[filename] = ast; | ||
ast.filename = rel; | ||
ast.dirname = path.dirname( filename ); | ||
assertConsistency( ast, options ); | ||
assertConsistency( ast, parseOptions ); | ||
fulfill( ast ); | ||
@@ -438,3 +434,5 @@ } | ||
let sources = Object.create(null); | ||
var messagesArray = []; | ||
let model = { sources, options }; | ||
getMessageFunction( model ); // make sure that we have a "central" messages array | ||
const parseOptions = optionsWithMessages( options, model ); | ||
@@ -444,8 +442,6 @@ for (let filename in content) { | ||
if (typeof source === 'string') { | ||
let ast = parse( source, filename, options ); | ||
let ast = parse( source, filename, parseOptions ); | ||
sources[filename] = ast; | ||
ast.filename = filename; | ||
assertConsistency( ast, options ); | ||
if (ast && ast.messages) | ||
messagesArray.push( ast.messages ); | ||
assertConsistency( ast, parseOptions ); | ||
} | ||
@@ -470,7 +466,15 @@ // else | ||
return compileDo( sources, messagesArray, options, sourcesDict ); | ||
return compileDo( model ); | ||
} | ||
function compileDo( sources, messagesArray, options = {}, fileContentDict ) { | ||
var model = { sources, options, messages: [].concat( ...messagesArray ) }; // flatten | ||
// Make sure to use a "central" messages array during parsing: | ||
function optionsWithMessages( options, model ) { | ||
return (options.messages) | ||
? options | ||
// : node 8.3 / elint-5+: { messages: model.messages, ...options }; | ||
: Object.assign( { messages: model.messages }, options ); | ||
} | ||
function compileDo( model ) { | ||
let options = model.options; | ||
if (!options.testMode) { | ||
@@ -480,8 +484,7 @@ model.version = versionObject( options );//TODO remove | ||
} | ||
// if (!options.parseOnly) return define(model); | ||
if (!options.parseOnly) { | ||
define(model); | ||
resolve(model); | ||
} | ||
if (options.parseOnly) | ||
return handleMessages( model ); | ||
define( model ); | ||
resolve( model ); | ||
assertConsistency( model ); | ||
@@ -491,17 +494,6 @@ handleMessages( model ); // stop compilation with errors | ||
return model; | ||
semanticChecks(model); | ||
handleMessages( model ); | ||
model = propagator.propagate( model ); | ||
if (!options.modelExtender || '$draft.cds' in sources) | ||
return signModel(model); | ||
let draft = options.modelExtender( model ); | ||
if (!draft) | ||
return signModel(model); | ||
if (typeof draft === 'string') | ||
return signModel(compileSources( Object.assign( { '$draft.cds': draft }, fileContentDict ), | ||
options )); | ||
handleMessages( draft ); | ||
throw new Error( 'Option `modelExtender` returns illegal value' ); | ||
return propagator.propagate( model ); | ||
} | ||
@@ -661,2 +653,4 @@ | ||
toOdata : backends.toOdata, | ||
// TODO: Expose when transformers are switched to CSN | ||
// toOdataWithCsn: backends.toOdataWithCsn, | ||
preparedCsnToEdmx : backends.preparedCsnToEdmx, | ||
@@ -663,0 +657,0 @@ preparedCsnToEdm : backends.preparedCsnToEdm, |
'use strict' | ||
const revealInternalProperties = require('./revealInternalProperties'); | ||
const util = require('util'); | ||
const crypto = require('crypto'); | ||
// Low-level utility functions to work with augmented CSN. | ||
@@ -166,5 +162,7 @@ | ||
absolute: absoluteName.substring(1), | ||
location: node.location, // inherit location from main element | ||
}, | ||
val: theValue, | ||
literal: 'boolean', | ||
location: node.location, // inherit location from main element | ||
}; | ||
@@ -442,35 +440,2 @@ } | ||
// If model verification is enabled, add a SHA-256 hash digest of the raw augmented CSN of 'model.definitions' | ||
// to 'model' and return 'model'. | ||
// Otherwise, just return 'model' unchanged | ||
function signModel(model) { | ||
if (!model.options || !model.options.verifyModel) { | ||
return model; | ||
} | ||
model.hash = hashModel(model); | ||
return model; | ||
} | ||
// If model verification is enabled, verify the SHA-256 hash digest of the raw augmented CSN of | ||
// 'model.definitions' against 'model.hash', throwing an error mentioning 'procesor' if it does | ||
// not match | ||
// Return 'model' unchanged. | ||
function verifyModelSignature(model, processor) { | ||
if (!model.options || !model.options.verifyModel) { | ||
return model; | ||
} | ||
let hash = hashModel(model); | ||
if (hash != model.hash) { | ||
throw new Error(`This augmented CSN was not produced by the CDS compiler and cannot be processed by ${processor || 'the backend'}`); | ||
} | ||
return model; | ||
} | ||
// Return a SHA-256 hash digest of the raw augmented CSN of 'model.definitions' | ||
function hashModel(model) { | ||
let hash = crypto.createHash('sha256'); | ||
hash.update(util.inspect(revealInternalProperties({ definitions: model.definitions }), false, null)); | ||
return hash.digest('hex'); | ||
} | ||
module.exports = { | ||
@@ -504,4 +469,2 @@ isManagedAssociationElement, | ||
getElementDatabaseNameOf, | ||
signModel, | ||
verifyModelSignature, | ||
}; |
@@ -25,2 +25,4 @@ // Make internal properties of the XSN / augmented CSN visible | ||
location: locationString, | ||
options: revealOptions, | ||
messages: reveal, | ||
artifacts: artifactDictionary, | ||
@@ -187,2 +189,8 @@ definitions: artifactDictionary, | ||
function revealOptions( node ) { | ||
return reveal( node.messages | ||
? Object.assign( {}, node, { messages: node.messages.length } ) | ||
: node ); | ||
} | ||
function reveal( node, protoProp, _array, identifierKind = 'query' ) { | ||
@@ -189,0 +197,0 @@ // warning: 'protoProp' bound via map() |
@@ -20,6 +20,6 @@ const { createOptionProcessor } = require('./base/optionProcessorHelper'); | ||
.option(' --trace-fs') | ||
.option(' --verify-model') | ||
.option('-R, --raw-output') | ||
.option(' --internal-msg') | ||
.option(' --beta-mode') | ||
.option(' --new-transformers') | ||
.option(' --new-csn') | ||
@@ -36,2 +36,3 @@ .option(' --old-csn') | ||
.option(' --old-localized-conv') | ||
.option(' --skip-compile') | ||
.help(` | ||
@@ -56,4 +57,3 @@ Usage: cdsc <command> [options] <file...> | ||
-o, --out <dir> Place generated files in directory <dir>, default is "-" for <stdout> | ||
-l, --lint-mode Generate nothing, just produce single-file error messages if any (for | ||
use by editors) | ||
-l, --lint-mode Generate nothing, just produce messages if any (for use by editors) | ||
--fuzzy-csn-error Report free-style CSN properties as errors | ||
@@ -66,3 +66,2 @@ -- Indicate the end of options (helpful if source names start with "-") | ||
--trace-fs Trace file system access caused by "using from" | ||
--verify-model Verify in backend that augmented CSN was created by compiler | ||
@@ -78,2 +77,3 @@ Internal options (for testing only, may be changed/removed at any time) | ||
requires --new-redirect-impl | ||
--new-transformers Use the new transformers that work on CSN instead of XSN | ||
--new-csn Produce CSN version 1.0 format (default) | ||
@@ -86,2 +86,4 @@ --old-csn Produce CSN version 0.1 format (deprecated, overrides --new-csn) | ||
--old-localized-conv Create localized convenience views on top of the original view | ||
--skip-compile Call directly the specified tranformer without compiling the source. | ||
Apply only with one CSN file and to commands toOdata, toHana and toSql. | ||
@@ -152,4 +154,4 @@ Backward compatibility options (deprecated, do not use) | ||
-v, --version <version> ODATA version | ||
v2: (default) ODATA V2 | ||
v4: ODATA V4 | ||
v2: ODATA V2 | ||
v4: (default) ODATA V4 | ||
-x, --xml (default) Generate XML output (separate or combined) | ||
@@ -156,0 +158,0 @@ -j, --json Generate JSON output as "<svc>.json" (not available for v2) |
@@ -21,3 +21,3 @@ "use strict"; | ||
function toCdsSource(model, options) { | ||
const { signal, warning } = alerts(model); | ||
const { signal, warning, error } = alerts(model); | ||
@@ -31,3 +31,4 @@ // Merge options (arguments first, then model options) | ||
// FIXME: This should happen in the caller | ||
let csn = compactModel(model); | ||
// with newTransformers model is already compacted | ||
let csn = options.newTransformers ? model : compactModel(model); | ||
@@ -574,3 +575,13 @@ // Create artificial namespace objects, so that each artifact has parents up to top-level. | ||
} else { | ||
const key = (col.key || (options.forHana && leaf && env._artifact.elements[leaf] && env._artifact.elements[leaf].key) ? 'key ' : ''); | ||
// If key is explicitly set in a not-first query of a UNION, issue an error. | ||
if(col.key && env.skipKeys){ | ||
// Make the $location look like a normal location property | ||
const location = { | ||
start: { column: col.$location.col, line: col.$location.line, offset: 0 }, // TODO: maybe col-4, to account for "key "? | ||
end: { column: col.$location.col, line: col.$location.line, offset: 0 }, | ||
filename: col.$location.file | ||
} | ||
signal(error`KEY must only be added in the first query of a UNION`, location); | ||
} | ||
const key = (!env.skipKeys && (col.key || (options.forHana && leaf && env._artifact.elements[leaf] && env._artifact.elements[leaf].key)) ? 'key ' : ''); | ||
result += env.indent + key + renderExpr(col, env, true); | ||
@@ -644,3 +655,12 @@ let alias = col.as; | ||
if (query.SET.op) { | ||
result += `\n${env.indent}${query.SET.op}${query.SET.all ? ' all' : ''} ${renderQuery(query.SET.args[1], false, 'view', env)}`; | ||
if(query.SET.op === 'union'){ | ||
// For UNION, don't render "key X as X" in the second SELECT | ||
env.skipKeys = true; | ||
} | ||
// Loop over all other arguments, i.e. for A UNION B UNION C UNION D ... | ||
for(let i = 1; i < query.SET.args.length; i++){ | ||
result += `\n${env.indent}${query.SET.op}${query.SET.all ? ' all' : ''} ${renderQuery(query.SET.args[i], false, 'view', env)}`; | ||
} | ||
// Reset afterwards | ||
env.skipKeys = false; | ||
} | ||
@@ -647,0 +667,0 @@ result += ')'; |
@@ -58,27 +58,25 @@ 'use strict'; | ||
// Only run the temporal-validation checks in beta-mode | ||
if(options.betaMode){ | ||
// semantic checks before flattening | ||
forEachDefinition(model, artifact => { | ||
// semantic checks before flattening | ||
forEachDefinition(model, artifact => { | ||
// Gather all elements with @cds.valid.from/to/key | ||
let validFrom = [], validTo = [], validKey = []; | ||
forEachMemberRecursively(artifact, member => { | ||
let [f, t, k] = extractValidFromToKeyElement(member); | ||
validFrom.push(...f); | ||
validTo.push(...t); | ||
validKey.push(...k); | ||
}); | ||
let validFrom = [], validTo = [], validKey = []; | ||
forEachMemberRecursively(artifact, member => { | ||
let [f, t, k] = extractValidFromToKeyElement(member); | ||
validFrom.push(...f); | ||
validTo.push(...t); | ||
validKey.push(...k); | ||
}); | ||
// Check that @cds.valid.from/to/key is only in valid places | ||
validFrom.forEach(e => checkAssignment('@cds.valid.from', e, artifact)); | ||
validTo.forEach(e => checkAssignment('@cds.valid.to', e, artifact)); | ||
validKey.forEach(e => checkAssignment('@cds.valid.key', e, artifact)); | ||
checkMultipleAssignments(validFrom, '@cds.valid.from', artifact); | ||
checkMultipleAssignments(validTo, '@cds.valid.to', artifact); | ||
checkMultipleAssignments(validKey, '@cds.valid.key', artifact); | ||
if(validKey.length && !(validFrom.length && validTo.length)){ | ||
signal(error`@cds.valid.key was used but @cds.valid.from and @cds.valid.to are missing`, artifact.location); | ||
} | ||
}); | ||
} | ||
validFrom.forEach(e => checkAssignment('@cds.valid.from', e, artifact)); | ||
validTo.forEach(e => checkAssignment('@cds.valid.to', e, artifact)); | ||
validKey.forEach(e => checkAssignment('@cds.valid.key', e, artifact)); | ||
checkMultipleAssignments(validFrom, '@cds.valid.from', artifact); | ||
checkMultipleAssignments(validTo, '@cds.valid.to', artifact); | ||
checkMultipleAssignments(validKey, '@cds.valid.key', artifact); | ||
if(validKey.length && !(validFrom.length && validTo.length)){ | ||
signal(error`@cds.valid.key was used but @cds.valid.from and @cds.valid.to are missing`, artifact.location); | ||
} | ||
}); | ||
// Second walk: Flatten structs, unravel derived types, deal with annotations | ||
@@ -121,2 +119,3 @@ forEachDefinition(model, (artifact) => { | ||
toFinalBaseType(member.returns); | ||
toFinalBaseType(member.returns && member.returns.items); | ||
// Mark fields with @odata.on.insert/update as @Core.Computed | ||
@@ -141,2 +140,3 @@ annotateCoreComputed(member); | ||
toFinalBaseType(artifact.returns); | ||
toFinalBaseType(artifact.returns && artifact.returns.items); | ||
} | ||
@@ -290,3 +290,3 @@ // If the artifact is a derived structured type, unravel that as well | ||
if (!containedElem.notNull) { | ||
signal(error`"${containedArtifact.name.absolute}.${containedElemName}": Association to container entity must have "NOT NULL"`, containedElem.location); | ||
signal(warning`"${containedArtifact.name.absolute}.${containedElemName}": Association to container entity must have "NOT NULL"`, containedElem.location); | ||
} | ||
@@ -297,3 +297,3 @@ } | ||
if (containedElem.notNull) { | ||
signal(error`"${containedArtifact.name.absolute}.${containedElemName}": Association to container entity in a recursive hierarchy must not have "NOT NULL`, containedElem.location); | ||
signal(warning`"${containedArtifact.name.absolute}.${containedElemName}": Association to container entity in a recursive hierarchy must not have "NOT NULL`, containedElem.location); | ||
} | ||
@@ -342,6 +342,6 @@ } | ||
// https://github.wdf.sap.corp/cdx/cds-compiler/issues/837 | ||
// add check here for @Analytics.Measure and @Aggregation.Default | ||
// add check here for @Analytics.Measure and @Aggregation.default | ||
// @Analytics has scope element | ||
if (member.kind && member.kind === 'element' | ||
&& member['@Analytics.Measure'] && !member['@Aggregation.Default']) { | ||
&& member['@Analytics.Measure'] && !member['@Aggregation.default']) { | ||
signal( | ||
@@ -384,2 +384,3 @@ warning`'@Analytics.Measure' expects '@Aggregation.default' to be assigned as well in element '${member.name.absolute}.${member.name.id}'`, | ||
// Walk the elements | ||
let eltCount = 0; | ||
for (let elemName in artifact.elements) { | ||
@@ -397,5 +398,7 @@ let elem = artifact.elements[elemName]; | ||
let ignore = hasBoolAnnotation(elem, '@cds.api.ignore', true); | ||
if (elem.key && elem.key.val && !ignore) { | ||
keyCount++; | ||
if(!elem._ignore) { | ||
eltCount++; | ||
if (elem.key && elem.key.val && !ignore) { | ||
keyCount++; | ||
} | ||
} | ||
@@ -407,2 +410,5 @@ if (elem['@Core.MediaType']) { | ||
if(eltCount == 0) { | ||
signal(error`Entity "${artifact.name.absolute}" must have at least one element`, artifact.location); | ||
} | ||
// Exposed non-abstract entities in non-contained artifacts must have a key | ||
@@ -590,2 +596,6 @@ if (keyCount == 0 && !artifact._containerEntity) { | ||
} | ||
// if odata.draft.enabled is explicitly set to false, we ignore it when linked via composition | ||
else if (hasBoolAnnotation(draftNode, '@odata.draft.enabled', false)) { | ||
continue; | ||
} | ||
else { | ||
@@ -592,0 +602,0 @@ // Generate draft stuff into the target |
@@ -11,2 +11,4 @@ 'use strict'; | ||
const { copyAnnotations } = require('../model/modelUtils'); | ||
const { dfilter } = require('./udict'); | ||
const { getUtils } = require('../model/csnUtils'); | ||
@@ -16,3 +18,2 @@ // Return the public functions of this module, with 'model' captured in a closure (for definitions, options etc). | ||
// 'model' is compacted new style CSN | ||
// TODO: Check if all the functions related to redirections and auto-exposures are needed at all? | ||
// TODO: Error and warnings handling with compacted CSN? - currently just throw new Error for everything | ||
@@ -23,2 +24,6 @@ // TODO: check the situation with assocs with values. In compacted CSN such elements have only "@Core.Computed": true | ||
let options = model.options || {}; | ||
const { | ||
getCsnDef, | ||
isStructured | ||
} = getUtils(model); | ||
@@ -30,10 +35,12 @@ return { | ||
flattenStructuredElement, | ||
flattenOnCond, | ||
// TODO: is it needed in compacted CSN? flattenStructStepsInPath, | ||
// TODO: is it needed? checkExposedAssoc, | ||
checkExposedAssoc, | ||
toFinalBaseType, | ||
getServiceName, | ||
// TODO: is it needed? addImplicitRedirections, | ||
// TODO: is it needed in compacted CSN? isAssociationOperand, | ||
// TODO: is it needed in compacted CSN? isDollarSelfOperand, | ||
// TODO: is it needed? createExposingProjection, | ||
// TODO: draft: createAndAddDraftAdminDataProjection, | ||
createExposingProjection, | ||
createAndAddDraftAdminDataProjection, | ||
createScalarElement, | ||
@@ -68,9 +75,13 @@ createAssociationElement, | ||
// get all the elements from the target that have 'key' identifier | ||
let targetKeys = | ||
Object.keys(targetArt.elements) | ||
.filter(elem => targetArt.elements[elem].key) | ||
.reduce( | ||
(obj, keyName) => Object.assign(obj, { [keyName]: targetArt.elements[keyName] }), | ||
Object.create(null) | ||
); | ||
let targetKeys = dfilter(targetArt.elements, elem => elem.key === true); | ||
// in case we have explicitly defined FKs | ||
Object.assign(targetKeys, dfilter(targetArt.elements, (elem, elemName) => { | ||
if (elem._flatElementNameWithDots) { | ||
// this is flattened elem -> keys still not flattened, have to check if starts with key ref | ||
return keys.map(key => key.ref.join('.')).some(keyDotName => elemName.startsWith(keyDotName)); | ||
} else { | ||
// exact match of the name | ||
return keys.map(key => key.ref.join('.')).some(keyDotName => keyDotName === elemName); | ||
} | ||
})) | ||
@@ -123,7 +134,7 @@ let result = []; | ||
function createForeignKeyElement(assoc, assocName, foreignKey, artifact) { | ||
// (TNT only): Use <assoc><key> instead of <assoc>_key> | ||
let fkSeparator = (options.tntFlavor && !options.tntFlavor.skipGeneratedFKsWithout_) ? '' : pathDelimiter; | ||
let fkSeparator = pathDelimiter; | ||
// Assemble foreign key element name from assoc name, '_' and foreign key name/alias | ||
let foreignKeyElementName = assocName.replace(/\./g, pathDelimiter) + fkSeparator + foreignKey.ref.join(pathDelimiter); | ||
// let foreignKeyElementName = assocName.replace(/\./g, pathDelimiter) + fkSeparator + foreignKey.ref.join(pathDelimiter); | ||
let foreignKeyElementName = `${assocName.replace(/\./g, pathDelimiter)}${fkSeparator}${foreignKey.as || foreignKey.ref.join(pathDelimiter)}`; | ||
@@ -133,3 +144,3 @@ // Assemble artificial foreign key element | ||
let fkArtifact = assocTargetDef.elements[foreignKey.ref.join(pathDelimiter)]; | ||
let fkArtifact = assocTargetDef.elements[foreignKey.ref.join(pathDelimiter)]; // foreignKey.as ??? | ||
@@ -146,2 +157,4 @@ let foreignKeyElement = Object.create(null); | ||
copyAnnotations(assoc, foreignKeyElement, true); | ||
// If the association is non-fkArtifact resp. key, so should be the foreign key field | ||
@@ -243,3 +256,3 @@ for (let prop of ['notNull', 'key']) { | ||
let childElem = struct[childName]; | ||
if (childElem.elements) { | ||
if (isStructured(childElem)) { | ||
// Descend recursively into structured children | ||
@@ -311,2 +324,111 @@ let grandChildElems = flattenStructuredElement(childElem, childName); | ||
// After flattening of elements we need to flatten the on-conditions of | ||
// unmanaged associations using those newly created elements | ||
// | ||
// Examples: | ||
// Input as: assoc.on: [{ ref: ['a1', 'x'] }, '=', { ref: ['$self', 'x'] }] | ||
// ^^^ ^^^ ^^^ ^^^ | ||
// (1) (2) (3) (4) | ||
// (1) is the current association which is a sub element of struct elem 's' ( ... elements: { s: { a1: association to ... }}) | ||
// (2) is non-structured element in the target | ||
// (3) is '$self' ... clearly | ||
// (4) is non-structured element in the current scope | ||
// The flatten on-condition is: [{ ref: ['s_a1', 'x'] }, '=', { ref: ['x'] }] | ||
// | ||
// Input as: assoc.on: [{ ref: ['a2', 'x'] }, '=', { ref: ['$self', 's', 'y'] }] | ||
// ^^^ ^^^ ^^^ ^^^^^^^ | ||
// (1) (2) (3) (4) | ||
// (1) is the current association which is a sub element of struct elem 's' ( ... elements: { s: { a2: association to ... }}) | ||
// (2) is non-structured element in the target | ||
// (3) is '$self' ... clearly | ||
// (4) is structured element in the current scope | ||
// The flatten on-condition is: [{ ref: ['s_a2', 'x'] }, '=', { ref: ['s_y'] }] | ||
// | ||
// Input as: assoc.on: [{ ref: [{ ref: ['a3', 'x'] }, '=', { ref: ['y'] }] | ||
// ^^^ ^^^ ^^^ | ||
// (1) (2) (3) | ||
// (1) is the current association which is a sub element of struct elem 's' ( ... elements: { s: { a3: association to ... }}) | ||
// (2) is non-structured element in the target | ||
// (3) element from the current nameresolution scope ( .. elements: { s : { y: ... ; a3: association to ... }) | ||
// The flatten on-condition is: [{ ref: ['s_a3', 'x'] }, '=', { ref: ['s_y'] }] | ||
// | ||
// Input as: assoc.on: [{ ref: ['a4', 'struct', 'k1'] }, '=', { ref: ['$self', 's', 'struct', 'k2'] }] | ||
// ^^^ ^^^^^^^^^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^ | ||
// (1) (2) (3) (4) | ||
// (1) is the current association which is a sub element of struct elem 's' ( ... elements: { s: { a4: association to ... }}) | ||
// (2) is structured element in the target | ||
// (3) is '$self' ... clearly | ||
// (4) is structured element in the current scope | ||
// The flatten on-condition is: [{ ref: ['s_a4', 'struct_k1'] }, '=', { ref: ['s_struct_k2'] }] | ||
function flattenOnCond(assoc, assocName, defElements) { | ||
if (!assoc.on) return; // nothing to do | ||
let allRefs = assoc.on.filter(elem => typeof elem === 'object' && elem.ref); | ||
let targetDef = getCsnDef(assoc.target); | ||
for (let refObj of allRefs) { | ||
// if reference is just '$self' | ||
if (refObj.ref.length === 1 && refObj.ref[0] === '$self') | ||
continue; | ||
// remove '$self' when first elem in ref and flatten the rest if needed | ||
if (refObj.ref[0] === '$self') { | ||
refObj.ref.shift(); | ||
// the rest of the reference should point to an element from the current definition | ||
let potentialFlattenElem = refObj.ref.join(pathDelimiter); | ||
if (defElements && defElements[potentialFlattenElem] && defElements[potentialFlattenElem].viaTransform) { | ||
refObj.ref = [potentialFlattenElem]; | ||
} | ||
continue; | ||
} | ||
// assoc element itself was flatten | ||
// -> every first step where current assoc is specified needs to be replaced | ||
if (assoc.viaTransform && assocName.endsWith(`${pathDelimiter}${refObj.ref[0]}`)) { | ||
refObj.ref.splice(0, 1, assocName); | ||
// the rest of the reference should point to an element from the target | ||
let potentialFlattenElem = refObj.ref.slice(1).join(pathDelimiter); | ||
if (targetDef.elements && targetDef.elements[potentialFlattenElem] && targetDef.elements[potentialFlattenElem].viaTransform) { | ||
refObj.ref.splice(1, refObj.ref.length - 1, potentialFlattenElem); | ||
} | ||
} | ||
// reference to flattened element in the target | ||
let flattenRefName = refObj.ref.join(pathDelimiter); | ||
if (targetDef.elements) { | ||
// exact match of flatten element name | ||
if (targetDef.elements[flattenRefName] && targetDef.elements[flattenRefName].viaTransform) { | ||
refObj.ref = [flattenRefName]; | ||
continue; | ||
} | ||
// when element is defined in the current name resolution scope, like | ||
// entity E { | ||
// key x: Integer; | ||
// s : { | ||
// y : Integer; | ||
// a3 : association to E on a3.x = y; | ||
// } | ||
// } | ||
let potentialFlattenElem = Object.keys(targetDef.elements) | ||
.find(elemName => elemName.endsWith(`${pathDelimiter}${flattenRefName}`)); | ||
if (potentialFlattenElem) { | ||
refObj.ref = [potentialFlattenElem]; | ||
continue; | ||
} | ||
} | ||
} | ||
} | ||
// Check that exposed associations do not point to non-exposed targets | ||
function checkExposedAssoc(artName, assocDef, assocName, service) { | ||
let assocTargetDef = getCsnDef(assocDef.target); | ||
if (!assocDef._ignore && assocDef.target && assocTargetDef && !assocDef.target.startsWith(service)) { | ||
// If we have a 'preserved dotted name' -> a result of flattening -> This scenario is not supported yet | ||
if (assocDef._flatElementNameWithDots) | ||
throw new Error(`Redirection for sub elements not supported yet - association "${artName}.${assocName}"`); | ||
else | ||
throw new Error( | ||
`Association "${artName}.${assocName}" must be redirected: Target "${assocDef.target}" is not exposed by service "${service}"` | ||
); | ||
} | ||
} | ||
// Replace the type of 'node' with its final base type (in contrast to the compiler, | ||
@@ -324,3 +446,3 @@ // also unravel derived enum types, i.e. take the final base type of the enum's base type. | ||
// Nothing to do if type is an array or a struct type | ||
if (!typeDef.type) return; | ||
if (typeDef.items || typeDef.elements) return; | ||
// if the declared element is an enum, these values are with priority | ||
@@ -335,2 +457,130 @@ if (!node.enum && typeDef.enum) | ||
function getServiceName(artifactName) { | ||
for(;;) { | ||
let idx = artifactName.lastIndexOf('.'); | ||
if (idx == -1) return null; | ||
artifactName = artifactName.substring(0, idx); | ||
let artifact = model.definitions[artifactName]; | ||
if (artifact && artifact.kind === 'service') { | ||
return artifactName; | ||
} | ||
} | ||
} | ||
// Return a full projection 'projectionId' of artifact 'art' for exposure in 'service'. | ||
// Add the created projection to the model and complain if artifact already exists. | ||
// Used by Draft generation | ||
function createExposingProjection(art, artName, projectionId, service) { | ||
let projectionAbsoluteName = `${service}.${projectionId}`; | ||
// If there already is an artifact with this name, this is either the second attempt or a conflict | ||
let existingProjection = model.definitions[projectionAbsoluteName]; | ||
if (existingProjection) { | ||
throw new Error(`Cannot generate projection "${projectionAbsoluteName}" because of name conflict with existing artifact "${service.name.absolute}.$projectionId}"`); | ||
} | ||
// Create elements matching the artifact's elements | ||
let elements = Object.create(null); | ||
for (let elemName in art.elements) { | ||
let artElem = art.elements[elemName]; | ||
let elem = Object.assign({}, artElem); | ||
// Transfer xrefs, that are redirected to the projection | ||
// TODO: shall we remove the transfered elements from the original? | ||
// if (artElem._xref) { | ||
// setProp(elem, '_xref', artElem._xref.filter(xref => xref.user && xref.user._main && xref.user._main._service == service)); | ||
// } | ||
// FIXME: Remove once the compactor no longer renders 'origin' | ||
elements[elemName] = elem; | ||
} | ||
let query = { | ||
'SELECT': { | ||
'from': { | ||
'ref': [ | ||
artName | ||
] | ||
} | ||
} | ||
}; | ||
// Assemble the projection itself and add it into the model | ||
let projection = { | ||
'kind': 'entity', | ||
query, | ||
elements, | ||
//'$syntax': 'projection' | ||
}; | ||
// copy annotations from art to projection | ||
for (let a of Object.keys(art).filter(x => x.startsWith('@'))) { | ||
projection[a] = art[a]; | ||
} | ||
// Sanity check: Can't already be there (checked above) | ||
if (model.definitions[projectionAbsoluteName]) { | ||
throw new Error('Duplicate projection: ' + projectionAbsoluteName); | ||
} | ||
model.definitions[projectionAbsoluteName] = projection; | ||
return projection; | ||
} | ||
// Create a 'DraftAdministrativeData' projection on entity 'DRAFT.DraftAdministrativeData' | ||
// in service 'service' and add it to the model. | ||
function createAndAddDraftAdminDataProjection(service) { | ||
// Make sure we have a DRAFT.DraftAdministrativeData entity | ||
let draftAdminDataEntity = model.definitions['DRAFT.DraftAdministrativeData']; | ||
if (!draftAdminDataEntity) { | ||
draftAdminDataEntity = createAndAddDraftAdminDataEntity(); | ||
model.definitions['DRAFT.DraftAdministrativeData'] = draftAdminDataEntity; | ||
} | ||
// Barf if it is not an entity or not what we expect | ||
if (draftAdminDataEntity.kind != 'entity' || !draftAdminDataEntity.elements['DraftUUID']) { | ||
throw new Error(`Generated entity "DRAFT.DraftAdministrativeData" conflicts with existing artifact`); | ||
} | ||
// Create a projection within this service | ||
return createExposingProjection(draftAdminDataEntity, 'DRAFT.DraftAdministrativeData', 'DraftAdministrativeData', service); | ||
// Create the 'DRAFT.DraftAdministrativeData' entity (unless it already exist) | ||
// Return the 'DRAFT.DraftAdministrativeData' entity. | ||
function createAndAddDraftAdminDataEntity() { | ||
// Create the 'DRAFT.DraftAdministrativeData' entity | ||
let artifact = { | ||
kind: 'entity', | ||
elements: Object.create(null), | ||
} | ||
// key DraftUUID : UUID | ||
let draftUuid = createScalarElement('DraftUUID', 'cds.UUID', true); | ||
addElement(draftUuid, artifact); | ||
// CreationDateTime : Timestamp; | ||
let creationDateTime = createScalarElement('CreationDateTime', 'cds.Timestamp'); | ||
addElement(creationDateTime, artifact); | ||
// CreatedByUser : String(256); | ||
let createdByUser = createScalarElement('CreatedByUser', 'cds.String'); | ||
createdByUser['CreatedByUser'].length = 256; | ||
addElement(createdByUser, artifact); | ||
// DraftIsCreatedByMe : Boolean; | ||
let draftIsCreatedByMe = createScalarElement('DraftIsCreatedByMe', 'cds.Boolean'); | ||
addElement(draftIsCreatedByMe, artifact); | ||
// LastChangeDateTime : Timestamp; | ||
let lastChangeDateTime = createScalarElement('LastChangeDateTime', 'cds.Timestamp'); | ||
addElement(lastChangeDateTime, artifact); | ||
// LastChangedByUser : String(256); | ||
let lastChangedByUser = createScalarElement('LastChangedByUser', 'cds.String'); | ||
lastChangedByUser['LastChangedByUser'].length = 256; | ||
addElement(lastChangedByUser, artifact); | ||
// InProcessByUser : String(256); | ||
let inProcessByUser = createScalarElement('InProcessByUser', 'cds.String'); | ||
inProcessByUser['InProcessByUser'].length = 256; | ||
addElement(inProcessByUser, artifact); | ||
// DraftIsProcessedByMe : Boolean; | ||
let draftIsProcessedByMe = createScalarElement('DraftIsProcessedByMe', 'cds.Boolean'); | ||
addElement(draftIsProcessedByMe, artifact); | ||
return artifact; | ||
} | ||
} | ||
// Create an artificial scalar element 'elemName' with final type 'typeName'. | ||
@@ -368,3 +618,3 @@ // Make the element a key element if 'isKey' is true. | ||
// type: 'cds.Association', target: 'Foo', | ||
// keys: [{ ref: ['id'] }], implicitForeignKeys: true | ||
// keys: [{ ref: ['id'] }] | ||
// } } | ||
@@ -386,3 +636,2 @@ function createAssociationElement(elemName, target, isManaged = false) { | ||
addForeignKey(foreignKey, assoc); | ||
assoc.implicitForeignKeys = true; | ||
} | ||
@@ -473,5 +722,4 @@ } | ||
let result = { [elemName]: {} }; | ||
let elemProps = elem[Object.keys(elem)[0]]; | ||
for (let prop in elemProps) | ||
result[elemName][prop] = elemProps[prop]; | ||
for (let prop in elem) | ||
result[elemName][prop] = elem[prop]; | ||
Object.assign(artifact.elements, result); | ||
@@ -537,11 +785,2 @@ return result; | ||
} | ||
// some model utilities => TODO: move them to separate file | ||
function getCsnDef(defName) { | ||
if (model.definitions[defName]) | ||
return model.definitions[defName] | ||
else | ||
throw new Error(`Nonexistent definition in the model: '${defName}'`); | ||
} | ||
} | ||
@@ -548,0 +787,0 @@ |
@@ -10,8 +10,8 @@ 'use strict' | ||
// Paths that start with an artifact of protected kind are special | ||
// either ignore them in QAT building or in path rewriting | ||
const internalArtifactKinds = ['builtin'/*, '$parameters'*/, 'param']; | ||
function translateAssocsToJoins(model) | ||
{ | ||
// Paths that start with an artifact of protected kind are special | ||
// either ignore them in QAT building or in path rewriting | ||
const internalArtifactKinds = ['builtin'/*, '$parameters'*/, 'param']; | ||
const { error, signal } = alerts(model); | ||
@@ -31,8 +31,2 @@ | ||
// Throw up if we have errors | ||
/* | ||
if (hasErrors(model.messages)) { | ||
throw new CompilationError( sortMessages(model.messages), model) | ||
} | ||
*/ | ||
return model; | ||
@@ -1145,145 +1139,2 @@ | ||
// Crawl all relevant sections of the query AST for paths | ||
function walkQuery(query, env) | ||
{ | ||
if(!query) | ||
return; | ||
if(!env.walkover) | ||
env.walkover = {}; | ||
env.location = query.op; | ||
if(query.op.val === 'query') | ||
{ | ||
env.lead = query; | ||
env.location = 'from'; | ||
walkFrom(query.from); | ||
env.location = 'select'; | ||
if(env.walkover[env.location]) | ||
{ | ||
for(let alias in query.elements) | ||
walk(query.elements[alias].value, env); | ||
env.location = 'Where'; | ||
walk(query.where, env); | ||
env.location = 'GroupBy'; | ||
walk(query.groupBy, env); | ||
env.location = 'Having'; | ||
walk(query.having, env); | ||
env.location = 'OrderBy'; | ||
walk(query.orderBy, env); | ||
// outer orderBy's of anonymous union | ||
walk(query.$orderBy, env); | ||
env.location = 'Limit'; | ||
walk(query.limit, env); | ||
env.location = 'Offset'; | ||
walk(query.offset, env); | ||
} | ||
} | ||
function walkFrom(query) | ||
{ | ||
let aliases = []; | ||
if(query) | ||
{ | ||
if(Array.isArray(query)) | ||
{ | ||
for(let q of query) | ||
aliases = aliases.concat(walkFrom(q)); | ||
} | ||
else if(env.walkover[env.location] && walkPath(query, env)) | ||
{ | ||
if(query.name) | ||
aliases.push(query.name.id); | ||
} | ||
else | ||
{ | ||
aliases = walkFrom(query.args); | ||
env.location = 'onCondFrom'; | ||
if(env.walkover[env.location]) | ||
{ | ||
env.tableAliases = aliases; | ||
walk(query.onCond || query.on, env) | ||
delete env.tableAliases; | ||
} | ||
env.location = 'from'; | ||
} | ||
} | ||
return aliases; | ||
} | ||
} | ||
/* node: any | ||
env: { callback: (array of) callback methods with signature(thing, env) | ||
...: any additional payload for the callback | ||
} | ||
*/ | ||
function walk(node, env) | ||
{ | ||
// In some expressions queries can occur, do not follow them as they | ||
// are walked as member of the queries array | ||
if(!env || !node || (node && node.op && node.op.val == 'query')) | ||
return; | ||
if(typeof node === 'object' && !Array.isArray(node)) | ||
if(walkPath(node, env)) | ||
return; | ||
// Ask for Array before typeof object (which would also be true for Array) | ||
if(Array.isArray(node)) | ||
node.map(n => walk(n, env)); | ||
// instanceof Object doesn't respect dictionaries... | ||
else if(typeof node === 'object') | ||
for(let n in node) | ||
walk(node[n], env); | ||
} | ||
function walkPath(node, env) | ||
{ | ||
let path = node['path']; | ||
// Ignore paths that that have no artifact (function calls etc) or that are builtins ($now, $user) | ||
// or that are parameters ($parameters or escaped paths (':') | ||
//path.length && path[ path.length-1 ]._artifact | ||
let art = path && path.length && path[path.length-1]._artifact; | ||
if(art && !internalArtifactKinds.includes(art.kind)) | ||
{ | ||
if(env.callback) | ||
{ | ||
// an array of callbacks applied to the node | ||
if(Array.isArray(env.callback)) | ||
env.callback.forEach(cb => cb(node, env)); | ||
else | ||
env.callback(node, env); | ||
} | ||
/* | ||
NOTE: As long as association path steps are not allowed in filters, | ||
it is not required to walk over filter expressions. | ||
Simple filter paths are rewritten inin createJoinTree (first filter) | ||
and createJoinQA (subsequent one that belong to the ON condition). | ||
If the filter becomes JOIN relevant, default FILTERS (part of the | ||
association definition) MUST be CLONED to each assoc path step | ||
BEFORE resolution. | ||
let filterEnv = Object.assign({walkover: {} }, env); | ||
filterEnv.location = 'filter'; | ||
if(filterEnv.walkover[filterEnv.location]) | ||
{ | ||
// Walk over all filter expressions (not JOIN relevant, | ||
// cannot be detected in generic walk. Store path step | ||
// to which this filter was attached to in filterEnt.pathStep | ||
path.filter(pathStep=>pathStep.where).forEach(pathStep => { | ||
filterEnv.pathStep = pathStep; | ||
walk(pathStep.where, filterEnv) }); | ||
} | ||
*/ | ||
// TODO: Parameter expressions! | ||
} | ||
return path; | ||
} | ||
function pathAsStr(p, delim='') | ||
@@ -1395,4 +1246,147 @@ { | ||
// Crawl all relevant sections of the query AST for paths | ||
function walkQuery(query, env) | ||
{ | ||
if(!query) | ||
return; | ||
if(!env.walkover) | ||
env.walkover = {}; | ||
env.location = query.op; | ||
module.exports = { translateAssocsToJoins, constructPathNode }; | ||
if(query.op.val === 'query') | ||
{ | ||
env.lead = query; | ||
env.location = 'from'; | ||
walkFrom(query.from); | ||
env.location = 'select'; | ||
if(env.walkover[env.location]) | ||
{ | ||
for(let alias in query.elements) | ||
walk(query.elements[alias].value, env); | ||
env.location = 'Where'; | ||
walk(query.where, env); | ||
env.location = 'GroupBy'; | ||
walk(query.groupBy, env); | ||
env.location = 'Having'; | ||
walk(query.having, env); | ||
env.location = 'OrderBy'; | ||
walk(query.orderBy, env); | ||
// outer orderBy's of anonymous union | ||
walk(query.$orderBy, env); | ||
env.location = 'Limit'; | ||
walk(query.limit, env); | ||
env.location = 'Offset'; | ||
walk(query.offset, env); | ||
} | ||
} | ||
function walkFrom(query) | ||
{ | ||
let aliases = []; | ||
if(query) | ||
{ | ||
if(Array.isArray(query)) | ||
{ | ||
for(let q of query) | ||
aliases = aliases.concat(walkFrom(q)); | ||
} | ||
else if(env.walkover[env.location] && walkPath(query, env)) | ||
{ | ||
if(query.name) | ||
aliases.push(query.name.id); | ||
} | ||
else | ||
{ | ||
aliases = walkFrom(query.args); | ||
env.location = 'onCondFrom'; | ||
if(env.walkover[env.location]) | ||
{ | ||
env.tableAliases = aliases; | ||
walk(query.onCond || query.on, env) | ||
delete env.tableAliases; | ||
} | ||
env.location = 'from'; | ||
} | ||
} | ||
return aliases; | ||
} | ||
} | ||
/* node: any | ||
env: { callback: (array of) callback methods with signature(thing, env) | ||
...: any additional payload for the callback | ||
} | ||
*/ | ||
function walk(node, env) | ||
{ | ||
// In some expressions queries can occur, do not follow them as they | ||
// are walked as member of the queries array | ||
if(!env || !node || (node && node.op && node.op.val == 'query')) | ||
return; | ||
if(typeof node === 'object' && !Array.isArray(node)) | ||
if(walkPath(node, env)) | ||
return; | ||
// Ask for Array before typeof object (which would also be true for Array) | ||
if(Array.isArray(node)) | ||
node.map(n => walk(n, env)); | ||
// instanceof Object doesn't respect dictionaries... | ||
else if(typeof node === 'object') | ||
for(let n in node) | ||
walk(node[n], env); | ||
} | ||
function walkPath(node, env) | ||
{ | ||
let path = node['path']; | ||
// Ignore paths that that have no artifact (function calls etc) or that are builtins ($now, $user) | ||
// or that are parameters ($parameters or escaped paths (':') | ||
//path.length && path[ path.length-1 ]._artifact | ||
let art = path && path.length && path[path.length-1]._artifact; | ||
if(art && !internalArtifactKinds.includes(art.kind)) | ||
{ | ||
if(env.callback) | ||
{ | ||
// an array of callbacks applied to the node | ||
if(Array.isArray(env.callback)) | ||
env.callback.forEach(cb => cb(node, env)); | ||
else | ||
env.callback(node, env); | ||
} | ||
/* | ||
NOTE: As long as association path steps are not allowed in filters, | ||
it is not required to walk over filter expressions. | ||
Simple filter paths are rewritten inin createJoinTree (first filter) | ||
and createJoinQA (subsequent one that belong to the ON condition). | ||
If the filter becomes JOIN relevant, default FILTERS (part of the | ||
association definition) MUST be CLONED to each assoc path step | ||
BEFORE resolution. | ||
let filterEnv = Object.assign({walkover: {} }, env); | ||
filterEnv.location = 'filter'; | ||
if(filterEnv.walkover[filterEnv.location]) | ||
{ | ||
// Walk over all filter expressions (not JOIN relevant, | ||
// cannot be detected in generic walk. Store path step | ||
// to which this filter was attached to in filterEnt.pathStep | ||
path.filter(pathStep=>pathStep.where).forEach(pathStep => { | ||
filterEnv.pathStep = pathStep; | ||
walk(pathStep.where, filterEnv) }); | ||
} | ||
*/ | ||
// TODO: Parameter expressions! | ||
} | ||
return path; | ||
} | ||
module.exports = { translateAssocsToJoins, constructPathNode, walkQuery }; |
{ | ||
"name": "@sap/cds-compiler", | ||
"version": "1.13.4", | ||
"version": "1.15.0", | ||
"dependencies": { | ||
@@ -5,0 +5,0 @@ "antlr4": { |
@@ -1,1 +0,1 @@ | ||
{"bin":{"cdsc":"bin/cdsc.js","cdshi":"bin/cdshi.js","cdsse":"bin/cdsse.js"},"bundleDependencies":false,"dependencies":{"antlr4":"4.7.1","resolve":"1.8.1","sax":"1.2.4"},"deprecated":false,"description":"CDS (Core Data Services) compiler and backends","keywords":["CDS"],"main":"lib/main.js","name":"@sap/cds-compiler","version":"1.13.4","license":"SEE LICENSE IN developer-license-3.1.txt"} | ||
{"bin":{"cdsc":"bin/cdsc.js","cdshi":"bin/cdshi.js","cdsse":"bin/cdsse.js"},"bundleDependencies":false,"dependencies":{"antlr4":"4.7.1","resolve":"1.8.1","sax":"1.2.4"},"deprecated":false,"description":"CDS (Core Data Services) compiler and backends","keywords":["CDS"],"main":"lib/main.js","name":"@sap/cds-compiler","version":"1.15.0","license":"SEE LICENSE IN developer-license-3.1.txt"} |
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 not supported yet
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 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
Sorry, the diff of this file is not supported yet
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
3233713
111
63542