All files / src/rules avoid_use.ts

100% Statements 152/152
92.3% Branches 48/52
100% Functions 8/8
100% Lines 152/152

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 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 1531x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 22904x 22904x 22904x 22904x 22904x 22904x 22904x 22904x 22904x 22904x 22904x 22904x 22904x 22904x 22904x 22904x 22904x 22904x 22904x 22904x 1x 11453x 11453x 11453x 11453x 11453x 34246x 34246x 34246x 34246x 34246x 34246x 34246x 34246x 34246x 34246x 34246x 34246x 34246x 34246x 34246x 34246x 34246x 34246x 34246x 34246x 11453x 11453x 32x 32x 11453x 11453x 10927x 10927x 11453x 11453x 248x 248x 11453x 11453x 295x 295x 295x 295x 295x 1563x 1563x 1563x 1563x 1563x 1563x 6x 1563x 6x 6x 6x 6x 6x 1557x 1x 1x 1551x 1x 1550x 1x 1549x 2x 1548x 2x 2x 1x 1x 1x 1546x 1x 1544x 5x 5x 5x 1562x 1563x 23x 23x 1562x 1562x 1562x 1562x 1563x 238x 238x 238x 10x 10x 1x 1x 9x 9x 9x 238x 1563x 295x 295x 295x 11453x 11453x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 11453x  
import * as Statements from "../abap/2_statements/statements";
import {Issue} from "../issue";
import {ABAPRule} from "./_abap_rule";
import {BasicRuleConfig} from "./_basic_rule_config";
import {TypeTable, TypeTableKey} from "../abap/2_statements/expressions";
import {IRuleMetadata, RuleTag} from "./_irule";
import {ABAPFile} from "../abap/abap_file";
import {LanguageVersion, Release, releaseAtLeast} from "../version";
import {StatementNode} from "../abap/nodes/statement_node";
import {EditHelper, IEdit} from "../edit_helper";
import {Comment} from "../abap/2_statements/statements/_statement";
 
export class AvoidUseConf extends BasicRuleConfig {
  /** Do not emit quick fix suggestion */
  public skipQuickFix?: boolean = false;
  /** Detects DEFINE (macro definitions) */
  public define: boolean = true;
  /** Detects statics */
  public statics: boolean = true;
  /** Detects DEFAULT KEY definitions, from version v740sp02 and up. Use pseudo comment DEFAULT_KEY to ignore */
  public defaultKey: boolean = true;
  /** Detects BREAK and BREAK-POINTS */
  public break: boolean = true;
  /** Detects TEST SEAMS. Use pseudo comment TEST_SEAM_USAGE to ignore */
  public testSeams: boolean = true;
  /** Detects DESCRIBE TABLE LINES, use lines() instead */
  public describeLines: boolean = true;
  /** Detects EXPORT TO MEMORY */
  public exportToMemory: boolean = true;
  /** Detects EXPORT TO DATABASE */
  public exportToDatabase: boolean = true;
}
 
export class AvoidUse extends ABAPRule {
 
  private conf = new AvoidUseConf();
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "avoid_use",
      title: "Avoid use of certain statements",
      shortDescription: `Detects usage of certain statements.`,
      extendedInformation: `DEFAULT KEY: https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#avoid-default-key
 
Macros: https://help.sap.com/doc/abapdocu_latest_index_htm/latest/en-US/abenmacros_guidl.html
 
STATICS: use CLASS-DATA instead
 
DESCRIBE TABLE LINES: use lines() instead (quickfix exists)
 
TEST-SEAMS: https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#use-test-seams-as-temporary-workaround
 
BREAK points`,
      tags: [RuleTag.Styleguide, RuleTag.SingleFile],
      badExample: `DESCRIBE TABLE items LINES count.`,
      goodExample: `count = lines( items ).`,
    };
  }
 
  private getDescription(statement: string): string {
    return "Avoid use of " + statement;
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public setConfig(conf: AvoidUseConf) {
    this.conf = conf;
  }
 
  public runParsed(file: ABAPFile) {
    const issues: Issue[] = [];
    let isStaticsBlock: boolean = false;
 
    const statements = file.getStatements();
    for (let i = 0; i < statements.length; i++) {
      const statementNode = statements[i];
      const statement = statementNode.get();
 
      let message: string | undefined = undefined;
      let fix: IEdit | undefined = undefined;
      if (this.conf.define && statement instanceof Statements.Define) {
        message = "DEFINE";
      } else if (this.conf.describeLines && statement instanceof Statements.Describe) {
        const children = statementNode.getChildren();
        if (children.length === 6 && children[3].getFirstToken().getStr().toUpperCase() === "LINES") {
          message = "DESCRIBE LINES, use lines() instead";
          fix = this.conf.skipQuickFix === true ? undefined : this.getDescribeLinesFix(file, statementNode);
        }
      } else if (this.conf.statics && statement instanceof Statements.StaticBegin) {
        isStaticsBlock = true;
        message = "STATICS";
      } else if (this.conf.statics && statement instanceof Statements.StaticEnd) {
        isStaticsBlock = false;
      } else if (this.conf.exportToMemory && statement instanceof Statements.Export && statementNode.concatTokens().includes("TO MEMORY ")) {
        message = "EXPORT TO MEMORY";
      } else if (this.conf.exportToDatabase && statement instanceof Statements.Export && statementNode.concatTokens().includes("TO DATABASE ")) {
        message = "EXPORT TO DATABASE";
      } else if (this.conf.testSeams && statement instanceof Statements.TestSeam) {
        const next = statements[i + 1];
        if (next?.get() instanceof Comment && next.concatTokens().includes("EC TEST_SEAM_USAGE")) {
          continue;
        }
        message = "TEST-SEAM";
      } else if (this.conf.statics && statement instanceof Statements.Static && isStaticsBlock === false) {
        message = "STATICS";
      } else if (this.conf.break && statement instanceof Statements.Break) {
        message = "BREAK/BREAK-POINT";
        fix = this.conf.skipQuickFix === true ? undefined : EditHelper.deleteStatement(file, statementNode);
      }
 
      if (message) {
        issues.push(Issue.atStatement(file, statementNode, this.getDescription(message), this.getMetadata().key, this.conf.severity, fix));
      }
 
      if (this.conf.defaultKey
          && (releaseAtLeast(this.reg.getConfig().getRelease(), Release.v740sp02)
          || this.reg.getConfig().getLanguageVersion() === LanguageVersion.Cloud)
          && (statement instanceof Statements.Data || statement instanceof Statements.Type)) {
        const tt = statementNode.findFirstExpression(TypeTable)?.findDirectExpression(TypeTableKey);
        const token = tt?.findDirectTokenByText("DEFAULT");
        if (tt && token) {
          const next = statements[i + 1];
          if (next?.get() instanceof Comment && next.concatTokens().includes("EC DEFAULT_KEY")) {
            continue;
          }
          message = "DEFAULT KEY";
          issues.push(Issue.atToken(file, token, this.getDescription(message), this.getMetadata().key, this.conf.severity));
        }
      }
    }
 
    return issues;
  }
 
  private getDescribeLinesFix(file: ABAPFile, statementNode: StatementNode): IEdit|undefined {
    const children = statementNode.getChildren();
    const target = children[4].concatTokens();
    const source = children[2].concatTokens();
 
    const startPosition = children[0].getFirstToken().getStart();
    const insertText = target + " = lines( " + source + " ).";
 
    const deleteFix = EditHelper.deleteStatement(file, statementNode);
    const insertFix = EditHelper.insertAt(file, startPosition, insertText);
 
    const finalFix = EditHelper.merge(deleteFix, insertFix);
 
    return finalFix;
  }
}