diff --git a/src/NodeVisitor/DocBlockParser.php b/src/NodeVisitor/DocBlockParser.php index abc3b05..6a4c77a 100644 --- a/src/NodeVisitor/DocBlockParser.php +++ b/src/NodeVisitor/DocBlockParser.php @@ -3,9 +3,11 @@ declare(strict_types = 1); namespace LanguageServer\NodeVisitor; -use PhpParser\{NodeVisitorAbstract, Node}; +use PhpParser; +use PhpParser\{NodeVisitorAbstract, Node, Comment}; use phpDocumentor\Reflection\DocBlockFactory; use phpDocumentor\Reflection\Types\Context; +use Exception; /** * Decorates all nodes with a docBlock attribute that is an instance of phpDocumentor\Reflection\DocBlock @@ -31,6 +33,11 @@ class DocBlockParser extends NodeVisitorAbstract */ private $aliases; + /** + * @var PhpParser\Error[] + */ + public $errors = []; + public function __construct(DocBlockFactory $docBlockFactory) { $this->docBlockFactory = $docBlockFactory; @@ -54,8 +61,17 @@ class DocBlockParser extends NodeVisitorAbstract return; } $context = new Context($this->namespace, $this->aliases); - $docBlock = $this->docBlockFactory->create($docComment->getText(), $context); - $node->setAttribute('docBlock', $docBlock); + try { + $docBlock = $this->docBlockFactory->create($docComment->getText(), $context); + $node->setAttribute('docBlock', $docBlock); + } catch (Exception $e) { + $this->errors[] = new PhpParser\Error($e->getMessage(), [ + 'startFilePos' => $docComment->getFilePos(), + 'endFilePos' => $docComment->getFilePos() + strlen($docComment->getText()), + 'startLine' => $docComment->getLine(), + 'endLine' => $docComment->getLine() + preg_match_all('/[\\n\\r]/', $docComment->getText()) + 1 + ]); + } } public function leaveNode(Node $node) diff --git a/src/PhpDocument.php b/src/PhpDocument.php index 10ab5cc..e024461 100644 --- a/src/PhpDocument.php +++ b/src/PhpDocument.php @@ -138,19 +138,8 @@ class PhpDocument $diagnostics = []; foreach ($errors as $error) { - $diagnostic = new Diagnostic(); - $startLine = max($error->getStartLine() - 1, 0); - $startColumn = $error->hasColumnInfo() ? $error->getStartColumn($this->content) - 1 : 0; - $endLine = max($error->getEndLine() - 1, $startLine); - $endColumn = $error->hasColumnInfo() ? $error->getEndColumn($this->content) : 0; - $diagnostic->range = new Range(new Position($startLine, $startColumn), new Position($endLine, $endColumn)); - $diagnostic->severity = DiagnosticSeverity::ERROR; - $diagnostic->source = 'php'; - // Do not include "on line ..." in the error message - $diagnostic->message = $error->getRawMessage(); - $diagnostics[] = $diagnostic; + $diagnostics[] = Diagnostic::fromError($error, $this->content, DiagnosticSeverity::ERROR, 'php'); } - $this->client->textDocument->publishDiagnostics($this->uri, $diagnostics); // $stmts can be null in case of a fatal parsing error if ($stmts) { @@ -166,9 +155,16 @@ class PhpDocument $traverser->addVisitor(new ColumnCalculator($content)); // Parse docblocks and add docBlock attributes to nodes - $traverser->addVisitor(new DocBlockParser($this->docBlockFactory)); + $docBlockParser = new DocBlockParser($this->docBlockFactory); + $traverser->addVisitor($docBlockParser); $traverser->traverse($stmts); + + // Report errors from parsing docblocks + foreach ($docBlockParser->errors as $error) { + $diagnostics[] = Diagnostic::fromError($error, $this->content, DiagnosticSeverity::WARNING, 'php'); + } + $traverser = new NodeTraverser; // Collect all definitions @@ -195,6 +191,8 @@ class PhpDocument $this->stmts = $stmts; } + + $this->client->textDocument->publishDiagnostics($this->uri, $diagnostics); } /** diff --git a/src/Protocol/Diagnostic.php b/src/Protocol/Diagnostic.php index b65f288..44a24c1 100644 --- a/src/Protocol/Diagnostic.php +++ b/src/Protocol/Diagnostic.php @@ -2,6 +2,8 @@ namespace LanguageServer\Protocol; +use PhpParser\Error; + /** * Represents a diagnostic, such as a compiler error or warning. Diagnostic objects are only valid in the scope of a * resource. @@ -44,4 +46,40 @@ class Diagnostic * @var string */ public $message; + + /** + * Creates a diagnostic from a PhpParser Error + * + * @param Error $error Message and code will be used + * @param string $content The file content to calculate the column info + * @param int $severity DiagnosticSeverity + * @param string $source A human-readable string describing the source of this diagnostic + * @return self + */ + public static function fromError(Error $error, string $content, int $severity = null, string $source = null): self + { + return new self( + $error->getRawMessage(), // Do not include "on line ..." in the error message + Range::fromError($error, $content), + $error->getCode(), + $severity, + $source + ); + } + + /** + * @param string $message The diagnostic's message + * @param Range $range The range at which the message applies + * @param int $code The diagnostic's code + * @param int $severity DiagnosticSeverity + * @param string $source A human-readable string describing the source of this diagnostic + */ + public function __construct(string $message = null, Range $range = null, int $code = null, int $severity = null, string $source = null) + { + $this->message = $message; + $this->range = $range; + $this->code = $code; + $this->severity = $severity; + $this->source = $source; + } } diff --git a/src/Protocol/Hover.php b/src/Protocol/Hover.php index 2de89e5..6e7656e 100644 --- a/src/Protocol/Hover.php +++ b/src/Protocol/Hover.php @@ -10,7 +10,7 @@ class Hover /** * The hover's content * - * @var string|MarkedString|(string|MarkedString)[] + * @var string|MarkedString|string[]|MarkedString[] */ public $contents; @@ -22,7 +22,7 @@ class Hover public $range; /** - * @param string|MarkedString|(string|MarkedString)[] $contents The hover's content + * @param string|MarkedString|string[]|MarkedString[] $contents The hover's content * @param Range $range An optional range */ public function __construct($contents = null, $range = null) diff --git a/src/Protocol/Range.php b/src/Protocol/Range.php index 17f31da..e4fe527 100644 --- a/src/Protocol/Range.php +++ b/src/Protocol/Range.php @@ -2,7 +2,7 @@ namespace LanguageServer\Protocol; -use PhpParser\Node; +use PhpParser\{Error, Node}; /** * A range in a text document expressed as (zero-based) start and end positions. @@ -37,6 +37,22 @@ class Range ); } + /** + * Returns the range where an error occured + * + * @param \PhpParser\Error $error + * @param string $content + * @return self + */ + public static function fromError(Error $error, string $content) + { + $startLine = max($error->getStartLine() - 1, 0); + $endLine = max($error->getEndLine() - 1, $startLine); + $startColumn = $error->hasColumnInfo() ? $error->getStartColumn($content) - 1 : 0; + $endColumn = $error->hasColumnInfo() ? $error->getEndColumn($content) : 0; + return new self(new Position($startLine, $startColumn), new Position($endLine, $endColumn)); + } + public function __construct(Position $start = null, Position $end = null) { $this->start = $start;