All files / src/rules exit_or_check.ts

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

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 771x 1x 1x 1x 1x 1x 1x 1x 1x 17846x 17846x 17846x 17846x 1x 8924x 8924x 8924x 8924x 8924x 35101x 35101x 35101x 35101x 35101x 35101x 35101x 35101x 35101x 35101x 35101x 8924x 8924x 8485x 8485x 8924x 8924x 203x 203x 8924x 8924x 226x 226x 226x 226x 226x 1239x 1239x 1239x 1239x 1239x 1239x 18x 1239x 1221x 1221x 1221x 18x 1221x 3x 3x 3x 3x 3x 3x 3x 1203x 3x 3x 3x 3x 3x 1239x 226x 226x 226x 8924x 8924x
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],
    };
  }
 
  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.slice(statement.getFirstToken().getEnd().getCol());
        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;
  }
 
}