vendor/symfony/config/Builder/ConfigBuilderGenerator.php line 55

  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Config\Builder;
  11. use Symfony\Component\Config\Definition\ArrayNode;
  12. use Symfony\Component\Config\Definition\BaseNode;
  13. use Symfony\Component\Config\Definition\BooleanNode;
  14. use Symfony\Component\Config\Definition\Builder\ExprBuilder;
  15. use Symfony\Component\Config\Definition\ConfigurationInterface;
  16. use Symfony\Component\Config\Definition\EnumNode;
  17. use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
  18. use Symfony\Component\Config\Definition\FloatNode;
  19. use Symfony\Component\Config\Definition\IntegerNode;
  20. use Symfony\Component\Config\Definition\NodeInterface;
  21. use Symfony\Component\Config\Definition\PrototypedArrayNode;
  22. use Symfony\Component\Config\Definition\ScalarNode;
  23. use Symfony\Component\Config\Definition\VariableNode;
  24. use Symfony\Component\Config\Loader\ParamConfigurator;
  25. /**
  26.  * Generate ConfigBuilders to help create valid config.
  27.  *
  28.  * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  29.  */
  30. class ConfigBuilderGenerator implements ConfigBuilderGeneratorInterface
  31. {
  32.     /**
  33.      * @var ClassBuilder[]
  34.      */
  35.     private array $classes = [];
  36.     private string $outputDir;
  37.     public function __construct(string $outputDir)
  38.     {
  39.         $this->outputDir $outputDir;
  40.     }
  41.     /**
  42.      * @return \Closure that will return the root config class
  43.      */
  44.     public function build(ConfigurationInterface $configuration): \Closure
  45.     {
  46.         $this->classes = [];
  47.         $rootNode $configuration->getConfigTreeBuilder()->buildTree();
  48.         $rootClass = new ClassBuilder('Symfony\\Config'$rootNode->getName());
  49.         $path $this->getFullPath($rootClass);
  50.         if (!is_file($path)) {
  51.             // Generate the class if the file not exists
  52.             $this->classes[] = $rootClass;
  53.             $this->buildNode($rootNode$rootClass$this->getSubNamespace($rootClass));
  54.             $rootClass->addImplements(ConfigBuilderInterface::class);
  55.             $rootClass->addMethod('getExtensionAlias''
  56. public function NAME(): string
  57. {
  58.     return \'ALIAS\';
  59. }', ['ALIAS' => $rootNode->getPath()]);
  60.             $this->writeClasses();
  61.         }
  62.         return function () use ($path$rootClass) {
  63.             require_once $path;
  64.             $className $rootClass->getFqcn();
  65.             return new $className();
  66.         };
  67.     }
  68.     private function getFullPath(ClassBuilder $class): string
  69.     {
  70.         $directory $this->outputDir.\DIRECTORY_SEPARATOR.$class->getDirectory();
  71.         if (!is_dir($directory)) {
  72.             @mkdir($directory0777true);
  73.         }
  74.         return $directory.\DIRECTORY_SEPARATOR.$class->getFilename();
  75.     }
  76.     private function writeClasses(): void
  77.     {
  78.         foreach ($this->classes as $class) {
  79.             $this->buildConstructor($class);
  80.             $this->buildToArray($class);
  81.             if ($class->getProperties()) {
  82.                 $class->addProperty('_usedProperties'null'[]');
  83.             }
  84.             $this->buildSetExtraKey($class);
  85.             file_put_contents($this->getFullPath($class), $class->build());
  86.         }
  87.         $this->classes = [];
  88.     }
  89.     private function buildNode(NodeInterface $nodeClassBuilder $classstring $namespace): void
  90.     {
  91.         if (!$node instanceof ArrayNode) {
  92.             throw new \LogicException('The node was expected to be an ArrayNode. This Configuration includes an edge case not supported yet.');
  93.         }
  94.         foreach ($node->getChildren() as $child) {
  95.             match (true) {
  96.                 $child instanceof ScalarNode => $this->handleScalarNode($child$class),
  97.                 $child instanceof PrototypedArrayNode => $this->handlePrototypedArrayNode($child$class$namespace),
  98.                 $child instanceof VariableNode => $this->handleVariableNode($child$class),
  99.                 $child instanceof ArrayNode => $this->handleArrayNode($child$class$namespace),
  100.                 default => throw new \RuntimeException(sprintf('Unknown node "%s".'$child::class)),
  101.             };
  102.         }
  103.     }
  104.     private function handleArrayNode(ArrayNode $nodeClassBuilder $classstring $namespace): void
  105.     {
  106.         $childClass = new ClassBuilder($namespace$node->getName());
  107.         $childClass->setAllowExtraKeys($node->shouldIgnoreExtraKeys());
  108.         $class->addRequire($childClass);
  109.         $this->classes[] = $childClass;
  110.         $hasNormalizationClosures $this->hasNormalizationClosures($node);
  111.         $comment $this->getComment($node);
  112.         if ($hasNormalizationClosures) {
  113.             $comment sprintf(" * @template TValue\n * @param TValue \$value\n%s"$comment);
  114.             $comment .= sprintf(' * @return %s|$this'."\n"$childClass->getFqcn());
  115.             $comment .= sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n "$childClass->getFqcn());
  116.         }
  117.         if ('' !== $comment) {
  118.             $comment "/**\n$comment*/\n";
  119.         }
  120.         $property $class->addProperty(
  121.             $node->getName(),
  122.             $this->getType($childClass->getFqcn(), $hasNormalizationClosures)
  123.         );
  124.         $nodeTypes $this->getParameterTypes($node);
  125.         $body $hasNormalizationClosures '
  126. COMMENTpublic function NAME(PARAM_TYPE $value = []): CLASS|static
  127. {
  128.     if (!\is_array($value)) {
  129.         $this->_usedProperties[\'PROPERTY\'] = true;
  130.         $this->PROPERTY = $value;
  131.         return $this;
  132.     }
  133.     if (!$this->PROPERTY instanceof CLASS) {
  134.         $this->_usedProperties[\'PROPERTY\'] = true;
  135.         $this->PROPERTY = new CLASS($value);
  136.     } elseif (0 < \func_num_args()) {
  137.         throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
  138.     }
  139.     return $this->PROPERTY;
  140. }' '
  141. COMMENTpublic function NAME(array $value = []): CLASS
  142. {
  143.     if (null === $this->PROPERTY) {
  144.         $this->_usedProperties[\'PROPERTY\'] = true;
  145.         $this->PROPERTY = new CLASS($value);
  146.     } elseif (0 < \func_num_args()) {
  147.         throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
  148.     }
  149.     return $this->PROPERTY;
  150. }';
  151.         $class->addUse(InvalidConfigurationException::class);
  152.         $class->addMethod($node->getName(), $body, [
  153.             'COMMENT' => $comment,
  154.             'PROPERTY' => $property->getName(),
  155.             'CLASS' => $childClass->getFqcn(),
  156.             'PARAM_TYPE' => \in_array('mixed'$nodeTypestrue) ? 'mixed' implode('|'$nodeTypes),
  157.         ]);
  158.         $this->buildNode($node$childClass$this->getSubNamespace($childClass));
  159.     }
  160.     private function handleVariableNode(VariableNode $nodeClassBuilder $class): void
  161.     {
  162.         $comment $this->getComment($node);
  163.         $property $class->addProperty($node->getName());
  164.         $class->addUse(ParamConfigurator::class);
  165.         $body '
  166. /**
  167. COMMENT *
  168.  * @return $this
  169.  */
  170. public function NAME(mixed $valueDEFAULT): static
  171. {
  172.     $this->_usedProperties[\'PROPERTY\'] = true;
  173.     $this->PROPERTY = $value;
  174.     return $this;
  175. }';
  176.         $class->addMethod($node->getName(), $body, [
  177.             'PROPERTY' => $property->getName(),
  178.             'COMMENT' => $comment,
  179.             'DEFAULT' => $node->hasDefaultValue() ? ' = '.var_export($node->getDefaultValue(), true) : '',
  180.         ]);
  181.     }
  182.     private function handlePrototypedArrayNode(PrototypedArrayNode $nodeClassBuilder $classstring $namespace): void
  183.     {
  184.         $name $this->getSingularName($node);
  185.         $prototype $node->getPrototype();
  186.         $methodName $name;
  187.         $hasNormalizationClosures $this->hasNormalizationClosures($node) || $this->hasNormalizationClosures($prototype);
  188.         $nodeParameterTypes $this->getParameterTypes($node);
  189.         $prototypeParameterTypes $this->getParameterTypes($prototype);
  190.         if (!$prototype instanceof ArrayNode || ($prototype instanceof PrototypedArrayNode && $prototype->getPrototype() instanceof ScalarNode)) {
  191.             $class->addUse(ParamConfigurator::class);
  192.             $property $class->addProperty($node->getName());
  193.             if (null === $key $node->getKeyAttribute()) {
  194.                 // This is an array of values; don't use singular name
  195.                 $nodeTypesWithoutArray array_filter($nodeParameterTypes, static fn ($type) => 'array' !== $type);
  196.                 $body '
  197. /**
  198.  * @param ParamConfigurator|list<ParamConfigurator|PROTOTYPE_TYPE>EXTRA_TYPE $value
  199.  *
  200.  * @return $this
  201.  */
  202. public function NAME(PARAM_TYPE $value): static
  203. {
  204.     $this->_usedProperties[\'PROPERTY\'] = true;
  205.     $this->PROPERTY = $value;
  206.     return $this;
  207. }';
  208.                 $class->addMethod($node->getName(), $body, [
  209.                     'PROPERTY' => $property->getName(),
  210.                     'PROTOTYPE_TYPE' => implode('|'$prototypeParameterTypes),
  211.                     'EXTRA_TYPE' => $nodeTypesWithoutArray '|'.implode('|'$nodeTypesWithoutArray) : '',
  212.                     'PARAM_TYPE' => \in_array('mixed'$nodeParameterTypestrue) ? 'mixed' 'ParamConfigurator|'.implode('|'$nodeParameterTypes),
  213.                 ]);
  214.             } else {
  215.                 $body '
  216. /**
  217.  * @return $this
  218.  */
  219. public function NAME(string $VAR, TYPE $VALUE): static
  220. {
  221.     $this->_usedProperties[\'PROPERTY\'] = true;
  222.     $this->PROPERTY[$VAR] = $VALUE;
  223.     return $this;
  224. }';
  225.                 $class->addMethod($methodName$body, [
  226.                     'PROPERTY' => $property->getName(),
  227.                     'TYPE' => \in_array('mixed'$prototypeParameterTypestrue) ? 'mixed' 'ParamConfigurator|'.implode('|'$prototypeParameterTypes),
  228.                     'VAR' => '' === $key 'key' $key,
  229.                     'VALUE' => 'value' === $key 'data' 'value',
  230.                 ]);
  231.             }
  232.             return;
  233.         }
  234.         $childClass = new ClassBuilder($namespace$name);
  235.         if ($prototype instanceof ArrayNode) {
  236.             $childClass->setAllowExtraKeys($prototype->shouldIgnoreExtraKeys());
  237.         }
  238.         $class->addRequire($childClass);
  239.         $this->classes[] = $childClass;
  240.         $property $class->addProperty(
  241.             $node->getName(),
  242.             $this->getType($childClass->getFqcn().'[]'$hasNormalizationClosures)
  243.         );
  244.         $comment $this->getComment($node);
  245.         if ($hasNormalizationClosures) {
  246.             $comment sprintf(" * @template TValue\n * @param TValue \$value\n%s"$comment);
  247.             $comment .= sprintf(' * @return %s|$this'."\n"$childClass->getFqcn());
  248.             $comment .= sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n "$childClass->getFqcn());
  249.         }
  250.         if ('' !== $comment) {
  251.             $comment "/**\n$comment*/\n";
  252.         }
  253.         if (null === $key $node->getKeyAttribute()) {
  254.             $body $hasNormalizationClosures '
  255. COMMENTpublic function NAME(PARAM_TYPE $value = []): CLASS|static
  256. {
  257.     $this->_usedProperties[\'PROPERTY\'] = true;
  258.     if (!\is_array($value)) {
  259.         $this->PROPERTY[] = $value;
  260.         return $this;
  261.     }
  262.     return $this->PROPERTY[] = new CLASS($value);
  263. }' '
  264. COMMENTpublic function NAME(array $value = []): CLASS
  265. {
  266.     $this->_usedProperties[\'PROPERTY\'] = true;
  267.     return $this->PROPERTY[] = new CLASS($value);
  268. }';
  269.             $class->addMethod($methodName$body, [
  270.                 'COMMENT' => $comment,
  271.                 'PROPERTY' => $property->getName(),
  272.                 'CLASS' => $childClass->getFqcn(),
  273.                 'PARAM_TYPE' => \in_array('mixed'$nodeParameterTypestrue) ? 'mixed' implode('|'$nodeParameterTypes),
  274.             ]);
  275.         } else {
  276.             $body $hasNormalizationClosures '
  277. COMMENTpublic function NAME(string $VAR, PARAM_TYPE $VALUE = []): CLASS|static
  278. {
  279.     if (!\is_array($VALUE)) {
  280.         $this->_usedProperties[\'PROPERTY\'] = true;
  281.         $this->PROPERTY[$VAR] = $VALUE;
  282.         return $this;
  283.     }
  284.     if (!isset($this->PROPERTY[$VAR]) || !$this->PROPERTY[$VAR] instanceof CLASS) {
  285.         $this->_usedProperties[\'PROPERTY\'] = true;
  286.         $this->PROPERTY[$VAR] = new CLASS($VALUE);
  287.     } elseif (1 < \func_num_args()) {
  288.         throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
  289.     }
  290.     return $this->PROPERTY[$VAR];
  291. }' '
  292. COMMENTpublic function NAME(string $VAR, array $VALUE = []): CLASS
  293. {
  294.     if (!isset($this->PROPERTY[$VAR])) {
  295.         $this->_usedProperties[\'PROPERTY\'] = true;
  296.         $this->PROPERTY[$VAR] = new CLASS($VALUE);
  297.     } elseif (1 < \func_num_args()) {
  298.         throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
  299.     }
  300.     return $this->PROPERTY[$VAR];
  301. }';
  302.             $class->addUse(InvalidConfigurationException::class);
  303.             $class->addMethod($methodNamestr_replace('$value''$VAR'$body), [
  304.                 'COMMENT' => $comment'PROPERTY' => $property->getName(),
  305.                 'CLASS' => $childClass->getFqcn(),
  306.                 'VAR' => '' === $key 'key' $key,
  307.                 'VALUE' => 'value' === $key 'data' 'value',
  308.                 'PARAM_TYPE' => \in_array('mixed'$prototypeParameterTypestrue) ? 'mixed' implode('|'$prototypeParameterTypes),
  309.             ]);
  310.         }
  311.         $this->buildNode($prototype$childClass$namespace.'\\'.$childClass->getName());
  312.     }
  313.     private function handleScalarNode(ScalarNode $nodeClassBuilder $class): void
  314.     {
  315.         $comment $this->getComment($node);
  316.         $property $class->addProperty($node->getName());
  317.         $class->addUse(ParamConfigurator::class);
  318.         $body '
  319. /**
  320. COMMENT * @return $this
  321.  */
  322. public function NAME($value): static
  323. {
  324.     $this->_usedProperties[\'PROPERTY\'] = true;
  325.     $this->PROPERTY = $value;
  326.     return $this;
  327. }';
  328.         $class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'COMMENT' => $comment]);
  329.     }
  330.     private function getParameterTypes(NodeInterface $node): array
  331.     {
  332.         $paramTypes = [];
  333.         if ($node instanceof BaseNode) {
  334.             $types $node->getNormalizedTypes();
  335.             if (\in_array(ExprBuilder::TYPE_ANY$typestrue)) {
  336.                 $paramTypes[] = 'mixed';
  337.             }
  338.             if (\in_array(ExprBuilder::TYPE_STRING$typestrue)) {
  339.                 $paramTypes[] = 'string';
  340.             }
  341.         }
  342.         if ($node instanceof BooleanNode) {
  343.             $paramTypes[] = 'bool';
  344.         } elseif ($node instanceof IntegerNode) {
  345.             $paramTypes[] = 'int';
  346.         } elseif ($node instanceof FloatNode) {
  347.             $paramTypes[] = 'float';
  348.         } elseif ($node instanceof EnumNode) {
  349.             $paramTypes[] = 'mixed';
  350.         } elseif ($node instanceof ArrayNode) {
  351.             $paramTypes[] = 'array';
  352.         } elseif ($node instanceof VariableNode) {
  353.             $paramTypes[] = 'mixed';
  354.         }
  355.         return array_unique($paramTypes);
  356.     }
  357.     private function getComment(BaseNode $node): string
  358.     {
  359.         $comment '';
  360.         if ('' !== $info = (string) $node->getInfo()) {
  361.             $comment .= ' * '.$info."\n";
  362.         }
  363.         if (!$node instanceof ArrayNode) {
  364.             foreach ((array) ($node->getExample() ?? []) as $example) {
  365.                 $comment .= ' * @example '.$example."\n";
  366.             }
  367.             if ('' !== $default $node->getDefaultValue()) {
  368.                 $comment .= ' * @default '.(null === $default 'null' var_export($defaulttrue))."\n";
  369.             }
  370.             if ($node instanceof EnumNode) {
  371.                 $comment .= sprintf(' * @param ParamConfigurator|%s $value'implode('|'array_map(function ($a) {
  372.                     return var_export($atrue);
  373.                 }, $node->getValues())))."\n";
  374.             } else {
  375.                 $parameterTypes $this->getParameterTypes($node);
  376.                 $comment .= ' * @param ParamConfigurator|'.implode('|'$parameterTypes).' $value'."\n";
  377.             }
  378.         } else {
  379.             foreach ((array) ($node->getExample() ?? []) as $example) {
  380.                 $comment .= ' * @example '.json_encode($example)."\n";
  381.             }
  382.             if ($node->hasDefaultValue() && [] != $default $node->getDefaultValue()) {
  383.                 $comment .= ' * @default '.json_encode($default)."\n";
  384.             }
  385.         }
  386.         if ($node->isDeprecated()) {
  387.             $comment .= ' * @deprecated '.$node->getDeprecation($node->getName(), $node->getParent()->getName())['message']."\n";
  388.         }
  389.         return $comment;
  390.     }
  391.     /**
  392.      * Pick a good singular name.
  393.      */
  394.     private function getSingularName(PrototypedArrayNode $node): string
  395.     {
  396.         $name $node->getName();
  397.         if (!str_ends_with($name's')) {
  398.             return $name;
  399.         }
  400.         $parent $node->getParent();
  401.         $mappings $parent instanceof ArrayNode $parent->getXmlRemappings() : [];
  402.         foreach ($mappings as $map) {
  403.             if ($map[1] === $name) {
  404.                 $name $map[0];
  405.                 break;
  406.             }
  407.         }
  408.         return $name;
  409.     }
  410.     private function buildToArray(ClassBuilder $class): void
  411.     {
  412.         $body '$output = [];';
  413.         foreach ($class->getProperties() as $p) {
  414.             $code '$this->PROPERTY';
  415.             if (null !== $p->getType()) {
  416.                 if ($p->isArray()) {
  417.                     $code $p->areScalarsAllowed()
  418.                         ? 'array_map(function ($v) { return $v instanceof CLASS ? $v->toArray() : $v; }, $this->PROPERTY)'
  419.                         'array_map(function ($v) { return $v->toArray(); }, $this->PROPERTY)'
  420.                     ;
  421.                 } else {
  422.                     $code $p->areScalarsAllowed()
  423.                         ? '$this->PROPERTY instanceof CLASS ? $this->PROPERTY->toArray() : $this->PROPERTY'
  424.                         '$this->PROPERTY->toArray()'
  425.                     ;
  426.                 }
  427.             }
  428.             $body .= strtr('
  429.     if (isset($this->_usedProperties[\'PROPERTY\'])) {
  430.         $output[\'ORG_NAME\'] = '.$code.';
  431.     }', ['PROPERTY' => $p->getName(), 'ORG_NAME' => $p->getOriginalName(), 'CLASS' => $p->getType()]);
  432.         }
  433.         $extraKeys $class->shouldAllowExtraKeys() ? ' + $this->_extraKeys' '';
  434.         $class->addMethod('toArray''
  435. public function NAME(): array
  436. {
  437.     '.$body.'
  438.     return $output'.$extraKeys.';
  439. }');
  440.     }
  441.     private function buildConstructor(ClassBuilder $class): void
  442.     {
  443.         $body '';
  444.         foreach ($class->getProperties() as $p) {
  445.             $code '$value[\'ORG_NAME\']';
  446.             if (null !== $p->getType()) {
  447.                 if ($p->isArray()) {
  448.                     $code $p->areScalarsAllowed()
  449.                         ? 'array_map(function ($v) { return \is_array($v) ? new '.$p->getType().'($v) : $v; }, $value[\'ORG_NAME\'])'
  450.                         'array_map(function ($v) { return new '.$p->getType().'($v); }, $value[\'ORG_NAME\'])'
  451.                     ;
  452.                 } else {
  453.                     $code $p->areScalarsAllowed()
  454.                         ? '\is_array($value[\'ORG_NAME\']) ? new '.$p->getType().'($value[\'ORG_NAME\']) : $value[\'ORG_NAME\']'
  455.                         'new '.$p->getType().'($value[\'ORG_NAME\'])'
  456.                     ;
  457.                 }
  458.             }
  459.             $body .= strtr('
  460.     if (array_key_exists(\'ORG_NAME\', $value)) {
  461.         $this->_usedProperties[\'PROPERTY\'] = true;
  462.         $this->PROPERTY = '.$code.';
  463.         unset($value[\'ORG_NAME\']);
  464.     }
  465. ', ['PROPERTY' => $p->getName(), 'ORG_NAME' => $p->getOriginalName()]);
  466.         }
  467.         if ($class->shouldAllowExtraKeys()) {
  468.             $body .= '
  469.     $this->_extraKeys = $value;
  470. ';
  471.         } else {
  472.             $body .= '
  473.     if ([] !== $value) {
  474.         throw new InvalidConfigurationException(sprintf(\'The following keys are not supported by "%s": \', __CLASS__).implode(\', \', array_keys($value)));
  475.     }';
  476.             $class->addUse(InvalidConfigurationException::class);
  477.         }
  478.         $class->addMethod('__construct''
  479. public function __construct(array $value = [])
  480. {'.$body.'
  481. }');
  482.     }
  483.     private function buildSetExtraKey(ClassBuilder $class): void
  484.     {
  485.         if (!$class->shouldAllowExtraKeys()) {
  486.             return;
  487.         }
  488.         $class->addUse(ParamConfigurator::class);
  489.         $class->addProperty('_extraKeys');
  490.         $class->addMethod('set''
  491. /**
  492.  * @param ParamConfigurator|mixed $value
  493.  *
  494.  * @return $this
  495.  */
  496. public function NAME(string $key, mixed $value): static
  497. {
  498.     $this->_extraKeys[$key] = $value;
  499.     return $this;
  500. }');
  501.     }
  502.     private function getSubNamespace(ClassBuilder $rootClass): string
  503.     {
  504.         return sprintf('%s\\%s'$rootClass->getNamespace(), substr($rootClass->getName(), 0, -6));
  505.     }
  506.     private function hasNormalizationClosures(NodeInterface $node): bool
  507.     {
  508.         try {
  509.             $r = new \ReflectionProperty($node'normalizationClosures');
  510.         } catch (\ReflectionException) {
  511.             return false;
  512.         }
  513.         $r->setAccessible(true);
  514.         return [] !== $r->getValue($node);
  515.     }
  516.     private function getType(string $classTypebool $hasNormalizationClosures): string
  517.     {
  518.         return $classType.($hasNormalizationClosures '|scalar' '');
  519.     }
  520. }