From 783ba5236f235ce3b31b2d9a763e3100d6d1e27c Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Sat, 17 Feb 2018 19:53:17 -0500 Subject: [PATCH] Add more docs to source control --- docs/checking_non_php_files.md | 13 +++ docs/configuration.md | 54 ++++++++++ docs/dealing_with_code_issues.md | 61 +++++++++++ docs/how_psalm_works.md | 2 + docs/installation.md | 8 ++ docs/running_psalm.md | 17 ++++ docs/typing_in_psalm.md | 170 +++++++++++++++++++++++++++++++ 7 files changed, 325 insertions(+) create mode 100644 docs/checking_non_php_files.md create mode 100644 docs/configuration.md create mode 100644 docs/dealing_with_code_issues.md create mode 100644 docs/installation.md create mode 100644 docs/running_psalm.md create mode 100644 docs/typing_in_psalm.md diff --git a/docs/checking_non_php_files.md b/docs/checking_non_php_files.md new file mode 100644 index 000000000..23a0bdf96 --- /dev/null +++ b/docs/checking_non_php_files.md @@ -0,0 +1,13 @@ +# Checking non-PHP files + +Psalm supports the ability to check various PHPish files by extending the `FileChecker` class. For example, if you have a template where the variables are set elsewhere, Psalm can scrape those variables and check the template with those variables pre-populated. + +An example TemplateChecker is provided [here](https://github.com/vimeo/psalm/blob/master/examples/TemplateChecker.php). + +To ensure your custom `FileChecker` is used, you must update the Psalm `fileExtensions` config in psalm.xml: +```xml + + + + +``` diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 000000000..c17493676 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,54 @@ +# Configuration + +Psalm uses an XML config file. A barebones example looks like this: + +```xml + + + + + + +``` + +### Options + +- `useDocblockTypes`
+ whether or not to use types as defined in docblocks. Defaults to `true`. +- `useDocblockPropertyTypes`
+ if not using all docblock types, you can still use docblock property types. Defaults to `false` (though only relevant if `useDocblockTypes` is `false`. +- `autoloader`
+ if your application registers one or more custom autoloaders, and/or declares universal constants/functions, this autoloader script will be executed by Psalm before scanning starts. Psalm always registers composer's autoloader by default. +- `throwExceptionOnError`
+ useful in testing, things makes Psalm throw a regular-old exception when it encounters an error. Defaults to `false`. +- `hideExternalErrors`
+ whether or not to show issues in files that are used by your project files, but which are not included in ``. Defaults to `false`. +- `cacheDirectory`
+ the directory used to store Psalm's cache data - if you specify one (and it does not already exist), its parent directory must already exist, otherwise Psalm will throw an error. +- `allowFileIncludes`
+ whether or not to allow `require`/`include` calls in your PHP. Defaults to `true`. +- `totallyTyped`
+ enabling this will make Psalm very strict, such that it needs to be able to evaluate the type of every single statement, and emitting a bevy of `Mixed*` issues if the types cannot be determined. Defaults to `false`. +- `strictBinaryOperands`
+ if true we force strict typing on numerical and string operations (see https://github.com/vimeo/psalm/issues/24) +- `requireVoidReturnType`
+ if `false`, Psalm will not complain when a function with no return types is missing an explicit `@return` annotation. Defaults to `true`. +- `useAssertForType`
+ Some like to use [`assert`](http://php.net/manual/en/function.assert.php) for type checks. If `true`, Psalm will process assertions inside `assert` calls. Defaults to `false`. +- `rememberPropertyAssignmentsAfterCall`
+ Setting this to `false` means that any function calls will cause Psalm to forget anything it knew about object properties within the scope of the function it's currently analysing. This duplicates functionality that Hack has. Defaults to `true`. + +### Project settings + +- ``
+ Contains a list of all the directories that Psalm should inspect +- `` (optional)
+ A list of extensions to search over. See [[Checking non-PHP files]] to understand how to extend this. +- `` (optional)
+ A list of `` entries. See the [[Plugins]] section for more information. +- `` (optional)
+ If you don't want Psalm to complain about every single issue it finds, the issueHandler tag allows you to configure that. [[Dealing with code issues]] tells you more. +- `` (optional)
+ Do you use mock classes in your tests? If you want Psalm to ignore them when checking files, include a fully-qualified path to the class with `` +- `` (optional)
+ If you codebase uses classes and functions that are not visible to Psalm via reflection (e.g. if there are internal packages that your codebase relies on that are not available on the machine running Psalm), you can use stub files. Used by PhpStorm (a popular IDE) and others, stubs provide a description of classes and functions without the implementations. You can find a list of stubs for common classes [here](https://github.com/JetBrains/phpstorm-stubs). List out each file with ``. diff --git a/docs/dealing_with_code_issues.md b/docs/dealing_with_code_issues.md new file mode 100644 index 000000000..b8ffe4e35 --- /dev/null +++ b/docs/dealing_with_code_issues.md @@ -0,0 +1,61 @@ +# Dealing with code issues + +Code issues in Psalm fall into three categories: +
+
error
+
this will cause Psalm to print a message, and to ultimately terminate with a non-zero exist status
+
info
+
this will cause Psalm to print a message
+
suppress
+
this will cause Psalm to ignore the code issue entirely
+
+ +The third category, `suppress`, is the one you will probably be most interested in, especially when introducing Psalm to a large codebase. + +## Suppressing issues + +There are two ways to suppress an issue – via the Psalm config or via a function docblock. + +### Config suppression + +You can use the `` tag in the config file to influence how issues are treated. + +```xml + + + + + + + + + + +``` + +### Docblock suppression + +You can also use `@psalm-suppress IssueName` on a function's docblock to suppress Psalm issues e.g. + +```php +/** + * @psalm-suppress InvalidReturnType + */ +function (int $a) : string { + return $a; +} +``` + +You can also suppress issues at the line level e.g. + +```php +/** + * @psalm-suppress InvalidReturnType + */ +function (int $a) : string { + /** + * @psalm-suppress InvalidReturnStatement + */ + return $a; +} +``` diff --git a/docs/how_psalm_works.md b/docs/how_psalm_works.md index 20dfd7799..dbaf94749 100644 --- a/docs/how_psalm_works.md +++ b/docs/how_psalm_works.md @@ -1,3 +1,5 @@ +# How Psalm works + The entry point for all analysis is [`ProjectChecker`](https://github.com/vimeo/psalm/blob/master/src/Psalm/Checker/ProjectChecker.php) `ProjectChecker` is in charge of two things: Scanning and Analysis diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 000000000..fa279cbdf --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,8 @@ +# Installation + +Psalm Requires PHP >= 5.4 and [Composer](https://getcomposer.org/). + +```bash +> composer require --dev "vimeo/psalm:dev-master" +> composer install +``` diff --git a/docs/running_psalm.md b/docs/running_psalm.md new file mode 100644 index 000000000..8ebc67ed7 --- /dev/null +++ b/docs/running_psalm.md @@ -0,0 +1,17 @@ +# Running Psalm + +Once you've set up your config file, you can run Psalm from your project's root directory with +```bash +./vendor/bin/psalm +``` + +and Psalm will scan all files in the project referenced by ``. + +If you want to run on specific files, use +```bash +./vendor/bin/psalm file1.php [file2.php...] +``` + +### Command-line options + +Run with `--help` to see a list of options that Psalm supports. diff --git a/docs/typing_in_psalm.md b/docs/typing_in_psalm.md new file mode 100644 index 000000000..06224100e --- /dev/null +++ b/docs/typing_in_psalm.md @@ -0,0 +1,170 @@ +# Typing in Psalm + +Psalm is able to interpret all PHPDoc type annotations, and use them to further understand the codebase. + +## Union Types + +PHP and other dynamically-typed languages allow expressions to resolved to conflicting types – for example, after this statement +```php +$rabbit = rand(0, 10) === 4 ? 'rabbit' : ['rabbit']; +``` +`$rabbit` will be either a `string` or an `array`. We can represent that idea with Union Types – so `$rabbit` is typed as `string|array`. Union types represent *all* the possible types a given variable can have. + +### Use of `false` in Union Types + +This also extends to builtin PHP methods, many of which can return `false` to denote some sort of failure. For example, `strpos` has the return type `int|false`. This is a more specific version of `int|bool`, and allows us to evaluate logic like +```php +function str_index_of(string $haystack, string $needle) : int { + $pos = strpos($haystack, $needle); + if ($pos === false) { + return -1; + } + return $pos; +} +``` +and verify that `str_index_of` *always* returns an integer. If we instead typed the return of `strpos` as `int|bool`, then according to Psalm the last statement `return $pos` could return either an integer or `true` (the solution would be to turn `if ($pos === false)` into `if (is_bool($pos))`. + +## Property declaration types vs Assignment typehints + +You can use the `/** @var Type */` docblock to annotate both [property declarations](http://php.net/manual/en/language.oop5.properties.php) and to help Psalm understand variable assignment. + +### Property declaration types + +You can specify a particular type for a class property declarion in Psalm by using the `@var` declaration: + +```php +/** @var string|null */ +public $foo; +``` + +When checking `$this->foo = $some_variable;`, Psalm will check to see whether `$some_variable` is either `string` or `null` and, if neither, emit an issue. + +If you leave off the property type docblock, Psalm will emit a `MissingPropertyType` issue. + +### Assignment typehints + +Consider the following code: + +```php +$a = null; + +foreach ([1, 2, 3] as $i) { + if ($a) { + return $a; + } + else { + $a = $i; + } +} +``` + +Because Psalm scans a file progressively, it cannot tell that `return $a` produces an integer. Instead it knows only that `$a` is not `empty`. We can fix this by adding a type hint docblock: + +```php +/** @var int|null */ +$a = null; + +foreach ([1, 2, 3] as $i) { + if ($a) { + return $a; + } + else { + $a = $i; + } +} +``` + +This tells Psalm that `int` is a possible type for `$a`, and allows it to infer that `return $a;` produces an integer. + +Unlike property types, however, assignment typehints are not binding – they can be overridden by a new assignment without Psalm emitting an issue e.g. + +```php +/** @var string|null */ +$a = foo(); +$a = 6; // $a is now typed as an int +``` + +You can also use typehints on specific variables e.g. + +```php +/** @var string $a */ +echo strpos($a, 'hello'); +``` + +This tells Psalm to assume that `$a` is a string (though it will still throw an error if `$a` is undefined). + +### Typing arrays + +In PHP, the `array` type is commonly used to represent three different data structures: + - a [List](https://en.wikipedia.org/wiki/List_(abstract_data_type)) + + ```php + $a = [1, 2, 3, 4, 5]; + ``` + - an [Associative array](https://en.wikipedia.org/wiki/Associative_array) + + ```php + $a = [0 => 'hello', 5 => 'goodbye']; + $b = ['a' => 'AA', 'b' => 'BB', 'c' => 'CC'] + ``` + - makeshift [Structs](https://en.wikipedia.org/wiki/Struct_(C_programming_language)) + + ```php + $a = ['name' => 'Psalm', 'type' => 'tool']; + ``` + +PHP treats all these arrays the same, essentially (though there are some optimisations under the hood for the first case). + +PHPDoc [allows you to specify](https://phpdoc.org/docs/latest/references/phpdoc/types.html#arrays) the type of values the array holds with the annotation: +```php +/** @return TValue[] */ +``` + +where `TValue` is a union type, but it does not allow you to specify the type of keys. + +Psalm uses a syntax [borrowed from Java](https://en.wikipedia.org/wiki/Generics_in_Java) to denote the types of both keys *and* values: +```php +/** @return array */ +``` + +### Makeshift Structs + +Ideally (in the author's opinion), all data would either be encoded as lists, associative arrays, or as well-defined objects. However, PHP arrays are often used as makeshift structs. + +[Hack](http://hacklang.org/) supports this usage by way of the [Shape datastructure](https://docs.hhvm.com/hack/shapes/introduction), but there is no agreed-upon documentation format for such arrays in regular PHP-land. + +Psalm solves this by adding another way annotate array types, by using an object-like syntax when describing them. + +So, for instance, the method below returns an array of arrays, both of which have the same keys: +```php +/** @return array> */ +function getToolsData() : array { + return [ + ['name' => 'Psalm', 'type' => 'tool', 'active' => true], + ['name' => 'PhpParser', 'type' => 'tool', 'active' => true] + ]; +} +``` + +Using the type annotation for associative arrays, we could evaluate the expression +```php +getToolsData()[0]['name'] +``` +and Psalm would know that it was had the type `string|bool`. + +However, we can provide a more-specific return type by using a brace annotation: +```php +/** @return array */ +function getToolsData() : array { + return [ + ['name' => 'Psalm', 'type' => 'tool', 'active' => true], + ['name' => 'PhpParser', 'type' => 'tool', 'active' => true] + ]; +} +``` + +This time, Psalm can evaluate `getToolsData()[0]['name']` and it knows that the expression evaluates to a string. + +### Backwards compatibility + +Psalm fully supports PHPDoc's array typing syntax, such that any array typed with `TValue[]` will be typed in Psalm as `array`. That also extends to generic type definitions with only one param e.g. `array`, which is equivalent to `array`.