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 15477x 15477x 15477x 15477x 15477x 151012x 151012x 151012x 151012x 151012x 151012x 151012x 151012x 151012x 151012x 151012x 151012x 151012x 151012x 15477x 15477x 14908x 14908x 15477x 15477x 264x 264x 15477x 15477x 292x 292x 15477x 15477x 389x 389x 389x 1x 1x 388x 389x 434x 434x 422x 422x 383x 383x 422x 434x 388x 388x 388x 15477x 15477x 422x 422x 422x 422x 376x 376x 376x 376x 46x 46x 422x 11x 11x 422x 39x 39x 7x 7x 7x 7x 7x 15477x  
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);
  }
}