1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2025-01-08 13:51:01 +01:00
phorge-arcanist/src/lint/ArcanistLintPatcher.php
Alex Vandiver 8f69a5c378 Use phutil functions to copy/move files
Summary:
The `cp` and `mv` commands, when run from a Windows
environment, error out.  Use library functions from `Filesystem`
for cross-platform compatibility.

Test Plan:
Ran `arc lint` with auto-fix lint errors on both Linux and
Windows.

Reviewers: epriestley, #blessed_reviewers

Reviewed By: epriestley, #blessed_reviewers

Spies: Korvin, epriestley

Differential Revision: https://secure.phabricator.com/D16273
2016-07-12 10:21:58 -07:00

144 lines
3.8 KiB
PHP

<?php
/**
* Applies lint patches to the working copy.
*/
final class ArcanistLintPatcher extends Phobject {
private $dirtyUntil = 0;
private $characterDelta = 0;
private $modifiedData = null;
private $lineOffsets = null;
private $lintResult = null;
private $applyMessages = array();
public static function newFromArcanistLintResult(ArcanistLintResult $result) {
$obj = new ArcanistLintPatcher();
$obj->lintResult = $result;
return $obj;
}
public function getUnmodifiedFileContent() {
return $this->lintResult->getData();
}
public function getModifiedFileContent() {
if ($this->modifiedData === null) {
$this->buildModifiedFile();
}
return $this->modifiedData;
}
public function writePatchToDisk() {
$path = $this->lintResult->getFilePathOnDisk();
$data = $this->getModifiedFileContent();
$ii = null;
do {
$lint = $path.'.linted'.($ii++);
} while (file_exists($lint));
// Copy existing file to preserve permissions. 'chmod --reference' is not
// supported under OSX.
if (Filesystem::pathExists($path)) {
// This path may not exist if we're generating a new file.
Filesystem::copyFile($path, $lint);
}
Filesystem::writeFile($lint, $data);
try {
Filesystem::rename($lint, $path);
} catch (FilesystemException $e) {
throw new Exception(
pht(
"Unable to overwrite path '%s', patched version was left at '%s'.",
$path,
$lint));
}
foreach ($this->applyMessages as $message) {
$message->didApplyPatch();
}
}
private function __construct() {}
private function buildModifiedFile() {
$data = $this->getUnmodifiedFileContent();
foreach ($this->lintResult->getMessages() as $lint) {
if (!$lint->isPatchable()) {
continue;
}
$orig_offset = $this->getCharacterOffset($lint->getLine() - 1);
$orig_offset += $lint->getChar() - 1;
$dirty = $this->getDirtyCharacterOffset();
if ($dirty > $orig_offset) {
continue;
}
// Adjust the character offset by the delta *after* checking for
// dirtiness. The dirty character cursor is a cursor on the original file,
// and should be compared with the patch position in the original file.
$working_offset = $orig_offset + $this->getCharacterDelta();
$old_str = $lint->getOriginalText();
$old_len = strlen($old_str);
$new_str = $lint->getReplacementText();
$new_len = strlen($new_str);
if ($working_offset == strlen($data)) {
// Temporary hack to work around a destructive hphpi issue, see #451031.
$data .= $new_str;
} else {
$data = substr_replace($data, $new_str, $working_offset, $old_len);
}
$this->changeCharacterDelta($new_len - $old_len);
$this->setDirtyCharacterOffset($orig_offset + $old_len);
$this->applyMessages[] = $lint;
}
$this->modifiedData = $data;
}
private function getCharacterOffset($line_num) {
if ($this->lineOffsets === null) {
$lines = explode("\n", $this->getUnmodifiedFileContent());
$this->lineOffsets = array(0);
$last = 0;
foreach ($lines as $line) {
$this->lineOffsets[] = $last + strlen($line) + 1;
$last += strlen($line) + 1;
}
}
if ($line_num >= count($this->lineOffsets)) {
throw new Exception(pht('Data has fewer than %d lines.', $line));
}
return idx($this->lineOffsets, $line_num);
}
private function setDirtyCharacterOffset($offset) {
$this->dirtyUntil = $offset;
return $this;
}
private function getDirtyCharacterOffset() {
return $this->dirtyUntil;
}
private function changeCharacterDelta($change) {
$this->characterDelta += $change;
return $this;
}
private function getCharacterDelta() {
return $this->characterDelta;
}
}