From 858b886c43586ad837d675ce540a43aad0c89334 Mon Sep 17 00:00:00 2001 From: yaegassy Date: Tue, 5 Jul 2022 10:03:00 +0900 Subject: [PATCH 1/2] feat: add "laravel/pint" integration feature --- README.md | 75 +- package.json | 49 +- schemas/README.md | 5 + schemas/pint-schema.json | 2128 +++++++++++++++++ src/actions/{fix.ts => pcfFix.ts} | 4 +- src/actions/pintFix.ts | 59 + src/commands/{download.ts => pcfDownload.ts} | 3 +- src/commands/{fix.ts => pcfFix.ts} | 2 +- src/commands/pintDownload.ts | 30 + src/commands/pintFix.ts | 21 + src/common.ts | 36 +- src/documentFormats/{fixer.ts => pcfFix.ts} | 6 +- src/documentFormats/pintFix.ts | 51 + .../pcfDownloader.ts} | 0 src/downloaders/pintDownloader.ts | 53 + src/{engine.ts => engines/pcfEngine.ts} | 16 +- src/engines/pintEngine.ts | 99 + src/index.ts | 59 +- src/statusBar.ts | 10 +- 19 files changed, 2650 insertions(+), 56 deletions(-) create mode 100644 schemas/README.md create mode 100644 schemas/pint-schema.json rename src/actions/{fix.ts => pcfFix.ts} (92%) create mode 100644 src/actions/pintFix.ts rename src/commands/{download.ts => pcfDownload.ts} (89%) rename src/commands/{fix.ts => pcfFix.ts} (93%) create mode 100644 src/commands/pintDownload.ts create mode 100644 src/commands/pintFix.ts rename src/documentFormats/{fixer.ts => pcfFix.ts} (87%) create mode 100644 src/documentFormats/pintFix.ts rename src/{downloader.ts => downloaders/pcfDownloader.ts} (100%) create mode 100644 src/downloaders/pintDownloader.ts rename src/{engine.ts => engines/pcfEngine.ts} (87%) create mode 100644 src/engines/pintEngine.ts diff --git a/README.md b/README.md index 00fa730..e97bbff 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,45 @@ # coc-php-cs-fixer -[PHP CS Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) (PHP Coding Standards Fixer) extension for [coc.nvim](https://github.com/neoclide/coc.nvim) +[PHP CS Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) (PHP Coding Standards Fixer) and [Laravel Pint](https://github.com/laravel/pint) extension for [coc.nvim](https://github.com/neoclide/coc.nvim) ## Install `:CocInstall coc-php-cs-fixer` +## Features + +`php-cs-fixer` and `laravel/pint` are supported. + +- Formatter +- Command +- Code Action +- Status Bar +- `pint.json` Auto Completion and JSON validation +- Downloader + ## Note -Detects the `php-cs-fixer` command. They are prioritized in order from the top. +The formatter tool used is `php-cs-fixer` by default. If you want to use `laravel/pint`, change the `php-cs-fixer.activateTool` setting in `coc-settings.json`. + +```json +{ + "php-cs-fixer.activateTool": "pint" +} +``` -1. `php-cs-fixer.toolPath` -1. `vendor/bin/php-cs-fixer` -1. `php-cs-fixer` retrieved by the download feature (`:CocCommand php-cs-fixer.download`) - - Mac/Linux: `~/.config/coc/extensions/coc-php-cs-fixer-data/php-cs-fixer` - - Windows: `~/AppData/Local/coc/extensions/coc-php-cs-fixer-data/php-cs-fixer` +--- + +Detects the `php-cs-fixer` or `pint` tool. They are prioritized in order from the top. + +1. `php-cs-fixer.toolPath` or `php-cs-fixer.pint.toolPath` +1. `vendor/bin/php-cs-fixer` or `vendor/bin/pint` +1. `php-cs-fixer` retrieved by the download feature (`:CocCommand php-cs-fixer.download` or `php-cs-fixer.pintDownload`) + - **php-cs-fixer**: + - Mac/Linux: `~/.config/coc/extensions/coc-php-cs-fixer-data/php-cs-fixer` + - Windows: `~/AppData/Local/coc/extensions/coc-php-cs-fixer-data/php-cs-fixer` + - **pint**: + - Mac/Linux: `~/.config/coc/extensions/coc-php-cs-fixer-data/pint` + - Windows: `~/AppData/Local/coc/extensions/coc-php-cs-fixer-data/pint` If "1" and "2" above are not detected, the download feature will be executed (The prompt will be displayed) @@ -36,7 +61,10 @@ Add the settings to `coc-settings.json`. ### Run from CocCommand -- `:CocCommand php-cs-fixer.fix` +- If the `php-cs-fixer.activateTool` setting is `php-cs-fixer` + - `:CocCommand php-cs-fixer.fix` +- If the `php-cs-fixer.activateTool` setting is `pint` + - `:CocCommand php-cs-fixer.pintFix` ### Run formatting from call function @@ -45,24 +73,39 @@ Add the settings to `coc-settings.json`. ### Run codeAction from call function - `:call CocAction('codeAction')` - - Choose action: "Run: php-cs-fixer.fix" + - If the `php-cs-fixer.activateTool` setting is `php-cs-fixer` + - Choose action: `"Run: php-cs-fixer.fix"` + - If the `php-cs-fixer.activateTool` setting is `pint` + - Choose action: `"Run: php-cs-fixer.pintFix"` -## Precedence of php-cs-fixer config files and options +## Precedence of "php-cs-fixer" and "laravel/pint" configuration files and options + +### php-cs-fixer 1. `php-cs-fixer.config` setting for this extension. 2. `.php-cs-fixer.php` or `.php-cs-fixer.dist.php` config file in the workspace (project) root. 3. options-reated settings for this extension. e.g. `php-cs-fixer.rules` and more. +### pint + +1. `php-cs-fixer.pint.config` setting for this extension. +2. `pint.json` config file in the workspace (project) root. +3. options-reated settings for this extension. `php-cs-fixer.pint.preset`. + ## Configuration options - `php-cs-fixer.enable`: Enable coc-php-cs-fixer extension, default: `true` +- `php-cs-fixer.activateTool`: Formatter tool to be used, valid option `["php-cs-fixer", "pint"]`, default: `"php-cs-fixer"` - `php-cs-fixer.toolPath`: The path to the php-cs-fixer tool (Absolute path), default: `""` - `php-cs-fixer.config`: Path to php-cs-fixer config file (--config), default: `""` - `php-cs-fixer.useCache`: Use a cache file when fixing files (--using-cache), default: `false` - `php-cs-fixer.allowRisky`: Determines whether risky rules are allowed (--allow-risky), default: `false` - `php-cs-fixer.rules`: Rules to use when fixing files (--rules), e.g. `"@PSR12,@Symfony"`, default: `"@PSR12"` - `php-cs-fixer.enableIgnoreEnv`: Add the environment variable `PHP_CS_FIXER_IGNORE_ENV=1` and run php-cs-fixer, default: `false` -- `php-cs-fixer.downloadCheckOnStartup`: Perform built-in download if php-cs-fixer is not present at startup, default: `true` +- `php-cs-fixer.pint.toolPath`: The path to the pint tool (Absolute path), default: `""` +- `php-cs-fixer.pint.config`: Path to `pint.json` config file (`--config`), default: `""` +- `php-cs-fixer.pint.preset`: Presets define a set of rules that can be used to fix code style issues in your code (`--preset`), valid option `["laravel", "psr12", "symfony"]`, default: `"laravel"` +- `php-cs-fixer.downloadCheckOnStartup`: If `php-cs-fixer` or `pint` is not present at startup, run the built-in download. The tool to be downloaded will follow the `php-cs-fixer.activateTool` configuration, default: `true` - `php-cs-fixer.downloadMajorVersion`: Specify the major version of php-cs-fixer to download for the extension, valid option `[2, 3]`, default: `3` - `php-cs-fixer.enableFormatProvider`: Enable format provider, default: `true` - `php-cs-fixer.enableActionProvider`: Enable codeAction provider, default: `true` @@ -70,13 +113,23 @@ Add the settings to `coc-settings.json`. ## Commands - `php-cs-fixer.fix`: Run php-cs-fixer fix +- `php-cs-fixer.pintFix`: Run pint - `php-cs-fixer.download`: Download php-cs-fixer - By default, the "v3" series will be downloaded. If you want to download "v2" series, please change the `php-cs-fixer.downloadMajorVersion` setting. +- `php-cs-fixer.pintDownload`: Download pint - `php-cs-fixer.showOutput`: Show php-cs-fixer output channel ## Code Actions - `Run: php-cs-fixer.fix` +- `Run: php-cs-fixer.pintFix` + +## Thanks + +- +- +- +- ## License diff --git a/package.json b/package.json index c1d5e55..9482276 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "url": "https://github.com/yaegassy/coc-php-cs-fixer" }, "scripts": { + "schema": "curl -o schemas/pint-schema.json https://raw.githubusercontent.com/open-southeners/vscode-laravel-pint/main/pint-schema.json", "lint": "eslint src --ext ts", "clean": "rimraf lib", "watch": "node esbuild.js --watch", @@ -53,6 +54,12 @@ "onLanguage:php" ], "contributes": { + "jsonValidation": [ + { + "fileMatch": "pint.json", + "url": "./schemas/pint-schema.json" + } + ], "configuration": { "type": "object", "title": "coc-php-cs-fixer configuration", @@ -61,7 +68,8 @@ "filetype": "php", "patterns": [ ".php-cs-fixer.php", - ".php-cs-fixer.dist.php" + ".php-cs-fixer.dist.php", + "pint.json" ] } ], @@ -71,6 +79,15 @@ "default": true, "description": "Enable coc-php-cs-fixer extension" }, + "php-cs-fixer.activateTool": { + "type": "string", + "default": "php-cs-fixer", + "description": "", + "enum": [ + "php-cs-fixer", + "pint" + ] + }, "php-cs-fixer.toolPath": { "type": "string", "default": "", @@ -104,7 +121,7 @@ "php-cs-fixer.downloadCheckOnStartup": { "type": "boolean", "default": true, - "description": "Perform built-in download if php-cs-fixer is not present at startup" + "description": "If `php-cs-fixer` or `pint` is not present at startup, run the built-in download. The tool to be downloaded will follow the `php-cs-fixer.activateTool` configuration" }, "php-cs-fixer.downloadMajorVersion": { "type": "number", @@ -116,6 +133,26 @@ "description": "Specify the major version of php-cs-fixer to download for the extension", "scope": "window" }, + "php-cs-fixer.pint.toolPath": { + "type": "string", + "default": "", + "description": "The path to the pint tool (Absolute path)" + }, + "php-cs-fixer.pint.config": { + "type": "string", + "default": "", + "description": "Path to pint.json config file (--config)" + }, + "php-cs-fixer.pint.preset": { + "type": "string", + "default": "laravel", + "description": "Presets define a set of rules that can be used to fix code style issues in your code (--preset)", + "enum": [ + "laravel", + "psr12", + "symfony" + ] + }, "php-cs-fixer.enableFormatProvider": { "type": "boolean", "default": true, @@ -140,6 +177,14 @@ { "command": "php-cs-fixer.showOutput", "title": "Show php-cs-fixer output channel" + }, + { + "command": "php-cs-fixer.pintFix", + "title": "Run pint" + }, + { + "command": "php-cs-fixer.pintDownload", + "title": "Download pint" } ] } diff --git a/schemas/README.md b/schemas/README.md new file mode 100644 index 0000000..8f86f63 --- /dev/null +++ b/schemas/README.md @@ -0,0 +1,5 @@ +# Schemas + +## pint-schema.json + +- diff --git a/schemas/pint-schema.json b/schemas/pint-schema.json new file mode 100644 index 0000000..85e1da3 --- /dev/null +++ b/schemas/pint-schema.json @@ -0,0 +1,2128 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "title": "Laravel Pint", + "type": "object", + "properties": { + "preset": { + "type": "string", + "description": "Preset that applies a group of rules to the formatting.", + "default": "laravel", + "oneOf": [ + { + "enum": [ + "laravel", + "symfony", + "psr-12" + ] + } + ] + }, + "exclude": { + "type": "array", + "description": "List of folders to exclude.", + "items": { + "type": "string" + } + }, + "notName": { + "type": "array", + "description": "List of file name patterns to exclude.", + "items": { + "type": "string" + } + }, + "notPath": { + "type": "array", + "description": "List of exact file paths to exclude.", + "items": { + "type": "string" + } + }, + "rules": { + "type": "object", + "description": "Customise rules used for the formatting, this replaces the ones of the preset", + "properties": { + "align_multiline_comment": { + "description": "Each line of multi-line DocComments must have an asterisk [PSR-5] and must be aligned with the first one." + }, + "array_indentation": { + "description": "Each element of an array must be indented exactly once." + }, + "array_push": { + "description": "Converts simple usages of `array_push($x, $y);` to `$x[] = $y;`." + }, + "array_syntax": { + "description": "PHP arrays should be declared using the configured syntax." + }, + "assign_null_coalescing_to_coalesce_equal": { + "description": "Use the null coalescing assignment operator `??=` where possible." + }, + "backtick_to_shell_exec": { + "description": "Converts backtick operators to `shell_exec` calls." + }, + "binary_operator_spaces": { + "description": "Binary operators should be surrounded by space as configured.", + "type": "object", + "properties": { + "default": { + "description": "Default fix strategy.", + "default": "single_space", + "oneOf": [ + { + "enum": [ + "align", + "align_single_space", + "align_single_space_minimal", + "single_space", + "no_space", + null + ] + } + ] + }, + "operators": { + "description": "Dictionary of `binary operator` => `fix strategy` values that differ from the default strategy. Supported are: `=`, `*`, `/`, `%`, `<`, `>`, `|`, `^`, `+`, `-`, `&`, `&=`, `&&`, `||`, `.=`, `/=`, `=>`, `==`, `>=`, `===`, `!=`, `<>`, `!==`, `<=`, `and`, `or`, `xor`, `-=`, `%=`, `*=`, `|=`, `+=`, `<<`, `<<=`, `>>`, `>>=`, `^=`, `**`, `**=`, `<=>`, `??`, `??=`", + "default": {}, + "type": "array" + } + } + }, + "blank_line_after_namespace": { + "description": "There MUST be one blank line after the namespace declaration." + }, + "blank_line_after_opening_tag": { + "description": "Ensure there is no code on the same line as the PHP open tag and it is followed by a blank line." + }, + "blank_line_before_statement": { + "description": "An empty line feed must precede any configured statement." + }, + "braces": { + "description": "The body of each structure MUST be enclosed by braces. Braces should be properly placed. Body of braces should be properly indented.", + "type": "object", + "properties": { + "allow_single_line_anonymous_class_with_empty_body": { + "description": "Whether single line anonymous class with empty body notation should be allowed.", + "default": false, + "type": "boolean" + }, + "allow_single_line_closure": { + "description": "Whether single line lambda notation should be allowed.", + "default": false, + "type": "boolean" + }, + "position_after_anonymous_constructs": { + "description": "whether the opening brace should be placed on \"next\" or \"same\" line after anonymous constructs (anonymous classes and lambda functions).", + "default": "same", + "oneOf": [ + { + "enum": [ + "next", + "same" + ] + } + ] + }, + "position_after_control_structures": { + "description": "whether the opening brace should be placed on \"next\" or \"same\" line after control structures.", + "default": "same", + "oneOf": [ + { + "enum": [ + "next", + "same" + ] + } + ] + }, + "position_after_functions_and_oop_constructs": { + "description": "whether the opening brace should be placed on \"next\" or \"same\" line after classy constructs (non-anonymous classes, interfaces, traits, methods and non-lambda functions).", + "default": "next", + "oneOf": [ + { + "enum": [ + "next", + "same" + ] + } + ] + } + } + }, + "cast_spaces": { + "description": "A single space or none should be between cast and variable." + }, + "class_attributes_separation": { + "description": "Class, trait and interface elements must be separated with one or none blank line." + }, + "class_definition": { + "description": "Whitespace around the keywords of a class, trait, enum or interfaces definition should be one space.", + "type": "object", + "properties": { + "inline_constructor_arguments": { + "description": "Whether constructor argument list in anonymous classes should be single line.", + "default": true, + "type": "boolean" + }, + "multi_line_extends_each_single_line": { + "description": "Whether definitions should be multiline.", + "default": false, + "type": "boolean" + }, + "single_item_single_line": { + "description": "Whether definitions should be single line when including a single item.", + "default": false, + "type": "boolean" + }, + "single_line": { + "description": "Whether definitions should be single line.", + "default": false, + "type": "boolean" + }, + "space_before_parenthesis": { + "description": "Whether there should be a single space after the parenthesis of anonymous class (PSR12) or not.", + "default": false, + "type": "boolean" + } + } + }, + "class_keyword_remove": { + "description": "Converts `::class` keywords to FQCN strings." + }, + "class_reference_name_casing": { + "description": "When referencing an internal class it must be written using the correct casing." + }, + "clean_namespace": { + "description": "Namespace must not contain spacing, comments or PHPDoc." + }, + "combine_consecutive_issets": { + "description": "Using `isset($var) &&` multiple times should be done in one call." + }, + "combine_consecutive_unsets": { + "description": "Calling `unset` on multiple items should be done in one call." + }, + "combine_nested_dirname": { + "description": "Replace multiple nested calls of `dirname` by only one call with second `$level` parameter. Requires PHP >= 7.0." + }, + "comment_to_phpdoc": { + "description": "Comments with annotation should be docblock when used on structural elements." + }, + "compact_nullable_typehint": { + "description": "Remove extra spaces in a nullable typehint." + }, + "concat_space": { + "description": "Concatenation should be spaced according configuration." + }, + "constant_case": { + "description": "The PHP constants `true`, `false`, and `null` MUST be written using the correct casing." + }, + "control_structure_continuation_position": { + "description": "Control structure continuation keyword must be on the configured line." + }, + "date_time_create_from_format_call": { + "description": "The first argument of `DateTime::createFromFormat` method must start with `!`." + }, + "date_time_immutable": { + "description": "Class `DateTimeImmutable` should be used instead of `DateTime`." + }, + "declare_equal_normalize": { + "description": "Equal sign in declare statement should be surrounded by spaces or not following configuration." + }, + "declare_parentheses": { + "description": "There must not be spaces around `declare` statement parentheses." + }, + "declare_strict_types": { + "description": "Force strict types declaration in all files. Requires PHP >= 7.0." + }, + "dir_constant": { + "description": "Replaces `dirname(__FILE__)` expression with equivalent `__DIR__` constant." + }, + "doctrine_annotation_array_assignment": { + "description": "Doctrine annotations must use configured operator for assignment in arrays.", + "type": "object", + "properties": { + "ignored_tags": { + "description": "List of tags that must not be treated as Doctrine Annotations.", + "default": [ + "abstract", + "access", + "code", + "deprec", + "encode", + "exception", + "final", + "ingroup", + "inheritdoc", + "inheritDoc", + "magic", + "name", + "toc", + "tutorial", + "private", + "static", + "staticvar", + "staticVar", + "throw", + "api", + "author", + "category", + "copyright", + "deprecated", + "example", + "filesource", + "global", + "ignore", + "internal", + "license", + "link", + "method", + "package", + "param", + "property", + "property-read", + "property-write", + "return", + "see", + "since", + "source", + "subpackage", + "throws", + "todo", + "TODO", + "usedBy", + "uses", + "var", + "version", + "after", + "afterClass", + "backupGlobals", + "backupStaticAttributes", + "before", + "beforeClass", + "codeCoverageIgnore", + "codeCoverageIgnoreStart", + "codeCoverageIgnoreEnd", + "covers", + "coversDefaultClass", + "coversNothing", + "dataProvider", + "depends", + "expectedException", + "expectedExceptionCode", + "expectedExceptionMessage", + "expectedExceptionMessageRegExp", + "group", + "large", + "medium", + "preserveGlobalState", + "requires", + "runTestsInSeparateProcesses", + "runInSeparateProcess", + "small", + "test", + "testdox", + "ticket", + "uses", + "SuppressWarnings", + "noinspection", + "package_version", + "enduml", + "startuml", + "psalm", + "phpstan", + "template", + "fix", + "FIXME", + "fixme", + "override" + ], + "type": "array" + }, + "operator": { + "description": "The operator to use.", + "default": "=", + "oneOf": [ + { + "enum": [ + "=", + ":" + ] + } + ] + } + } + }, + "doctrine_annotation_braces": { + "description": "Doctrine annotations without arguments must use the configured syntax.", + "type": "object", + "properties": { + "ignored_tags": { + "description": "List of tags that must not be treated as Doctrine Annotations.", + "default": [ + "abstract", + "access", + "code", + "deprec", + "encode", + "exception", + "final", + "ingroup", + "inheritdoc", + "inheritDoc", + "magic", + "name", + "toc", + "tutorial", + "private", + "static", + "staticvar", + "staticVar", + "throw", + "api", + "author", + "category", + "copyright", + "deprecated", + "example", + "filesource", + "global", + "ignore", + "internal", + "license", + "link", + "method", + "package", + "param", + "property", + "property-read", + "property-write", + "return", + "see", + "since", + "source", + "subpackage", + "throws", + "todo", + "TODO", + "usedBy", + "uses", + "var", + "version", + "after", + "afterClass", + "backupGlobals", + "backupStaticAttributes", + "before", + "beforeClass", + "codeCoverageIgnore", + "codeCoverageIgnoreStart", + "codeCoverageIgnoreEnd", + "covers", + "coversDefaultClass", + "coversNothing", + "dataProvider", + "depends", + "expectedException", + "expectedExceptionCode", + "expectedExceptionMessage", + "expectedExceptionMessageRegExp", + "group", + "large", + "medium", + "preserveGlobalState", + "requires", + "runTestsInSeparateProcesses", + "runInSeparateProcess", + "small", + "test", + "testdox", + "ticket", + "uses", + "SuppressWarnings", + "noinspection", + "package_version", + "enduml", + "startuml", + "psalm", + "phpstan", + "template", + "fix", + "FIXME", + "fixme", + "override" + ], + "type": "array" + }, + "syntax": { + "description": "Whether to add or remove braces.", + "default": "without_braces", + "oneOf": [ + { + "enum": [ + "with_braces", + "without_braces" + ] + } + ] + } + } + }, + "doctrine_annotation_indentation": { + "description": "Doctrine annotations must be indented with four spaces.", + "type": "object", + "properties": { + "ignored_tags": { + "description": "List of tags that must not be treated as Doctrine Annotations.", + "default": [ + "abstract", + "access", + "code", + "deprec", + "encode", + "exception", + "final", + "ingroup", + "inheritdoc", + "inheritDoc", + "magic", + "name", + "toc", + "tutorial", + "private", + "static", + "staticvar", + "staticVar", + "throw", + "api", + "author", + "category", + "copyright", + "deprecated", + "example", + "filesource", + "global", + "ignore", + "internal", + "license", + "link", + "method", + "package", + "param", + "property", + "property-read", + "property-write", + "return", + "see", + "since", + "source", + "subpackage", + "throws", + "todo", + "TODO", + "usedBy", + "uses", + "var", + "version", + "after", + "afterClass", + "backupGlobals", + "backupStaticAttributes", + "before", + "beforeClass", + "codeCoverageIgnore", + "codeCoverageIgnoreStart", + "codeCoverageIgnoreEnd", + "covers", + "coversDefaultClass", + "coversNothing", + "dataProvider", + "depends", + "expectedException", + "expectedExceptionCode", + "expectedExceptionMessage", + "expectedExceptionMessageRegExp", + "group", + "large", + "medium", + "preserveGlobalState", + "requires", + "runTestsInSeparateProcesses", + "runInSeparateProcess", + "small", + "test", + "testdox", + "ticket", + "uses", + "SuppressWarnings", + "noinspection", + "package_version", + "enduml", + "startuml", + "psalm", + "phpstan", + "template", + "fix", + "FIXME", + "fixme", + "override" + ], + "type": "array" + }, + "indent_mixed_lines": { + "description": "Whether to indent lines that have content before closing parenthesis.", + "default": false, + "type": "boolean" + } + } + }, + "doctrine_annotation_spaces": { + "description": "Fixes spaces in Doctrine annotations.", + "type": "object", + "properties": { + "after_argument_assignments": { + "description": "Whether to add, remove or ignore spaces after argument assignment operator.", + "default": false, + "type": [ + "null", + "boolean" + ] + }, + "after_array_assignments_colon": { + "description": "Whether to add, remove or ignore spaces after array assignment `:` operator.", + "default": true, + "type": [ + "null", + "boolean" + ] + }, + "after_array_assignments_equals": { + "description": "Whether to add, remove or ignore spaces after array assignment `=` operator.", + "default": true, + "type": [ + "null", + "boolean" + ] + }, + "around_commas": { + "description": "Whether to fix spaces around commas.", + "default": true, + "type": "boolean" + }, + "around_parentheses": { + "description": "Whether to fix spaces around parentheses.", + "default": true, + "type": "boolean" + }, + "before_argument_assignments": { + "description": "Whether to add, remove or ignore spaces before argument assignment operator.", + "default": false, + "type": [ + "null", + "boolean" + ] + }, + "before_array_assignments_colon": { + "description": "Whether to add, remove or ignore spaces before array `:` assignment operator.", + "default": true, + "type": [ + "null", + "boolean" + ] + }, + "before_array_assignments_equals": { + "description": "Whether to add, remove or ignore spaces before array `=` assignment operator.", + "default": true, + "type": [ + "null", + "boolean" + ] + }, + "ignored_tags": { + "description": "List of tags that must not be treated as Doctrine Annotations.", + "default": [ + "abstract", + "access", + "code", + "deprec", + "encode", + "exception", + "final", + "ingroup", + "inheritdoc", + "inheritDoc", + "magic", + "name", + "toc", + "tutorial", + "private", + "static", + "staticvar", + "staticVar", + "throw", + "api", + "author", + "category", + "copyright", + "deprecated", + "example", + "filesource", + "global", + "ignore", + "internal", + "license", + "link", + "method", + "package", + "param", + "property", + "property-read", + "property-write", + "return", + "see", + "since", + "source", + "subpackage", + "throws", + "todo", + "TODO", + "usedBy", + "uses", + "var", + "version", + "after", + "afterClass", + "backupGlobals", + "backupStaticAttributes", + "before", + "beforeClass", + "codeCoverageIgnore", + "codeCoverageIgnoreStart", + "codeCoverageIgnoreEnd", + "covers", + "coversDefaultClass", + "coversNothing", + "dataProvider", + "depends", + "expectedException", + "expectedExceptionCode", + "expectedExceptionMessage", + "expectedExceptionMessageRegExp", + "group", + "large", + "medium", + "preserveGlobalState", + "requires", + "runTestsInSeparateProcesses", + "runInSeparateProcess", + "small", + "test", + "testdox", + "ticket", + "uses", + "SuppressWarnings", + "noinspection", + "package_version", + "enduml", + "startuml", + "psalm", + "phpstan", + "template", + "fix", + "FIXME", + "fixme", + "override" + ], + "type": "array" + } + } + }, + "echo_tag_syntax": { + "description": "Replaces short-echo `= 7.0." + }, + "explicit_string_variable": { + "description": "Converts implicit variables into explicit ones in double-quoted strings or heredoc syntax." + }, + "final_class": { + "description": "All classes must be final, except abstract ones and Doctrine entities." + }, + "final_internal_class": { + "description": "Internal classes should be `final`.", + "type": "object", + "properties": { + "annotation_exclude": { + "description": "Class level annotations tags that must be omitted to fix the class, even if all of the white list ones are used as well. (case insensitive)", + "default": [ + "@final", + "@Entity", + "@ORM\\Entity", + "@ORM\\Mapping\\Entity", + "@Mapping\\Entity", + "@Document", + "@ODM\\Document" + ], + "type": "array" + }, + "annotation_include": { + "description": "Class level annotations tags that must be set in order to fix the class. (case insensitive)", + "default": [ + "@internal" + ], + "type": "array" + }, + "consider_absent_docblock_as_internal_class": { + "description": "Should classes without any DocBlock be fixed to final?", + "default": false, + "type": "boolean" + } + } + }, + "final_public_method_for_abstract_class": { + "description": "All `public` methods of `abstract` classes should be `final`." + }, + "fopen_flag_order": { + "description": "Order the flags in `fopen` calls, `b` and `t` must be last." + }, + "fopen_flags": { + "description": "The flags in `fopen` calls must omit `t`, and `b` must be omitted or included consistently." + }, + "full_opening_tag": { + "description": "PHP code must use the long `= 7.3." + }, + "heredoc_to_nowdoc": { + "description": "Convert `heredoc` to `nowdoc` where possible." + }, + "implode_call": { + "description": "Function `implode` must be called with 2 arguments in the documented order." + }, + "include": { + "description": "Include/Require and file path should be divided with a single space. File path should not be placed under brackets." + }, + "increment_style": { + "description": "Pre- or post-increment and decrement operators should be used if possible." + }, + "indentation_type": { + "description": "Code MUST use configured indentation type." + }, + "integer_literal_case": { + "description": "Integer literals must be in correct case." + }, + "is_null": { + "description": "Replaces `is_null($var)` expression with `null === $var`." + }, + "lambda_not_used_import": { + "description": "Lambda must not import variables it doesn't use." + }, + "line_ending": { + "description": "All PHP files must use same line ending." + }, + "linebreak_after_opening_tag": { + "description": "Ensure there is no code on the same line as the PHP open tag." + }, + "list_syntax": { + "description": "List (`array` destructuring) assignment should be declared using the configured syntax. Requires PHP >= 7.1." + }, + "logical_operators": { + "description": "Use `&&` and `||` logical operators instead of `and` and `or`." + }, + "lowercase_cast": { + "description": "Cast should be written in lower case." + }, + "lowercase_keywords": { + "description": "PHP keywords MUST be in lower case." + }, + "lowercase_static_reference": { + "description": "Class static references `self`, `static` and `parent` MUST be in lower case." + }, + "magic_constant_casing": { + "description": "Magic constants should be referred to using the correct casing." + }, + "magic_method_casing": { + "description": "Magic method definitions and calls must be using the correct casing." + }, + "mb_str_functions": { + "description": "Replace non multibyte-safe functions with corresponding mb function." + }, + "method_argument_space": { + "description": "In method arguments and method call, there MUST NOT be a space before each comma and there MUST be one space after each comma. Argument lists MAY be split across multiple lines, where each subsequent line is indented once. When doing so, the first item in the list MUST be on the next line, and there MUST be only one argument per line.", + "type": "object", + "properties": { + "after_heredoc": { + "description": "Whether the whitespace between heredoc end and comma should be removed.", + "default": false, + "type": "boolean" + }, + "keep_multiple_spaces_after_comma": { + "description": "Whether keep multiple spaces after comma.", + "default": false, + "type": "boolean" + }, + "on_multiline": { + "description": "Defines how to handle function arguments lists that contain newlines.", + "default": "ensure_fully_multiline", + "oneOf": [ + { + "enum": [ + "ignore", + "ensure_single_line", + "ensure_fully_multiline" + ] + } + ] + } + } + }, + "method_chaining_indentation": { + "description": "Method chaining MUST be properly indented. Method chaining with different levels of indentation is not supported." + }, + "modernize_strpos": { + "description": "Replace `strpos()` calls with `str_starts_with()` or `str_contains()` if possible." + }, + "modernize_types_casting": { + "description": "Replaces `intval`, `floatval`, `doubleval`, `strval` and `boolval` function calls with according type casting operator." + }, + "multiline_comment_opening_closing": { + "description": "DocBlocks must start with two asterisks, multiline comments must start with a single asterisk, after the opening slash. Both must end with a single asterisk before the closing slash." + }, + "multiline_whitespace_before_semicolons": { + "description": "Forbid multi-line whitespace before the closing semicolon or move the semicolon to the new line for chained calls." + }, + "native_constant_invocation": { + "description": "Add leading `\\` before constant invocation of internal constant to speed up resolving. Constant name match is case-sensitive, except for `null`, `false` and `true`.", + "type": "object", + "properties": { + "exclude": { + "description": "List of constants to ignore.", + "default": [ + "null", + "false", + "true" + ], + "type": "array" + }, + "fix_built_in": { + "description": "Whether to fix constants returned by `get_defined_constants`. User constants are not accounted in this list and must be specified in the include one.", + "default": true, + "type": "boolean" + }, + "include": { + "description": "List of additional constants to fix.", + "default": [], + "type": "array" + }, + "scope": { + "description": "Only fix constant invocations that are made within a namespace or fix all.", + "default": "all", + "oneOf": [ + { + "enum": [ + "all", + "namespaced" + ] + } + ] + }, + "strict": { + "description": "Whether leading `\\` of constant invocation not meant to have it should be removed.", + "default": true, + "type": "boolean" + } + } + }, + "native_function_casing": { + "description": "Function defined by PHP should be called using the correct casing." + }, + "native_function_invocation": { + "description": "Add leading `\\` before function invocation to speed up resolving.", + "type": "object", + "properties": { + "exclude": { + "description": "List of functions to ignore.", + "default": [], + "type": "array" + }, + "include": { + "description": "List of function names or sets to fix. Defined sets are `@internal` (all native functions), `@all` (all global functions) and `@compiler_optimized` (functions that are specially optimized by Zend).", + "default": [ + "@compiler_optimized" + ], + "type": "array" + }, + "scope": { + "description": "Only fix function calls that are made within a namespace or fix all.", + "default": "all", + "oneOf": [ + { + "enum": [ + "all", + "namespaced" + ] + } + ] + }, + "strict": { + "description": "Whether leading `\\` of function call not meant to have it should be removed.", + "default": true, + "type": "boolean" + } + } + }, + "native_function_type_declaration_casing": { + "description": "Native type hints for functions should use the correct case." + }, + "new_with_braces": { + "description": "All instances created with `new` keyword must (not) be followed by braces.", + "type": "object", + "properties": { + "anonymous_class": { + "description": "Whether anonymous classes should be followed by parentheses.", + "default": true, + "type": "boolean" + }, + "named_class": { + "description": "Whether named classes should be followed by parentheses.", + "default": true, + "type": "boolean" + } + } + }, + "no_alias_functions": { + "description": "Master functions shall be used instead of aliases." + }, + "no_alias_language_construct_call": { + "description": "Master language constructs shall be used instead of aliases." + }, + "no_alternative_syntax": { + "description": "Replace control structure alternative syntax to use braces." + }, + "no_binary_string": { + "description": "There should not be a binary flag before strings." + }, + "no_blank_lines_after_class_opening": { + "description": "There should be no empty lines after class opening brace." + }, + "no_blank_lines_after_phpdoc": { + "description": "There should not be blank lines between docblock and the documented element." + }, + "no_blank_lines_before_namespace": { + "description": "There should be no blank lines before a namespace declaration." + }, + "no_break_comment": { + "description": "There must be a comment when fall-through is intentional in a non-empty case body." + }, + "no_closing_tag": { + "description": "The closing `?>` tag MUST be omitted from files containing only PHP." + }, + "no_empty_comment": { + "description": "There should not be any empty comments." + }, + "no_empty_phpdoc": { + "description": "There should not be empty PHPDoc blocks." + }, + "no_empty_statement": { + "description": "Remove useless (semicolon) statements." + }, + "no_extra_blank_lines": { + "description": "Removes extra blank lines and/or blank lines following configuration." + }, + "no_homoglyph_names": { + "description": "Replace accidental usage of homoglyphs (non ascii characters) in names." + }, + "no_leading_import_slash": { + "description": "Remove leading slashes in `use` clauses." + }, + "no_leading_namespace_whitespace": { + "description": "The namespace declaration line shouldn't contain leading whitespace." + }, + "no_mixed_echo_print": { + "description": "Either language construct `print` or `echo` should be used." + }, + "no_multiline_whitespace_around_double_arrow": { + "description": "Operator `=>` should not be surrounded by multi-line whitespaces." + }, + "no_null_property_initialization": { + "description": "Properties MUST not be explicitly initialized with `null` except when they have a type declaration (PHP 7.4)." + }, + "no_php4_constructor": { + "description": "Convert PHP4-style constructors to `__construct`." + }, + "no_short_bool_cast": { + "description": "Short cast `bool` using double exclamation mark should not be used." + }, + "no_singleline_whitespace_before_semicolons": { + "description": "Single-line whitespace before closing semicolon are prohibited." + }, + "no_space_around_double_colon": { + "description": "There must be no space around double colons (also called Scope Resolution Operator or Paamayim Nekudotayim)." + }, + "no_spaces_after_function_name": { + "description": "When making a method or function call, there MUST NOT be a space between the method or function name and the opening parenthesis." + }, + "no_spaces_around_offset": { + "description": "There MUST NOT be spaces around offset braces." + }, + "no_spaces_inside_parenthesis": { + "description": "There MUST NOT be a space after the opening parenthesis. There MUST NOT be a space before the closing parenthesis." + }, + "no_superfluous_elseif": { + "description": "Replaces superfluous `elseif` with `if`." + }, + "no_superfluous_phpdoc_tags": { + "description": "Removes `@param`, `@return` and `@var` tags that don't provide any useful information.", + "type": "object", + "properties": { + "allow_mixed": { + "description": "Whether type `mixed` without description is allowed (`true`) or considered superfluous (`false`)", + "default": false, + "type": "boolean" + }, + "allow_unused_params": { + "description": "Whether `param` annotation without actual signature is allowed (`true`) or considered superfluous (`false`)", + "default": false, + "type": "boolean" + }, + "remove_inheritdoc": { + "description": "Remove `@inheritDoc` tags", + "default": false, + "type": "boolean" + } + } + }, + "no_trailing_comma_in_list_call": { + "description": "Remove trailing commas in list function calls." + }, + "no_trailing_comma_in_singleline_array": { + "description": "PHP single-line arrays should not have trailing comma." + }, + "no_trailing_comma_in_singleline_function_call": { + "description": "When making a method or function call on a single line there MUST NOT be a trailing comma after the last argument." + }, + "no_trailing_whitespace": { + "description": "Remove trailing whitespace at the end of non-blank lines." + }, + "no_trailing_whitespace_in_comment": { + "description": "There MUST be no trailing spaces inside comment or PHPDoc." + }, + "no_trailing_whitespace_in_string": { + "description": "There must be no trailing whitespace in strings." + }, + "no_unneeded_control_parentheses": { + "description": "Removes unneeded parentheses around control statements." + }, + "no_unneeded_curly_braces": { + "description": "Removes unneeded curly braces that are superfluous and aren't part of a control structure's body." + }, + "no_unneeded_final_method": { + "description": "Removes `final` from methods where possible." + }, + "no_unneeded_import_alias": { + "description": "Imports should not be aliased as the same name." + }, + "no_unreachable_default_argument_value": { + "description": "In function arguments there must not be arguments with default values before non-default ones." + }, + "no_unset_cast": { + "description": "Variables must be set `null` instead of using `(unset)` casting." + }, + "no_unset_on_property": { + "description": "Properties should be set to `null` instead of using `unset`." + }, + "no_unused_imports": { + "description": "Unused `use` statements must be removed." + }, + "no_useless_else": { + "description": "There should not be useless `else` cases." + }, + "no_useless_return": { + "description": "There should not be an empty `return` statement at the end of a function." + }, + "no_useless_sprintf": { + "description": "There must be no `sprintf` calls with only the first argument." + }, + "no_whitespace_before_comma_in_array": { + "description": "In array declaration, there MUST NOT be a whitespace before each comma." + }, + "no_whitespace_in_blank_line": { + "description": "Remove trailing whitespace at the end of blank lines." + }, + "non_printable_character": { + "description": "Remove Zero-width space (ZWSP), Non-breaking space (NBSP) and other invisible unicode symbols." + }, + "normalize_index_brace": { + "description": "Array index should always be written by using square braces." + }, + "not_operator_with_space": { + "description": "Logical NOT operators (`!`) should have leading and trailing whitespaces." + }, + "not_operator_with_successor_space": { + "description": "Logical NOT operators (`!`) should have one trailing whitespace." + }, + "nullable_type_declaration_for_default_null_value": { + "description": "Adds or removes `?` before type declarations for parameters with a default `null` value." + }, + "object_operator_without_whitespace": { + "description": "There should not be space before or after object operators `->` and `?->`." + }, + "octal_notation": { + "description": "Literal octal must be in `0o` notation." + }, + "operator_linebreak": { + "description": "Operators - when multiline - must always be at the beginning or at the end of the line.", + "type": "object", + "properties": { + "only_booleans": { + "description": "whether to limit operators to only boolean ones", + "default": false, + "type": "boolean" + }, + "position": { + "description": "whether to place operators at the beginning or at the end of the line", + "default": "beginning", + "oneOf": [ + { + "enum": [ + "beginning", + "end" + ] + } + ] + } + } + }, + "ordered_class_elements": { + "description": "Orders the elements of classes/interfaces/traits/enums.", + "type": "object", + "properties": { + "order": { + "description": "List of strings defining order of elements.", + "default": [ + "use_trait", + "case", + "constant_public", + "constant_protected", + "constant_private", + "property_public", + "property_protected", + "property_private", + "construct", + "destruct", + "magic", + "phpunit", + "method_public", + "method_protected", + "method_private" + ], + "type": "array", + "oneOf": [ + { + "enum": [ + [ + "use_trait", + "public", + "protected", + "private", + "case", + "constant", + "constant_public", + "constant_protected", + "constant_private", + "property", + "property_static", + "property_public", + "property_protected", + "property_private", + "property_public_readonly", + "property_protected_readonly", + "property_private_readonly", + "property_public_static", + "property_protected_static", + "property_private_static", + "method", + "method_abstract", + "method_static", + "method_public", + "method_protected", + "method_private", + "method_public_abstract", + "method_protected_abstract", + "method_private_abstract", + "method_public_abstract_static", + "method_protected_abstract_static", + "method_private_abstract_static", + "method_public_static", + "method_protected_static", + "method_private_static", + "construct", + "destruct", + "magic", + "phpunit" + ] + ] + } + ] + }, + "sort_algorithm": { + "description": "How multiple occurrences of same type statements should be sorted", + "default": "none", + "oneOf": [ + { + "enum": [ + "none", + "alpha" + ] + } + ] + } + } + }, + "ordered_imports": { + "description": "Ordering `use` statements.", + "type": "object", + "properties": { + "imports_order": { + "description": "Defines the order of import types.", + "default": null, + "type": [ + "array", + "null" + ] + }, + "sort_algorithm": { + "description": "whether the statements should be sorted alphabetically or by length, or not sorted", + "default": "alpha", + "oneOf": [ + { + "enum": [ + "alpha", + "length", + "none" + ] + } + ] + } + } + }, + "ordered_interfaces": { + "description": "Orders the interfaces in an `implements` or `interface extends` clause.", + "type": "object", + "properties": { + "direction": { + "description": "Which direction the interfaces should be ordered", + "default": "ascend", + "oneOf": [ + { + "enum": [ + "ascend", + "descend" + ] + } + ] + }, + "order": { + "description": "How the interfaces should be ordered", + "default": "alpha", + "oneOf": [ + { + "enum": [ + "alpha", + "length" + ] + } + ] + } + } + }, + "ordered_traits": { + "description": "Trait `use` statements must be sorted alphabetically." + }, + "php_unit_construct": { + "description": "PHPUnit assertion method calls like `->assertSame(true, $foo)` should be written with dedicated method like `->assertTrue($foo)`." + }, + "php_unit_dedicate_assert": { + "description": "PHPUnit assertions like `assertInternalType`, `assertFileExists`, should be used over `assertTrue`." + }, + "php_unit_dedicate_assert_internal_type": { + "description": "PHPUnit assertions like `assertIsArray` should be used over `assertInternalType`." + }, + "php_unit_expectation": { + "description": "Usages of `->setExpectedException*` methods MUST be replaced by `->expectException*` methods." + }, + "php_unit_fqcn_annotation": { + "description": "PHPUnit annotations should be a FQCNs including a root namespace." + }, + "php_unit_internal_class": { + "description": "All PHPUnit test classes should be marked as internal." + }, + "php_unit_method_casing": { + "description": "Enforce camel (or snake) case for PHPUnit test methods, following configuration." + }, + "php_unit_mock": { + "description": "Usages of `->getMock` and `->getMockWithoutInvokingTheOriginalConstructor` methods MUST be replaced by `->createMock` or `->createPartialMock` methods." + }, + "php_unit_mock_short_will_return": { + "description": "Usage of PHPUnit's mock e.g. `->will($this->returnValue(..))` must be replaced by its shorter equivalent such as `->willReturn(...)`." + }, + "php_unit_namespaced": { + "description": "PHPUnit classes MUST be used in namespaced version, e.g. `\\PHPUnit\\Framework\\TestCase` instead of `\\PHPUnit_Framework_TestCase`." + }, + "php_unit_no_expectation_annotation": { + "description": "Usages of `@expectedException*` annotations MUST be replaced by `->setExpectedException*` methods.", + "type": "object", + "properties": { + "target": { + "description": "Target version of PHPUnit.", + "default": "newest", + "type": "string", + "oneOf": [ + { + "enum": [ + "3.2", + "4.3", + "newest" + ] + } + ] + }, + "use_class_const": { + "description": "Use ::class notation.", + "default": true, + "type": "boolean" + } + } + }, + "php_unit_set_up_tear_down_visibility": { + "description": "Changes the visibility of the `setUp()` and `tearDown()` functions of PHPUnit to `protected`, to match the PHPUnit TestCase." + }, + "php_unit_size_class": { + "description": "All PHPUnit test cases should have `@small`, `@medium` or `@large` annotation to enable run time limits." + }, + "php_unit_strict": { + "description": "PHPUnit methods like `assertSame` should be used instead of `assertEquals`." + }, + "php_unit_test_annotation": { + "description": "Adds or removes @test annotations from tests, following configuration." + }, + "php_unit_test_case_static_method_calls": { + "description": "Calls to `PHPUnit\\Framework\\TestCase` static methods must all be of the same type, either `$this->`, `self::` or `static::`.", + "type": "object", + "properties": { + "call_type": { + "description": "The call type to use for referring to PHPUnit methods.", + "default": "static", + "type": "string", + "oneOf": [ + { + "enum": [ + "this", + "self", + "static" + ] + } + ] + }, + "methods": { + "description": "Dictionary of `method` => `call_type` values that differ from the default strategy.", + "default": [], + "type": "array" + } + } + }, + "php_unit_test_class_requires_covers": { + "description": "Adds a default `@coversNothing` annotation to PHPUnit test classes that have no `@covers*` annotation." + }, + "phpdoc_add_missing_param_annotation": { + "description": "PHPDoc should contain `@param` for all params." + }, + "phpdoc_align": { + "description": "All items of the given phpdoc tags must be either left-aligned or (by default) aligned vertically.", + "type": "object", + "properties": { + "align": { + "description": "Align comments", + "default": "vertical", + "type": "string", + "oneOf": [ + { + "enum": [ + "left", + "vertical" + ] + } + ] + }, + "tags": { + "description": "The tags that should be aligned.", + "default": [ + "method", + "param", + "property", + "return", + "throws", + "type", + "var" + ], + "type": "array", + "oneOf": [ + { + "enum": [ + [ + "param", + "property", + "property-read", + "property-write", + "return", + "throws", + "type", + "var", + "method" + ] + ] + } + ] + } + } + }, + "phpdoc_annotation_without_dot": { + "description": "PHPDoc annotation descriptions should not be a sentence." + }, + "phpdoc_indent": { + "description": "Docblocks should have the same indentation as the documented subject." + }, + "phpdoc_inline_tag_normalizer": { + "description": "Fixes PHPDoc inline tags." + }, + "phpdoc_line_span": { + "description": "Changes doc blocks from single to multi line, or reversed. Works for class constants, properties and methods only.", + "type": "object", + "properties": { + "const": { + "description": "Whether const blocks should be single or multi line", + "default": "multi", + "oneOf": [ + { + "enum": [ + "single", + "multi", + null + ] + } + ] + }, + "method": { + "description": "Whether method doc blocks should be single or multi line", + "default": "multi", + "oneOf": [ + { + "enum": [ + "single", + "multi", + null + ] + } + ] + }, + "property": { + "description": "Whether property doc blocks should be single or multi line", + "default": "multi", + "oneOf": [ + { + "enum": [ + "single", + "multi", + null + ] + } + ] + } + } + }, + "phpdoc_no_access": { + "description": "`@access` annotations should be omitted from PHPDoc." + }, + "phpdoc_no_alias_tag": { + "description": "No alias PHPDoc tags should be used." + }, + "phpdoc_no_empty_return": { + "description": "`@return void` and `@return null` annotations should be omitted from PHPDoc." + }, + "phpdoc_no_package": { + "description": "`@package` and `@subpackage` annotations should be omitted from PHPDoc." + }, + "phpdoc_no_useless_inheritdoc": { + "description": "Classy that does not inherit must not have `@inheritdoc` tags." + }, + "phpdoc_order": { + "description": "Annotations in PHPDoc should be ordered so that `@param` annotations come first, then `@throws` annotations, then `@return` annotations." + }, + "phpdoc_order_by_value": { + "description": "Order phpdoc tags by value." + }, + "phpdoc_return_self_reference": { + "description": "The type of `@return` annotations of methods returning a reference to itself must the configured one." + }, + "phpdoc_scalar": { + "description": "Scalar types should always be written in the same form. `int` not `integer`, `bool` not `boolean`, `float` not `real` or `double`." + }, + "phpdoc_separation": { + "description": "Annotations in PHPDoc should be grouped together so that annotations of the same type immediately follow each other, and annotations of a different type are separated by a single blank line." + }, + "phpdoc_single_line_var_spacing": { + "description": "Single line `@var` PHPDoc should have proper spacing." + }, + "phpdoc_summary": { + "description": "PHPDoc summary should end in either a full stop, exclamation mark, or question mark." + }, + "phpdoc_tag_casing": { + "description": "Fixes casing of PHPDoc tags." + }, + "phpdoc_tag_type": { + "description": "Forces PHPDoc tags to be either regular annotations or inline." + }, + "phpdoc_to_comment": { + "description": "Docblocks should only be used on structural elements." + }, + "phpdoc_to_param_type": { + "description": "EXPERIMENTAL: Takes `@param` annotations of non-mixed types and adjusts accordingly the function signature. Requires PHP >= 7.0." + }, + "phpdoc_to_property_type": { + "description": "EXPERIMENTAL: Takes `@var` annotation of non-mixed types and adjusts accordingly the property signature. Requires PHP >= 7.4." + }, + "phpdoc_to_return_type": { + "description": "EXPERIMENTAL: Takes `@return` annotation of non-mixed types and adjusts accordingly the function signature. Requires PHP >= 7.0." + }, + "phpdoc_trim": { + "description": "PHPDoc should start and end with content, excluding the very first and last line of the docblocks." + }, + "phpdoc_trim_consecutive_blank_line_separation": { + "description": "Removes extra blank lines after summary and after description in PHPDoc." + }, + "phpdoc_types": { + "description": "The correct case must be used for standard PHP types in PHPDoc." + }, + "phpdoc_types_order": { + "description": "Sorts PHPDoc types.", + "type": "object", + "properties": { + "null_adjustment": { + "description": "Forces the position of `null` (overrides `sort_algorithm`).", + "default": "always_first", + "oneOf": [ + { + "enum": [ + "always_first", + "always_last", + "none" + ] + } + ] + }, + "sort_algorithm": { + "description": "The sorting algorithm to apply.", + "default": "alpha", + "oneOf": [ + { + "enum": [ + "alpha", + "none" + ] + } + ] + } + } + }, + "phpdoc_var_annotation_correct_order": { + "description": "`@var` and `@type` annotations must have type and name in the correct order." + }, + "phpdoc_var_without_name": { + "description": "`@var` and `@type` annotations of classy properties should not contain the name." + }, + "pow_to_exponentiation": { + "description": "Converts `pow` to the `**` operator." + }, + "protected_to_private": { + "description": "Converts `protected` variables and methods to `private` where possible." + }, + "psr_autoloading": { + "description": "Classes must be in a path that matches their namespace, be at least one namespace deep and the class name should match the file name." + }, + "random_api_migration": { + "description": "Replaces `rand`, `srand`, `getrandmax` functions calls with their `mt_*` analogs or `random_int`." + }, + "regular_callable_call": { + "description": "Callables must be called without using `call_user_func*` when possible." + }, + "return_assignment": { + "description": "Local, dynamic and directly referenced variables should not be assigned and directly returned by a function or method." + }, + "return_type_declaration": { + "description": "There should be one or no space before colon, and one space after it in return type declarations, according to configuration." + }, + "self_accessor": { + "description": "Inside class or interface element `self` should be preferred to the class name itself." + }, + "self_static_accessor": { + "description": "Inside a `final` class or anonymous class `self` should be preferred to `static`." + }, + "semicolon_after_instruction": { + "description": "Instructions must be terminated with a semicolon." + }, + "set_type_to_cast": { + "description": "Cast shall be used, not `settype`." + }, + "short_scalar_cast": { + "description": "Cast `(boolean)` and `(integer)` should be written as `(bool)` and `(int)`, `(double)` and `(real)` as `(float)`, `(binary)` as `(string)`." + }, + "simple_to_complex_string_variable": { + "description": "Converts explicit variables in double-quoted strings and heredoc syntax from simple to complex format (`${` to `{$`)." + }, + "simplified_if_return": { + "description": "Simplify `if` control structures that return the boolean result of their condition." + }, + "simplified_null_return": { + "description": "A return statement wishing to return `void` should not return `null`." + }, + "single_blank_line_at_eof": { + "description": "A PHP file without end tag must always end with a single empty line feed." + }, + "single_blank_line_before_namespace": { + "description": "There should be exactly one blank line before a namespace declaration." + }, + "single_class_element_per_statement": { + "description": "There MUST NOT be more than one property or constant declared per statement." + }, + "single_import_per_statement": { + "description": "There MUST be one use keyword per declaration." + }, + "single_line_after_imports": { + "description": "Each namespace use MUST go on its own line and there MUST be one blank line after the use statements block." + }, + "single_line_comment_spacing": { + "description": "Single-line comments must have proper spacing." + }, + "single_line_comment_style": { + "description": "Single-line comments and multi-line comments with only one line of actual content should use the `//` syntax." + }, + "single_line_throw": { + "description": "Throwing exception must be done in single line." + }, + "single_quote": { + "description": "Convert double quotes to single quotes for simple strings." + }, + "single_space_after_construct": { + "description": "Ensures a single space after language constructs." + }, + "single_trait_insert_per_statement": { + "description": "Each trait `use` must be done as single statement." + }, + "space_after_semicolon": { + "description": "Fix whitespace after a semicolon." + }, + "standardize_increment": { + "description": "Increment and decrement operators should be used if possible." + }, + "standardize_not_equals": { + "description": "Replace all `<>` with `!=`." + }, + "static_lambda": { + "description": "Lambdas not (indirect) referencing `$this` must be declared `static`." + }, + "strict_comparison": { + "description": "Comparisons should be strict." + }, + "strict_param": { + "description": "Functions should be used with `$strict` param set to `true`." + }, + "string_length_to_empty": { + "description": "String tests for empty must be done against `''`, not with `strlen`." + }, + "string_line_ending": { + "description": "All multi-line strings must use correct line ending." + }, + "switch_case_semicolon_to_colon": { + "description": "A case should be followed by a colon and not a semicolon." + }, + "switch_case_space": { + "description": "Removes extra spaces between colon and case value." + }, + "switch_continue_to_break": { + "description": "Switch case must not be ended with `continue` but with `break`." + }, + "ternary_operator_spaces": { + "description": "Standardize spaces around ternary operator." + }, + "ternary_to_elvis_operator": { + "description": "Use the Elvis operator `?:` where possible." + }, + "ternary_to_null_coalescing": { + "description": "Use `null` coalescing operator `??` where possible. Requires PHP >= 7.0." + }, + "trailing_comma_in_multiline": { + "description": "Multi-line arrays, arguments list and parameters list must have a trailing comma.", + "type": "object", + "properties": { + "after_heredoc": { + "description": "Whether a trailing comma should also be placed after heredoc end.", + "default": false, + "type": "boolean" + }, + "elements": { + "description": "Where to fix multiline trailing comma (PHP >= 7.3 required for `arguments`, PHP >= 8.0 for `parameters`).", + "default": [ + "arrays" + ], + "type": "array", + "oneOf": [ + { + "enum": [ + [ + "arrays", + "arguments", + "parameters" + ] + ] + } + ] + } + } + }, + "trim_array_spaces": { + "description": "Arrays should be formatted like function/method arguments, without leading or trailing single line space." + }, + "types_spaces": { + "description": "A single space or none should be around union type operator.", + "type": "object", + "properties": { + "space": { + "description": "spacing to apply around union type operator.", + "default": "none", + "oneOf": [ + { + "enum": [ + "none", + "single" + ] + } + ] + }, + "space_multiple_catch": { + "description": "spacing to apply around type operator when catching exceptions of multiple types, use `null` to follow the value configured for `space`.", + "default": null, + "oneOf": [ + { + "enum": [ + "none", + "single", + null + ] + } + ] + } + } + }, + "unary_operator_spaces": { + "description": "Unary operators should be placed adjacent to their operands." + }, + "use_arrow_functions": { + "description": "Anonymous functions with one-liner return statement must use arrow functions." + }, + "visibility_required": { + "description": "Visibility MUST be declared on all properties and methods; `abstract` and `final` MUST be declared before the visibility; `static` MUST be declared after the visibility." + }, + "void_return": { + "description": "Add `void` return type to functions with missing or empty return statements, but priority is given to `@return` annotations. Requires PHP >= 7.1." + }, + "whitespace_after_comma_in_array": { + "description": "In array declaration, there MUST be a whitespace after each comma." + }, + "yoda_style": { + "description": "Write conditions in Yoda style (`true`), non-Yoda style (`['equal' => false, 'identical' => false, 'less_and_greater' => false]`) or ignore those conditions (`null`) based on configuration.", + "type": "object", + "properties": { + "always_move_variable": { + "description": "Whether variables should always be on non assignable side when applying Yoda style.", + "default": false, + "type": "boolean" + }, + "equal": { + "description": "Style for equal (`==`, `!=`) statements.", + "default": true, + "type": [ + "boolean", + "null" + ] + }, + "identical": { + "description": "Style for identical (`===`, `!==`) statements.", + "default": true, + "type": [ + "boolean", + "null" + ] + }, + "less_and_greater": { + "description": "Style for less and greater than (`<`, `<=`, `>`, `>=`) statements.", + "default": null, + "type": [ + "boolean", + "null" + ] + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/actions/fix.ts b/src/actions/pcfFix.ts similarity index 92% rename from src/actions/fix.ts rename to src/actions/pcfFix.ts index ed879c3..aff9132 100644 --- a/src/actions/fix.ts +++ b/src/actions/pcfFix.ts @@ -17,12 +17,12 @@ export function activate(context: ExtensionContext) { if (workspace.getConfiguration('php-cs-fixer').get('enableActionProvider', true)) { context.subscriptions.push( - languages.registerCodeActionProvider(documentSelector, new FixCodeActionProvider(), 'php-cs-fixer') + languages.registerCodeActionProvider(documentSelector, new PcfFixCodeActionProvider(), 'php-cs-fixer') ); } } -export class FixCodeActionProvider implements CodeActionProvider { +export class PcfFixCodeActionProvider implements CodeActionProvider { // eslint-disable-next-line @typescript-eslint/no-unused-vars public async provideCodeActions(document: TextDocument, range: Range, context: CodeActionContext) { const codeActions: CodeAction[] = []; diff --git a/src/actions/pintFix.ts b/src/actions/pintFix.ts new file mode 100644 index 0000000..4fd65c9 --- /dev/null +++ b/src/actions/pintFix.ts @@ -0,0 +1,59 @@ +import { + CodeAction, + CodeActionContext, + CodeActionProvider, + Command, + Document, + DocumentSelector, + ExtensionContext, + languages, + Range, + TextDocument, + workspace, +} from 'coc.nvim'; + +export function activate(context: ExtensionContext) { + const documentSelector: DocumentSelector = [{ language: 'php', scheme: 'file' }]; + + if (workspace.getConfiguration('php-cs-fixer').get('enableActionProvider', true)) { + context.subscriptions.push( + languages.registerCodeActionProvider(documentSelector, new PintFixCodeActionProvider(), 'php-cs-fixer') + ); + } +} + +export class PintFixCodeActionProvider implements CodeActionProvider { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async provideCodeActions(document: TextDocument, range: Range, context: CodeActionContext) { + const codeActions: CodeAction[] = []; + if (document.languageId !== 'php') return; + const doc = workspace.getDocument(document.uri); + + if (this.wholeRange(doc, range)) { + const title = `Run: php-cs-fixer.pintFix`; + const command: Command = { + title: '', + command: 'php-cs-fixer.pintFix', + }; + + const action: CodeAction = { + title, + command, + }; + + codeActions.push(action); + } + + return codeActions; + } + + private wholeRange(doc: Document, range: Range): boolean { + const whole = Range.create(0, 0, doc.lineCount, 0); + return ( + whole.start.line === range.start.line && + whole.start.character === range.start.character && + whole.end.line === range.end.line && + whole.end.character === whole.end.character + ); + } +} diff --git a/src/commands/download.ts b/src/commands/pcfDownload.ts similarity index 89% rename from src/commands/download.ts rename to src/commands/pcfDownload.ts index 25e30b2..5c46f2d 100644 --- a/src/commands/download.ts +++ b/src/commands/pcfDownload.ts @@ -1,5 +1,5 @@ import { commands, ExtensionContext, window, workspace } from 'coc.nvim'; -import { download } from '../downloader'; +import { download } from '../downloaders/pcfDownloader'; export function activate(context: ExtensionContext) { context.subscriptions.push(commands.registerCommand('php-cs-fixer.download', runDownloadCommand(context))); @@ -18,6 +18,7 @@ async function downloadWrapper(context: ExtensionContext, downloadMajorVersion: if (ret) { try { await download(context, downloadMajorVersion); + commands.executeCommand('editor.action.restart'); } catch (e) { console.error(e); msg = 'Download php-cs-fixer failed, you can get it from https://github.com/FriendsOfPHP/PHP-CS-Fixer'; diff --git a/src/commands/fix.ts b/src/commands/pcfFix.ts similarity index 93% rename from src/commands/fix.ts rename to src/commands/pcfFix.ts index 3ec51e8..9f1936c 100644 --- a/src/commands/fix.ts +++ b/src/commands/pcfFix.ts @@ -1,6 +1,6 @@ import { commands, ExtensionContext, OutputChannel, TextEdit, workspace } from 'coc.nvim'; import { fullDocumentRange } from '../common'; -import { doFormat } from '../engine'; +import { doFormat } from '../engines/pcfEngine'; export function activate(context: ExtensionContext, outputChannel: OutputChannel) { context.subscriptions.push(commands.registerCommand('php-cs-fixer.fix', runFixCommand(context, outputChannel))); diff --git a/src/commands/pintDownload.ts b/src/commands/pintDownload.ts new file mode 100644 index 0000000..48a84cd --- /dev/null +++ b/src/commands/pintDownload.ts @@ -0,0 +1,30 @@ +import { commands, ExtensionContext, window } from 'coc.nvim'; +import { download } from '../downloaders/pintDownloader'; + +export function activate(context: ExtensionContext) { + context.subscriptions.push(commands.registerCommand('php-cs-fixer.pintDownload', runDownloadCommand(context))); +} + +function runDownloadCommand(context: ExtensionContext) { + return async () => { + await downloadWrapper(context); + }; +} + +async function downloadWrapper(context: ExtensionContext) { + let msg = 'Do you want to download "pint"?'; + const ret = await window.showPrompt(msg); + if (ret) { + try { + await download(context); + commands.executeCommand('editor.action.restart'); + } catch (e) { + console.error(e); + msg = 'Download pint failed, you can get it from https://github.com/laravel/pint/releases'; + window.showErrorMessage(msg); + return; + } + } else { + return; + } +} diff --git a/src/commands/pintFix.ts b/src/commands/pintFix.ts new file mode 100644 index 0000000..a17711c --- /dev/null +++ b/src/commands/pintFix.ts @@ -0,0 +1,21 @@ +import { commands, ExtensionContext, OutputChannel, TextEdit, workspace } from 'coc.nvim'; +import { fullDocumentRange } from '../common'; +import { doFormat } from '../engines/pintEngine'; + +export function activate(context: ExtensionContext, outputChannel: OutputChannel) { + context.subscriptions.push(commands.registerCommand('php-cs-fixer.pintFix', runFixCommand(context, outputChannel))); +} + +function runFixCommand(context: ExtensionContext, outputChannel: OutputChannel) { + return async () => { + const doc = await workspace.document; + + const code = await doFormat(context, outputChannel, doc.textDocument, undefined); + if (!code) return; + + const edits = [TextEdit.replace(fullDocumentRange(doc.textDocument), code)]; + if (edits) { + await doc.applyEdits(edits); + } + }; +} diff --git a/src/common.ts b/src/common.ts index 2338e0f..2086fc3 100644 --- a/src/common.ts +++ b/src/common.ts @@ -1,4 +1,7 @@ -import { Range, TextDocument, workspace } from 'coc.nvim'; +import { ExtensionContext, Range, TextDocument, workspace } from 'coc.nvim'; + +import fs from 'fs'; +import path from 'path'; export function fullDocumentRange(document: TextDocument): Range { const lastLineId = document.lineCount - 1; @@ -6,3 +9,34 @@ export function fullDocumentRange(document: TextDocument): Range { return Range.create({ character: 0, line: 0 }, { character: doc.getline(lastLineId).length, line: lastLineId }); } + +export function getPcfPath(context: ExtensionContext) { + // 1. User setting php-cs-fixer + let toolPath = workspace.getConfiguration('php-cs-fixer').get('toolPath', ''); + if (!toolPath) { + if (fs.existsSync(path.join(workspace.root, 'vendor', 'bin', 'php-cs-fixer'))) { + // 2. vendor/bin/php-cs-fixer + toolPath = path.join(workspace.root, 'vendor', 'bin', 'php-cs-fixer'); + } else if (fs.existsSync(path.join(context.storagePath, 'php-cs-fixer'))) { + // 3. builtin php-cs-fixer + toolPath = path.join(context.storagePath, 'php-cs-fixer'); + } + } + + return toolPath; +} + +export function getPintPath(context: ExtensionContext) { + // 1. User setting pint + let toolPath = workspace.getConfiguration('php-cs-fixer').get('pint.toolPath', ''); + if (!toolPath) { + if (fs.existsSync(path.join(workspace.root, 'vendor', 'bin', 'pint'))) { + // 2. vendor/bin/pint + toolPath = path.join(workspace.root, 'vendor', 'bin', 'pint'); + } else if (fs.existsSync(path.join(context.storagePath, 'pint'))) { + // 3. builtin pint + toolPath = path.join(context.storagePath, 'pint'); + } + } + return toolPath; +} diff --git a/src/documentFormats/fixer.ts b/src/documentFormats/pcfFix.ts similarity index 87% rename from src/documentFormats/fixer.ts rename to src/documentFormats/pcfFix.ts index 83d5310..88fa8c6 100644 --- a/src/documentFormats/fixer.ts +++ b/src/documentFormats/pcfFix.ts @@ -10,7 +10,7 @@ import { workspace, } from 'coc.nvim'; import { fullDocumentRange } from '../common'; -import { doFormat } from '../engine'; +import { doFormat } from '../engines/pcfEngine'; export function activate(context: ExtensionContext, outputChannel: OutputChannel) { if (workspace.getConfiguration('php-cs-fixer').get('enableFormatProvider', true)) { @@ -20,14 +20,14 @@ export function activate(context: ExtensionContext, outputChannel: OutputChannel context.subscriptions.push( languages.registerDocumentFormatProvider( languageSelector, - new FixerFormattingEditProvider(context, outputChannel), + new PcfFixFormattingEditProvider(context, outputChannel), priority ) ); } } -class FixerFormattingEditProvider implements DocumentFormattingEditProvider { +class PcfFixFormattingEditProvider implements DocumentFormattingEditProvider { public _context: ExtensionContext; public _outputChannel: OutputChannel; diff --git a/src/documentFormats/pintFix.ts b/src/documentFormats/pintFix.ts new file mode 100644 index 0000000..3551f85 --- /dev/null +++ b/src/documentFormats/pintFix.ts @@ -0,0 +1,51 @@ +import { + DocumentFormattingEditProvider, + DocumentSelector, + ExtensionContext, + languages, + OutputChannel, + Range, + TextDocument, + TextEdit, + workspace, +} from 'coc.nvim'; +import { fullDocumentRange } from '../common'; +import { doFormat } from '../engines/pintEngine'; + +export function activate(context: ExtensionContext, outputChannel: OutputChannel) { + if (workspace.getConfiguration('php-cs-fixer').get('enableFormatProvider', true)) { + const languageSelector: DocumentSelector = [{ language: 'php', scheme: 'file' }]; + const priority = 1; + + context.subscriptions.push( + languages.registerDocumentFormatProvider( + languageSelector, + new PintFixFormattingEditProvider(context, outputChannel), + priority + ) + ); + } +} + +class PintFixFormattingEditProvider implements DocumentFormattingEditProvider { + public _context: ExtensionContext; + public _outputChannel: OutputChannel; + + constructor(context: ExtensionContext, outputChannel: OutputChannel) { + this._context = context; + this._outputChannel = outputChannel; + } + + public provideDocumentFormattingEdits(document: TextDocument): Promise { + return this._provideEdits(document, undefined); + } + + private async _provideEdits(document: TextDocument, range?: Range): Promise { + const code = await doFormat(this._context, this._outputChannel, document, range); + if (!code) return []; + if (!range) { + range = fullDocumentRange(document); + } + return [TextEdit.replace(range, code)]; + } +} diff --git a/src/downloader.ts b/src/downloaders/pcfDownloader.ts similarity index 100% rename from src/downloader.ts rename to src/downloaders/pcfDownloader.ts diff --git a/src/downloaders/pintDownloader.ts b/src/downloaders/pintDownloader.ts new file mode 100644 index 0000000..a0a47ab --- /dev/null +++ b/src/downloaders/pintDownloader.ts @@ -0,0 +1,53 @@ +import { ExtensionContext, window } from 'coc.nvim'; +import { randomBytes } from 'crypto'; +import { createWriteStream, promises as fs } from 'fs'; +import { HttpsProxyAgent } from 'https-proxy-agent'; +import fetch from 'node-fetch'; +import path from 'path'; +import stream from 'stream'; +import util from 'util'; + +const pipeline = util.promisify(stream.pipeline); +const agent = process.env.https_proxy ? new HttpsProxyAgent(process.env.https_proxy as string) : null; + +export async function download(context: ExtensionContext): Promise { + const statusItem = window.createStatusBarItem(0, { progress: true }); + statusItem.text = `Downloading pint`; + statusItem.show(); + + const downloadUrl = 'https://github.com/laravel/pint/releases/latest/download/pint.phar'; + + // @ts-ignore + const resp = await fetch(downloadUrl, { agent }); + if (!resp.ok) { + statusItem.hide(); + throw new Error('Download failed'); + } + + let cur = 0; + const len = Number(resp.headers.get('content-length')); + resp.body.on('data', (chunk: Buffer) => { + cur += chunk.length; + const p = ((cur / len) * 100).toFixed(2); + statusItem.text = `${p}% Downloading pint`; + }); + + const _path = path.join(context.storagePath, 'pint'); + const randomHex = randomBytes(5).toString('hex'); + const tempFile = path.join(context.storagePath, `pint-${randomHex}`); + + const destFileStream = createWriteStream(tempFile, { mode: 0o755 }); + await pipeline(resp.body, destFileStream); + await new Promise((resolve) => { + destFileStream.on('close', resolve); + destFileStream.destroy(); + setTimeout(resolve, 1000); + }); + + await fs.unlink(_path).catch((err) => { + if (err.code !== 'ENOENT') throw err; + }); + await fs.rename(tempFile, _path); + + statusItem.hide(); +} diff --git a/src/engine.ts b/src/engines/pcfEngine.ts similarity index 87% rename from src/engine.ts rename to src/engines/pcfEngine.ts index 5b2a860..e7f46c0 100644 --- a/src/engine.ts +++ b/src/engines/pcfEngine.ts @@ -4,6 +4,7 @@ import cp from 'child_process'; import fs from 'fs'; import path from 'path'; import tmp from 'tmp'; +import { getPcfPath } from '../common'; interface ProcessEnv { [key: string]: string | undefined; @@ -36,19 +37,10 @@ export async function doFormat( const fixerRules = extensionConfig.get('rules', '@PSR12'); const enableIgnoreEnv = extensionConfig.get('enableIgnoreEnv', false); - // 1. User setting php-cs-fixer - let toolPath = extensionConfig.get('toolPath', ''); + const toolPath = getPcfPath(context); if (!toolPath) { - if (fs.existsSync(path.join(workspace.root, 'vendor', 'bin', 'php-cs-fixer'))) { - // 2. vendor/bin/php-cs-fixer - toolPath = path.join(workspace.root, 'vendor', 'bin', 'php-cs-fixer'); - } else if (fs.existsSync(path.join(context.storagePath, 'php-cs-fixer'))) { - // 3. builtin php-cs-fixer - toolPath = path.join(context.storagePath, 'php-cs-fixer'); - } else { - window.showErrorMessage(`Unable to find the php-cs-fixer tool.`); - return; - } + window.showErrorMessage(`Unable to find the php-cs-fixer tool.`); + return; } const text = document.getText(range); diff --git a/src/engines/pintEngine.ts b/src/engines/pintEngine.ts new file mode 100644 index 0000000..6f3baad --- /dev/null +++ b/src/engines/pintEngine.ts @@ -0,0 +1,99 @@ +import { ExtensionContext, OutputChannel, Range, TextDocument, Uri, window, workspace } from 'coc.nvim'; + +import cp from 'child_process'; +import fs from 'fs'; +import path from 'path'; +import tmp from 'tmp'; +import { getPintPath } from '../common'; + +export async function doFormat( + context: ExtensionContext, + outputChannel: OutputChannel, + document: TextDocument, + range?: Range +): Promise { + if (document.languageId !== 'php') { + window.showErrorMessage(`php-cs-fixer.pint cannot run, not a php file`); + return; + } + + const extensionConfig = workspace.getConfiguration('php-cs-fixer'); + let extensionPintConfig = extensionConfig.get('pint.config', ''); + const preset = extensionConfig.get('pint.preset', 'laravel'); + + const toolPath = getPintPath(context); + if (!toolPath) { + window.showErrorMessage(`Unable to find the pint tool.`); + return; + } + + const text = document.getText(range); + const args: string[] = []; + const cwd = Uri.file(workspace.root).fsPath; + + const opts = { cwd, shell: true }; + + args.push(toolPath); + + const existsPintConfigFile = isExistsPintConfigFileFromProjectRoot(); + + if (extensionPintConfig) { + if (!path.isAbsolute(extensionPintConfig)) { + let currentPath = opts.cwd; + const triedPaths = [currentPath]; + while (!fs.existsSync(currentPath + path.sep + extensionPintConfig)) { + const lastPath = currentPath; + currentPath = path.dirname(currentPath); + if (lastPath == currentPath) { + window.showErrorMessage(`Unable to find ${extensionPintConfig} file in ${triedPaths.join(', ')}`); + return ''; + } else { + triedPaths.push(currentPath); + } + } + extensionPintConfig = currentPath + path.sep + extensionPintConfig; + } + args.push('--config=' + extensionPintConfig); + } else if (existsPintConfigFile) { + // If the pint.json config file exists for the project root. + // + // ...noop + } else { + if (preset) { + args.push(`--preset=${preset}`); + } + } + + const tmpFile = tmp.fileSync(); + fs.writeFileSync(tmpFile.name, text); + + // ---- Output the command to be executed to channel log. ---- + outputChannel.appendLine(`${'#'.repeat(10)} pint\n`); + outputChannel.appendLine(`Run: php ${args.join(' ')} ${tmpFile.name}`); + outputChannel.appendLine(`Opts: ${JSON.stringify(opts)}\n`); + + return new Promise(function (resolve) { + cp.execFile('php', [...args, tmpFile.name], opts, function (err) { + if (err) { + tmpFile.removeCallback(); + + if (err.code === 'ENOENT') { + window.showErrorMessage('Unable to find the pint tool.'); + throw err; + } + + window.showErrorMessage('An error occurred while running pint, please run pint with cli to see if it works'); + throw err; + } + + const text = fs.readFileSync(tmpFile.name, 'utf-8'); + tmpFile.removeCallback(); + + resolve(text); + }); + }); +} + +function isExistsPintConfigFileFromProjectRoot() { + return fs.existsSync(path.join(workspace.root, 'pint.json')); +} diff --git a/src/index.ts b/src/index.ts index 55b068c..e7497c5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,44 +1,61 @@ import { commands, ExtensionContext, window, workspace } from 'coc.nvim'; import fs from 'fs'; -import path from 'path'; -import * as fixCodeActionFeature from './actions/fix'; -import * as downloadCommandFeature from './commands/download'; -import * as fixCommandFeature from './commands/fix'; +import * as pcfFixCodeActionFeature from './actions/pcfFix'; +import * as pintFixCodeActionFeature from './actions/pintFix'; +import * as pcfDownloadCommandFeature from './commands/pcfDownload'; +import * as pcfFixCommandFeature from './commands/pcfFix'; +import * as pintDonwloadCommandFeature from './commands/pintDownload'; +import * as pintFixCommandFeature from './commands/pintFix'; import * as showOutputCommandFeature from './commands/showOutput'; -import * as fixerDocumentFormatFeature from './documentFormats/fixer'; +import { getPcfPath, getPintPath } from './common'; +import * as pcfFixDocumentFormatFeature from './documentFormats/pcfFix'; +import * as pintFixDocumentFormatFeature from './documentFormats/pintFix'; import * as statusBarFeature from './statusBar'; export async function activate(context: ExtensionContext): Promise { if (!workspace.getConfiguration('php-cs-fixer').get('enable', true)) return; - const outputChannel = window.createOutputChannel('php-cs-fixer'); const extensionStoragePath = context.storagePath; if (!fs.existsSync(extensionStoragePath)) { fs.mkdirSync(extensionStoragePath); } - fixCommandFeature.activate(context, outputChannel); - downloadCommandFeature.activate(context); + const outputChannel = window.createOutputChannel('php-cs-fixer'); showOutputCommandFeature.activate(context, outputChannel); + pcfDownloadCommandFeature.activate(context); + pintDonwloadCommandFeature.activate(context); + + const activateTool = workspace.getConfiguration('php-cs-fixer').get('activateTool', 'php-cs-fixer'); - let toolPath = workspace.getConfiguration('php-cs-fixer').get('toolPath', ''); - if (!toolPath) { - if (fs.existsSync(path.join(workspace.root, 'vendor', 'bin', 'php-cs-fixer'))) { - toolPath = path.join(workspace.root, 'vendor', 'bin', 'php-cs-fixer'); - } else if (fs.existsSync(path.join(context.storagePath, 'php-cs-fixer'))) { - toolPath = path.join(context.storagePath, 'php-cs-fixer'); + let toolPath: string | undefined; + if (activateTool === 'php-cs-fixer') { + toolPath = getPcfPath(context); + if (workspace.getConfiguration('php-cs-fixer').get('downloadCheckOnStartup', true)) { + if (!toolPath) { + commands.executeCommand('php-cs-fixer.download'); + } } - } - if (workspace.getConfiguration('php-cs-fixer').get('downloadCheckOnStartup', true)) { - if (!toolPath) { - commands.executeCommand('php-cs-fixer.download'); + } else if (activateTool === 'pint') { + toolPath = getPintPath(context); + if (workspace.getConfiguration('php-cs-fixer').get('downloadCheckOnStartup', true)) { + if (!toolPath) { + commands.executeCommand('php-cs-fixer.pintDownload'); + } } } if (!toolPath) return; - fixerDocumentFormatFeature.activate(context, outputChannel); - fixCodeActionFeature.activate(context); - statusBarFeature.activate(context); + if (activateTool === 'php-cs-fixer') { + pcfFixCommandFeature.activate(context, outputChannel); + pcfFixDocumentFormatFeature.activate(context, outputChannel); + pcfFixCodeActionFeature.activate(context); + } else if (activateTool === 'pint') { + pintFixCommandFeature.activate(context, outputChannel); + pintFixDocumentFormatFeature.activate(context, outputChannel); + pintFixCodeActionFeature.activate(context); + } + + if (activateTool === 'php-cs-fixer' || activateTool === 'pint') statusBarFeature.activate(context); } diff --git a/src/statusBar.ts b/src/statusBar.ts index 6a087c3..99a6247 100644 --- a/src/statusBar.ts +++ b/src/statusBar.ts @@ -19,8 +19,14 @@ export async function activate(context: ExtensionContext) { if (!workspace.getConfiguration('php-cs-fixer').get('enable')) { statusBar.hide(); } else if (['php'].includes(document.languageId)) { - statusBar.text = 'PhpCsFixer'; - statusBar.show(); + const activateTool = workspace.getConfiguration('php-cs-fixer').get('activateTool', 'php-cs-fixer'); + if (activateTool === 'php-cs-fixer') { + statusBar.text = 'PhpCsFixer'; + statusBar.show(); + } else if (activateTool === 'pint') { + statusBar.text = 'Pint'; + statusBar.show(); + } } else { statusBar.hide(); } From e70302ed4778dbf938ba09098297ffb51fc6f17c Mon Sep 17 00:00:00 2001 From: yaegassy Date: Tue, 5 Jul 2022 10:20:14 +0900 Subject: [PATCH 2/2] chore(doc): update --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e97bbff..151da15 100644 --- a/README.md +++ b/README.md @@ -27,13 +27,15 @@ The formatter tool used is `php-cs-fixer` by default. If you want to use `larave } ``` +- [DEMO](https://github.com/yaegassy/coc-php-cs-fixer/pull/7#issue-1293669659) + --- Detects the `php-cs-fixer` or `pint` tool. They are prioritized in order from the top. 1. `php-cs-fixer.toolPath` or `php-cs-fixer.pint.toolPath` 1. `vendor/bin/php-cs-fixer` or `vendor/bin/pint` -1. `php-cs-fixer` retrieved by the download feature (`:CocCommand php-cs-fixer.download` or `php-cs-fixer.pintDownload`) +1. `php-cs-fixer` or `pint` retrieved by the download feature (`:CocCommand php-cs-fixer.download` or `php-cs-fixer.pintDownload`) - **php-cs-fixer**: - Mac/Linux: `~/.config/coc/extensions/coc-php-cs-fixer-data/php-cs-fixer` - Windows: `~/AppData/Local/coc/extensions/coc-php-cs-fixer-data/php-cs-fixer`