All files / src/rules definitions_top.ts

95.59% Statements 65/68
94.55% Branches 52/55
100% Functions 8/8
95.59% Lines 65/68

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   7267x             21665x                   6x       7008x       114x       144x   144x 144x 13x       131x   131x 131x 43x 43x   43x 43x 6x       131x             130x 219x   219x 15x 204x 28x     176x         5x   1x   171x           22x   5x 5x 5x 5x   5x   17x   149x 1x 1x 148x     148x 42x 106x 87x 87x 19x         104x       5x   5x 5x 5x   5x    
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()) {
      Iif (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;
          Eif (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 Iif (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);
  }
}