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 10347x 10347x 10347x 10347x 10347x 10347x 10347x 10347x 10347x 30872x 30872x 30872x 30872x 30872x 30872x 30872x 30872x 30872x 30872x 30872x 30872x 30872x 30872x 30872x 30872x 10347x 10347x 17x 17x 10347x 10347x 9829x 9829x 10347x 10347x 241x 241x 10347x 10347x 280x 280x 280x 280x 13x 13x 267x 267x 280x 7x 7x 260x 260x 280x 102x 102x 102x 102x 102x 102x 102x 102x 17x 17x 102x 260x 260x 260x 10347x 10347x 10347x 10347x 406x 406x 406x 681x 681x 681x 366x   366x 1x 366x 53x 365x 48x 48x 366x 579x 579x 681x 315x 315x 681x 10x 3x 3x 3x 3x 3x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 3x 3x 10x 7x 7x 681x 569x 264x 264x 264x 264x 569x 39x 15x 15x 14x 14x 14x 14x 14x 14x 14x 39x 24x 24x 569x 1x 1x 530x     529x 101x 529x 304x 304x 55x 55x 304x 506x 506x 506x 333x 333x 333x 10347x 10347x 14x 14x 14x 14x 14x 14x 14x 14x 14x 10347x
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);
  }
}