All files / src/rules preferred_compare_operator.ts

100% Statements 100/100
94.44% Branches 17/18
100% Functions 9/9
100% Lines 100/100

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 1001x 1x 1x 1x 1x 1x 1x 1x 1x 22612x 22612x 22612x 22612x 1x 11307x 11307x 11307x 11307x 11307x 11307x 11307x 33781x 33781x 33781x 33781x 33781x 33781x 33781x 33781x 33781x 33781x 33781x 11307x 11307x 20x 20x 11307x 11307x 10810x 10810x 11307x 11307x 233x 233x 11307x 11307x 278x 278x 278x 278x 278x 12x 12x 266x 266x 278x 38x 38x 38x 20x 20x 38x 266x 266x 266x 11307x 11307x 278x 210x 210x 210x 210x 210x 210x 210x 210x 210x 210x 210x 210x 210x 210x 210x 278x 11307x 11307x 20x 20x 20x 20x 19x 19x 19x 20x 1x 1x 1x 20x 11307x 11307x
import * as Expressions from "../abap/2_statements/expressions";
import {Issue} from "../issue";
import {ABAPRule} from "./_abap_rule";
import {BasicRuleConfig} from "./_basic_rule_config";
import {EditHelper} from "../edit_helper";
import {AbstractToken} from "../abap/1_lexer/tokens/abstract_token";
import {IRuleMetadata, RuleTag} from "./_irule";
import {ABAPFile} from "../abap/abap_file";
 
export class PreferredCompareOperatorConf extends BasicRuleConfig {
  /** Operators which are not allowed */
  public badOperators: string[] = ["EQ", "><", "NE", "GE", "GT", "LT", "LE"];
}
 
export class PreferredCompareOperator extends ABAPRule {
 
  private conf = new PreferredCompareOperatorConf();
 
  private readonly operatorMapping: Map<string, string> = new Map<string, string>();
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "preferred_compare_operator",
      title: "Preferred compare operator",
      shortDescription: `Configure undesired operator variants`,
      tags: [RuleTag.SingleFile, RuleTag.Quickfix],
      badExample: `IF foo EQ bar.
ENDIF.`,
      goodExample: `IF foo = bar.
ENDIF.`,
    };
  }
 
  private getDescription(operator: string): string {
    return "Compare operator \"" + operator + "\" not preferred";
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public setConfig(conf: PreferredCompareOperatorConf) {
    this.conf = conf;
  }
 
  public runParsed(file: ABAPFile) {
    this.buildMapping();
    const issues: Issue[] = [];
 
    const struc = file.getStructure();
    if (struc === undefined) {
      return [];
    }
 
    const operators = struc.findAllExpressionsMulti([Expressions.CompareOperator, Expressions.SQLCompareOperator]);
    for (const op of operators) {
      const token = op.getLastToken();
      // todo, performance, lookup in hash map instead(JS object)
      if (this.conf.badOperators.indexOf(token.getStr().toUpperCase()) >= 0) {
        issues.push(this.createIssue(token, file));
      }
    }
 
    return issues;
  }
 
  private buildMapping() {
    if (this.operatorMapping.size === 0) {
      this.operatorMapping.set("EQ", "=");
      this.operatorMapping.set("><", "<>");
      this.operatorMapping.set("NE", "<>");
      this.operatorMapping.set("GE", ">=");
      this.operatorMapping.set("GT", ">");
      this.operatorMapping.set("LT", "<");
      this.operatorMapping.set("LE", "<=");
 
      this.operatorMapping.set("=", "EQ");
      this.operatorMapping.set("<>", "NE");
      this.operatorMapping.set(">=", "GE");
      this.operatorMapping.set(">", "GT");
      this.operatorMapping.set("<", "LT");
      this.operatorMapping.set("<=", "LE");
    }
  }
 
  private createIssue(token: AbstractToken, file: ABAPFile): Issue {
    const message = this.getDescription(token.getStr());
    const replacementToken = this.operatorMapping?.get(token.getStr());
    // values in badOperators can be entered by the user and may not necessarily be actual operators
    if (replacementToken) {
      const fix = EditHelper.replaceRange(file, token.getStart(), token.getEnd(), replacementToken);
      const issue = Issue.atToken(file, token, message, this.getMetadata().key, this.conf.severity, fix);
      return issue;
    } else {
      const issue = Issue.atToken(file, token, message, this.getMetadata().key, this.conf.severity);
      return issue;
    }
  }
 
}