├── .github ├── dependabot.yml └── workflows │ ├── release.yaml │ └── test.yaml ├── .gitignore ├── .run ├── runIde.run.xml └── test.run.xml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main ├── kotlin │ └── com │ │ └── funivan │ │ └── idea │ │ └── phpClean │ │ ├── actions │ │ └── useNamedConstructor │ │ │ ├── NamedConstructorUsageViewDescriptor.kt │ │ │ ├── UseNamedConstructorAction.html │ │ │ ├── UseNamedConstructorAction.kt │ │ │ ├── UseNamedConstructorHandler.kt │ │ │ └── UseNamedConstructorProcessor.kt │ │ ├── constrains │ │ ├── ConstrainInterface.kt │ │ ├── clazz │ │ │ └── IsAloneClass.kt │ │ └── method │ │ │ ├── IsMagic.kt │ │ │ ├── Name.kt │ │ │ └── aliases │ │ │ └── InvalidClassReturnType.kt │ │ ├── inspections │ │ ├── assignMisused │ │ │ ├── AssignMisusedInspection.html │ │ │ └── AssignMisusedInspection.kt │ │ ├── classNameCollision │ │ │ ├── ClassNameCollisionInspection.html │ │ │ └── ClassNameCollisionInspection.kt │ │ ├── deprecatedDocTag │ │ │ ├── DeprecatedDocTagInspection.html │ │ │ └── DeprecatedDocTagInspection.kt │ │ ├── globalVariableUsage │ │ │ ├── GlobalVariableUsageInspection.html │ │ │ └── GlobalVariableUsageInspection.kt │ │ ├── methodCanBePrivate │ │ │ ├── MethodCanBePrivateInspection.html │ │ │ └── MethodCanBePrivateInspection.kt │ │ ├── methodShouldBeFinal │ │ │ ├── MethodShouldBeFinalInspection.html │ │ │ └── MethodShouldBeFinalInspection.kt │ │ ├── methodVisibility │ │ │ ├── MethodVisibilityInspection.html │ │ │ └── MethodVisibilityInspection.kt │ │ ├── missingParameterType │ │ │ ├── ClassesByFqn.kt │ │ │ ├── Implementations.kt │ │ │ ├── InvalidMethodParameters.kt │ │ │ ├── MissingParameterTypeDeclarationInspection.html │ │ │ ├── MissingParameterTypeDeclarationInspection.kt │ │ │ └── Parameters.kt │ │ ├── missingReturnType │ │ │ ├── MissingReturnTypeInspection.html │ │ │ └── MissingReturnTypeInspection.kt │ │ ├── parentPropertyDeprecated │ │ │ ├── ParentPropertyDeprecatedInspection.html │ │ │ └── ParentPropertyDeprecatedInspection.kt │ │ ├── prohibitedClassExtend │ │ │ ├── ProhibitedClassExtendInspection.html │ │ │ └── ProhibitedClassExtendInspection.kt │ │ ├── propertyAnnotation │ │ │ ├── AddNullTypeQF.kt │ │ │ ├── PropertyAnnotationInspection.html │ │ │ └── PropertyAnnotationInspection.kt │ │ ├── propertyCanBePrivate │ │ │ ├── PropertyCanBePrivateInspection.html │ │ │ └── PropertyCanBePrivateInspection.kt │ │ ├── redundantDocCommentTag │ │ │ ├── RedundantDocCommentTagInspection.html │ │ │ ├── RedundantDocCommentTagInspection.kt │ │ │ └── tags │ │ │ │ ├── ParameterInfo.kt │ │ │ │ ├── ParameterType.kt │ │ │ │ └── ReturnType.kt │ │ ├── toStringCall │ │ │ ├── AddToStringCallQF.kt │ │ │ ├── IsSingleClassType.kt │ │ │ ├── IsToStringContext.kt │ │ │ ├── ToStringCallInspection.html │ │ │ └── ToStringCallInspection.kt │ │ └── virtualTypeCheck │ │ │ ├── UseAssertQF.kt │ │ │ ├── VirtualTypeCheckInspection.html │ │ │ └── VirtualTypeCheckInspection.kt │ │ ├── qf │ │ └── makeClassMemberPrivate │ │ │ └── MakeClassMemberPrivateQF.kt │ │ ├── spl │ │ ├── ParameterDescription.kt │ │ ├── PhpCleanInspection.kt │ │ ├── Pointer.kt │ │ └── jb │ │ │ └── qf │ │ │ └── RemoveTagQF.kt │ │ └── visitors │ │ └── MethodVisitor.kt └── resources │ ├── META-INF │ ├── plugin.xml │ └── pluginIcon.svg │ └── inspectionDescriptions │ └── .gitkeep └── test └── kotlin └── com └── funivan └── idea └── phpClean ├── BaseInspectionTest.kt └── inspections ├── assignMisused └── AssignMisusedInspectionTest.kt ├── classNameCollision └── ClassNameCollisionInspectionTest.kt ├── deprecatedDocTag └── DeprecatedDocTagInspectionTest.kt ├── globalVariableUsage └── GlobalVariableUsageInspectionTest.kt ├── methodCanBePrivate └── MethodCanBePrivateInspectionTest.kt ├── methodShouldBeFinal └── MethodShouldBeFinalInspectionTest.kt ├── methodVisibility └── MethodVisibilityInspectionTest.kt ├── missingParameterType └── MissingParameterTypeDeclarationInspectionTest.kt ├── missingReturnType └── MissingReturnTypeInspectionTest.kt ├── parentPropertyDeprecated └── ParentPropertyDeprecatedInspectionTest.kt ├── prohibitedClassExtend └── ProhibitedClassExtendInspectionTest.kt ├── propertyAnnotation └── PropertyAnnotationInspectionTest.kt ├── propertyCanBePrivate └── PropertyCanBePrivateInspectionTest.kt ├── redundantDocCommentTag └── RedundantDocCommentTagInspectionTest.kt ├── toStringCall ├── ToStringCallInspectionTest.kt ├── ToStringCallInspectionTestFunction.kt ├── ToStringCallInspectionTestTypeCast.kt └── ToStringCallInspectionTestVariable.kt └── virtualTypeCheck └── VirtualTypeCheckInspectionTest.kt /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gradle" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | jobs: 8 | release: 9 | name: Release plugin 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-java@v1 14 | with: 15 | java-version: 11 16 | - name: Save plugin version and type to ENV 17 | run: | 18 | echo "PLUGIN_VERSION=$(echo ${GITHUB_REF:10})" >> $GITHUB_ENV 19 | echo "PLUGIN_CHANNEL=$(echo "${GITHUB_REF}" | grep -oE "[^-]+$" | grep -oE "^[a-z]+$")" >> $GITHUB_ENV 20 | - name: Configure Gradle environment variables 21 | run: | 22 | echo "ORG_GRADLE_PROJECT_pluginVersion=$(echo "${PLUGIN_VERSION}")" >> $GITHUB_ENV 23 | echo "ORG_GRADLE_PROJECT_intellijPublishToken=${{ secrets.intellijPublishToken }}" >> $GITHUB_ENV 24 | echo "ORG_GRADLE_PROJECT_intellijPublishChannel=$(echo "${PLUGIN_CHANNEL}")" >> $GITHUB_ENV 25 | - name: Print environment variables 26 | run: | 27 | echo "PLUGIN_VERSION=$PLUGIN_VERSION" 28 | echo "PLUGIN_CHANNEL=$PLUGIN_CHANNEL" 29 | echo "ORG_GRADLE_PROJECT_pluginVersion=$ORG_GRADLE_PROJECT_version" 30 | echo "ORG_GRADLE_PROJECT_intellijPublishToken=$ORG_GRADLE_PROJECT_intellijPublishToken" 31 | echo "ORG_GRADLE_PROJECT_intellijPublishChannel=$ORG_GRADLE_PROJECT_intellijPublishChannel" 32 | 33 | - name: Publish plugin to JB [STABLE] 34 | uses: burrunan/gradle-cache-action@v1.5 35 | if: ${{ env.PLUGIN_CHANNEL == '' }} 36 | with: 37 | arguments: publishPlugin 38 | 39 | - name: Publishplugin to JB [ALPHA] 40 | uses: burrunan/gradle-cache-action@v1.5 41 | if: ${{ env.PLUGIN_CHANNEL == 'alpha' }} 42 | with: 43 | arguments: publishPlugin 44 | 45 | - name: Build Nightly version 46 | uses: burrunan/gradle-cache-action@v1.5 47 | if: ${{ env.PLUGIN_CHANNEL == 'nightly' }} 48 | with: 49 | arguments: buildPlugin 50 | 51 | - name: Upload binaries to release 52 | uses: svenstaro/upload-release-action@v2 53 | with: 54 | repo_token: ${{ secrets.GITHUB_TOKEN }} 55 | tag: ${{ github.ref }} 56 | file: build/libs/* 57 | file_glob: true 58 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | jobs: 6 | test: 7 | name: Test 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-java@v1 12 | with: 13 | java-version: 11 14 | 15 | - uses: burrunan/gradle-cache-action@v1.5 16 | with: 17 | arguments: check buildPlugin 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .env 3 | PhpClean.iml 4 | .grade/ 5 | out/ 6 | build/ 7 | .gradle/ 8 | local.properties 9 | src/main/resources/inspectionDescriptions/*.html 10 | !/**/.gitkeep 11 | -------------------------------------------------------------------------------- /.run/runIde.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | true 19 | true 20 | 21 | 22 | 31 | 32 | false 33 | 34 | 35 | -------------------------------------------------------------------------------- /.run/test.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | true 19 | true 20 | 21 | 22 | 31 | 32 | false 33 | 34 | 35 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # PhpClean Changelog 4 | ## [2023.12.17] 5 | ### Fixed 6 | - #186 RedundantDocCommentTagInspection - Skip checking array shapes 7 | 8 | 9 | # PhpClean Changelog 10 | ## [2023.04.01] 11 | ### Fixed 12 | - #162 Skip "use assert" qf before variable declaration 13 | ### Added 14 | - Introduce new action - use named constructor 15 | 16 | ## [2022.08.30] 17 | ### Changed 18 | - #163 Skip "final check" for anonymous classes 19 | - #151 Fix property visibility inspection for php8.1 20 | - #156 Skip Virtual type check for generic types 21 | 22 | ## [2022.10.22] 23 | ### Changed 24 | - #143 [Fixed] Check automatic string typecast on methods only 25 | 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Ivan Shcherbak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PhpClean - PhpStorm/IDEA plugin 2 | 3 | [![Version](https://img.shields.io/jetbrains/plugin/v/11272.svg?style=flat-square)](https://plugins.jetbrains.com/plugin/11272-phpclean) 4 | [![Downloads](https://img.shields.io/jetbrains/plugin/d/11272.svg?style=flat-square)](https://plugins.jetbrains.com/plugin/11272-phpclean) 5 | [![License](https://img.shields.io/github/license/funivan/PhpClean.svg?style=flat-square)](LICENSE.md) 6 | 7 | 8 | 9 | Static Code Analysis for PhpStorm and Intellij Idea. 10 | 11 | ## Installation 12 | Open IDE go to `Settings->Plugins->Marketplace` search for the `PhpClean`. 13 | Hit `install` button. 14 | 15 | ![GitHub commits (since latest release)](https://img.shields.io/github/commits-since/funivan/PhpClean/latest.svg?style=flat-square) 16 | 17 | [- Inspections](#Inspections) 18 | 19 | [- Actions](#Actions) 20 | 21 | 22 | 23 | ## Inspections 24 | #### AssignMisused 25 | Detects assignment and comparison operators in one statement. 26 | ```php 27 | while (false !== $current = ldap_next_entry($con, $current)) { 28 | // ^^^ Hard to read this statements 29 | yield $this->getSingleEntry($con, $current); 30 | } 31 | ``` 32 | #### ClassNameCollision 33 | Classes with same name in different namespaces can be confused. 34 | (Disabled by default) 35 | ```php 36 | namespace App { 37 | class User {}; // <- Class name collision with \Cli\User 38 | } 39 | namespace Cli { 40 | class User {}; // <- Class name collision with \App\User 41 | } 42 | ``` 43 | #### DeprecatedDocTag 44 | You can deprecate some PhpDoc tags in your project. 45 | #### GlobalVariableUsage 46 | This inspection detects usages of global variables. 47 | ```php 48 | echo $_GET['name']; // <-- Global variable usage 49 | ``` 50 | #### MethodCanBePrivate 51 | Protected methods can be converted to private. 52 | ```php 53 | final class User { 54 | protected function name() {} // <-- Method can be private 55 | } 56 | ``` 57 | #### MethodShouldBeFinal 58 | Methods should be closed (make method or class final) 59 | ```php 60 | class User { 61 | public function name(): string { // <-- Method should be final 62 | return ''; 63 | } 64 | } 65 | ``` 66 | #### MethodVisibility 67 | Protected methods make our classes more open. Write private or public methods only. 68 | #### MissingParameterTypeDeclaration 69 | Always specify parameter type. This is a good practice. 70 | ```php 71 | class User { 72 | public function withName($name) {} // <-- Missing parameter type 73 | } 74 | ``` 75 | #### MissingReturnType 76 | Always specify result type of the function. 77 | ```php 78 | function phrase() { // <-- Missing return type 79 | return 'hi'; 80 | } 81 | ``` 82 | #### ParentPropertyDeprecated 83 | Check if parent property is deprecated. 84 | ```php 85 | class A { 86 | /** @deprecated */ 87 | protected $name; 88 | } 89 | class B extends A { 90 | protected $name; // <-- Warn about deprecation 91 | } 92 | ``` 93 | #### ProhibitedClassExtend 94 | Classes marked with `@final` doc tag should not be extended 95 | ```php 96 | /** 97 | * @final 98 | */ 99 | class User {}; 100 | 101 | class Admin extends User {}; // <- Prohibited extentions of @final class User. 102 | ``` 103 | #### PropertyAnnotation 104 | Properties that are not initialized in the constructor should be annotated as nullable. 105 | ```php 106 | class User { 107 | /** @var string */ // <-- Property is not annotated correctly. Add null type 108 | private $name; 109 | public function getName() { } 110 | public function setName(string $name) { } 111 | } 112 | ``` 113 | #### PropertyCanBePrivate 114 | Protected properties can be converted to private. 115 | ```php 116 | class User { 117 | protected $user; // <-- Property can be private 118 | } 119 | ``` 120 | #### RedundantDocCommentTag 121 | Types that are specified in the php can be omitted in the PhpDoc blocks
122 | ```php 123 | /** 124 | * @return void // <-- Redundant PhpDoc tag 125 | */ 126 | function show(string $message): void {} 127 | ``` 128 | #### ToStringCall 129 | Detect automatic type casting 130 | ```php 131 | class Hello { 132 | public function randomize(): self { /* ... */return $this; } 133 | public function __toString() { return 'Hi'; } 134 | } 135 | echo (new Hello())->randomize(); // <-- Deprecated __toString call 136 | ``` 137 | #### VirtualTypeCheck 138 | Use assert to check variable type instead of doc comment. 139 | ```php 140 | /** @var User $user */ // <-- Use assert to check variable type 141 | assert($user instanceof User); 142 | ``` 143 | 144 | ## Actions 145 | #### UseNamedConstructor 146 | Replace `new ClassName()` with selected named constructor. 147 | 148 | ```php 149 | class Text { 150 | public function __construct(string $name){ } 151 | public static fromName(string $n){} 152 | } 153 | ``` 154 | Invoke `refactor this` on method name `fromName` 155 | and all new statements with this class will be changed 156 | 157 | ```php 158 | new Text('User') // old code 159 | Text::fromName('User') // new code 160 | ``` 161 | 162 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | id("java") 5 | id("org.jetbrains.kotlin.jvm") version "2.1.10" 6 | id("org.jetbrains.intellij") version "1.17.4" 7 | id("org.jetbrains.changelog") version "2.2.1" 8 | // ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle 9 | // id("org.jlleitschuh.gradle.ktlint") version "10.1.0" 10 | } 11 | fun properties(key: String) = project.findProperty(key).toString() 12 | 13 | // Import variables from gradle.properties file 14 | val pluginGroup: String by project 15 | // `pluginName_` variable ends with `_` because of the collision with Kotlin magic getter in the `intellij` closure. 16 | // Read more about the issue: https://github.com/JetBrains/intellij-platform-plugin-template/issues/29 17 | val pluginName_: String by project 18 | val pluginVersion: String by project 19 | val pluginSinceBuild: String by project 20 | val pluginVerifierIdeVersions: String by project 21 | val intellijPublishChannel: String by project 22 | val intellijPublishToken: String by project 23 | 24 | val platformType: String by project 25 | val platformVersion: String by project 26 | val platformPlugins: String by project 27 | val platformDownloadSources: String by project 28 | 29 | group = pluginGroup 30 | version = pluginVersion 31 | println("version: $version") 32 | // Configure project's dependencies 33 | repositories { 34 | mavenCentral() 35 | } 36 | 37 | // Configure gradle-intellij-plugin plugin. 38 | // Read more: https://github.com/JetBrains/gradle-intellij-plugin 39 | intellij { 40 | pluginName.set(pluginName_) 41 | version.set(platformVersion) 42 | type.set(platformType) 43 | downloadSources.set(platformDownloadSources.toBoolean()) 44 | updateSinceUntilBuild.set(true) 45 | // Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file. 46 | plugins.set(properties("platformPlugins").split(',').map(String::trim).filter(String::isNotEmpty)) 47 | } 48 | dependencies { 49 | testImplementation(gradleTestKit()) 50 | testImplementation(kotlin("test")) 51 | testImplementation(kotlin("test-junit")) 52 | } 53 | 54 | // Read more: https://github.com/JetBrains/gradle-changelog-plugin 55 | changelog { 56 | version.set(pluginVersion) 57 | headerParserRegex.set("""(\d+\.\d+\.\d+)""".toRegex()) 58 | } 59 | tasks { 60 | properties("javaVersion").let { 61 | withType { 62 | sourceCompatibility = it 63 | targetCompatibility = it 64 | } 65 | withType { 66 | kotlinOptions.jvmTarget = it 67 | } 68 | } 69 | 70 | publishPlugin { 71 | token.set(intellijPublishToken) 72 | channels.set(listOf(intellijPublishChannel, "default").filter(String::isNotEmpty).take(1)) 73 | } 74 | patchPluginXml { 75 | version.set(pluginVersion) 76 | sinceBuild.set(pluginSinceBuild) 77 | changeNotes.set( 78 | changelog.getLatest().toHTML() 79 | ) 80 | } 81 | 82 | runPluginVerifier { 83 | ideVersions.set( 84 | properties("pluginVerifierIdeVersions") 85 | .split(',').map(String::trim).filter(String::isNotEmpty) 86 | ) 87 | } 88 | 89 | register("copyInspections") { 90 | doLast { 91 | inspections().forEach { 92 | write( 93 | File("src/main/resources/inspectionDescriptions/" + it.file().name), 94 | it.content() 95 | ) 96 | } 97 | } 98 | } 99 | register("checkReadme") { 100 | doLast { 101 | if (readmeFile().readText() != generatedReadmeContent(readmeFile())) { 102 | throw GradleException("Readme is not up to date") 103 | } 104 | } 105 | } 106 | register("updateReadme") { 107 | doLast { 108 | val readme = readmeFile() 109 | if (write(readme, generatedReadmeContent(readme))) { 110 | println("Readme updated") 111 | } 112 | } 113 | } 114 | 115 | named("test") { 116 | dependsOn("checkReadme") 117 | } 118 | named("buildPlugin") { 119 | dependsOn("copyInspections") 120 | } 121 | named("runIde") { 122 | dependsOn("copyInspections") 123 | } 124 | } 125 | tasks.getByName("buildSearchableOptions").onlyIf { false } 126 | 127 | // Custom functions 128 | fun write(file: File, content: String): Boolean { 129 | var result = false 130 | if (!file.exists()) { 131 | file.createNewFile() 132 | } 133 | if (file.readText() != content) { 134 | result = true 135 | file.writeText(content) 136 | } 137 | return result 138 | } 139 | 140 | class Descriptor( 141 | private val file: File, 142 | private val uid: String 143 | ) { 144 | fun uid() = uid 145 | fun file() = file 146 | fun content() = file.readText() 147 | fun short() = content() 148 | .replace(Regex("(.*)", RegexOption.DOT_MATCHES_ALL), "") 149 | .trim() 150 | } 151 | 152 | fun actions() = projectHtmlFiles("Action") 153 | fun inspections() = projectHtmlFiles("Inspection") 154 | 155 | 156 | fun generatedReadmeContent(readme: File): String = 157 | readme.readText().replace( 158 | Regex(".+", RegexOption.DOT_MATCHES_ALL), "" 159 | ) + "\n" + generateSections() 160 | 161 | fun readmeFile() = File("README.md") 162 | fun projectHtmlFiles(type: String) = File("src/main/kotlin/com/funivan/idea/phpClean") 163 | .walkTopDown() 164 | .filter { it.name.contains("${type}.html") } 165 | .map { 166 | Descriptor( 167 | File(it.path), 168 | it.name.replace("${type}.html", "") 169 | ) 170 | } 171 | 172 | fun generateSections() = listOf( 173 | Pair("Inspections", inspections()), 174 | Pair("Actions", actions()), 175 | ).fold("") { acc, pair -> 176 | acc + "## ${pair.first}\n" + 177 | pair.second.sortedBy { it.uid() } 178 | .map { 179 | val description = it.short().replace("
", "```php").replace("
", "```") 180 | "#### ${it.uid()}\n$description\n" 181 | } 182 | .joinToString("") + 183 | "\n" 184 | } 185 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | pluginGroup = com.funivan.idea.phpClean 2 | pluginName_ = PhpClean 3 | name = PhpClean 4 | pluginVersion = 2023.12.17 5 | pluginSinceBuild = 2022.2.3 6 | #pluginUntilBuild = 203.* 7 | # Plugin Verifier integration -> https://github.com/JetBrains/gradle-intellij-plugin#plugin-verifier-dsl 8 | # See https://jb.gg/intellij-platform-builds-list for available build versions 9 | pluginVerifierIdeVersions = 2022.3.2 10 | platformType = IU 11 | platformVersion = 2022.2.3 12 | platformDownloadSources = true 13 | # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html 14 | platformPlugins = com.intellij.java, com.jetbrains.php:222.4345.15 15 | javaVersion = 17 16 | # Opt-out flag for bundling Kotlin standard library. 17 | # See https://kotlinlang.org/docs/reference/using-gradle.html#dependency-on-the-standard-library for details. 18 | kotlin.stdlib.default.dependency = false 19 | 20 | # Publish configuration 21 | intellijPublishChannel="" 22 | intellijPublishToken="" 23 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funivan/PhpClean/945e8e5b401999f2dc5d115b5d83ef4460a467d2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = name -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/actions/useNamedConstructor/NamedConstructorUsageViewDescriptor.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.actions.useNamedConstructor 2 | 3 | import com.intellij.usageView.UsageViewBundle 4 | import com.intellij.usageView.UsageViewDescriptor 5 | import com.jetbrains.php.lang.psi.elements.Method 6 | 7 | class NamedConstructorUsageViewDescriptor( 8 | val constructor: Method, 9 | val target: Method 10 | ) : UsageViewDescriptor { 11 | override fun getElements() = 12 | arrayOf(constructor) 13 | 14 | override fun getProcessedElementsHeader() = 15 | "Use named constructor: " + target.name 16 | 17 | override fun getCodeReferencesText(usagesCount: Int, filesCount: Int) = 18 | UsageViewBundle.getReferencesString(usagesCount, filesCount) 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/actions/useNamedConstructor/UseNamedConstructorAction.html: -------------------------------------------------------------------------------- 1 | Replace `new ClassName()` with selected named constructor. 2 | 3 |
 4 | class Text {
 5 |  public function __construct(string $name){ }
 6 |  public static fromName(string $n){}
 7 | }
 8 | 
9 | Invoke `refactor this` on method name `fromName` 10 | and all new statements with this class will be changed 11 | 12 |
13 |  new Text('User') // old code
14 |  Text::fromName('User') // new code
15 | 
16 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/actions/useNamedConstructor/UseNamedConstructorAction.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.actions.useNamedConstructor 2 | 3 | import com.intellij.lang.Language 4 | import com.intellij.openapi.actionSystem.DataContext 5 | import com.intellij.openapi.editor.Editor 6 | import com.intellij.psi.PsiElement 7 | import com.intellij.psi.PsiFile 8 | import com.intellij.refactoring.RefactoringActionHandler 9 | import com.intellij.refactoring.actions.BaseRefactoringAction 10 | import com.jetbrains.php.lang.PhpLanguage 11 | import com.jetbrains.php.lang.psi.elements.Method 12 | 13 | class UseNamedConstructorAction : BaseRefactoringAction() { 14 | 15 | override fun isAvailableInEditorOnly() = false 16 | 17 | override fun isEnabledOnElements(elements: Array): Boolean { 18 | return elements.map(::isNamedConstructorCandidate).all { it == false } 19 | } 20 | 21 | override fun isAvailableOnElementInEditorAndFile( 22 | element: PsiElement, 23 | editor: Editor, 24 | file: PsiFile, 25 | context: DataContext 26 | ): Boolean { 27 | return isNamedConstructorCandidate(element) 28 | } 29 | 30 | private fun isNamedConstructorCandidate(element: PsiElement): Boolean { 31 | if (element is Method && element.isStatic) { 32 | val constructorParameters = element.containingClass?.constructor?.let { it.parameters.map { it.typeDeclaration?.text ?: "" } } 33 | val methodParameters = element.parameters.map { it.typeDeclaration?.text ?: "" } 34 | return constructorParameters == methodParameters 35 | } 36 | return false 37 | } 38 | 39 | override fun getHandler(dataContext: DataContext): RefactoringActionHandler { 40 | return UseNamedConstructorHandler() 41 | } 42 | 43 | override fun isAvailableForFile(file: PsiFile): Boolean { 44 | return isAvailableForLanguage(file.language) 45 | } 46 | 47 | override fun isAvailableForLanguage(language: Language): Boolean { 48 | return language.isKindOf(PhpLanguage.INSTANCE) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/actions/useNamedConstructor/UseNamedConstructorHandler.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.actions.useNamedConstructor 2 | 3 | import com.intellij.openapi.actionSystem.CommonDataKeys 4 | import com.intellij.openapi.actionSystem.DataContext 5 | import com.intellij.openapi.editor.Editor 6 | import com.intellij.openapi.project.Project 7 | import com.intellij.psi.PsiElement 8 | import com.intellij.psi.PsiFile 9 | import com.intellij.refactoring.RefactoringActionHandler 10 | import com.jetbrains.php.lang.psi.elements.Method 11 | 12 | class UseNamedConstructorHandler : RefactoringActionHandler { 13 | override fun invoke(project: Project, editor: Editor?, file: PsiFile?, dataContext: DataContext?) { 14 | if (editor == null) { 15 | return 16 | } 17 | dataContext 18 | ?.let { CommonDataKeys.PSI_ELEMENT.getData(it) as Method? } 19 | ?.let { 20 | val constructor = it.containingClass?.ownConstructor 21 | if (constructor != null) { 22 | UseNamedConstructorProcessor(constructor, it).run() 23 | } 24 | } 25 | } 26 | 27 | override fun invoke(project: Project, elements: Array, dataContext: DataContext?) { 28 | // Do nothing. This action can not be triggered in non-editor context 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/actions/useNamedConstructor/UseNamedConstructorProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.actions.useNamedConstructor 2 | 3 | import com.intellij.psi.search.searches.ReferencesSearch 4 | import com.intellij.psi.util.parentOfType 5 | import com.intellij.psi.util.parentOfTypes 6 | import com.intellij.refactoring.BaseRefactoringProcessor 7 | import com.intellij.usageView.UsageInfo 8 | import com.intellij.usageView.UsageViewDescriptor 9 | import com.jetbrains.php.lang.psi.PhpPsiElementFactory 10 | import com.jetbrains.php.lang.psi.elements.Method 11 | import com.jetbrains.php.lang.psi.elements.NewExpression 12 | 13 | class UseNamedConstructorProcessor( 14 | val constructor: Method, 15 | val namedConstructor: Method, 16 | ) : BaseRefactoringProcessor(constructor.project) { 17 | 18 | override fun createUsageViewDescriptor(usages: Array): UsageViewDescriptor { 19 | return NamedConstructorUsageViewDescriptor(constructor, namedConstructor) 20 | } 21 | 22 | override fun findUsages(): Array { 23 | return ReferencesSearch.search(constructor) 24 | .map { it.element.parent as? NewExpression } 25 | .filter { 26 | val inMethod = it?.parentOfTypes(Method::class) 27 | if (inMethod is Method) { 28 | val isSameM = inMethod.name == namedConstructor.name 29 | val isSameClass = inMethod.containingClass?.fqn == namedConstructor.containingClass?.fqn 30 | (isSameM && isSameClass) == false 31 | } else { 32 | true 33 | } 34 | } 35 | .filterNotNull() 36 | .map { UsageInfo(it) }.toTypedArray() 37 | } 38 | 39 | override fun performRefactoring(usages: Array) { 40 | usages.forEach { 41 | (it.element as? NewExpression)?.let { 42 | val n = it.classReference?.element?.text 43 | if (n != null) { 44 | it.replace( 45 | PhpPsiElementFactory.createMethodReference( 46 | it.project, 47 | n + "::" + namedConstructor.name 48 | + "(" + it.parameterList?.text.orEmpty() + ")" 49 | ) 50 | ) 51 | } 52 | } 53 | } 54 | } 55 | 56 | override fun getCommandName() = "Use named constructor" 57 | } 58 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/constrains/ConstrainInterface.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.constrains 2 | 3 | 4 | interface ConstrainInterface { 5 | fun match(target: T): Boolean 6 | operator fun invoke(target: T): Boolean { 7 | return match(target) 8 | } 9 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/constrains/clazz/IsAloneClass.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.constrains.clazz 2 | 3 | import com.funivan.idea.phpClean.constrains.ConstrainInterface 4 | import com.jetbrains.php.PhpClassHierarchyUtils 5 | import com.jetbrains.php.lang.psi.elements.PhpClass 6 | 7 | class IsAloneClass : ConstrainInterface { 8 | override fun match(target: PhpClass): Boolean { 9 | return ( 10 | !target.isAbstract && 11 | !target.isTrait && 12 | PhpClassHierarchyUtils.getAllSubclasses(target).isEmpty() 13 | && 14 | target.extendsList.referenceElements.isEmpty()) 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/constrains/method/IsMagic.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.constrains.method 2 | 3 | import com.funivan.idea.phpClean.constrains.ConstrainInterface 4 | import com.jetbrains.php.lang.psi.elements.Method 5 | 6 | class IsMagic : ConstrainInterface { 7 | companion object { 8 | val names = setOf( 9 | "__construct", 10 | "__destruct", 11 | "__call", 12 | "__callstatic", 13 | "__get", 14 | "__set", 15 | "__isset", 16 | "__unset", 17 | "__sleep", 18 | "__wakeup", 19 | "__tostring", 20 | "__invoke", 21 | "__set_state", 22 | "__clone", 23 | "__debuginfo" 24 | ) 25 | } 26 | 27 | override fun match(target: Method): Boolean { 28 | return names.contains(target.name.lowercase()) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/constrains/method/Name.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.constrains.method 2 | 3 | import com.jetbrains.php.lang.psi.elements.Method 4 | 5 | 6 | class Name(private val regex: Regex) : com.funivan.idea.phpClean.constrains.ConstrainInterface { 7 | 8 | override fun match(target: Method): Boolean { 9 | return regex.matches(target.name) 10 | } 11 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/constrains/method/aliases/InvalidClassReturnType.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.constrains.method.aliases 2 | 3 | import com.jetbrains.php.PhpIndex 4 | import com.jetbrains.php.lang.psi.elements.Method 5 | 6 | 7 | class InvalidClassReturnType : com.funivan.idea.phpClean.constrains.ConstrainInterface { 8 | 9 | override fun match(target: Method): Boolean { 10 | var result = false 11 | if (target.declaredType.isComplete) { 12 | val type = target.declaredType 13 | .filterPrimitives().types 14 | .firstOrNull { it != "\\stdClass" && it != "\\Generator" } 15 | if (type != null) { 16 | val classes = PhpIndex.getInstance(target.project).getAnyByFQN(type) 17 | if (classes.isNotEmpty() && !classes.first().isInterface) { 18 | result = true 19 | } 20 | } 21 | } 22 | return result 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/assignMisused/AssignMisusedInspection.html: -------------------------------------------------------------------------------- 1 | Detects assignment and comparison operators in one statement. 2 |
3 | while (false !== $current = ldap_next_entry($con, $current)) {
4 |   // ^^^ Hard to read this statements
5 |   yield $this->getSingleEntry($con, $current);
6 | }
7 | 
8 | 9 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/assignMisused/AssignMisusedInspection.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.assignMisused 2 | 3 | import com.funivan.idea.phpClean.spl.PhpCleanInspection 4 | import com.intellij.codeInspection.ProblemsHolder 5 | import com.intellij.psi.PsiElementVisitor 6 | import com.intellij.psi.tree.TokenSet 7 | import com.jetbrains.php.lang.lexer.PhpTokenTypes 8 | import com.jetbrains.php.lang.psi.elements.AssignmentExpression 9 | import com.jetbrains.php.lang.psi.elements.BinaryExpression 10 | import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor 11 | 12 | class AssignMisusedInspection : PhpCleanInspection() { 13 | override fun getShortName() = "AssignMisusedInspection" 14 | val tokens = lazy { 15 | TokenSet.create( 16 | PhpTokenTypes.opEQUAL, 17 | PhpTokenTypes.opNOT_EQUAL, 18 | PhpTokenTypes.opIDENTICAL, 19 | PhpTokenTypes.opNOT_IDENTICAL, 20 | PhpTokenTypes.opGREATER, 21 | PhpTokenTypes.opGREATER_OR_EQUAL, 22 | PhpTokenTypes.opLESS, 23 | PhpTokenTypes.opLESS_OR_EQUAL 24 | ) 25 | } 26 | 27 | override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { 28 | return object : PhpElementVisitor() { 29 | override fun visitPhpBinaryExpression(expression: BinaryExpression) { 30 | val operator = expression.operationType 31 | if ( 32 | tokens.value.contains(operator) 33 | && expression.lastChild is AssignmentExpression 34 | ) { 35 | holder.registerProblem( 36 | expression, 37 | "Assignment and comparison operators used in one statement" 38 | ) 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/classNameCollision/ClassNameCollisionInspection.html: -------------------------------------------------------------------------------- 1 | Classes with same name in different namespaces can be confused. 2 | (Disabled by default) 3 |
 4 | namespace App {
 5 |   class User {}; // <- Class name collision with \Cli\User
 6 | }
 7 | namespace Cli {
 8 |   class User {}; // <- Class name collision with \App\User
 9 | }
10 | 
11 | 12 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/classNameCollision/ClassNameCollisionInspection.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.classNameCollision 2 | 3 | import com.funivan.idea.phpClean.spl.PhpCleanInspection 4 | import com.intellij.codeInspection.ProblemsHolder 5 | import com.intellij.psi.PsiElement 6 | import com.intellij.psi.PsiElementVisitor 7 | import com.jetbrains.php.PhpIndex 8 | import com.jetbrains.php.lang.psi.elements.PhpClass 9 | import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor 10 | 11 | class ClassNameCollisionInspection : PhpCleanInspection() { 12 | override fun getShortName() = "ClassNameCollisionInspection" 13 | override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { 14 | return object : PhpElementVisitor() { 15 | override fun visitPhpClass(phpClass: PhpClass) { 16 | phpClass.nameIdentifier?.let { name -> 17 | find(phpClass, name)?.let { clazz -> 18 | holder.registerProblem( 19 | name, 20 | "Class name collision with ${clazz.fqn}" 21 | ) 22 | } 23 | } 24 | } 25 | } 26 | } 27 | 28 | private fun find( 29 | origin: PhpClass, 30 | name: PsiElement 31 | ) = PhpIndex.getInstance(origin.project) 32 | .getClassesByName(name.text) 33 | .firstOrNull { it.fqn != origin.fqn } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/deprecatedDocTag/DeprecatedDocTagInspection.html: -------------------------------------------------------------------------------- 1 | You can deprecate some PhpDoc tags in your project. 2 | 3 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/deprecatedDocTag/DeprecatedDocTagInspection.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.deprecatedDocTag 2 | 3 | import com.funivan.idea.phpClean.spl.PhpCleanInspection 4 | import com.funivan.idea.phpClean.spl.Pointer 5 | import com.funivan.idea.phpClean.spl.jb.qf.RemoveTagQF 6 | import com.intellij.codeInspection.ProblemsHolder 7 | import com.intellij.codeInspection.ui.ListEditForm 8 | import com.intellij.psi.PsiElementVisitor 9 | import com.intellij.util.ui.JBUI 10 | import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag 11 | import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor 12 | import javax.swing.JComponent 13 | 14 | class DeprecatedDocTagInspection : PhpCleanInspection() { 15 | var tags: MutableList = mutableListOf() 16 | override fun getShortName() = "DeprecatedDocTagInspection" 17 | override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { 18 | return object : PhpElementVisitor() { 19 | override fun visitPhpDocTag(tag: PhpDocTag) { 20 | if (tags.contains(tag.name.removePrefix("@"))) { 21 | holder.registerProblem( 22 | tag, 23 | "Deprecated tag", 24 | RemoveTagQF(Pointer(tag).create()) 25 | ) 26 | } 27 | } 28 | } 29 | } 30 | 31 | 32 | override fun createOptionsPanel(): JComponent { 33 | val form = ListEditForm("Deprecated tags", this.tags) 34 | val panel = form.contentPanel 35 | panel.preferredSize = JBUI.size(150, 100) 36 | return panel 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/globalVariableUsage/GlobalVariableUsageInspection.html: -------------------------------------------------------------------------------- 1 | This inspection detects usages of global variables. 2 |
3 | echo $_GET['name']; // <-- Global variable usage
4 | 
5 | 6 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/globalVariableUsage/GlobalVariableUsageInspection.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.globalVariableUsage 2 | 3 | import com.funivan.idea.phpClean.spl.PhpCleanInspection 4 | import com.intellij.codeInspection.ProblemsHolder 5 | import com.intellij.psi.PsiElementVisitor 6 | import com.jetbrains.php.lang.psi.elements.Variable 7 | import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor 8 | 9 | 10 | class GlobalVariableUsageInspection : PhpCleanInspection() { 11 | val names = hashSetOf( 12 | "GLOBALS", 13 | "_SERVER", 14 | "_REQUEST", 15 | "_POST", 16 | "_GET", 17 | "_FILES", 18 | "_ENV", 19 | "_COOKIE", 20 | "_SESSION" 21 | ) 22 | 23 | override fun getShortName() = "GlobalVariableUsageInspection" 24 | override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { 25 | return object : PhpElementVisitor() { 26 | override fun visitPhpVariable(variable: Variable) { 27 | if (names.contains(variable.name)) { 28 | holder.registerProblem( 29 | variable, 30 | "Global variable usage" 31 | ) 32 | } 33 | } 34 | } 35 | } 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/methodCanBePrivate/MethodCanBePrivateInspection.html: -------------------------------------------------------------------------------- 1 | Protected methods can be converted to private. 2 |
3 | final class User {
4 |   protected function name() {} // <-- Method can be private
5 | }
6 | 
7 | 8 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/methodCanBePrivate/MethodCanBePrivateInspection.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.methodCanBePrivate 2 | 3 | import com.funivan.idea.phpClean.qf.makeClassMemberPrivate.MakeClassMemberPrivateQF 4 | import com.funivan.idea.phpClean.spl.PhpCleanInspection 5 | import com.intellij.codeInspection.ProblemsHolder 6 | import com.intellij.psi.PsiElementVisitor 7 | import com.jetbrains.php.lang.psi.elements.PhpClass 8 | import com.jetbrains.php.lang.psi.elements.PhpModifierList 9 | import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor 10 | 11 | class MethodCanBePrivateInspection : PhpCleanInspection() { 12 | override fun getShortName() = "MethodCanBePrivateInspection" 13 | override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { 14 | return object : PhpElementVisitor() { 15 | override fun visitPhpClass(clazz: PhpClass) { 16 | if (clazz.isFinal && clazz.extendsList.referenceElements.isEmpty()) { 17 | for (method in clazz.ownMethods.filter { it.modifier.isProtected }) { 18 | holder.registerProblem( 19 | method.nameIdentifier ?: method, 20 | "Method can be private", 21 | MakeClassMemberPrivateQF.create( 22 | method.firstChild as PhpModifierList?, 23 | "Make method private" 24 | ) 25 | ) 26 | } 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/methodShouldBeFinal/MethodShouldBeFinalInspection.html: -------------------------------------------------------------------------------- 1 | Methods should be closed (make method or class final) 2 |
 3 | class User {
 4 |  public function name(): string { // <-- Method should be final
 5 |    return '';
 6 |  }
 7 | }
 8 | 
9 | 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/methodShouldBeFinal/MethodShouldBeFinalInspection.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.methodShouldBeFinal 2 | 3 | import com.funivan.idea.phpClean.constrains.method.IsMagic 4 | import com.funivan.idea.phpClean.spl.PhpCleanInspection 5 | import com.intellij.codeInspection.ProblemsHolder 6 | import com.intellij.psi.PsiElementVisitor 7 | import com.jetbrains.php.lang.psi.elements.Method 8 | import com.jetbrains.php.lang.psi.elements.PhpClass 9 | import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor 10 | 11 | class MethodShouldBeFinalInspection : PhpCleanInspection() { 12 | private val magic = IsMagic() 13 | override fun getShortName() = "MethodShouldBeFinalInspection" 14 | override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { 15 | return object : PhpElementVisitor() { 16 | override fun visitPhpClass(phpClass: PhpClass) { 17 | if (!phpClass.isFinal && !phpClass.isInterface && !phpClass.isAnonymous) { 18 | for (method in methods(phpClass)) { 19 | holder.registerProblem( 20 | method.nameIdentifier ?: method, 21 | "Method should be final" 22 | ) 23 | } 24 | } 25 | } 26 | } 27 | } 28 | 29 | private fun methods(clazz: PhpClass): List { 30 | return clazz.ownMethods.filter { 31 | !it.modifier.isFinal 32 | && !it.modifier.isPrivate 33 | && !it.modifier.isAbstract 34 | && !it.modifier.isStatic 35 | && !magic.match(it) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/methodVisibility/MethodVisibilityInspection.html: -------------------------------------------------------------------------------- 1 | Protected methods make our classes more open. Write private or public methods only. 2 | 3 | The better way is to use 4 | object composition technique 5 | if you want to make you object more flexible. 6 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/methodVisibility/MethodVisibilityInspection.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.methodVisibility 2 | 3 | import com.funivan.idea.phpClean.spl.PhpCleanInspection 4 | import com.funivan.idea.phpClean.visitors.MethodVisitor 5 | import com.intellij.codeInspection.ProblemsHolder 6 | import com.intellij.psi.PsiElementVisitor 7 | 8 | 9 | class MethodVisibilityInspection : PhpCleanInspection() { 10 | 11 | override fun getShortName(): String { 12 | return "MethodVisibilityInspection" 13 | } 14 | 15 | override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { 16 | return MethodVisitor( 17 | { it.modifier.isProtected }, 18 | "Do not write protected methods. Only public or private", 19 | holder 20 | ) 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/missingParameterType/ClassesByFqn.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.missingParameterType 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.jetbrains.php.PhpIndex 5 | import com.jetbrains.php.lang.psi.elements.PhpClass 6 | 7 | class ClassesByFqn(private val project: Project, private val fqns: Iterable) { 8 | fun all(): List { 9 | val index = PhpIndex.getInstance(project) 10 | return fqns.flatMap { index.getClassesByFQN(it) }.toList() 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/missingParameterType/Implementations.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.missingParameterType 2 | 3 | import com.jetbrains.php.lang.psi.elements.PhpClass 4 | 5 | class Implementations(private val phpClass: PhpClass) : Iterable { 6 | override fun iterator(): Iterator { 7 | return phpClass.extendsList.referenceElements 8 | .plus(phpClass.implementsList.referenceElements) 9 | .map { it.fqn } 10 | .filterNotNull() 11 | .iterator() 12 | } 13 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/missingParameterType/InvalidMethodParameters.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.missingParameterType 2 | 3 | import com.funivan.idea.phpClean.spl.ParameterDescription 4 | import com.jetbrains.php.lang.psi.elements.Method 5 | import com.jetbrains.php.lang.psi.elements.Parameter 6 | 7 | class InvalidMethodParameters(private val method: Method, private val inspectionName: String) { 8 | private var parameters: List? = null 9 | fun method() = method 10 | fun parameters(): List { 11 | val result: List 12 | if (parameters != null) { 13 | result = parameters as List 14 | } else { 15 | val description = ParameterDescription(method) 16 | result = Parameters(method) 17 | .filter { it.declaredType.size() == 0 } 18 | .filter { !description.get(it.name).contains("@Suppress($inspectionName)") } 19 | .toList() 20 | parameters = result 21 | } 22 | return result 23 | } 24 | 25 | 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/missingParameterType/MissingParameterTypeDeclarationInspection.html: -------------------------------------------------------------------------------- 1 | Always specify parameter type. This is a good practice. 2 |
3 | class User {
4 |  public function withName($name) {}  // <-- Missing parameter type
5 | }
6 | 
7 | 8 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/missingParameterType/MissingParameterTypeDeclarationInspection.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.missingParameterType 2 | 3 | 4 | import com.funivan.idea.phpClean.spl.PhpCleanInspection 5 | import com.intellij.codeInspection.ProblemsHolder 6 | import com.jetbrains.php.lang.psi.elements.PhpClass 7 | import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor 8 | 9 | 10 | class MissingParameterTypeDeclarationInspection : PhpCleanInspection() { 11 | 12 | private val name = "MissingParameterTypeDeclarationInspection" 13 | 14 | override fun getShortName() = name 15 | 16 | 17 | override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PhpElementVisitor { 18 | return object : PhpElementVisitor() { 19 | override fun visitPhpClass(phpClass: PhpClass) { 20 | val parent by lazy { 21 | ClassesByFqn(phpClass.project, Implementations(phpClass)).all() 22 | } 23 | val contains = fun(base: List, name: String): Boolean { 24 | return base.firstOrNull { it.findMethodByName(name) != null } != null 25 | } 26 | phpClass.ownMethods 27 | .map { InvalidMethodParameters(it, name) } 28 | .filter { it.parameters().isNotEmpty() } 29 | .filter { 30 | !contains(parent, it.method().name) 31 | } 32 | .flatMap { it.parameters() } 33 | .forEach { 34 | holder.registerProblem(it, "Missing parameter type") 35 | } 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/missingParameterType/Parameters.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.missingParameterType 2 | 3 | import com.jetbrains.php.lang.psi.elements.Method 4 | import com.jetbrains.php.lang.psi.elements.Parameter 5 | 6 | class Parameters(private val method: Method) : Iterable { 7 | override fun iterator(): Iterator { 8 | return method.parameters 9 | .filter { it.name !== "" } 10 | .iterator() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/missingReturnType/MissingReturnTypeInspection.html: -------------------------------------------------------------------------------- 1 | Always specify result type of the function. 2 |
3 | function phrase() { // <-- Missing return type
4 |     return 'hi';
5 | }
6 | 
7 | 8 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/missingReturnType/MissingReturnTypeInspection.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.missingReturnType 2 | 3 | import com.funivan.idea.phpClean.spl.PhpCleanInspection 4 | import com.intellij.codeInspection.ProblemsHolder 5 | import com.intellij.psi.PsiElementVisitor 6 | import com.jetbrains.php.lang.psi.elements.Method 7 | import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor 8 | 9 | 10 | class MissingReturnTypeInspection : PhpCleanInspection() { 11 | private val skip = hashSetOf("__construct", "__clone", "__destruct") 12 | override fun getShortName() = "MissingReturnTypeInspection" 13 | override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { 14 | return object : PhpElementVisitor() { 15 | override fun visitPhpMethod(method: Method) { 16 | if (method.declaredType.isEmpty) { 17 | val name = method.nameNode?.psi 18 | if (name != null && !skip.contains(name.text)) { 19 | holder.registerProblem(name, "Missing return type") 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/parentPropertyDeprecated/ParentPropertyDeprecatedInspection.html: -------------------------------------------------------------------------------- 1 | Check if parent property is deprecated. 2 |
 3 |   class A {
 4 |     /** @deprecated */
 5 |     protected $name;
 6 |   }
 7 |   class B extends A {
 8 |     protected $name; // <-- Warn about deprecation
 9 |   }
10 | 
11 | 12 | The recommended way of resolving this error is to provide deprecated tag for deprecated properties. 13 | 14 | Another case it to remove parent deprecated property. 15 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/parentPropertyDeprecated/ParentPropertyDeprecatedInspection.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.parentPropertyDeprecated 2 | 3 | import com.funivan.idea.phpClean.spl.PhpCleanInspection 4 | import com.intellij.codeInspection.ProblemsHolder 5 | import com.intellij.psi.PsiElementVisitor 6 | import com.jetbrains.php.lang.psi.elements.Field 7 | import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor 8 | 9 | class ParentPropertyDeprecatedInspection : PhpCleanInspection() { 10 | override fun getShortName() = "ParentPropertyDeprecatedInspection" 11 | override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { 12 | return object : PhpElementVisitor() { 13 | override fun visitPhpField(field: Field) { 14 | val nameNode = field.nameNode 15 | if (!field.isConstant && !field.isDeprecated && nameNode != null) { 16 | field.containingClass 17 | ?.superClass 18 | ?.findOwnFieldByName(field.name, false) 19 | ?.let { 20 | if (it.isDeprecated) { 21 | holder.registerProblem(nameNode.psi, "Parent property is deprecated") 22 | } 23 | } 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/prohibitedClassExtend/ProhibitedClassExtendInspection.html: -------------------------------------------------------------------------------- 1 | Classes marked with `@final` doc tag should not be extended 2 |
 3 | /**
 4 |  * @final
 5 |  */
 6 |  class User {};
 7 | 
 8 |  class Admin extends User {}; // <- Prohibited extentions of @final class User.
 9 | 
10 | 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/prohibitedClassExtend/ProhibitedClassExtendInspection.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.prohibitedClassExtend 2 | 3 | import com.funivan.idea.phpClean.inspections.missingParameterType.ClassesByFqn 4 | import com.funivan.idea.phpClean.inspections.missingParameterType.Implementations 5 | import com.funivan.idea.phpClean.spl.PhpCleanInspection 6 | import com.intellij.codeInspection.ProblemsHolder 7 | import com.intellij.psi.PsiElementVisitor 8 | import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment 9 | import com.jetbrains.php.lang.psi.elements.PhpClass 10 | import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor 11 | 12 | class ProhibitedClassExtendInspection : PhpCleanInspection() { 13 | override fun getShortName() = "ProhibitedClassExtendInspection" 14 | override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { 15 | 16 | return object : PhpElementVisitor() { 17 | private fun parent(phpClass: PhpClass) = ClassesByFqn(phpClass.project, Implementations(phpClass)).all() 18 | private fun hasFinalTag(phpClass: PhpClass): Boolean { 19 | val docComment = phpClass.docComment 20 | return when (docComment is PhpDocComment) { 21 | true -> docComment.getTagElementsByName("@final").isNotEmpty() 22 | false -> false 23 | } 24 | } 25 | 26 | override fun visitPhpClass(phpClass: PhpClass) { 27 | phpClass.nameIdentifier?.let { name -> 28 | parent(phpClass).forEach { extendClass -> 29 | if (hasFinalTag(extendClass)) { 30 | holder.registerProblem( 31 | name, 32 | "Prohibited extentions of @final class ${extendClass.fqn}" 33 | ) 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/propertyAnnotation/AddNullTypeQF.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.propertyAnnotation 2 | 3 | import com.intellij.codeInspection.LocalQuickFix 4 | import com.intellij.codeInspection.ProblemDescriptor 5 | import com.intellij.openapi.project.Project 6 | import com.intellij.psi.SmartPsiElementPointer 7 | import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocParamTag 8 | import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag 9 | import com.jetbrains.php.lang.psi.PhpPsiElementFactory 10 | 11 | 12 | class AddNullTypeQF(private val pointer: SmartPsiElementPointer) : LocalQuickFix { 13 | override fun getFamilyName() = "Add null type" 14 | 15 | override fun applyFix(project: Project, descriptor: ProblemDescriptor) { 16 | val element = pointer.element 17 | if (element is PhpDocTag) { 18 | val newText = element.text.replace(Regex("(@var[ ]+)([^ ]+)( |$)"), "$1$2|null$3") 19 | val elementToInsert = PhpPsiElementFactory.createFromText(project, PhpDocTag::class.java, "/** $newText */\\n") 20 | if (elementToInsert !== null) { 21 | element.replace(elementToInsert) 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/propertyAnnotation/PropertyAnnotationInspection.html: -------------------------------------------------------------------------------- 1 | Properties that are not initialized in the constructor should be annotated as nullable. 2 |
 3 | class User {
 4 |  /** @var string */ // <-- Property is not annotated correctly. Add null type
 5 |  private $name;
 6 |  public function getName() {  }
 7 |  public function setName(string $name) {  }
 8 | }
 9 | 
10 | 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/propertyAnnotation/PropertyAnnotationInspection.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.propertyAnnotation 2 | 3 | import com.funivan.idea.phpClean.spl.PhpCleanInspection 4 | import com.funivan.idea.phpClean.spl.Pointer 5 | import com.intellij.codeInspection.ProblemsHolder 6 | import com.intellij.psi.PsiElementVisitor 7 | import com.intellij.psi.util.PsiTreeUtil 8 | import com.jetbrains.php.lang.psi.elements.AssignmentExpression 9 | import com.jetbrains.php.lang.psi.elements.FieldReference 10 | import com.jetbrains.php.lang.psi.elements.PhpClass 11 | import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor 12 | 13 | 14 | class PropertyAnnotationInspection : PhpCleanInspection() { 15 | override fun getShortName() = "PropertyAnnotationInspection" 16 | override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { 17 | return object : PhpElementVisitor() { 18 | override fun visitPhpClass(clazz: PhpClass) { 19 | val clazzConstructor = clazz.constructor 20 | val propertiesInitedInConstructor = mutableListOf() 21 | if (clazzConstructor != null) { 22 | PsiTreeUtil.processElements(clazzConstructor) { 23 | if (it is AssignmentExpression) { 24 | val variable = it.variable 25 | if (variable is FieldReference) { 26 | propertiesInitedInConstructor.add(variable.name) 27 | } 28 | } 29 | return@processElements true 30 | } 31 | } 32 | var properties = clazz.ownFields.filter { 33 | it.isPhysical && !propertiesInitedInConstructor.contains(it.name) 34 | } 35 | if (!clazz.isFinal || clazz.extendsList.referenceElements.isNotEmpty()) { 36 | properties = properties.filter { it.modifier.isPrivate } 37 | } 38 | for (property in properties) { 39 | val nameNode = property.nameNode 40 | if (nameNode != null && property.children.isEmpty()) { 41 | val comment = property.docComment 42 | if (comment != null) { 43 | val varTag = comment.varTag 44 | if (varTag !== null) { 45 | val type = varTag.type 46 | val qf = AddNullTypeQF( 47 | Pointer(varTag).create() 48 | ) 49 | if (!type.types.contains("\\null")) { 50 | holder.registerProblem( 51 | nameNode.psi, 52 | "Property is not annotated correctly. Add null type", 53 | qf 54 | ) 55 | } 56 | } 57 | } 58 | } 59 | } 60 | super.visitPhpClass(clazz) 61 | } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/propertyCanBePrivate/PropertyCanBePrivateInspection.html: -------------------------------------------------------------------------------- 1 | Protected properties can be converted to private. 2 |
3 | class User {
4 |   protected $user; // <-- Property can be private
5 | }
6 | 
7 | 8 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/propertyCanBePrivate/PropertyCanBePrivateInspection.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.propertyCanBePrivate 2 | 3 | import com.funivan.idea.phpClean.constrains.clazz.IsAloneClass 4 | import com.funivan.idea.phpClean.qf.makeClassMemberPrivate.MakeClassMemberPrivateQF 5 | import com.funivan.idea.phpClean.spl.PhpCleanInspection 6 | import com.intellij.codeInspection.ProblemsHolder 7 | import com.intellij.psi.PsiElementVisitor 8 | import com.jetbrains.php.lang.psi.elements.Field 9 | import com.jetbrains.php.lang.psi.elements.PhpClass 10 | import com.jetbrains.php.lang.psi.elements.PhpModifierList 11 | import com.jetbrains.php.lang.psi.elements.impl.PhpPromotedFieldParameterImpl 12 | import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor 13 | 14 | class PropertyCanBePrivateInspection : PhpCleanInspection() { 15 | val constraint = IsAloneClass() 16 | override fun getShortName() = "PropertyCanBePrivateInspection" 17 | override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { 18 | return object : PhpElementVisitor() { 19 | override fun visitPhpClass(target: PhpClass) { 20 | if (constraint.match(target)) { 21 | for (property in target.ownFields.filter { it.modifier.isProtected }) { 22 | holder.registerProblem( 23 | property.nameIdentifier ?: property, 24 | "Property can be private", 25 | qf(property) 26 | ) 27 | } 28 | } 29 | } 30 | 31 | private fun qf(property: Field?): MakeClassMemberPrivateQF? { 32 | val qf = when (property is PhpPromotedFieldParameterImpl) { 33 | true -> MakeClassMemberPrivateQF.create(property, "Make property private") 34 | false -> MakeClassMemberPrivateQF.create( 35 | property?.parent?.firstChild as? PhpModifierList, 36 | "Make property private" 37 | ) 38 | } 39 | return qf 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/redundantDocCommentTag/RedundantDocCommentTagInspection.html: -------------------------------------------------------------------------------- 1 | Types that are specified in the php can be omitted in the PhpDoc blocks
2 |
3 | /**
4 |  * @return void // <-- Redundant PhpDoc tag
5 |  */
6 |  function show(string $message): void {}
7 | 
8 | 9 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/redundantDocCommentTag/RedundantDocCommentTagInspection.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.redundantDocCommentTag 2 | 3 | import com.funivan.idea.phpClean.inspections.redundantDocCommentTag.tags.ParameterInfo 4 | import com.funivan.idea.phpClean.inspections.redundantDocCommentTag.tags.ParameterType 5 | import com.funivan.idea.phpClean.inspections.redundantDocCommentTag.tags.ReturnType 6 | import com.funivan.idea.phpClean.spl.PhpCleanInspection 7 | import com.funivan.idea.phpClean.spl.Pointer 8 | import com.funivan.idea.phpClean.spl.jb.qf.RemoveTagQF 9 | import com.intellij.codeInspection.ProblemsHolder 10 | import com.intellij.psi.PsiElementVisitor 11 | import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocType 12 | import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag 13 | import com.jetbrains.php.lang.psi.elements.Field 14 | import com.jetbrains.php.lang.psi.elements.Function 15 | import com.jetbrains.php.lang.psi.elements.Method 16 | import com.jetbrains.php.lang.psi.resolve.types.PhpType 17 | import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor 18 | 19 | 20 | class RedundantDocCommentTagInspection : PhpCleanInspection() { 21 | override fun getShortName() = "RedundantDocCommentTagInspection" 22 | override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { 23 | return object : PhpElementVisitor() { 24 | override fun visitPhpMethod(method: Method) { 25 | visitPhpFunction(method) 26 | } 27 | 28 | override fun visitPhpFunction(function: Function) { 29 | val comment = function.docComment 30 | val items = mutableListOf() 31 | if (comment != null) { 32 | items.add(ReturnType(comment.returnTag, function)) 33 | for (paramTag in comment.paramTags) { 34 | items.add(ParameterInfo(paramTag, function)) 35 | } 36 | for (item in items) { 37 | checkComment(item.doc(), item.type()) 38 | } 39 | } 40 | } 41 | 42 | override fun visitPhpField(field: Field) { 43 | val comment = field.docComment 44 | if (comment != null) { 45 | for (paramTag in comment.paramTags) { 46 | checkComment(paramTag, field.declaredType) 47 | } 48 | } 49 | } 50 | 51 | fun checkComment(tag: PhpDocTag?, type: PhpType?) { 52 | if (tag != null && type != null && tag.tagValue == "") { 53 | val doc = tag.type.toStringResolved() 54 | val declared = type.toStringResolved() 55 | if (doc == declared) { 56 | val skip = (tag.firstPsiChild as? PhpDocType)?.canonicalText?.contains(Regex("[<\\]\\{]+")) 57 | if (skip == false) { 58 | holder.registerProblem( 59 | tag, 60 | "Redundant PhpDoc tag", 61 | RemoveTagQF(Pointer(tag).create()) 62 | ) 63 | } 64 | } 65 | } 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/redundantDocCommentTag/tags/ParameterInfo.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.redundantDocCommentTag.tags 2 | 3 | import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocParamTag 4 | import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag 5 | import com.jetbrains.php.lang.psi.elements.Function 6 | import com.jetbrains.php.lang.psi.resolve.types.PhpType 7 | 8 | class ParameterInfo(private val paramTag: PhpDocParamTag, private val function: Function) : ParameterType { 9 | override fun doc(): PhpDocTag? { 10 | return paramTag 11 | } 12 | 13 | override fun type(): PhpType? { 14 | return function.parameters 15 | .filter { it.name == paramTag.varName } 16 | .map { it.declaredType } 17 | .firstOrNull() 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/redundantDocCommentTag/tags/ParameterType.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.redundantDocCommentTag.tags 2 | 3 | import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag 4 | import com.jetbrains.php.lang.psi.resolve.types.PhpType 5 | 6 | interface ParameterType { 7 | fun doc(): PhpDocTag? 8 | fun type(): PhpType? 9 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/redundantDocCommentTag/tags/ReturnType.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.redundantDocCommentTag.tags 2 | 3 | import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocReturnTag 4 | import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag 5 | import com.jetbrains.php.lang.psi.elements.Function 6 | import com.jetbrains.php.lang.psi.resolve.types.PhpType 7 | 8 | class ReturnType(private val returnTag: PhpDocReturnTag?, private val function: Function) : ParameterType { 9 | override fun doc(): PhpDocTag? { 10 | return returnTag 11 | } 12 | 13 | override fun type(): PhpType? { 14 | return function.declaredType 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/toStringCall/AddToStringCallQF.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.toStringCall 2 | 3 | import com.intellij.codeInspection.LocalQuickFix 4 | import com.intellij.codeInspection.ProblemDescriptor 5 | import com.intellij.openapi.project.Project 6 | import com.intellij.psi.SmartPsiElementPointer 7 | import com.jetbrains.php.lang.psi.PhpPsiElementFactory 8 | import com.jetbrains.php.lang.psi.elements.NewExpression 9 | import com.jetbrains.php.lang.psi.elements.PhpPsiElement 10 | 11 | 12 | class AddToStringCallQF(private val pointer: SmartPsiElementPointer) : LocalQuickFix { 13 | override fun getFamilyName() = "Add __toString call" 14 | override fun applyFix(project: Project, descriptor: ProblemDescriptor) { 15 | val element = pointer.element 16 | val braces = element is NewExpression 17 | if (element is PhpPsiElement) { 18 | var text = element.text 19 | if (braces) { 20 | text = "($text)" 21 | } 22 | val fn = PhpPsiElementFactory.createMethodReference(project, "$text->__toString()") 23 | element.replace(fn) 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/toStringCall/IsSingleClassType.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.toStringCall 2 | 3 | import com.funivan.idea.phpClean.constrains.ConstrainInterface 4 | import com.jetbrains.php.PhpIndex 5 | import com.jetbrains.php.lang.psi.elements.PhpTypedElement 6 | 7 | 8 | class IsSingleClassType : ConstrainInterface { 9 | override fun match(target: PhpTypedElement): Boolean { 10 | var result = false 11 | val type = target.type 12 | if (type.types.size == 1 && type.isComplete) { 13 | result = PhpIndex.getInstance(target.project).getClassesByFQN(type.toStringResolved()).isNotEmpty() 14 | } 15 | return result 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/toStringCall/IsToStringContext.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.toStringCall 2 | 3 | import com.funivan.idea.phpClean.constrains.ConstrainInterface 4 | import com.intellij.psi.PsiElement 5 | import com.jetbrains.php.lang.lexer.PhpTokenTypes 6 | import com.jetbrains.php.lang.psi.elements.* 7 | 8 | 9 | class IsToStringContext : ConstrainInterface { 10 | override fun match(target: PsiElement): Boolean { 11 | return (target is PhpEchoStatement) 12 | || (target is PhpPrintExpression) 13 | || (target is ConcatenationExpression) 14 | || (target is UnaryExpression && target.operation?.node?.elementType == PhpTokenTypes.opSTRING_CAST) 15 | || ( 16 | target is BinaryExpression 17 | && target.operation?.node?.elementType == PhpTokenTypes.opEQUAL 18 | && (target.leftOperand is StringLiteralExpression || target.rightOperand is StringLiteralExpression) 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/toStringCall/ToStringCallInspection.html: -------------------------------------------------------------------------------- 1 | Detect automatic type casting 2 |
 3 | class Hello {
 4 |     public function randomize(): self { /* ... */return $this; }
 5 |     public function __toString() { return 'Hi'; }
 6 | }
 7 | echo (new Hello())->randomize(); // <-- Deprecated __toString call
 8 | 
9 | 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/toStringCall/ToStringCallInspection.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.toStringCall 2 | 3 | import com.funivan.idea.phpClean.spl.PhpCleanInspection 4 | import com.funivan.idea.phpClean.spl.Pointer 5 | import com.intellij.codeInspection.ProblemsHolder 6 | import com.intellij.psi.PsiElementVisitor 7 | import com.jetbrains.php.lang.psi.elements.* 8 | import com.jetbrains.php.lang.psi.elements.Function 9 | import com.jetbrains.php.lang.psi.resolve.types.PhpType 10 | import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor 11 | 12 | 13 | class ToStringCallInspection : PhpCleanInspection() { 14 | val context = IsToStringContext() 15 | val safeCastTypes = lazy { 16 | PhpType.builder() 17 | .add(PhpType.NULL) 18 | .add(PhpType.STRING) 19 | .add(PhpType.FALSE).add(PhpType.BOOLEAN) 20 | .add(PhpType.INT).add(PhpType.FLOAT).add(PhpType.NUMBER).build() 21 | } 22 | 23 | override fun getShortName() = "ToStringCallInspection" 24 | override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { 25 | return object : PhpElementVisitor() { 26 | override fun visitPhpVariable(variable: Variable) { 27 | if (context.match(variable.parent)) { 28 | if (IsSingleClassType().match(variable)) { 29 | holder.registerProblem( 30 | variable, 31 | "Deprecated __toString call", 32 | AddToStringCallQF( 33 | Pointer(variable as PhpPsiElement).create() 34 | ) 35 | ) 36 | } 37 | } 38 | } 39 | 40 | override fun visitPhpNewExpression(expression: NewExpression) { 41 | val parent = expression.parent 42 | if (context.match(parent)) { 43 | holder.registerProblem( 44 | expression, 45 | "Deprecated __toString call", 46 | AddToStringCallQF( 47 | Pointer(expression as PhpPsiElement).create() 48 | ) 49 | ) 50 | } 51 | } 52 | 53 | override fun visitPhpMethodReference(reference: MethodReference) { 54 | if (context.match(reference.parent)) { 55 | val resolve = reference.resolve() 56 | if (resolve is Function && resolve.name != "__toString") { 57 | val declaredType = resolve.declaredType 58 | val types = declaredType.filter(safeCastTypes.value) 59 | if (!types.isEmpty) { 60 | holder.registerProblem( 61 | reference, 62 | "Deprecated __toString call", 63 | AddToStringCallQF( 64 | Pointer(reference as PhpPsiElement).create() 65 | ) 66 | ) 67 | } 68 | } 69 | } 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/virtualTypeCheck/UseAssertQF.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.virtualTypeCheck 2 | 3 | import com.intellij.codeInspection.LocalQuickFix 4 | import com.intellij.codeInspection.ProblemDescriptor 5 | import com.intellij.openapi.project.Project 6 | import com.intellij.psi.PsiElement 7 | import com.intellij.psi.SmartPsiElementPointer 8 | import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocType 9 | import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocVariable 10 | import com.jetbrains.php.lang.psi.PhpPsiElementFactory 11 | import com.jetbrains.php.lang.psi.elements.Statement 12 | 13 | 14 | class UseAssertQF( 15 | private val pointer: SmartPsiElementPointer, 16 | private val variable: SmartPsiElementPointer, 17 | private val type: SmartPsiElementPointer 18 | ) : LocalQuickFix { 19 | override fun getFamilyName() = "Use assert" 20 | override fun applyFix(project: Project, descriptor: ProblemDescriptor) { 21 | val tag = pointer.element 22 | val variableEl = variable.element 23 | val typeEl = type.element 24 | if (tag is PsiElement && variableEl is PhpDocVariable && typeEl is PhpDocType) { 25 | val new = PhpPsiElementFactory.createFromText( 26 | project, 27 | Statement::class.java, 28 | "assert(${variableEl.text} instanceof ${typeEl.text});" 29 | ) 30 | new?.let { 31 | tag.replace(it) 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/virtualTypeCheck/VirtualTypeCheckInspection.html: -------------------------------------------------------------------------------- 1 | Use assert to check variable type instead of doc comment. 2 |
3 | /** @var User $user */ // <-- Use assert to check variable type
4 | assert($user instanceof User);
5 | 
6 | 7 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/inspections/virtualTypeCheck/VirtualTypeCheckInspection.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.virtualTypeCheck 2 | 3 | import com.funivan.idea.phpClean.spl.PhpCleanInspection 4 | import com.funivan.idea.phpClean.spl.Pointer 5 | import com.intellij.codeInspection.ProblemsHolder 6 | import com.intellij.psi.PsiElementVisitor 7 | import com.jetbrains.php.PhpIndex 8 | import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment 9 | import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocType 10 | import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocVariable 11 | import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag 12 | import com.jetbrains.php.lang.psi.elements.AssignmentExpression 13 | import com.jetbrains.php.lang.psi.resolve.types.PhpType 14 | import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor 15 | 16 | 17 | class VirtualTypeCheckInspection : PhpCleanInspection() { 18 | override fun getShortName() = "VirtualTypeCheckInspection" 19 | override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { 20 | return object : PhpElementVisitor() { 21 | override fun visitPhpDocTag(tag: PhpDocTag) { 22 | if (tag.children.size == 3 && tag.name == "@var") { 23 | val variable = tag.children.firstOrNull { it is PhpDocVariable } 24 | val variableType = tag.children.firstOrNull { it is PhpDocType } 25 | if (variable is PhpDocVariable && variableType is PhpDocType) { 26 | val type = variableType.type 27 | if (!type.isAmbiguous && type.types.size == 1 && !variableType.text.contains("<")) { 28 | val plain = type.toStringResolved() 29 | if (!PhpType.isPluralType(plain) && !PhpType.isNotExtendablePrimitiveType(plain)) { 30 | val index = PhpIndex.getInstance(variableType.project) 31 | if (index.getClassesByFQN(plain).isNotEmpty() || index.getInterfacesByFQN(plain) 32 | .isNotEmpty() 33 | ) { 34 | val nextPsiSibling = (tag.parent as? PhpDocComment)?.nextPsiSibling?.firstChild 35 | if (nextPsiSibling is AssignmentExpression && nextPsiSibling.variable?.name == variable.name) { 36 | holder.registerProblem( 37 | variableType, 38 | "Use assert to check variable type" 39 | ) 40 | } else { 41 | holder.registerProblem( 42 | variableType, 43 | "Use assert to check variable type", 44 | UseAssertQF( 45 | Pointer(tag.parent).create(), 46 | Pointer(variable).create(), 47 | Pointer(variableType).create() 48 | ) 49 | ) 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/qf/makeClassMemberPrivate/MakeClassMemberPrivateQF.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.qf.makeClassMemberPrivate 2 | 3 | import com.funivan.idea.phpClean.spl.Pointer 4 | import com.intellij.codeInspection.LocalQuickFix 5 | import com.intellij.codeInspection.ProblemDescriptor 6 | import com.intellij.openapi.project.Project 7 | import com.intellij.psi.PsiElement 8 | import com.intellij.psi.SmartPsiElementPointer 9 | import com.jetbrains.php.lang.lexer.PhpTokenTypes 10 | import com.jetbrains.php.lang.psi.PhpPsiElementFactory 11 | import com.jetbrains.php.lang.psi.elements.PhpModifierList 12 | import com.jetbrains.php.lang.psi.elements.impl.PhpPromotedFieldParameterImpl 13 | 14 | class MakeClassMemberPrivateQF( 15 | private val pointer: SmartPsiElementPointer, 16 | private val familyName: String 17 | ) : LocalQuickFix { 18 | override fun getFamilyName() = familyName 19 | override fun applyFix(project: Project, descriptor: ProblemDescriptor) { 20 | val element = pointer.element 21 | if (element is PsiElement) { 22 | val elementToInsert = PhpPsiElementFactory.createFromText(project, PhpTokenTypes.kwPRIVATE, "private") 23 | if (elementToInsert !== null) { 24 | element.replace(elementToInsert) 25 | } 26 | } 27 | } 28 | 29 | companion object { 30 | fun create(element: PhpPromotedFieldParameterImpl, name: String) = element.node 31 | .firstChildNode 32 | ?.psi 33 | ?.let { 34 | MakeClassMemberPrivateQF(Pointer(it).create(), name) 35 | } 36 | 37 | fun create(modifier: PhpModifierList?, name: String) = modifier 38 | ?.node 39 | ?.findChildByType(PhpTokenTypes.kwPROTECTED) 40 | ?.psi 41 | ?.let { 42 | MakeClassMemberPrivateQF(Pointer(it).create(), name) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/spl/ParameterDescription.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.spl 2 | 3 | import com.jetbrains.php.lang.psi.elements.Method 4 | 5 | 6 | class ParameterDescription(private val method: Method) { 7 | private val description = lazy { description() } 8 | 9 | fun get(name: String): String { 10 | return description.value.get(name) ?: "" 11 | } 12 | 13 | private fun description(): HashMap { 14 | val comment = method.docComment 15 | val result = hashMapOf() 16 | if (comment != null) { 17 | for (tag in comment.paramTags) { 18 | val name = tag.varName 19 | val description = tag.tagValue 20 | if (name != null) { 21 | result[name] = description 22 | } 23 | } 24 | } 25 | return result 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/spl/PhpCleanInspection.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.spl 2 | 3 | import com.jetbrains.php.lang.inspections.PhpInspection 4 | 5 | abstract class PhpCleanInspection : PhpInspection() -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/spl/Pointer.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.spl 2 | 3 | import com.intellij.psi.PsiElement 4 | import com.intellij.psi.SmartPointerManager 5 | import com.intellij.psi.SmartPsiElementPointer 6 | 7 | class Pointer(private val keyword: T) { 8 | fun create(): SmartPsiElementPointer { 9 | return SmartPointerManager.getInstance(keyword.project).createSmartPsiElementPointer(keyword) 10 | } 11 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/spl/jb/qf/RemoveTagQF.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.spl.jb.qf 2 | 3 | import com.intellij.codeInspection.LocalQuickFix 4 | import com.intellij.codeInspection.ProblemDescriptor 5 | import com.intellij.openapi.project.Project 6 | import com.intellij.psi.PsiWhiteSpace 7 | import com.intellij.psi.SmartPsiElementPointer 8 | import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment 9 | import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag 10 | import com.jetbrains.php.lang.lexer.PhpTokenTypes 11 | 12 | 13 | class RemoveTagQF(private val pointer: SmartPsiElementPointer) : LocalQuickFix { 14 | override fun getFamilyName() = "Remove tag" 15 | override fun applyFix(project: Project, descriptor: ProblemDescriptor) { 16 | val element = pointer.element 17 | if (element is PhpDocTag) { 18 | val space = element.prevSibling 19 | val parent = element.parent 20 | var asterisk = space 21 | if (space is PsiWhiteSpace) { 22 | asterisk = space.prevSibling 23 | } 24 | if (asterisk?.node?.elementType == PhpTokenTypes.DOC_LEADING_ASTERISK) { 25 | asterisk.delete() 26 | } 27 | element.delete() 28 | if(parent is PhpDocComment && parent.text.matches(Regex("[*\\s\\\\/]+"))){ 29 | parent.delete() 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/kotlin/com/funivan/idea/phpClean/visitors/MethodVisitor.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.visitors 2 | 3 | import com.intellij.codeInspection.ProblemsHolder 4 | import com.jetbrains.php.lang.psi.elements.Method 5 | import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor 6 | 7 | class MethodVisitor( 8 | private val valid: (Method) -> Boolean, 9 | private val message: String, 10 | private val holder: ProblemsHolder 11 | ) : PhpElementVisitor() { 12 | override fun visitPhpMethod(method: Method) { 13 | super.visitPhpMethod(method) 14 | val nameNode = method.nameNode 15 | if (nameNode != null && valid(method)) { 16 | holder.registerProblem(nameNode.psi, message) 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.funivan.idea.phpClean 4 | PhpClean 5 | 6 | Ivan Shcherbak 7 | 8 | 9 | 12 | com.jetbrains.php 13 | com.intellij.modules.platform 14 | 15 | 16 | 17 | 27 | 37 | 47 | 57 | 67 | 77 | 87 | 97 | 107 | 117 | 127 | 137 | 147 | 157 | 167 | 177 | 178 | 179 | 180 | 185 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/resources/inspectionDescriptions/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funivan/PhpClean/945e8e5b401999f2dc5d115b5d83ef4460a467d2/src/main/resources/inspectionDescriptions/.gitkeep -------------------------------------------------------------------------------- /src/test/kotlin/com/funivan/idea/phpClean/BaseInspectionTest.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean 2 | 3 | import com.intellij.psi.PsiDocumentManager 4 | import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase 5 | import com.jetbrains.php.lang.PhpFileType 6 | import com.jetbrains.php.lang.inspections.PhpInspection 7 | 8 | 9 | abstract class BaseInspectionTest : LightJavaCodeInsightFixtureTestCase() { 10 | fun assert(inspection: PhpInspection, code: String, fixed: String? = null) { 11 | myFixture.configureByText(PhpFileType.INSTANCE, code) 12 | PsiDocumentManager.getInstance(project).commitAllDocuments() 13 | myFixture.enableInspections(inspection) 14 | myFixture.testHighlighting(true, false, true) 15 | if (fixed != null) { 16 | myFixture.getAllQuickFixes().forEach { myFixture.launchAction(it) } 17 | myFixture.checkResult(fixed, false) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/kotlin/com/funivan/idea/phpClean/inspections/assignMisused/AssignMisusedInspectionTest.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.assignMisused 2 | 3 | import com.funivan.idea.phpClean.BaseInspectionTest 4 | import kotlin.test.Test 5 | 6 | class AssignMisusedInspectionTest : BaseInspectionTest() { 7 | 8 | @Test 9 | fun testValidCase() { 10 | assert( 11 | AssignMisusedInspection(), 12 | """false !== ${'$'}a = ${'$'}b); 27 | do{} while (false !== ${'$'}a = ${'$'}b); 28 | do{} while (false == ${'$'}a = ${'$'}b); 29 | do{} while (false != ${'$'}a = ${'$'}b); 30 | if(0 > ${'$'}a = ${'$'}b){} 31 | if(0 >= ${'$'}a = ${'$'}b){} 32 | if(0 < ${'$'}a = ${'$'}b){} 33 | if(0 <= ${'$'}a = ${'$'}b){} 34 | """ 35 | ) 36 | } 37 | 38 | @Test 39 | fun testMultiplyOperator() { 40 | assert( 41 | AssignMisusedInspection(), 42 | """User{}; 15 | } 16 | namespace Cli { 17 | class User{}; 18 | } 19 | """ 20 | ) 21 | } 22 | 23 | @Test 24 | fun testCollisionInGlobalNamespace() { 25 | assert( 26 | ClassNameCollisionInspection(), 27 | """User{}; 30 | } 31 | namespace App { 32 | class User{}; 33 | } 34 | """ 35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/kotlin/com/funivan/idea/phpClean/inspections/deprecatedDocTag/DeprecatedDocTagInspectionTest.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.deprecatedDocTag 2 | 3 | import com.funivan.idea.phpClean.BaseInspectionTest 4 | import kotlin.test.Test 5 | 6 | class DeprecatedDocTagInspectionTest : BaseInspectionTest() { 7 | 8 | @Test 9 | fun testDeprecatedTag() { 10 | val inspection = DeprecatedDocTagInspection() 11 | inspection.tags.add("property") 12 | assert( 13 | inspection, 14 | """@property ${'$'}name 18 | */ 19 | class User{} 20 | """, 21 | """@property-read ${'$'}name 39 | * @method user() 40 | */ 41 | """, 42 | """${'$'}_GET['name']; 14 | """ 15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/kotlin/com/funivan/idea/phpClean/inspections/methodCanBePrivate/MethodCanBePrivateInspectionTest.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.methodCanBePrivate 2 | 3 | import com.funivan.idea.phpClean.BaseInspectionTest 4 | import kotlin.test.Test 5 | 6 | class MethodCanBePrivateInspectionTest : BaseInspectionTest() { 7 | 8 | @Test 9 | fun testFindMethodsThatCanBePrivate() { 10 | assert( 11 | MethodCanBePrivateInspection(), 12 | """name() {} 15 | } 16 | """, 17 | """name() : string { 15 | return ""; 16 | } 17 | } 18 | """ 19 | ) 20 | } 21 | @Test 22 | fun testIgnoreAnonumousClass() { 23 | assert( 24 | MethodShouldBeFinalInspection(), 25 | """test() : int{ 44 | return 1; 45 | } 46 | public function __construct( 47 | public string ${'$'}name, 48 | public string ${'$'}email, 49 | public DateTimeImmutable ${'$'}birth_date, 50 | ) {} 51 | } 52 | """ 53 | ) 54 | } 55 | 56 | @Test 57 | fun testSkipFinalClass() { 58 | assert( 59 | MethodShouldBeFinalInspection(), 60 | """name() : string {} 15 | public function id() : string {} 16 | private function login() : string {} 17 | } 18 | """ 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/kotlin/com/funivan/idea/phpClean/inspections/missingParameterType/MissingParameterTypeDeclarationInspectionTest.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.missingParameterType 2 | 3 | import com.funivan.idea.phpClean.BaseInspectionTest 4 | import kotlin.test.Test 5 | 6 | class MissingParameterTypeDeclarationInspectionTest : BaseInspectionTest() { 7 | 8 | @Test 9 | fun testMissingParameterType() { 10 | assert( 11 | MissingParameterTypeDeclarationInspection(), 12 | """${'$'}name){} 15 | } 16 | """ 17 | ) 18 | } 19 | 20 | @Test 21 | fun testMethodWithParameterTypes() { 22 | assert( 23 | MissingParameterTypeDeclarationInspection(), 24 | """,) 54 | } 55 | """ 56 | ) 57 | } 58 | 59 | @Test 60 | fun testSkipOverwrittenMethods() { 61 | assert( 62 | MissingParameterTypeDeclarationInspection(), 63 | """${'$'}salt); 66 | } 67 | class Id implements Uid{ 68 | function with(${'$'}id){} 69 | } 70 | class TrimmedId extends Id{ 71 | function with(${'$'}id){} 72 | function hash(${'$'}salt){} 73 | function rebuild(${'$'}uid){} 74 | } 75 | """ 76 | ) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/kotlin/com/funivan/idea/phpClean/inspections/missingReturnType/MissingReturnTypeInspectionTest.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.missingReturnType 2 | 3 | import com.funivan.idea.phpClean.BaseInspectionTest 4 | import kotlin.test.Test 5 | 6 | class MissingReturnTypeInspectionTest : BaseInspectionTest() { 7 | 8 | @Test 9 | fun testMethodWithoutReturnType() { 10 | assert( 11 | MissingReturnTypeInspection(), 12 | """hide(){} 16 | } 17 | """ 18 | ) 19 | } 20 | 21 | @Test 22 | fun testWithoutReturnType() { 23 | assert( 24 | MissingReturnTypeInspection(), 25 | """${'$'}name; 19 | } 20 | """ 21 | ) 22 | } 23 | 24 | @Test 25 | fun testAllPropertiesAreDeprecated() { 26 | assert( 27 | ParentPropertyDeprecatedInspection(), 28 | """ 29 | Admin extends User{} 17 | """ 18 | ) 19 | } 20 | 21 | @Test 22 | fun testIgnoreCases() { 23 | assert( 24 | ProhibitedClassExtendInspection(), 25 | """${'$'}first; 16 | } 17 | """ 18 | ) 19 | } 20 | 21 | @Test 22 | fun testPropertiesWithoutDocumentation() { 23 | assert( 24 | PropertyAnnotationInspection(), 25 | """ 26 | ${'$'}second; 69 | 70 | private function getFirst() : array { 71 | 72 | } 73 | } 74 | """ 75 | ) 76 | } 77 | 78 | @Test 79 | fun testPropertiesInFinalClass() { 80 | assert( 81 | PropertyAnnotationInspection(), 82 | """ 83 | ${'$'}names; 87 | /** @var int */ 88 | protected ${'$'}id; 89 | } 90 | """ 91 | ) 92 | } 93 | 94 | @Test 95 | fun testPropertiesInFinalClassWithExtends() { 96 | assert( 97 | PropertyAnnotationInspection(), 98 | """ 99 | ${'$'}age; 105 | } 106 | """ 107 | ) 108 | } 109 | 110 | @Test 111 | fun testPrivatePropertiesInClassesWithoutConstructor() { 112 | assert( 113 | PropertyAnnotationInspection(), 114 | """ 115 | ${'$'}first; 119 | } 120 | """ 121 | ) 122 | } 123 | 124 | @Test 125 | fun testPrivatePropertyNotInitializedInTheConstructor() { 126 | assert( 127 | PropertyAnnotationInspection(), 128 | """ 129 | ${'$'}first; 135 | } 136 | """ 137 | ) 138 | } 139 | 140 | @Test 141 | fun testWithParentConstructor() { 142 | assert( 143 | PropertyAnnotationInspection(), 144 | """ 145 | ${'$'}p; 155 | /** @var string */ 156 | protected ${'$'}name; 157 | } 158 | """ 159 | ) 160 | } 161 | 162 | @Test 163 | fun testWithoutInitInConstructor() { 164 | assert( 165 | PropertyAnnotationInspection(), 166 | """ 167 | ${'$'}p; 173 | 174 | public function __construct() { 175 | } 176 | } 177 | class B { 178 | private ?string ${'$'}id; 179 | } 180 | """ 181 | ) 182 | } 183 | 184 | @Test 185 | fun testWithInitInConstructor() { 186 | assert( 187 | PropertyAnnotationInspection(), 188 | """ 189 | p = ""; 198 | } 199 | } 200 | """ 201 | ) 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /src/test/kotlin/com/funivan/idea/phpClean/inspections/propertyCanBePrivate/PropertyCanBePrivateInspectionTest.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.propertyCanBePrivate 2 | 3 | import com.funivan.idea.phpClean.BaseInspectionTest 4 | import kotlin.test.Test 5 | 6 | class PropertyCanBePrivateInspectionTest : BaseInspectionTest() { 7 | 8 | @Test 9 | fun testPhp81() { 10 | assert( 11 | PropertyCanBePrivateInspection(), 12 | """${'$'}name 16 | ) {} 17 | } 18 | """, 19 | """${'$'}user; 36 | } 37 | """, 38 | """${'$'}user; 91 | } 92 | """, 93 | """@return void 14 | */ 15 | function show(string ${'$'}message):void {} 16 | """, """ 17 | @return void 31 | */ 32 | function show(string ${'$'}message):void {} 33 | """, """ 34 | @param string ${'$'}message 50 | * @param string ${'$'}test 51 | */ 52 | function show(int ${'$'}a, string ${'$'}message):void {} 53 | """, """ 54 | @param \stdClass ${'$'}c 71 | * @param string ${'$'}a 72 | */ 73 | function isObject(${'$'}a, ${'$'}b, stdClass ${'$'}c):bool {} 74 | } 75 | """ 76 | ) 77 | } 78 | 79 | @Test 80 | fun testReturnMultipleTypes() { 81 | assert( 82 | RedundantDocCommentTagInspection(), """ 83 | @return bool|null 112 | */ 113 | function show():?bool {} 114 | """ 115 | ) 116 | } 117 | 118 | @Test 119 | fun testCheckNullableParameterType() { 120 | assert( 121 | RedundantDocCommentTagInspection(), """ 122 | @param stdClass|null ${'$'}o 125 | */ 126 | function show(?stdClass ${'$'}o) {} 127 | """ 128 | ) 129 | } 130 | 131 | @Test 132 | fun testRedundantFieldTag() { 133 | assert( 134 | RedundantDocCommentTagInspection(), """ 135 | @var bool 139 | */ 140 | private bool ${'$'}isClean = false; 141 | } 142 | """ 143 | ) 144 | } 145 | 146 | @Test 147 | fun testFieldTagWithClassFQN() { 148 | assert( 149 | RedundantDocCommentTagInspection(), """ 150 | @var \stdClass 154 | */ 155 | private \stdClass ${'$'}cleaner; 156 | } 157 | """ 158 | ) 159 | } 160 | 161 | @Test 162 | fun testIgnoreArrayShapeTags() { 163 | assert( 164 | RedundantDocCommentTagInspection(), """ 165 | 123, 'name'=>'test']; 173 | } 174 | } 175 | """ 176 | ) 177 | } 178 | 179 | @Test 180 | fun testFieldTagWithMultipleTypes() { 181 | assert( 182 | RedundantDocCommentTagInspection(), """ 183 | @var \stdClass|null 217 | */ 218 | private ?\stdClass ${'$'}cleaner; 219 | } 220 | """ 221 | ) 222 | } 223 | 224 | @Test 225 | fun testGenericType() { 226 | assert( 227 | RedundantDocCommentTagInspection(), """ 228 | 237 | */ 238 | public function get(array ${'$'}items):array { 239 | } 240 | } 241 | """ 242 | ) 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/test/kotlin/com/funivan/idea/phpClean/inspections/toStringCall/ToStringCallInspectionTest.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.toStringCall 2 | 3 | import com.funivan.idea.phpClean.BaseInspectionTest 4 | import kotlin.test.Test 5 | 6 | class ToStringCallInspectionTest : BaseInspectionTest() { 7 | @Test 8 | fun testMethodCall() { 9 | assert( 10 | ToStringCallInspection(), 11 | """ 12 | (new Hello())->randomize(); 18 | """, 19 | """ 20 | randomize()->__toString(); 26 | """ 27 | ) 28 | } 29 | 30 | @Test 31 | fun testConcatenation() { 32 | assert( 33 | ToStringCallInspection(), 34 | """ 35 | (new Hello())->randomize(); 41 | """, 42 | """ 43 | randomize()->__toString(); 49 | """ 50 | ) 51 | } 52 | 53 | @Test 54 | fun testManualTypeCasting() { 55 | assert( 56 | ToStringCallInspection(), 57 | """ 58 | randomize()->__toString(); 64 | 'Who say ' . (new Hello())->randomize()->__toString() . '?'; 65 | """ 66 | ) 67 | } 68 | 69 | @Test 70 | fun testNullableString() { 71 | assert( 72 | ToStringCallInspection(), 73 | """ 74 | test(); 79 | """ 80 | ) 81 | } 82 | 83 | @Test 84 | fun testNewObject() { 85 | assert( 86 | ToStringCallInspection(), 87 | """ 88 | new Hello(); 93 | ${'$'}phrase = new Hello() . ' there'; 94 | """, 95 | """ 96 | __toString(); 101 | ${'$'}phrase = (new Hello())->__toString() . ' there'; 102 | """ 103 | ) 104 | } 105 | 106 | @Test 107 | fun testMethodStringReturned() { 108 | assert( 109 | ToStringCallInspection(), 110 | """ 111 | str(); 120 | ${'$'}hi = new Hello(); 121 | echo ${'$'}hi->str(); 122 | ${'$'}message = ${'$'}hi->str() . 'msg'; 123 | echo (new Hello())->s(); 124 | """ 125 | ) 126 | } 127 | 128 | @Test 129 | fun testStaticMethod() { 130 | assert( 131 | ToStringCallInspection(), 132 | """ 133 | SomeClass::create(); 141 | """, 142 | """ 143 | __toString(); 151 | """ 152 | ) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/test/kotlin/com/funivan/idea/phpClean/inspections/toStringCall/ToStringCallInspectionTestFunction.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.toStringCall 2 | 3 | import com.funivan.idea.phpClean.BaseInspectionTest 4 | import kotlin.test.Test 5 | 6 | class ToStringCallInspectionTestFunction : BaseInspectionTest() { 7 | @Test 8 | fun testFunctionCall() { 9 | assert( 10 | ToStringCallInspection(), 11 | """ 12 | __toString(); 21 | echo getcwd() . "/test.php"; 22 | """ 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/kotlin/com/funivan/idea/phpClean/inspections/toStringCall/ToStringCallInspectionTestTypeCast.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.toStringCall 2 | 3 | import com.funivan.idea.phpClean.BaseInspectionTest 4 | import kotlin.test.Test 5 | 6 | class ToStringCallInspectionTestTypeCast : BaseInspectionTest() { 7 | @Test 8 | fun testMethodTypeCast() { 9 | assert( 10 | ToStringCallInspection(), 11 | """ 12 | (new Hello())->randomize(); 18 | (string) (new Hello())->randomize()->__toString(); 19 | """ 20 | ) 21 | } 22 | 23 | @Test 24 | fun testVariableTypeCast() { 25 | assert( 26 | ToStringCallInspection(), 27 | """ 28 | ${'$'}phrase; 18 | ${'$'}phrase->__toString(); 19 | """ 20 | ) 21 | } 22 | 23 | @Test 24 | fun testShortTagEcho() { 25 | assert( 26 | ToStringCallInspection(), 27 | """ 28 | 34 | ${'$'}phrase ?> 35 | """ 36 | ) 37 | } 38 | 39 | @Test 40 | fun testVariableComparison() { 41 | assert( 42 | ToStringCallInspection(), 43 | """ 44 | ${'$'}phrase == 'Hi'; 50 | "${'$'}a Hi" == ${'$'}phrase; 51 | ${'$'}phrase === 'test'; 52 | ${'$'}phrase == 123; 53 | """ 54 | ) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/kotlin/com/funivan/idea/phpClean/inspections/virtualTypeCheck/VirtualTypeCheckInspectionTest.kt: -------------------------------------------------------------------------------- 1 | package com.funivan.idea.phpClean.inspections.virtualTypeCheck 2 | 3 | import com.funivan.idea.phpClean.BaseInspectionTest 4 | import kotlin.test.Test 5 | 6 | class VirtualTypeCheckInspectionTest : BaseInspectionTest() { 7 | @Test 8 | fun testUseAssert() { 9 | assert( 10 | VirtualTypeCheckInspection(), 11 | """User ${'$'}user */ 14 | assert(${'$'}user instanceof User); // Valid 15 | """, 16 | """User ${'$'}user */ 32 | assert(${'$'}user instanceof User); // Valid 33 | """ 34 | ) 35 | } 36 | 37 | @Test 38 | fun testIgnoreArray() { 39 | assert( 40 | VirtualTypeCheckInspection(), 41 | """Options ${'$'}options */ 94 | class Options{}; 95 | """.trimIndent(), 96 | """ */; 111 | """ 112 | ) 113 | } 114 | 115 | @Test 116 | fun testSkipQFOnUninitializedVariable() { 117 | assert( 118 | VirtualTypeCheckInspection(), 119 | """Letter ${'$'}letter */ 122 | ${'$'}letter = new Letter(); 123 | """, 124 | """