All files / src/rules definitions_top.ts

98.34% Statements 178/181
86.36% Branches 57/66
100% Functions 8/8
98.34% Lines 178/181

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 1811x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 8927x 8927x 8927x 8927x 8927x 8927x 8927x 8927x 8927x 60564x 60564x 60564x 60564x 60564x 60564x 60564x 60564x 60564x 60564x 8927x 8927x 16x 16x 8927x 8927x 8485x 8485x 8927x 8927x 202x 202x 8927x 8927x 239x 239x 239x 239x 13x 13x 226x 226x 239x 7x 7x 219x 219x 239x 92x 92x 92x 92x 92x 92x 92x 92x 1x 1x 92x 91x 91x 92x 16x 16x 92x 219x 219x 219x 8927x 8927x 8927x 8927x 383x 383x 383x 640x   640x 50x 640x 41x 41x 549x 549x 640x 303x 303x 640x 10x 3x 3x 3x 3x 3x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 3x 3x 10x 7x 7x 640x 539x 246x 246x 246x 539x 33x 13x 13x 13x 13x 13x 13x 13x 33x 20x 20x 539x 1x 1x 506x     505x 90x 505x 292x 292x 53x 53x 292x 479x 479x 479x 313x 313x 313x 8927x 8927x 13x 13x 13x 13x 13x 13x 13x 13x 13x 8927x
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.findAllStructures(Structures.Form).concat(structure.findAllStructures(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) {
        if (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()) {
      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) {
          // 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
          && (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 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 && 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;
        }
      }
 
      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);
  }
}