240 lines
7.4 KiB
PHP
240 lines
7.4 KiB
PHP
|
<?php declare(strict_types=1);
|
||
|
|
||
|
namespace PhpParser;
|
||
|
|
||
|
class Comment implements \JsonSerializable
|
||
|
{
|
||
|
protected $text;
|
||
|
protected $startLine;
|
||
|
protected $startFilePos;
|
||
|
protected $startTokenPos;
|
||
|
protected $endLine;
|
||
|
protected $endFilePos;
|
||
|
protected $endTokenPos;
|
||
|
|
||
|
/**
|
||
|
* Constructs a comment node.
|
||
|
*
|
||
|
* @param string $text Comment text (including comment delimiters like /*)
|
||
|
* @param int $startLine Line number the comment started on
|
||
|
* @param int $startFilePos File offset the comment started on
|
||
|
* @param int $startTokenPos Token offset the comment started on
|
||
|
*/
|
||
|
public function __construct(
|
||
|
string $text,
|
||
|
int $startLine = -1, int $startFilePos = -1, int $startTokenPos = -1,
|
||
|
int $endLine = -1, int $endFilePos = -1, int $endTokenPos = -1
|
||
|
) {
|
||
|
$this->text = $text;
|
||
|
$this->startLine = $startLine;
|
||
|
$this->startFilePos = $startFilePos;
|
||
|
$this->startTokenPos = $startTokenPos;
|
||
|
$this->endLine = $endLine;
|
||
|
$this->endFilePos = $endFilePos;
|
||
|
$this->endTokenPos = $endTokenPos;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the comment text.
|
||
|
*
|
||
|
* @return string The comment text (including comment delimiters like /*)
|
||
|
*/
|
||
|
public function getText() : string {
|
||
|
return $this->text;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the line number the comment started on.
|
||
|
*
|
||
|
* @return int Line number (or -1 if not available)
|
||
|
*/
|
||
|
public function getStartLine() : int {
|
||
|
return $this->startLine;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the file offset the comment started on.
|
||
|
*
|
||
|
* @return int File offset (or -1 if not available)
|
||
|
*/
|
||
|
public function getStartFilePos() : int {
|
||
|
return $this->startFilePos;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the token offset the comment started on.
|
||
|
*
|
||
|
* @return int Token offset (or -1 if not available)
|
||
|
*/
|
||
|
public function getStartTokenPos() : int {
|
||
|
return $this->startTokenPos;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the line number the comment ends on.
|
||
|
*
|
||
|
* @return int Line number (or -1 if not available)
|
||
|
*/
|
||
|
public function getEndLine() : int {
|
||
|
return $this->endLine;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the file offset the comment ends on.
|
||
|
*
|
||
|
* @return int File offset (or -1 if not available)
|
||
|
*/
|
||
|
public function getEndFilePos() : int {
|
||
|
return $this->endFilePos;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the token offset the comment ends on.
|
||
|
*
|
||
|
* @return int Token offset (or -1 if not available)
|
||
|
*/
|
||
|
public function getEndTokenPos() : int {
|
||
|
return $this->endTokenPos;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the line number the comment started on.
|
||
|
*
|
||
|
* @deprecated Use getStartLine() instead
|
||
|
*
|
||
|
* @return int Line number
|
||
|
*/
|
||
|
public function getLine() : int {
|
||
|
return $this->startLine;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the file offset the comment started on.
|
||
|
*
|
||
|
* @deprecated Use getStartFilePos() instead
|
||
|
*
|
||
|
* @return int File offset
|
||
|
*/
|
||
|
public function getFilePos() : int {
|
||
|
return $this->startFilePos;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the token offset the comment started on.
|
||
|
*
|
||
|
* @deprecated Use getStartTokenPos() instead
|
||
|
*
|
||
|
* @return int Token offset
|
||
|
*/
|
||
|
public function getTokenPos() : int {
|
||
|
return $this->startTokenPos;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the comment text.
|
||
|
*
|
||
|
* @return string The comment text (including comment delimiters like /*)
|
||
|
*/
|
||
|
public function __toString() : string {
|
||
|
return $this->text;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the reformatted comment text.
|
||
|
*
|
||
|
* "Reformatted" here means that we try to clean up the whitespace at the
|
||
|
* starts of the lines. This is necessary because we receive the comments
|
||
|
* without trailing whitespace on the first line, but with trailing whitespace
|
||
|
* on all subsequent lines.
|
||
|
*
|
||
|
* @return mixed|string
|
||
|
*/
|
||
|
public function getReformattedText() {
|
||
|
$text = trim($this->text);
|
||
|
$newlinePos = strpos($text, "\n");
|
||
|
if (false === $newlinePos) {
|
||
|
// Single line comments don't need further processing
|
||
|
return $text;
|
||
|
} elseif (preg_match('((*BSR_ANYCRLF)(*ANYCRLF)^.*(?:\R\s+\*.*)+$)', $text)) {
|
||
|
// Multi line comment of the type
|
||
|
//
|
||
|
// /*
|
||
|
// * Some text.
|
||
|
// * Some more text.
|
||
|
// */
|
||
|
//
|
||
|
// is handled by replacing the whitespace sequences before the * by a single space
|
||
|
return preg_replace('(^\s+\*)m', ' *', $this->text);
|
||
|
} elseif (preg_match('(^/\*\*?\s*[\r\n])', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) {
|
||
|
// Multi line comment of the type
|
||
|
//
|
||
|
// /*
|
||
|
// Some text.
|
||
|
// Some more text.
|
||
|
// */
|
||
|
//
|
||
|
// is handled by removing the whitespace sequence on the line before the closing
|
||
|
// */ on all lines. So if the last line is " */", then " " is removed at the
|
||
|
// start of all lines.
|
||
|
return preg_replace('(^' . preg_quote($matches[1]) . ')m', '', $text);
|
||
|
} elseif (preg_match('(^/\*\*?\s*(?!\s))', $text, $matches)) {
|
||
|
// Multi line comment of the type
|
||
|
//
|
||
|
// /* Some text.
|
||
|
// Some more text.
|
||
|
// Indented text.
|
||
|
// Even more text. */
|
||
|
//
|
||
|
// is handled by removing the difference between the shortest whitespace prefix on all
|
||
|
// lines and the length of the "/* " opening sequence.
|
||
|
$prefixLen = $this->getShortestWhitespacePrefixLen(substr($text, $newlinePos + 1));
|
||
|
$removeLen = $prefixLen - strlen($matches[0]);
|
||
|
return preg_replace('(^\s{' . $removeLen . '})m', '', $text);
|
||
|
}
|
||
|
|
||
|
// No idea how to format this comment, so simply return as is
|
||
|
return $text;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get length of shortest whitespace prefix (at the start of a line).
|
||
|
*
|
||
|
* If there is a line with no prefix whitespace, 0 is a valid return value.
|
||
|
*
|
||
|
* @param string $str String to check
|
||
|
* @return int Length in characters. Tabs count as single characters.
|
||
|
*/
|
||
|
private function getShortestWhitespacePrefixLen(string $str) : int {
|
||
|
$lines = explode("\n", $str);
|
||
|
$shortestPrefixLen = \INF;
|
||
|
foreach ($lines as $line) {
|
||
|
preg_match('(^\s*)', $line, $matches);
|
||
|
$prefixLen = strlen($matches[0]);
|
||
|
if ($prefixLen < $shortestPrefixLen) {
|
||
|
$shortestPrefixLen = $prefixLen;
|
||
|
}
|
||
|
}
|
||
|
return $shortestPrefixLen;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return array
|
||
|
* @psalm-return array{nodeType:string, text:mixed, line:mixed, filePos:mixed}
|
||
|
*/
|
||
|
public function jsonSerialize() : array {
|
||
|
// Technically not a node, but we make it look like one anyway
|
||
|
$type = $this instanceof Comment\Doc ? 'Comment_Doc' : 'Comment';
|
||
|
return [
|
||
|
'nodeType' => $type,
|
||
|
'text' => $this->text,
|
||
|
// TODO: Rename these to include "start".
|
||
|
'line' => $this->startLine,
|
||
|
'filePos' => $this->startFilePos,
|
||
|
'tokenPos' => $this->startTokenPos,
|
||
|
'endLine' => $this->endLine,
|
||
|
'endFilePos' => $this->endFilePos,
|
||
|
'endTokenPos' => $this->endTokenPos,
|
||
|
];
|
||
|
}
|
||
|
}
|