UNPKG

@sap/cds-compiler

Version:

CDS (Core Data Services) compiler and backends

167 lines (149 loc) 6.23 kB
// Kick-start: prepare to resolve all references 'use strict'; const { isBetaEnabled, forEachGeneric } = require('../base/model'); const { setLink, annotationVal, annotationIsFalse, isDirectComposition, } = require('./utils'); function kickStart( model ) { const { options } = model; const { message } = model.$messageFunctions; const { resolveUncheckedPath, resolvePath } = model.$functions; // Set _service link (sorted to set it on parent first). Could be set // directly, but beware a namespace becoming a service later. Object.keys( model.definitions ).sort().forEach( setAncestorsAndService ); forEachGeneric( model, 'definitions', postProcessArtifact ); forEachGeneric( model, 'sources', resolveUsings ); return; /** * Set projection ancestors, and _service link for artifact with absolute name 'name': * - not set: internal artifact * - null: not within service * - service: the artifact of the embedding service * This function must be called ordered: parent first * * @param {string} name Artifact name */ function setAncestorsAndService( name ) { const art = model.definitions[name]; if (art._parent === undefined) return; // nothing to do for builtins and redefinitions if (art.query && art._ancestors === undefined && art.kind === 'entity') setProjectionAncestors( art ); let parent = art._parent; if (parent === model.definitions.localized) parent = model.definitions[name.substring( 'localized.'.length )]; const service = parent && (parent._service || parent.kind === 'service' && parent); setLink( art, '_service', service ); if (!parent || !service) return; // To be removed when nested services are allowed if (!isBetaEnabled( options, 'nestedServices' ) && art.kind === 'service') { while (parent.kind !== 'service') parent = parent._parent; message( 'service-nested-service', [ art.name.location, art ], { art: parent }, 'A service can\'t be nested within a service $(ART)' ); } else if (art.kind === 'context') { while (parent.kind !== 'service') parent = parent._parent; // TODO: remove this error message( 'service-nested-context', [ art.name.location, art ], { art: parent }, 'A context can\'t be nested within a service $(ART)' ); } } function setProjectionAncestors( art ) { // Must be run after processLocalizedData() as we could have a projection // on a generated entity. // TODO: do not do implicit redirection across services, i.e. Service2.E is // no redirection target for E if Service2.E = projection on Service1.E and // Service1.E = projection on E // Remark: _ancestors are also set with includes, and there also for aspects, // types and events. const chain = []; const autoexposed = annotationVal( art['@cds.autoexposed'] ); // no need to set preferredRedirectionTarget in the while loop as we would // use the projection having @cds.redirection.target anyhow instead of // `art` anyway (if we do the no-x-service-implicit-redirection TODO above) while (art?.query?.from?.path && // direct select with one source art._ancestors !== 0) { // prevent inf-loop chain.push( art ); setLink( art, '_ancestors', 0 ); // avoid infloop with cyclic from const name = resolveUncheckedPath( art.query.from, 'from', art ); art = name && model.definitions[name]; if (autoexposed) break; // only direct projection for auto-exposed } let ancestors = art && (!autoexposed && art._ancestors || []); chain.reverse(); for (const a of chain) { ancestors = (ancestors ? [ ...ancestors, art ] : []); setLink( a, '_ancestors', ancestors ); art = a; } } function postProcessArtifact( art ) { tagCompositionTargets( art ); if (art.$queries) { for (const query of art.$queries) { if (query.mixin) forEachGeneric( query, 'mixin', tagCompositionTargets ); } } if (!art._ancestors || art.kind !== 'entity') return; // redirections only to entities const service = art._service; if (!service) return; const sname = service.name.id; art._ancestors.forEach( expose ); return; function expose( ancestor ) { if (ancestor._service === service || annotationIsFalse( art['@cds.redirection.target'] )) return; const desc = ancestor._descendants || setLink( ancestor, '_descendants', Object.create( null ) ); if (!desc[sname]) desc[sname] = [ art ]; else desc[sname].push( art ); } } function tagCompositionTargets( elem ) { if (elem.target && isDirectComposition( elem )) { // A target aspect would have already moved to property `targetAspect` in // define.js (hm... more something for kick-start.js...) // TODO: for safety, just use resolveUncheckedPath() const target = resolvePath( elem.target, 'target', elem ); if (target) model.$compositionTargets[target.name.id] = true; } if (elem.targetAspect?.elements) elem = elem.targetAspect; forEachGeneric( elem, 'elements', tagCompositionTargets ); } // Resolve the using declarations in `using`. Issue // error message if the referenced artifact does not exist. // TODO: think of moving this to resolve.js function resolveUsings( src, topLevel ) { if (!src.usings) return; for (const def of src.usings) { if (def.usings) // using {...} resolveUsings( def ); if (!def.name || !def.name.id) continue; // using {...}, parse error const art = model.definitions[def.name.absolute]; if (art && art.$duplicates) continue; const ref = def.extern; const user = (topLevel ? def : src); const from = user.fileDep; if (art || !from || from.realname) // no error for non-existing ref with non-existing module resolvePath( ref, 'using', def ); // TODO: consider FROM for validNames } } } module.exports = kickStart;