All files / src/rules final_newline.ts

100% Statements 88/88
100% Branches 24/24
100% Functions 7/7
100% Lines 88/88

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 891x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 14689x 14689x 14689x 14689x 14689x 143132x 143132x 143132x 143132x 143132x 143132x 143132x 143132x 143132x 143132x 143132x 143132x 143132x 143132x 14689x 14689x 14120x 14120x 14689x 14689x 264x 264x 14689x 14689x 292x 292x 14689x 14689x 389x 389x 389x 1x 1x 388x 389x 434x 434x 422x 422x 383x 383x 422x 434x 388x 388x 388x 14689x 14689x 422x 422x 422x 422x 376x 376x 376x 376x 46x 46x 422x 11x 11x 422x 39x 39x 7x 7x 7x 7x 7x 14689x  
import {Issue} from "../issue";
import {Position} from "../position";
import {BasicRuleConfig} from "./_basic_rule_config";
import {EditHelper} from "../edit_helper";
import {IRule, IRuleMetadata, RuleTag} from "./_irule";
import {IRegistry} from "../_iregistry";
import {IObject} from "../objects/_iobject";
import {IFile} from "../files/_ifile";
import {MIMEObject, WebMIME} from "../objects";
 
export class FinalNewlineConf extends BasicRuleConfig {
}
 
export class FinalNewline implements IRule {
 
  private conf = new FinalNewlineConf();
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "final_newline",
      title: "Final newline",
      shortDescription: `Checks that ABAP and XML files end with exactly one newline`,
      extendedInformation: `abapGit serializes every file with exactly one newline at the end. A file without it,
or with empty lines at the end, is changed again on the next pull and shows up in every diff.
 
Empty files and files containing only line breaks are not checked. A carriage return before the
newline is ignored, see rule line_break_style. SMIM and W3MI files and XSLT sources are not checked.`,
      tags: [RuleTag.Whitespace, RuleTag.Quickfix, RuleTag.SingleFile],
      badExample: `WRITE 'hello'.`,
      goodExample: `WRITE 'hello'.\n`,
    };
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public setConfig(conf: FinalNewlineConf) {
    this.conf = conf;
  }
 
  public initialize(_reg: IRegistry): IRule {
    return this;
  }
 
  public run(obj: IObject): readonly Issue[] {
    const issues: Issue[] = [];
 
    if (obj instanceof MIMEObject || obj instanceof WebMIME) {
      return issues;
    }
 
    for (const file of obj.getFiles()) {
      const filename = file.getFilename();
      if ((filename.endsWith(".abap") || filename.endsWith(".xml")) && !filename.endsWith(".xslt.source.xml")) {
        const issue = this.checkFile(file);
        if (issue) {
          issues.push(issue);
        }
      }
    }
 
    return issues;
  }
 
  private checkFile(file: IFile): Issue | undefined {
    const rows = file.getRawRows();
    const last = rows[rows.length - 1];
 
    if (last !== "") {
      const end = new Position(rows.length, last.length + 1);
      const fix = EditHelper.insertAt(file, end, "\n");
      return Issue.atPosition(file, end, "Add newline at end of file", this.getMetadata().key, this.conf.severity, fix);
    }
 
    let row = rows.length - 2;
    while (row >= 0 && (rows[row] === "" || rows[row] === "\r")) {
      row--;
    }
    if (row < 0 || row === rows.length - 2) {
      return undefined;
    }
 
    const start = new Position(row + 2, 1);
    const fix = EditHelper.deleteRange(file, start, new Position(rows.length, 1));
    return Issue.atPosition(file, start, "Remove empty lines at end of file", this.getMetadata().key, this.conf.severity, fix);
  }
}