diff --git a/.editorconfig b/.editorconfig index 650290f..1ac5d54 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,7 +7,7 @@ ij_php_align_assignments = true ij_php_align_class_constants = true ij_php_align_enum_cases = true ij_php_align_key_value_pairs = true -ij_php_align_multiline_parameters_in_calls = true +ij_php_align_multiline_parameters_in_calls = false ij_php_align_phpdoc_comments = true ij_php_align_phpdoc_param_names = true ij_php_blank_lines_before_package = 0 diff --git a/.gitignore b/.gitignore index 10e7225..82bd149 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ composer.lock # PHPUnit tests/coverage/ tests/cache/ +tests/tmp/ .phpunit.result.cache diff --git a/composer.json b/composer.json index 9f9036c..c99a8ca 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "okapi/code-transformer", "description": "PHP Code Transformer is a PHP library that allows you to modify and transform the source code of a loaded PHP class.", - "version": "1.2.0", + "version": "1.3.0", "type": "library", "homepage": "https://github.com/okapi-web/php-code-transformer", "license": "MIT", @@ -33,7 +33,7 @@ "roave/better-reflection": "^6.8" }, "require-dev": { - "phpunit/phpunit": "dev-main", + "phpunit/phpunit": "^10", "symfony/var-dumper": "^6.2" }, "autoload": { @@ -48,11 +48,5 @@ "psr-4": { "Okapi\\CodeTransformer\\Tests\\": "tests/" } - }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/WalterWoshid/phpunit" - } - ] + } } diff --git a/src/Core/AutoloadInterceptor/ClassLoader.php b/src/Core/AutoloadInterceptor/ClassLoader.php index e33e733..88aea7e 100644 --- a/src/Core/AutoloadInterceptor/ClassLoader.php +++ b/src/Core/AutoloadInterceptor/ClassLoader.php @@ -80,7 +80,7 @@ public function loadClass($namespacedClass): bool /** * Find the path to the file and match and apply the transformers. * - * @param $namespacedClass + * @param class-string $namespacedClass * * @return false|string * @@ -105,7 +105,6 @@ public function findFile($namespacedClass): false|string $filePath = Path::resolve($filePath); - // Query cache state $cacheState = $this->cacheStateManager->queryCacheState($filePath); @@ -145,11 +144,11 @@ protected function isInternal(string $namespacedClass): bool 'Okapi\\CodeTransformer\\', 'Okapi\\Path\\', 'Okapi\\Wildcards\\', - 'DI\\' + 'DI\\', ], [ - 'Okapi\\CodeTransformer\\Tests\\' - ] + 'Okapi\\CodeTransformer\\Tests\\', + ], ); } } diff --git a/src/Core/Cache/CacheState.php b/src/Core/Cache/CacheState.php index 2ecc0f7..546a1af 100644 --- a/src/Core/Cache/CacheState.php +++ b/src/Core/Cache/CacheState.php @@ -10,12 +10,15 @@ abstract class CacheState { public const TYPE = 'type'; + public const DATA = 'data'; public const ORIGINAL_FILE_PATH_KEY = 'originalFilePath'; + public const NAMESPACED_CLASS_KEY = 'namespacedClass'; public const MODIFICATION_TIME_KEY = 'modificationTime'; public string $originalFilePath; - public int $modificationTime; + protected string $namespacedClass; + protected int $modificationTime; /** * CacheState constructor. @@ -80,10 +83,31 @@ public function getRequiredKeys(): array { return [ static::ORIGINAL_FILE_PATH_KEY, + static::NAMESPACED_CLASS_KEY, static::MODIFICATION_TIME_KEY, ]; } + /** + * Create a cache state if it is valid. + * + * @param array $cacheStateArray + * + * @return self|null + */ + public function createIfValid(array $cacheStateArray): ?CacheState + { + if (!$this->valid($cacheStateArray)) { + // @codeCoverageIgnoreStart + return null; + // @codeCoverageIgnoreEnd + } + + $this->setData($cacheStateArray); + + return $this; + } + /** * Validate the cache state. * @@ -91,7 +115,7 @@ public function getRequiredKeys(): array * * @return bool */ - public function valid(array $cacheStateArray): bool + private function valid(array $cacheStateArray): bool { // Check if all required keys are present foreach ($this->getRequiredKeys() as $requiredKey) { @@ -110,7 +134,7 @@ public function valid(array $cacheStateArray): bool * * @param array $cacheStateArray */ - public function setData(array $cacheStateArray): void + private function setData(array $cacheStateArray): void { foreach ($cacheStateArray as $key => $value) { $this->{$key} = $value; @@ -124,6 +148,12 @@ public function setData(array $cacheStateArray): void */ public function isFresh(): bool { + if (!file_exists($this->originalFilePath)) { + // @codeCoverageIgnoreStart + return false; + // @codeCoverageIgnoreEnd + } + if (filemtime($this->originalFilePath) > $this->modificationTime) { return false; } @@ -137,161 +167,4 @@ public function isFresh(): bool * @return string|null */ abstract public function getFilePath(): ?string; - - // /** - // * CacheState constructor. - // * - // * @param string $originalFilePath - // * @param string $className - // * @param string|null $cachedFilePath - // * @param int|null $transformedTime - // * @param string[]|null $transformerFilePaths - // */ - // public function __construct( - // public string $originalFilePath, - // public string $className, - // public ?string $cachedFilePath, - // public ?int $transformedTime, - // public ?array $transformerFilePaths, - // ) {} - // - // /** - // * Use the cached file path if aspects have been applied. - // * Otherwise, use the original file path if no aspects have been applied. - // * - // * @return string - // */ - // public function getFilePath(): string - // { - // return $this->cachedFilePath ?? $this->originalFilePath; - // } - // - // - // - // - // /** - // * Get the cache state as an array. - // * - // * @return array - // */ - // public function toArray(): array - // { - // return [ - // $this->originalFilePath, - // $this->className, - // $this->cachedFilePath, - // $this->transformedTime, - // $this->transformerFilePaths, - // ]; - // } - // - // /** - // * Check if the cache is not outdated. - // * - // * @return bool - // */ - // public function isFresh(): bool - // { - // // @codeCoverageIgnoreStart - // // This should only happen if the project is misconfigured - // if ($this->checkInfiniteLoop()) { - // return false; - // } - // // @codeCoverageIgnoreEnd - // - // $allFiles = array_merge( - // [$this->originalFilePath], - // $this->transformerFilePaths, - // ); - // - // if ($this->checkFilesModified($allFiles)) { - // return false; - // } - // - // if ($this->cachedFilePath) { - // $allFiles[] = $this->cachedFilePath; - // } - // - // if (!$this->checkFilesExist($allFiles)) { - // return false; - // } - // - // if (!$this->checkTransformerCount()) { - // return false; - // } - // - // return true; - // } - // - // /** - // * Check if the cache is in an infinite loop. - // * - // * @return bool True if the cache is in an infinite loop - // */ - // protected function checkInfiniteLoop(): bool - // { - // if ($this->cachedFilePath !== null) { - // // Same original file and cached file - // if ($this->originalFilePath === $this->cachedFilePath) { - // return true; - // } - // } - // - // return false; - // } - // - // /** - // * Check if the files have been modified. - // * - // * @param string[] $files - // * - // * @return bool True if any file has been modified - // */ - // protected function checkFilesModified(array $files): bool - // { - // $lastModified = max(array_map('filemtime', $files)); - // if ($lastModified >= $this->transformedTime) { - // return true; - // } - // - // return false; - // } - // - // /** - // * Check if the files exist. - // * - // * @param string[] $files - // * - // * @return bool True if all files exist - // */ - // protected function checkFilesExist(array $files): bool - // { - // // Check if the cache file exists - // foreach ($files as $file) { - // if (!file_exists($file)) { - // return false; - // } - // } - // - // return true; - // } - // - // /** - // * Check if the transformer count is the same. - // * - // * @return bool True if the count is the same - // */ - // protected function checkTransformerCount(): bool - // { - // // Checking the count alone should be enough - // $cachedTransformerCount = count($this->transformerFilePaths); - // $currentTransformerCount = count( - // $this->transformerMatcher->match($this->className), - // ); - // if ($cachedTransformerCount !== $currentTransformerCount) { - // return false; - // } - // - // return true; - // } } diff --git a/src/Core/Cache/CacheState/EmptyResultCacheState.php b/src/Core/Cache/CacheState/EmptyResultCacheState.php index 779b16c..cbd783d 100644 --- a/src/Core/Cache/CacheState/EmptyResultCacheState.php +++ b/src/Core/Cache/CacheState/EmptyResultCacheState.php @@ -9,10 +9,6 @@ * * This class is used to represent an empty result cache state, which means that * the class was not matched by any transformer. - * - * @todo: I think when a transformer is changed, the cache state should be - * invalidated. This is not currently the case. Maybe clear the whole cache - * when a transformer is changed? */ class EmptyResultCacheState extends CacheState { diff --git a/src/Core/Cache/CacheState/TransformedCacheState.php b/src/Core/Cache/CacheState/TransformedCacheState.php index f9b3985..0eb4841 100644 --- a/src/Core/Cache/CacheState/TransformedCacheState.php +++ b/src/Core/Cache/CacheState/TransformedCacheState.php @@ -1,5 +1,5 @@ transformedFilePath)) { + return false; + } + // Check if any of the transformer files have been modified or deleted foreach ($this->transformerFilePaths as $transformerFilePath) { if (!file_exists($transformerFilePath)) { @@ -48,15 +52,6 @@ public function isFresh(): bool return false; // @codeCoverageIgnoreEnd } - - if (filemtime($transformerFilePath) > $this->modificationTime) { - return false; - } - } - - // Check if the transformed file has been deleted - if (!file_exists($this->transformedFilePath)) { - return false; } return true; diff --git a/src/Core/Cache/CacheStateFactory.php b/src/Core/Cache/CacheStateFactory.php index a3a9bdf..ffbc1e1 100644 --- a/src/Core/Cache/CacheStateFactory.php +++ b/src/Core/Cache/CacheStateFactory.php @@ -50,23 +50,21 @@ public function createCacheStates(array $cacheStatesArray): array // Instantiate cache state try { /** @var CacheState $cacheState */ - $cacheState = DI::make(self::CACHE_STATE_MAP[$type]); + $cacheState = DI::make(static::CACHE_STATE_MAP[$type]); // @codeCoverageIgnoreStart } catch (TypeError) { continue; } // @codeCoverageIgnoreEnd - // Validate cache state - if (!$cacheState->valid($cacheStateArray)) { + // Create if valid + $cacheState = $cacheState->createIfValid($cacheStateArray); + if (!$cacheState) { // @codeCoverageIgnoreStart continue; // @codeCoverageIgnoreEnd } - // Set cache state data - $cacheState->setData($cacheStateArray); - // Add cache state to array $cacheStateObjects[$cacheState->originalFilePath] = $cacheState; } diff --git a/src/Core/Cache/CacheStateManager.php b/src/Core/Cache/CacheStateManager.php index c66bb04..d5d8fcb 100644 --- a/src/Core/Cache/CacheStateManager.php +++ b/src/Core/Cache/CacheStateManager.php @@ -57,9 +57,16 @@ class CacheStateManager implements ServiceInterface /** * Cached metadata for the transformation state of a file. * - * @var array The key is the original file path. + * @var array */ - public array $cacheState = []; + private array $cacheState = []; + + /** + * New metadata for the transformation state of a file. + * + * @var array + */ + private array $newCacheState = []; // region Initialization @@ -121,8 +128,7 @@ private function loadCacheState(): void $cacheStatesArray = eval($cacheFileContent); // Check the hash - $transformers = $this->transformerManager->getTransformers(); - $hash = md5(serialize($transformers)); + $hash = $this->getHash(); if (!isset($cacheStatesArray[static::HASH]) || $cacheStatesArray[static::HASH] !== $hash ) { @@ -136,6 +142,17 @@ private function loadCacheState(): void $this->cacheState = $cacheStates; } + /** + * Get the hash of the transformers. + * + * @return string + */ + protected function getHash(): string + { + $transformers = $this->transformerManager->getTransformers(); + return md5(serialize($transformers)); + } + // endregion // region Destructor @@ -155,7 +172,7 @@ public function __destruct() */ private function saveCacheState(): void { - if (empty($this->cacheState)) { + if (empty($this->newCacheState)) { // @codeCoverageIgnoreStart return; // @codeCoverageIgnoreEnd @@ -167,12 +184,11 @@ private function saveCacheState(): void // Create the cache state array $cacheStateArray = array_map( fn (CacheState $cacheState) => $cacheState->toArray(), - $this->cacheState, + array_merge($this->newCacheState, $this->cacheState), ); // Set the hash - $transformers = $this->transformerManager->getTransformers(); - $cacheStateArray[static::HASH] = md5(serialize($transformers)); + $cacheStateArray[static::HASH] = $this->getHash(); // Serialize the cache state $phpCode .= var_export($cacheStateArray, true); @@ -223,6 +239,6 @@ public function setCacheState( string $filePath, CacheState $cacheState, ): void { - $this->cacheState[$filePath] = $cacheState; + $this->newCacheState[$filePath] = $cacheState; } } diff --git a/src/Core/Container/TransformerManager.php b/src/Core/Container/TransformerManager.php index 4414fb2..bdcc64f 100644 --- a/src/Core/Container/TransformerManager.php +++ b/src/Core/Container/TransformerManager.php @@ -9,6 +9,7 @@ use Okapi\CodeTransformer\Core\Exception\Transformer\TransformerNotFoundException; use Okapi\CodeTransformer\Core\ServiceInterface; use Okapi\CodeTransformer\Transformer; +use ReflectionClass as BaseReflectionClass; /** * # Transformer Manager @@ -27,7 +28,7 @@ class TransformerManager implements ServiceInterface /** * The list of transformer containers. * - * @var TransformerContainer[] + * @var array Key is the transformer file path */ private array $transformerContainers = []; @@ -66,6 +67,9 @@ public function register(): void * Get the transformer instances. * * @return void + * + * @noinspection PhpUnhandledExceptionInspection + * @noinspection PhpDocMissingThrowsInspection */ private function loadTransformers(): void { @@ -89,7 +93,11 @@ private function loadTransformers(): void 'transformerInstance' => $transformerInstance, ]); - $this->transformerContainers[] = $transformerContainer; + // Create a reflection of the transformer + $transformerRefClass = new BaseReflectionClass($transformerInstance); + + $filePath = $transformerRefClass->getFileName(); + $this->transformerContainers[$filePath] = $transformerContainer; } } @@ -108,7 +116,7 @@ public function getTransformers(): array /** * Get the transformer containers. * - * @return TransformerContainer[] + * @return array Key is the transformer file path */ public function getTransformerContainers(): array { diff --git a/src/Core/Matcher/TransformerMatcher.php b/src/Core/Matcher/TransformerMatcher.php index 5a38795..833fbc1 100644 --- a/src/Core/Matcher/TransformerMatcher.php +++ b/src/Core/Matcher/TransformerMatcher.php @@ -3,6 +3,7 @@ namespace Okapi\CodeTransformer\Core\Matcher; use DI\Attribute\Inject; +use Okapi\CodeTransformer\Core\Cache\CacheState; use Okapi\CodeTransformer\Core\Cache\CacheState\EmptyResultCacheState; use Okapi\CodeTransformer\Core\Cache\CacheStateManager; use Okapi\CodeTransformer\Core\Container\TransformerContainer; @@ -38,8 +39,8 @@ class TransformerMatcher /** * Check if the class should be transformed. * - * @param string $namespacedClass - * @param string $filePath + * @param class-string $namespacedClass + * @param string $filePath * * @return bool */ @@ -80,9 +81,10 @@ function (bool $carry, TransformerContainer $container) use ($transformerContain // Cache the result if (!$matchedTransformerContainers) { $cacheState = DI::make(EmptyResultCacheState::class, [ - 'data' => [ - 'originalFilePath' => $filePath, - 'modificationTime' => filemtime($filePath), + CacheState::DATA => [ + CacheState::ORIGINAL_FILE_PATH_KEY => $filePath, + CacheState::NAMESPACED_CLASS_KEY => $namespacedClass, + CacheState::MODIFICATION_TIME_KEY => filemtime($filePath), ], ]); @@ -99,7 +101,7 @@ function (bool $carry, TransformerContainer $container) use ($transformerContain /** * Get the matched transformers for the given class. * - * @param string $namespacedClass + * @param class-string $namespacedClass * * @return TransformerContainer[] */ diff --git a/src/Core/Options.php b/src/Core/Options.php index b7596be..eacaeab 100644 --- a/src/Core/Options.php +++ b/src/Core/Options.php @@ -2,8 +2,10 @@ namespace Okapi\CodeTransformer\Core; +use Composer\Autoload\ClassLoader; use Okapi\CodeTransformer\CodeTransformerKernel; use Okapi\Path\Path; +use ReflectionClass; /** * # Options @@ -68,13 +70,9 @@ public function setOptions( ?int $cacheFileMode, ?bool $debug, ): void { - $rootDir = getcwd(); - - if ($rootDir === false) { - // @codeCoverageIgnoreStart - $rootDir = Path::resolve(Path::join(__DIR__, '../../../../..')); - // @codeCoverageIgnoreEnd - } + $composerRef = new ReflectionClass(ClassLoader::class); + $composerDir = $composerRef->getFileName(); + $rootDir = Path::resolve(Path::join($composerDir, '../../..')); $this->appDir = $rootDir; $this->cacheDir = $cacheDir ?? Path::join($rootDir, $this->defaultCacheDir); diff --git a/src/Core/Processor/TransformerProcessor.php b/src/Core/Processor/TransformerProcessor.php index 19a1e37..68a2147 100644 --- a/src/Core/Processor/TransformerProcessor.php +++ b/src/Core/Processor/TransformerProcessor.php @@ -4,6 +4,7 @@ use DI\Attribute\Inject; use Okapi\CodeTransformer\Core\Cache\CachePaths; +use Okapi\CodeTransformer\Core\Cache\CacheState; use Okapi\CodeTransformer\Core\Cache\CacheState\NoTransformationsCacheState; use Okapi\CodeTransformer\Core\Cache\CacheState\TransformedCacheState; use Okapi\CodeTransformer\Core\Cache\CacheStateManager; @@ -49,14 +50,14 @@ public function transform(Metadata $metadata): void $transformerContainers = $this->transformerMatcher->getMatchedTransformerContainers($namespacedClass); $this->processTransformers($metadata, $transformerContainers); - $originalFilePath = $metadata->uri; - $cacheFilePath = $this->cachePaths->getTransformedCachePath($originalFilePath); - $transformed = $metadata->code->hasChanges(); + $originalFilePath = $metadata->uri; + $transformedFilePath = $this->cachePaths->getTransformedCachePath($originalFilePath); + $transformed = $metadata->code->hasChanges(); // Save the transformed code if ($transformed) { Filesystem::writeFile( - $cacheFilePath, + $transformedFilePath, $metadata->code->getNewSource(), ); } @@ -67,18 +68,20 @@ public function transform(Metadata $metadata): void $transformerFilePaths = $this->getTransformerFilePaths($transformerContainers); $cacheState = DI::make(TransformedCacheState::class, [ - 'data' => [ - 'originalFilePath' => $originalFilePath, - 'modificationTime' => $modificationTime, - 'transformedFilePath' => $cacheFilePath, - 'transformerFilePaths' => $transformerFilePaths, + CacheState::DATA => [ + CacheState::ORIGINAL_FILE_PATH_KEY => $originalFilePath, + CacheState::NAMESPACED_CLASS_KEY => $namespacedClass, + CacheState::MODIFICATION_TIME_KEY => $modificationTime, + TransformedCacheState::TRANSFORMED_FILE_PATH_KEY => $transformedFilePath, + TransformedCacheState::TRANSFORMER_FILE_PATHS_KEY => $transformerFilePaths, ], ]); } else { $cacheState = DI::make(NoTransformationsCacheState::class, [ - 'data' => [ - 'originalFilePath' => $originalFilePath, - 'modificationTime' => $modificationTime, + CacheState::DATA => [ + CacheState::ORIGINAL_FILE_PATH_KEY => $originalFilePath, + CacheState::NAMESPACED_CLASS_KEY => $namespacedClass, + CacheState::MODIFICATION_TIME_KEY => $modificationTime, ], ]); } @@ -121,12 +124,9 @@ function (TransformerContainer $a, TransformerContainer $b) { * @return string[] * * @noinspection PhpDocMissingThrowsInspection Handled by TransformerNotFoundException - * - * @todo: Move this logic to the CacheStateManager by checking - * if the transformers exist and are up-to-date. */ protected function getTransformerFilePaths( - array $transformerContainers + array $transformerContainers, ): array { return array_map( function (TransformerContainer $transformerContainer) { diff --git a/src/Transformer/Code.php b/src/Transformer/Code.php index baf3af1..ff6eb05 100644 --- a/src/Transformer/Code.php +++ b/src/Transformer/Code.php @@ -46,8 +46,8 @@ class Code /** * Code constructor. * - * @param string $source The source code. - * @param string $namespacedClass The namespaced class name. + * @param string $source The source code. + * @param class-string $namespacedClass The namespaced class name. */ public function __construct( private readonly string $source, @@ -204,7 +204,7 @@ public function hasChanges(): bool /** * Get the namespaced class name. * - * @return string + * @return class-string */ public function getNamespacedClass(): string { diff --git a/tests/Functional/AddedTransformerTest.php b/tests/Functional/AddedTransformerTest.php new file mode 100644 index 0000000..7fb989e --- /dev/null +++ b/tests/Functional/AddedTransformerTest.php @@ -0,0 +1,47 @@ +assertWillBeTransformed($class); + + $addedTransformerClass = new $class(); + + $this->assertSame( + 'Hello Code Transformer!', + $addedTransformerClass->test(), + ); + } + + public function testCachedAddedTransformer(): void + { + AddedTransformerKernel::init(); + + $class = AddedTransformerClass::class; + $this->assertWillBeTransformed($class); + + $addedTransformerClass = new $class(); + $this->assertSame( + 'Hello from Code Transformer!', + $addedTransformerClass->test(), + ); + } +} diff --git a/tests/Functional/ChangedClassTest.php b/tests/Functional/ChangedClassTest.php new file mode 100644 index 0000000..6947d41 --- /dev/null +++ b/tests/Functional/ChangedClassTest.php @@ -0,0 +1,85 @@ +assertWillBeTransformed($class); + + $changedClass = new $class(); + $this->assertSame( + 'Hello World from Code Transformer!', + $changedClass->test(), + ); + + // Change class + $classFilePath = Util::CLASSES_TO_TRANSFORM_DIR . '/ChangedClass.php'; + $classFileContent = Filesystem::readFile($classFilePath); + $tmpPath = Util::TMP_DIR . '/ChangedClass.php'; + Filesystem::writeFile($tmpPath, $classFileContent); + + $changedFileContent = str_replace( + 'Hello World!', + 'Hello Changed World!', + $classFileContent, + ); + + sleep(1); + Filesystem::writeFile($classFilePath, $changedFileContent); + } + + public function testCachedChangedClass(): void + { + $exception = null; + try { + ApplicationKernel::init(); + + $class = ChangedClass::class; + $this->assertWillBeTransformed($class); + + $classInstance = new $class(); + $this->assertSame( + 'Hello Changed World from Code Transformer!', + $classInstance->test(), + ); + } catch (Throwable $e) { + $exception = $e; + } + + // Restore class + $tmpPath = Util::TMP_DIR . '/ChangedClass.php'; + if (!file_exists($tmpPath)) { + return; + } + + $tmpFileContent = Filesystem::readFile($tmpPath); + $classFilePath = Util::CLASSES_TO_TRANSFORM_DIR . '/ChangedClass.php'; + + Filesystem::writeFile($classFilePath, $tmpFileContent); + + if ($exception !== null) { + /** @noinspection PhpUnhandledExceptionInspection */ + throw $exception; + } + } +} diff --git a/tests/Functional/DeleteCacheFileTest.php b/tests/Functional/DeleteCacheFileTest.php new file mode 100644 index 0000000..b9e2401 --- /dev/null +++ b/tests/Functional/DeleteCacheFileTest.php @@ -0,0 +1,51 @@ +assertWillBeTransformed($class); + + $deleteCacheFileClass = new $class(); + $this->assertSame( + 'Hello World!', + $deleteCacheFileClass->test(), + ); + } + + public function testCachedDeleteCacheFileClass(): void + { + ApplicationKernel::init(); + + $cachedFilePath = Util::CACHED_CLASSES_TO_TRANSFORM_DIR . '/DeleteCacheFileClass.php'; + + $this->assertFileExists($cachedFilePath); + Filesystem::rm($cachedFilePath); + $this->assertFileDoesNotExist($cachedFilePath); + + $class = DeleteCacheFileClass::class; + $this->assertWillBeTransformed($class); + + $deleteCacheFileClass = new $class(); + $this->assertSame('Hello World!', $deleteCacheFileClass->test()); + + $this->assertFileExists($cachedFilePath); + } +} diff --git a/tests/Functional/KernelTest.php b/tests/Functional/KernelTest.php new file mode 100644 index 0000000..675818d --- /dev/null +++ b/tests/Functional/KernelTest.php @@ -0,0 +1,32 @@ +assertFalse(ApplicationKernel::isInitialized()); + ApplicationKernel::init(); + $this->assertTrue(ApplicationKernel::isInitialized()); + + $this->assertFileDoesNotExist(Util::CACHE_STATES_FILE); + } + + public function testCachedKernel(): void + { + $this->assertFalse(ApplicationKernel::isInitialized()); + ApplicationKernel::init(); + $this->assertTrue(ApplicationKernel::isInitialized()); + + $this->assertFileExists(Util::CACHE_STATES_FILE); + } +} diff --git a/tests/Functional/MultipleTransformersTest.php b/tests/Functional/MultipleTransformersTest.php new file mode 100644 index 0000000..36668b5 --- /dev/null +++ b/tests/Functional/MultipleTransformersTest.php @@ -0,0 +1,57 @@ +assertWillBeTransformed($class); + + $multipleTransformersClass = new $class(); + + $this->assertSame( + 'Hello from Code Transformer!', + $multipleTransformersClass->test(), + ); + + $this->assertSame( + "You can't get me!", + $multipleTransformersClass->privateProperty, + ); + } + + public function testCachedMultipleTransformers(): void + { + ApplicationKernel::init(); + + $class = MultipleTransformersClass::class; + $this->assertTransformerLoadedFromCache($class); + + $multipleTransformersClass = new $class(); + + $this->assertSame( + 'Hello from Code Transformer!', + $multipleTransformersClass->test(), + ); + + $this->assertSame( + "You can't get me!", + $multipleTransformersClass->privateProperty, + ); + } +} diff --git a/tests/Functional/NoChangesClassTest.php b/tests/Functional/NoChangesClassTest.php new file mode 100644 index 0000000..0060f18 --- /dev/null +++ b/tests/Functional/NoChangesClassTest.php @@ -0,0 +1,42 @@ +assertWillBeTransformed($class); + + new $class(); + } + + public function testCachedNoChangesClass(): void + { + ApplicationKernel::init(); + + $class = NoChangesClass::class; + $this->assertTransformerNotApplied($class); + + new $class(); + + $originalFilePath = Util::CLASSES_TO_TRANSFORM_DIR . '/NoChangesClass.php'; + $cachedFilePath = Util::CACHED_CLASSES_TO_TRANSFORM_DIR . '/NoChangesClass.php'; + $this->assertFileExists($originalFilePath); + $this->assertFileDoesNotExist($cachedFilePath); + } +} diff --git a/tests/Functional/ReplaceStringInClassTest.php b/tests/Functional/ReplaceStringInClassTest.php new file mode 100644 index 0000000..3072be6 --- /dev/null +++ b/tests/Functional/ReplaceStringInClassTest.php @@ -0,0 +1,96 @@ +assertWillBeTransformed($class); + + $stringClass = new $class(); + $this->assertSame( + 'Hello from Code Transformer!', + $stringClass->test(), + ); + + $file = Util::CLASSES_TO_TRANSFORM_DIR . '/StringClass.php'; + $content = Filesystem::readFile($file); + + $this->assertEquals( + $content, + StringTransformer::$originalSourceCode, + ); + } + + public function testCachedReplaceStringClass(): void + { + ApplicationKernel::init(); + + $class = StringClass::class; + $this->assertTransformerLoadedFromCache($class); + + $stringClass = new $class(); + $this->assertSame( + 'Hello from Code Transformer!', + $stringClass->test(), + ); + } + + public function testCachedFile(): void + { + $cachedFilePath = Util::CACHED_CLASSES_TO_TRANSFORM_DIR . '/StringClass.php'; + + $this->assertFileExists($cachedFilePath); + $file = Filesystem::readFile($cachedFilePath); + + $this->assertStringContainsString( + 'Hello from Code Transformer!', + $file, + ); + + $this->assertStringContainsString( + '$iAmAppended = true;', + $file, + ); + } + + public function testDestructor(): void + { + Util::clearCache(); + ApplicationKernel::init(); + + $class = StringClass::class; + $stringClass = new $class(); + + $cacheStateManager = DI::get(CacheStateManager::class); + + $this->assertFileDoesNotExist(Util::CACHE_STATES_FILE); + $cacheStateManager->__destruct(); + $this->assertFileExists(Util::CACHE_STATES_FILE); + + $file = require Util::CACHE_STATES_FILE; + + $key = 'CODE_TRANSFORMER_APP_DIR\tests\Stubs\ClassesToTransform\StringClass.php'; + $key = str_replace('\\', DIRECTORY_SEPARATOR, $key); + $this->assertArrayHasKey($key, $file); + } +} diff --git a/tests/Functional/SyntaxErrorClassTest.php b/tests/Functional/SyntaxErrorClassTest.php new file mode 100644 index 0000000..01ac0f7 --- /dev/null +++ b/tests/Functional/SyntaxErrorClassTest.php @@ -0,0 +1,28 @@ +expectException(SyntaxError::class); + + /** @noinspection PhpUnusedLocalVariableInspection */ + $syntaxErrorClass = new SyntaxErrorClass(); + } +} diff --git a/tests/Functional/Workflow/A_ApplicationTest.php b/tests/Functional/Workflow/A_ApplicationTest.php deleted file mode 100644 index 458018c..0000000 --- a/tests/Functional/Workflow/A_ApplicationTest.php +++ /dev/null @@ -1,155 +0,0 @@ -assertFalse(ApplicationKernel::isInitialized()); - ApplicationKernel::init(); - $this->assertTrue(ApplicationKernel::isInitialized()); - - $this->assertFileDoesNotExist(Util::CACHE_STATES_FILE); - } - - /** - * Cached test in {@see CachedApplicationTest::testStringClass()} - */ - public function testStringClass(): void - { - $class = ClassesToTransform\StringClass::class; - $this->assertWillBeTransformed($class); - - $stringClass = new $class(); - $this->assertSame('Hello from Code Transformer!', $stringClass->test()); - - $file = __DIR__ . '/../../Stubs/ClassesToTransform/StringClass.php'; - $content = Filesystem::readFile($file); - - $this->assertEquals($content, StringTransformer::$originalSourceCode); - } - - /** - * Cached test in {@see CachedApplicationTest::testNoChangesClass()} - */ - public function testNoChangesClass(): void - { - $class = ClassesToTransform\NoChangesClass::class; - $this->assertWillBeTransformed($class); - - /** @noinspection PhpUnusedLocalVariableInspection */ - $noChangesClass = new $class(); - } - - public function testSyntaxErrorClass(): void - { - $this->expectException(SyntaxError::class); - - /** @noinspection PhpUnusedLocalVariableInspection */ - $syntaxErrorClass = new ClassesToTransform\SyntaxErrorClass(); - } - - /** - * Cached test in {@see CachedApplicationTest::testChangedClass()} - */ - public function testChangedClass(): void - { - $class = ClassesToTransform\ChangedClass::class; - $this->assertWillBeTransformed($class); - - $changedClass = new $class(); - $this->assertSame( - 'Hello World from Code Transformer!', - $changedClass->test(), - ); - } - - /** - * Cached test in {@see CachedApplicationTest::testChangedTransformer()} - */ - public function testChangedTransformer(): void - { - $class = ClassesToTransform\ChangedTransformer::class; - $this->assertWillBeTransformed($class); - - $changedTransformer = new $class(); - $this->assertSame( - 'Hello World from Code Transformer!', - $changedTransformer->test(), - ); - } - - /** - * Cached test in {@see CachedApplicationTest::testDeleteCacheFileClass()} - */ - public function testDeleteCacheFileClass(): void - { - $class = ClassesToTransform\DeleteCacheFileClass::class; - $this->assertWillBeTransformed($class); - - $deleteCacheFileClass = new $class(); - $this->assertSame('Hello World!', $deleteCacheFileClass->test()); - } - - /** - * Cached test in {@see CachedApplicationTest::testMultipleTransformers()} - */ - public function testMultipleTransformers(): void - { - $class = ClassesToTransform\MultipleTransformersClass::class; - $this->assertWillBeTransformed($class); - - $multipleTransformersClass = new $class(); - - $this->assertSame('Hello from Code Transformer!', $multipleTransformersClass->test()); - $this->assertSame("You can't get me!", $multipleTransformersClass->privateProperty); - } - - /** - * Cached test in {@see CachedApplicationTest::testAddedTransformer()} - */ - public function testAddedTransformer(): void - { - $class = ClassesToTransform\AddedTransformerClass::class; - $this->assertWillBeTransformed($class); - - $addedTransformerClass = new $class(); - $this->assertSame('Hello Code Transformer!', $addedTransformerClass->test()); - } - - public function testDestructor(): void - { - $cacheStateManager = DI::get(CacheStateManager::class); - - $this->assertFileDoesNotExist(Util::CACHE_STATES_FILE); - $cacheStateManager->__destruct(); - $this->assertFileExists(Util::CACHE_STATES_FILE); - - $file = require Util::CACHE_STATES_FILE; - - $key = 'CODE_TRANSFORMER_APP_DIR\tests\Stubs\ClassesToTransform\StringClass.php'; - $key = str_replace('\\', DIRECTORY_SEPARATOR, $key); - $this->assertArrayHasKey($key, $file); - } -} diff --git a/tests/Functional/Workflow/B_CachedApplicationTest.php b/tests/Functional/Workflow/B_CachedApplicationTest.php deleted file mode 100644 index e23a6dd..0000000 --- a/tests/Functional/Workflow/B_CachedApplicationTest.php +++ /dev/null @@ -1,180 +0,0 @@ -assertFalse(ApplicationKernel::isInitialized()); - ApplicationKernel::init(); - $this->assertTrue(ApplicationKernel::isInitialized()); - - $this->assertFileExists(Util::CACHE_STATES_FILE); - } - - public function testCachedFile(): void - { - $cachedFilePath = Util::CACHE_DIR . '/transformed/tests/Stubs/ClassesToTransform/StringClass.php'; - - $this->assertFileExists($cachedFilePath); - $file = Filesystem::readFile($cachedFilePath); - - $this->assertStringContainsString('Hello from Code Transformer!', $file); - $this->assertStringContainsString('$iAmAppended = true;', $file); - } - - /** - * Cached by {@see ApplicationTest::testStringClass()} - */ - public function testStringClass(): void - { - $class = ClassesToTransform\StringClass::class; - $this->assertTransformerLoadedFromCache($class); - - $stringClass = new $class(); - $this->assertSame('Hello from Code Transformer!', $stringClass->test()); - } - - /** - * Cached by {@see ApplicationTest::testNoChangesClass()} - */ - public function testNoChangesClass(): void - { - $class = ClassesToTransform\NoChangesClass::class; - $this->assertTransformerNotApplied($class); - - /** @noinspection PhpUnusedLocalVariableInspection */ - $stringClass = new $class(); - - $originalFilePath = __DIR__ . '/../../Stubs/ClassesToTransform/NoChangesClass.php'; - $cachedFilePath = Util::CACHE_DIR . '/transformed/tests/Stubs/ClassesToTransform/NoChangesClass.php'; - $this->assertFileExists($originalFilePath); - $this->assertFileDoesNotExist($cachedFilePath); - } - - /** - * Cached by {@see ApplicationTest::testChangedClass()} - */ - public function testChangedClass(): void - { - $class = ClassesToTransform\ChangedClass::class; - $this->assertWillBeTransformed($class); - - $changedClass = new $class(); - $this->assertSame( - 'Hello Changed World from Code Transformer!', - $changedClass->test(), - ); - } - - /** - * Cached by {@see ApplicationTest::testChangedTransformer()} - */ - public function testChangedTransformer(): void - { - $class = ClassesToTransform\ChangedTransformer::class; - $this->assertWillBeTransformed($class); - - $classInstance = new $class; - $this->assertSame( - 'Hello Changed World from Code Transformer!', - $classInstance->test(), - ); - } - - /** - * Cached by {@see ApplicationTest::testDeleteCacheFileClass()} - */ - public function testDeleteCacheFileClass(): void - { - $cachedFilePath = Util::CACHE_DIR . '/transformed/tests/Stubs/ClassesToTransform/DeleteCacheFileClass.php'; - - $this->assertFileExists($cachedFilePath); - Filesystem::rm($cachedFilePath); - $this->assertFileDoesNotExist($cachedFilePath); - - $class = ClassesToTransform\DeleteCacheFileClass::class; - $this->assertWillBeTransformed($class); - - $deleteCacheFileClass = new $class(); - $this->assertSame('Hello World!', $deleteCacheFileClass->test()); - - $this->assertFileExists($cachedFilePath); - } - - /** - * Cached by {@see ApplicationTest::testMultipleTransformers()} - */ - public function testMultipleTransformers(): void - { - $class = ClassesToTransform\MultipleTransformersClass::class; - $this->assertTransformerLoadedFromCache($class); - - $multipleTransformersClass = new $class(); - $this->assertSame('Hello from Code Transformer!', $multipleTransformersClass->test()); - $this->assertSame("You can't get me!", $multipleTransformersClass->privateProperty); - } -} diff --git a/tests/Functional/Workflow/C_AddedTransformerTest.php b/tests/Functional/Workflow/C_AddedTransformerTest.php deleted file mode 100644 index 2ca25f3..0000000 --- a/tests/Functional/Workflow/C_AddedTransformerTest.php +++ /dev/null @@ -1,46 +0,0 @@ -assertFalse(AddedTransformerKernel::isInitialized()); - ApplicationKernel::init(); - $this->assertTrue(ApplicationKernel::isInitialized()); - - $this->assertFileExists(Util::CACHE_STATES_FILE); - } - - /** - * Cached by {@see ApplicationTest::testAddedTransformer()} - */ - public function testAddedTransformer(): void - { - $class = ClassesToTransform\AddedTransformerClass::class; - $this->assertWillBeTransformed($class); - - $addedTransformerClass = new $class(); - $this->assertSame('Hello from Code Transformer!', $addedTransformerClass->test()); - } -} diff --git a/tests/Stubs/ClassesToTransform/ChangedTransformer.php b/tests/Stubs/ClassesToTransform/ChangedTransformer.php deleted file mode 100644 index 901070d..0000000 --- a/tests/Stubs/ClassesToTransform/ChangedTransformer.php +++ /dev/null @@ -1,11 +0,0 @@ -getSourceFileNode(); - - foreach ($sourceFileNode->getDescendantNodes() as $node) { - // Find 'Hello World!' string - if ($node instanceof StringLiteral) { - if ($node->getStringContentsText() === 'Hello World!') { - // Replace it with 'Hello Changed World from Code Transformer!' - $code->edit( - $node->children, - "'Hello World from Code Transformer!'", - ); - } - } - } - - $reflection = $code->getReflectionClass(); - assert($reflection->getName() === ChangedTransformer::class); - - $className = $code->getClassName(); - assert($className === 'ChangedTransformer'); - } -} diff --git a/tests/Stubs/Transformer/SyntaxErrorTransformer.php b/tests/Stubs/Transformer/SyntaxErrorTransformer.php index 56670cb..9c07c6a 100644 --- a/tests/Stubs/Transformer/SyntaxErrorTransformer.php +++ b/tests/Stubs/Transformer/SyntaxErrorTransformer.php @@ -16,5 +16,11 @@ public function getTargetClass(): string|array public function transform(Code $code): void { $code->append('}'); + + $refClass = $code->getReflectionClass(); + assert($refClass->getName() === SyntaxErrorClass::class); + + $className = $code->getClassName(); + assert($className === 'SyntaxErrorClass'); } } diff --git a/tests/Util.php b/tests/Util.php index 57d585e..653006f 100644 --- a/tests/Util.php +++ b/tests/Util.php @@ -6,16 +6,26 @@ class Util { - public const CACHE_DIR = __DIR__ . '/cache'; + public const CACHE_DIR = __DIR__ . '/cache'; + public const STUBS_DIR = __DIR__ . '/Stubs'; + public const CACHED_STUBS_DIR = self::CACHE_DIR . '/transformed/tests/Stubs'; + public const CLASSES_TO_TRANSFORM_DIR = self::STUBS_DIR . '/ClassesToTransform'; + public const CACHED_CLASSES_TO_TRANSFORM_DIR = self::CACHED_STUBS_DIR . '/ClassesToTransform'; + public const TMP_DIR = __DIR__ . '/tmp'; public const CACHE_STATES_FILE = self::CACHE_DIR . '/cache_states.php'; public static function clearCache(): void { Filesystem::rm( - path: Util::CACHE_DIR, + path: Util::CACHE_DIR, recursive: true, - force: true, + force: true, + ); + Filesystem::rm( + path: Util::TMP_DIR, + recursive: true, + force: true, ); } }