Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenvanassche committed Jun 21, 2024
1 parent 241795f commit f75d83d
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 7 deletions.
134 changes: 132 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1032,8 +1032,7 @@ class PickNode implements TypeScriptNode, TypeScriptNamedNode
```

The write method is responsible for transforming the TypeScript node to a string, the `WritingContext` object is passed
to
lower level TypeScript nodes to reference other TypeScript types and can generally be ignored.
to lower level TypeScript nodes to reference other TypeScript types and can generally be ignored.

Some TypeScript nodes represent a type with a name like an interface, enum, ... these nodes should implement
the `TypeScriptNamedNode` interface. The `getName` method should return the name of the TypeScript node so that it can
Expand Down Expand Up @@ -1096,8 +1095,139 @@ class TypeScriptGeneric implements TypeScriptForwardingNamedNode, TypeScriptNode
}
```

It contains a single node property `type` and an iterable property `genericTypes`. From now on the package will visit
the nodes within these properties.

### Visiting TypeScript nodes

When working with TypeScript nodes in a class property processor or a custom TypeScript node, it might be necessary to
visit and alter nodes in the tree. The `Visitor` class can be used to visit such a tree of TypeScript
nodes.

The visitor will start in a node and then traverse the tree of TypeScript nodes, it is possible to register a `before`
and `after` callback for each node it visits. The `before` callback is called before visiting the children of a node and
the `after` callback is called after visiting the children of a node.

```php
use Spatie\TypeScriptTransformer\Visitor\Visitor;

Visitor::create()
->before(function (TypeScriptNode $node){
echo 'Before visiting ' . $node::class . PHP_EOL;
})
->after(function (TypeScriptNode $node) {
echo 'After visiting ' . $node::class . PHP_EOL;
})
->execute($rootNode);
```

When running the visitor on the following node:

```php
$rootNode = new TypeScriptUnion([
new TypeScriptString(),
new TypeScriptNumber(),
]);
```

The output will be (redacted for readability):

```
Before visiting TypeScriptUnion
Before visiting TypeScriptString
After visiting TypeScriptString
Before visiting TypeScriptNumber
After visiting TypeScriptNumber
After visiting TypeScriptUnion
```

By default, the visitor will visit the tree of nodes and run the callback on each node within the tree. It is possible
to limit the types of nodes the callback runs on:

```php
Visitor::create()
->after(function (TypeScriptUnion $node, [TypeScriptUnion::class]) {
// Do something with TypeScriptUnion nodes
})
->execute($rootNode);
```

When not returning a TypeScript node from the callback, the visitor will continue traversing the tree. It is possible to
replace a node in the tree like this:

```php
use Spatie\TypeScriptTransformer\Visitor\VisitorOperation;

Visitor::create()
->after(function (TypeScriptUnion $node, [TypeScriptUnion::class]) {
if(count($node->types) === 1) {
return VisitorOperation::replace(array_values($node->types)[0]);
}
})
->execute($rootNode);
```

The visitor above will replace all union nodes with a single type with that type.

It is also possible to remove a node from the tree:

```php
Visitor::create()
->after(function (TypeScriptString $node, [TypeScriptString::class]) {
return VisitorOperation::remove();
})
->execute($rootNode);
```

### Hooking into TypeScript transformer

Every time the TypeScript transformer is executed, it will go through a series of steps, it is possible to run a visitor
in between some of these steps.

The steps look as following:

1. Running of the TypeProviders creating a collection of Transformed types
2. Possible hooking point: `providedVisitorHook`
3. Connecting references between Transformed types
4. Possible hooking point: `connectedVisitorHook`
5. Create a collection of WriteableFiles
6. Write those files to disk
7. Format the files

The two hooking points above can be used to run a visitor on the collection of Transformed types:

```php
use Spatie\TypeScriptTransformer\Visitor\VisitorClosureType;

$config->providedVisitorHook(
fn(TransformedCollection $collection) => Visitor::create()->execute($collection),
[TypeScriptUnion::class],
VisitorClosureType::Before
);

$config->connectedVisitorHook(
fn(TransformedCollection $collection) => Visitor::create()->execute($collection),
[TypeScriptUnion::class],
VisitorClosureType::Before
);
```

Running visitors as an after hook is also possible:

```php
$config->providedVisitorHook(
fn(TransformedCollection $collection) => Visitor::create()->execute($collection),
[TypeScriptUnion::class],
VisitorClosureType::After
);

$config->connectedVisitorHook(
fn(TransformedCollection $collection) => Visitor::create()->execute($collection),
[TypeScriptUnion::class],
VisitorClosureType::After
);
```

### Building your own Writer

Writers are responsible for writing out the TypeScript types, the package comes with three writers:
Expand Down
4 changes: 2 additions & 2 deletions src/TypeScriptTransformerConfigFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public function formatter(Formatter|string $formatter): self
return $this;
}

public function providedVisitor(
public function providedVisitorHook(
VisitorClosure|Closure $visitor,
?array $allowedNodes = null,
VisitorClosureType $type = VisitorClosureType::Before
Expand All @@ -125,7 +125,7 @@ public function providedVisitor(
return $this;
}

public function connectedVisitor(
public function connectedVisitorHook(
VisitorClosure|Closure $visitor,
?array $allowedNodes = null,
VisitorClosureType $type = VisitorClosureType::Before
Expand Down
2 changes: 1 addition & 1 deletion src/Visitor/Visitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function after(
Closure $closure,
?array $allowedNodes = null,
): self {
$this->closures[] = new VisitorClosure($closure, $allowedNodes, VisitorClosureType::Before);
$this->closures[] = new VisitorClosure($closure, $allowedNodes, VisitorClosureType::After);

return $this;
}
Expand Down
27 changes: 27 additions & 0 deletions tests/Visitor/VisitorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,30 @@
expect($visited)->toBe($unionNode);
expect($unionNode->types)->toEqual([$stringNode, new TypeScriptBoolean()]);
});

it('will execute a before and after closure correctly', function () {
$rootNode = new TypeScriptUnion([
new TypeScriptString(),
new TypeScriptNumber(),
]);

$order = [];

Visitor::create()
->before(function (TypeScriptNode $node) use (&$order) {
$order[] = 'before '. $node::class;
})
->after(function (TypeScriptNode $node) use (&$order) {
$order[] = 'after '. $node::class;
})
->execute($rootNode);

expect($order)->toEqual([
'before '. TypeScriptUnion::class,
'before '. TypeScriptString::class,
'after '. TypeScriptString::class,
'before '. TypeScriptNumber::class,
'after '. TypeScriptNumber::class,
'after '. TypeScriptUnion::class,
]);
});
4 changes: 2 additions & 2 deletions tests/VisitorClosures.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
])
)))
->writer($writer = new MemoryWriter())
->providedVisitor(function (TypeScriptObject $reference) {
->providedVisitorHook(function (TypeScriptObject $reference) {
return VisitorOperation::replace(new TypeScriptString());
}, [TypeScriptObject::class])
->get();
Expand All @@ -40,7 +40,7 @@
])
)))
->writer($writer = new MemoryWriter())
->connectedVisitor(function (TypeScriptObject $reference) {
->connectedVisitorHook(function (TypeScriptObject $reference) {
return VisitorOperation::replace(new TypeScriptString());
}, [TypeScriptObject::class])
->get();
Expand Down

0 comments on commit f75d83d

Please sign in to comment.