Giter Club home page Giter Club logo

psalm-plugin-wordpress's Introduction

WordPress plugin for Psalm

Packagist Packagist

Write type-safe WordPress code.

This Psalm plugin provides all WordPress and WP CLI stubs, so your WordPress based project or plugin will have type information for calls to WordPress APIs. This ensures your WordPress plugin or theme has less bugs!

  • Stubs for all of WordPress Core
  • Stubs for WP CLI
  • Types for apply_filters return values.
  • Types for add_filter / add_action
  • Configuration options to use your own stubs

Installation

Please refer to the full Psalm documentation for a more detailed guide on introducing Psalm into your project.

After Psalm is installed, install this package and enable the plugin:

composer require --dev humanmade/psalm-plugin-wordpress
./vendor/bin/psalm-plugin enable humanmade/psalm-plugin-wordpress

Configuration

If you follow the installation instructions, the psalm-plugin command will add this plugin configuration to the psalm.xml configuration file.

<?xml version="1.0"?>
<psalm xmlns="https://getpsalm.org/schema/config">
	<!-- project configuration -->

	<plugins>
		<pluginClass class="PsalmWordPress\Plugin" />
	</plugins>
</psalm>

Further details about plugins can be found on Psalm's website.

Default WordPress stubs

If you do not want to use the default WordPress class/method/function stubs, which are part of this plugin, useDefaultStubs must be set to false:

<pluginClass class="PsalmWordPress\Plugin">
	<useDefaultStubs value="false" />
</pluginClass>

Default WordPress hooks

If you do not want to use the default WordPress hooks, which are part of this plugin, useDefaultHooks must be set to false:

<pluginClass class="PsalmWordPress\Plugin">
	<useDefaultHooks value="false" />
</pluginClass>

Custom hooks

You can also provide custom hooks:

<pluginClass class="PsalmWordPress\Plugin">
	<hooks>
		<directory name="some/dir/hooks" recursive="true" />
		<directory name="/absolute/other/dir/hooks" />
		<file name="my-special-hooks/actions.json" />
	</hooks>
</pluginClass>

If a directory is provided, the plugin will search for the following files:

  • actions.json
  • filters.json
  • hooks.json

The plugin expects a JSON representation of the hooks as per wp-hooks/generator.

Require passing all parameters

If you want to get an error if apply_filters and do_action do not have the same number of parameters in all cases, requireAllParams must be set to true:

<pluginClass class="PsalmWordPress\Plugin">
	<requireAllParams value="true" />
</pluginClass>

WordPress paths

To help Psalm analyze your project you might need to define some of WordPress' default global constants such as those for paths.

<?xml version="1.0"?>
<psalm autoloader="tests/bootstrap.php" xmlns="https://getpsalm.org/schema/config">
	<!-- project configuration -->
</psalm>

The following example bootstrap file is for a Bedrock installation:

<?php

require_once dirname( __DIR__ ) . '/config/application.php';

define( 'WPMU_PLUGIN_DIR', WP_CONTENT_DIR . '/mu-plugins' );

define( 'WP_PLUGIN_DIR', WP_CONTENT_DIR . '/plugins' );

You could require WordPress' default constants functions but that requires a lot more boilerplating to allow those functions to effectively define constants.

Interested in contributing?

Feel free to open a PR to fix bugs or add features!

In addition, have a look at Psalm's contribution guidelines.

Who made this

Created by @joehoyle, maintained by the Psalm community.

psalm-plugin-wordpress's People

Contributors

anomiex avatar dependabot[bot] avatar joehoyle avatar kkmuffme avatar ktomk avatar marcosh avatar mcaskill avatar ntwb avatar roborourke avatar ssnepenthe avatar superdav42 avatar weirdan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

psalm-plugin-wordpress's Issues

2.0.0: - humanmade/psalm-plugin-wordpress 2.0.0 requires vimeo/psalm dev-master -> satisfiable by vimeo/psalm[dev-master] but these conflict with your requirements or minimum-stability.

Follow-up #6

Unfortunately, dev-master will require projects to drop their minimum-stability stable -> dev.

$ composer require --dev humanmade/psalm-plugin-wordpress
Using version ^2.0 for humanmade/psalm-plugin-wordpress
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Installation request for humanmade/psalm-plugin-wordpress ^2.0 -> satisfiable by humanmade/psalm-plugin-wordpress[2.0.0].
    - humanmade/psalm-plugin-wordpress 2.0.0 requires vimeo/psalm dev-master -> satisfiable by vimeo/psalm[dev-master] but these conflict with your requirements or minimum-stability.

Perhaps there's another "latest stable" constraint expression available? Or we could do 4.x after all for now?

wp-hooks dependencies is deprecated

"johnbillion/wp-hooks": "^0.4.4",

Unfortunately, since it's version is <1, the caret treats each sub-version as separate version, so we're behind at 0.4.4 even though 0.7.1 is available.

The markup in the .json files has changed since 0.4.4

The new version also contains hooks for WP 5.8

Use my own hooks

Instead of loading only WP core hooks/filters, allow using my own hooks via config options.
This is helpful if I want to e.g. load WooCommerce or any custom plugin hooks that should be checked.

See #12 (comment)

Consider checking $function_to_add signature against $accepted_args

This is already implicitly checked since you are using $accepted_args to build up the list of params for $function_to_add, so it's understandable if there is no interest in this.

Still - it could be nice for Psalm to explicitly tell us that there is a mismatch between the two.

For example, given a call to add_filter where I forget to set $accepted_args like:

add_filter( 'some_filter', function( One $one, Two $two ) { return $one; } );

Psalm might show me something along the lines of:

ERROR: InvalidArgument - test.php:29:5 - Argument 2 of add_filter expects callable(One):(One), pure-Closure(One, Two):(One) provided (see https://psalm.dev/004)

As opposed to something like:

ERROR: TooFewArguments - test.php:29:5 - add_filter $accepted_args is 1 but some_filter callback accepts 2 arguments

The message could definitely use some work... Also TooFewArguments and TooManyArguments seemed to be the most sensible built-in issues I could find but a custom issue might make more sense.

Dynamic hooks trigger `HookNotFound`

Hi, it looks like dynamic hooks aren't supported yet. e.g., plugin_action_links_{$plugin_file}:

ERROR: HookNotFound - ../../../../../private/tmp/plugin-basic-google-maps-placemarksqDa7oc/settings.php:31:4 - Hook plugin_action_links_basic-google-maps-placemarks/basic-google-maps-placemarks.php not found.

add_filter( 'plugin_action_links_basic-google-maps-placemarks/basic-google-maps-placemarks.php', array( $this, 'addSettingsLink' ) );

https://github.com/humanmade/psalm-plugin-wordpress/blob/8fe21386fb4ff63d4253322dc3b8eaa1bb71c99f/Plugin.php#L245

I don't have any thoughts for an elegant solution at the moment, but I'll think about it some more. Worst case, a brute-force approach would probably work, but might have performance issues.

P.S. Thanks for creating this plugin, it's super helpful!

Uncaught ArgumentCountError: Too few arguments

Uncaught ArgumentCountError: Too few arguments to function Psalm\Internal\PhpVisitor\Reflector\FunctionLikeDocblockParser::parse(), 1 passed in ***\vendor\humanmade\psalm-plugin-wordpress\Plugin.php on line 330 and exactly 3 expected in ***\vendor\vimeo\psalm\src\Psalm\Internal\PhpVisitor\Reflector\FunctionLikeDocblockParser.php:41

Line 330
$comments = Psalm\Internal\PhpVisitor\Reflector\FunctionLikeDocblockParser::parse( $this->last_doc );

Line 41

public static function parse(
        PhpParser\Comment\Doc $comment,
        CodeLocation $code_location,
        string $cased_function_id
    ): FunctionDocblockComment {

psalm --version returns:
Psalm 4.26.0@6998fabb2bf528b65777bf9941920888d23c03ac

I'm new to Psalm and using it with WP. I'm not sure what else you need to know. Psalm runs fine if I remove

    <plugins>
        <pluginClass class="PsalmWordPress\Plugin"/>
    </plugins>

from my psalm.xml. But it natually then flags all the WP functions as errors :(

Perhaps I need to use a different version of Psalm and/or the humanmade WP plugin?

Again, this is all new to me so please type slowly if there are any instruction I need to follow :)

cannot install with current version of Psalm (v4.1)

it gives a composer error

  Problem 1
    - humanmade/psalm-plugin-wordpress 1.0.1 requires vimeo/psalm ^3.11 -> satisfiable by vimeo/psalm[3.11.0, 3.11.1, 3.11.2, 3.11.3, 3.11.4, 3.11.5, 3.11.6, 3.11.7, 3.12.0, 3.12.1, 3.12.2, 3.13.0, 3.13.1, 3.14.0, 3.14.1, 3.14.2, 3.15, 3.16, 3.17.0, 3.17.1, 3.17.2, 3.18.0, 3.18.2, 3.x-dev] but these conflict with your requirements or minimum-stability.
    - humanmade/psalm-plugin-wordpress 1.0.0 requires vimeo/psalm ^3.11 -> satisfiable by vimeo/psalm[3.11.0, 3.11.1, 3.11.2, 3.11.3, 3.11.4, 3.11.5, 3.11.6, 3.11.7, 3.12.0, 3.12.1, 3.12.2, 3.13.0, 3.13.1, 3.14.0, 3.14.1, 3.14.2, 3.15, 3.16, 3.17.0, 3.17.1, 3.17.2, 3.18.0, 3.18.2, 3.x-dev] but these conflict with your requirements or minimum-stability.
    - Installation request for humanmade/psalm-plugin-wordpress ^1.0 -> satisfiable by humanmade/psalm-plugin-wordpress[1.0.0, 1.0.1].

my composer entry looks like this

        "vimeo/psalm": "^4.1",

Why not just remove the requirement for Psalm because this plugin does not actually require Psalm. A fork of this repo has done just that. (I think they are using the stub files for PHPStan.)

Crashed due to an uncaught Throwable: Uncaught Exception: UnexpectedValueException

Hi all,

First of all thank you for the psalm plugin. I'm running into an crash while scanning my project after activating this Psalm plugin. After some Googling I landed on vimeo/psalm#3216 and analysed the the error and saw that it had to do with the activated Psalm plugin:

#4 [internal function]: PsalmWordpress\Plugin::PsalmWordpress\{closure}(Object(PhpParser\Node\Arg)) #5 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/humanmade/psalm-plugin-wordpress/Plugin.php(213): array_map(Object(Closure), Array)

Uncaught Exception: UnexpectedValueException There should be a node type provider
Emitted in /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php:667
Stack trace in the forked worker:
#0 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/SourceAnalyzer.php(180): Psalm\Internal\Analyzer\FileAnalyzer->getNodeTypeProvider()
#1 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/SourceAnalyzer.php(180): Psalm\Internal\Analyzer\SourceAnalyzer->getNodeTypeProvider()
#2 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/SourceAnalyzer.php(180): Psalm\Internal\Analyzer\SourceAnalyzer->getNodeTypeProvider()
#3 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/humanmade/psalm-plugin-wordpress/Plugin.php(191): Psalm\Internal\Analyzer\SourceAnalyzer->getNodeTypeProvider()
**#4 [internal function]: PsalmWordpress\Plugin::PsalmWordpress\{closure}(Object(PhpParser\Node\Arg))
#5 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/humanmade/psalm-plugin-wordpress/Plugin.php(213): array_map(Object(Closure), Array)**
#6 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/EventDispatcher.php(367): PsalmWordpress\Plugin::afterEveryFunctionCallAnalysis(Object(PhpParser\Node\Expr\FuncCall), 'apply_filters', Object(Psalm\Context), Object(Psalm\Internal\Analyzer\MethodAnalyzer), Object(Psalm\Codebase))
#7 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php(262): Psalm\Internal\EventDispatcher->dispatchAfterEveryFunctionCallAnalysis(Object(Psalm\Plugin\EventHandler\Event\AfterEveryFunctionCallAnalysisEvent))
#8 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php(296): Psalm\Internal\Analyzer\Statements\Expression\Call\FunctionCallAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\FuncCall), Object(Psalm\Context))
#9 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php(78): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::handleExpression(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\FuncCall), Object(Psalm\Context), false, NULL, false)
#10 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php(240): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\FuncCall), Object(Psalm\Context))
#11 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php(167): Psalm\Internal\Analyzer\Statements\Expression\AssignmentAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\Variable), Object(PhpParser\Node\Expr\FuncCall), NULL, Object(Psalm\Context), NULL)
#12 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php(78): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::handleExpression(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\Assign), Object(Psalm\Context), false, NULL, true)
#13 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(571): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\Assign), Object(Psalm\Context), false, NULL, true)
#14 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(206): Psalm\Internal\Analyzer\StatementsAnalyzer::analyzeStatement(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\Expression), Object(Psalm\Context), NULL)
#15 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php(476): Psalm\Internal\Analyzer\StatementsAnalyzer->analyze(Array, Object(Psalm\Context), NULL, true)
#16 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php(131): Psalm\Internal\Analyzer\FunctionLikeAnalyzer->analyze(Object(Psalm\Context), Object(Psalm\Internal\Provider\NodeDataProvider), NULL, true)
#17 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php(441): Psalm\Internal\Analyzer\ClassLikeAnalyzer->getMethodMutations('__construct', Object(Psalm\Context))
#18 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php(1413): Psalm\Internal\Analyzer\FileAnalyzer->getMethodMutations(Object(Psalm\Internal\MethodIdentifier), Object(Psalm\Context), true)
#19 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php(408): Psalm\Internal\Analyzer\ProjectAnalyzer->getMethodMutations(Object(Psalm\Internal\MethodIdentifier), Object(Psalm\Context), '/Users/jmslbam/...', 'themes/reiscomp...')
#20 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php(127): Psalm\Internal\Analyzer\FileAnalyzer->getMethodMutations(Object(Psalm\Internal\MethodIdentifier), Object(Psalm\Context))
#21 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php(853): Psalm\Internal\Analyzer\Statements\Expression\Call\StaticMethod\ExistingAtomicStaticCallAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\StaticCall), Object(PhpParser\Node\Identifier), Array, Object(Psalm\Context), Object(Psalm\Type\Atomic\TNamedObject), Object(Psalm\Internal\MethodIdentifier), 'SecureHoliday\\S...', Object(Psalm\Storage\ClassLikeStorage), false)
#22 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php(201): Psalm\Internal\Analyzer\Statements\Expression\Call\StaticMethod\AtomicStaticCallAnalyzer::handleNamedCall(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\StaticCall), Object(PhpParser\Node\Identifier), Object(Psalm\Context), Object(Psalm\Type\Atomic\TNamedObject), Array, 'SecureHoliday\\S...', false, true)
#23 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php(215): Psalm\Internal\Analyzer\Statements\Expression\Call\StaticMethod\AtomicStaticCallAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\StaticCall), Object(Psalm\Context), Object(Psalm\Type\Atomic\TNamedObject), false, false, false, true)
#24 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php(190): Psalm\Internal\Analyzer\Statements\Expression\Call\StaticCallAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\StaticCall), Object(Psalm\Context))
#25 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php(78): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::handleExpression(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\StaticCall), Object(Psalm\Context), false, Object(Psalm\Context), true)
#26 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(571): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\StaticCall), Object(Psalm\Context), false, Object(Psalm\Context), true)
#27 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(206): Psalm\Internal\Analyzer\StatementsAnalyzer::analyzeStatement(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\Expression), Object(Psalm\Context), Object(Psalm\Context))
#28 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php(476): Psalm\Internal\Analyzer\StatementsAnalyzer->analyze(Array, Object(Psalm\Context), Object(Psalm\Context), true)
#29 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassAnalyzer.php(1241): Psalm\Internal\Analyzer\FunctionLikeAnalyzer->analyze(Object(Psalm\Context), Object(Psalm\Internal\Provider\NodeDataProvider), Object(Psalm\Context), true)
#30 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassAnalyzer.php(539): Psalm\Internal\Analyzer\ClassAnalyzer->checkPropertyInitialization(Object(Psalm\Codebase), Object(Psalm\Config), Object(Psalm\Storage\ClassLikeStorage), Object(Psalm\Context), Object(Psalm\Context), Object(Psalm\Internal\Analyzer\MethodAnalyzer))
#31 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php(229): Psalm\Internal\Analyzer\ClassAnalyzer->analyze(Object(Psalm\Context), Object(Psalm\Context))
#32 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(362): Psalm\Internal\Analyzer\FileAnalyzer->analyze()
#33 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Fork/Pool.php(211): Psalm\Internal\Codebase\Analyzer->Psalm\Internal\Codebase\{closure}(1, '/Users/jmslbam/...')
#34 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(428): Psalm\Internal\Fork\Pool->__construct(Object(Psalm\Config), Array, Object(Closure), Object(Closure), Object(Closure), Object(Closure))
#35 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(291): Psalm\Internal\Codebase\Analyzer->doAnalysis(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 9)
#36 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php(685): Psalm\Internal\Codebase\Analyzer->analyzeFiles(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 9, false, true)
#37 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Cli/Psalm.php(373): Psalm\Internal\Analyzer\ProjectAnalyzer->check('/Users/jmslbam/...', true)
#38 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/psalm(7): Psalm\Internal\Cli\Psalm::run(Array)
#39 {main} in /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Fork/Pool.php:402
Stack trace:
#0 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Fork/Pool.php(436): Psalm\Internal\Fork\Pool->readResultsFromChildren()
#1 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(503): Psalm\Internal\Fork\Pool->wait()
#2 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(291): Psalm\Internal\Codebase\Analyzer->doAnalysis(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 9)
#3 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php(685): Psalm\Internal\Codebase\Analyzer->analyzeFiles(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 9, false, true)
#4 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Cli/Psalm.php(373): Psalm\Internal\Analyzer\ProjectAnalyzer->check('/Users/jmslbam/...', true)
#5 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/psalm(7): Psalm\Internal\Cli\Psalm::run(Array)
#6 {main}
(Psalm 4.23.0@f1fe6ff483bf325c803df9f510d09a03fd796f88 crashed due to an uncaught Throwable)

Adding newlines to the code always solves the issue:

Before (crashes)

if ( ! empty( $output ) ) {
	return $output;
}
return $this->primaryAccommodation->get_the_excerpt( true );

After (passes)

if ( ! empty( $output ) ) {
	return $output;
}

return $this->primaryAccommodation->get_the_excerpt( true );

Before (crashes)

if ( is_a( $oldTerm, 'WP_Error' ) ) {
	error_log( 'SH > Linker: ' . $oldTerm->get_error_code() );
	continue;
}

After (passes)

if ( is_a( $oldTerm, 'WP_Error' ) ) {
	error_log( 'SH > Linker: ' . $oldTerm->get_error_code() );

	continue;
}

I don't have any experience with writing Psalm plugins or Psalm in general. This is my first project with it, so I did wanted to let you know about the error / crash and the "fix". I do understand if this will become a won't fix, but I did wanted to drop it here for future devs that are Googling for the error may they encouter this situation.

Don't really now what to do to fix it, or which coding convention to follow, because the lines it crashes on don't really have a pattern...

Let me know if I can provide some extra information if needed.

Kinds regards and have nice weekend!

Jaime

Attempts to Use Non-Existing Files

The Symptom

Psalm continues to be ignorant of WP symbols even when the plugin is present and used.

Reproducing

  1. Use WP_Site anywhere in your code.
  2. Install this plugin and activate it with Psalm.
  3. Run Psalm.
  4. Observe symptom: Psalm will report UndefinedClass WP_Site.

Possible Cause

As described here, the plugin points Psalm to stub files that don't exist. The reasons are the following:

  1. php-stubs/wordpress-stubs is never installed, because it is a dev-dependency of this plugin, and this plugin wil be used in another project, whereas require-dev is root only.
  2. This plugin points to files in a vendor dir relative to its root dir, which of course would not exist because when used as a plugin the root will be elsewhere.

Current Working Directory must not have Vendor Folder

The project requires files out of its dependencies, that are stubs and wp-hooks metadata.

Previously while doing so the assumption is that the current working directory contains the vendor/... paths to the files.

This assumption is unchecked.

Fix is to verify the assumption of an existing directory and then continue with getcwd() as the base-path. If not and a path with a base relative to the plugin package itself resolves as a base-path for such paths, take it. Otherwise continue with getcwd() as base-path, even this may or may not result in non-existing file(s).

Migrate humanmade/psalm-plugin-wordpress to psalm/plugin-wordpress

This issue is to continue the discussion, from #41, and preparations for moving this repository to a new organization and adding additional maintainers.

The plan, so far, in no particular order, for the moment:

  • Tag and release one last version that is compatible with Psalm 4.
  • Integrate compatibility with Psalm 5 and all other features currently proposed for the plugin.
  • Awaiting approval from the Psalm team.
    • Move repository from humanmade to psalm.
      • GitHub repository org/name: psalm/psalm-plugin-wordpress
      • Optional. Composer package vendor/name: psalm/plugin-wordpress
        • Update Plugin.php to reflect Composer package name.
        • On Packagist, mark humanmade/psalm-plugin-wordpress as replaced with psalm/plugin-wordpress.

Interested/concerned parties: @joehoyle, @ntwb, @kkmuffme, @orklah

Plugin hooks deprecation

Just in case you hadn't seen this yet...

It looks like the psalm plugin hooks have been deprecated and could be removed as early as v5 (vimeo/psalm#4700 (comment)).

I suppose plugins are meant to use eventhandlers instead.

I didn't see anything anywhere about a timeline for v5 release.

Discussion on the value of type checks for hooks

I wonder how much value these type checks provide... Or rather if they risk introducing new problems by giving developers a false sense of type safety.

WordPress has no built-in type enforcement mechanism for hooks so a misbehaving theme or plugin is free to return any arbitrary value via a filter regardless of the documented type.

Further - there is no protection against third party code triggering core hooks, so initial values aren't really safe either.

For this reason I still generally prefer to be very defensive in action/filter callbacks.

Unfortunately it seems that this plugin discourages type checks by raising various errors for redundant conditions, type contradictions, etc.

Consider the following example:

add_filter( 'the_content', function( $content ) {
    if ( ! is_string( $content ) ) {
        throw new RuntimeException();
    }

    return $content;
} );

Running psalm gives us the following:

ERROR: TypeDoesNotContainType - psalm-wp-test.php:14:12 - Type string for $content is always string (see https://psalm.dev/056)

If we modify the example so that types are documented via docblock or native types, psalm will report type contradiction errors.

This is of course a very contrived example - If a plugin is destroying your post content you have much bigger problems.

For a more real-world example - I recently ran into an issue using the 'pre_post_link' filter where a 3rd party plugin managed to trigger the filter with a stdClass instance despite the documented type being WP_Post.

Maybe it is just me and I am overthinking things?

I don't really have a solution to propose, just curious what anybody else thinks about this and if you've run into similar issues?

Uncaught UnexpectedValueException

Env:

PHP 8.1
Psalm 5.19.0
psalm-plugin-wordpress: ~3.0.0

Running GitHub Action, crashed psalm due to an uncaught Throwable:

Uncaught UnexpectedValueException: Unsupported dynamic hook name with type PhpParser\Node\Expr\ConstFetch in /home/runner/work/PATH/vendor/humanmade/psalm-plugin-wordpress/Plugin.php:486
Stack trace:
#0 /home/runnerPATH/work//vendor/humanmade/psalm-plugin-wordpress/Plugin.php(405): PsalmWordPress\Plugin::getDynamicHookName()
#1 /home/runner/work/PATH/vendor/humanmade/psalm-plugin-wordpress/Plugin.php(825): PsalmWordPress\Plugin::getDynamicHookName()
#2 /home/runner/work/PATH/vendor/vimeo/psalm/src/Psalm/Internal/Provider/FunctionParamsProvider.php(83): PsalmWordPress\Plugin::getFunctionParams()
#3 /home/runner/work/PATH/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php(582): Psalm\Internal\Provider\FunctionParamsProvider->getFunctionParams()
#4 /home/runner/work/PATH/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php(148): Psalm\Internal\Analyzer\Statements\Expression\Call\FunctionCallAnalyzer::handleNamedFunction()
#5 /home/runner/work/PATH/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php(284): Psalm\Internal\Analyzer\Statements\Expression\Call\FunctionCallAnalyzer::analyze()
#6 /home/runner/work/PATH/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php(88): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::handleExpression()
#7 /home/runner/work/PATH/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(567): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::analyze()
#8 /home/runner/work/PATH/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(195): Psalm\Internal\Analyzer\StatementsAnalyzer::analyzeStatement()
#9 /home/runner/work/PATH/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php(507): Psalm\Internal\Analyzer\StatementsAnalyzer->analyze()
#10 /home/runner/work/PATH/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassAnalyzer.php(1822): Psalm\Internal\Analyzer\FunctionLikeAnalyzer->analyze()
#11 /home/runner/work/PATH/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassAnalyzer.php(433): Psalm\Internal\Analyzer\ClassAnalyzer->analyzeClassMethod()
#12 /home/runner/work/PATH/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php(202): Psalm\Internal\Analyzer\ClassAnalyzer->analyze()
#13 /home/runner/work/PATH/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(1591): Psalm\Internal\Analyzer\FileAnalyzer->analyze()
#14 /home/runner/work/PATH/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(522): Psalm\Internal\Codebase\Analyzer->analysisWorker()
#15 /home/runner/work/PATH/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(272): Psalm\Internal\Codebase\Analyzer->doAnalysis()
#16 /home/runner/work/PATH/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php(556): Psalm\Internal\Codebase\Analyzer->analyzeFiles()
#17 /home/runner/work/PATH/vendor/vimeo/psalm/src/Psalm/Internal/Cli/Psalm.php(373): Psalm\Internal\Analyzer\ProjectAnalyzer->check()
#18 /home/runner/work/PATH/vendor/vimeo/psalm/psalm(9): Psalm\Internal\Cli\Psalm::run()
#19 /home/runner/work/PATH/vendor/bin/psalm(119): include('...')
#20 ***main***
(Psalm 5.19.0@06b71be009a6bd6d81b9811855d6629b9fe90e1b crashed due to an uncaught Throwable)

GitHub Action:

jobs:
  code-checkout:
    uses: ./.github/workflows/reusable-code-checkout.yml
    secrets: inherit

  psalm:
    name: Psalm
    runs-on: ubuntu-latest
    needs: [ code-checkout ]

    steps:
      # The source is shared between workflows, so we need to fetch it.
      - name: Fetch shared source
        uses: actions/download-artifact@v4
        with:
          name: code-artifact

      # The shared source is compressed to avoid artifact rate limiting, extract it first.
      - name: Extract shared source
        working-directory: ./
        run: tar -zxf code-artifact.tar.gz

      - name: Setup PHP environment
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.1'
          tools: composer:v2
          extensions: gd, zip

      # Create cache locations that work across action containers to speed up testing if and when possible.
      - name: Cache composer dependencies
        uses: actions/cache@v3
        with:
          path: ~/.composer/cache
          key: dependencies-composer-${{ hashFiles('composer.lock') }}

      # Run the Composer installer, but this time including developer tools.
      - name: Install Composer Dev-dependencies
        working-directory: ./
        env:
          COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }}
        run: |
          composer install --no-progress

      - name: Setup Psalm cache
        uses: actions/cache@v3
        with:
          path: ~/.cache/psalm
          key: dependencies-psalm-${{ hashFiles('composer.lock') }}

      - name: Run Psalm analyzer
        working-directory: ./
        run: |
          composer run psalm

      - name: Upload Security Analysis results to GitHub
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: psalm-results.sarif

composer.json:

	"scripts": {
		"psalm": [
			"Composer\\Config::disableProcessTimeout",
			"psalm --output-format=github --no-diff --no-cache --taint-analysis --report=psalm-results.sarif"
		]
	}
}

The Psalm does not respect named arguments

On this line of code:

add_action('init', [$this, 'registerPostType'], accepted_args: 0);

I got two warnings:

psalm: HookInvalidArgs: Hook "init" does not accept any args, but the default number of args of add_action is 1. Please pass 0 as 4th argument
psalm: InvalidNamedArgument: Parameter $accepted_args does not exist on function add_action

Incorrect override for WP_CLI::add_command()

https://github.com/humanmade/psalm-plugin-wordpress/blob/c15b5d5159ef879f0707c33cc9a3fe993618edd1/stubs/overrides.php#L6-L14

add_command accepts a third parameter, $args which looks like:

@param array $args {
    Optional. An associative array with additional registration parameters.

    @type callable $before_invoke Callback to execute before invoking the command.
    @type callable $after_invoke  Callback to execute after invoking the command.
    @type string   $shortdesc     Short description (80 char or less) for the command.
    @type string   $longdesc      Description of arbitrary length for examples, etc.
    @type string   $synopsis      The synopsis for the command (string or array).
    @type string   $when          Execute callback on a named WP-CLI hook (e.g. before_wp_load).
    @type bool     $is_deferred   Whether the command addition had already been deferred.
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.