@sap/eslint-plugin-cds
Version:
ESLint plugin including recommended SAP Cloud Application Programming model and environment rules
164 lines (136 loc) • 3.67 kB
JavaScript
'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,
}