All files / src/rules use_line_exists.ts

92.85% Statements 104/112
82.35% Branches 28/34
100% Functions 7/7
92.85% Lines 104/112

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 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 1121x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 8922x 8922x 8922x 8922x 26618x 26618x 26618x 26618x 26618x 26618x 26618x 26618x 26618x 26618x 26618x 26618x 26618x 26618x 26618x 26618x 8922x 8922x 8485x 8485x 8922x 8922x 202x 202x 8922x 8922x 228x 228x 228x 18x 18x 210x 210x 228x   228x     210x 210x 228x 1183x 1183x 1169x 1169x 14x 14x 1183x 1183x 1183x 10x 10x 1183x 210x 210x 210x 8922x 8922x 8922x 8922x 11x 13x 13x 2x 2x 11x 11x 11x 11x 11x 11x           8922x 8922x 8922x 11x 31x 31x 10x 31x 2x 21x 1x 1x 31x 10x 10x 8922x 8922x
import {Issue} from "../issue";
import * as Statements from "../abap/2_statements/statements";
import * as Expressions from "../abap/2_statements/expressions";
import {ABAPRule} from "./_abap_rule";
import {BasicRuleConfig} from "./_basic_rule_config";
import {Version} from "../version";
import {IRuleMetadata, RuleTag} from "./_irule";
import {StatementNode} from "../abap/nodes";
import {Comment} from "../abap/2_statements/statements/_statement";
import {ABAPFile} from "../abap/abap_file";
import {ABAPObject} from "../objects/_abap_object";
 
export class UseLineExistsConf extends BasicRuleConfig {
}
 
export class UseLineExists extends ABAPRule {
  private conf = new UseLineExistsConf();
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "use_line_exists",
      title: "Use line_exists",
      shortDescription: `Use line_exists, from 740sp02 and up`,
      extendedInformation: `
https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#prefer-line_exists-to-read-table-or-loop-at
 
Not reported if the READ TABLE statement contains BINARY SEARCH.`,
      tags: [RuleTag.Upport, RuleTag.Styleguide, RuleTag.SingleFile],
      badExample: `READ TABLE my_table TRANSPORTING NO FIELDS WITH KEY key = 'A'.
IF sy-subrc = 0.
ENDIF.`,
      goodExample: `IF line_exists( my_table[ key = 'A' ] ).
ENDIF.`,
    };
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public setConfig(conf: UseLineExistsConf) {
    this.conf = conf;
  }
 
  public runParsed(file: ABAPFile, obj: ABAPObject) {
    const issues: Issue[] = [];
 
    if (obj.getType() === "INTF") {
      return [];
    }
 
    const vers = this.reg.getConfig().getVersion();
    if (vers === Version.OpenABAP) {
      return [];
    } else if (vers < Version.v740sp02 && vers !== Version.Cloud) {
      return [];
    }
 
    const statements = file.getStatements();
    for (let i = 0; i < statements.length; i++) {
      const statement = statements[i];
      if (!(statement.get() instanceof Statements.ReadTable)) {
        continue;
      }
      const concat = statement.concatTokens().toUpperCase();
      if (concat.includes(" TRANSPORTING NO FIELDS") === true
          && concat.includes(" BINARY SEARCH") === false
          && this.checksSubrc(i, statements) === true
          && this.usesTabix(i, statements) === false) {
        issues.push(Issue.atStatement(file, statement, "Use line_exists", this.getMetadata().key, this.conf.severity));
      }
    }
 
    return issues;
  }
 
///////////////////////
 
  private checksSubrc(index: number, statements: readonly StatementNode[]): boolean {
    for (let i = index + 1; i < statements.length; i++) {
      const statement = statements[i];
      if (statement.get() instanceof Comment) {
        continue;
      }
      for (const c of statement.findAllExpressions(Expressions.Cond)) {
        for (const s of c.findAllExpressions(Expressions.Source)) {
          if (s.concatTokens().toUpperCase() === "SY-SUBRC") {
            return true;
          }
        }
      }
      return false;
    }
    return false;
  }
 
  // this is a heuristic, data flow analysis is required to get the correct result
  private usesTabix(index: number, statements: readonly StatementNode[]): boolean {
    for (let i = index + 1; i < index + 5; i++) {
      const statement = statements[i];
      if (statement === undefined) {
        break;
      } else if (statement.get() instanceof Comment) {
        continue;
      } else if (statement.concatTokens().toUpperCase().includes(" SY-TABIX")) {
        return true;
      }
    }
    return false;
  }
 
}