All files / src/rules definitions_top.ts

97.9% Statements 140/143
95.92% Branches 47/49
100% Functions 8/8
97.9% Lines 140/143

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 1431x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 7655x 7655x 7655x 7655x 7655x 7655x 7655x 7655x 7655x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 22823x 7655x 7655x 9x 9x 7655x 7655x 7346x 7346x 7655x 7655x 139x 139x 7655x 7655x 165x 165x 165x 165x 13x 13x 152x 152x 152x 152x 152x 165x 57x 57x 57x 57x 57x 9x 9x 57x 152x 152x 152x 7655x 7655x 7655x 7655x 206x 206x 344x   344x 26x 344x 31x 31x 287x 287x 344x 155x 155x 344x 5x 1x 1x 1x 344x 282x 132x 132x 132x 282x 25x 8x 8x 8x 8x 8x 8x 8x 25x 17x 17x 282x 1x 1x 257x     256x 56x 256x 149x 149x 33x 33x 149x 344x 163x 163x 163x 7655x 7655x 8x 8x 8x 8x 8x 8x 8x 8x 7655x
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";
 
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: StatementNode | 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 and FORMs.`,
      extendedInformation: `https://docs.abapopenchecks.org/checks/17/`,
      tags: [RuleTag.SingleFile, RuleTag.Quickfix],
    };
  }
 
  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 [];
    }
 
    // one fix per file
    this.fixed = false;
 
    const routines = structure.findAllStructures(Structures.Form).concat(structure.findAllStructures(Structures.Method));
    for (const r of routines) {
      this.mode = DEFINITION;
      this.moveTo = r.getFirstStatement();
 
      const found = this.walk(r, file);
      if (found) {
        issues.push(found);
      }
    }
 
    return issues;
  }
 
//////////////////
 
  private walk(r: StructureNode, file: ABAPFile): Issue | undefined {
 
    for (const c of r.getChildren()) {
      if (c instanceof StatementNode && c.get() instanceof Comment) {
        continue;
      } else if (c instanceof StatementNode && c.get() instanceof Statements.Form) {
        continue;
      } else if (c instanceof StatementNode && c.get() instanceof Statements.MethodImplementation) {
        continue;
      }
 
      if (c instanceof StructureNode
          && (c.get() instanceof Structures.Data
          || c.get() instanceof Structures.Types
          || c.get() instanceof Structures.Constants
          || c.get() instanceof Structures.Statics)) {
        if (this.mode === AFTER) {
          // no quick fixes for these, its difficult?
          return Issue.atStatement(file, c.getFirstStatement()!, this.getMessage(), this.getMetadata().key, this.conf.severity);
        }
      } else if (c instanceof StatementNode
          && (c.get() instanceof Statements.Data
          || c.get() instanceof Statements.Type
          || c.get() instanceof Statements.Constant
          || c.get() instanceof Statements.Static
          || c.get() instanceof Statements.FieldSymbol)) {
        if (this.mode === AFTER) {
          // only one fix per file, as it reorders a lot
          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;
        }
      } else if (c instanceof StructureNode && c.get() instanceof Structures.Define) {
        this.mode = IGNORE;
        return undefined;
      } else if (c instanceof StatementNode && c.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;
        }
      }
    }
 
    return undefined;
  }
 
  private buildFix(file: ABAPFile, statement: StatementNode, start: StatementNode): IEdit {
    const concat = statement.concatTokens();
 
    const fix1 = EditHelper.deleteStatement(file, statement);
    const indentation = " ".repeat(statement.getFirstToken().getCol() - 1);
    const fix2 = EditHelper.insertAt(file, start.getEnd(), "\n" + indentation + concat);
 
    return EditHelper.merge(fix1, fix2);
  }
}