From e80ea291d36d33c420209973a41d491cfbdb510b Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Wed, 28 Feb 2024 12:17:43 +0530 Subject: [PATCH] Add Psalm and PHP-CS-Fixer --- .github/workflows/psalm.yml | 34 +++++ .github/workflows/run-tests.yml | 14 +- .gitignore | 1 + .php-cs-fixer.php | 132 ++++++++++++++++++ composer.json | 17 ++- psalm-baseline.xml | 27 ++++ psalm.xml | 22 +++ src/CardServiceProvider.php | 3 +- src/Http/Controllers/WorldClockController.php | 2 + src/WorldClock.php | 2 +- .../Controllers/WorldClockControllerTest.php | 5 +- 11 files changed, 240 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/psalm.yml create mode 100644 .php-cs-fixer.php create mode 100644 psalm-baseline.xml create mode 100644 psalm.xml diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml new file mode 100644 index 0000000..66a71a9 --- /dev/null +++ b/.github/workflows/psalm.yml @@ -0,0 +1,34 @@ +name: Psalm + +on: + push: + paths: + - '**.php' + - 'psalm*' + +jobs: + psalm: + name: psalm + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + coverage: none + + - name: Cache composer dependencies + uses: actions/cache@v4 + with: + path: vendor + key: composer-${{ hashFiles('composer.json') }} + + - name: Run composer install + run: | + composer config "http-basic.nova.laravel.com" "${{ secrets.NOVA_USERNAME }}" "${{ secrets.NOVA_4_LICENSE_KEY }}" + composer install -n --prefer-dist + + - name: Run Psalm + run: ./vendor/bin/psalm --shepherd diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 20c02f6..5266ca4 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,34 +1,30 @@ name: run-tests -on: - - push - - pull_request +on: [ push, pull_request ] jobs: test: - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest strategy: fail-fast: true matrix: - os: [ubuntu-latest] - php: [8.1, 8.2] + php: [ 8.3, 8.2 ] dependency-version: ['--prefer-lowest', '--prefer-stable'] laravel: [10.*] include: - laravel: 10.* testbench: 8.* - name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} - ${{ matrix.os }} + name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo coverage: none - name: Setup problem matchers diff --git a/.gitignore b/.gitignore index 89585b5..d2f7eed 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ auth.json phpunit.xml .phpunit.result.cache +.phpunit.cache/ .DS_Store Thumbs.db diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php new file mode 100644 index 0000000..2518285 --- /dev/null +++ b/.php-cs-fixer.php @@ -0,0 +1,132 @@ +in(__DIR__) + ->exclude( + [ + 'vendor', + ] + ) + ->name('*.php') + ->ignoreDotFiles(false) + ->ignoreVCS(true) + ->ignoreVCSIgnored(true); + +return (new \PhpCsFixer\Config()) + ->setUsingCache(true) + ->setRiskyAllowed(true) + ->setIndent(' ') + ->setLineEnding("\n") + ->setRules([ + // Basic PER Coding Style 2.0 ruleset plus our "fixes" for it + '@PER-CS2.0' => true, // https://www.php-fig.org/per/coding-style/} + // 'concat_space' => ['spacing' => 'none'], // make strings shorter "'hello' . $name . '!'" => "'hello'.$name.'!'" + // 'blank_line_after_opening_tag' => false, // it makes " false, // It makes "fn ()" into "fn()" and conflicts with our PHPCS ruleset + 'single_line_empty_body' => false, // It has conflict with PSR2.Classes.ClassDeclaration.OpenBraceNewLine + // 'unary_operator_spaces' => false, // It has conflict with PHPCS ruleset + + // Additional rules on the top of PER-CS2 + // Please keep these rules alphabetically + 'align_multiline_comment' => ['comment_type' => 'phpdocs_only'], + 'array_indentation' => true, + 'assign_null_coalescing_to_coalesce_equal' => true, + 'binary_operator_spaces' => ['default' => 'single_space'], + 'cast_spaces' => ['space' => 'single'], + 'class_attributes_separation' => ['elements' => ['method' => 'one']], + 'declare_strict_types' => true, + 'explicit_string_variable' => true, + // 'final_public_method_for_abstract_class' => true, // @todo enable it + 'general_phpdoc_annotation_remove' => [ + 'annotations' => [ + 'api', + 'access', + 'author', + 'category', + 'copyright', + 'created', + 'license', + 'link', + 'package', + 'since', + 'subpackage', + 'version', + ], + ], + 'modernize_types_casting' => true, + 'no_alias_functions' => true, + 'no_binary_string' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => ['tokens' => ['extra', 'curly_brace_block']], + 'no_homoglyph_names' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => true, + 'no_short_bool_cast' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_spaces_around_offset' => true, + 'no_trailing_comma_in_singleline' => false, // it's a good marker that there are more elements in an array + 'no_unneeded_braces' => true, + 'no_unneeded_control_parentheses' => true, + 'no_unneeded_final_method' => true, + 'no_unreachable_default_argument_value' => true, + 'no_unused_imports' => true, + 'no_useless_concat_operator' => true, + 'no_useless_return' => true, + 'no_whitespace_before_comma_in_array' => true, + 'normalize_index_brace' => true, + 'nullable_type_declaration' => ['syntax' => 'question_mark'], + 'object_operator_without_whitespace' => true, + /* + * @see https://github.com/slevomat/coding-standard/issues/1620#issuecomment-1758006718 + * 'ordered_class_elements' => [ + 'order' => [ + 'use_trait', + 'constant', + 'case', // for enums only + 'property', + 'method', + ] + ],*/ + 'php_unit_construct' => true, + 'php_unit_dedicate_assert' => ['target' => 'newest'], + 'php_unit_expectation' => true, + 'php_unit_fqcn_annotation' => true, + 'php_unit_method_casing' => ['case' => 'snake_case'], + 'php_unit_no_expectation_annotation' => true, + 'php_unit_set_up_tear_down_visibility' => true, + 'php_unit_strict' => true, + 'php_unit_test_annotation' => ['style' => 'annotation'], + 'php_unit_test_class_requires_covers' => true, + 'phpdoc_align' => ['align' => 'left'], + 'phpdoc_indent' => true, + 'phpdoc_line_span' => ['const' => 'single', 'property' => 'single', 'method' => 'single'], + 'phpdoc_param_order' => true, + 'phpdoc_scalar' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_tag_casing' => true, + 'phpdoc_types' => true, + 'protected_to_private' => true, + 'psr_autoloading' => true, + 'self_accessor' => true, + 'self_static_accessor' => true, + 'single_line_comment_spacing' => true, + 'single_line_comment_style' => ['comment_types' => ['asterisk', 'hash']], + 'space_after_semicolon' => true, + 'standardize_not_equals' => true, + 'strict_param' => true, + 'ternary_to_null_coalescing' => true, + 'trim_array_spaces' => true, + 'trailing_comma_in_multiline' => true, + 'type_declaration_spaces' => true, + 'types_spaces' => ['space' => 'single'], + 'whitespace_after_comma_in_array' => true, + 'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false], + ]) + ->setFinder($finder); diff --git a/composer.json b/composer.json index 23297d0..aa72084 100644 --- a/composer.json +++ b/composer.json @@ -16,14 +16,18 @@ "laravel/nova": "^4.20" }, "require-dev": { - "interaction-design-foundation/coding-standard": "^0.0.5", - "orchestra/testbench": "^8.5", - "phpunit/phpunit": "^10.2" + "interaction-design-foundation/coding-standard": "^0.2.0", + "orchestra/testbench": "^8.3", + "phpunit/phpunit": "^10.5 || ^11.0", + "vimeo/psalm": "^5.22" }, "repositories": [ { "type": "composer", - "url": "https://nova.laravel.com" + "url": "https://nova.laravel.com", + "only": [ + "laravel/nova" + ] } ], "minimum-stability": "dev", @@ -40,7 +44,8 @@ }, "config": { "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true + "dealerdirect/phpcodesniffer-composer-installer": true, + "ergebnis/composer-normalize": true }, "sort-packages": true }, @@ -52,8 +57,10 @@ } }, "scripts": { + "cs": "@cs:fix", "cs:check": "phpcs -p -s --colors --report-full --report-summary", "cs:fix": "phpcbf -p --colors", + "psalm": "vendor/bin/psalm", "test": "phpunit --colors=always" } } diff --git a/psalm-baseline.xml b/psalm-baseline.xml new file mode 100644 index 0000000..8b64562 --- /dev/null +++ b/psalm-baseline.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..4f1ad3a --- /dev/null +++ b/psalm.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + diff --git a/src/CardServiceProvider.php b/src/CardServiceProvider.php index bdb218a..7645b70 100644 --- a/src/CardServiceProvider.php +++ b/src/CardServiceProvider.php @@ -4,7 +4,6 @@ use Illuminate\Support\Facades\Route; use Illuminate\Support\ServiceProvider; -use Laravel\Nova\Events\ServingNova; use Laravel\Nova\Nova; class CardServiceProvider extends ServiceProvider @@ -16,7 +15,7 @@ public function boot(): void $this->routes(); }); - Nova::serving(static function (ServingNova $event) { + Nova::serving(static function (): void { Nova::script('worldclock-card', __DIR__.'/../dist/js/card.js'); }); } diff --git a/src/Http/Controllers/WorldClockController.php b/src/Http/Controllers/WorldClockController.php index aabe3e5..8d1e6a9 100644 --- a/src/Http/Controllers/WorldClockController.php +++ b/src/Http/Controllers/WorldClockController.php @@ -19,9 +19,11 @@ public function timezones(Request $request): array $timeFormat = $request->input('timeFormat', 'h:i:s'); $nightHours = $request->input('nightHours'); + assert(is_array($nightHours) && array_is_list($nightHours) && count($nightHours) === 2, 'Night hours must be an array with two elements'); $hideContinents = $request->json('hideContinents') === true; $times = []; + /** @var string $timezone */ foreach ($request->input('timezones', []) as $timezone) { $time = now($timezone); $night = $this->isNight($time, (int) $nightHours[0], (int) $nightHours[1]); diff --git a/src/WorldClock.php b/src/WorldClock.php index 61065ee..115d180 100644 --- a/src/WorldClock.php +++ b/src/WorldClock.php @@ -10,7 +10,7 @@ class WorldClock extends Card * The width of the card (1/3, 1/2, or full). * @var string */ - public $width = '1/3'; + public $width = self::ONE_THIRD_WIDTH; /** * Create a new element. diff --git a/tests/Http/Controllers/WorldClockControllerTest.php b/tests/Http/Controllers/WorldClockControllerTest.php index 054a189..0e51a72 100644 --- a/tests/Http/Controllers/WorldClockControllerTest.php +++ b/tests/Http/Controllers/WorldClockControllerTest.php @@ -3,10 +3,11 @@ namespace InteractionDesignFoundation\WorldClockCard\Tests\Http\Controllers; use InteractionDesignFoundation\WorldClockCard\Tests\TestCase; +use PHPUnit\Framework\Attributes\Test; final class WorldClockControllerTest extends TestCase { - /** @test */ + #[Test] public function it_can_return_a_response(): void { $response = $this->post('nova-vendor/interaction-design-foundation/worldclock/timezones', [ @@ -18,7 +19,7 @@ public function it_can_return_a_response(): void $response->assertSuccessful(); } - /** @test */ + #[Test] public function it_accepts_empty_array_of_timezones(): void { $response = $this->post('nova-vendor/interaction-design-foundation/worldclock/timezones', [