All files / src/rules definitions_top.ts

98.36% Statements 181/184
85.93% Branches 55/64
100% Functions 8/8
98.36% Lines 181/184

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 1841x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 9366x 9366x 9366x 9366x 9366x 9366x 9366x 9366x 9366x 27939x 27939x 27939x 27939x 27939x 27939x 27939x 27939x 27939x 27939x 9366x 9366x 16x 16x 9366x 9366x 8898x 8898x 9366x 9366x 216x 216x 9366x 9366x 257x 257x 257x 257x 13x 13x 244x 244x 257x 7x 7x 237x 237x 257x 94x 94x 94x 94x 94x 94x 94x 94x 1x 1x 93x 93x 94x 16x 16x 94x 237x 237x 237x 9366x 9366x 9366x 9366x 385x 385x 385x 644x 644x 644x 341x   341x 50x 341x 43x 43x 341x 551x 551x 644x 303x 303x 644x 10x 3x 3x 3x 3x 3x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 3x 3x 10x 7x 7x 644x 541x 248x 248x 248x 541x 33x 13x 13x 13x 13x 13x 13x 13x 33x 20x 20x 541x 1x 1x 508x     507x 92x 507x 292x 292x 53x 53x 292x 481x 481x 481x 315x 315x 315x 9366x 9366x 13x 13x 13x 13x 13x 13x 13x 13x 13x 9366x
import {Issue} from "../issue";
import {Comment, Unknown} from "../abap/2_statements/statements/_statement";
import * as Statements from "../abap/2_statements/statements";
import * as Expressions from "../abap/2_statements/expressions";
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";
import {Version} from "../version";
 
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 and FORMs.`,
      extendedInformation: `If the routine has inline definitions then no issues are reported
 
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 [];
    }
 
    const containsUnknown = file.getStatements().some(s => s.get() instanceof Unknown);
    if (containsUnknown === true) {
      return [];
    }
 
    const routines = structure.findAllStructuresMulti([Structures.Form, Structures.Method]);
    for (const r of routines) {
      // one fix per routine
      this.fixed = false;
 
      this.mode = DEFINITION;
      this.moveTo = r.getFirstStatement()?.getLastToken().getEnd();
 
      if (this.reg.getConfig().getVersion() !== Version.v702
          && r.findFirstExpression(Expressions.InlineData)) {
        continue;
      }
 
      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.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.Static
          || get instanceof Statements.FieldSymbol)) {
        if (this.mode === AFTER) {
          // only one fix per routine, 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.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);
  }
}