All files / src/rules prefer_raise_message.ts

100% Statements 87/87
88.23% Branches 30/34
100% Functions 6/6
100% Lines 87/87

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 881x 1x 1x 1x 1x 1x 1x 1x 1x 29386x 29386x 29386x 29386x 1x 14701x 14701x 14701x 14701x 14701x 142755x 142755x 142755x 142755x 142755x 142755x 142755x 142755x 142755x 142755x 142755x 142755x 142755x 142755x 142755x 14701x 14701x 14120x 14120x 14701x 14701x 268x 268x 14701x 14701x 311x 311x 311x 311x 1667x 1648x 1648x 19x 1667x 1x 1667x 3x 3x 15x 15x 15x 15x 15x 1667x 1667x 4x 4x 1667x 11x 11x 11x 15x 1667x 3x 3x 12x 1667x 10x 1667x 1667x 8x 8x 8x 1667x 311x 311x 311x 14701x  
import {Issue} from "../issue";
import {ABAPRule} from "./_abap_rule";
import {BasicRuleConfig} from "./_basic_rule_config";
import {IRuleMetadata, RuleTag} from "./_irule";
import {ABAPFile} from "../abap/abap_file";
import {ExpressionNode} from "../abap/nodes/expression_node";
import * as Statements from "../abap/2_statements/statements";
import * as Expressions from "../abap/2_statements/expressions";
 
export class PreferRaiseMessageConf extends BasicRuleConfig {
  /** List of exception class names to ignore, case insensitive */
  public ignoreExceptionClasses: string[] = [];
}
 
export class PreferRaiseMessage extends ABAPRule {
 
  private conf = new PreferRaiseMessageConf();
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "prefer_raise_message",
      title: "Prefer RAISE EXCEPTION with MESSAGE",
      shortDescription: `Prefer RAISE EXCEPTION with MESSAGE instead of supplying parameter TEXT or TEXTID`,
      extendedInformation: `Reports RAISE EXCEPTION statements where parameter TEXT or TEXTID is supplied.
 
Use a message class instead, so the text is translatable and can be found via where-used.
 
Exception classes can be excluded via configuration "ignoreExceptionClasses".`,
      tags: [RuleTag.SingleFile],
      goodExample: `RAISE EXCEPTION TYPE zcx_error MESSAGE e001(zclass).`,
      badExample: `RAISE EXCEPTION TYPE zcx_error
  EXPORTING text = 'hello world'.`,
    };
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public setConfig(conf: PreferRaiseMessageConf) {
    this.conf = conf;
  }
 
  public runParsed(file: ABAPFile): Issue[] {
    const issues: Issue[] = [];
    const ignore = (this.conf.ignoreExceptionClasses ?? []).map(c => c.toUpperCase());
 
    for (const statement of file.getStatements()) {
      if (!(statement.get() instanceof Statements.Raise)) {
        continue;
      }
      const concat = statement.concatTokens().toUpperCase();
      if (!concat.startsWith("RAISE EXCEPTION ") && !concat.startsWith("RAISE RESUMABLE EXCEPTION ")) {
        continue;
      } else if (concat.includes(" MESSAGE")) {
        continue;
      }
 
      let className: string | undefined;
      let parameters: ExpressionNode | undefined;
 
      const newObject = statement.findDirectExpression(Expressions.Source)
        ?.findDirectExpression(Expressions.MethodCallChain)?.findDirectExpression(Expressions.NewObject);
      if (newObject) {
        className = newObject.findDirectExpression(Expressions.TypeNameOrInfer)?.concatTokens();
        parameters = newObject.findDirectExpression(Expressions.ParameterListS);
      } else {
        className = statement.findDirectExpression(Expressions.ClassName)?.concatTokens();
        parameters = statement.findDirectExpression(Expressions.ParameterListS);
      }
 
      if (className === undefined || ignore.includes(className.toUpperCase())) {
        continue;
      }
 
      const name = parameters?.findDirectExpressions(Expressions.ParameterS)
        .map(p => p.findDirectExpression(Expressions.ParameterName)?.concatTokens().toUpperCase())
        .find(n => n === "TEXT" || n === "TEXTID");
      if (name) {
        const message = "Prefer RAISE EXCEPTION with MESSAGE, " + className + " raised with parameter " + name;
        issues.push(Issue.atStatement(file, statement, message, this.getMetadata().key, this.conf.severity));
      }
    }
 
    return issues;
  }
}