Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: update Parser to improve TypeScript interface generation fo… #10

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 79 additions & 73 deletions ParseTypeScript/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@

namespace Irontec\TypeScriptGeneratorBundle\ParseTypeScript;

use Irontec\TypeScriptGeneratorBundle\Attribute\TypeScriptMe;
use ReflectionNamedType;
use CodeIgniter\CLI\CLI;

/**
* @author Irontec <[email protected]>
Expand All @@ -15,76 +14,63 @@
*/
class Parser
{
public const PARAM_UNKNOWN = 'unknown';

private TypeScriptBaseInterface $currentInterface;
const PARAM_UNKNOWN = 'unknown';

/**
* @var TypeScriptBaseInterface
*/
private $currentInterface;

/**
* @var TypeScriptBaseInterface[]
*/
private array $output = [];
private $output = [];

/**
* @var \ReflectionProperty[]
*/
private array $properties = [];
private $properties = [];

public function __construct(string $filename)
public function __construct(string $filePath)
{
if (!class_exists($this->getClassFromFile($filename))) {
if (!class_exists($this->getClassFromFile($filePath))) {
CLI::write('Class does not exist for file: ' . $filePath);
return;
}

$typeScriptMeFound = false;

$reflectionClass = new \ReflectionClass($this->getClassFromFile($filename));

if (0 < count($reflectionClass->getAttributes(TypeScriptMe::class))) {
$typeScriptMeFound = true;
} else {
$source = file_get_contents($filename);

if (!is_string($source)) {
throw new \ErrorException("Failure reading `{$filename}`.");
}

$tokens = token_get_all($source);
$comment = [T_COMMENT, T_DOC_COMMENT];
$source = file_get_contents($filePath);
$tokens = token_get_all($source);
$comment = [T_COMMENT, T_DOC_COMMENT];

foreach ($tokens as $token) {
if (is_array($token) && in_array((int) $token[0], $comment)) {
if (strpos($token[1], 'TypeScriptMe') !== false) {
$typeScriptMeFound = true;
break;
}
$invalid = false;
foreach ($tokens as $token) {
if (is_array($token) && in_array((int) $token[0], $comment)) {
if (strpos($token[1], 'TypeScriptMe') !== false) {
$invalid = true;
break;
}
}
}

if (false === $typeScriptMeFound) {
if ($invalid === false) {
CLI::write('TypeScriptMe annotation not found in file: ' . $filePath);
return;
}

$reflectionClass = new \ReflectionClass($this->getClassFromFile($filePath));
$this->currentInterface = new TypeScriptBaseInterface($reflectionClass->getShortName());

$this->properties = $reflectionClass->getProperties();

if (empty($this->properties)) {
CLI::write('No properties found in class: ' . $reflectionClass->getName());
return;
}

$matches = [];

/**
* @var \ReflectionProperty $property
*/
foreach ($this->properties as $property) {

$type = $this->parsePhpDocForProperty($property);
$docComment = $property->getDocComment();
$isNull = false;

if (preg_match('/nullable=true/i', (string) $docComment, $matches)) {
if (preg_match('/nullable=true/i', $property->getDocComment(), $matches)) {
$isNull = true;
}

Expand All @@ -96,39 +82,50 @@ public function __construct(string $filename)
}

$this->output[] = $this->currentInterface;
CLI::write('Interface generated for class: ' . $reflectionClass->getName());
}

/**
* Obtiene el raw de la interaface de Typescript
* @return string
*/
public function getOutput(): string
{
return implode(PHP_EOL . PHP_EOL, array_map(function ($item) {return (string) $item;}, $this->output));
return implode(PHP_EOL . PHP_EOL, array_map(function ($item) { return (string) $item;}, $this->output));
}

/**
* Obtiene la interface que se esta usando actualmente
*
* @return TypeScriptBaseInterface
*/
public function getCurrentInterface(): TypeScriptBaseInterface
public function getCurrentInterface()
{
return $this->currentInterface;
}

/**
* Obtiene el tipo de la variable en Typescript, segun el tipo de la propiedad
*
* @param \ReflectionProperty $property
* @return string
*/
private function getTypescriptPropertyByPropertyType(\ReflectionProperty $property): string
{
$type = $property->getType();

if ($type instanceof ReflectionNamedType) {
$name = $type->getName();
if ($type instanceof \ReflectionUnionType) {
// Union type handling
$types = $type->getTypes();
$typeNames = array_map(fn($t) => $t->getName(), $types);
// Handle union types as needed, e.g., return a combined type or choose one
// For simplicity, let's assume we return the first type for now
$name = $typeNames[0];
} else {
throw new \ErrorException('Unexpected type.');
$name = $type->getName();
}

$expl = explode('\\', $name);

if (sizeof($expl) >= 2) {
$result = end($expl);

Expand All @@ -148,23 +145,26 @@ private function getTypescriptPropertyByPropertyType(\ReflectionProperty $proper

/**
* Obtiene el tipo de la propiedad en formato Typescript, en base a los comentarios/anotaciones
*
* @param \ReflectionProperty $property
* @return string
*/
private function parsePhpDocForProperty(\ReflectionProperty $property): string
{

$result = self::PARAM_UNKNOWN;

if (is_null($property->getType()) !== true) {
return $this->getTypescriptPropertyByPropertyType($property);
}

$docComment = $property->getDocComment();

if (!is_string($docComment)) {
if (is_null($property->getDocComment()) === true) {
return $result;
}

$matches = [];
$docComment = $property->getDocComment();

$matches = [];
if (preg_match('/@var (.*)/i', $docComment, $matches)) {
if (preg_match('/@var[ \t]+([a-z0-9]+)/i', $docComment, $matches)) {
$t = trim(strtolower($matches[1]));
Expand All @@ -182,20 +182,22 @@ private function parsePhpDocForProperty(\ReflectionProperty $property): string
}
}

var_dump($property);
var_dump($result);
die;
return $result;

}

/**
* En base a un tipo del tipado de la propiedad, se obtiene el correspondiente tipo en Typescript
* @param string $type
* @return string
*/
private function getTypescriptProperty(string $type): string
{
$type = preg_replace('/[^A-Za-z0-9\-]/', '', $type);

if (!is_string($type)) {
throw new \ErrorException('Unexpected type.');
}

$type = preg_replace('/[^A-Za-z0-9\-]/', '', $type);
$type = strtolower($type);

$result = self::PARAM_UNKNOWN;
Expand All @@ -213,15 +215,21 @@ private function getTypescriptProperty(string $type): string
}

return $result;

}

/**
* Obtiene el nombre de la entidad relacionada, si esta en un comentario con el formato "@var \App\Entity\Test"
*
* @param string $type
* @return string
*/
private function getRelationProperty(string $type): string
private function getRelationProperty($type): string
{
$result = self::PARAM_UNKNOWN;

var_dump($type);die('getRelationProperty');

$result = self::PARAM_UNKNOWN;
$matches = [];

if (preg_match('/@var \SApp\SEntity\S([a-zA-Z]+)(\[\])?/i', $type, $matches)) {
Expand All @@ -234,23 +242,27 @@ private function getRelationProperty(string $type): string
}

return $result;

}

/**
* Obtiene el nombre de la entidad relacionada, en base a una anotación de doctrine.
* @param string $type
* @return string
*/
private function getRelationCollectionProperty(\ReflectionProperty $type): string
private function getRelationCollectionProperty($type): string
{

$classRelations = [
'Doctrine\ORM\Mapping\ManyToMany',
'Doctrine\ORM\Mapping\OneToMany',
'Doctrine\ORM\Mapping\ManyToOne'
];

if (empty($type->getAttributes()) === false) {
if (method_exists($type, 'getAttributes') && empty($type->getAttributes()) === false) {
$entity = '';
$collection = '[]';

/** @var \ReflectionProperty $type */
foreach ($type->getAttributes() as $att) {
if (strpos($att->getName(), 'OneToOne') !== false || strpos($type, 'ManyToOne') !== false) {
$collection = '';
Expand All @@ -271,19 +283,15 @@ private function getRelationCollectionProperty(\ReflectionProperty $type): strin

$type = $type->getDocComment();

if (!is_string($type)) {
throw new \ErrorException('Unexpected type.');
}

$result = self::PARAM_UNKNOWN;

$matches = [];

$regex = [
$regex = array(
'/targetEntity="([a-zA-Z]+)"/i',
'/targetEntity=([a-zA-Z]+)::class/i',
'/targetEntity="([a-zA-Z]+)\\\\([a-zA-Z]+)\\\\([a-zA-Z]+)"/i',
];
);

foreach ($regex as $reg) {
if (preg_match($reg, $type, $matches)) {
Expand All @@ -299,22 +307,20 @@ private function getRelationCollectionProperty(\ReflectionProperty $type): strin
}

return $result;

}

/**
* Obtiene el namespace y nombre de clase, de un archivo PHP
*
* https://stackoverflow.com/a/7153391
* @param string $file
* @return string
*/
private function getClassFromFile(string $filename): string
private function getClassFromFile(string $file): string
{
$code = file_get_contents($filename);

if (!is_string($code)) {
throw new \ErrorException("Failure reading `{$filename}`.");
}

$tokens = token_get_all($code);
$tokens = token_get_all(file_get_contents($file));
$count = count($tokens);

$namespace = '';
Expand Down