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

Add a simple linter and a pre-commit hook for it #3029

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
php lint.php
npm run lint
composer cs
vendor/bin/phpstan analyse --memory-limit=1G
1 change: 0 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Initialize Parse.ly option before registering the setting ([#2001](https://github.com/Parsely/wp-parsely/pull/2001))


## [3.11.0](https://github.com/Parsely/wp-parsely/compare/3.10.0...3.11.0) - 2023-11-13

### Added
Expand Down
186 changes: 186 additions & 0 deletions lint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<?php
/**
* Small linter that can detect and fix some code style violations
*
* Usage:
* php lint.php // Show errors.
* php lint.php --fix // Auto-fix errors where supported.
*
* @since 3.18.0
*/

// Configuration.
$config = array(
new Lint_Rule(
'Two or more consecutive empty lines found',
'/(\r?\n){3,}/',
'/\.(php|js|css|html|scss|yml|md|ts|tsx|sh|json)$/',
"\n\n"
),
new Lint_Rule(
'One or more empty lines between closing brackets found',
'/(\})\n{2,}(\})/',
'/\.(php|js|css|html|scss|yml|md|ts|tsx|sh|json)$/',
"$1\n$2"
),
);

// Run the rules and display any errors.
foreach ( $config as $rule ) {
$errors = $rule->run( in_array( '--fix', $argv ) );

if ( '' !== $errors ) {
$exit_code = 1;
echo $errors;
}
}

exit( $exit_code ?? 0 );

/**
* A class representing a linting rule.
*
* @since 3.18.0
*/
class Lint_Rule {
/**
* @var string Error message to display when this linting rule is violated.
*/
private $error_message;

/**
* @var string Regex pattern used for rule violation detection.
*/
private $error_pattern;

/**
* @var string Regex pattern used to specify the files to search in.
*/
private $include_files;

/**
* @var string|null Optional regex pattern used to auto-fix the violation.
*/
private $fix_pattern;

/**
* @var array<string> Directories to exclude from search.
*/
private $exclude_dirs;

/**
* @var array<string> Array of errors found when running the rule.
*/
private $errors = array();

/**
* Constructor.
*
* @since 3.18.0
*
* @param string $error_message The error message to display.
* @param string $error_pattern The regex matching the rule violation.
* @param string $include_files The regex matching the files to search in.
* @param string|null $fix_pattern The regex to fix any violations (optional).
* @param array<string> $exclude_dirs The directories to exclude from search.
*/
public function __construct(
string $error_message,
string $error_pattern,
string $include_files,
string $fix_pattern = null,
array $exclude_dirs = array( 'artifacts', 'build', 'vendor', 'node_modules' )
) {
$this->error_message = $error_message;
$this->error_pattern = $error_pattern;
$this->include_files = $include_files;
$this->fix_pattern = $fix_pattern;
$this->exclude_dirs = $exclude_dirs;
}

/**
* Runs the rule to detect or auto-fix any violations.
*
* @since 3.18.0
*
* @param bool $auto_fix_mode Whether violations should be auto-fixed.
* @return string The violations found, or an empty string.
*/
public function run( $auto_fix_mode ): string {
$base_dir = __DIR__ . '/';
$iterator = $this->create_iterator( $this->exclude_dirs );

// Iterate over the directories and files.
foreach ( $iterator as $file ) {
// Check if the item is a file that should be checked.
if ( $file->isFile() && preg_match( $this->include_files, $file->getFilename() ) ) {
$file_content = file_get_contents( $file->getPathname() );

// Check for rule violations.
if ( preg_match_all( $this->error_pattern, $file_content, $matches, PREG_OFFSET_CAPTURE ) ) {
$relative_path = str_replace( $base_dir, '', $file->getPathname() );

// Auto-fix or log the issue.
if ( $auto_fix_mode && null !== $this->fix_pattern ) {
$fixed_content = preg_replace( $this->error_pattern, $this->fix_pattern, $file_content );

if ( null === $fixed_content ) {
$this->errors[] = "Error while trying to lint {$relative_path}.";
}

if ( false === file_put_contents( $file->getPathname(), $fixed_content ) ) {
$this->errors[] = "Failed to save fix for {$relative_path}.";
}
} else {
foreach ( $matches[0] as $match ) {
$line_number = substr_count( substr( $file_content, 0, $match[1] ), "\n" ) + 1;
$this->errors[] = "{$relative_path} \033[90m(line {$line_number})\033[0m";
}
}
}
}
}

if ( empty( $this->errors ) ) {
return '';
}

$output = "\033[31mERROR: {$this->error_message}.\033[0m\n";
if ( null !== $this->fix_pattern ) {
$output .= "\033[33mAuto-fixable with the --fix parameter.\033[0m\n";
}
$output .= "\033[36mYou can use the {$this->error_pattern} regex to find the offending code.\033[0m\n\n";

foreach ( $this->errors as $error ) {
$output .= " - {$error}\n";
}

return $output . "\n\n";
}

/**
* Creates an iterator that iterates across the project's folder structure,
* skipping excluded directories.
*
* @since 3.18.0
*
* @return RecursiveIteratorIterator
*/
private function create_iterator(): RecursiveIteratorIterator {
return new RecursiveIteratorIterator(
new RecursiveCallbackFilterIterator(
new RecursiveDirectoryIterator( __DIR__, RecursiveDirectoryIterator::SKIP_DOTS ),
function ( $current, $key, $iterator ) {
if ( $iterator->hasChildren() && in_array( $current->getFilename(), $this->exclude_dirs ) ) {
return false; // Skip excluded directories.
}

return true;
}
),
RecursiveIteratorIterator::SELF_FIRST
);
}
}

// phpcs:ignoreFile
1 change: 0 additions & 1 deletion src/@types/assets/window.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,4 @@ declare global {
};
};
}

}
1 change: 0 additions & 1 deletion src/Telemetry/Tracks/class-tracks-pixel.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
use Parsely\Utils\Utils;
use WP_Error;


/**
* Handles all operations related to the Tracks pixel.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ abstract public static function get_style_id(): string;
*/
abstract public function run(): void;


/**
* Examines filters and conditions to determine whether the feature can be
* enabled.
Expand Down
2 changes: 0 additions & 2 deletions src/content-helper/common/components/input-range/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,4 @@
}
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@
}
}


// Multi-percentage bar.
div.multi-percentage-bar {
position: relative;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,6 @@
}
}


.related-posts-list {
display: flex;
padding: to_rem(6px) 0 var(--grid-unit-20) 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,6 @@ public function get_smart_links( WP_REST_Request $request ): WP_REST_Response {
return new WP_REST_Response( array( 'data' => $response ), 200 );
}


/**
* API Endpoint: POST /smart-linking/{post_id}/add.
*
Expand Down Expand Up @@ -475,7 +474,6 @@ public function set_smart_links( WP_REST_Request $request ): WP_REST_Response {
return new WP_REST_Response( array( 'data' => $response ), 200 );
}


/**
* API Endpoint: POST /smart-linking/url-to-post-type.
*
Expand Down
1 change: 0 additions & 1 deletion src/rest-api/settings/class-base-settings-endpoint.php
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,6 @@ protected function get_default( array $keys ) {
return $current; // Return default value for valid key path.
}


/**
* Gets the specifications for nested settings based on a composite key.
*
Expand Down
1 change: 0 additions & 1 deletion src/services/class-base-api-service.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ abstract class Base_API_Service {
*/
private $parsely;


/**
* Initializes the class.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@ public function call( array $args = array() ) {
return $this->request( 'GET', $query_args );
}


/**
* Appends multiple parameters to the URL.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,6 @@ public function test_parsely_stats_column_visibility_on_empty_plugin_options():
self::assertNotContains( self::$parsely_stats_column_header, $this->get_admin_columns() );
}


/**
* Verifies Parse.ly Stats column visibility.
*
Expand Down Expand Up @@ -503,7 +502,6 @@ public function test_content_of_parsely_stats_column_on_valid_posts(): void {
);
}


/**
* Verifies content of Parse.ly Stats column.
*
Expand Down Expand Up @@ -768,7 +766,6 @@ private function mock_parsely_stats_response( ?array $return_value ): Post_List_
return $obj;
}


/**
* Verifies Parse.ly API call and enqueued status of Parse.ly Stats script.
*
Expand Down Expand Up @@ -1201,7 +1198,6 @@ public function test_parsely_stats_response_on_valid_post_type_and_having_data_f
);
}


/**
* Verifies Parse.ly Stats response.
*
Expand Down
1 change: 0 additions & 1 deletion tests/Integration/RestAPI/BaseAPIControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@ public function test_register_multiple_endpoints(): void {
->method( 'get_endpoint_slug' )
->willReturn( 'test2' );


$this->test_controller->testable_register_endpoints( array( $endpoint1, $endpoint2 ) ); // @phpstan-ignore-line

$endpoints = $this->test_controller->get_endpoints();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,6 @@ public function test_is_available_to_current_user_returns_error_if_no_permission
self::assertInstanceOf( WP_Error::class, $this->get_endpoint()->is_available_to_current_user( new WP_REST_Request() ) );
}


/**
* Tests that the endpoint is not available to the current user, since the user is not logged in.
*
Expand Down
1 change: 0 additions & 1 deletion tests/Integration/UI/SettingsPageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,6 @@ function () use ( $badge_options ) {
self::expectOutputContains( $expected_html );
}


/**
* Mocks a success response from the API credentials validation endpoint.
*
Expand Down
1 change: 0 additions & 1 deletion wp-parsely.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@
// Load Telemetry classes.
require_once __DIR__ . '/src/Telemetry/telemetry-init.php';


add_action( 'plugins_loaded', __NAMESPACE__ . '\\parsely_initialize_plugin' );
/**
* Registers the basic classes to initialize the plugin.
Expand Down
Loading