UNPKG

@sap/eslint-plugin-cds

Version:

ESLint plugin including recommended SAP Cloud Application Programming model and environment rules

164 lines (136 loc) 3.67 kB
'use strict' /** * Utility class for traversing a CSN. * * Experimental class. Not used directly, but instead wrapped in `forEach*` functions, * which are exported by this file. */ class CsnTraversal { csnIterators = { __proto__: null, definitions: this.dictionary, extensions: this.array, type: this.standard, target: this.standard, targetAspect: this.standard, returns: this.standard, items: this.standard, elements: this.dictionary, enum: this.dictionary, key: this.array, actions: this.dictionary, params: this.dictionary, mixin: this.dictionary, query: this.standard, SELECT: this.standard, SET: this.standard, from: this.standard, columns: this.array, expand: this.array, inline: this.array, ref: this.array, xpr: this.array, list: this.array, args: this.args, on: this.standard, default: this.standard, where: this.standard, groupBy: this.standard, orderBy: this.standard, having: this.standard, limit: this.standard, rows: this.standard, offset: this.standard, '@': this.annotation, } constructor(callbacks) { this.callbacks = callbacks this.ctx = new CsnTraversalContext } array( node, _prop, arr ) { if (!Array.isArray(arr)) return for (let i = 0; i < arr.length; ++i) { const entry = arr[i] this.standard(arr, i, entry) } } dictionary( node, _prop, dict ) { if (!node || typeof node !== 'object') return for (const name of Object.getOwnPropertyNames( dict )) this.standard( dict, name, dict[name] ) } // eslint-disable-next-line no-unused-vars annotation( parent, prop, node ) { // no-op for now; remove eslint-comment once implemented } // Arguments can be named (dictionary) or unnamed/positional (array). args( parent, prop, node ) { if (Array.isArray(node)) this.array(parent, prop, node) else this.dictionary(parent, prop, node) } /** * Traverse a "standard" CSN object, i.e. an object with CSN properties and * not a dictionary such as "elements". */ standard( _parent, _prop, node ) { if (!node || typeof node !== 'object') return this.ctx.pushLocationOf(node) if (Array.isArray(node)) { node.forEach( (n, i) => { this.standard(node, i, n) } ) } else { for (const prop of Object.getOwnPropertyNames( node )) { if (this.callbacks[prop]) { this.ctx.pushLocationOf(node[prop]) this.ctx.property = prop this.callbacks[prop](node[prop], this.ctx) this.ctx.popLocation() } const traverse = this.csnIterators[prop] || this.csnIterators[prop.charAt(0)] if (traverse) traverse.call(this, node, prop, node[prop] ) } } this.ctx.popLocation() } } class CsnTraversalContext { constructor(parent) { this.parent = parent || null this.property = null this.locations = [] } /** If a location on "node" is available, add it to the stack. */ pushLocationOf(node) { this.locations.push(node.$location ?? this.locations.at(-1)) } popLocation() { this.locations.pop() } get $location() { return this.locations.at(-1) } } /** * For each `xpr` (i.e. expression) in a definition, invoke the callback. * * @param {object} def * @param {Function} callback */ function forEachXprInDefinition( def, callback ) { const traversal = new CsnTraversal({ xpr: callback, where: callback, }) traversal.standard(null, def.name, def) } module.exports = { forEachXprInDefinition, }