2016-09-30 09:30:08 +00:00
|
|
|
<?php
|
2016-09-30 09:54:49 +00:00
|
|
|
declare(strict_types = 1);
|
2016-09-30 09:30:08 +00:00
|
|
|
|
|
|
|
namespace LanguageServer;
|
|
|
|
|
2016-10-08 10:59:22 +00:00
|
|
|
use LanguageServer\Protocol\{Diagnostic, DiagnosticSeverity, Range, Position, SymbolKind, TextEdit};
|
2016-10-08 11:22:34 +00:00
|
|
|
use LanguageServer\NodeVisitors\{ReferencesAdder, SymbolFinder, ColumnCalculator};
|
2016-09-30 09:30:08 +00:00
|
|
|
use PhpParser\{Error, Comment, Node, ParserFactory, NodeTraverser, Lexer, Parser};
|
|
|
|
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
|
|
|
|
use PhpParser\NodeVisitor\NameResolver;
|
|
|
|
|
|
|
|
class PhpDocument
|
|
|
|
{
|
2016-10-08 10:51:55 +00:00
|
|
|
/**
|
|
|
|
* The LanguageClient instance (to report errors etc)
|
|
|
|
*
|
|
|
|
* @var LanguageClient
|
|
|
|
*/
|
2016-09-30 09:30:08 +00:00
|
|
|
private $client;
|
2016-10-08 10:51:55 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The Project this document belongs to (to register definitions etc)
|
|
|
|
*
|
|
|
|
* @var Project
|
|
|
|
*/
|
2016-09-30 09:30:08 +00:00
|
|
|
private $project;
|
2016-10-08 10:51:55 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The PHPParser instance
|
|
|
|
*
|
|
|
|
* @var Parser
|
|
|
|
*/
|
2016-09-30 09:30:08 +00:00
|
|
|
private $parser;
|
|
|
|
|
2016-10-08 10:51:55 +00:00
|
|
|
/**
|
|
|
|
* The URI of the document
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
2016-09-30 09:30:08 +00:00
|
|
|
private $uri;
|
2016-10-08 10:51:55 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The content of the document
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
2016-09-30 09:30:08 +00:00
|
|
|
private $content;
|
2016-10-08 10:51:55 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var SymbolInformation[]
|
|
|
|
*/
|
2016-09-30 09:30:08 +00:00
|
|
|
private $symbols = [];
|
|
|
|
|
2016-10-08 10:51:55 +00:00
|
|
|
/**
|
|
|
|
* @param string $uri The URI of the document
|
|
|
|
* @param Project $project The Project this document belongs to (to register definitions etc)
|
|
|
|
* @param LanguageClient $client The LanguageClient instance (to report errors etc)
|
|
|
|
* @param Parser $parser The PHPParser instance
|
|
|
|
*/
|
2016-09-30 09:30:08 +00:00
|
|
|
public function __construct(string $uri, Project $project, LanguageClient $client, Parser $parser)
|
|
|
|
{
|
|
|
|
$this->uri = $uri;
|
|
|
|
$this->project = $project;
|
|
|
|
$this->client = $client;
|
|
|
|
$this->parser = $parser;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns all symbols in this document.
|
|
|
|
*
|
|
|
|
* @return SymbolInformation[]
|
|
|
|
*/
|
|
|
|
public function getSymbols()
|
|
|
|
{
|
|
|
|
return $this->symbols;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns symbols in this document filtered by query string.
|
|
|
|
*
|
|
|
|
* @param string $query The search query
|
|
|
|
* @return SymbolInformation[]
|
|
|
|
*/
|
|
|
|
public function findSymbols(string $query)
|
|
|
|
{
|
|
|
|
return array_filter($this->symbols, function($symbol) use(&$query) {
|
|
|
|
return stripos($symbol->name, $query) !== false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the content on this document.
|
|
|
|
*
|
|
|
|
* @param string $content
|
2016-10-08 10:51:55 +00:00
|
|
|
* @return void
|
2016-09-30 09:30:08 +00:00
|
|
|
*/
|
|
|
|
public function updateContent(string $content)
|
|
|
|
{
|
|
|
|
$this->content = $content;
|
|
|
|
$this->parse();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Re-parses a source file, updates symbols, reports parsing errors
|
|
|
|
* that may have occured as diagnostics and returns parsed nodes.
|
|
|
|
*
|
|
|
|
* @return \PhpParser\Node[]
|
|
|
|
*/
|
|
|
|
public function parse()
|
|
|
|
{
|
|
|
|
$stmts = null;
|
|
|
|
$errors = [];
|
|
|
|
try {
|
|
|
|
$stmts = $this->parser->parse($this->content);
|
2016-10-08 10:51:55 +00:00
|
|
|
} catch (\PhpParser\Error $e) {
|
2016-09-30 09:30:08 +00:00
|
|
|
// Lexer can throw errors. e.g for unterminated comments
|
|
|
|
// unfortunately we don't get a location back
|
|
|
|
$errors[] = $e;
|
|
|
|
}
|
|
|
|
|
|
|
|
$errors = array_merge($this->parser->getErrors(), $errors);
|
|
|
|
|
|
|
|
$diagnostics = [];
|
|
|
|
foreach ($errors as $error) {
|
|
|
|
$diagnostic = new Diagnostic();
|
|
|
|
$diagnostic->range = new Range(
|
|
|
|
new Position($error->getStartLine() - 1, $error->hasColumnInfo() ? $error->getStartColumn($this->content) - 1 : 0),
|
|
|
|
new Position($error->getEndLine() - 1, $error->hasColumnInfo() ? $error->getEndColumn($this->content) : 0)
|
|
|
|
);
|
|
|
|
$diagnostic->severity = DiagnosticSeverity::ERROR;
|
|
|
|
$diagnostic->source = 'php';
|
|
|
|
// Do not include "on line ..." in the error message
|
|
|
|
$diagnostic->message = $error->getRawMessage();
|
|
|
|
$diagnostics[] = $diagnostic;
|
|
|
|
}
|
|
|
|
$this->client->textDocument->publishDiagnostics($this->uri, $diagnostics);
|
|
|
|
|
|
|
|
// $stmts can be null in case of a fatal parsing error
|
|
|
|
if ($stmts) {
|
|
|
|
$traverser = new NodeTraverser;
|
2016-10-08 11:22:34 +00:00
|
|
|
|
|
|
|
// Resolve aliased names to FQNs
|
2016-09-30 09:30:08 +00:00
|
|
|
$traverser->addVisitor(new NameResolver);
|
2016-10-08 11:22:34 +00:00
|
|
|
|
|
|
|
// Add parentNode, previousSibling, nextSibling attributes
|
|
|
|
$traverser->addVisitor(new ReferencesAdder);
|
|
|
|
|
|
|
|
// Add column attributes to nodes
|
2016-09-30 09:30:08 +00:00
|
|
|
$traverser->addVisitor(new ColumnCalculator($this->content));
|
2016-10-08 11:22:34 +00:00
|
|
|
|
|
|
|
// Collect all symbols
|
|
|
|
$symbolFinder = new SymbolFinder($this->uri);
|
|
|
|
$traverser->addVisitor($symbolFinder);
|
|
|
|
|
2016-09-30 09:30:08 +00:00
|
|
|
$traverser->traverse($stmts);
|
|
|
|
|
2016-10-08 11:22:34 +00:00
|
|
|
$this->symbols = $symbolFinder->symbols;
|
2016-09-30 09:30:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $stmts;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns this document as formatted text.
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getFormattedText()
|
|
|
|
{
|
|
|
|
$stmts = $this->parse();
|
|
|
|
if (empty($stmts)) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
$prettyPrinter = new PrettyPrinter();
|
|
|
|
$edit = new TextEdit();
|
|
|
|
$edit->range = new Range(new Position(0, 0), new Position(PHP_INT_MAX, PHP_INT_MAX));
|
|
|
|
$edit->newText = $prettyPrinter->prettyPrintFile($stmts);
|
|
|
|
return [$edit];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns this document's text content.
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getContent()
|
|
|
|
{
|
|
|
|
return $this->content;
|
|
|
|
}
|
|
|
|
}
|