Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Config: display user-friendly message when invalid generator is passed #771

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

rodrigoprimo
Copy link
Contributor

Description

This PR improves how Config::processLongArgument() handles the --generator parameter to display a user-friendly message if an invalid generator name is passed. Before, an invalid generator name caused a fatal error.

I have checked, and it is not possible to add custom generators using third-party code (at least not without adding files to the PHPCS directory). This is because the Runner class only loads generators found in the src/Generator/ directory:

$class = 'PHP_CodeSniffer\Generators\\'.$this->config->generator;
$generator = new $class($ruleset);

So, it should be fine to limit the list of valid generators added in this PR to only the three generators provided by PHPCS.

Suggested changelog entry

Config: display a user-friendly message if an invalid generator name is passed in the command line

Related issues/external references

Fixes #709

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
    • This change is only breaking for integrators, not for external standards or end-users.
  • Documentation improvement

PR checklist

  • I have checked there is no other PR open for the same change.
  • I have read the Contribution Guidelines.
  • I grant the project the right to include and distribute the code under the BSD-3-Clause license (and I have the right to grant these rights).
  • I have added tests to cover my changes.
  • I have verified that the code complies with the projects coding standards.
  • [Required for new sniffs] I have added XML documentation for the sniff.

This commit improves how `Config::processLongArgument()` handles the
`--generator` parameter. Now it will show a user-friendly message if an
invalid generator name is passed. Before, an invalid generator name
caused a fatal error.
@rodrigoprimo
Copy link
Contributor Author

@jrfnl, while working on this PR, I noticed that, besides what I reported in #709, it is possible to trigger a different PHP fatal error if Generator is passed as the name of the generator:

$ phpcs test.php --generator=Generator --standard=Generic
PHP Fatal error:  Uncaught Error: Cannot instantiate abstract class PHP_CodeSniffer\Generators\Generator in src/Runner.php:98
Stack trace:
#0 bin/phpcs(14): PHP_CodeSniffer\Runner->runPHPCS()
#1 {main}
  thrown in src/Runner.php on line 98

This happens because PHPCS tries to instantiate anything that it finds in the src/Generator directory:

$class = 'PHP_CodeSniffer\Generators\\'.$this->config->generator;
$generator = new $class($ruleset);

The change suggested in this PR prevents this fatal error from happening, at least when using PHPCS in the command line. But I'm mentioning it anyway in case we want to do something else about it.

@rodrigoprimo
Copy link
Contributor Author

Looking into the unrelated test that failed in the CI build.

@jrfnl
Copy link
Member

jrfnl commented Dec 13, 2024

Looking into the unrelated test that failed in the CI build.

Eh.. the test failure is not unrelated...

@@ -153,7 +153,7 @@ public static function dataDeprecatedSniffsListDoesNotShowNeedsCsMode()
return [
'Standard using deprecated sniffs; documentation is requested' => [
'standard' => __DIR__.'/ShowSniffDeprecationsTest.xml',
'additionalArgs' => ['--generator=text'],
'additionalArgs' => ['--generator=Text'],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please undo this change. This is perfectly valid. Your PR needs a fix, not this unrelated test.

Copy link
Contributor Author

@rodrigoprimo rodrigoprimo Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh.. the test failure is not unrelated...

Indeed, I chose a poor word to describe what I meant.

Please undo this change. This is perfectly valid. Your PR needs a fix, not this unrelated test.

@jrfnl, just checking, did you check the commit message? Do you disagree with my assessment describing why I updated the test?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not, but have now and I don't agree with your assessment.

The basic premise for the CLI arguments is that they should be handled case-insensitively, so text is perfectly valid and should be accepted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But on master, phpcs --standard=Generic --generator=text does not work and throws a fatal error. Or maybe it throws a fatal error on Linux (case-sensitive OS) but not on Windows (case-insensitive OS)?

If that is the case, I failed to consider case-insensitive operating systems in this PR. I'm not sure what would be the best approach for this PR to accommodate this.

Maybe perform a case-insensitive check for the generator name when running on Windows and a case-sensitive check when running on other systems? What I don't like about this approach is that PHPCS would continue to behave differently depending on the OS.

Another option is to change PHPCS to ensure that it behaves consistently across different operating systems and handles the --generator CLI argument case-insensitively.

The basic premise for the CLI arguments is that they should be handled case-insensitively

I was not aware of that and I can think of one other example of a CLI argument that is not case-insensitive when PHPCS is running on Linux: --standard=generic. I'm not sure if there are other cases.

phpcs --generator=Text --standard=generic                                              
ERROR: the "generic" coding standard is not installed. The installed coding standards are MySource, PEAR, PSR1, PSR2, PSR12, Squiz, Zend and MyStandard

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But on master, phpcs --standard=Generic --generator=text does not work and throws a fatal error. Or maybe it throws a fatal error on Linux (case-sensitive OS) but not on Windows (case-insensitive OS)?

Exactly. It is currently (unintentionally) OS-dependent, but shouldn't be.

Another option is to change PHPCS to ensure that it behaves consistently across different operating systems and handles the --generator CLI argument case-insensitively.

IMO, that would be the correct thing to do.

The basic premise for the CLI arguments is that they should be handled case-insensitively

I was not aware of that and I can think of one other example of a CLI argument that is not case-insensitive when PHPCS is running on Linux: --standard=generic. I'm not sure if there are other cases.

That's outside the scope of this PR. Might be worth opening an issue to investigate.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re: CLI and case-insensitive: squizlabs/PHP_CodeSniffer#1656 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the additional information. I will drop the commit that changed testDeprecatedSniffsListDoesNotShowNeedsCsMode() and update this PR to ensure that the error displayed in Config::processLongArgument() for invalid generator names treats the value of the --generator argument case-insensitively.

Another option is to change PHPCS to ensure that it behaves consistently across different operating systems and handles the --generator CLI argument case-insensitively.

IMO, that would be the correct thing to do.

To implement this, I believe it will be necessary to change Runner::runPHPCS() to load the correct generator class no matter what is the case of the passed generator argument. This is a separate change not directly related to #709. Do you prefer to review this change in this PR or a separate PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you think the case-(in)sensitively should be handled in the Runner class instead of in the Config class ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because I did not consider normalizing the input in the Config class when setting Config::generator, and was thinking about doing that in the Runner class.

To illustrate what I had in mind, assuming --generator=text is passed, my plan was to do a case-insensitive check in the Config class to see if the generator name passed is valid. Then, on the Runner class the code normalizes the generator name (i.e, modifies the generator name to match the case of the desired file/class name).

After seeing your question, I realized that it makes sense to normalize the generator name in the Config class, and that is probably what you had in mind. Is this correct?

I was also missing that Runner::runPHPCS() creates an instance of Config. Since I was missing that, I was worried about runPHPCS() being called directly and attempting to instantiate a generator without the validation and normalization performed in the Config class.

@rodrigoprimo rodrigoprimo force-pushed the generator-param-error-handling branch from 7141e6d to 6bb661d Compare December 18, 2024 14:58
@rodrigoprimo
Copy link
Contributor Author

@jrfnl, I dropped the commit that changed testDeprecatedSniffsListDoesNotShowNeedsCsMode() and added a new commit to ensure that the code handles the --generator argument in a case-insensitive way. This PR is ready for another review.

Copy link
Member

@jrfnl jrfnl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rodrigoprimo Thanks for updating this. I have left some comments inline, mostly nitpicks/dotting of the i's and crossing the t's.

Other than that, this is looking good to go into the 3.12.0 release.

*
* @var array<string, string>
*/
private static $validGenerators = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for making this property static ?

Comment on lines +207 to +208
* Note: once support for PHP < 5.6 is dropped, this property should be refactored into a class
* constant.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Note: once support for PHP < 5.6 is dropped, this property should be refactored into a class
* constant.
* {@internal Once support for PHP < 5.6 is dropped, this property should be refactored into a class
* constant.}

Comment on lines +204 to +205
* - Keys: lowercase version of the generator name.
* - Values: name of the generator PHP class.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be part of the "long description" of the variable or part of the description of the @var type ?

$lowerCaseGeneratorName = strtolower($generatorName);

if (isset(self::$validGenerators[$lowerCaseGeneratorName]) === false) {
$validOptions = implode(', ', array_values(self::$validGenerators));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the array_values() really needed here ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also wondering if for readability, the last comma should be replaced with " and " ?

if (isset(self::$validGenerators[$lowerCaseGeneratorName]) === false) {
$validOptions = implode(', ', array_values(self::$validGenerators));
$error = sprintf(
'ERROR: "%s" is not a valid generator. Valid options are: %s.'.PHP_EOL.PHP_EOL,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a suggestion...

Suggested change
'ERROR: "%s" is not a valid generator. Valid options are: %s.'.PHP_EOL.PHP_EOL,
'ERROR: "%s" is not a valid generator. The following generators are supported: %s.'.PHP_EOL.PHP_EOL,

@@ -23,37 +23,59 @@ final class GeneratorArgTest extends TestCase
/**
* Ensure that the generator property is set when the parameter is passed a valid value.
*
* @param string $generatorName Generator name.
* @param string $argumentValue Generator name passed in the command line.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param string $argumentValue Generator name passed in the command line.
* @param string $argumentValue Generator name passed on the command line.

*
* @return array<int, array<string>>
*/
public static function dataGeneratorNames()
public static function dataValidGeneratorNames()
{
return [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this array is now a little more involved, I think it would make it more straight-forward to maintain and to read the tests if the array included test case names + argument names.

@jrfnl jrfnl added this to the 3.12.0 milestone Dec 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

PHP fatal error when user passes an invalid generator
2 participants