getReturnReference() ? '&' : '') . $function->getName(); $returnType = $this->printReturnType($function, $namespace); return Helpers::formatDocComment($function->getComment() . "\n") . self::printAttributes($function->getAttributes(), $namespace) . $line . $this->printParameters($function, $namespace, strlen($line) + strlen($returnType) + 2) // 2 = parentheses . $returnType . "\n{\n" . $this->indent(ltrim(rtrim($function->getBody()) . "\n")) . "}\n"; } public function printClosure(Closure $closure): string { $uses = []; foreach ($closure->getUses() as $param) { $uses[] = ($param->isReference() ? '&' : '') . '$' . $param->getName(); } $useStr = strlen($tmp = implode(', ', $uses)) > (new Dumper)->wrapLength && count($uses) > 1 ? "\n" . $this->indentation . implode(",\n" . $this->indentation, $uses) . "\n" : $tmp; return self::printAttributes($closure->getAttributes(), null, true) . 'function ' . ($closure->getReturnReference() ? '&' : '') . $this->printParameters($closure, null) . ($uses ? " use ($useStr)" : '') . $this->printReturnType($closure, null) . " {\n" . $this->indent(ltrim(rtrim($closure->getBody()) . "\n")) . '}'; } public function printArrowFunction(Closure $closure): string { foreach ($closure->getUses() as $use) { if ($use->isReference()) { throw new Nette\InvalidArgumentException('Arrow function cannot bind variables by-reference.'); } } return self::printAttributes($closure->getAttributes(), null) . 'fn' . ($closure->getReturnReference() ? '&' : '') . $this->printParameters($closure, null) . $this->printReturnType($closure, null) . ' => ' . trim($closure->getBody()) . ';'; } public function printMethod(Method $method, PhpNamespace $namespace = null): string { $method->validate(); $line = ($method->isAbstract() ? 'abstract ' : '') . ($method->isFinal() ? 'final ' : '') . ($method->getVisibility() ? $method->getVisibility() . ' ' : '') . ($method->isStatic() ? 'static ' : '') . 'function ' . ($method->getReturnReference() ? '&' : '') . $method->getName(); $returnType = $this->printReturnType($method, $namespace); return Helpers::formatDocComment($method->getComment() . "\n") . self::printAttributes($method->getAttributes(), $namespace) . $line . ($params = $this->printParameters($method, $namespace, strlen($line) + strlen($returnType) + strlen($this->indentation) + 2)) // 2 = parentheses . $returnType . ($method->isAbstract() || $method->getBody() === null ? ";\n" : (strpos($params, "\n") === false ? "\n" : ' ') . "{\n" . $this->indent(ltrim(rtrim($method->getBody()) . "\n")) . "}\n"); } public function printClass(ClassType $class, PhpNamespace $namespace = null): string { $class->validate(); $resolver = $this->resolveTypes && $namespace ? [$namespace, 'unresolveUnionType'] : function ($s) { return $s; }; $traits = []; foreach ($class->getTraitResolutions() as $trait => $resolutions) { $traits[] = 'use ' . $resolver($trait) . ($resolutions ? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n" : ";\n"); } $consts = []; foreach ($class->getConstants() as $const) { $def = ($const->getVisibility() ? $const->getVisibility() . ' ' : '') . 'const ' . $const->getName() . ' = '; $consts[] = Helpers::formatDocComment((string) $const->getComment()) . self::printAttributes($const->getAttributes(), $namespace) . $def . $this->dump($const->getValue(), strlen($def)) . ";\n"; } $properties = []; foreach ($class->getProperties() as $property) { $type = $property->getType(); $def = (($property->getVisibility() ?: 'public') . ($property->isStatic() ? ' static' : '') . ' ' . ltrim($this->printType($type, $property->isNullable(), $namespace) . ' ') . '$' . $property->getName()); $properties[] = Helpers::formatDocComment((string) $property->getComment()) . self::printAttributes($property->getAttributes(), $namespace) . $def . ($property->getValue() === null && !$property->isInitialized() ? '' : ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = ' . ";\n"; } $methods = []; foreach ($class->getMethods() as $method) { $methods[] = $this->printMethod($method, $namespace); } $members = array_filter([ implode('', $traits), $this->joinProperties($consts), $this->joinProperties($properties), ($methods && $properties ? str_repeat("\n", $this->linesBetweenMethods - 1) : '') . implode(str_repeat("\n", $this->linesBetweenMethods), $methods), ]); return Strings::normalize( Helpers::formatDocComment($class->getComment() . "\n") . self::printAttributes($class->getAttributes(), $namespace) . ($class->isAbstract() ? 'abstract ' : '') . ($class->isFinal() ? 'final ' : '') . ($class->getName() ? $class->getType() . ' ' . $class->getName() . ' ' : '') . ($class->getExtends() ? 'extends ' . implode(', ', array_map($resolver, (array) $class->getExtends())) . ' ' : '') . ($class->getImplements() ? 'implements ' . implode(', ', array_map($resolver, $class->getImplements())) . ' ' : '') . ($class->getName() ? "\n" : '') . "{\n" . ($members ? $this->indent(implode("\n", $members)) : '') . '}' ) . ($class->getName() ? "\n" : ''); } public function printNamespace(PhpNamespace $namespace): string { $name = $namespace->getName(); $uses = $this->printUses($namespace); $classes = []; foreach ($namespace->getClasses() as $class) { $classes[] = $this->printClass($class, $namespace); } $body = ($uses ? $uses . "\n\n" : '') . implode("\n", $classes); if ($namespace->hasBracketedSyntax()) { return 'namespace' . ($name ? " $name" : '') . "\n{\n" . $this->indent($body) . "}\n"; } else { return ($name ? "namespace $name;\n\n" : '') . $body; } } public function printFile(PhpFile $file): string { $namespaces = []; foreach ($file->getNamespaces() as $namespace) { $namespaces[] = $this->printNamespace($namespace); } return Strings::normalize( "getComment() ? "\n" . Helpers::formatDocComment($file->getComment() . "\n") : '') . "\n" . ($file->hasStrictTypes() ? "declare(strict_types=1);\n\n" : '') . implode("\n\n", $namespaces) ) . "\n"; } /** @return static */ public function setTypeResolving(bool $state = true): self { $this->resolveTypes = $state; return $this; } protected function indent(string $s): string { $s = str_replace("\t", $this->indentation, $s); return Strings::indent($s, 1, $this->indentation); } protected function dump($var, int $column = 0): string { return (new Dumper)->dump($var, $column); } protected function printUses(PhpNamespace $namespace): string { $name = $namespace->getName(); $uses = []; foreach ($namespace->getUses() as $alias => $original) { if ($original !== ($name ? $name . '\\' . $alias : $alias)) { $uses[] = $alias === $original || substr($original, -(strlen($alias) + 1)) === '\\' . $alias ? "use $original;" : "use $original as $alias;"; } } return implode("\n", $uses); } /** * @param Closure|GlobalFunction|Method $function */ public function printParameters($function, PhpNamespace $namespace = null, int $column = 0): string { $params = []; $list = $function->getParameters(); $special = false; foreach ($list as $param) { $variadic = $function->isVariadic() && $param === end($list); $type = $param->getType(); $promoted = $param instanceof PromotedParameter ? $param : null; $params[] = ($promoted ? Helpers::formatDocComment((string) $promoted->getComment()) : '') . ($attrs = self::printAttributes($param->getAttributes(), $namespace, true)) . ($promoted ? ($promoted->getVisibility() ?: 'public') . ' ' : '') . ltrim($this->printType($type, $param->isNullable(), $namespace) . ' ') . ($param->isReference() ? '&' : '') . ($variadic ? '...' : '') . '$' . $param->getName() . ($param->hasDefaultValue() && !$variadic ? ' = ' . $this->dump($param->getDefaultValue()) : ''); $special = $special || $promoted || $attrs; } $line = implode(', ', $params); return count($params) > 1 && ($special || strlen($line) + $column > (new Dumper)->wrapLength) ? "(\n" . $this->indent(implode(",\n", $params)) . ($special ? ',' : '') . "\n)" : "($line)"; } public function printType(?string $type, bool $nullable = false, PhpNamespace $namespace = null): string { if ($type === null) { return ''; } if ($this->resolveTypes && $namespace) { $type = $namespace->unresolveUnionType($type); } if ($nullable && strcasecmp($type, 'mixed')) { $type = strpos($type, '|') === false ? '?' . $type : $type . '|null'; } return $type; } /** * @param Closure|GlobalFunction|Method $function */ private function printReturnType($function, ?PhpNamespace $namespace): string { return ($tmp = $this->printType($function->getReturnType(), $function->isReturnNullable(), $namespace)) ? $this->returnTypeColon . $tmp : ''; } private function printAttributes(array $attrs, ?PhpNamespace $namespace, bool $inline = false): string { if (!$attrs) { return ''; } $items = []; foreach ($attrs as $attr) { $args = (new Dumper)->format('...?:', $attr->getArguments()); $items[] = $this->printType($attr->getName(), false, $namespace) . ($args ? "($args)" : ''); } return $inline ? '#[' . implode(', ', $items) . '] ' : '#[' . implode("]\n#[", $items) . "]\n"; } private function joinProperties(array $props) { return $this->linesBetweenProperties ? implode(str_repeat("\n", $this->linesBetweenProperties), $props) : preg_replace('#^(\w.*\n)\n(?=\w.*;)#m', '$1', implode("\n", $props)); } }