All files / src/rules double_space.ts

98.95% Statements 189/191
98.36% Branches 60/61
100% Functions 10/10
98.95% Lines 189/191

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 188 189 190 1911x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 15306x 15306x 15306x 15306x 15306x 15306x 15306x 15306x 15306x 15306x 1x 7654x 7654x 7654x 7654x 7654x 30176x 30176x 30176x 30176x 30176x 30176x 30176x 30176x 30176x 7654x 7654x 15x 15x 7654x 7654x 12782x 12782x 7654x 7654x 139x 139x 7654x 7654x 182x 182x 182x 875x 875x 875x 875x 875x 875x 835x 835x 875x 875x 875x 875x 182x 182x 182x 182x 182x 7654x 7654x 182x 182x 182x 182x     182x 182x 830x 830x 764x 830x 33x 33x 33x 33x 33x 830x 70x 7x 70x 37x 37x 26x 70x 2x 2x 2x 2x 2x 26x 26x 26x 26x 175x 175x 175x 7654x 7654x 875x 875x 875x 875x 3593x 875x 875x 875x 2718x 2718x 2718x 3593x 3593x 3593x 5x 5x 5x 5x 5x 2718x 2718x 2718x 3593x 3593x 3593x 3x 3x 3x 3x 3x 2718x 2718x 2718x 875x 875x 875x 7654x 7654x 835x 835x 835x 835x 87x 87x 87x 748x 835x 2791x 748x 748x 748x 2043x 2043x 2043x 2791x 2791x 2791x 2791x 666x 666x 666x 666x 1377x 1377x 2791x 5x 5x 5x 5x 5x 5x 1372x 1372x 1372x 743x 743x 7654x 7654x
import {Issue} from "../issue";
import {ABAPRule} from "./_abap_rule";
import {BasicRuleConfig} from "./_basic_rule_config";
import {Token} from "../abap/1_lexer/tokens/_token";
import {ParenLeftW, Comment, WParenRightW, WParenRight} from "../abap/1_lexer/tokens";
import {TokenNode, StatementNode, TokenNodeRegex} from "../abap/nodes";
import {Unknown, MacroContent, MacroCall} from "../abap/2_statements/statements/_statement";
import {MethodDef} from "../abap/2_statements/statements";
import {Position} from "../position";
import {EditHelper} from "../edit_helper";
import {IRuleMetadata, RuleTag} from "./_irule";
import {ABAPFile} from "../abap/abap_file";
 
export class DoubleSpaceConf extends BasicRuleConfig {
  /** Check for double space after keywords */
  public keywords: boolean = true;
  /** Check for double space after start parenthesis */
  public startParen: boolean = true;
  /** Check for double space before end parenthesis */
  public endParen: boolean = true;
  /** Check for double space after colon/chaining operator */
  public afterColon: boolean = true;
}
 
export class DoubleSpace extends ABAPRule {
 
  private conf = new DoubleSpaceConf();
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "double_space",
      title: "Double space",
      shortDescription: `Checks that only a single space follows certain common statements.`,
      tags: [RuleTag.Whitespace, RuleTag.Quickfix, RuleTag.SingleFile],
      badExample: `DATA  foo TYPE i.`,
      goodExample: `DATA foo TYPE i.`,
    };
  }
 
  private getMessage(): string {
    return "Remove double space";
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public setConfig(conf: DoubleSpaceConf) {
    this.conf = conf;
  }
 
  public runParsed(file: ABAPFile) {
    let issues: Issue[] = [];
 
    for (const s of file.getStatements()) {
 
      if (this.conf.keywords === true
          && !(s.get() instanceof Unknown)
          && !(s.get() instanceof MethodDef)
          && !(s.get() instanceof MacroCall)
          && !(s.get() instanceof MacroContent)) {
        issues = issues.concat(this.checkKeywords(s, file));
      }
 
      issues = issues.concat(this.checkParen(s, file));
 
    }
 
    issues = issues.concat(this.checkAfterColon(file));
 
    return issues;
  }
 
  private checkAfterColon(file: ABAPFile): Issue[] {
    const issues: Issue[] = [];
    let cPosition: Position | undefined = undefined;
 
    if (this.conf.afterColon !== true) {
      return [];
    }
 
    for (const s of file.getStatements()) {
      const colon = s.getColon();
      if (colon === undefined) {
        continue;
      } else if (cPosition !== undefined && cPosition.getCol() === colon.getCol()) {
        continue;
      }
 
      cPosition = colon.getStart();
 
      for (const t of s.getTokens()) {
        if (t.getRow() !== cPosition.getRow()) {
          return [];
        } else if (t.getCol() < cPosition.getCol()) {
          continue;
        }
 
        if (t.getCol() > cPosition.getCol() + 2) {
          const issueStartPos = new Position(cPosition.getRow(), cPosition.getCol() + 2);
          const issueEndPos = new Position(t.getRow(), t.getCol());
          const fix = EditHelper.deleteRange(file, issueStartPos, issueEndPos);
          issues.push(Issue.atRange( file, issueStartPos, issueEndPos, this.getMessage(), this.getMetadata().key, this.conf.severity, fix));
        }
 
        break;
      }
    }
 
    return issues;
  }
 
  private checkParen(s: StatementNode, file: ABAPFile): Issue[] {
    const issues: Issue[] = [];
 
    let prev: Token | undefined = undefined;
    for (const t of s.getTokens()) {
      if (prev === undefined) {
        prev = t;
        continue;
      }
 
      if (this.getConfig().startParen === true
          && prev.getRow() === t.getRow()
          && prev instanceof ParenLeftW
          && !(t instanceof Comment)
          && prev.getEnd().getCol() + 1 < t.getCol()) {
        const issueStartPos = new Position(prev.getRow(), prev.getCol() + 2);
        const issueEndPos = new Position(t.getRow(), t.getCol());
        const fix = EditHelper.deleteRange(file, issueStartPos, issueEndPos);
        issues.push(Issue.atRange( file, issueStartPos, issueEndPos, this.getMessage(), this.getMetadata().key, this.conf.severity, fix));
      }
 
      if (this.getConfig().endParen === true
          && prev.getRow() === t.getRow()
          && !(prev instanceof ParenLeftW)
          && (t instanceof WParenRightW || t instanceof WParenRight)
          && prev.getEnd().getCol() + 1 < t.getCol()) {
        const issueStartPos = new Position(prev.getEnd().getRow(), prev.getEnd().getCol() + 1);
        const issueEndPos = new Position(t.getRow(), t.getCol());
        const fix = EditHelper.deleteRange(file, issueStartPos, issueEndPos);
        issues.push(Issue.atRange( file, issueStartPos, issueEndPos, this.getMessage(), this.getMetadata().key, this.conf.severity, fix));
      }
 
      prev = t;
    }
 
    return issues;
  }
 
  private checkKeywords(s: StatementNode, file: ABAPFile): Issue[] {
    const issues: Issue[] = [];
    let prev: TokenNode | undefined = undefined;
 
    if (s.getColon() !== undefined || s.getPragmas().length > 0) {
      // for chained statments just give up
      return [];
    }
 
    for (const n of s.getTokenNodes()) {
      if (prev === undefined) {
        prev = n;
        continue;
      }
 
      const upper = prev.get().getStr().toUpperCase();
      if (prev instanceof TokenNodeRegex
          || upper === "("
          || upper === "CHANGING"
          || upper === "EXPORTING"
          || upper === "OTHERS") {
        // not a keyword, continue
        prev = n;
        continue;
      }
 
      if (prev.get().getStart().getRow() === n.get().getStart().getRow()
          && prev.get().getEnd().getCol() + 1 < n.get().getStart().getCol()) {
        const issueStartPos = new Position(prev.get().getEnd().getRow(), prev.get().getEnd().getCol() + 1 );
        const issueEndPos = new Position(n.get().getRow(), n.get().getCol());
        const fix = EditHelper.deleteRange(file, issueStartPos, issueEndPos);
        issues.push(Issue.atRange( file, issueStartPos, issueEndPos, this.getMessage(), this.getMetadata().key, this.conf.severity, fix));
        return issues;
      }
 
      prev = n;
    }
    return [];
  }
 
}