Skip to content
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
60 changes: 59 additions & 1 deletion src/lib/Translation/Extractor/JavaScriptFileVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
use Peast\Syntax\Node;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;
use RuntimeException;
use SplFileInfo;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Process;
use Twig\Node\Node as TwigNode;

final class JavaScriptFileVisitor implements FileVisitorInterface, LoggerAwareInterface
Expand Down Expand Up @@ -57,6 +60,10 @@
try {
$source = file_get_contents($file->getRealPath());

if (str_ends_with($file->getRealPath(), '.ts')) {
$source = $this->transpileTypeScript($source);

Check failure on line 64 in src/lib/Translation/Extractor/JavaScriptFileVisitor.php

View workflow job for this annotation

GitHub Actions / Tests (8.3)

Parameter #1 $source of method Ibexa\AdminUi\Translation\Extractor\JavaScriptFileVisitor::transpileTypeScript() expects string, string|false given.

Check failure on line 64 in src/lib/Translation/Extractor/JavaScriptFileVisitor.php

View workflow job for this annotation

GitHub Actions / Tests (8.4)

Parameter #1 $source of method Ibexa\AdminUi\Translation\Extractor\JavaScriptFileVisitor::transpileTypeScript() expects string, string|false given.
}

$parser = Peast::latest($source, [
'comments' => true,
'jsx' => true,
Expand All @@ -73,6 +80,14 @@
$e->getPosition()->getColumn()
));

return;
} catch (RuntimeException $e) {
$this->logger?->error(sprintf(
'Unable to parse file %s: %s',
$file->getRealPath(),
$e->getMessage()
));

return;
}

Expand Down Expand Up @@ -204,8 +219,51 @@
return null;
}

/**
* Strips TypeScript syntax (type annotations, interfaces, generics, etc.) so the
* resulting source can be parsed by Peast, which only understands plain ECMAScript.
*/
private function transpileTypeScript(string $source): string
{
$process = new Process([
$this->findEsbuildBinary(),
'--loader=ts',
'--format=esm',
'--target=esnext',
]);
$process->setInput($source);
$process->run();

if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf(

Check warning on line 238 in src/lib/Translation/Extractor/JavaScriptFileVisitor.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define and throw a dedicated exception instead of using a generic one.

See more on https://sonarcloud.io/project/issues?id=ibexa_admin-ui&issues=AZ8izVNnuMD-_Ulj2tJW&open=AZ8izVNnuMD-_Ulj2tJW&pullRequest=1955
'Unable to transpile TypeScript source: %s',
$process->getErrorOutput()
));
}

return $process->getOutput();
}

private function findEsbuildBinary(): string
{
$candidate = getcwd() . '/node_modules/.bin/esbuild';
if (is_executable($candidate)) {
return $candidate;
}

$binary = (new ExecutableFinder())->find('esbuild');
if ($binary !== null) {
return $binary;
}

throw new RuntimeException(

Check warning on line 259 in src/lib/Translation/Extractor/JavaScriptFileVisitor.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define and throw a dedicated exception instead of using a generic one.

See more on https://sonarcloud.io/project/issues?id=ibexa_admin-ui&issues=AZ8izVNnuMD-_Ulj2tJX&open=AZ8izVNnuMD-_Ulj2tJX&pullRequest=1955
'Unable to locate the "esbuild" executable required to parse TypeScript files. ' .
'Run this command from the project root or install esbuild (yarn add -D esbuild).'
);
}

private function supports(SplFileInfo $file): bool
{
return str_ends_with($file->getRealPath(), '.js') && !str_ends_with($file->getRealPath(), '.min.js');
return (str_ends_with($file->getRealPath(), '.js') || str_ends_with($file->getRealPath(), '.ts')) && !str_ends_with($file->getRealPath(), '.min.js');
}
}
Loading