diff --git a/composer.json b/composer.json index 853fb8e..6a0163a 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,8 @@ "nikic/php-parser": "^3.0.0beta1", "phpdocumentor/reflection-docblock": "^3.0", "sabre/event": "^3.0", - "felixfbecker/advanced-json-rpc": "^1.2" + "felixfbecker/advanced-json-rpc": "^1.2", + "friendsofphp/php-cs-fixer" : "^1.12" }, "minimum-stability": "dev", "prefer-stable": true, diff --git a/fixtures/format.php b/fixtures/format.php index b45ebab..6f8d734 100644 --- a/fixtures/format.php +++ b/fixtures/format.php @@ -1,6 +1,6 @@ prepareFixers($config); + $fixers = $this->sortFixers($fixers); + + return $this->doFixContent($content, $uri, $fixers); + } + + /** + * @param string $content + * @param string $uri + * @param array $fixers + * + * @return string|null formated source code or null if no change was made + */ + private function doFixContent(string $content, string $uri, array $fixers) + { + $new = $old = $content; + + if ('' === $old || + // PHP 5.3 has a broken implementation of token_get_all when the file uses __halt_compiler() starting in 5.3.6 + (PHP_VERSION_ID >= 50306 && PHP_VERSION_ID < 50400 && false !== stripos($old, '__halt_compiler()'))) { + return null; + } + + // we do not need Tokens to still caching previously fixed file - so clear the cache + Tokens::clearCache(); + + try { + $file = new \SplFileInfo($uri); + foreach ($fixers as $fixer) { + if (!$fixer->supports($file)) { + continue; + } + + $newest = $fixer->fix($file, $new); + $new = $newest; + } + } catch (\ParseError $e) { + return null; + } catch (\Error $e) { + return null; + } catch (\Exception $e) { + return null; + } + + if ($new !== $old) { + return $new; + } + + return null; + } + + /** + * @param FixerInterface[] $fixers + * + * @return FixerInterface[] + */ + private function sortFixers(array $fixers) + { + usort($fixers, function (FixerInterface $a, FixerInterface $b) { + return Utils::cmpInt($b->getPriority(), $a->getPriority()); + }); + + return $fixers; + } + + /** + * @param ConfigInterface $config + * + * @return FixerInterface[] + */ + private function prepareFixers(ConfigInterface $config) + { + $fixers = $config->getFixers(); + + foreach ($fixers as $fixer) { + if ($fixer instanceof ConfigAwareInterface) { + $fixer->setConfig($config); + } + } + + return $fixers; + } +} diff --git a/src/Server/Formatter.php b/src/Server/Formatter.php new file mode 100644 index 0000000..ab05162 --- /dev/null +++ b/src/Server/Formatter.php @@ -0,0 +1,59 @@ +fixer = new CustomCSFixer(); + $this->fixer->registerBuiltInFixers(); + $this->fixer->registerBuiltInConfigs(); + } + + /** + * @param string $content + * + * @return TextEdit[] + */ + public function format(string $content, string $uri) + { + // remove 'file://' prefix + $uri = substr($uri, 7); + + $config = new Config(); + + // TODO read user configuration from workspace root (.php_cs file) + + // register custom fixers from config + $this->fixer->registerCustomFixers($config->getCustomFixers()); + + $resolver = new ConfigurationResolver(); + $resolver->setAllFixers($this->fixer->getFixers()) + ->setConfig($config) + //->setOptions(array('level' => 'psr2')) + ->resolve(); + + $config->fixers($resolver->getFixers()); + $formatted = $this->fixer->fixContent($content, $uri, $config); + if ($formatted == null) { + return []; + } + + $edit = new TextEdit(); + $edit->range = new Range(new Position(0, 0), new Position(PHP_INT_MAX, PHP_INT_MAX)); + $edit->newText = $formatted; + return [$edit]; + } +} diff --git a/src/Server/TextDocument.php b/src/Server/TextDocument.php index 23c01c0..8190ba7 100644 --- a/src/Server/TextDocument.php +++ b/src/Server/TextDocument.php @@ -42,6 +42,13 @@ class TextDocument */ private $client; + /** + * Opened source files content + * + * @var string[] + */ + private $documents = []; + public function __construct(LanguageClient $client) { $this->client = $client; @@ -103,6 +110,8 @@ class TextDocument */ private function updateAst(string $uri, string $content) { + $this->documents[$uri] = $content; + $stmts = $this->parser->parse($content); $diagnostics = []; foreach ($this->parser->getErrors() as $error) { @@ -128,24 +137,34 @@ class TextDocument } } + /** + * The document close notification is sent from the client to the server when + * the document got closed in the client. The document's truth now exists where the document's + * uri points to (e.g. if the document's uri is a file uri the truth now exists on disk). + * + * @param TextDocumentIdentifier $textDocument + */ + public function didClose(TextDocumentIdentifier $textDocument) + { + unset($this->documents[$textDocument->uri]); + } + /** * The document formatting request is sent from the server to the client to format a whole document. * * @param TextDocumentIdentifier $textDocument The document to format - * @param FormattingOptions $options The format options + * @param FormattingOptions $options The format options + * * @return TextEdit[] */ public function formatting(TextDocumentIdentifier $textDocument, FormattingOptions $options) { - $nodes = $this->asts[$textDocument->uri]; - if (empty($nodes)) { + $formatter = new Formatter(); + $content = $this->documents[$textDocument->uri]; + if (!$content) { 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($nodes); - return [$edit]; + + return $formatter->format($content, $textDocument->uri); } - }