All files / src/rules prefix_is_current_class.ts

96.96% Statements 128/132
88.88% Branches 24/27
100% Functions 8/8
96.96% Lines 128/132

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 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 1321x 1x 1x 1x 1x 1x 1x 1x 1x 1x 21018x 21018x 21018x 21018x 21018x 21018x 1x 10515x 10515x 10515x 10515x 31339x 31339x 31339x 31339x 31339x 31339x 31339x 31339x 31339x 10515x 10515x 9970x 9970x 10515x 10515x 253x 253x 10515x 10515x 275x 275x 10515x 10515x 275x 275x 12x 12x 263x 263x 263x 275x 59x 59x     59x 59x 59x 66x 66x 3x 3x     3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 66x 59x 59x 263x 263x 263x 10515x 10515x 275x 275x 12x 12x 263x 263x 263x 263x 263x 263x 275x 244x 244x 244x 244x 942x 942x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 942x 934x 934x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 942x 244x 263x 263x 10515x
import {Issue} from "../issue";
import {ABAPRule} from "./_abap_rule";
import * as Structures from "../abap/3_structures/structures";
import {BasicRuleConfig} from "./_basic_rule_config";
import {ClassName, MethodCall, InterfaceName, TypeName} from "../abap/2_statements/expressions";
import {Position} from "../position";
import {EditHelper} from "../edit_helper";
import {RuleTag} from "./_irule";
import {ABAPFile} from "../abap/abap_file";
 
export class PrefixIsCurrentClassConf extends BasicRuleConfig {
  /**
   * Checks usages of self references with 'me' when calling instance methods
   */
  public omitMeInstanceCalls: boolean = true;
}
 
export class PrefixIsCurrentClass extends ABAPRule {
  private conf = new PrefixIsCurrentClassConf();
 
  public getMetadata() {
    return {
      key: "prefix_is_current_class",
      title: "Prefix is current class",
      shortDescription: `Reports errors if the current class or interface references itself with "current_class=>"`,
      // eslint-disable-next-line max-len
      extendedInformation: `https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#omit-the-self-reference-me-when-calling-an-instance-attribute-or-method`,
      tags: [RuleTag.Styleguide, RuleTag.Quickfix, RuleTag.SingleFile],
    };
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public setConfig(conf: PrefixIsCurrentClassConf) {
    this.conf = conf;
  }
 
  public runParsed(file: ABAPFile) {
    return this.checkClasses(file).concat(this.checkInterfaces(file));
  }
 
  private checkInterfaces(file: ABAPFile): Issue[] {
    const struc = file.getStructure();
    if (struc === undefined) {
      return [];
    }
 
    const issues: Issue[] = [];
 
    for (const s of struc.findDirectStructures(Structures.Interface)) {
      const name = s.findFirstExpression(InterfaceName)?.getFirstToken().getStr().toUpperCase();
      if (name === undefined) {
        continue;
      }
      const staticAccess = name + "=>";
 
      for (const e of s.findAllExpressions(TypeName)) {
        const concat = e.concatTokens().toUpperCase();
        if (concat.startsWith(staticAccess)) {
          const stat = e.findDirectTokenByText("=>");
          if (stat === undefined) {
            continue;
          }
          const start = new Position(stat.getRow(), stat.getCol() - name.length);
          const end = new Position(stat.getRow(), stat.getCol() + 2);
          const fix = EditHelper.deleteRange(file, start, end);
          issues.push(Issue.atToken(
            file,
            e.getFirstToken(),
            "Reference to current interface can be omitted",
            this.getMetadata().key,
            this.conf.severity,
            fix));
        }
      }
 
    }
 
    return issues;
  }
 
  private checkClasses(file: ABAPFile): Issue[] {
    const struc = file.getStructure();
    if (struc === undefined) {
      return [];
    }
 
    const issues: Issue[] = [];
    const classStructures = struc.findDirectStructures(Structures.ClassImplementation);
    classStructures.push(...struc.findDirectStructures(Structures.ClassDefinition));
    const meAccess = "ME->";
 
    for (const c of classStructures) {
      const className = c.findFirstExpression(ClassName)!.getFirstToken().getStr().toUpperCase();
      const staticAccess = className + "=>";
 
      for (const s of c.findAllStatementNodes()) {
        const concat = s.concatTokensWithoutStringsAndComments().toUpperCase();
        if (concat.includes(staticAccess)) {
          const tokenPos = s.findTokenSequencePosition(className, "=>");
          if (tokenPos) {
            const end = new Position(tokenPos.getRow(), tokenPos.getCol() + className.length + 2);
            const fix = EditHelper.deleteRange(file, tokenPos, end);
            issues.push(Issue.atRange(
              file,
              tokenPos, end,
              "Reference to current class can be omitted: \"" + staticAccess + "\"",
              this.getMetadata().key,
              this.conf.severity,
              fix));
          }
        } else if (this.conf.omitMeInstanceCalls === true
            && concat.includes(meAccess)
            && s.findFirstExpression(MethodCall)) {
          const tokenPos = s.findTokenSequencePosition("me", "->");
          if (tokenPos) {
            const end = new Position(tokenPos.getRow(), tokenPos.getCol() + 4);
            const fix = EditHelper.deleteRange(file, tokenPos, end);
            issues.push(Issue.atRange(
              file,
              tokenPos, end,
              "Omit 'me->' in instance calls",
              this.getMetadata().key, this.conf.severity, fix));
          }
        }
      }
    }
    return issues;
  }
}