UNPKG

@sap/cds-compiler

Version:

CDS (Core Data Services) compiler and backends

305 lines (282 loc) 8.36 kB
'use strict'; // This file contains functions related to XSN/CSN-location objects, // and the class definition for semantic locations const { copyPropIfExist } = require('../utils/objectUtils'); class Location { file; line; col; endLine; endCol; tokenIndex; constructor( file, line, col, endLine, endCol ) { this.file = file; this.line = line; this.col = col; this.endLine = endLine; this.endCol = endCol; } toString() { return locationString( this ); } } class SemanticLocation { mainKind; absolute; action; param; select; mixin; element = []; suffix; innerKind; toString() { return semanticLocationString( this ); } } function semanticLocationString( name, extended = false ) { const parts = [ `${ name.mainKind }:“${ name.absolute }”` ]; if (name.action != null) parts.push( `“action:${ name.action }”` ); if (name.param != null) parts.push( name.param ? `param:“${ name.param }”` : 'returns' ); if (name.select != null) parts.push( name.select ? `select:${ name.select }` : 'select' ); if (name.mixin != null) { const prop = (name.innerKind === 'alias') ? 'alias' : 'mixin'; parts.push( `${ prop }:${ stringOrRaw( name.mixin ) }` ); } const { element, innerKind } = name; if (element.length) { const append = innerKind === 'item' || innerKind === 'aspect'; const prop = !append && innerKind || !innerKind && name.select != null && 'column'; if (!prop || append) { parts.push( `element:${ stringOrRaw( element ) }` ); if (append) parts.push( innerKind ); } else if (prop === 'column') { const pos = extended ? -1 : element.findIndex( ( e, idx ) => idx && typeof e !== 'string' ); parts.push( `column:${ stringOrRaw( pos > 0 ? element.slice( 0, pos ) : element ) }` ); } else { if (element.length > 1) parts.push( `element:${ stringOrRaw( element.slice( 0, -1 ) ) }` ); parts.push( `${ innerKind }:“${ element[element.length - 1] }”` ); } } if (name.suffix) parts.push( name.suffix ); return parts.join( '/' ); } function stringOrRaw( val ) { if (!Array.isArray( val )) return (typeof val === 'string') ? `“${ val }”` : val; return val.every( e => typeof e === 'string' ) // && no '.' ? ? `“${ val.join( '.' ) }”` : val.map( stringOrRaw ).join( '→' ); // for XSN output, is sliced otherwise } /** * Create a location with properties `file`, `line` and `col` from argument * `start`, and properties `endLine` and `endCol` from argument `end`. * * @param {XSN.WithLocation} start * @param {XSN.WithLocation} end * @returns {CSN.Location} * * TODO: make this function a CDL parser-only function (i.e. there should be * no need to use it outside), it is XSN-only anyway already now */ function combinedLocation( start, end ) { if (!start || !start.location) return end?.location; else if (!end || !end.location) return start.location; const loc = { file: start.location.file, line: start.location.line, col: start.location.col, }; copyPropIfExist(end.location, 'endLine', loc); copyPropIfExist(end.location, 'endCol', loc); return loc; } /** * Create an empty location object with the given file name. * * @param {string} filename * @returns {CSN.Location} * * TODO: make this function redundant (XSN sparse locations project) */ function emptyLocation( filename ) { return { __proto__: Location.prototype, file: filename, line: 1, col: 1, endLine: 1, endCol: 1, }; } /** * Create an empty location object with the given file name. * The end line/column is not set and therefore the location is weak. * * @param {string} filename * @returns {CSN.Location} * * TODO: make this function redundant (XSN sparse locations project) */ function emptyWeakLocation( filename ) { return { __proto__: Location.prototype, file: filename, line: 1, col: 1, endLine: undefined, endCol: undefined, }; } /** * @param {Location} loc * @returns {Location} */ function weakLocation( loc ) { return (!loc?.endLine) ? loc : { __proto__: Location.prototype, file: loc.file, line: loc.line, col: loc.col, endLine: undefined, endCol: undefined, }; } /** * Return a location to be used for compiler-generated artifacts whose location is * best derived from a reference (`type`, `includes`, `target`, `value`) or a name. * Omit the end position to indicate that this is just an approximate location. * * If represented by a `path` (not always the case for a `name`), use the location * of its last item. Reason: think of an IDE functionality “Go to Definition” – only * a double-click on the _last_ identifier token of the reference jumps to the artifact * represented by the complete reference. * * @param {Location} loc * @returns {Location} */ function weakRefLocation( ref ) { if (!ref) return ref; const { path } = ref; const loc = path?.length ? path[path.length - 1].location : ref.location; return (!loc?.endLine) ? loc : { __proto__: Location.prototype, file: loc.file, line: loc.line, col: loc.col, endLine: undefined, endCol: undefined, }; } /** * @param {Location} loc * @returns {Location} */ function weakEndLocation( loc ) { return loc && { __proto__: Location.prototype, file: loc.file, line: loc.endLine, col: loc.endCol && loc.endCol - 1, endline: undefined, endCol: undefined, }; } /** * Returns a dummy location for built-in definitions. * * @returns {CSN.Location} * * TODO: make this function redundant (XSN sparse locations project) */ function builtinLocation() { return emptyLocation('<built-in>'); } /** * Return gnu-style error string for location `loc`: * - 'File:Line:Col' without `loc.end` * - 'File:Line:StartCol-EndCol' if Line = start.line = end.line * - 'File:StartLine.StartCol-EndLine.EndCol' otherwise * * @param {CSN.Location|CSN.Location} location * @param {boolean} [normalizeFilename] */ function locationString( location, normalizeFilename ) { if (!location) return '<???>'; const loc = location; const filename = (loc.file && normalizeFilename) ? loc.file.replace( /\\/g, '/' ) : loc.file; if (!(loc instanceof Object)) return loc; if (!loc.line) { return filename; } else if (!loc.endLine) { return (loc.col) ? `${ filename }:${ loc.line }:${ loc.col }` : `${ filename }:${ loc.line }`; } return (loc.line === loc.endLine) ? `${ filename }:${ loc.line }:${ loc.col }-${ loc.endCol }` : `${ filename }:${ loc.line }.${ loc.col }-${ loc.endLine }.${ loc.endCol }`; } /** * Return the source location of the complete dictionary `dict`. If * `extraLocation` is truthy, also consider this location. * ASSUMPTION: all entries in the dictionary have a property `location` and * `location.file` has always the same value. * * TODO: remove this function - if we really want to have dictionary locations, * set them in the CDL parser, e.g. via a symbol. * * @param {object} dict * @param {CSN.Location} [extraLocation] * @returns {CSN.Location} */ function dictLocation( dict, extraLocation ) { if (!dict) return extraLocation; if (!Array.isArray(dict)) dict = Object.getOwnPropertyNames( dict ).map( name => dict[name] ); /** @type {CSN.Location[]} */ const locations = [].concat( ...dict.map( _objLocations ) ); if (extraLocation) locations.push( extraLocation ); const min = locations.reduce( (a, b) => (a.line < b.line || (a.line === b.line && a.col < b.col) ? a : b) ); const max = locations.reduce( (a, b) => { const lineA = (a.endLine || a.line); const lineB = (b.endLine || b.line); return (lineA > lineB || (lineA === lineB && (a.endCol || a.col) > (b.endCol || b.col)) ? a : b); }); return new Location( min.file, min.line, min.col, max.endLine, max.endCol ); } function _objLocations( obj ) { return Array.isArray(obj) ? obj.map( o => o.location ) : [ obj.location ]; } module.exports = { Location, SemanticLocation, combinedLocation, emptyLocation, emptyWeakLocation, weakLocation, weakRefLocation, weakEndLocation, builtinLocation, dictLocation, locationString, };