UNPKG

@sap/eslint-plugin-cds

Version:

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

65 lines (58 loc) 2.65 kB
'use strict' const { RULE_CATEGORIES } = require('../../constants') const { CdsHandlerRule } = require('./CdsHandlerRule') const isCqlClauseStart = node => ['SELECT', 'UPDATE', 'INSERT', 'DELETE', 'UPSERT'].includes(node.name) /** * ESLint goes through member functions in reverse order: * x.y.z -> z, y, x * so we can not track when we * enter a CQL clause, but instead have to check the chain of ancestors for each member * recursively. If we find the topmost function call is a CQL clause (SELECT, UPDATE, etc.), * none of the member functions in the chain are allowed to use untagged template strings. */ const isInCqlClause = node => { if (!node) return false if (node.type === 'CallExpression' && isCqlClauseStart(node.callee)) return true if (node.type === 'TaggedTemplateExpression' && isCqlClauseStart(node.tag)) return true // f(...) and f`...` have slightly different structure, the former has .callee, the latter has .tag // -> use the first that is available to ascend through the call chain return isInCqlClause(node.callee?.object ?? node.tag?.object) } class CqlSelectUseTemplateStrings extends CdsHandlerRule { CallExpression(node) { super.CallExpression(node) const [arg] = node.arguments ?? [] if (arg?.type !== 'TemplateLiteral') return if (arg.expressions.length === 0) return // no expressions in the template string, so no SQL injection risk if (!isInCqlClause(node)) return const [functionName, prefix] = node.callee.type === 'MemberExpression' // for ….where`...` we need to use the full preceding expression in the following replacement ? [node.callee.property?.name, this.context.getSourceCode().getText(node.callee)] // for SELECT`...` we can use the function name directly : [node.callee.name, node.callee.name] this.context.report({ node, message: 'Do not use {{functionName}}(`...`) inside CQL statements, which is prone to SQL injections.', data: { functionName }, suggest: [{ desc: 'Use {{functionName}}`...` instead of {{functionName}}(`...`)', data: { functionName }, fix: fixer => fixer.replaceText(node, `${prefix}${this.context.getSourceCode().getText(arg)}`) }] }) } } module.exports = { meta: { type: 'problem', docs: { recommended: true, category: RULE_CATEGORIES.javascript, description: 'Discourage use of SELECT(...), which allows SQL injections, in favour of SELECT`...`.' }, fixable: 'code', schema: [], hasSuggestions: true }, create: context => new CqlSelectUseTemplateStrings(context).asESLintVisitor() }