All files / src/rules preferred_compare_operator.ts

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

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 971x 1x 1x 1x 1x 1x 1x 1x 1x 20650x 20650x 20650x 20650x 1x 10326x 10326x 10326x 10326x 10326x 10326x 10326x 30823x 30823x 30823x 30823x 30823x 30823x 30823x 10326x 10326x 20x 20x 10326x 10326x 9811x 9811x 10326x 10326x 242x 242x 10326x 10326x 288x 288x 288x 288x 288x 12x 12x 276x 276x 288x 53x 53x 53x 20x 20x 53x 276x 276x 276x 10326x 10326x 288x 220x 220x 220x 220x 220x 220x 220x 220x 220x 220x 220x 220x 220x 220x 220x 288x 10326x 10326x 20x 20x 20x 20x 19x 19x 19x 19x 1x 1x 1x 1x 20x 10326x 10326x
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],
    };
  }
 
  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;
    }
  }
 
}