Skip to content

Commit

Permalink
0.4.2
Browse files Browse the repository at this point in the history
  • Loading branch information
Brainshaker95 authored Oct 15, 2023
2 parents 1f8e047 + 6202f41 commit a033736
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 17 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,16 +345,17 @@ final class MyService
* Multiline `@deprecated` and `@template` descriptions cannot contain empty lines between paragraphs. Only a single new line can be used as a separator. All other lines will be considered as part of the property description.
* No support for nested readonly types for array shapes. Only the array property itself will be marked as readonly, which would technically allow nested properties to be modified.
* No support for array shapes where some items have keys and some do not.
* No support for `value-of` on backed enums.
* No support for automatically removing generated TypeScript files when corresponding TypeScriptable is deleted. (See: https://github.com/Brainshaker95/php-to-ts-bundle/issues/27)

<p align="right"><a href="#top" title="Back to top">&nbsp;&nbsp;&nbsp;⬆&nbsp;&nbsp;&nbsp;</a></p>

## 🔨 TODOs / Roadmap

* Document example TypeScriptable class
* Document example TypeScriptable enum
* Document usage of Hidden attribute
* Document TsController usage
* Fix conversion of generic types like key-of or value-of
* Document usage of TsController

<p align="right"><a href="#top" title="Back to top">&nbsp;&nbsp;&nbsp;⬆&nbsp;&nbsp;&nbsp;</a></p>

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "brainshaker95/php-to-ts-bundle",
"description": "Convert PHP model classes to TypeScript interfaces",
"version": "0.4.1",
"version": "0.4.2",
"keywords": [
"bundle",
"symfony",
Expand Down
36 changes: 31 additions & 5 deletions src/Model/Ast/Type/GenericTypeNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use function array_key_exists;
use function array_map;
use function count;
use function current;
use function implode;
use function sprintf;

Expand All @@ -42,20 +43,45 @@ public function toString(): string
{
$type = $this->type->name;

if (array_key_exists($type, array_flip(Converter::SIMPLE_TYPES))) {
return $type;
}

$genericTypeCount = count($this->genericTypes);

if ($type === Converter::TYPE_KEY_OF) {
if ($genericTypeCount !== 1) {
throw new UnsupportedNodeException(sprintf(
'Expected generic key-of node to contain 1 sub type, %d given.',
$genericTypeCount,
));
}

return 'keyof ' . current($this->genericTypes);
}

if ($type === Converter::TYPE_VALUE_OF) {
if ($genericTypeCount !== 1) {
throw new UnsupportedNodeException(sprintf(
'Expected generic value-of node to contain 1 sub type, %d given.',
$genericTypeCount,
));
}

return 'ValueOf<' . current($this->genericTypes) . '>';
}

if ($type === TsProperty::TYPE_UNKNOWN . '[]') {
$genericTypeCount = count($this->genericTypes);
$type = 'Array';
$type = 'Array';

if ($genericTypeCount === 2) {
$type = 'Record';
} elseif ($genericTypeCount !== 1) {
throw new UnsupportedNodeException(sprintf(
'Expected generic array node to contain 1 or 2 sub types, %s given.',
'Expected generic array node to contain 1 or 2 sub types, %d given.',
$genericTypeCount,
));
}
} elseif (array_key_exists($type, array_flip(Converter::NON_ITERABLE_TYPE_MAP))) {
return $type;
}

return $type . '<' . implode(', ', $this->genericTypes) . '>';
Expand Down
4 changes: 2 additions & 2 deletions src/Model/Ast/Type/IdentifierTypeNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ public static function fromPhpStan(PHPStanNode $node): self
$name = $node->name;
$type = self::TYPE_DEFAULT;

if (array_key_exists($name, Converter::NON_ITERABLE_TYPE_MAP)) {
$name = Converter::NON_ITERABLE_TYPE_MAP[$name];
if (array_key_exists($name, Converter::SIMPLE_TYPES)) {
$name = Converter::SIMPLE_TYPES[$name];
} elseif (in_array($name, Converter::ITERABLE_TYPES, true)) {
$name = TsProperty::TYPE_UNKNOWN . '[]';
} elseif ($name === '\stdClass' || $name === 'stdClass') {
Expand Down
2 changes: 1 addition & 1 deletion src/Model/TsEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public function toString(): string

return $string
->append('} satisfies Record<string, ')
->append(Converter::NON_ITERABLE_TYPE_MAP[$this->scalarType])
->append(Converter::SIMPLE_TYPES[$this->scalarType])
->append('>;')
->append(PHP_EOL)
->append(PHP_EOL)
Expand Down
13 changes: 11 additions & 2 deletions src/Model/TsInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,15 @@ public function getSortedProperties(?array $sortStrategies = null): array
*/
private function getImports(): array
{
$imports = $this->parentName ? [$this->parentName] : [];
$genericNames = TsGeneric::getNames($this->generics);
$imports = $this->parentName ? [$this->parentName] : [];
$genericNames = TsGeneric::getNames($this->generics);
$doRequireValueOf = false;

foreach ($this->properties as $property) {
if (!$doRequireValueOf && $property->doesRequireValueOf) {
$doRequireValueOf = true;
}

foreach ($property->classIdentifiers as $classIdentifier) {
if (!in_array($classIdentifier, $imports, true)
&& !in_array($classIdentifier, $genericNames, true)) {
Expand All @@ -179,6 +184,10 @@ private function getImports(): array
}
}

if ($doRequireValueOf) {
$imports[] = 'ValueOf';
}

natcasesort($imports);

$fileNameStrategy = $this->config?->getFileNameStrategy() ?? C::FILE_NAME_STRATEGY_DEFAULT;
Expand Down
1 change: 1 addition & 0 deletions src/Model/TsProperty.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public function __construct(
public readonly bool $isEnumProperty = false,
public readonly array $classIdentifiers = [],
public readonly array $generics = [],
public readonly bool $doesRequireValueOf = false,
public readonly ?string $description = null,
public bool|string|null $deprecation = null,
public ?Config $config = null,
Expand Down
28 changes: 27 additions & 1 deletion src/Service/Dumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Brainshaker95\PhpToTsBundle\Service;

use Brainshaker95\PhpToTsBundle\Interface\Config;
use Brainshaker95\PhpToTsBundle\Model\Config\FileType;
use Brainshaker95\PhpToTsBundle\Model\TsEnum;
use Brainshaker95\PhpToTsBundle\Model\TsInterface;
use Brainshaker95\PhpToTsBundle\Service\Traits\HasConfiguration;
Expand Down Expand Up @@ -106,18 +107,43 @@ public function dumpFile(
return;
}

$pathPrefix = $config->getOutputDir() . DIRECTORY_SEPARATOR;
$pathPrefix = $config->getOutputDir() . DIRECTORY_SEPARATOR;
$doRequireValueOf = false;

foreach ($tsInterfaces as $tsInterface) {
$fileName = $tsInterface->getFileName();
$path = $this->filesystem->makeAbsolute($pathPrefix . $fileName);

$this->filesystem->dumpFile($path, $tsInterface->toString() . PHP_EOL);

if (!$doRequireValueOf) {
foreach ($tsInterface->properties as $property) {
if ($property->doesRequireValueOf) {
$doRequireValueOf = true;

break;
}
}
}

if ($successCallback) {
$successCallback($path, $tsInterface);
}
}

if (!$doRequireValueOf) {
return;
}

$valueOfPath = $pathPrefix . (new ($config->getFileNameStrategy())())->getName('valueOf') . '.ts';
$isModule = $config->getFileType() === FileType::TYPE_MODULE;

$this->filesystem->dumpFile(
$valueOfPath,
($isModule ? 'export' : 'declare')
. ' type ValueOf<T> = T[keyof T];'
. PHP_EOL,
);
}

/**
Expand Down
32 changes: 29 additions & 3 deletions src/Tool/Converter.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ abstract class Converter
public const TYPE_TRUTHY_STRING = 'truthy-string';
public const TYPE_VALUE_OF = 'value-of';

public const NON_ITERABLE_TYPE_MAP = [
public const SIMPLE_TYPES = [
self::TYPE_ARRAY_KEY => TsProperty::TYPE_STRING,
self::TYPE_BOOL => TsProperty::TYPE_BOOLEAN,
self::TYPE_BOOLEAN => TsProperty::TYPE_BOOLEAN,
Expand All @@ -110,7 +110,6 @@ abstract class Converter
self::TYPE_INT => TsProperty::TYPE_NUMBER,
self::TYPE_INT_MASK => TsProperty::TYPE_NUMBER,
self::TYPE_INTEGER => TsProperty::TYPE_NUMBER,
self::TYPE_KEY_OF => TsProperty::TYPE_STRING,
self::TYPE_MIXED => TsProperty::TYPE_UNKNOWN,
self::TYPE_NULL => TsProperty::TYPE_NULL,
self::TYPE_LITERAL_STRING => TsProperty::TYPE_STRING,
Expand All @@ -127,7 +126,6 @@ abstract class Converter
self::TYPE_THIS => TsProperty::TYPE_THIS,
self::TYPE_TRUE => TsProperty::TYPE_TRUE,
self::TYPE_TRUTHY_STRING => TsProperty::TYPE_STRING,
self::TYPE_VALUE_OF => TsProperty::TYPE_STRING,
];

public const ITERABLE_TYPES = [
Expand Down Expand Up @@ -256,6 +254,10 @@ final public static function toProperty(
static fn (string $classIdentifier) => !in_array($classIdentifier, $genericNames, true),
);

$doesRequireValueOf = $data['rootNode']
? self::getValueOfNodes([$data['rootNode']]) !== []
: false;

return new TsProperty(
name: $name,
type: $data['rootNode'] ?? TsProperty::TYPE_UNKNOWN,
Expand All @@ -264,6 +266,7 @@ final public static function toProperty(
isEnumProperty: $property instanceof EnumCase,
classIdentifiers: $classIdentifiers,
generics: $generics,
doesRequireValueOf: $doesRequireValueOf,
description: $data['description'] ?? null,
deprecation: isset($data['deprecatedNode']) ? ($data['deprecatedNode']->description ?: true) : null,
);
Expand Down Expand Up @@ -515,6 +518,29 @@ private static function getClassIdentifiers(array $nodes, array $identifiers = [
return $identifiers;
}

/**
* @param Node[] $nodes
* @param GenericTypeNode[] $valueOfNodes
*
* @return GenericTypeNode[]
*/
private static function getValueOfNodes(array $nodes, array $valueOfNodes = []): array
{
foreach ($nodes as $node) {
if ($node instanceof GenericTypeNode && $node->type->name === self::TYPE_VALUE_OF) {
$valueOfNodes[] = $node;
}

$nextLevelNodes = self::getNextLevelNodes($node);

if (count($nextLevelNodes)) {
$valueOfNodes = self::getValueOfNodes($nextLevelNodes, $valueOfNodes);
}
}

return $valueOfNodes;
}

/**
* @return string[]
*/
Expand Down

0 comments on commit a033736

Please sign in to comment.