├── plugin-content.yaml ├── testData ├── codeInsight │ ├── documentationProvider │ │ ├── VarTag.html │ │ ├── VarTag.php │ │ ├── PhpStanTags.php │ │ ├── CustomPhpStanTags.php │ │ ├── PhpStanTags.html │ │ └── CustomPhpStanTags.html │ └── typeInference │ │ ├── NestedConditionalTypes.php │ │ ├── DocTags.php │ │ ├── ConditionalTypesWithGenerics.php │ │ ├── ConditionalType.php │ │ ├── AliasImportType.php │ │ └── Templates.php ├── completion │ └── DocTagCompletions.php ├── parser │ ├── Inheritance.txt │ ├── InplaceCovariantGeneric.txt │ ├── InplaceContravariantGeneric.txt │ ├── Method.txt │ ├── Template.txt │ ├── Type.txt │ ├── DocTags.txt │ ├── DocMethodsTags.txt │ └── Assert.txt └── output │ ├── Simple.txt │ └── Simple.php ├── resources ├── META-INF │ ├── phpstan-remote-plugin.xml │ └── plugin.xml ├── inspectionDescriptions │ └── PhpStanGlobal.html └── messages │ └── PhpStanBundle.properties ├── CONTRIBUTING.md ├── src └── com │ └── jetbrains │ └── php │ ├── tools │ └── quality │ │ └── phpstan │ │ ├── PhpStanAddToIgnoredAction.java │ │ ├── PhpStanBlackList.java │ │ ├── PhpStanValidationInspection.java │ │ ├── PhpStanOpenSettingsProvider.java │ │ ├── PhpStanBundle.java │ │ ├── PhpStanQualityToolAnnotatorInfo.java │ │ ├── PhpStanConfigurationProvider.java │ │ ├── PhpStanConfigurable.java │ │ ├── PhpStanSettingsTransferStartupActivity.kt │ │ ├── PhpStanConfigurationBaseManager.java │ │ ├── PhpStanConfigurationManager.java │ │ ├── PhpStanProjectConfiguration.java │ │ ├── PhpStanConfigurableForm.java │ │ ├── PhpStanOptionsConfiguration.java │ │ ├── remote │ │ ├── PhpStanRemoteConfiguration.java │ │ └── PhpStanRemoteConfigurationProvider.java │ │ ├── PhpStanConfiguration.java │ │ ├── PhpStanQualityToolType.java │ │ ├── PhpStanAnnotatorProxy.java │ │ ├── PhpStanGlobalInspection.java │ │ ├── PhpStanOptionsPanel.java │ │ ├── PhpStanMessageProcessor.java │ │ ├── PhpStanComposerConfig.java │ │ └── PhpStanOptionsPanel.form │ └── phpstan │ └── completion │ └── PhpStanCompletionContributor.java ├── tests ├── com │ └── jetbrains │ │ └── php │ │ └── phpstan │ │ ├── completion │ │ └── PhpStanCompletionTest.java │ │ ├── quality │ │ └── tools │ │ │ ├── PhpStanOutputParsingTest.java │ │ │ ├── PhpStanComposerConfigTest.java │ │ │ ├── PhpStanVersionValidatorTest.java │ │ │ ├── PhpStanAnnotatorTest.kt │ │ │ └── PhpStanConfigFileFromComposerTest.java │ │ ├── types │ │ └── PhpStanTypeInferenceTest.java │ │ └── lang │ │ └── documentation │ │ ├── parser │ │ └── PhpStanDocParserTest.java │ │ └── PhpStanDocumentationProviderTest.java └── integration │ └── tests │ └── phpstan │ └── PhpStanNewTagsTest.kt ├── README.md ├── intellij.phpstan.iml ├── CODE_OF_CONDUCT.md ├── BUILD.bazel └── LICENSE /plugin-content.yaml: -------------------------------------------------------------------------------- 1 | - name: lib/phpstan.jar 2 | modules: 3 | - name: intellij.phpstan -------------------------------------------------------------------------------- /testData/codeInsight/documentationProvider/VarTag.html: -------------------------------------------------------------------------------- 1 | int 
2 | -------------------------------------------------------------------------------- /testData/codeInsight/documentationProvider/VarTag.php: -------------------------------------------------------------------------------- 1 | $a; 7 | } 8 | -------------------------------------------------------------------------------- /testData/completion/DocTagCompletions.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | trait Foo { 12 | } 13 | 14 | 15 | class Lorem implements Bar { 16 | use Foo; 17 | } -------------------------------------------------------------------------------- /testData/codeInsight/typeInference/NestedConditionalTypes.php: -------------------------------------------------------------------------------- 1 | a(0, 1, 2); -------------------------------------------------------------------------------- /resources/META-INF/phpstan-remote-plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | If you are introducing a user-visible change, please make sure to: 4 | - create the corresponding issue first in [our tracker](https://youtrack.jetbrains.com/newIssue?project=WI) 5 | - comment about intention to start a work on the fix 6 | - cover the change with a test with the implementation 7 | - mention issue id in pull request -------------------------------------------------------------------------------- /testData/codeInsight/documentationProvider/PhpStanTags.php: -------------------------------------------------------------------------------- 1 | 7 | * @phpstan-throws Exception 8 | * @phpstan-template T of Foo with description 9 | * @phpstan-template-covariant M of Foo with description 10 | */ 11 | function foo(int $a, $b) { 12 | 13 | } 14 | 15 | class Foo { 16 | 17 | } -------------------------------------------------------------------------------- /testData/codeInsight/typeInference/DocTags.php: -------------------------------------------------------------------------------- 1 | $param; 15 | $secondParam; 16 | $this->a; 17 | } 18 | } 19 | 20 | (new Some())->foo(1); 21 | -------------------------------------------------------------------------------- /resources/inspectionDescriptions/PhpStanGlobal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | Runs PHPStan to find code problems. 5 |

6 | The inspection requires PHPStan to be properly installed and set up in the IDE under 7 | Settings | PHP | Quality Tools | PHPStan. 8 |

To learn more about installing PHPStan, see: 9 | https://phpstan.org/user-guide/getting-started 10 |

11 | 12 | -------------------------------------------------------------------------------- /testData/codeInsight/typeInference/ConditionalTypesWithGenerics.php: -------------------------------------------------------------------------------- 1 | $id 11 | * 12 | * @return ($id is class-string ? T : mixed) 13 | */ 14 | public function get(string $id); 15 | } 16 | 17 | /** @var Container $container */ 18 | $test = $container->get(Container::class); 19 | $test_2 = $container->get('foo'); 20 | -------------------------------------------------------------------------------- /testData/codeInsight/documentationProvider/CustomPhpStanTags.php: -------------------------------------------------------------------------------- 1 | 11 | * @phpstan-assert-if-false int $this->test() 12 | * @phpstan-assert-if-true null $this->b 13 | * @template NewT 14 | * @phpstan-this-out self 15 | * @phpstan-self-out self 16 | */ 17 | function a() 18 | { 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanAddToIgnoredAction.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.jetbrains.php.tools.quality.QualityToolAddToIgnoredAction; 5 | import com.jetbrains.php.tools.quality.QualityToolType; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | final class PhpStanAddToIgnoredAction extends QualityToolAddToIgnoredAction { 9 | @Override 10 | protected @NotNull QualityToolType getQualityToolType(Project project) { 11 | return PhpStanQualityToolType.INSTANCE; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /testData/codeInsight/typeInference/ConditionalType.php: -------------------------------------------------------------------------------- 1 | fillArray(0); 9 | 10 | class is{} 11 | /** 12 | * @return is 13 | */ 14 | function f(int $size){} 15 | 16 | f(); 17 | 18 | class ConditionalThis 19 | { 20 | /** 21 | * @return (static is int ? int : string) 22 | */ 23 | public function foo(): int|string 24 | { 25 | 26 | } 27 | } 28 | 29 | (new ConditionalThis())->foo() -------------------------------------------------------------------------------- /testData/codeInsight/typeInference/AliasImportType.php: -------------------------------------------------------------------------------- 1 | (new User())->f(); 31 | (new User())->f1(); -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanBlackList.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.intellij.openapi.components.State; 4 | import com.intellij.openapi.components.Storage; 5 | import com.intellij.openapi.components.StoragePathMacros; 6 | import com.intellij.openapi.project.Project; 7 | import com.jetbrains.php.tools.quality.QualityToolBlackList; 8 | 9 | @State(name = "PhpStanBlackList", storages = @Storage(StoragePathMacros.WORKSPACE_FILE)) 10 | public class PhpStanBlackList extends QualityToolBlackList { 11 | 12 | public static PhpStanBlackList getInstance(Project project) { 13 | return project.getService(PhpStanBlackList.class); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/com/jetbrains/php/phpstan/completion/PhpStanCompletionTest.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.phpstan.completion; 2 | 3 | import com.jetbrains.php.fixtures.PhpCompletionTestCase; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public class PhpStanCompletionTest extends PhpCompletionTestCase { 7 | 8 | @Override 9 | protected @NotNull String getTestDataHome() { 10 | return "/phpstorm/phpstan/testData/"; 11 | } 12 | 13 | @Override 14 | protected String getFixtureTestDataFolder() { 15 | return "completion"; 16 | } 17 | 18 | public void testDocTagCompletions() { 19 | doTestLookupElementsContains("phpstan-require-extends", "phpstan-require-implements"); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /resources/messages/PhpStanBundle.properties: -------------------------------------------------------------------------------- 1 | phpstan.checkbox.full.project.run=Full project run (batch mode only) 2 | phpstan.label.options=Memory limit: 3 | phpstan.configuration.file=Configuration file 4 | phpstan.autoload.file=Autoload file 5 | inspection.php.stan.global.display.name=PHPStan validation 6 | action.PhpStanAddToIgnored.text=Add to Ignored PHPStan Files 7 | local=Local 8 | undefined.interpreter=Undefined interpreter 9 | label.autoload.file=Autoload file: 10 | label.configuration.file=Configuration file: 11 | label.level=Level: 12 | inspection.php.group.quality.tools=Quality tools 13 | label.system.php=System PHP 14 | config.file.doesnt.exist=Configuration file doesn't exist 15 | configurable.quality.tool.phpstan=PHPStan -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![official JetBrains project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) 2 | 3 | # PHPStan plugin for PhpStorm 4 | This plugin adds dedicated functionality for [PHPStan](https://phpstan.org/) to [PhpStorm](https://www.jetbrains.com/phpstorm/). The plugin is under active development and will be bundled into PhpStorm. 5 | 6 | # Features 7 | 8 | **PHPStan as a first-class quality tool** 9 | 10 | - On-the-fly file highlighting and ability to batch-run inspections 11 | - Ability to run via remote interpreters such as Docker, WSL, and others 12 | - Automatic detection of PHPStan settings from composer.json 13 | - Manual configuration of PHPStan settings in the corresponding inspection options -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanValidationInspection.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.intellij.openapi.util.NlsSafe; 4 | import com.jetbrains.php.tools.quality.QualityToolValidationInspection; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import static com.jetbrains.php.tools.quality.phpstan.PhpStanConfigurationBaseManager.PHP_STAN; 8 | 9 | @SuppressWarnings("InspectionDescriptionNotFoundInspection") 10 | public class PhpStanValidationInspection extends QualityToolValidationInspection { 11 | 12 | @Override 13 | protected @NotNull PhpStanAnnotatorProxy getAnnotator() { 14 | return PhpStanAnnotatorProxy.INSTANCE; 15 | } 16 | 17 | @Override 18 | public @NlsSafe String getToolName() { 19 | return PHP_STAN; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanOpenSettingsProvider.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.intellij.openapi.options.ShowSettingsUtil; 4 | import com.intellij.openapi.project.Project; 5 | import com.jetbrains.php.composer.actions.log.ComposerLogMessageBuilder; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public class PhpStanOpenSettingsProvider extends ComposerLogMessageBuilder.Settings { 9 | public static final PhpStanOpenSettingsProvider PHP_STAN_OPEN_SETTINGS_PROVIDER = new PhpStanOpenSettingsProvider(); 10 | 11 | public PhpStanOpenSettingsProvider() {super("\u200D");} 12 | 13 | @Override 14 | public void show(@NotNull Project project) { 15 | ShowSettingsUtil.getInstance().showSettingsDialog(project, PhpStanBundle.message("configurable.quality.tool.phpstan")); 16 | } 17 | } -------------------------------------------------------------------------------- /src/com/jetbrains/php/phpstan/completion/PhpStanCompletionContributor.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.phpstan.completion; 2 | 3 | import com.intellij.codeInsight.completion.CompletionContributor; 4 | import com.intellij.codeInsight.completion.CompletionType; 5 | import com.intellij.openapi.project.DumbAware; 6 | import com.jetbrains.php.completion.PhpTraitDocTagCompletionProvider; 7 | 8 | import static com.intellij.patterns.PlatformPatterns.psiElement; 9 | import static com.jetbrains.php.lang.documentation.phpdoc.lexer.PhpDocTokenTypes.DOC_TAG_NAME; 10 | 11 | public final class PhpStanCompletionContributor extends CompletionContributor implements DumbAware { 12 | private static final String[] ADDITIONAL_TRAIT_DOC_TAG_COMPLETIONS = { 13 | "phpstan-require-extends", 14 | "phpstan-require-implements" 15 | }; 16 | 17 | public PhpStanCompletionContributor() { 18 | extend(CompletionType.BASIC, psiElement(DOC_TAG_NAME), new PhpTraitDocTagCompletionProvider(ADDITIONAL_TRAIT_DOC_TAG_COMPLETIONS)); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanBundle.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.intellij.DynamicBundle; 4 | import org.jetbrains.annotations.Nls; 5 | import org.jetbrains.annotations.NonNls; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.PropertyKey; 8 | 9 | import java.util.function.Supplier; 10 | 11 | public final class PhpStanBundle { 12 | public static final @NonNls String BUNDLE = "messages.PhpStanBundle"; 13 | private static final DynamicBundle INSTANCE = new DynamicBundle(PhpStanBundle.class, BUNDLE); 14 | 15 | private PhpStanBundle() {} 16 | 17 | public static @NotNull @Nls String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) { 18 | return INSTANCE.getMessage(key, params); 19 | } 20 | 21 | public static @NotNull Supplier<@Nls String> messagePointer(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) { 22 | return INSTANCE.getLazyMessage(key, params); 23 | } 24 | } -------------------------------------------------------------------------------- /tests/com/jetbrains/php/phpstan/quality/tools/PhpStanOutputParsingTest.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.phpstan.quality.tools; 2 | 3 | import com.jetbrains.php.fixtures.PhpHeavyCodeInsightFixtureTestCase; 4 | import com.jetbrains.php.tools.quality.phpstan.PhpStanConfigurationManager; 5 | import com.jetbrains.php.tools.quality.phpstan.PhpStanGlobalInspection; 6 | 7 | public class PhpStanOutputParsingTest extends PhpHeavyCodeInsightFixtureTestCase { 8 | 9 | public void testSimple() { 10 | PhpStanConfigurationManager.getInstance(myFixture.getProject()).getOrCreateLocalSettings().setToolPath("phpstan"); // Dummy, needed to run annotator 11 | configureByFiles(getFileBeforeRelativePath().replace(".php", ".txt")); 12 | myFixture.enableInspections(new PhpStanGlobalInspection()); 13 | myFixture.testHighlighting(true, false, true); 14 | } 15 | 16 | @Override 17 | protected String getFixtureTestDataFolder() { 18 | return "output"; 19 | } 20 | 21 | @Override 22 | protected String getBasePath() { 23 | return "/phpstorm/phpstan/testData/output"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/com/jetbrains/php/phpstan/quality/tools/PhpStanComposerConfigTest.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.phpstan.quality.tools; 2 | 3 | import com.intellij.openapi.util.SystemInfo; 4 | import com.intellij.openapi.util.io.FileUtilRt; 5 | import com.jetbrains.php.config.composer.QualityToolsComposerConfigTest; 6 | import com.jetbrains.php.tools.quality.QualityToolConfigurationManager; 7 | import com.jetbrains.php.tools.quality.phpstan.PhpStanConfigurationManager; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public class PhpStanComposerConfigTest extends QualityToolsComposerConfigTest { 11 | 12 | @Override 13 | protected QualityToolConfigurationManager getQualityToolConfigurationManager() { 14 | return PhpStanConfigurationManager.getInstance(getProject()); 15 | } 16 | 17 | @NotNull 18 | @Override 19 | protected String getPath() { 20 | return FileUtilRt.toSystemIndependentName(myFixture.getTempDirPath()) + "/vendor/bin/phpstan" + (SystemInfo.isWindows ? ".bat" : ""); 21 | } 22 | 23 | @NotNull 24 | @Override 25 | protected String getPackageName() { 26 | return "phpstan/phpstan"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/com/jetbrains/php/phpstan/types/PhpStanTypeInferenceTest.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.phpstan.types; 2 | 3 | import com.jetbrains.php.codeInsight.PhpTypeInferenceTestCase; 4 | import com.jetbrains.php.phpstan.lang.documentation.parser.PhpStanDocParserTest; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public class PhpStanTypeInferenceTest extends PhpTypeInferenceTestCase { 8 | @Override 9 | protected @NotNull String getTestDataHome() { 10 | return PhpStanDocParserTest.TEST_DATA_HOME; 11 | } 12 | 13 | @Override 14 | protected String getFixtureTestDataFolder() { 15 | return "codeInsight/typeInference"; 16 | } 17 | 18 | public void testDocTags() { 19 | doTypeTest(); 20 | } 21 | 22 | public void testTemplates() { 23 | doTypeTest(); 24 | } 25 | 26 | public void testAliasImportType() { 27 | doTypeTest(); 28 | } 29 | 30 | public void testConditionalType() { 31 | doTypeTest(); 32 | } 33 | 34 | public void testNestedConditionalTypes() { 35 | doTypeTest(); 36 | } 37 | 38 | public void testConditionalTypesWithGenerics() { 39 | doTypeTest(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanQualityToolAnnotatorInfo.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.intellij.codeInspection.InspectionProfile; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.psi.PsiFile; 6 | import com.jetbrains.php.tools.quality.QualityToolAnnotatorInfo; 7 | import com.jetbrains.php.tools.quality.QualityToolConfiguration; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | public class PhpStanQualityToolAnnotatorInfo extends QualityToolAnnotatorInfo { 12 | 13 | public PhpStanQualityToolAnnotatorInfo(@Nullable PsiFile psiFile, 14 | @NotNull PhpStanValidationInspection inspection, 15 | @NotNull InspectionProfile profile, 16 | @NotNull Project project, 17 | @NotNull QualityToolConfiguration configuration, boolean isOnTheFly) { 18 | super(psiFile, inspection, profile, project, configuration, isOnTheFly); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanConfigurationProvider.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.intellij.openapi.diagnostic.Logger; 4 | import com.intellij.openapi.extensions.ExtensionPointName; 5 | import com.jetbrains.php.tools.quality.QualityToolConfigurationProvider; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import java.util.List; 9 | 10 | public abstract class PhpStanConfigurationProvider extends QualityToolConfigurationProvider { 11 | private static final Logger LOG = Logger.getInstance(PhpStanConfigurationProvider.class); 12 | private static final ExtensionPointName EP_NAME = 13 | ExtensionPointName.create("com.jetbrains.php.tools.quality.PhpStan.PhpStanConfigurationProvider"); 14 | 15 | public static @Nullable PhpStanConfigurationProvider getInstances() { 16 | List extensions = EP_NAME.getExtensionList(); 17 | if (extensions.size() > 1) { 18 | LOG.error("Several providers for remote PHPStan configuration was found"); 19 | } 20 | return extensions.isEmpty() ? null : extensions.get(0); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/com/jetbrains/php/phpstan/quality/tools/PhpStanVersionValidatorTest.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.phpstan.quality.tools; 2 | 3 | import com.intellij.openapi.util.Pair; 4 | import com.intellij.testFramework.fixtures.CodeInsightFixtureTestCase; 5 | import com.jetbrains.php.tools.quality.phpstan.PhpStanConfigurableForm; 6 | import com.jetbrains.php.tools.quality.phpstan.PhpStanConfiguration; 7 | 8 | public class PhpStanVersionValidatorTest extends CodeInsightFixtureTestCase { 9 | 10 | public void testSimple(){ 11 | final String STRING = "PHPStan - PHP Static Analysis Tool 0.12.25"; 12 | final Pair result = new PhpStanConfigurableForm<>(myFixture.getProject(), new PhpStanConfiguration()) 13 | .validateMessage(STRING); 14 | assertTrue(result.first); 15 | assertEquals("OK, " + STRING, result.second); 16 | } 17 | 18 | public void testDevMaster(){ 19 | final String message = "PHPStan - PHP Static Analysis Tool 0.12.x-dev@41b16d5"; 20 | final Pair result = new PhpStanConfigurableForm<>(myFixture.getProject(), new PhpStanConfiguration()) 21 | .validateMessage(message); 22 | assertTrue(result.first); 23 | assertEquals("OK, " + message, result.second); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /testData/codeInsight/typeInference/Templates.php: -------------------------------------------------------------------------------- 1 | $c = (new A())->mirror($a, $b); 21 | 22 | namespace BB; 23 | 24 | /** 25 | * @phpstan-template T 26 | * @phpstan-template T1 27 | */ 28 | abstract class Base 29 | { 30 | 31 | 32 | /** 33 | * @phpstan-var T 34 | */ 35 | public $first; 36 | 37 | /** 38 | * @phpstan-var T1 39 | */ 40 | public $second; 41 | } 42 | 43 | class P 44 | { 45 | } 46 | class P1 47 | { 48 | } 49 | 50 | 51 | /** 52 | * @phpstan-extends Base 53 | */ 54 | class Child extends Base 55 | { 56 | } 57 | 58 | /** 59 | * @phpstan-extends Base

60 | */ 61 | class ChildPartial extends Base 62 | { 63 | } 64 | 65 | (new Child())->first; 66 | (new Child())->second; 67 | (new ChildPartial())->first; 68 | (new ChildPartial())->second; -------------------------------------------------------------------------------- /tests/com/jetbrains/php/phpstan/lang/documentation/parser/PhpStanDocParserTest.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.phpstan.lang.documentation.parser; 2 | 3 | import com.jetbrains.php.lang.parser.BasicPhpParserTestCase; 4 | 5 | public class PhpStanDocParserTest extends BasicPhpParserTestCase { 6 | public static final String TEST_DATA_HOME = "/phpstorm/phpstan/testData/"; 7 | 8 | @Override 9 | protected String getDataPath() { 10 | return TEST_DATA_HOME + "parser"; 11 | } 12 | 13 | public void test$DocTags() throws Throwable { 14 | doTest(); 15 | } 16 | 17 | public void test$DocMethodsTags() throws Throwable { 18 | doTest(); 19 | } 20 | 21 | public void test$Template() throws Throwable { 22 | doTest(); 23 | } 24 | 25 | public void test$Assert() throws Throwable { 26 | doTest(); 27 | } 28 | 29 | public void test$Inheritance() throws Throwable { 30 | doTest(); 31 | } 32 | 33 | public void test$Method() throws Throwable { 34 | doTest(); 35 | } 36 | 37 | public void test$Type() throws Throwable { 38 | doTest(); 39 | } 40 | 41 | public void test$InplaceCovariantGeneric() throws Throwable { 42 | doTest(); 43 | } 44 | 45 | public void test$InplaceContravariantGeneric() throws Throwable { 46 | doTest(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanConfigurable.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.intellij.openapi.options.Configurable; 4 | import com.intellij.openapi.project.Project; 5 | import com.jetbrains.php.tools.quality.QualityToolConfigurationComboBox; 6 | import com.jetbrains.php.tools.quality.QualityToolProjectConfigurableForm; 7 | import com.jetbrains.php.tools.quality.QualityToolType; 8 | import com.jetbrains.php.tools.quality.QualityToolsOptionsPanel; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | public class PhpStanConfigurable extends QualityToolProjectConfigurableForm implements Configurable.NoScroll { 12 | 13 | public PhpStanConfigurable(@NotNull Project project) { 14 | super(project); 15 | } 16 | 17 | @Override 18 | protected QualityToolsOptionsPanel getQualityToolOptionPanel(QualityToolConfigurationComboBox configurationBox, Runnable validate) { 19 | return new PhpStanOptionsPanel(myProject, configurationBox, validate); 20 | } 21 | 22 | @Override 23 | public @NotNull String getId() { 24 | return "settings.php.quality.tools.phpstan"; 25 | } 26 | 27 | @Override 28 | protected @NotNull QualityToolType getQualityToolType() { 29 | return PhpStanQualityToolType.INSTANCE; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanSettingsTransferStartupActivity.kt: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import com.intellij.openapi.project.Project 5 | import com.intellij.openapi.startup.ProjectActivity 6 | import com.intellij.profile.codeInspection.InspectionProfileManager 7 | import com.intellij.util.PlatformUtils 8 | 9 | private class PhpStanSettingsTransferStartupActivity : ProjectActivity { 10 | override suspend fun execute(project: Project) { 11 | if (project.isDefault) return 12 | val app = ApplicationManager.getApplication() 13 | if (app.isUnitTestMode || app.isHeadlessEnvironment || !PlatformUtils.isPhpStorm()) return 14 | 15 | val tool = PhpStanQualityToolType.INSTANCE.getGlobalTool(project, InspectionProfileManager.getInstance(project).currentProfile) as? PhpStanGlobalInspection 16 | val instance = PhpStanOptionsConfiguration.getInstance(project) 17 | tool?.let { 18 | if (!instance.isTransferred) { 19 | instance.config = tool.config 20 | instance.autoload = tool.autoload 21 | instance.level = tool.level 22 | instance.memoryLimit = tool.memoryLimit 23 | instance.isFullProject = tool.FULL_PROJECT 24 | instance.isTransferred = true 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanConfigurationBaseManager.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.intellij.openapi.util.NlsSafe; 4 | import com.intellij.util.xmlb.XmlSerializer; 5 | import com.jetbrains.php.tools.quality.QualityToolConfigurationBaseManager; 6 | import com.jetbrains.php.tools.quality.QualityToolType; 7 | import org.jdom.Element; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | public class PhpStanConfigurationBaseManager extends QualityToolConfigurationBaseManager { 12 | private static final @NlsSafe String PHP_STAN_PATH = "PhpStanPath"; 13 | public static final @NlsSafe String PHP_STAN = "PHPStan"; 14 | private static final @NlsSafe String ROOT_NAME = "PhpStan_settings"; 15 | 16 | @Override 17 | protected @NotNull QualityToolType getQualityToolType() { 18 | return PhpStanQualityToolType.INSTANCE; 19 | } 20 | 21 | @Override 22 | protected @NotNull String getOldStyleToolPathName() { 23 | return PHP_STAN_PATH; 24 | } 25 | 26 | @Override 27 | protected @NotNull String getConfigurationRootName() { 28 | return ROOT_NAME; 29 | } 30 | 31 | @Override 32 | protected @Nullable PhpStanConfiguration loadLocal(Element element) { 33 | return XmlSerializer.deserialize(element, PhpStanConfiguration.class); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /testData/codeInsight/documentationProvider/PhpStanTags.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 10 |
3 |

Templates:

T of Foo – with description
4 | covariant M of Foo – with description
6 |

Parameters:

int $a – cshdcygdc
7 | array<int, Foo> $b
9 |

Returns:

Foo 
11 |

Throws:

Exception 
12 | -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanConfigurationManager.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | import com.intellij.openapi.components.State; 5 | import com.intellij.openapi.components.Storage; 6 | import com.intellij.openapi.project.Project; 7 | import com.jetbrains.php.tools.quality.QualityToolConfigurationManager; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | public class PhpStanConfigurationManager extends QualityToolConfigurationManager { 12 | 13 | public static final int DEFAULT_MAX_MESSAGES_PER_FILE = 50; 14 | 15 | public PhpStanConfigurationManager(@Nullable Project project) { 16 | super(project); 17 | if (project != null) { 18 | myProjectManager = project.getService(PhpStanProjectConfigurationManager.class); 19 | } 20 | myApplicationManager = ApplicationManager.getApplication().getService(PhpStanAppConfigurationManager.class); 21 | } 22 | 23 | public static PhpStanConfigurationManager getInstance(@NotNull Project project) { 24 | return project.getService(PhpStanConfigurationManager.class); 25 | } 26 | 27 | @State(name = "PhpStan", storages = @Storage("php.xml")) 28 | static class PhpStanProjectConfigurationManager extends PhpStanConfigurationBaseManager { 29 | } 30 | 31 | @State(name = "PhpStan", storages = @Storage("php.xml")) 32 | static class PhpStanAppConfigurationManager extends PhpStanConfigurationBaseManager { 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanProjectConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.intellij.openapi.components.PersistentStateComponent; 4 | import com.intellij.openapi.components.State; 5 | import com.intellij.openapi.components.Storage; 6 | import com.intellij.openapi.components.StoragePathMacros; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.util.xmlb.XmlSerializerUtil; 9 | import com.jetbrains.php.tools.quality.QualityToolProjectConfiguration; 10 | import com.jetbrains.php.tools.quality.QualityToolType; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | @State(name = "PhpStanProjectConfiguration", storages = @Storage(StoragePathMacros.WORKSPACE_FILE)) 15 | public class PhpStanProjectConfiguration extends QualityToolProjectConfiguration 16 | implements PersistentStateComponent { 17 | 18 | @Override 19 | public @Nullable PhpStanProjectConfiguration getState() { 20 | return this; 21 | } 22 | 23 | @Override 24 | public void loadState(@NotNull PhpStanProjectConfiguration state) { 25 | XmlSerializerUtil.copyBean(state, this); 26 | } 27 | 28 | @Override 29 | protected QualityToolType getQualityToolType() { 30 | return PhpStanQualityToolType.INSTANCE; 31 | } 32 | 33 | public static PhpStanProjectConfiguration getInstance(Project project) { 34 | return project.getService(PhpStanProjectConfiguration.class); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /testData/parser/Inheritance.txt: -------------------------------------------------------------------------------- 1 | 28 | PsiWhiteSpace('\n ') 29 | PsiElement(DOC_LEADING_ASTERISK)('*') 30 | PsiWhiteSpace(' ') 31 | PhpDocTagImpl: @phpstan-require-implemen... 32 | PsiElement(DOC_TAG_NAME)('@phpstan-require-implements') 33 | PsiWhiteSpace(' ') 34 | PhpDocTypeImpl: A 35 | PsiElement(DOC_IDENTIFIER)('A') 36 | PhpPsiElementImpl 37 | 38 | PsiWhiteSpace('\n ') 39 | PsiElement(DOC_COMMENT_END)('*/') 40 | PsiWhiteSpace('\n') 41 | PhpClass: C 42 | PsiElement(trait)('trait') 43 | PsiWhiteSpace(' ') 44 | PsiElement(identifier)('C') 45 | PsiWhiteSpace(' ') 46 | Extends list 47 | 48 | Implements list 49 | 50 | PsiElement({)('{') 51 | PsiWhiteSpace('\n\n') 52 | PsiElement(})('}') -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanConfigurableForm.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.util.Pair; 5 | import com.intellij.openapi.util.Version; 6 | import com.jetbrains.php.PhpBundle; 7 | import com.jetbrains.php.tools.quality.QualityToolConfigurableForm; 8 | import com.jetbrains.php.tools.quality.QualityToolType; 9 | import org.jetbrains.annotations.NonNls; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import static com.jetbrains.php.tools.quality.phpstan.PhpStanConfigurationBaseManager.PHP_STAN; 13 | 14 | public class PhpStanConfigurableForm extends QualityToolConfigurableForm { 15 | 16 | public PhpStanConfigurableForm(@NotNull Project project, @NotNull C configuration) { 17 | super(project, configuration, PHP_STAN, "phpstan"); 18 | } 19 | 20 | @Override 21 | public QualityToolType getQualityToolType() { 22 | return PhpStanQualityToolType.INSTANCE; 23 | } 24 | 25 | @Override 26 | public String getHelpTopic() { 27 | return "reference.settings.php.PhpStan"; 28 | } 29 | 30 | @Override 31 | public @NotNull Pair validateMessage(@NonNls String message) { 32 | final Version version = extractVersion(message.trim().replaceFirst("PHPStan.* ([\\d.]*).*", "$1").trim()); 33 | if (version == null || !message.contains(PHP_STAN)) { 34 | return Pair.create(false, PhpBundle.message("quality.tool.can.not.determine.version", message)); 35 | } 36 | return Pair.create(true, "OK, " + message); 37 | } 38 | } -------------------------------------------------------------------------------- /tests/com/jetbrains/php/phpstan/quality/tools/PhpStanAnnotatorTest.kt: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.phpstan.quality.tools 2 | 3 | import com.intellij.profile.codeInspection.InspectionProfileManager 4 | import com.intellij.psi.PsiFile 5 | import com.intellij.testFramework.builders.ModuleFixtureBuilder 6 | import com.intellij.testFramework.fixtures.CodeInsightFixtureTestCase 7 | import com.jetbrains.php.tools.quality.phpstan.* 8 | 9 | class PhpStanAnnotatorTest : CodeInsightFixtureTestCase>() { 10 | override fun setUp() { 11 | super.setUp() 12 | PhpStanConfigurationManager.getInstance(myFixture.project).getOrCreateLocalSettings().toolPath = "phpstan" 13 | myFixture.addFileToProject("file.txt", "") 14 | myFixture.configureByText("file.php", " 5 | */ 6 | function name($p) {} 7 | --- 8 | PHP file 9 | PsiElement(Non Lazy Group statement) 10 | PsiElement(php opening tag)('') 32 | PhpPsiElementImpl 33 | 34 | PsiWhiteSpace('\n ') 35 | PsiElement(DOC_COMMENT_END)('*/') 36 | PsiWhiteSpace('\n') 37 | FunctionImpl: name 38 | PsiElement(function)('function') 39 | PsiWhiteSpace(' ') 40 | PsiElement(identifier)('name') 41 | PsiElement(()('(') 42 | Parameter list 43 | ParameterImpl: p 44 | PsiElement(variable)('$p') 45 | PsiElement())(')') 46 | PsiWhiteSpace(' ') 47 | PsiElement(Group statement) 48 | PsiElement({)('{') 49 | PsiElement(})('}') -------------------------------------------------------------------------------- /testData/parser/InplaceContravariantGeneric.txt: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | function name($p) {} 7 | --- 8 | PHP file 9 | PsiElement(Non Lazy Group statement) 10 | PsiElement(php opening tag)('') 32 | PhpPsiElementImpl 33 | 34 | PsiWhiteSpace('\n ') 35 | PsiElement(DOC_COMMENT_END)('*/') 36 | PsiWhiteSpace('\n') 37 | FunctionImpl: name 38 | PsiElement(function)('function') 39 | PsiWhiteSpace(' ') 40 | PsiElement(identifier)('name') 41 | PsiElement(()('(') 42 | Parameter list 43 | ParameterImpl: p 44 | PsiElement(variable)('$p') 45 | PsiElement())(')') 46 | PsiWhiteSpace(' ') 47 | PsiElement(Group statement) 48 | PsiElement({)('{') 49 | PsiElement(})('}') -------------------------------------------------------------------------------- /testData/output/Simple.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/com/jetbrains/php/phpstan/lang/documentation/PhpStanDocumentationProviderTest.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.phpstan.lang.documentation; 2 | 3 | import com.intellij.psi.PsiDocCommentBase; 4 | import com.intellij.util.CollectConsumer; 5 | import com.jetbrains.php.fixtures.PhpCodeInsightFixtureTestCase; 6 | import com.jetbrains.php.lang.documentation.PhpDocumentationProvider; 7 | import com.jetbrains.php.phpstan.lang.documentation.parser.PhpStanDocParserTest; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public class PhpStanDocumentationProviderTest extends PhpCodeInsightFixtureTestCase { 11 | @Override 12 | protected @NotNull String getTestDataHome() { 13 | return PhpStanDocParserTest.TEST_DATA_HOME; 14 | } 15 | 16 | @Override 17 | protected String getFixtureTestDataFolder() { 18 | return "codeInsight/documentationProvider"; 19 | } 20 | 21 | @NotNull 22 | @Override 23 | protected String getFileAfterExtension() { 24 | return "html"; 25 | } 26 | 27 | public void doTestRenderedDoc() { 28 | configureByFile(); 29 | PhpDocumentationProvider docProvider = new PhpDocumentationProvider(); 30 | CollectConsumer docComments = new CollectConsumer<>(); 31 | PhpDocumentationProvider provider = new PhpDocumentationProvider(); 32 | provider.collectDocComments(myFixture.getFile(), docComments); 33 | StringBuilder builder = new StringBuilder(); 34 | docComments.getResult().forEach(c -> { 35 | builder.append(docProvider.generateRenderedDoc(c)); 36 | builder.append("\n"); 37 | }); 38 | String res = builder.toString().replace("
", "
\n").replace("

", "\n

").replace("", "\n
").replace("", "\n"); 39 | configurePhpByText("result.html", res); 40 | checkResultByFile(); 41 | } 42 | 43 | public void testPhpStanTags() { 44 | doTestRenderedDoc(); 45 | } 46 | 47 | public void testVarTag() { 48 | doTestRenderedDoc(); 49 | } 50 | 51 | public void testCustomPhpStanTags() { 52 | doTestRenderedDoc(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /intellij.phpstan.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanOptionsConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.intellij.openapi.components.PersistentStateComponent; 4 | import com.intellij.openapi.components.State; 5 | import com.intellij.openapi.components.Storage; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.openapi.util.NlsSafe; 8 | import com.intellij.util.xmlb.XmlSerializerUtil; 9 | import com.jetbrains.php.tools.quality.QualityToolsOptionsConfiguration; 10 | import org.jetbrains.annotations.NonNls; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | @State(name = "PhpStanOptionsConfiguration", storages = @Storage("php.xml")) 15 | public class PhpStanOptionsConfiguration extends QualityToolsOptionsConfiguration implements PersistentStateComponent { 16 | private boolean fullProject = false; 17 | private @NonNls String memoryLimit = "2G"; 18 | private int level = 4; 19 | private @NlsSafe String config = ""; 20 | private @NlsSafe String autoload = ""; 21 | 22 | public boolean isFullProject() { 23 | return fullProject; 24 | } 25 | 26 | public void setFullProject(boolean fullProject) { 27 | this.fullProject = fullProject; 28 | } 29 | 30 | public String getMemoryLimit() { 31 | return memoryLimit; 32 | } 33 | 34 | public void setMemoryLimit(String memoryLimit) { 35 | this.memoryLimit = memoryLimit; 36 | } 37 | 38 | public int getLevel() { 39 | return level; 40 | } 41 | 42 | public void setLevel(int level) { 43 | this.level = level; 44 | } 45 | 46 | public String getConfig() { 47 | return config; 48 | } 49 | 50 | public void setConfig(String config) { 51 | this.config = config; 52 | } 53 | 54 | public String getAutoload() { 55 | return autoload; 56 | } 57 | 58 | public void setAutoload(String autoload) { 59 | this.autoload = autoload; 60 | } 61 | 62 | @Override 63 | public @Nullable PhpStanOptionsConfiguration getState() { 64 | return this; 65 | } 66 | 67 | @Override 68 | public void loadState(@NotNull PhpStanOptionsConfiguration state) { 69 | XmlSerializerUtil.copyBean(state, this); 70 | } 71 | 72 | public static PhpStanOptionsConfiguration getInstance(Project project) { 73 | return project.getService(PhpStanOptionsConfiguration.class); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /testData/output/Simple.php: -------------------------------------------------------------------------------- 1 | protected $type; 9 | protected $data; 10 | 11 | public function __construct($type, $data) 12 | { 13 | switch ($type) { 14 | case self::TYPE_XML: 15 | break; 16 | default: 17 | throw new \WebServCo\Framework\Exceptions\ApplicationException('Type not implemented.'); 18 | break; 19 | } 20 | $this->type = $type; 21 | $this->data = $data; 22 | } 23 | 24 | public function highlight() 25 | { 26 | switch ($this->type) { 27 | case self::TYPE_XML: 28 | return $this->highlightXml($this->data); 29 | break; 30 | default: 31 | return false; 32 | break; 33 | } 34 | } 35 | 36 | protected function highlightXml($data) 37 | { 38 | $data = htmlentities($data); 39 | $data = str_replace('<', '<', $data); 40 | $data = str_replace('>', '>', $data); 41 | return $data; 42 | } 43 | } 44 | \count(); 45 | -------------------------------------------------------------------------------- /testData/codeInsight/documentationProvider/CustomPhpStanTags.html: -------------------------------------------------------------------------------- 1 |
2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 | 18 | 20 |
3 |

Templates:

NewT
5 |

Phpstan-assert:

string[] $arr
7 |

Psalm-check-type:

$bar = int
9 |

Psalm-type:

PhoneType = array{phone: string}
11 |

Phpstan-param-out:

int $a
13 |

Phpstan-if-this-is:

a<int>
15 |

Phpstan-assert-if-false:

int $this->test()
17 |

Phpstan-assert-if-true:

null $this->b
19 |

Phpstan-this-out:

self<NewT>
21 |

Phpstan-self-out:

self<NewT>
22 | -------------------------------------------------------------------------------- /tests/com/jetbrains/php/phpstan/quality/tools/PhpStanConfigFileFromComposerTest.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.phpstan.quality.tools; 2 | 3 | import com.intellij.openapi.vfs.VirtualFile; 4 | import com.intellij.testFramework.fixtures.CodeInsightFixtureTestCase; 5 | import com.jetbrains.php.tools.quality.phpstan.PhpStanComposerConfig; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | public class PhpStanConfigFileFromComposerTest extends CodeInsightFixtureTestCase { 10 | private void doRulesetTest(@NotNull String content, @Nullable String expectedPath) { 11 | myFixture.configureByText("composer.json", content); 12 | VirtualFile configFile = myFixture.getFile().getVirtualFile(); 13 | assertEquals(expectedPath, new PhpStanComposerConfig().getRuleset(configFile)); 14 | } 15 | 16 | public void testCFormat() { 17 | doRulesetTest(""" 18 | 19 | { 20 | "scripts": { 21 | "phpstan": "vendor/bin/phpstan analyse -l 2 -c tests/phpstan/phpstan.neon system/src --memory-limit=256M" 22 | } 23 | } 24 | """, "tests/phpstan/phpstan.neon"); 25 | } 26 | 27 | public void testNoScriptsSection() { 28 | doRulesetTest(""" 29 | 30 | { 31 | } 32 | """, null); 33 | } 34 | 35 | public void testNoPHPStanInScriptsSection() { 36 | doRulesetTest(""" 37 | 38 | { 39 | "scripts": { 40 | } 41 | } 42 | """, null); 43 | } 44 | 45 | public void testConfigFormat() { 46 | doRulesetTest(""" 47 | 48 | { 49 | "scripts": { 50 | "phpstan": "vendor/bin/phpstan analyse -l 2 --configuration=tests/phpstan/phpstan.neon system/src --memory-limit=256M" 51 | } 52 | } 53 | """, "tests/phpstan/phpstan.neon"); 54 | } 55 | 56 | public void testNoConfigArg() { 57 | doRulesetTest(""" 58 | 59 | { 60 | "scripts": { 61 | "phpstan": "vendor/bin/phpstan analyse -l 2 system/src --memory-limit=256M" 62 | } 63 | } 64 | """, null); 65 | } 66 | 67 | public void testWrongFormat() { 68 | doRulesetTest(""" 69 | 70 | { 71 | "scripts": { 72 | "phpstan": "vendor/bin/phpstan analyse -l 2 -c" 73 | } 74 | } 75 | """, null); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /testData/parser/Method.txt: -------------------------------------------------------------------------------- 1 | 33 | PsiWhiteSpace('\n ') 34 | PsiElement(DOC_LEADING_ASTERISK)('*') 35 | PsiWhiteSpace(' ') 36 | PhpDocPropertyTagImpl: @phpstan-property-read 37 | PsiElement(DOC_TAG_NAME)('@phpstan-property-read') 38 | PsiWhiteSpace(' ') 39 | PhpDocTypeImpl: int 40 | PsiElement(DOC_IDENTIFIER)('int') 41 | PsiWhiteSpace(' ') 42 | PhpDocPropertyImpl: b 43 | PsiElement(DOC_VARIABLE)('$b') 44 | PhpPsiElementImpl 45 | 46 | PsiWhiteSpace('\n ') 47 | PsiElement(DOC_LEADING_ASTERISK)('*') 48 | PsiWhiteSpace(' ') 49 | PhpDocPropertyTagImpl: @phpstan-property-write 50 | PsiElement(DOC_TAG_NAME)('@phpstan-property-write') 51 | PsiWhiteSpace(' ') 52 | PhpDocTypeImpl: int 53 | PsiElement(DOC_IDENTIFIER)('int') 54 | PsiWhiteSpace(' ') 55 | PhpDocPropertyImpl: c 56 | PsiElement(DOC_VARIABLE)('$c') 57 | PhpPsiElementImpl 58 | 59 | PsiWhiteSpace('\n ') 60 | PsiElement(DOC_LEADING_ASTERISK)('*') 61 | PsiWhiteSpace(' ') 62 | PhpDocMethodTagImpl: phpstan-method 63 | PsiElement(error silence)('@') 64 | PsiElement(identifier)('phpstan-method') 65 | PsiWhiteSpace(' ') 66 | PhpDocMethodImpl: f 67 | PsiElement(identifier)('f') 68 | PsiElement(()('(') 69 | Parameter list 70 | 71 | PsiElement())(')') 72 | PhpPsiElementImpl 73 | 74 | PsiWhiteSpace('\n ') 75 | PsiElement(DOC_COMMENT_END)('*/') 76 | PsiWhiteSpace('\n') 77 | PhpClass: F 78 | PsiElement(class)('class') 79 | PsiWhiteSpace(' ') 80 | PsiElement(identifier)('F') 81 | PsiWhiteSpace(' ') 82 | Extends list 83 | 84 | Implements list 85 | 86 | PsiElement({)('{') 87 | PsiWhiteSpace('\n\n') 88 | PsiElement(})('}') -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ### Code of Conduct 2 | 3 | This code of conduct outlines our expectations for all those who participate in our open source projects and communities (community programs), as well as the consequences for unacceptable behaviour. We invite all those who participate to help us create safe and positive experiences for everyone. Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society. 4 | 5 | #### How to behave 6 | The following behaviours are expected and requested of all community members: 7 | 8 | * Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community. 9 | * Exercise consideration, respect and empathy in your speech and actions. Remember, we have all been through different stages of learning when adopting technologies. 10 | * Refrain from demeaning, discriminatory, or harassing behaviour and speech. 11 | * Disagreements on things are fine, argumentative behaviour or trolling are not. 12 | 13 | #### How not to behave 14 | 15 | * Do not perform threats of violence or use violent language directed against another person. 16 | * Do not make jokes of sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory nature, or use language of this nature. 17 | * Do not post or display sexually explicit or violent material. 18 | * Do not post or threaten to post other people’s personally identifying information ("doxing"). 19 | * Do not make personal insults, particularly those related to gender, sexual orientation, race, religion, or disability. 20 | * Do not engage in sexual attention. This includes, sexualised comments or jokes and sexual advances. 21 | * Do not advocate for, or encourage, any of the above behaviour. 22 | 23 | Please take into account that online communities bring together people from many different cultures and backgrounds. It's important to understand that sometimes the combination of cultural differences and online interaction can lead to misunderstandings. That is why having empathy is very important. 24 | 25 | #### How to report issues 26 | 27 | If someone is acting inappropriately or violating this Code of Conduct in any shape or form, and they are not receptive to your feedback or you prefer not to confront them, please reach out to JetBrains via codeofconduct@jetbrains.com 28 | 29 | #### Consequences of Unacceptable Behaviour 30 | 31 | Unacceptable behaviour from any community member will not be tolerated. Anyone asked to stop unacceptable behaviour is expected to comply immediately. If a community member engages in unacceptable behaviour, JetBrains and/or community organisers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning. 32 | 33 | ##### License and attribution 34 | The license is based off of The Citizen Code of Conduct is distributed by Stumptown Syndicate under a Creative Commons Attribution-ShareAlike license. -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/remote/PhpStanRemoteConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan.remote; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.util.NlsContexts; 5 | import com.intellij.openapi.util.NlsSafe; 6 | import com.intellij.util.xmlb.annotations.Attribute; 7 | import com.intellij.util.xmlb.annotations.Tag; 8 | import com.jetbrains.php.PhpBundle; 9 | import com.jetbrains.php.config.interpreters.PhpInterpretersManagerImpl; 10 | import com.jetbrains.php.config.interpreters.PhpSdkDependentConfiguration; 11 | import com.jetbrains.php.tools.quality.phpstan.PhpStanBundle; 12 | import com.jetbrains.php.tools.quality.phpstan.PhpStanConfiguration; 13 | import org.jetbrains.annotations.Nls; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.jetbrains.annotations.Nullable; 16 | 17 | import static com.intellij.openapi.util.text.StringUtil.isEmpty; 18 | import static com.jetbrains.php.tools.quality.QualityToolProjectConfiguration.DEFAULT_INTERPRETER_CONFIGURATION_ID; 19 | 20 | @Tag("phpstan_by_interpreter") 21 | public class PhpStanRemoteConfiguration extends PhpStanConfiguration implements PhpSdkDependentConfiguration { 22 | private String myInterpreterId; 23 | 24 | @Override 25 | @Attribute("interpreter_id") 26 | public @Nullable @NlsSafe String getInterpreterId() { 27 | return myInterpreterId; 28 | } 29 | 30 | @Override 31 | public void setInterpreterId(@NotNull String interpreterId) { 32 | myInterpreterId = interpreterId; 33 | } 34 | 35 | @Override 36 | public @NotNull @NlsContexts.Label String getPresentableName(@Nullable Project project) { 37 | if (isCreatedAsDefaultInterpreterConfiguration()) return PhpBundle.message("quality.tools.label.by.default.project.interpreter"); 38 | return getDefaultName(PhpInterpretersManagerImpl.getInstance(project).findInterpreterName(getInterpreterId())); 39 | } 40 | 41 | @Override 42 | public @NotNull @Nls String getId() { 43 | if (isCreatedAsDefaultInterpreterConfiguration()) return DEFAULT_INTERPRETER_CONFIGURATION_ID; 44 | final String interpreterId = getInterpreterId(); 45 | return isEmpty(interpreterId) ? PhpStanBundle.message("undefined.interpreter") : interpreterId; 46 | } 47 | 48 | public static @NotNull @Nls String getDefaultName(@Nls @Nullable String interpreterName) { 49 | return isEmpty(interpreterName) ? PhpStanBundle.message("undefined.interpreter") : interpreterName; 50 | } 51 | 52 | @Override 53 | public PhpStanRemoteConfiguration clone() { 54 | PhpStanRemoteConfiguration settings = new PhpStanRemoteConfiguration(); 55 | settings.myInterpreterId = myInterpreterId; 56 | settings.setCreatedAsDefaultInterpreterConfiguration(this.isCreatedAsDefaultInterpreterConfiguration()); 57 | settings.setDeletedFromTheList(this.isDeletedFromTheList()); 58 | clone(settings); 59 | return settings; 60 | } 61 | 62 | @Override 63 | public String serialize(@Nullable String path) { 64 | return path; 65 | } 66 | 67 | @Override 68 | public String deserialize(@Nullable String path) { 69 | return path; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | ### auto-generated section `build intellij.phpstan` start 2 | load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") 3 | 4 | resourcegroup( 5 | name = "phpstan_resources", 6 | srcs = glob(["resources/**/*"]), 7 | strip_prefix = "resources" 8 | ) 9 | 10 | resourcegroup( 11 | name = "phpstan_test_resources", 12 | srcs = glob(["testData/**/*"]), 13 | strip_prefix = "testData" 14 | ) 15 | 16 | jvm_library( 17 | name = "phpstan", 18 | module_name = "intellij.phpstan", 19 | visibility = ["//visibility:public"], 20 | srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), 21 | resources = [":phpstan_resources"], 22 | deps = [ 23 | "//phpstorm/php:php-impl", 24 | "@community//platform/core-api:core", 25 | "@community//platform/analysis-impl", 26 | "@community//platform/editor-ui-api:editor-ui", 27 | "@community//platform/projectModel-api:projectModel", 28 | "@community//platform/analysis-api:analysis", 29 | "@community//platform/platform-api:ide", 30 | "@community//platform/remote-core", 31 | "@community//platform/util/jdom", 32 | "@community//libraries/gson", 33 | "@community//platform/core-impl", 34 | "//phpstorm/phpstorm-remote-interpreter:php-remoteInterpreter", 35 | "@community//platform/platform-impl:ide-impl", 36 | "@community//platform/core-ui", 37 | "//phpstorm/php-openapi:php", 38 | "@community//platform/platform-util-io:ide-util-io", 39 | "@community//platform/forms_rt:java-guiForms-rt", 40 | ] 41 | ) 42 | 43 | jvm_library( 44 | name = "phpstan_test_lib", 45 | visibility = ["//visibility:public"], 46 | srcs = glob(["tests/**/*.kt", "tests/**/*.java", "tests/**/*.form"], allow_empty = True), 47 | resources = [":phpstan_test_resources"], 48 | associates = [":phpstan"], 49 | deps = [ 50 | "//phpstorm/php:php-impl", 51 | "//phpstorm/php:php-impl_test_lib", 52 | "@community//platform/core-api:core", 53 | "@community//platform/analysis-impl", 54 | "@community//platform/editor-ui-api:editor-ui", 55 | "@community//platform/projectModel-api:projectModel", 56 | "@community//platform/analysis-api:analysis", 57 | "@community//platform/platform-api:ide", 58 | "@community//platform/remote-core", 59 | "@community//platform/testFramework", 60 | "@community//platform/testFramework:testFramework_test_lib", 61 | "@community//platform/util/jdom", 62 | "@community//libraries/gson", 63 | "@community//platform/core-impl", 64 | "//phpstorm/phpstorm-remote-interpreter:php-remoteInterpreter", 65 | "//phpstorm/phpstorm-remote-interpreter:php-remoteInterpreter_test_lib", 66 | "@community//platform/platform-impl:ide-impl", 67 | "@community//platform/core-ui", 68 | "//phpstorm/php-openapi:php", 69 | "@community//platform/platform-util-io:ide-util-io", 70 | "@community//platform/forms_rt:java-guiForms-rt", 71 | ] 72 | ) 73 | ### auto-generated section `build intellij.phpstan` end 74 | 75 | ### auto-generated section `test intellij.phpstan` start 76 | load("@community//build:tests-options.bzl", "jps_test") 77 | 78 | jps_test( 79 | name = "phpstan_test", 80 | runtime_deps = [":phpstan_test_lib"] 81 | ) 82 | ### auto-generated section `test intellij.phpstan` end -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.intellij.openapi.util.text.StringUtil; 4 | import com.intellij.util.xmlb.annotations.Attribute; 5 | import com.intellij.util.xmlb.annotations.Transient; 6 | import com.jetbrains.php.tools.quality.QualityToolConfiguration; 7 | import org.jetbrains.annotations.Nls; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import static com.jetbrains.php.tools.quality.phpstan.PhpStanConfigurationManager.DEFAULT_MAX_MESSAGES_PER_FILE; 12 | 13 | 14 | /** 15 | * Stores configuration needed to run PHPStan in selected environment. 16 | */ 17 | public class PhpStanConfiguration extends QualityToolConfiguration { 18 | private String myPhpStanPath = ""; 19 | private int myMaxMessagesPerFile = DEFAULT_MAX_MESSAGES_PER_FILE; 20 | private int myTimeoutMs = 30000; 21 | 22 | @Override 23 | @Transient 24 | public String getToolPath() { 25 | return myPhpStanPath; 26 | } 27 | 28 | @Override 29 | public void setToolPath(String toolPath) { 30 | myPhpStanPath = toolPath; 31 | } 32 | 33 | @SuppressWarnings("UnusedDeclaration") 34 | @Attribute("tool_path") 35 | public @Nullable String getSerializedToolPath() { 36 | return serialize(myPhpStanPath); 37 | } 38 | 39 | @SuppressWarnings("UnusedDeclaration") 40 | public void setSerializedToolPath(@Nullable String configurationFilePath) { 41 | myPhpStanPath = deserialize(configurationFilePath); 42 | } 43 | 44 | @Override 45 | @Attribute("max_messages_per_file") 46 | public int getMaxMessagesPerFile() { 47 | return myMaxMessagesPerFile; 48 | } 49 | 50 | @Override 51 | @Attribute("timeout") 52 | public int getTimeout() { 53 | return myTimeoutMs; 54 | } 55 | 56 | @Override 57 | public void setTimeout(int timeout) { 58 | myTimeoutMs = timeout; 59 | } 60 | 61 | @Override 62 | public @NotNull @Nls String getId() { 63 | return PhpStanBundle.message("local"); 64 | } 65 | 66 | @Override 67 | public @Nullable String getInterpreterId() { 68 | return null; 69 | } 70 | 71 | @Override 72 | public PhpStanConfiguration clone() { 73 | PhpStanConfiguration settings = new PhpStanConfiguration(); 74 | clone(settings); 75 | return settings; 76 | } 77 | 78 | public PhpStanConfiguration clone(@NotNull PhpStanConfiguration settings) { 79 | settings.myPhpStanPath = myPhpStanPath; 80 | settings.myMaxMessagesPerFile = myMaxMessagesPerFile; 81 | settings.myTimeoutMs = myTimeoutMs; 82 | return settings; 83 | } 84 | 85 | @Override 86 | public int compareTo(@NotNull QualityToolConfiguration o) { 87 | if (!(o instanceof PhpStanConfiguration)) { 88 | return 1; 89 | } 90 | 91 | if (StringUtil.equals(getPresentableName(null), PhpStanBundle.message("label.system.php"))) { 92 | return -1; 93 | } 94 | else if (StringUtil.equals(o.getPresentableName(null), PhpStanBundle.message("label.system.php"))) { 95 | return 1; 96 | } 97 | return StringUtil.compare(getPresentableName(null), o.getPresentableName(null), false); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /testData/parser/Template.txt: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | function f() { 10 | } 11 | 12 | --- 13 | PHP file 14 | PsiElement(Non Lazy Group statement) 15 | PsiElement(php opening tag)(' 34 | PsiWhiteSpace('\n ') 35 | PsiElement(DOC_LEADING_ASTERISK)('*') 36 | PsiWhiteSpace(' ') 37 | PhpDocTemplateTagImpl: @phpstan-template-covaria... 38 | PsiElement(DOC_TAG_NAME)('@phpstan-template-covariant') 39 | PsiWhiteSpace(' ') 40 | PhpDocTemplateParameterImpl: M 41 | PsiElement(DOC_IDENTIFIER)('M') 42 | PsiWhiteSpace(' ') 43 | PsiElement(DOC_IDENTIFIER)('of') 44 | PsiWhiteSpace(' ') 45 | PhpDocTypeImpl: F 46 | PsiElement(DOC_IDENTIFIER)('F') 47 | PsiWhiteSpace(' ') 48 | PhpPsiElementImpl 49 | PsiElement(DOC_IDENTIFIER)('Description') 50 | PsiWhiteSpace('\n ') 51 | PsiElement(DOC_LEADING_ASTERISK)('*') 52 | PsiWhiteSpace(' ') 53 | PhpDocTemplateTagImpl: @phpstan-template-contrav... 54 | PsiElement(DOC_TAG_NAME)('@phpstan-template-contravariant') 55 | PsiWhiteSpace(' ') 56 | PhpDocTemplateParameterImpl: L 57 | PsiElement(DOC_IDENTIFIER)('L') 58 | PhpPsiElementImpl 59 | 60 | PsiWhiteSpace('\n ') 61 | PsiElement(DOC_LEADING_ASTERISK)('*') 62 | PsiWhiteSpace(' ') 63 | PhpDocTagImpl: @phpstan 64 | PsiElement(DOC_TAG_NAME)('@phpstan') 65 | PsiWhiteSpace(' ') 66 | PhpPsiElementImpl 67 | PsiElement(DOC_IDENTIFIER)('T') 68 | PsiWhiteSpace('\n ') 69 | PsiElement(DOC_LEADING_ASTERISK)('*') 70 | PsiWhiteSpace(' ') 71 | PhpDocTagImpl: @phpstan-template-use 72 | PsiElement(DOC_TAG_NAME)('@phpstan-template-use') 73 | PsiWhiteSpace(' ') 74 | PhpDocTypeImpl: MyClass 75 | PsiElement(DOC_IDENTIFIER)('MyClass') 76 | PhpPsiElementImpl 77 | PsiElement(DOC_LAB)('<') 78 | PhpDocTypeImpl: T 79 | PsiElement(DOC_IDENTIFIER)('T') 80 | PsiElement(DOC_RAB)('>') 81 | PhpPsiElementImpl 82 | 83 | PsiWhiteSpace('\n ') 84 | PsiElement(DOC_COMMENT_END)('*/') 85 | PsiWhiteSpace('\n') 86 | FunctionImpl: f 87 | PsiElement(function)('function') 88 | PsiWhiteSpace(' ') 89 | PsiElement(identifier)('f') 90 | PsiElement(()('(') 91 | Parameter list 92 | 93 | PsiElement())(')') 94 | PsiWhiteSpace(' ') 95 | PsiElement(Group statement) 96 | PsiElement({)('{') 97 | PsiWhiteSpace('\n') 98 | PsiElement(})('}') -------------------------------------------------------------------------------- /testData/parser/Type.txt: -------------------------------------------------------------------------------- 1 | 35 | PsiWhiteSpace('\n ') 36 | PsiElement(DOC_COMMENT_END)('*/') 37 | PsiWhiteSpace('\n') 38 | Statement 39 | AssignmentExpressionImpl: $foo = 1 40 | VariableImpl: foo 41 | PsiElement(variable)('$foo') 42 | PsiWhiteSpace(' ') 43 | PsiElement(assign)('=') 44 | PsiWhiteSpace(' ') 45 | PhpExpressionImpl: 1 46 | PsiElement(integer)('1') 47 | PsiElement(semicolon)(';') 48 | PsiWhiteSpace('\n') 49 | PhpDocCommentImpl 50 | PsiElement(DOC_COMMENT_START)('/**') 51 | PsiWhiteSpace('\n ') 52 | PsiElement(DOC_LEADING_ASTERISK)('*') 53 | PsiWhiteSpace(' ') 54 | PhpDocTagImpl: @phpstan-check-type 55 | PsiElement(DOC_TAG_NAME)('@phpstan-check-type') 56 | PsiWhiteSpace(' ') 57 | PhpDocVarImpl: bar 58 | PsiElement(DOC_VARIABLE)('$bar') 59 | PsiWhiteSpace(' ') 60 | PsiElement(DOC_TEXT)('=') 61 | PsiWhiteSpace(' ') 62 | PhpDocTypeImpl: int 63 | PsiElement(DOC_IDENTIFIER)('int') 64 | PhpPsiElementImpl 65 | 66 | PsiWhiteSpace('\n ') 67 | PsiElement(DOC_LEADING_ASTERISK)('*') 68 | PsiWhiteSpace(' ') 69 | PhpDocTagImpl: @phpstan-type 70 | PsiElement(DOC_TAG_NAME)('@phpstan-type') 71 | PsiWhiteSpace(' ') 72 | PsiElement(DOC_IDENTIFIER)('PhoneType') 73 | PsiWhiteSpace(' ') 74 | PsiElement(DOC_TEXT)('=') 75 | PsiWhiteSpace(' ') 76 | PhpDocTypeImpl: array 77 | PsiElement(DOC_IDENTIFIER)('array') 78 | PhpPsiElementImpl 79 | PsiElement(DOC_LBRACE)('{') 80 | PsiElement(DOC_IDENTIFIER)('phone') 81 | PsiElement(DOC_TEXT)(':') 82 | PsiWhiteSpace(' ') 83 | PhpDocTypeImpl: string 84 | PsiElement(DOC_IDENTIFIER)('string') 85 | PsiElement(DOC_RBRACE)('}') 86 | PhpPsiElementImpl 87 | 88 | PsiWhiteSpace('\n ') 89 | PsiElement(DOC_COMMENT_END)('*/') 90 | PsiWhiteSpace('\n') 91 | Statement 92 | AssignmentExpressionImpl: $bar = 1 93 | VariableImpl: bar 94 | PsiElement(variable)('$bar') 95 | PsiWhiteSpace(' ') 96 | PsiElement(assign)('=') 97 | PsiWhiteSpace(' ') 98 | PhpExpressionImpl: 1 99 | PsiElement(integer)('1') 100 | PsiElement(semicolon)(';') -------------------------------------------------------------------------------- /resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | PHPStan Support 3 | com.intellij.php.tools.quality.phpstan 4 | Plugin provides PHPStan static analysis tool support 5 | JetBrains 6 | com.jetbrains.php 7 | com.intellij.modules.ultimate 8 | PHP Tools 9 | messages.PhpStanBundle 10 | org.jetbrains.plugins.phpstorm-remote-interpreter 11 | 12 | 14 | 15 | 16 | 17 | 18 | 20 | 27 | 28 | 29 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanQualityToolType.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.intellij.codeInspection.InspectionProfile; 4 | import com.intellij.codeInspection.ex.InspectionToolWrapper; 5 | import com.intellij.openapi.options.Configurable; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.profile.codeInspection.InspectionProjectProfileManager; 8 | import com.jetbrains.php.tools.quality.*; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | import static com.intellij.util.ObjectUtils.tryCast; 13 | import static com.jetbrains.php.tools.quality.phpstan.PhpStanConfigurationBaseManager.PHP_STAN; 14 | 15 | public final class PhpStanQualityToolType extends QualityToolType { 16 | public static final PhpStanQualityToolType INSTANCE = new PhpStanQualityToolType(); 17 | 18 | private PhpStanQualityToolType() { 19 | } 20 | 21 | @Override 22 | public @NotNull String getDisplayName() { 23 | return PHP_STAN; 24 | } 25 | 26 | @Override 27 | public @NotNull QualityToolBlackList getQualityToolBlackList(@NotNull Project project) { 28 | return PhpStanBlackList.getInstance(project); 29 | } 30 | 31 | @Override 32 | public @NotNull QualityToolConfigurationManager getConfigurationManager(@NotNull Project project) { 33 | return PhpStanConfigurationManager.getInstance(project); 34 | } 35 | 36 | @Override 37 | protected @NotNull PhpStanValidationInspection getInspection() { 38 | return new PhpStanValidationInspection(); 39 | } 40 | 41 | @Override 42 | protected @Nullable QualityToolConfigurationProvider getConfigurationProvider() { 43 | return PhpStanConfigurationProvider.getInstances(); 44 | } 45 | 46 | @Override 47 | protected @NotNull QualityToolConfigurableForm createConfigurableForm(@NotNull Project project, 48 | PhpStanConfiguration settings) { 49 | return new PhpStanConfigurableForm<>(project, settings); 50 | } 51 | 52 | @Override 53 | protected @NotNull Configurable getToolConfigurable(@NotNull Project project) { 54 | return new PhpStanConfigurable(project); 55 | } 56 | 57 | @Override 58 | protected @NotNull QualityToolProjectConfiguration getProjectConfiguration(@NotNull Project project) { 59 | return PhpStanProjectConfiguration.getInstance(project); 60 | } 61 | 62 | @Override 63 | protected @NotNull PhpStanConfiguration createConfiguration() { 64 | return new PhpStanConfiguration(); 65 | } 66 | 67 | @Override 68 | public @NotNull String getHelpTopic() { 69 | return "reference.settings.php.PHPStan"; 70 | } 71 | 72 | @Override 73 | public QualityToolValidationGlobalInspection getGlobalTool(@NotNull Project project, 74 | @Nullable InspectionProfile profile) { 75 | if (profile == null) { 76 | profile = InspectionProjectProfileManager.getInstance(project).getCurrentProfile(); 77 | } 78 | final InspectionToolWrapper inspectionTool = profile.getInspectionTool(getInspectionId(), project); 79 | if (inspectionTool == null) { 80 | return null; 81 | } 82 | return tryCast(inspectionTool.getTool(), PhpStanGlobalInspection.class); 83 | } 84 | 85 | @Override 86 | public String getInspectionId() { 87 | return "PhpStanGlobal"; 88 | } 89 | 90 | @Override 91 | public String getInspectionShortName(@NotNull Project project) { 92 | final QualityToolValidationGlobalInspection tool = getGlobalTool(project, null); 93 | if (tool != null) { 94 | return tool.getShortName(); 95 | } 96 | return getInspection().getShortName(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /tests/integration/tests/phpstan/PhpStanNewTagsTest.kt: -------------------------------------------------------------------------------- 1 | package integration.tests.phpstan 2 | 3 | import com.intellij.openapi.application.PathManager 4 | import com.intellij.openapi.util.io.FileUtil 5 | import com.intellij.psi.util.childrenOfType 6 | import com.intellij.testFramework.UsefulTestCase 7 | import com.jetbrains.php.PhpIndex 8 | import com.jetbrains.php.fixtures.PhpCodeInsightFixtureTestCase 9 | import com.jetbrains.php.lang.psi.elements.impl.ArrayCreationExpressionImpl 10 | import com.jetbrains.php.lang.psi.elements.impl.ForeachImpl 11 | import com.jetbrains.php.phpstan.lang.documentation.parser.PhpStanDocParserTest 12 | import java.io.BufferedInputStream 13 | import java.io.BufferedOutputStream 14 | import java.io.File 15 | import java.io.FileOutputStream 16 | import java.net.URL 17 | import java.util.zip.ZipInputStream 18 | import kotlin.io.path.div 19 | 20 | class PhpStanNewTagsTest : PhpCodeInsightFixtureTestCase() { 21 | 22 | private lateinit var phpstanFolder: File 23 | override fun getFixtureTestDataFolder(): String { 24 | return "newTags" 25 | } 26 | 27 | override fun getTestDataHome(): String { 28 | return PhpStanDocParserTest.TEST_DATA_HOME 29 | } 30 | 31 | 32 | override fun setUp() { 33 | super.setUp() 34 | phpstanFolder = File(PathManager.getHomePath() + "/" + basePath) 35 | FileUtil.createDirectory(phpstanFolder) 36 | val zipUrl = URL("https://github.com/phpstan/phpstan-src/archive/refs/heads/1.10.x.zip") 37 | downloadAndUnpackZip(zipUrl, phpstanFolder) 38 | } 39 | 40 | fun testNewTags() { 41 | myFixture.copyFileToProject("phpstan-src-1.10.x/src/PhpDoc/PhpDocNodeResolver.php") 42 | val classWithAnnotations = PhpIndex.getInstance(project).getClassesByFQN("PHPStan\\PhpDoc\\PhpDocNodeResolver").first() 43 | val annotations = mutableListOf() 44 | classWithAnnotations.methods.forEach { method -> 45 | method.lastChild.childrenOfType().forEach{ forEach -> 46 | if(forEach.array is ArrayCreationExpressionImpl){ 47 | (forEach.array as ArrayCreationExpressionImpl).values().forEach { 48 | annotations.add(it.text) 49 | } 50 | } 51 | } 52 | } 53 | val resultFileName = "phpstan-tags.txt" 54 | val resultFile = (phpstanFolder.toPath() / resultFileName).toFile() 55 | resultFile.writeText(annotations.sorted().joinToString("\n")) 56 | println("##teamcity[publishArtifacts '${resultFile.absolutePath}']") 57 | val pathToPreviousResults = (phpstanFolder.toPath() / "previousResults" / resultFileName).toFile() 58 | if(pathToPreviousResults.exists()){ 59 | UsefulTestCase.assertSameElements(annotations, pathToPreviousResults.readLines()) 60 | } else { 61 | println("Previous results are not found at ${pathToPreviousResults.absolutePath}") 62 | } 63 | } 64 | private fun downloadAndUnpackZip(zipUrl: URL, destinationDirectory: File) { 65 | val zipConnection = zipUrl.openConnection() 66 | val zipInputStream = ZipInputStream(BufferedInputStream(zipConnection.getInputStream())) 67 | var zipEntry = zipInputStream.nextEntry 68 | while (zipEntry != null) { 69 | val file = File(destinationDirectory, zipEntry.name) 70 | if (zipEntry.isDirectory) { 71 | file.mkdirs() 72 | } 73 | else { 74 | val fileOutputStream = FileOutputStream(file) 75 | val bufferedOutputStream = BufferedOutputStream(fileOutputStream, 2048) 76 | val data = ByteArray(2048) 77 | var count: Int 78 | while (zipInputStream.read(data, 0, data.size).also { count = it } != -1) { 79 | bufferedOutputStream.write(data, 0, count) 80 | } 81 | bufferedOutputStream.flush() 82 | bufferedOutputStream.close() 83 | } 84 | zipInputStream.closeEntry() 85 | zipEntry = zipInputStream.nextEntry 86 | } 87 | zipInputStream.close() 88 | } 89 | } 90 | 91 | -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanAnnotatorProxy.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.intellij.codeInspection.InspectionProfile; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.roots.ProjectRootManager; 6 | import com.intellij.openapi.vfs.VirtualFile; 7 | import com.intellij.psi.PsiFile; 8 | import com.intellij.util.SmartList; 9 | import com.jetbrains.php.tools.quality.QualityToolAnnotator; 10 | import com.jetbrains.php.tools.quality.QualityToolAnnotatorInfo; 11 | import com.jetbrains.php.tools.quality.QualityToolConfiguration; 12 | import com.jetbrains.php.tools.quality.QualityToolMessageProcessor; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | import java.util.List; 17 | 18 | import static com.intellij.openapi.util.text.StringUtil.isNotEmpty; 19 | import static com.intellij.util.containers.ContainerUtil.*; 20 | import static java.util.Collections.singletonList; 21 | 22 | public final class PhpStanAnnotatorProxy extends QualityToolAnnotator { 23 | public static final PhpStanAnnotatorProxy INSTANCE = new PhpStanAnnotatorProxy(); 24 | 25 | @Override 26 | protected List getOptions(@Nullable String filePath, @NotNull PhpStanValidationInspection inspection, 27 | @Nullable InspectionProfile profile, @NotNull Project project) { 28 | return emptyList(); 29 | } 30 | 31 | @Override 32 | protected @Nullable List getOptions(@Nullable String filePath, 33 | @NotNull PhpStanValidationInspection inspection, 34 | @Nullable InspectionProfile profile, 35 | @NotNull Project project, 36 | boolean isOnTheFly) { 37 | final PhpStanGlobalInspection tool = (PhpStanGlobalInspection)getQualityToolType().getGlobalTool(project, profile); 38 | if (tool == null) { 39 | return emptyList(); 40 | } 41 | 42 | if (isOnTheFly) { 43 | return tool.getCommandLineOptions(singletonList(filePath), project); 44 | } 45 | PhpStanOptionsConfiguration configuration = PhpStanOptionsConfiguration.getInstance(project); 46 | return tool.getCommandLineOptions(configuration.isFullProject() 47 | ? new SmartList<>(filePath, project.getBasePath()) 48 | : isNotEmpty(configuration.getConfig()) ? emptyList() : concat(map( 49 | ProjectRootManager.getInstance(project).getContentSourceRoots(), 50 | VirtualFile::getPath)), project); 51 | } 52 | 53 | @Override 54 | protected QualityToolMessageProcessor createMessageProcessor(@NotNull QualityToolAnnotatorInfo collectedInfo) { 55 | return new PhpStanMessageProcessor(collectedInfo); 56 | } 57 | 58 | @Override 59 | protected @NotNull QualityToolAnnotatorInfo createAnnotatorInfo(@Nullable PsiFile file, 60 | PhpStanValidationInspection tool, 61 | InspectionProfile inspectionProfile, 62 | Project project, 63 | QualityToolConfiguration configuration, 64 | boolean isOnTheFly) { 65 | return new PhpStanQualityToolAnnotatorInfo(file, tool, inspectionProfile, project, configuration, isOnTheFly); 66 | } 67 | 68 | @Override 69 | protected @NotNull PhpStanQualityToolType getQualityToolType() { 70 | return PhpStanQualityToolType.INSTANCE; 71 | } 72 | 73 | @Override 74 | public String getPairedBatchInspectionShortName() { 75 | return getQualityToolType().getInspectionId(); 76 | } 77 | 78 | @Override 79 | protected boolean showMessage(@NotNull String message) { 80 | return !message.contains("The Xdebug PHP extension is active, but \"--xdebug\" is not used"); 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/remote/PhpStanRemoteConfigurationProvider.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan.remote; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.util.text.StringUtil; 5 | import com.intellij.util.NullableFunction; 6 | import com.intellij.util.xmlb.XmlSerializer; 7 | import com.jetbrains.php.config.interpreters.PhpInterpreter; 8 | import com.jetbrains.php.config.interpreters.PhpInterpretersManagerImpl; 9 | import com.jetbrains.php.config.interpreters.PhpSdkAdditionalData; 10 | import com.jetbrains.php.remote.interpreter.PhpRemoteSdkAdditionalData; 11 | import com.jetbrains.php.remote.tools.quality.QualityToolByInterpreterConfigurableForm; 12 | import com.jetbrains.php.remote.tools.quality.QualityToolByInterpreterDialog; 13 | import com.jetbrains.php.tools.quality.QualityToolConfigurableForm; 14 | import com.jetbrains.php.tools.quality.phpstan.*; 15 | import org.jdom.Element; 16 | import org.jetbrains.annotations.NonNls; 17 | import org.jetbrains.annotations.NotNull; 18 | import org.jetbrains.annotations.Nullable; 19 | 20 | import java.util.List; 21 | 22 | import static com.intellij.openapi.util.text.StringUtil.isNotEmpty; 23 | import static com.jetbrains.php.remote.tools.quality.QualityToolByInterpreterDialog.getLocalOrDefaultInterpreterConfiguration; 24 | import static com.jetbrains.php.tools.quality.phpstan.PhpStanConfigurationBaseManager.PHP_STAN; 25 | 26 | public class PhpStanRemoteConfigurationProvider extends PhpStanConfigurationProvider { 27 | 28 | private static final @NonNls String PHPSTAN_BY_INTERPRETER = "phpstan_by_interpreter"; 29 | 30 | @Override 31 | public boolean canLoad(@NotNull String tagName) { 32 | return StringUtil.equals(tagName, PHPSTAN_BY_INTERPRETER); 33 | } 34 | 35 | @Override 36 | public @Nullable PhpStanConfiguration load(@NotNull Element element) { 37 | return XmlSerializer.deserialize(element, PhpStanRemoteConfiguration.class); 38 | } 39 | 40 | @Override 41 | public @Nullable QualityToolConfigurableForm createConfigurationForm(@NotNull Project project, 42 | @NotNull PhpStanConfiguration settings) { 43 | if (settings instanceof PhpStanRemoteConfiguration remoteConfiguration) { 44 | final PhpStanConfigurableForm delegate = 45 | new PhpStanConfigurableForm<>(project, remoteConfiguration); 46 | return new QualityToolByInterpreterConfigurableForm<>(project, remoteConfiguration, delegate); 47 | } 48 | return null; 49 | } 50 | 51 | @Override 52 | public PhpStanConfiguration createNewInstance(@Nullable Project project, @NotNull List existingSettings) { 53 | var dialog = 54 | new QualityToolByInterpreterDialog<>(project, existingSettings, PHP_STAN, PhpStanConfiguration.class, PhpStanQualityToolType.INSTANCE); 55 | if (dialog.showAndGet()) { 56 | final String id = PhpInterpretersManagerImpl.getInstance(project).findInterpreterId(dialog.getSelectedInterpreterName()); 57 | if (isNotEmpty(id)) { 58 | final PhpStanRemoteConfiguration settings = new PhpStanRemoteConfiguration(); 59 | settings.setInterpreterId(id); 60 | 61 | final PhpSdkAdditionalData data = PhpInterpretersManagerImpl.getInstance(project).findInterpreterDataById(id); 62 | fillDefaultSettings(project, settings, PhpStanConfigurationManager.getInstance(project).getOrCreateLocalSettings(), data, data instanceof PhpRemoteSdkAdditionalData); 63 | 64 | return settings; 65 | } 66 | return (PhpStanConfiguration)getLocalOrDefaultInterpreterConfiguration(dialog.getSelectedInterpreterName(), project, PhpStanQualityToolType.INSTANCE); 67 | } 68 | return null; 69 | } 70 | 71 | @Override 72 | public PhpStanConfiguration createConfigurationByInterpreter(@NotNull PhpInterpreter interpreter) { 73 | final PhpStanRemoteConfiguration settings = new PhpStanRemoteConfiguration(); 74 | settings.setInterpreterId(interpreter.getId()); 75 | return settings; 76 | } 77 | 78 | @Override 79 | protected void fillSettingsByDefaultValue(@NotNull PhpStanConfiguration settings, 80 | @NotNull PhpStanConfiguration localConfiguration, 81 | @NotNull NullableFunction preparePath) { 82 | super.fillSettingsByDefaultValue(settings, localConfiguration, preparePath); 83 | settings.setTimeout(60000); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanGlobalInspection.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.intellij.codeInspection.*; 4 | import com.intellij.codeInspection.ex.ExternalAnnotatorBatchInspection; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.openapi.util.Key; 7 | import com.intellij.openapi.util.NlsSafe; 8 | import com.intellij.psi.PsiFile; 9 | import com.intellij.util.containers.ContainerUtil; 10 | import com.jetbrains.php.tools.quality.QualityToolAnnotatorInfo; 11 | import com.jetbrains.php.tools.quality.QualityToolValidationGlobalInspection; 12 | import com.jetbrains.php.tools.quality.QualityToolXmlMessageProcessor; 13 | import org.jetbrains.annotations.NonNls; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.jetbrains.annotations.Nullable; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.Objects; 20 | 21 | import static com.intellij.openapi.util.text.StringUtil.isNotEmpty; 22 | import static com.jetbrains.php.tools.quality.QualityToolAnnotator.updateIfRemoteMappingExists; 23 | 24 | public class PhpStanGlobalInspection extends QualityToolValidationGlobalInspection implements ExternalAnnotatorBatchInspection { 25 | public boolean FULL_PROJECT = false; 26 | public @NonNls String memoryLimit = "2G"; 27 | public int level = 4; 28 | public @NlsSafe String config = ""; 29 | public @NlsSafe String autoload = ""; 30 | public static final Key> PHPSTAN_ANNOTATOR_INFO = Key.create("ANNOTATOR_INFO_2"); 31 | 32 | @Override 33 | public void inspectionStarted(@NotNull InspectionManager manager, 34 | @NotNull GlobalInspectionContext globalContext, 35 | @NotNull ProblemDescriptionsProcessor problemDescriptionsProcessor) { 36 | super.inspectionStarted(manager, globalContext, problemDescriptionsProcessor); 37 | final PhpStanAnnotatorProxy annotator = getAnnotator(); 38 | final QualityToolAnnotatorInfo info = 39 | annotator.collectAnnotatorInfo(null, null, globalContext.getProject(), ((InspectionManagerBase)manager).getCurrentProfile(), false); 40 | if (info != null) { 41 | manager.getProject().putUserData(ANNOTATOR_INFO, annotator.doAnnotate(info)); 42 | } 43 | } 44 | 45 | @Override 46 | public @Nullable LocalInspectionTool getSharedLocalInspectionTool() { 47 | return new PhpStanValidationInspection(); 48 | } 49 | 50 | @Override 51 | protected @NotNull PhpStanAnnotatorProxy getAnnotator() { 52 | return PhpStanAnnotatorProxy.INSTANCE; 53 | } 54 | 55 | @Override 56 | protected Key> getKey() { 57 | return PHPSTAN_ANNOTATOR_INFO; 58 | } 59 | 60 | public List getCommandLineOptions(@NotNull List filePath, @NotNull Project project) { 61 | @NonNls ArrayList options = new ArrayList<>(); 62 | PhpStanOptionsConfiguration configuration = PhpStanOptionsConfiguration.getInstance(project); 63 | options.add("analyze"); 64 | if (isNotEmpty(configuration.getConfig())) { 65 | options.add("-c"); 66 | options.add(updateIfRemoteMappingExists(configuration.getConfig(), project, PhpStanQualityToolType.INSTANCE)); 67 | } 68 | else { 69 | options.add("--level=" + configuration.getLevel()); 70 | } 71 | if (isNotEmpty(configuration.getAutoload())) { 72 | options.add("-a"); 73 | options.add(updateIfRemoteMappingExists(configuration.getAutoload(), project, PhpStanQualityToolType.INSTANCE)); 74 | } 75 | options.add("--memory-limit=" + configuration.getMemoryLimit()); 76 | options.add("--error-format=checkstyle"); 77 | options.add("--no-progress"); 78 | options.add("--no-ansi"); 79 | options.add("--no-interaction"); 80 | List filePaths = ContainerUtil.filter(filePath, Objects::nonNull); 81 | filePaths = ContainerUtil.map(filePaths, it -> updateIfRemoteMappingExists(it, project, PhpStanQualityToolType.INSTANCE)); 82 | options.addAll(filePaths); 83 | return options; 84 | } 85 | 86 | @Override 87 | public ProblemDescriptor @NotNull [] checkFile(@NotNull PsiFile file, 88 | @NotNull GlobalInspectionContext context, 89 | @NotNull InspectionManager manager) { 90 | ProblemsHolder holder = new ProblemsHolder(manager, file, false); 91 | super.checkFile(file, manager, holder, context, null); 92 | return holder.getResultsArray(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanOptionsPanel.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.util.text.StringUtil; 5 | import com.intellij.ui.DocumentAdapter; 6 | import com.intellij.ui.JBIntSpinner; 7 | import com.intellij.ui.components.JBCheckBox; 8 | import com.intellij.ui.components.JBTextField; 9 | import com.jetbrains.php.config.interpreters.PhpInterpreter; 10 | import com.jetbrains.php.config.interpreters.PhpTextFieldWithSdkBasedBrowse; 11 | import com.jetbrains.php.tools.quality.QualityToolConfigurationComboBox; 12 | import com.jetbrains.php.tools.quality.QualityToolsOptionsPanel; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | import javax.swing.*; 17 | import javax.swing.event.DocumentEvent; 18 | import java.net.URL; 19 | 20 | import static com.intellij.openapi.vfs.VfsUtil.findFileByURL; 21 | import static com.intellij.openapi.vfs.VfsUtilCore.convertToURL; 22 | import static com.intellij.openapi.vfs.VfsUtilCore.pathToUrl; 23 | 24 | public class PhpStanOptionsPanel extends QualityToolsOptionsPanel { 25 | private JPanel myOptionsPanel; 26 | private JBCheckBox myFullProjectRunJBCheckBox; 27 | private JBTextField myMemoryLimitTextField; 28 | private JBIntSpinner myJBIntSpinner; 29 | private PhpTextFieldWithSdkBasedBrowse myConfigPathTextField; 30 | private PhpTextFieldWithSdkBasedBrowse myAutoloadPathTextField; 31 | private final QualityToolConfigurationComboBox myComboBox; 32 | 33 | public PhpStanOptionsPanel(Project project, 34 | QualityToolConfigurationComboBox comboBox, 35 | Runnable validate) { 36 | super(project, validate, PhpStanQualityToolType.INSTANCE); 37 | myComboBox = comboBox; 38 | PhpStanOptionsConfiguration configuration = PhpStanOptionsConfiguration.getInstance(project); 39 | myFullProjectRunJBCheckBox.setSelected(configuration.isFullProject()); 40 | myMemoryLimitTextField.setText(configuration.getMemoryLimit()); 41 | myJBIntSpinner.setNumber(configuration.getLevel()); 42 | myConfigPathTextField.setText(configuration.getConfig()); 43 | myConfigPathTextField 44 | .init(project, getSdkAdditionalData(project, comboBox), PhpStanBundle.message("phpstan.configuration.file"), true, false); 45 | myAutoloadPathTextField.setText(configuration.getAutoload()); 46 | myAutoloadPathTextField 47 | .init(project, getSdkAdditionalData(project, comboBox), PhpStanBundle.message("phpstan.autoload.file"), true, false); 48 | myConfigPathTextField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { 49 | @Override 50 | protected void textChanged(@NotNull DocumentEvent e) { 51 | validate.run(); 52 | } 53 | }); 54 | } 55 | 56 | private void createUIComponents() { 57 | myJBIntSpinner = new JBIntSpinner(4, 0, 8); 58 | } 59 | 60 | @Override 61 | public JPanel getOptionsPanel() { 62 | return myOptionsPanel; 63 | } 64 | 65 | @Override 66 | public void reset() { 67 | PhpStanOptionsConfiguration configuration = PhpStanOptionsConfiguration.getInstance(myProject); 68 | myFullProjectRunJBCheckBox.setSelected(configuration.isFullProject()); 69 | myMemoryLimitTextField.setText(configuration.getMemoryLimit()); 70 | myJBIntSpinner.setNumber(configuration.getLevel()); 71 | myConfigPathTextField.setText(configuration.getConfig()); 72 | myAutoloadPathTextField.setText(configuration.getAutoload()); 73 | } 74 | 75 | @Override 76 | public boolean isModified() { 77 | PhpStanOptionsConfiguration configuration = PhpStanOptionsConfiguration.getInstance(myProject); 78 | if (myFullProjectRunJBCheckBox.isSelected() != configuration.isFullProject()) return true; 79 | if (!StringUtil.equals(myMemoryLimitTextField.getText(), configuration.getMemoryLimit())) return true; 80 | if (myJBIntSpinner.getNumber() != configuration.getLevel()) return true; 81 | if (!StringUtil.equals(myConfigPathTextField.getText(), configuration.getConfig())) return true; 82 | if (!StringUtil.equals(myAutoloadPathTextField.getText(), configuration.getAutoload())) return true; 83 | return false; 84 | } 85 | 86 | @Override 87 | public void apply() { 88 | PhpStanOptionsConfiguration configuration = PhpStanOptionsConfiguration.getInstance(myProject); 89 | configuration.setFullProject(myFullProjectRunJBCheckBox.isSelected()); 90 | configuration.setMemoryLimit(myMemoryLimitTextField.getText()); 91 | configuration.setLevel(myJBIntSpinner.getNumber()); 92 | configuration.setConfig(myConfigPathTextField.getText()); 93 | configuration.setAutoload(myAutoloadPathTextField.getText()); 94 | } 95 | 96 | @Override 97 | protected @Nullable String validatePath() { 98 | PhpInterpreter interpreter = getSelectedInterpreter(myProject, myComboBox); 99 | if (interpreter != null && interpreter.isRemote()) { 100 | //TODO: validate remote path? 101 | return null; 102 | } 103 | final URL url = convertToURL(pathToUrl(myConfigPathTextField.getText())); 104 | if (url == null || findFileByURL(url) == null) { 105 | return PhpStanBundle.message("config.file.doesnt.exist"); 106 | } 107 | return null; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /testData/parser/DocTags.txt: -------------------------------------------------------------------------------- 1 | 39 | PsiWhiteSpace('\n ') 40 | PsiElement(DOC_LEADING_ASTERISK)('*') 41 | PsiWhiteSpace(' ') 42 | PhpDocReturnTagImpl: @phpstan-return 43 | PsiElement(DOC_TAG_NAME)('@phpstan-return') 44 | PsiWhiteSpace(' ') 45 | PhpDocTypeImpl: string 46 | PsiElement(DOC_IDENTIFIER)('string') 47 | PhpPsiElementImpl 48 | 49 | PsiWhiteSpace('\n ') 50 | PsiElement(DOC_LEADING_ASTERISK)('*') 51 | PsiWhiteSpace(' ') 52 | PhpDocThrowsTagImpl: @phpstan-throws 53 | PsiElement(DOC_TAG_NAME)('@phpstan-throws') 54 | PsiWhiteSpace(' ') 55 | PhpDocTypeImpl: A 56 | PsiElement(DOC_IDENTIFIER)('A') 57 | PhpPsiElementImpl 58 | 59 | PsiWhiteSpace('\n ') 60 | PsiElement(DOC_LEADING_ASTERISK)('*') 61 | PsiWhiteSpace(' ') 62 | PhpDocTagImpl: @phpstan-use 63 | PsiElement(DOC_TAG_NAME)('@phpstan-use') 64 | PsiWhiteSpace(' ') 65 | PhpDocTypeImpl: A 66 | PsiElement(DOC_IDENTIFIER)('A') 67 | PhpPsiElementImpl 68 | 69 | PsiWhiteSpace('\n ') 70 | PsiElement(DOC_LEADING_ASTERISK)('*') 71 | PsiWhiteSpace(' ') 72 | PhpDocTagImpl: @phpstan-implements 73 | PsiElement(DOC_TAG_NAME)('@phpstan-implements') 74 | PsiWhiteSpace(' ') 75 | PhpDocTypeImpl: A 76 | PsiElement(DOC_IDENTIFIER)('A') 77 | PhpPsiElementImpl 78 | 79 | PsiWhiteSpace('\n ') 80 | PsiElement(DOC_LEADING_ASTERISK)('*') 81 | PsiWhiteSpace(' ') 82 | PhpDocTagImpl: @phpstan-extends 83 | PsiElement(DOC_TAG_NAME)('@phpstan-extends') 84 | PsiWhiteSpace(' ') 85 | PhpDocTypeImpl: A 86 | PsiElement(DOC_IDENTIFIER)('A') 87 | PhpPsiElementImpl 88 | 89 | PsiWhiteSpace('\n ') 90 | PsiElement(DOC_LEADING_ASTERISK)('*') 91 | PsiWhiteSpace(' ') 92 | PhpDocParamTagImpl: @phpstan-param-out 93 | PsiElement(DOC_TAG_NAME)('@phpstan-param-out') 94 | PsiWhiteSpace(' ') 95 | PhpDocTypeImpl: int 96 | PsiElement(DOC_IDENTIFIER)('int') 97 | PsiWhiteSpace(' ') 98 | PhpDocVarImpl: a 99 | PsiElement(DOC_VARIABLE)('$a') 100 | PhpPsiElementImpl 101 | 102 | PsiWhiteSpace('\n ') 103 | PsiElement(DOC_LEADING_ASTERISK)('*') 104 | PsiWhiteSpace(' ') 105 | PhpDocParamTagImpl: @param-out 106 | PsiElement(DOC_TAG_NAME)('@param-out') 107 | PsiWhiteSpace(' ') 108 | PhpDocTypeImpl: string 109 | PsiElement(DOC_IDENTIFIER)('string') 110 | PsiWhiteSpace(' ') 111 | PhpDocVarImpl: s 112 | PsiElement(DOC_VARIABLE)('$s') 113 | PhpPsiElementImpl 114 | 115 | PsiWhiteSpace('\n ') 116 | PsiElement(DOC_LEADING_ASTERISK)('*') 117 | PsiWhiteSpace(' ') 118 | PhpDocTagImpl: @phpstan-scope-this 119 | PsiElement(DOC_TAG_NAME)('@phpstan-scope-this') 120 | PsiWhiteSpace(' ') 121 | PhpDocTypeImpl: Exception 122 | PsiElement(DOC_IDENTIFIER)('Exception') 123 | PhpPsiElementImpl 124 | 125 | PsiWhiteSpace('\n ') 126 | PsiElement(DOC_LEADING_ASTERISK)('*') 127 | PsiWhiteSpace(' ') 128 | PhpDocParamTagImpl: @phpstan-param 129 | PsiElement(DOC_TAG_NAME)('@phpstan-param') 130 | PsiWhiteSpace(' ') 131 | PhpDocTypeImpl: %EMPTY% 132 | PsiElement(DOC_TEXT)('!') 133 | PsiElement(DOC_IDENTIFIER)('null') 134 | PsiWhiteSpace(' ') 135 | PhpDocVarImpl: c 136 | PsiElement(DOC_VARIABLE)('$c') 137 | PhpPsiElementImpl 138 | 139 | PsiWhiteSpace('\n ') 140 | PsiElement(DOC_COMMENT_END)('*/') 141 | PsiWhiteSpace('\n') 142 | FunctionImpl: a 143 | PsiElement(function)('function') 144 | PsiWhiteSpace(' ') 145 | PsiElement(identifier)('a') 146 | PsiElement(()('(') 147 | Parameter list 148 | 149 | PsiElement())(')') 150 | PsiWhiteSpace(' ') 151 | PsiElement(Group statement) 152 | PsiElement({)('{') 153 | PsiWhiteSpace('\n') 154 | PhpDocCommentImpl 155 | PsiElement(DOC_COMMENT_START)('/**') 156 | PsiWhiteSpace(' ') 157 | PhpDocParamTagImpl: @phpstan-var 158 | PsiElement(DOC_TAG_NAME)('@phpstan-var') 159 | PsiWhiteSpace(' ') 160 | PhpDocTypeImpl: int 161 | PsiElement(DOC_IDENTIFIER)('int') 162 | PsiWhiteSpace(' ') 163 | PhpDocVarImpl: a 164 | PsiElement(DOC_VARIABLE)('$a') 165 | PhpPsiElementImpl 166 | 167 | PsiWhiteSpace(' ') 168 | PsiElement(DOC_COMMENT_END)('*/') 169 | PsiWhiteSpace('\n') 170 | Echo 171 | PsiElement(echo)('echo') 172 | PsiWhiteSpace(' ') 173 | VariableImpl: %EMPTY% 174 | PsiElement(dollar)('$') 175 | PsiErrorElement:Expected: identifier 176 | 177 | PsiElement(semicolon)(';') 178 | PsiWhiteSpace('\n') 179 | PsiElement(})('}') -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanMessageProcessor.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.intellij.codeHighlighting.HighlightDisplayLevel; 4 | import com.intellij.openapi.application.ReadAction; 5 | import com.intellij.openapi.editor.Document; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.openapi.util.TextRange; 8 | import com.intellij.psi.PsiDocumentManager; 9 | import com.intellij.psi.PsiFile; 10 | import com.intellij.util.PathUtil; 11 | import com.jetbrains.php.tools.quality.QualityToolAnnotatorInfo; 12 | import com.jetbrains.php.tools.quality.QualityToolMessage; 13 | import com.jetbrains.php.tools.quality.QualityToolXmlMessageProcessor; 14 | import org.jetbrains.annotations.NonNls; 15 | import org.jetbrains.annotations.NotNull; 16 | import org.jetbrains.annotations.Nullable; 17 | import org.xml.sax.Attributes; 18 | import org.xml.sax.InputSource; 19 | import org.xml.sax.SAXException; 20 | 21 | import java.io.IOException; 22 | import java.util.ArrayList; 23 | import java.util.HashSet; 24 | import java.util.List; 25 | import java.util.Set; 26 | 27 | import static com.jetbrains.php.tools.quality.QualityToolMessage.Severity.ERROR; 28 | import static com.jetbrains.php.tools.quality.QualityToolMessage.Severity.WARNING; 29 | import static com.jetbrains.php.tools.quality.phpstan.PhpStanGlobalInspection.PHPSTAN_ANNOTATOR_INFO; 30 | 31 | public class PhpStanMessageProcessor extends QualityToolXmlMessageProcessor { 32 | private static final String ERROR_TAG = "error"; 33 | private static final String FILE_TAG = "file"; 34 | private static final @NonNls String WARNING_MESSAGE_START = " lineMessages = new HashSet<>(); 43 | private final HighlightDisplayLevel myWarningsHighlightLevel; 44 | final String myFilePath; 45 | final PsiFile myPsiFile; 46 | final Project myProject; 47 | 48 | protected PhpStanMessageProcessor(QualityToolAnnotatorInfo info) { 49 | super(info); 50 | myWarningsHighlightLevel = HighlightDisplayLevel.WARNING; // TODO: fix 51 | myFilePath = info.getTempFilePath(); 52 | myPsiFile = info.getPsiFile(); 53 | myProject = info.getProject(); 54 | } 55 | 56 | @Override 57 | protected boolean show(@NotNull String message) { 58 | return !message.contains("The Xdebug PHP extension is active, but \"--xdebug\" is not used"); 59 | } 60 | 61 | @Override 62 | protected void processMessage(InputSource source) throws SAXException, IOException { 63 | PhpStanXmlMessageHandler messageHandler = (PhpStanXmlMessageHandler)getXmlMessageHandler(myFilePath); 64 | mySAXParser.parse(source, messageHandler); 65 | if (messageHandler.isStatusValid()) { 66 | if (myPsiFile != null) { 67 | List list = messageHandler.getProblemList(); 68 | if (list == null) return; 69 | for (ProblemDescription problem : list) { 70 | if (myProject.isDisposed()) return; 71 | Document document = ReadAction.compute(() -> PsiDocumentManager.getInstance(myPsiFile.getProject()).getDocument(myPsiFile)); 72 | QualityToolMessage qualityToolMessage; 73 | if (document != null && problem.getLineNumber() - 1 > 0 && problem.getLineNumber() - 1 < document.getLineCount()) { 74 | qualityToolMessage = new QualityToolMessage(this, TextRange 75 | .create(document.getLineStartOffset(problem.getLineNumber() - 1) + problem.getColumn(), 76 | document.getLineEndOffset(problem.getLineNumber() - 1)), problem.getSeverity(), problem.getMessage()); 77 | } 78 | else { 79 | qualityToolMessage = new QualityToolMessage(this, problem.getLineNumber(), problem.getSeverity(), problem.getMessage()); 80 | } 81 | if (lineMessages.add(problem)) { 82 | addMessage(qualityToolMessage); 83 | } 84 | } 85 | } else { 86 | final List data = myProject.getUserData(PHPSTAN_ANNOTATOR_INFO); 87 | if (data != null) { 88 | data.addAll(messageHandler.getProblemList()); 89 | myProject.putUserData(PHPSTAN_ANNOTATOR_INFO, data); 90 | } 91 | else { 92 | myProject.putUserData(PHPSTAN_ANNOTATOR_INFO, messageHandler.getProblemList()); 93 | } 94 | } 95 | } 96 | } 97 | 98 | @Override 99 | protected XMLMessageHandler getXmlMessageHandler() { 100 | return null; 101 | } 102 | 103 | protected XMLMessageHandler getXmlMessageHandler(@Nullable String filePath) { 104 | return new PhpStanXmlMessageHandler(filePath); 105 | } 106 | 107 | @Override 108 | public int getMessageStart(@NotNull String line) { 109 | return line.indexOf(WARNING_MESSAGE_START); 110 | } 111 | 112 | @Override 113 | public int getMessageEnd(@NotNull String line) { 114 | return line.indexOf(WARNING_MESSAGE_END); 115 | } 116 | 117 | @Override 118 | protected @NonNls @Nullable String getMessagePrefix() { 119 | return "phpstan"; 120 | } 121 | 122 | @Override 123 | protected @Nullable HighlightDisplayLevel severityToDisplayLevel(@NotNull QualityToolMessage.Severity severity) { 124 | return WARNING.equals(severity) ? myWarningsHighlightLevel : null; 125 | } 126 | 127 | @Override 128 | protected PhpStanQualityToolType getQualityToolType() { 129 | return PhpStanQualityToolType.INSTANCE; 130 | } 131 | 132 | static final class PhpStanXmlMessageHandler extends XMLMessageHandler { 133 | 134 | private final String myFilePath; 135 | private String myFileAttr; 136 | 137 | private PhpStanXmlMessageHandler(@Nullable String filePath) { 138 | myFilePath = filePath; 139 | } 140 | 141 | private List myProblemList; 142 | 143 | private List getProblemList() { 144 | return myProblemList; 145 | } 146 | 147 | @Override 148 | protected void parseTag(@NotNull String tagName, @NotNull Attributes attributes) { 149 | if (FILE_TAG.equals(tagName)) { 150 | myFileAttr = PathUtil.toSystemIndependentName(attributes.getValue(FILE_NAME_ATTR)); 151 | myProblemList = myFilePath == null || myFilePath.endsWith(myFileAttr == null ? "": myFileAttr) ? new ArrayList<>() : null; 152 | } 153 | else if (ERROR_TAG.equals(tagName) || WARNING_TAG.equals(tagName)) { 154 | if (myProblemList != null) { 155 | mySeverity = attributes.getValue(SEVERITY_ATTR).equals(ERROR_TAG) ? ERROR : WARNING; 156 | myLineNumber = parseLineNumber(attributes.getValue(LINE_NUMBER_ATTR)); 157 | int column = parseLineNumber(attributes.getValue(COLUMN_NUMBER_ATTR)); 158 | myProblemList 159 | .add(new ProblemDescription(mySeverity, myLineNumber, Math.max(0, column - 1), attributes.getValue(MESSAGE_ATTR), myFileAttr)); 160 | } 161 | } 162 | } 163 | } 164 | } -------------------------------------------------------------------------------- /testData/parser/DocMethodsTags.txt: -------------------------------------------------------------------------------- 1 | 37 | PsiElement())(')') 38 | PsiWhiteSpace(' ') 39 | PhpDocMethodImpl: borp 40 | PsiElement(identifier)('borp') 41 | PsiElement(()('(') 42 | Parameter list 43 | ParameterImpl: int1 44 | PhpPsiElementImpl 45 | ClassReferenceImpl: int 46 | PsiElement(identifier)('int') 47 | PsiWhiteSpace(' ') 48 | PsiElement(variable)('$int1') 49 | PsiElement(comma)(',') 50 | PsiWhiteSpace(' ') 51 | ParameterImpl: int2 52 | PhpPsiElementImpl 53 | ClassReferenceImpl: int 54 | PsiElement(identifier)('int') 55 | PsiElement([)('[') 56 | PsiElement(])(']') 57 | PsiWhiteSpace(' ') 58 | PsiElement(variable)('$int2') 59 | PsiElement())(')') 60 | PsiWhiteSpace(' ') 61 | PhpPsiElementImpl 62 | PsiElement(identifier)('multiply') 63 | PsiWhiteSpace(' ') 64 | PsiElement(identifier)('two') 65 | PsiWhiteSpace(' ') 66 | PsiElement(identifier)('integers') 67 | PsiWhiteSpace('\n ') 68 | PsiElement(DOC_LEADING_ASTERISK)('*') 69 | PsiWhiteSpace(' ') 70 | PhpDocMethodTagImpl: method 71 | PsiElement(error silence)('@') 72 | PsiElement(identifier)('method') 73 | PsiWhiteSpace(' ') 74 | PhpPsiElementImpl 75 | ClassReferenceImpl: int 76 | PsiElement(identifier)('int') 77 | PsiElement(bit or)('|') 78 | PhpPsiElementImpl 79 | ClassReferenceImpl: array 80 | PsiElement(array)('array') 81 | PsiWhiteSpace(' ') 82 | PsiElement(identifier)('borp') 83 | PsiElement(()('(') 84 | Parameter list 85 | 86 | PsiElement())(')') 87 | PsiWhiteSpace(' ') 88 | PhpDocMethodImpl: borp 89 | PsiElement(identifier)('borp') 90 | PsiElement(()('(') 91 | Parameter list 92 | ParameterImpl: int1 93 | PhpPsiElementImpl 94 | ClassReferenceImpl: int 95 | PsiElement(identifier)('int') 96 | PsiWhiteSpace(' ') 97 | PsiElement(variable)('$int1') 98 | PsiElement(comma)(',') 99 | PsiWhiteSpace(' ') 100 | ParameterImpl: int2 101 | PhpPsiElementImpl 102 | ClassReferenceImpl: int 103 | PsiElement(identifier)('int') 104 | PsiElement([)('[') 105 | PsiElement(])(']') 106 | PsiWhiteSpace(' ') 107 | PsiElement(variable)('$int2') 108 | PsiElement())(')') 109 | PsiWhiteSpace(' ') 110 | PhpPsiElementImpl 111 | PsiElement(identifier)('multiply') 112 | PsiWhiteSpace(' ') 113 | PsiElement(identifier)('two') 114 | PsiWhiteSpace(' ') 115 | PsiElement(identifier)('integers') 116 | PsiWhiteSpace('\n ') 117 | PsiElement(DOC_LEADING_ASTERISK)('*') 118 | PsiWhiteSpace(' ') 119 | PhpDocTagImpl: @phpstan-methodWrong 120 | PsiElement(DOC_TAG_NAME)('@phpstan-methodWrong') 121 | PsiWhiteSpace(' ') 122 | PhpPsiElementImpl 123 | PsiElement(DOC_IDENTIFIER)('int') 124 | PsiElement(DOC_PIPE)('|') 125 | PsiElement(DOC_IDENTIFIER)('array') 126 | PsiWhiteSpace(' ') 127 | PsiElement(DOC_IDENTIFIER)('borp') 128 | PsiElement(DOC_LPAREN)('(') 129 | PsiElement(DOC_RPAREN)(')') 130 | PsiWhiteSpace(' ') 131 | PsiElement(DOC_IDENTIFIER)('borp') 132 | PsiElement(DOC_LPAREN)('(') 133 | PsiElement(DOC_IDENTIFIER)('int') 134 | PsiWhiteSpace(' ') 135 | PsiElement(DOC_VARIABLE)('$int1') 136 | PsiElement(DOC_COMMA)(',') 137 | PsiWhiteSpace(' ') 138 | PsiElement(DOC_IDENTIFIER)('int') 139 | PsiElement(DOC_LBRACKET)('[') 140 | PsiElement(DOC_RBRACKET)(']') 141 | PsiWhiteSpace(' ') 142 | PsiElement(DOC_VARIABLE)('$int2') 143 | PsiElement(DOC_RPAREN)(')') 144 | PsiWhiteSpace(' ') 145 | PsiElement(DOC_IDENTIFIER)('multiply') 146 | PsiWhiteSpace(' ') 147 | PsiElement(DOC_IDENTIFIER)('two') 148 | PsiWhiteSpace(' ') 149 | PsiElement(DOC_IDENTIFIER)('integers') 150 | PsiWhiteSpace('\n ') 151 | PsiElement(DOC_LEADING_ASTERISK)('*') 152 | PsiWhiteSpace(' ') 153 | PhpDocTagImpl: @wrong-method 154 | PsiElement(DOC_TAG_NAME)('@wrong-method') 155 | PsiWhiteSpace(' ') 156 | PhpPsiElementImpl 157 | PsiElement(DOC_IDENTIFIER)('int') 158 | PsiElement(DOC_LBRACKET)('[') 159 | PsiElement(DOC_RBRACKET)(']') 160 | PsiWhiteSpace(' ') 161 | PsiElement(DOC_IDENTIFIER)('borp') 162 | PsiElement(DOC_LPAREN)('(') 163 | PsiElement(DOC_IDENTIFIER)('int') 164 | PsiElement(DOC_PIPE)('|') 165 | PsiElement(DOC_IDENTIFIER)('string') 166 | PsiWhiteSpace(' ') 167 | PsiElement(DOC_VARIABLE)('$int1') 168 | PsiElement(DOC_COMMA)(',') 169 | PsiWhiteSpace(' ') 170 | PsiElement(DOC_IDENTIFIER)('int') 171 | PsiWhiteSpace(' ') 172 | PsiElement(DOC_VARIABLE)('$int2') 173 | PsiElement(DOC_RPAREN)(')') 174 | PsiWhiteSpace(' ') 175 | PsiElement(DOC_IDENTIFIER)('multiply') 176 | PsiWhiteSpace(' ') 177 | PsiElement(DOC_IDENTIFIER)('two') 178 | PsiWhiteSpace(' ') 179 | PsiElement(DOC_IDENTIFIER)('integers') 180 | PsiWhiteSpace('\n ') 181 | PsiElement(DOC_COMMENT_END)('*/') 182 | PsiWhiteSpace('\n') 183 | PhpClass: Cart 184 | PsiElement(class)('class') 185 | PsiWhiteSpace(' ') 186 | PsiElement(identifier)('Cart') 187 | PsiWhiteSpace(' ') 188 | Extends list 189 | 190 | Implements list 191 | 192 | PsiElement({)('{') 193 | PsiWhiteSpace('\n') 194 | PsiElement(})('}') -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanComposerConfig.java: -------------------------------------------------------------------------------- 1 | package com.jetbrains.php.tools.quality.phpstan; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonObject; 5 | import com.google.gson.JsonParseException; 6 | import com.intellij.openapi.application.ReadAction; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.util.Key; 9 | import com.intellij.openapi.util.NlsSafe; 10 | import com.intellij.openapi.util.Ref; 11 | import com.intellij.openapi.util.SystemInfo; 12 | import com.intellij.openapi.util.text.StringUtil; 13 | import com.intellij.openapi.vfs.LocalFileSystem; 14 | import com.intellij.openapi.vfs.VirtualFile; 15 | import com.intellij.profile.codeInspection.InspectionProfileManager; 16 | import com.intellij.psi.PsiDirectory; 17 | import com.intellij.psi.PsiManager; 18 | import com.intellij.util.Consumer; 19 | import com.jetbrains.php.composer.ComposerDataService; 20 | import com.jetbrains.php.composer.actions.log.ComposerLogMessageBuilder; 21 | import com.jetbrains.php.tools.quality.QualityToolConfigurationManager; 22 | import com.jetbrains.php.tools.quality.QualityToolsComposerConfig; 23 | import org.jetbrains.annotations.NonNls; 24 | import org.jetbrains.annotations.NotNull; 25 | import org.jetbrains.annotations.Nullable; 26 | 27 | import java.io.IOException; 28 | import java.util.List; 29 | 30 | import static com.intellij.openapi.util.text.StringUtil.*; 31 | import static com.jetbrains.php.composer.ComposerConfigUtils.parseJson; 32 | import static com.jetbrains.php.tools.quality.phpstan.PhpStanOpenSettingsProvider.PHP_STAN_OPEN_SETTINGS_PROVIDER; 33 | 34 | public class PhpStanComposerConfig extends QualityToolsComposerConfig { 35 | private static final @NonNls String PACKAGE = "phpstan/phpstan"; 36 | private static final @NonNls String RELATIVE_PATH = "bin/phpstan" + (SystemInfo.isWindows ? ".bat" : ""); 37 | private static final @NonNls String PHPSTAN_NEON = "phpstan.neon"; 38 | 39 | public PhpStanComposerConfig() { 40 | super(PACKAGE, RELATIVE_PATH); 41 | } 42 | 43 | @Override 44 | public String getQualityInspectionShortName() { 45 | return PhpStanQualityToolType.INSTANCE.getInspectionId(); 46 | } 47 | 48 | @Override 49 | protected boolean applyRulesetFromComposer(@NotNull Project project, PhpStanConfiguration configuration) { 50 | final String configPath = ComposerDataService.getInstance(project).getConfigPath(); 51 | PhpStanOptionsConfiguration projectConfiguration = PhpStanOptionsConfiguration.getInstance(project); 52 | 53 | final VirtualFile config = LocalFileSystem.getInstance().refreshAndFindFileByPath(configPath); 54 | if (config == null) return false; 55 | 56 | final String ruleset = getRuleset(config); 57 | if (ruleset == null) return false; 58 | final VirtualFile customRulesetFile = detectCustomRulesetFile(config.getParent(), ruleset); 59 | final boolean customRulesetChanged = customRulesetFile != null && 60 | modifyRulesetPhpStanInspectionSetting(project, tool -> applyRuleset(projectConfiguration, 61 | customRulesetFile.getPath())); 62 | 63 | final String memoryLimit = getMemoryLimit(config); 64 | final boolean memoryLimitChanged = 65 | memoryLimit != null && modifyRulesetPhpStanInspectionSetting(project, tool -> applyMemoryLimit(projectConfiguration, memoryLimit)); 66 | 67 | return customRulesetChanged || memoryLimitChanged; 68 | } 69 | 70 | @Override 71 | protected void applyInspectionSettingsFromComposer(Project project, PhpStanConfiguration configuration) { 72 | final String configPath = ComposerDataService.getInstance(project).getConfigPath(); 73 | final VirtualFile config = LocalFileSystem.getInstance().refreshAndFindFileByPath(configPath); 74 | if (config == null) return; 75 | 76 | final String memoryLimit = getMemoryLimit(config); 77 | if (memoryLimit != null) { 78 | modifyRulesetPhpStanInspectionSetting(project, 79 | tool -> applyMemoryLimit(PhpStanOptionsConfiguration.getInstance(project), memoryLimit)); 80 | } 81 | } 82 | 83 | private @Nullable String getMemoryLimit(VirtualFile config) { 84 | JsonElement element; 85 | try { 86 | element = parseJson(config); 87 | } 88 | catch (IOException | JsonParseException e) { 89 | return null; 90 | } 91 | 92 | if (element instanceof JsonObject) { 93 | final JsonElement scriptElement = ((JsonObject)element).get("scripts"); 94 | if (scriptElement != null) { 95 | final Ref result = new Ref<>(); 96 | parse(scriptElement, result, (el, res) -> parseLimit(el, result)); 97 | return result.isNull() ? null : result.get(); 98 | } 99 | } 100 | return null; 101 | } 102 | 103 | private static void parseLimit(JsonElement el, Ref result) { 104 | final String string = el.getAsString(); 105 | if (string != null && string.contains("phpstan")) { 106 | final List split = split(string, " "); 107 | for (String arg: split) { 108 | final String prefix = "--memory-limit="; 109 | if (startsWith(arg, prefix)) { 110 | result.set(trimStart(arg, prefix)); 111 | return; 112 | } 113 | final int index = split.indexOf(arg); 114 | if (StringUtil.equals(arg, "--memory-limit") && index < split.size() - 1) { 115 | result.set(split.get(index + 1)); 116 | return; 117 | } 118 | } 119 | } 120 | } 121 | 122 | @Override 123 | protected boolean applyRulesetFromRoot(@NotNull Project project) { 124 | VirtualFile customRulesetFile = detectCustomRulesetFile(project.getBaseDir(), PHPSTAN_NEON); 125 | if(customRulesetFile == null){ 126 | customRulesetFile = detectCustomRulesetFile(project.getBaseDir(), PHPSTAN_NEON + ".dist"); 127 | } 128 | 129 | if (customRulesetFile != null) { 130 | final String path = customRulesetFile.getPath(); 131 | return modifyRulesetPhpStanInspectionSetting(project, tool -> applyRuleset(PhpStanOptionsConfiguration.getInstance(project), path)); 132 | } 133 | return false; 134 | } 135 | 136 | @Override 137 | protected void checkComposerScriptsLeaves(JsonElement element, Ref result) { 138 | final String string = element.getAsString(); 139 | if (string != null && string.contains("phpstan")) { 140 | final List split = split(string, " "); 141 | for (String arg: split) { 142 | final String prefix = "--configuration="; 143 | if (startsWith(arg, prefix)) { 144 | result.set(trimStart(arg, prefix)); 145 | return; 146 | } 147 | final int index = split.indexOf(arg); 148 | if (StringUtil.equals(arg, "-c") && index < split.size() - 1) { 149 | result.set(split.get(index + 1)); 150 | return; 151 | } 152 | } 153 | } 154 | } 155 | 156 | @Override 157 | public @Nullable ComposerLogMessageBuilder.Settings getSettings() { 158 | return PHP_STAN_OPEN_SETTINGS_PROVIDER; 159 | } 160 | 161 | private static void applyRuleset(PhpStanOptionsConfiguration configuration, @NlsSafe String customRuleset) { 162 | configuration.setConfig(customRuleset); 163 | } 164 | 165 | private static void applyMemoryLimit(PhpStanOptionsConfiguration configuration, @NlsSafe String memoryLimit) { 166 | configuration.setMemoryLimit(memoryLimit); 167 | } 168 | 169 | protected boolean modifyRulesetPhpStanInspectionSetting(@NotNull Project project, @NotNull Consumer consumer) { 170 | VirtualFile projectDir = project.getBaseDir(); 171 | if (projectDir == null) return false; 172 | 173 | PsiDirectory file = ReadAction.compute(() -> PsiManager.getInstance(project).findDirectory(projectDir)); 174 | if (file != null) { 175 | Key key = Key.create(PhpStanQualityToolType.INSTANCE.getInspectionId()); 176 | InspectionProfileManager.getInstance(project).getCurrentProfile().modifyToolSettings(key, file, consumer); 177 | return true; 178 | } 179 | return false; 180 | } 181 | 182 | @Override 183 | public @NotNull QualityToolConfigurationManager getConfigurationManager(@NotNull Project project) { 184 | return PhpStanConfigurationManager.getInstance(project); 185 | } 186 | } -------------------------------------------------------------------------------- /src/com/jetbrains/php/tools/quality/phpstan/PhpStanOptionsPanel.form: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 |
140 | -------------------------------------------------------------------------------- /testData/parser/Assert.txt: -------------------------------------------------------------------------------- 1 | 23 | * @phpstan-assert-if-false int $this->test() 24 | * @phpstan-assert-if-false int $b->test() 25 | * @phpstan-assert-if-true null $this->b 26 | * @template NewT 27 | * @phpstan-this-out self 28 | * @phpstan-self-out self 29 | */ 30 | public function test(B|A $b): void { 31 | 32 | } 33 | } 34 | --- 35 | PHP file 36 | PsiElement(Non Lazy Group statement) 37 | PsiElement(php opening tag)(' 56 | PsiWhiteSpace('\n ') 57 | PsiElement(DOC_LEADING_ASTERISK)('*') 58 | PsiWhiteSpace(' ') 59 | PhpDocTagImpl: @phpstan-assert-if-false 60 | PsiElement(DOC_TAG_NAME)('@phpstan-assert-if-false') 61 | PsiWhiteSpace(' ') 62 | PhpDocTypeImpl: B 63 | PsiElement(DOC_IDENTIFIER)('B') 64 | PsiWhiteSpace(' ') 65 | PhpDocVarImpl: a 66 | PsiElement(DOC_VARIABLE)('$a') 67 | PhpPsiElementImpl 68 | 69 | PsiWhiteSpace('\n ') 70 | PsiElement(DOC_LEADING_ASTERISK)('*') 71 | PsiWhiteSpace(' ') 72 | PhpDocTagImpl: @phpstan-assert-if-true 73 | PsiElement(DOC_TAG_NAME)('@phpstan-assert-if-true') 74 | PsiWhiteSpace(' ') 75 | PhpDocTypeImpl: A 76 | PsiElement(DOC_IDENTIFIER)('A') 77 | PsiWhiteSpace(' ') 78 | PhpDocVarImpl: b 79 | PsiElement(DOC_VARIABLE)('$b') 80 | PhpPsiElementImpl 81 | 82 | PsiWhiteSpace('\n ') 83 | PsiElement(DOC_COMMENT_END)('*/') 84 | PsiWhiteSpace('\n') 85 | FunctionImpl: a 86 | PsiElement(function)('function') 87 | PsiWhiteSpace(' ') 88 | PsiElement(identifier)('a') 89 | PsiElement(()('(') 90 | Parameter list 91 | ParameterImpl: arr 92 | Parameter type 93 | ClassReferenceImpl: array 94 | PsiElement(array)('array') 95 | PsiWhiteSpace(' ') 96 | PsiElement(variable)('$arr') 97 | PsiElement(comma)(',') 98 | PsiWhiteSpace(' ') 99 | ParameterImpl: arg 100 | Parameter type 101 | ClassReferenceImpl: array 102 | PsiElement(array)('array') 103 | PsiWhiteSpace(' ') 104 | PsiElement(variable)('$arg') 105 | PsiElement())(')') 106 | PsiWhiteSpace(' ') 107 | PsiElement(colon)(':') 108 | PsiWhiteSpace(' ') 109 | Return type 110 | ClassReferenceImpl: void 111 | PsiElement(identifier)('void') 112 | PsiWhiteSpace(' ') 113 | PsiElement(Group statement) 114 | PsiElement({)('{') 115 | PsiWhiteSpace('\n\n') 116 | PsiElement(})('}') 117 | PsiWhiteSpace('\n\n') 118 | PhpClass: B 119 | PsiElement(class)('class') 120 | PsiWhiteSpace(' ') 121 | PsiElement(identifier)('B') 122 | PsiWhiteSpace(' ') 123 | Extends list 124 | 125 | Implements list 126 | 127 | PsiElement({)('{') 128 | PsiWhiteSpace('\n ') 129 | MethodImpl: test 130 | PhpModifierListImpl: %EMPTY% 131 | 132 | PsiElement(function)('function') 133 | PsiWhiteSpace(' ') 134 | PsiElement(identifier)('test') 135 | PsiElement(()('(') 136 | Parameter list 137 | 138 | PsiElement())(')') 139 | PsiWhiteSpace(' ') 140 | PsiElement(Group statement) 141 | PsiElement({)('{') 142 | PsiWhiteSpace('\n\n ') 143 | PsiElement(})('}') 144 | PsiWhiteSpace('\n') 145 | PsiElement(})('}') 146 | PsiWhiteSpace('\n\n') 147 | PhpClass: a 148 | PsiElement(class)('class') 149 | PsiWhiteSpace(' ') 150 | PsiElement(identifier)('a') 151 | PsiWhiteSpace(' ') 152 | Extends list 153 | 154 | Implements list 155 | 156 | PsiElement({)('{') 157 | PsiWhiteSpace('\n ') 158 | PhpPsiElementImpl 159 | PhpModifierListImpl: public 160 | PsiElement(public keyword)('public') 161 | PsiWhiteSpace(' ') 162 | Field type 163 | ClassReferenceImpl: int 164 | PsiElement(identifier)('int') 165 | PsiWhiteSpace(' ') 166 | FieldImpl: b 167 | PsiElement(variable)('$b') 168 | PsiWhiteSpace(' ') 169 | PsiElement(assign)('=') 170 | PsiWhiteSpace(' ') 171 | PhpExpressionImpl: 0 172 | PsiElement(integer)('0') 173 | PsiElement(semicolon)(';') 174 | PsiWhiteSpace('\n\n ') 175 | PhpDocCommentImpl 176 | PsiElement(DOC_COMMENT_START)('/**') 177 | PsiWhiteSpace('\n ') 178 | PsiElement(DOC_LEADING_ASTERISK)('*') 179 | PsiWhiteSpace(' ') 180 | PhpDocTagImpl: @phpstan-if-this-is 181 | PsiElement(DOC_TAG_NAME)('@phpstan-if-this-is') 182 | PsiWhiteSpace(' ') 183 | PhpDocTypeImpl: a 184 | PsiElement(DOC_IDENTIFIER)('a') 185 | PhpPsiElementImpl 186 | PsiElement(DOC_LAB)('<') 187 | PhpDocTypeImpl: int 188 | PsiElement(DOC_IDENTIFIER)('int') 189 | PsiElement(DOC_RAB)('>') 190 | PhpPsiElementImpl 191 | 192 | PsiWhiteSpace('\n ') 193 | PsiElement(DOC_LEADING_ASTERISK)('*') 194 | PsiWhiteSpace(' ') 195 | PhpDocTagImpl: @phpstan-assert-if-false 196 | PsiElement(DOC_TAG_NAME)('@phpstan-assert-if-false') 197 | PsiWhiteSpace(' ') 198 | PhpDocTypeImpl: int 199 | PsiElement(DOC_IDENTIFIER)('int') 200 | PsiWhiteSpace(' ') 201 | PhpDocRefImpl: test 202 | PhpDocVarImpl: this 203 | PsiElement(DOC_VARIABLE)('$this') 204 | PsiElement(DOC_ARROW)('->') 205 | PsiElement(DOC_IDENTIFIER)('test') 206 | PsiElement(DOC_LPAREN)('(') 207 | PsiElement(DOC_RPAREN)(')') 208 | PhpPsiElementImpl 209 | 210 | PsiWhiteSpace('\n ') 211 | PsiElement(DOC_LEADING_ASTERISK)('*') 212 | PsiWhiteSpace(' ') 213 | PhpDocTagImpl: @phpstan-assert-if-false 214 | PsiElement(DOC_TAG_NAME)('@phpstan-assert-if-false') 215 | PsiWhiteSpace(' ') 216 | PhpDocTypeImpl: int 217 | PsiElement(DOC_IDENTIFIER)('int') 218 | PsiWhiteSpace(' ') 219 | PhpDocRefImpl: test 220 | PhpDocVarImpl: b 221 | PsiElement(DOC_VARIABLE)('$b') 222 | PsiElement(DOC_ARROW)('->') 223 | PsiElement(DOC_IDENTIFIER)('test') 224 | PsiElement(DOC_LPAREN)('(') 225 | PsiElement(DOC_RPAREN)(')') 226 | PhpPsiElementImpl 227 | 228 | PsiWhiteSpace('\n ') 229 | PsiElement(DOC_LEADING_ASTERISK)('*') 230 | PsiWhiteSpace(' ') 231 | PhpDocTagImpl: @phpstan-assert-if-true 232 | PsiElement(DOC_TAG_NAME)('@phpstan-assert-if-true') 233 | PsiWhiteSpace(' ') 234 | PhpDocTypeImpl: null 235 | PsiElement(DOC_IDENTIFIER)('null') 236 | PsiWhiteSpace(' ') 237 | PhpDocRefImpl: b 238 | PhpDocVarImpl: this 239 | PsiElement(DOC_VARIABLE)('$this') 240 | PsiElement(DOC_ARROW)('->') 241 | PsiElement(DOC_IDENTIFIER)('b') 242 | PhpPsiElementImpl 243 | 244 | PsiWhiteSpace('\n ') 245 | PsiElement(DOC_LEADING_ASTERISK)('*') 246 | PsiWhiteSpace(' ') 247 | PhpDocTemplateTagImpl: @template 248 | PsiElement(DOC_TAG_NAME)('@template') 249 | PsiWhiteSpace(' ') 250 | PhpDocTemplateParameterImpl: NewT 251 | PsiElement(DOC_IDENTIFIER)('NewT') 252 | PhpPsiElementImpl 253 | 254 | PsiWhiteSpace('\n ') 255 | PsiElement(DOC_LEADING_ASTERISK)('*') 256 | PsiWhiteSpace(' ') 257 | PhpDocTagImpl: @phpstan-this-out 258 | PsiElement(DOC_TAG_NAME)('@phpstan-this-out') 259 | PsiWhiteSpace(' ') 260 | PhpDocTypeImpl: self 261 | PsiElement(DOC_IDENTIFIER)('self') 262 | PhpPsiElementImpl 263 | PsiElement(DOC_LAB)('<') 264 | PhpDocTypeImpl: NewT 265 | PsiElement(DOC_IDENTIFIER)('NewT') 266 | PsiElement(DOC_RAB)('>') 267 | PhpPsiElementImpl 268 | 269 | PsiWhiteSpace('\n ') 270 | PsiElement(DOC_LEADING_ASTERISK)('*') 271 | PsiWhiteSpace(' ') 272 | PhpDocTagImpl: @phpstan-self-out 273 | PsiElement(DOC_TAG_NAME)('@phpstan-self-out') 274 | PsiWhiteSpace(' ') 275 | PhpDocTypeImpl: self 276 | PsiElement(DOC_IDENTIFIER)('self') 277 | PhpPsiElementImpl 278 | PsiElement(DOC_LAB)('<') 279 | PhpDocTypeImpl: NewT 280 | PsiElement(DOC_IDENTIFIER)('NewT') 281 | PsiElement(DOC_RAB)('>') 282 | PhpPsiElementImpl 283 | 284 | PsiWhiteSpace('\n ') 285 | PsiElement(DOC_COMMENT_END)('*/') 286 | PsiWhiteSpace('\n ') 287 | MethodImpl: test 288 | PhpModifierListImpl: public 289 | PsiElement(public keyword)('public') 290 | PsiWhiteSpace(' ') 291 | PsiElement(function)('function') 292 | PsiWhiteSpace(' ') 293 | PsiElement(identifier)('test') 294 | PsiElement(()('(') 295 | Parameter list 296 | ParameterImpl: b 297 | Parameter type 298 | ClassReferenceImpl: B 299 | PsiElement(identifier)('B') 300 | PsiElement(bit or)('|') 301 | ClassReferenceImpl: A 302 | PsiElement(identifier)('A') 303 | PsiWhiteSpace(' ') 304 | PsiElement(variable)('$b') 305 | PsiElement())(')') 306 | PsiElement(colon)(':') 307 | PsiWhiteSpace(' ') 308 | Return type 309 | ClassReferenceImpl: void 310 | PsiElement(identifier)('void') 311 | PsiWhiteSpace(' ') 312 | PsiElement(Group statement) 313 | PsiElement({)('{') 314 | PsiWhiteSpace('\n\n ') 315 | PsiElement(})('}') 316 | PsiWhiteSpace('\n') 317 | PsiElement(})('}') -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2010-2020 JetBrains s.r.o. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. --------------------------------------------------------------------------------