All files / src/rules sql_escape_host_variables.ts

97.3% Statements 36/37
92.11% Branches 35/38
100% Functions 5/5
97.3% Lines 36/37

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 901x 1x 1x 1x 1x 1x 1x       1x     1x 7270x     21682x                       7013x       114x       148x   148x 10x     138x       138x 701x 701x       12x                 8x   4x 4x 4x   689x       7x 1x   6x 7x 7x 4x 4x 4x 4x           138x    
import * as Statements from "../abap/2_statements/statements";
import * as Expressions from "../abap/2_statements/expressions";
import {Issue} from "../issue";
import {ABAPRule} from "./_abap_rule";
import {BasicRuleConfig} from "./_basic_rule_config";
import {Version} from "../version";
import {RuleTag, IRuleMetadata} from "./_irule";
import {ABAPFile} from "../abap/abap_file";
import {ABAPObject} from "../objects/_abap_object";
 
export class SQLEscapeHostVariablesConf extends BasicRuleConfig {
}
 
export class SQLEscapeHostVariables extends ABAPRule {
  private conf = new SQLEscapeHostVariablesConf();
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "sql_escape_host_variables",
      title: "Escape SQL host variables",
      shortDescription: `Escape SQL host variables, from 740sp05`,
      extendedInformation: `https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#avoid-obsolete-language-elements`,
      tags: [RuleTag.Upport, RuleTag.Styleguide],
      badExample: `SELECT * FROM tab INTO TABLE res WHERE field = val.`,
      goodExample: `SELECT * FROM tab INTO TABLE @res WHERE field = @val.`,
    };
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public setConfig(conf: SQLEscapeHostVariablesConf) {
    this.conf = conf;
  }
 
  public runParsed(file: ABAPFile, obj: ABAPObject) {
    const issues: Issue[] = [];
 
    if (obj.getType() === "INTF") {
      return [];
    }
 
    Iif (this.reg.getConfig().getVersion() < Version.v740sp02 && this.reg.getConfig().getVersion() !== Version.Cloud) {
      return [];
    }
 
    for (const s of file.getStatements()) {
      const str = s.concatTokens().toUpperCase();
      if (s.get() instanceof Statements.Select
          || s.get() instanceof Statements.SelectLoop) {
// this is not completely correct and does not catch all, but okay for now
// todo: replace with logic from "else if" branch below, when/if it proves to work
        if (str.includes(" INTO ( @")
            || str.includes(" INTO (@")
            || str.includes(" INTO @")
            || str.includes(" INTO TABLE @")
            || str.includes(" INTO CORRESPONDING FIELDS OF @")
            || str.includes(" INTO CORRESPONDING FIELDS OF TABLE @")
            || str.includes(" APPENDING TABLE @")
            || ( str.includes(" APPENDING ") === false && str.includes(" INTO ") === false )
            || str.includes(" APPENDING CORRESPONDING FIELDS OF TABLE @")) {
          continue;
        } else {
          const message = "Escape SQL host variables";
          const issue = Issue.atToken(file, s.getFirstToken(), message, this.getMetadata().key, this.conf.severity);
          issues.push(issue);
        }
      } else if (s.get() instanceof Statements.UpdateDatabase
          || s.get() instanceof Statements.ModifyDatabase
          || s.get() instanceof Statements.InsertDatabase
          || s.get() instanceof Statements.DeleteDatabase) {
        if (str.startsWith("MODIFY SCREEN FROM ")) {
          continue;
        }
        for (const o of s.findAllExpressions(Expressions.SQLSource)) {
          const first = o.getFirstChild();
          if (first?.get() instanceof Expressions.Source && first.getChildren()[0].get() instanceof Expressions.FieldChain) {
            const message = "Escape SQL host variables";
            const issue = Issue.atToken(file, first.getFirstToken(), message, this.getMetadata().key, this.conf.severity);
            issues.push(issue);
            break;
          }
        }
      }
    }
 
    return issues;
  }
}