All files / src/rules definitions_top.ts

98.4% Statements 185/188
86.76% Branches 59/68
100% Functions 8/8
98.4% Lines 185/188

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 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 1881x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 11000x 11000x 11000x 11000x 11000x 11000x 11000x 11000x 11000x 32812x 32812x 32812x 32812x 32812x 32812x 32812x 32812x 32812x 32812x 32812x 32812x 32812x 32812x 32812x 32812x 11000x 11000x 17x 17x 11000x 11000x 10444x 10444x 11000x 11000x 260x 260x 11000x 11000x 298x 298x 298x 298x 13x 13x 285x 285x 298x 8x 8x 277x 277x 298x 111x 111x 111x 111x 111x 111x 111x 111x 17x 17x 111x 277x 277x 277x 11000x 11000x 11000x 11000x 443x 443x 443x 745x 745x 745x 399x   399x 1x 399x 61x 398x 49x 49x 399x 634x 634x 745x 346x 346x 745x 12x 3x 3x 3x 3x 3x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 3x 3x 12x 9x 9x 745x 622x 288x 288x 288x 288x 622x 39x 15x 15x 14x 14x 14x 14x 14x 14x 14x 39x 24x 24x 622x 2x 2x 583x     581x 109x 581x 332x 332x 55x 55x 332x 560x 560x 560x 369x 369x 369x 11000x 11000x 14x 14x 14x 14x 14x 14x 14x 14x 14x 11000x
import {Issue} from "../issue";
import {Comment, Unknown} from "../abap/2_statements/statements/_statement";
import * as Statements from "../abap/2_statements/statements";
import * as Structures from "../abap/3_structures/structures";
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 {StructureNode, StatementNode} from "../abap/nodes";
import {Position} from "../position";
 
export class DefinitionsTopConf extends BasicRuleConfig {
}
 
// todo, use enum instead?
// const ANY = 1;
const DEFINITION = 2;
const AFTER = 3;
const IGNORE = 4;
 
export class DefinitionsTop extends ABAPRule {
 
  private conf = new DefinitionsTopConf();
 
  private mode: number;
  private fixed: boolean;
  private moveTo: Position | undefined;
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "definitions_top",
      title: "Place definitions in top of routine",
      shortDescription: `Checks that definitions are placed at the beginning of METHODs, FORMs and FUNCTIONs.`,
      extendedInformation: `https://docs.abapopenchecks.org/checks/17/`,
      tags: [RuleTag.SingleFile, RuleTag.Quickfix],
      badExample: `FROM foo.
  WRITE 'hello'.
  DATA int TYPE i.
ENDFORM.`,
      goodExample: `FROM foo.
  DATA int TYPE i.
  WRITE 'hello'.
ENDFORM.`,
    };
  }
 
  private getMessage(): string {
    return "Reorder definitions to top of routine";
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public setConfig(conf: DefinitionsTopConf) {
    this.conf = conf;
  }
 
  public runParsed(file: ABAPFile) {
    const issues: Issue[] = [];
 
    const structure = file.getStructure();
    if (structure === undefined) {
      return [];
    }
 
    const containsUnknown = file.getStatements().some(s => s.get() instanceof Unknown);
    if (containsUnknown === true) {
      return [];
    }
 
    const routines = structure.findAllStructuresMulti([Structures.Form, Structures.Method, Structures.FunctionModule]);
    for (const r of routines) {
      // one fix per routine
      this.fixed = false;
 
      this.mode = DEFINITION;
      this.moveTo = r.getFirstStatement()?.getLastToken().getEnd();
 
      const found = this.walk(r, file);
      if (found) {
        issues.push(found);
      }
    }
 
    return issues;
  }
 
//////////////////
 
  private walk(r: StructureNode, file: ABAPFile): Issue | undefined {
 
    let previous: StatementNode | StructureNode | undefined = undefined;
    for (const c of r.getChildren()) {
      const get = c.get();
 
      if (c instanceof StatementNode) {
        if (get instanceof Comment) {
          continue;
        } else if (get instanceof Statements.FunctionModule) {
          continue;
        } else if (get instanceof Statements.Form) {
          continue;
        } else if (get instanceof Statements.MethodImplementation) {
          continue;
        }
      }
 
      if (c instanceof StructureNode
          && (get instanceof Structures.Data
          || get instanceof Structures.Types
          || get instanceof Structures.Constants
          || get instanceof Structures.Statics)) {
        if (this.mode === AFTER) {
          // These are chained structured statements
          let fix = undefined;
          if (c.getLastChild()?.getLastChild()?.getFirstToken().getStr() === "."
              && !(previous instanceof StructureNode)
              && this.moveTo) {
            // this is not perfect, but will work for now
            const start = c.getFirstChild()?.getFirstChild()?.getFirstToken().getStart();
            const end = c.getLastChild()?.getLastChild()?.getLastToken().getEnd();
            if (start && end ) {
              let concat = c.concatTokens();
              concat = concat.replace(/,/g, ".\n");
              const fix1 = EditHelper.deleteRange(file, start, end);
              const fix2 = EditHelper.insertAt(file, this.moveTo, "\n" + concat);
              fix = EditHelper.merge(fix1, fix2);
            }
          }
          // no quick fixes for these, its difficult?
          return Issue.atStatement(file, c.getFirstStatement()!, this.getMessage(), this.getMetadata().key, this.conf.severity, fix);
        } else {
          this.moveTo = c.getLastToken().getEnd();
        }
      } else if (c instanceof StatementNode
          && (get instanceof Statements.Data
          || get instanceof Statements.Type
          || get instanceof Statements.Constant
          || (get instanceof Statements.Move && c.concatTokens().toUpperCase().startsWith("DATA("))
          || get instanceof Statements.Static
          || get instanceof Statements.FieldSymbol)) {
        if (this.mode === AFTER) {
          // only one fix per routine, as it reorders a lot
          if (!(get instanceof Statements.Move && c.concatTokens().toUpperCase().startsWith("DATA("))) {
            let fix = undefined;
            if (this.fixed === false && this.moveTo) {
              fix = this.buildFix(file, c, this.moveTo);
              this.fixed = true;
            }
            return Issue.atStatement(file, c, this.getMessage(), this.getMetadata().key, this.conf.severity, fix);
          }
        } else {
          this.moveTo = c.getLastToken().getEnd();
        }
      } else if (c instanceof StructureNode && get instanceof Structures.Define) {
        this.mode = IGNORE;
        return undefined;
      } else if (c instanceof StatementNode && get instanceof Unknown) {
        this.mode = IGNORE;
        return undefined;
      } else if (c instanceof StatementNode && this.mode === DEFINITION) {
        this.mode = AFTER;
      } else if (c instanceof StructureNode) {
        const found = this.walk(c, file);
        if (found) {
          return found;
        }
      }
 
      previous = c;
    }
 
    return undefined;
  }
 
  private buildFix(file: ABAPFile, statement: StatementNode, at: Position): IEdit {
    let concat = statement.concatTokens();
    concat = concat.replace(/,$/, ".");
 
    const fix1 = EditHelper.deleteStatement(file, statement);
    const indentation = " ".repeat(statement.getFirstToken().getCol() - 1);
    const fix2 = EditHelper.insertAt(file, at, "\n" + indentation + concat);
 
    return EditHelper.merge(fix1, fix2);
  }
}