All files / src/objects/rename renamer.ts

87.74% Statements 93/106
64% Branches 16/25
100% Functions 7/7
87.74% Lines 93/106

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 1061x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 12x 12x 1x 1x 1x 1x 9x 9x 9x   9x     9x 9x 9x 1x 1x 1x 12x 12x 12x 12x   12x 1x 1x 1x 11x 11x 11x 11x 11x 1x 1x 1x 1x 11x 11x 6x 11x 5x 11x   11x 11x 1x 1x 9x 9x 9x 9x 34x 22x 34x 12x 12x     34x 9x 9x 9x 1x 1x 22x 22x     22x 22x 26x     26x 26x 26x 26x 26x 22x 22x 22x 1x 1x 9x 12x 12x     12x 12x 12x 12x 9x 1x
import {RenameFile, TextDocumentEdit, WorkspaceEdit} from "vscode-languageserver-types";
import {MemoryFile} from "../../files/memory_file";
import {IRegistry} from "../../_iregistry";
import {RenameGlobalClass} from "./rename_global_class";
import {RenameGlobalInterface} from "./rename_global_interface";
import {ObjectRenamer} from "./_object_renamer";
 
export class Renamer {
  private readonly reg: IRegistry;
 
  public constructor(reg: IRegistry) {
    this.reg = reg;
  }
 
  /** Applies the renaming to the objects and files in the registry,
   *  after renaming the registry is not parsed */
  public rename(type: string, oldName: string, newName: string) {
    const edits = this.buildEdits(type, oldName, newName);
 
    if (edits === undefined) {
      throw new Error("no changes could be determined");
    } else if (edits.changes) {
      throw new Error("only documentChanges expected");
    }
 
    this.apply(edits);
  }
 
  /** Builds edits, but does not apply to registry, used by LSP */
  public buildEdits(type: string, oldName: string, newName: string): WorkspaceEdit | undefined {
    this.reg.parse(); // the registry must be parsed to dermine references
 
    const obj = this.reg.getObject(type, oldName);
    if (obj === undefined) {
      throw new Error("rename, object not found");
    } else if (newName.length > obj.getAllowedNaming().maxLength) {
      // todo, also do not allow strange characters and spaces
      throw new Error("Name not allowed");
    }
 
    const r = this.factory(type);
 
    return r.buildEdits(obj, oldName.toUpperCase(), newName);
  }
 
//////////////////
 
  private factory(type: string): ObjectRenamer {
    switch (type) {
      case "CLAS":
        return new RenameGlobalClass(this.reg);
      case "INTF":
        return new RenameGlobalInterface(this.reg);
      default:
        throw new Error("Renaming of " + type + " not yet supported");
    }
  }
 
  private apply(edits: WorkspaceEdit) {
    const renames: RenameFile[] = [];
 
    // assumption: only renames or text changes, no deletes or creates
    for (const dc of edits.documentChanges || []) {
      if (TextDocumentEdit.is(dc)) {
        this.applyEdit(dc);
      } else if (RenameFile.is(dc)) {
        renames.push(dc);
      } else {
        throw new Error("unexpected documentChange type");
      }
    }
 
    this.applyRenames(renames);
  }
 
  private applyEdit(dc: TextDocumentEdit) {
    const file = this.reg.getFileByName(dc.textDocument.uri);
    if (file === undefined) {
      throw new Error("file " + dc.textDocument.uri + " not found");
    }
    const rows = file.getRawRows();
    for (const e of dc.edits) {
      if (e.range.start.line !== e.range.end.line) {
        throw new Error("applyEdit, start and end line differ");
      }
      const before = rows[e.range.start.line];
      rows[e.range.start.line] = before.substring(0, e.range.start.character) +
        e.newText +
        before.substring(e.range.end.character);
    }
    const newFile = new MemoryFile(dc.textDocument.uri, rows.join("\n"));
    this.reg.updateFile(newFile);
  }
 
  private applyRenames(renames: RenameFile[]) {
    for (const r of renames) {
      const old = this.reg.getFileByName(r.oldUri);
      if (old === undefined) {
        throw new Error("applyRenames, old not found");
      }
      const newFile = new MemoryFile(r.newUri, old.getRaw());
      this.reg.removeFile(old);
      this.reg.addFile(newFile);
    }
  }
}