2016-12-04 01:11:30 +01:00
|
|
|
<?php
|
|
|
|
namespace Psalm;
|
|
|
|
|
2018-01-02 02:04:03 +01:00
|
|
|
use Psalm\Checker\CommentChecker;
|
|
|
|
|
2016-12-04 01:11:30 +01:00
|
|
|
class CodeLocation
|
|
|
|
{
|
|
|
|
/** @var string */
|
|
|
|
public $file_path;
|
|
|
|
|
|
|
|
/** @var string */
|
|
|
|
public $file_name;
|
|
|
|
|
|
|
|
/** @var int */
|
2017-02-12 00:56:38 +01:00
|
|
|
private $line_number;
|
2016-12-08 04:38:57 +01:00
|
|
|
|
|
|
|
/** @var int */
|
2017-02-12 00:56:38 +01:00
|
|
|
private $file_start;
|
2016-12-08 04:38:57 +01:00
|
|
|
|
|
|
|
/** @var int */
|
2017-02-12 00:56:38 +01:00
|
|
|
private $file_end;
|
2016-12-04 01:11:30 +01:00
|
|
|
|
|
|
|
/** @var bool */
|
2017-02-12 00:56:38 +01:00
|
|
|
private $single_line;
|
2016-12-04 01:11:30 +01:00
|
|
|
|
|
|
|
/** @var int */
|
2017-02-12 00:56:38 +01:00
|
|
|
private $preview_start;
|
2016-12-08 04:38:57 +01:00
|
|
|
|
|
|
|
/** @var int */
|
2017-02-12 00:56:38 +01:00
|
|
|
private $preview_end = -1;
|
2016-12-08 04:38:57 +01:00
|
|
|
|
|
|
|
/** @var int */
|
2017-02-12 00:56:38 +01:00
|
|
|
private $selection_start = -1;
|
2016-12-08 04:38:57 +01:00
|
|
|
|
|
|
|
/** @var int */
|
2017-02-12 00:56:38 +01:00
|
|
|
private $selection_end = -1;
|
2016-12-08 04:38:57 +01:00
|
|
|
|
2017-01-16 04:39:26 +01:00
|
|
|
/** @var int */
|
2017-02-12 00:56:38 +01:00
|
|
|
private $column = -1;
|
2017-01-16 04:39:26 +01:00
|
|
|
|
2016-12-08 04:38:57 +01:00
|
|
|
/** @var string */
|
2017-02-12 00:56:38 +01:00
|
|
|
private $snippet = '';
|
2016-12-04 01:11:30 +01:00
|
|
|
|
2018-01-02 02:04:03 +01:00
|
|
|
/** @var ?string */
|
|
|
|
private $text;
|
|
|
|
|
2017-02-08 06:28:26 +01:00
|
|
|
/** @var int|null */
|
2017-02-12 00:56:38 +01:00
|
|
|
private $docblock_start_line_number;
|
2017-02-08 06:28:26 +01:00
|
|
|
|
2016-12-04 01:11:30 +01:00
|
|
|
/** @var int|null */
|
2017-02-12 00:56:38 +01:00
|
|
|
private $docblock_line_number;
|
2016-12-04 01:11:30 +01:00
|
|
|
|
2018-01-02 02:04:03 +01:00
|
|
|
/** @var ?int */
|
|
|
|
private $regex_type;
|
2016-12-08 04:38:57 +01:00
|
|
|
|
2017-05-27 02:16:18 +02:00
|
|
|
/** @var bool */
|
2016-12-08 04:38:57 +01:00
|
|
|
private $have_recalculated = false;
|
2016-12-06 22:33:47 +01:00
|
|
|
|
2017-06-21 20:22:52 +02:00
|
|
|
/** @var ?CodeLocation */
|
|
|
|
public $previous_location;
|
|
|
|
|
2018-01-02 02:04:03 +01:00
|
|
|
const VAR_TYPE = 0;
|
|
|
|
const FUNCTION_RETURN_TYPE = 1;
|
|
|
|
const FUNCTION_PARAM_TYPE = 2;
|
|
|
|
const FUNCTION_PHPDOC_RETURN_TYPE = 3;
|
|
|
|
const FUNCTION_PHPDOC_PARAM_TYPE = 4;
|
|
|
|
const FUNCTION_PARAM_VAR = 5;
|
|
|
|
|
2016-12-04 01:11:30 +01:00
|
|
|
/**
|
2018-01-02 02:04:03 +01:00
|
|
|
* @param bool $single_line
|
|
|
|
* @param ?int $regex_type
|
|
|
|
* @param ?CodeLocation $previous_location
|
|
|
|
* @param ?string $selected_text
|
2016-12-04 01:11:30 +01:00
|
|
|
*/
|
2016-12-08 04:38:57 +01:00
|
|
|
public function __construct(
|
2018-01-21 18:44:46 +01:00
|
|
|
FileSource $file_source,
|
2016-12-08 04:38:57 +01:00
|
|
|
\PhpParser\Node $stmt,
|
2017-06-21 20:22:52 +02:00
|
|
|
CodeLocation $previous_location = null,
|
2016-12-08 04:38:57 +01:00
|
|
|
$single_line = false,
|
2018-01-02 02:04:03 +01:00
|
|
|
$regex_type = null,
|
|
|
|
$selected_text = null
|
2016-12-08 04:38:57 +01:00
|
|
|
) {
|
2016-12-04 01:11:30 +01:00
|
|
|
$this->file_start = (int)$stmt->getAttribute('startFilePos');
|
|
|
|
$this->file_end = (int)$stmt->getAttribute('endFilePos');
|
2018-01-21 18:44:46 +01:00
|
|
|
$this->file_path = $file_source->getCheckedFilePath();
|
|
|
|
$this->file_name = $file_source->getCheckedFileName();
|
2016-12-04 01:11:30 +01:00
|
|
|
$this->single_line = $single_line;
|
2018-01-02 02:04:03 +01:00
|
|
|
$this->regex_type = $regex_type;
|
2017-06-21 20:22:52 +02:00
|
|
|
$this->previous_location = $previous_location;
|
2018-01-02 02:04:03 +01:00
|
|
|
$this->text = $selected_text;
|
2016-12-04 01:11:30 +01:00
|
|
|
|
|
|
|
$doc_comment = $stmt->getDocComment();
|
|
|
|
$this->preview_start = $doc_comment ? $doc_comment->getFilePos() : $this->file_start;
|
2017-02-08 06:28:26 +01:00
|
|
|
$this->docblock_start_line_number = $doc_comment ? $doc_comment->getLine() : null;
|
|
|
|
$this->line_number = $stmt->getLine();
|
2016-12-04 01:11:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $line
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-12-04 01:11:30 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
2016-12-06 22:33:47 +01:00
|
|
|
public function setCommentLine($line)
|
|
|
|
{
|
2017-02-08 06:28:26 +01:00
|
|
|
$this->docblock_line_number = $line;
|
2016-12-08 04:38:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-suppress MixedArrayAccess
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-12-08 04:38:57 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
private function calculateRealLocation()
|
|
|
|
{
|
|
|
|
if ($this->have_recalculated) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-12-13 01:11:21 +01:00
|
|
|
$this->have_recalculated = true;
|
|
|
|
|
2016-12-08 04:38:57 +01:00
|
|
|
$this->selection_start = $this->file_start;
|
2016-12-08 21:57:18 +01:00
|
|
|
$this->selection_end = $this->file_end + 1;
|
2016-12-08 04:38:57 +01:00
|
|
|
|
|
|
|
$project_checker = Checker\ProjectChecker::getInstance();
|
|
|
|
|
|
|
|
$file_contents = $project_checker->getFileContents($this->file_path);
|
|
|
|
|
2016-12-13 01:11:21 +01:00
|
|
|
$preview_end = strpos(
|
2016-12-08 04:38:57 +01:00
|
|
|
$file_contents,
|
|
|
|
"\n",
|
|
|
|
$this->single_line ? $this->selection_start : $this->selection_end
|
|
|
|
);
|
|
|
|
|
2016-12-13 01:11:21 +01:00
|
|
|
// if the string didn't contain a newline
|
|
|
|
if ($preview_end === false) {
|
|
|
|
$preview_end = $this->selection_end;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->preview_end = $preview_end;
|
|
|
|
|
2017-02-08 06:28:26 +01:00
|
|
|
if ($this->docblock_line_number &&
|
|
|
|
$this->docblock_start_line_number &&
|
|
|
|
$this->preview_start < $this->selection_start
|
|
|
|
) {
|
2016-12-08 04:38:57 +01:00
|
|
|
$preview_lines = explode(
|
|
|
|
"\n",
|
|
|
|
substr(
|
|
|
|
$file_contents,
|
|
|
|
$this->preview_start,
|
|
|
|
$this->selection_start - $this->preview_start - 1
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
$preview_offset = 0;
|
|
|
|
|
|
|
|
$i = 0;
|
|
|
|
|
2017-02-08 06:28:26 +01:00
|
|
|
$comment_line_offset = $this->docblock_line_number - $this->docblock_start_line_number;
|
2016-12-08 04:38:57 +01:00
|
|
|
|
2017-05-27 02:05:57 +02:00
|
|
|
for ($i = 0; $i < $comment_line_offset; ++$i) {
|
2016-12-08 04:38:57 +01:00
|
|
|
$preview_offset += strlen($preview_lines[$i]) + 1;
|
|
|
|
}
|
|
|
|
|
2017-01-02 05:30:59 +01:00
|
|
|
$key_line = $preview_lines[$i];
|
2016-12-08 04:38:57 +01:00
|
|
|
|
2017-01-02 05:30:59 +01:00
|
|
|
$indentation = (int)strpos($key_line, '@');
|
|
|
|
|
|
|
|
$key_line = trim(preg_replace('@\**/\s*@', '', substr($key_line, $indentation)));
|
|
|
|
|
|
|
|
$this->selection_start = $preview_offset + $indentation + $this->preview_start;
|
|
|
|
$this->selection_end = $this->selection_start + strlen($key_line);
|
2018-01-02 02:04:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->regex_type !== null) {
|
|
|
|
switch ($this->regex_type) {
|
|
|
|
case self::VAR_TYPE:
|
|
|
|
$regex = '/@(psalm-)?var[ \t]+' . CommentChecker::TYPE_REGEX . '/';
|
|
|
|
$match_offset = 2;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case self::FUNCTION_RETURN_TYPE:
|
2018-01-07 04:11:33 +01:00
|
|
|
$regex = '/\\:\s+(\\??\s*[A-Za-z0-9_\\\\\[\]]+)/';
|
2018-01-02 02:04:03 +01:00
|
|
|
$match_offset = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case self::FUNCTION_PARAM_TYPE:
|
2018-01-07 04:11:33 +01:00
|
|
|
$regex = '/^(\\??\s*[A-Za-z0-9_\\\\\[\]]+)\s/';
|
2018-01-02 02:04:03 +01:00
|
|
|
$match_offset = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case self::FUNCTION_PHPDOC_RETURN_TYPE:
|
|
|
|
$regex = '/@(psalm-)?return[ \t]+' . CommentChecker::TYPE_REGEX . '/';
|
|
|
|
$match_offset = 2;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case self::FUNCTION_PHPDOC_PARAM_TYPE:
|
|
|
|
$regex = '/@(psalm-)?param[ \t]+' . CommentChecker::TYPE_REGEX . '/';
|
|
|
|
$match_offset = 2;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case self::FUNCTION_PARAM_VAR:
|
|
|
|
$regex = '/(\$[^ ]*)/';
|
|
|
|
$match_offset = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new \UnexpectedValueException('Unrecognised regex type ' . $this->regex_type);
|
|
|
|
}
|
|
|
|
|
2016-12-08 04:38:57 +01:00
|
|
|
$preview_snippet = substr(
|
|
|
|
$file_contents,
|
|
|
|
$this->selection_start,
|
|
|
|
$this->selection_end - $this->selection_start
|
|
|
|
);
|
|
|
|
|
2018-01-02 02:04:03 +01:00
|
|
|
if ($this->text) {
|
|
|
|
$regex = '/(' . str_replace(',', ',[ ]*', preg_quote($this->text)) . ')/';
|
|
|
|
$match_offset = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (preg_match($regex, $preview_snippet, $matches, PREG_OFFSET_CAPTURE)) {
|
|
|
|
$this->selection_start = $this->selection_start + (int)$matches[$match_offset][1];
|
|
|
|
$this->selection_end = $this->selection_start + strlen((string)$matches[$match_offset][0]);
|
2016-12-08 04:38:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// reset preview start to beginning of line
|
|
|
|
$this->preview_start = (int)strrpos(
|
|
|
|
$file_contents,
|
|
|
|
"\n",
|
|
|
|
min($this->preview_start, $this->selection_start) - strlen($file_contents)
|
|
|
|
) + 1;
|
|
|
|
|
2016-12-13 01:11:21 +01:00
|
|
|
$this->selection_start = max($this->preview_start, $this->selection_start);
|
|
|
|
$this->selection_end = min($this->preview_end, $this->selection_end);
|
|
|
|
|
2017-01-28 06:39:16 +01:00
|
|
|
if ($this->preview_end - $this->selection_end > 200) {
|
|
|
|
$this->preview_end = (int)strrpos(
|
|
|
|
$file_contents,
|
|
|
|
"\n",
|
|
|
|
$this->selection_end + 200 - strlen($file_contents)
|
|
|
|
);
|
2017-02-08 07:39:49 +01:00
|
|
|
|
|
|
|
// if the line is over 200 characters long
|
|
|
|
if ($this->preview_end < $this->selection_end) {
|
|
|
|
$this->preview_end = $this->selection_end + 50;
|
|
|
|
}
|
2017-01-28 06:39:16 +01:00
|
|
|
}
|
|
|
|
|
2017-01-16 04:39:26 +01:00
|
|
|
// reset preview start to beginning of line
|
|
|
|
$this->column = $this->selection_start -
|
|
|
|
(int)strrpos($file_contents, "\n", $this->selection_start - strlen($file_contents));
|
|
|
|
|
2016-12-08 04:38:57 +01:00
|
|
|
$this->snippet = substr($file_contents, $this->preview_start, $this->preview_end - $this->preview_start);
|
2018-01-02 02:04:03 +01:00
|
|
|
$this->text = substr($file_contents, $this->selection_start, $this->selection_end - $this->selection_start);
|
2016-12-08 04:38:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
public function getLineNumber()
|
|
|
|
{
|
2017-02-08 06:28:26 +01:00
|
|
|
return $this->docblock_line_number ?: $this->line_number;
|
2016-12-08 04:38:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getSnippet()
|
|
|
|
{
|
|
|
|
$this->calculateRealLocation();
|
|
|
|
|
|
|
|
return $this->snippet;
|
|
|
|
}
|
|
|
|
|
2018-01-02 02:04:03 +01:00
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getSelectedText()
|
|
|
|
{
|
|
|
|
$this->calculateRealLocation();
|
|
|
|
|
|
|
|
return (string)$this->text;
|
|
|
|
}
|
|
|
|
|
2017-01-16 04:39:26 +01:00
|
|
|
/**
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
public function getColumn()
|
|
|
|
{
|
|
|
|
$this->calculateRealLocation();
|
|
|
|
|
|
|
|
return $this->column;
|
|
|
|
}
|
|
|
|
|
2016-12-08 04:38:57 +01:00
|
|
|
/**
|
|
|
|
* @return array<int, int>
|
|
|
|
*/
|
|
|
|
public function getSelectionBounds()
|
|
|
|
{
|
|
|
|
$this->calculateRealLocation();
|
|
|
|
|
|
|
|
return [$this->selection_start, $this->selection_end];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array<int, int>
|
|
|
|
*/
|
|
|
|
public function getSnippetBounds()
|
|
|
|
{
|
|
|
|
$this->calculateRealLocation();
|
|
|
|
|
|
|
|
return [$this->preview_start, $this->preview_end];
|
2016-12-04 01:11:30 +01:00
|
|
|
}
|
|
|
|
}
|