All files / src/rules prefer_insert_into_table.ts

95.12% Statements 78/82
86.66% Branches 13/15
100% Functions 6/6
95.12% Lines 78/82

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 831x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 11817x 11817x 11817x 11817x 69165x 69165x 69165x 69165x 69165x 69165x 69165x 69165x 69165x 69165x 69165x 11817x 11817x 11817x 11817x 11817x 298x 298x 298x 1585x 1566x 1566x 1585x 1x 1x 18x 18x 18x 18x 18x 298x 298x 298x 11817x 11817x 18x 18x 18x 18x 18x     18x 18x 18x 18x 18x 18x 18x 18x 18x 81x 18x 18x 18x 81x 18x     18x 18x 18x 18x 18x 18x 18x 11817x  
import {Issue} from "../issue";
import * as Statements from "../abap/2_statements/statements";
import {ABAPRule} from "./_abap_rule";
import {BasicRuleConfig} from "./_basic_rule_config";
import {IRuleMetadata, RuleTag} from "./_irule";
import {ABAPFile} from "../abap/abap_file";
import {EditHelper, IEdit} from "../edit_helper";
import {StatementNode} from "../abap/nodes/statement_node";
 
export class PreferInsertIntoTableConf extends BasicRuleConfig {}
 
export class PreferInsertIntoTable extends ABAPRule {
  private conf = new PreferInsertIntoTableConf();
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "prefer_insert_into_table",
      title: "Prefer INSERT INTO TABLE over APPEND",
      shortDescription: `Prefer INSERT INTO TABLE over APPEND`,
      // eslint-disable-next-line max-len
      extendedInformation: `INSERT INTO TABLE respects the table type, while APPEND always tries to add to the end. This will dump if the sort order of a sorted table would change.\n\nhttps://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.md#prefer-insert-into-table-to-append-to`,
      tags: [RuleTag.SingleFile, RuleTag.Styleguide, RuleTag.Quickfix],
      badExample: `APPEND row TO itab.`,
      goodExample: `INSERT row INTO TABLE itab.`,
    };
  }
 
  public getConfig() { return this.conf; }
  public setConfig(conf: PreferInsertIntoTableConf) { this.conf = conf; }
 
  public runParsed(file: ABAPFile): Issue[] {
    const issues: Issue[] = [];
 
    for (const stat of file.getStatements()) {
      if (!(stat.get() instanceof Statements.Append)) {
        continue;
      }
      if (stat.concatTokens().toUpperCase().includes(" SORTED BY ")) {
        continue;
      }
 
      const fix = this.buildFix(file, stat);
      const message = "Prefer INSERT INTO TABLE over APPEND";
      issues.push(Issue.atStatement(file, stat, message, this.getMetadata().key, this.conf.severity, fix));
    }
 
    return issues;
  }
 
  private buildFix(file: ABAPFile, stat: StatementNode): IEdit | undefined {
    const tokens = stat.getTokens();
 
    // Find the APPEND token (first token)
    const appendToken = tokens[0];
    if (appendToken.getStr().toUpperCase() !== "APPEND") {
      return undefined;
    }
 
    // Find the last "TO" token that precedes the table target.
    // In all fixable forms the table target follows the last standalone "TO":
    //   APPEND row TO itab
    //   APPEND LINES OF itab2 TO itab
    //   APPEND LINES OF itab2 FROM 1 TO 3 TO itab   <- last TO is the table
    //   APPEND INITIAL LINE TO itab
    let lastToIndex = -1;
    for (let i = tokens.length - 1; i >= 0; i--) {
      if (tokens[i].getStr().toUpperCase() === "TO") {
        lastToIndex = i;
        break;
      }
    }
    if (lastToIndex < 0) {
      return undefined;
    }
 
    const toToken = tokens[lastToIndex];
 
    const fix1 = EditHelper.replaceToken(file, appendToken, "INSERT");
    const fix2 = EditHelper.replaceToken(file, toToken, "INTO TABLE");
    return EditHelper.merge(fix1, fix2);
  }
}