44
55namespace Drupal \sdc_validator \Commands ;
66
7+ use Consolidation \AnnotatedCommand \CommandResult ;
78use Drupal \Core \Plugin \Component ;
89use Drupal \Core \Render \Component \Exception \InvalidComponentException ;
910use Drupal \Core \Template \ComponentNodeVisitor ;
1011use Drupal \Core \Template \TwigEnvironment ;
1112use Drupal \Core \Theme \Component \ComponentValidator ;
1213use Drupal \Core \Theme \ComponentPluginManager ;
1314use Drush \Commands \DrushCommands ;
15+ use Psy \Command \Command ;
1416use Symfony \Component \Yaml \Yaml ;
1517use Twig \Error \Error ;
16- use Twig \Node \Node ;
1718
1819/**
1920 * Validates SDC component definitions using Drupal core's ComponentValidator.
@@ -51,8 +52,10 @@ public function __construct(protected ComponentPluginManager $componentPluginMan
5152 * @command sdc_validator:validate
5253 * @usage drush sdc_validator:validate '<path to components>'
5354 * @usage drush sdc_validator:validate 'web/themes/custom/civictheme/components'
55+ *
56+ * @SuppressWarnings(PHPMD.StaticAccess)
5457 */
55- public function validateComponentDefinitions (string $ components_path ): void {
58+ public function validateComponentDefinitions (string $ components_path ): CommandResult {
5659 if (!is_dir ($ components_path )) {
5760 throw new \Exception ('❌ Components directory not found: ' . $ components_path );
5861 }
@@ -86,36 +89,8 @@ public function validateComponentDefinitions(string $components_path): void {
8689 $ component_name = basename ((string ) $ component_file , '.component.yml ' );
8790 $ component_id = $ component_base_identifier . ': ' . $ component_name ;
8891 $ component = $ this ->componentPluginManager ->find ($ component_id );
89- $ template_path = $ component ->getTemplatePath ();
90- if ($ template_path === NULL ) {
91- throw new \Exception (sprintf ('❌ %s does not have a template. ' , $ component_id ));
92- }
93- $ source = $ this ->twig ->getLoader ()->getSourceContext ($ template_path );
94- try {
95- // Need to load as a component.
96- $ node_tree = $ this ->twig ->parse ($ this ->twig ->tokenize ($ source ));
97- }
98- catch (Error $ error ) {
99- throw new \Exception ("❌ Error parsing twig file: " . $ error ->getMessage (), $ error ->getCode (), $ error );
100- }
101- $ this ->validateSlots ($ component , $ node_tree ->getNode ('blocks ' ));
102- $ definition = Yaml::parseFile ($ component_file );
103- // Merge with additional required keys.
104- $ definition = array_merge (
105- $ definition ,
106- [
107- 'machineName ' => $ component_name ,
108- 'extension_type ' => 'theme ' ,
109- 'id ' => 'civictheme: ' . $ component_name ,
110- 'library ' => ['css ' => ['component ' => ['foo.css ' => []]]],
111- 'path ' => '' ,
112- 'provider ' => 'civictheme ' ,
113- 'template ' => $ component_name . '.twig ' ,
114- 'group ' => 'civictheme-group ' ,
115- 'description ' => 'CivicTheme component ' ,
116- ]
117- );
118- $ this ->validateComponentFile ($ definition );
92+ $ this ->validateSlots ($ component );
93+ $ this ->validateComponentFile ($ component_file , $ component_id );
11994 $ valid_count ++;
12095 }
12196 catch (\Exception $ e ) {
@@ -135,20 +110,39 @@ public function validateComponentDefinitions(string $components_path): void {
135110 foreach ($ errors as $ error ) {
136111 $ this ->output ()->writeln (sprintf ("❌ %s - %s " , $ error ['file ' ], $ error ['error ' ]));
137112 }
138- throw new \ Exception ( " Component validation failed. " );
113+ return CommandResult:: dataWithExitCode ( ' Component validation failed. ' , Command:: FAILURE );
139114 }
140- $ this -> output ()-> writeln ( " ✨ All components are valid " );
115+ return CommandResult:: dataWithExitCode ( ' ✨ All components are valid ' , Command:: SUCCESS );
141116 }
142117
143118 /**
144119 * Validates a single component definition file.
145120 *
146- * @param array $definition
147- * The component definition.
121+ * @param string $component_file
122+ * Path to the file.
123+ * @param string $component_id
124+ * The component id.
148125 *
149126 * @throws \Drupal\Core\Render\Component\Exception\InvalidComponentException
150127 */
151- public function validateComponentFile (array $ definition ): void {
128+ public function validateComponentFile (string $ component_file , string $ component_id ): void {
129+ [, $ component_name ] = explode (': ' , $ component_id );
130+ $ definition = Yaml::parseFile ($ component_file );
131+ // Merge with additional required keys.
132+ $ definition = array_merge (
133+ $ definition ,
134+ [
135+ 'machineName ' => $ component_name ,
136+ 'extension_type ' => 'theme ' ,
137+ 'id ' => $ component_id ,
138+ 'library ' => ['css ' => ['component ' => ['foo.css ' => []]]],
139+ 'path ' => '' ,
140+ 'provider ' => 'civictheme ' ,
141+ 'template ' => $ component_name . '.twig ' ,
142+ 'group ' => 'civictheme-group ' ,
143+ 'description ' => 'CivicTheme component ' ,
144+ ]
145+ );
152146 $ this ->componentValidator ->validateDefinition ($ definition , TRUE );
153147 }
154148
@@ -163,12 +157,31 @@ public function validateComponentFile(array $definition): void {
163157 * undeclared slots. This cheap validation lets us validate during runtime
164158 * even in production.
165159 *
160+ * @param \Drupal\Core\Plugin\Component $component
161+ * The component to validate the slots against.
162+ *
166163 * @throws \Drupal\Core\Render\Component\Exception\InvalidComponentException
164+ * When the twig doesn't parse or template does not exist.
165+ * @throws \Exception
167166 * When the slots don't pass validation.
168167 *
169168 * @see \Drupal\Core\Template\ComponentNodeVisitor::validateSlots
170169 */
171- protected function validateSlots (Component $ component , Node $ node ): void {
170+ protected function validateSlots (Component $ component ): void {
171+ $ template_path = $ component ->getTemplatePath ();
172+ if ($ template_path === NULL ) {
173+ throw new \Exception (sprintf ('❌ %s does not have a template. ' , $ component ->getI ));
174+ }
175+ $ source = $ this ->twig ->getLoader ()->getSourceContext ($ template_path );
176+ try {
177+ // Need to load as a component.
178+ $ node_tree = $ this ->twig ->parse ($ this ->twig ->tokenize ($ source ));
179+ $ node = $ node_tree ->getNode ('blocks ' );
180+ }
181+ catch (Error $ error ) {
182+ throw new \Exception ("❌ Error parsing twig file: " . $ error ->getMessage (), $ error ->getCode (), $ error );
183+ }
184+
172185 $ metadata = $ component ->metadata ;
173186 if (!$ metadata ->mandatorySchemas ) {
174187 return ;
0 commit comments