public function testTransformerPluginNames()
 {
     $sourceIn = ['<?php' . PHP_EOL, '/**' . PHP_EOL, ' */' . PHP_EOL, 'class Foo {' . PHP_EOL, '  var $uses = [\'Bar.Baz\'];' . PHP_EOL, '}' . PHP_EOL];
     $sourceOutExpected = ['<?php' . PHP_EOL, '/**' . PHP_EOL, ' * @property Baz $Baz' . PHP_EOL, ' */' . PHP_EOL, 'class Foo {' . PHP_EOL, '  var $uses = [\'Bar.Baz\'];' . PHP_EOL, '}' . PHP_EOL];
     $properties = ['uses' => function ($sym) {
         return $sym;
     }];
     $tree = $this->parser->parse(join('', $sourceIn));
     $this->visitor->setProperties(array_keys($properties));
     $this->traverser->traverse($tree);
     $sourceOutActual = ClassTransformer::apply($sourceIn, $this->visitor->getClasses(), $properties);
     $this->assertSame($sourceOutExpected, $sourceOutActual);
 }
 /**
  * Perform the analysis for special properties and write them to the files.
  *
  * Use addSource() before calling this.
  *
  * @return int  Number of source files changed (does not necessarily mean they
  *              have been actually modified in case dryRun if true)
  */
 public function applyMagic()
 {
     $sourcesChanged = 0;
     # start processing
     $traverser = new NodeTraverser();
     $classVisitor = new ClassVisitor();
     $traverser->addVisitor($classVisitor);
     # Store which class is in which file and whose parent it has
     $classes = [];
     foreach ($this->files as $file) {
         $this->logger->info('Parsing ' . $file->getRealPath());
         $code = file($file);
         $parser = new Parser(new Lexer());
         $tree = $parser->parse(join('', $code));
         $traverser->traverse($tree);
         $classesInFile = $classVisitor->getClasses();
         foreach ($classesInFile as $class) {
             # A class without an parent cannot be any CakePHP managed class
             if (!isset($class->extends)) {
                 continue;
             }
             if (isset($classes[$class->name])) {
                 $this->logger->error(sprintf('Ignoring class definition of %s found in file %s:%d, first' . ' found in %s:%d', $class->name, $file, $class->getAttribute('startLine'), $classes[$class->name]['file'], $classes[$class->name]['class']->getAttribute('startLine')));
                 continue;
             }
             # Remember for next pass
             $classes[$class->name] = ['class' => $class, 'file' => $file->getRealPath(), 'code' => $code];
         }
     }
     $traverser->removeVisitor($classVisitor);
     $propertyVisitor = new PropertyVisitor();
     $propertyVisitor->setProperties($this->getProperties());
     $traverser->addVisitor($propertyVisitor);
     if (empty($classes)) {
         $this->logger->warning('No classes found');
         return $sourcesChanged;
     }
     $fnFindTopAncestor = function ($className) use($classes) {
         while (isset($classes[$className])) {
             $className = $classes[$className]['class']->extends->toString();
         }
         return $className;
     };
     foreach ($classes as $className => $classData) {
         $fileName = $classData['file'];
         $transformedSource = NULL;
         $topAncestor = $fnFindTopAncestor($className);
         if (!isset($this->classToPropertySymbolTransform[$topAncestor])) {
             $this->logger->info("Ignoring {$fileName} , not a recognized class");
             continue;
         }
         $traverser->traverse([$classData['class']]);
         $transformedSource = ClassTransformer::apply($classData['code'], $propertyVisitor->getClasses(), $this->classToPropertySymbolTransform[$topAncestor], $this->removeUnknownProperties);
         if ($classData['code'] === $transformedSource) {
             continue;
         }
         $sourcesChanged++;
         if ($this->dryRun) {
             $this->logger->info('Dry-run, not writing changes to ' . $fileName);
             continue;
         }
         $this->logger->info('Writing changes to ' . $fileName);
         file_put_contents($fileName, $transformedSource);
     }
     return $sourcesChanged;
 }