All files / src/rules exit_or_check.ts

100% Statements 84/84
100% Branches 25/25
100% Functions 6/6
100% Lines 84/84

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 851x 1x 1x 1x 1x 1x 1x 1x 1x 22176x 22176x 22176x 22176x 1x 11089x 11089x 11089x 11089x 11089x 33130x 33130x 33130x 33130x 33130x 33130x 33130x 33130x 33130x 33130x 33130x 33130x 33130x 33130x 33130x 33130x 33130x 11089x 11089x 10568x 10568x 11089x 11089x 245x 245x 11089x 11089x 280x 280x 280x 280x 280x 1513x 1513x 1513x 1513x 1513x 1513x 8x 1513x 1505x 1505x 1505x 8x 1505x 5x 5x 5x 5x 5x 5x 5x 5x 1497x 2x 2x 2x 2x 2x 1513x 280x 280x 280x 11089x 11089x  
import {Issue} from "../issue";
import * as Statements from "../abap/2_statements/statements";
import {ABAPRule} from "./_abap_rule";
import {StatementNode} from "../abap/nodes";
import {BasicRuleConfig} from "./_basic_rule_config";
import {IRuleMetadata, RuleTag} from "./_irule";
import {ABAPFile} from "../abap/abap_file";
import {EditHelper} from "../edit_helper";
 
export class ExitOrCheckConf extends BasicRuleConfig {
  public allowExit: boolean = false;
  public allowCheck: boolean = false;
}
 
export class ExitOrCheck extends ABAPRule {
 
  private conf = new ExitOrCheckConf();
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "exit_or_check",
      title: "Find EXIT or CHECK outside loops",
      shortDescription: `Detects usages of EXIT or CHECK statements outside of loops.
Use RETURN to leave procesing blocks instead.`,
      extendedInformation: `https://help.sap.com/doc/abapdocu_751_index_htm/7.51/en-US/abenleave_processing_blocks.htm
https://help.sap.com/doc/abapdocu_750_index_htm/7.50/en-US/abapcheck_processing_blocks.htm
https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#check-vs-return`,
      tags: [RuleTag.Styleguide, RuleTag.SingleFile, RuleTag.Quickfix],
      badExample: `CHECK is_valid = abap_true.
do_something( ).`,
      goodExample: `IF is_valid = abap_false.
  RETURN.
ENDIF.
do_something( ).`,
    };
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public setConfig(conf: ExitOrCheckConf) {
    this.conf = conf;
  }
 
  public runParsed(file: ABAPFile) {
    const issues: Issue[] = [];
 
    const stack: StatementNode[] = [];
 
    for (const statement of file.getStatements()) {
      const get = statement.get();
      if (get instanceof Statements.Loop
          || get instanceof Statements.While
          || get instanceof Statements.LoopAtScreen
          || get instanceof Statements.SelectLoop
          || get instanceof Statements.Do) {
        stack.push(statement);
      } else if (get instanceof Statements.EndLoop
          || get instanceof Statements.EndWhile
          || get instanceof Statements.EndSelect
          || get instanceof Statements.EndDo) {
        stack.pop();
      } else if (this.conf.allowCheck === false && get instanceof Statements.Check && stack.length === 0) {
        const message = "CHECK is not allowed outside of loops";
        let tokensString = statement.concatTokens();
        tokensString = tokensString.replace(/^check /i, "CHECK ");
        tokensString = tokensString.split("CHECK")[1].trim();
        const replacement = "IF NOT " + tokensString + "\n  RETURN.\nENDIF.";
        const fix = EditHelper.replaceRange(file, statement.getFirstToken().getStart(), statement.getLastToken().getEnd(), replacement);
        const issue = Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity, fix);
        issues.push(issue);
      } else if (this.conf.allowExit === false && get instanceof Statements.Exit && stack.length === 0) {
        const message = "EXIT is not allowed outside of loops";
        const fix = EditHelper.replaceToken(file, statement.getFirstToken(), "RETURN");
        const issue = Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity, fix);
        issues.push(issue);
      }
    }
 
    return issues;
  }
 
}