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 11125x 11125x 11125x 11125x 11125x 11125x 11125x 11125x 11125x 33181x 33181x 33181x 33181x 33181x 33181x 33181x 33181x 33181x 33181x 33181x 33181x 33181x 33181x 33181x 33181x 11125x 11125x 17x 17x 11125x 11125x 10557x 10557x 11125x 11125x 266x 266x 11125x 11125x 304x 304x 304x 304x 13x 13x 291x 291x 304x 8x 8x 283x 283x 304x 114x 114x 114x 114x 114x 114x 114x 114x 17x 17x 114x 283x 283x 283x 11125x 11125x 11125x 11125x 453x 453x 453x 762x 762x 762x 409x   409x 1x 409x 61x 408x 52x 52x 409x 648x 648x 762x 353x 353x 762x 12x 3x 3x 3x 3x 3x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 3x 3x 12x 9x 9x 762x 636x 295x 295x 295x 295x 636x 42x 15x 15x 14x 14x 14x 14x 14x 14x 14x 42x 27x 27x 636x 2x 2x 594x     592x 112x 592x 339x 339x 55x 55x 339x 574x 574x 574x 379x 379x 379x 11125x 11125x 14x 14x 14x 14x 14x 14x 14x 14x 14x 11125x
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);
  }
}