UNPKG

@sap/cds-compiler

Version:

CDS (Core Data Services) compiler and backends

143 lines (127 loc) 5.45 kB
// Kick-start: prepare to resolve all references // Remark: it would probably be better to move this to extend.js 'use strict'; const { isBetaEnabled } = require('../base/specialOptions'); const { setLink, annotationVal, annotationIsFalse, forEachGeneric, } = require('./utils'); function kickStart( model ) { const { options } = model; const { message } = model.$messageFunctions; const { resolveUncheckedPath, createGapArtifact } = model.$functions; // Set _service link (sorted to set it on parent first). Could be set // directly, but beware a gap artifact becoming a service later. Object.keys( model.definitions ).sort().forEach( setAncestorsAndService ); forEachGeneric( model, 'definitions', postProcessArtifact ); 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 * * Remark: _service links are not already set in define.js, because we might * have a @cds.redirection.service in the future * * @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._entityIncludes) && art._ancestors === undefined && art.kind === 'entity') setProjectionAncestors( art ); // TODO: set to [] for other entities 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') { message( 'service-nested-service', [ art.name.location, art ], { art: service }, 'A service can\'t be nested within a service $(ART)' ); } else if (art.kind === 'context') { // TODO: remove this error message( 'service-nested-context', [ art.name.location, art ], { art: service }, 'A context can\'t be nested within a service $(ART)' ); } } function setProjectionAncestors( art ) { // TODO: rename // 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 entity includes in extend.js, // Remark: _ancestors are also tested in populate.js for minmal exposure // // Remark: @cds.autoexpose is considered on-demand in populate.js 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?.kind === 'entity' && (art.query?.from?.path || // direct select with one source art._entityIncludes?.length) && !art._ancestors && art._ancestors !== 0) { // prevent inf-loop setLink( art, '_ancestors', 0 ); // avoid infloop with cyclic from if (art._entityIncludes?.length) { if (art._entityIncludes.length === 1) { chain.push( art ); art = art._entityIncludes[0]; continue; } setLink( art, '_ancestors', [] ); for (const incl of art._entityIncludes) { if ((incl.query || incl._entityIncludes?.length) && incl._ancestors === undefined) setProjectionAncestors( incl ); art._ancestors.push( ...(incl._ancestors || []), incl ); } break; } chain.push( art ); const name = resolveUncheckedPath( art.query.from, 'from', art ); art = name && (model.definitions[name] || createGapArtifact( 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 ) { 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 ); } } } module.exports = kickStart;