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