Merge remote-tracking branch 'origin/master'
authorKonstantin Bulenkov <kb@jetbrains.com>
Wed, 19 Oct 2016 12:26:02 +0000 (14:26 +0200)
committerKonstantin Bulenkov <kb@jetbrains.com>
Wed, 19 Oct 2016 12:26:02 +0000 (14:26 +0200)
142 files changed:
.idea/libraries/Eclipse.xml
bin/linux/restart.py
java/compiler/impl/src/com/intellij/task/impl/InternalProjectTaskRunner.java
java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsEx.java
java/execution/impl/src/com/intellij/execution/junit/JUnitUtil.java
java/java-analysis-impl/src/com/intellij/codeInspection/LambdaCanBeMethodReferenceInspection.java
java/java-analysis-impl/src/com/intellij/codeInspection/SimplifyStreamApiCallChainsInspection.java
java/java-impl/src/com/intellij/codeInsight/completion/ConstructorInsertHandler.java
java/java-impl/src/com/intellij/codeInsight/completion/JavaConstructorCallElement.java
java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java
java/java-impl/src/com/intellij/codeInsight/intention/impl/InlineStreamMapAction.java
java/java-impl/src/com/intellij/refactoring/util/LambdaRefactoringUtil.java
java/java-tests/testData/codeInsight/completion/signature/AnonymousNonDefaultConstructor.java [new file with mode: 0644]
java/java-tests/testData/codeInsight/completion/signature/AnonymousNonDefaultConstructor_after.java [new file with mode: 0644]
java/java-tests/testData/codeInsight/completion/signature/NonDefaultConstructor.java [new file with mode: 0644]
java/java-tests/testData/codeInsight/completion/signature/NonDefaultConstructor_after.java [new file with mode: 0644]
java/java-tests/testData/codeInsight/completion/signature/OnlyDefaultConstructor.java [new file with mode: 0644]
java/java-tests/testData/codeInsight/completion/signature/OnlyDefaultConstructor_after.java [new file with mode: 0644]
java/java-tests/testData/codeInsight/completion/signature/SeveralConstructors.java [new file with mode: 0644]
java/java-tests/testData/inspection/streamApiCallChains/afterStreamToCollection.java [new file with mode: 0644]
java/java-tests/testData/inspection/streamApiCallChains/afterStreamToCollectionGeneric.java [new file with mode: 0644]
java/java-tests/testData/inspection/streamApiCallChains/afterStreamToCollectionMyTypeAddAll.java [new file with mode: 0644]
java/java-tests/testData/inspection/streamApiCallChains/afterStreamToCollectionMyTypeGeneric.java [new file with mode: 0644]
java/java-tests/testData/inspection/streamApiCallChains/afterStreamToCollectionOtherType.java [new file with mode: 0644]
java/java-tests/testData/inspection/streamApiCallChains/afterStreamToList.java [new file with mode: 0644]
java/java-tests/testData/inspection/streamApiCallChains/afterStreamToListOtherType.java [new file with mode: 0644]
java/java-tests/testData/inspection/streamApiCallChains/afterStreamToSet.java [new file with mode: 0644]
java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollection.java [new file with mode: 0644]
java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionGeneric.java [new file with mode: 0644]
java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionInvalid.java [new file with mode: 0644]
java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionMyType.java [new file with mode: 0644]
java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionMyTypeAddAll.java [new file with mode: 0644]
java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionMyTypeAddAllPrivate.java [new file with mode: 0644]
java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionMyTypeGeneric.java [new file with mode: 0644]
java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionOtherType.java [new file with mode: 0644]
java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToList.java [new file with mode: 0644]
java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToListOtherType.java [new file with mode: 0644]
java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToSet.java [new file with mode: 0644]
java/java-tests/testSrc/com/intellij/codeInsight/completion/SignatureCompletionTest.groovy [new file with mode: 0644]
java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/OrderEntryTest.java
java/testFramework/src/com/intellij/codeInsight/daemon/quickFix/ActionHint.java
java/testFramework/src/com/intellij/codeInsight/daemon/quickFix/LightQuickFixTestCase.java
jps/jps-builders/src/org/jetbrains/jps/builders/impl/java/EclipseCompilerTool.java
json/gen/com/intellij/json/JsonElementTypes.java
json/gen/com/intellij/json/JsonParser.java
json/gen/com/intellij/json/psi/impl/JsonArrayImpl.java
json/gen/com/intellij/json/psi/impl/JsonBooleanLiteralImpl.java
json/gen/com/intellij/json/psi/impl/JsonContainerImpl.java
json/gen/com/intellij/json/psi/impl/JsonLiteralImpl.java
json/gen/com/intellij/json/psi/impl/JsonNullLiteralImpl.java
json/gen/com/intellij/json/psi/impl/JsonNumberLiteralImpl.java
json/gen/com/intellij/json/psi/impl/JsonObjectImpl.java
json/gen/com/intellij/json/psi/impl/JsonPropertyImpl.java
json/gen/com/intellij/json/psi/impl/JsonReferenceExpressionImpl.java
json/gen/com/intellij/json/psi/impl/JsonStringLiteralImpl.java
json/gen/com/intellij/json/psi/impl/JsonValueImpl.java
json/json.bnf
lib/ecj-4.5.2.jar [deleted file]
lib/ecj-4.6.1.jar [new file with mode: 0644]
lib/required_for_dist.txt
platform/core-impl/src/com/intellij/openapi/application/TransactionGuardImpl.java
platform/diff-impl/src/com/intellij/diff/contents/DiffPsiFileSupport.java
platform/platform-api/src/com/intellij/execution/configurations/GeneralCommandLine.java
platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileChooserDialogImpl.java
platform/platform-impl/src/com/intellij/ui/EditorComboBox.java
platform/platform-impl/src/com/intellij/util/Restarter.java
platform/platform-tests/testSrc/com/intellij/application/TransactionTest.groovy
plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/InspectionGadgetsBundle.properties
plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/junit/AssertEqualsHint.java
plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/junit/AssertsWithoutMessagesInspection.java
plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/junit/ConstantJUnitAssertArgumentInspection.java
plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/junit/JUnit4MethodNamingConventionInspectionBase.java
plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/junit/JUnitTestClassNamingConventionInspectionBase.java
plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/junit/TestMethodInProductCodeInspection.java
plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/junit/TestMethodWithoutAssertionInspectionBase.java
plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/naming/InstanceMethodNamingConventionInspectionBase.java
plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/TestUtils.java
plugins/InspectionGadgets/src/com/siyeh/ig/junit/JUnitTestClassNamingConventionInspection.java
plugins/InspectionGadgets/src/com/siyeh/ig/style/MethodRefCanBeReplacedWithLambdaInspection.java
plugins/InspectionGadgets/src/inspectionDescriptions/JUnit4MethodNamingConvention.html
plugins/InspectionGadgets/src/inspectionDescriptions/TestMethodInProductCode.html
plugins/InspectionGadgets/test/com/siyeh/igtest/junit/junit4_method_naming_convention/JUnit4MethodNamingConvention.java
plugins/coverage-common/src/com/intellij/coverage/SimpleCoverageAnnotator.java
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/auxiliary/annotation/GrAnnotationArgumentListImpl.java
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/auxiliary/annotation/GrAnnotationNameValuePairImpl.java
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/stubs/elements/GrAnnotationArgumentListElementType.java
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/stubs/elements/GrStubFileElementType.java
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/stubs/stubs.kt
plugins/maven/src/main/java/org/jetbrains/idea/maven/server/MavenServerManager.java
plugins/maven/src/main/resources/RunnerBundle.properties
python/gen/com/jetbrains/commandInterface/commandLine/CommandLineParser.java
python/gen/com/jetbrains/commandInterface/commandLine/psi/CommandLineArgument.java
python/gen/com/jetbrains/commandInterface/commandLine/psi/impl/CommandLineArgumentImpl.java
python/gen/com/jetbrains/commandInterface/commandLine/psi/impl/CommandLineCommandImpl.java
python/gen/com/jetbrains/commandInterface/commandLine/psi/impl/CommandLineOptionImpl.java
python/helpers/coveragepy/coverage/__init__.py
python/helpers/coveragepy/coverage/__main__.py
python/helpers/coveragepy/coverage/annotate.py
python/helpers/coveragepy/coverage/backunittest.py [new file with mode: 0644]
python/helpers/coveragepy/coverage/backward.py
python/helpers/coveragepy/coverage/bytecode.py
python/helpers/coveragepy/coverage/cmdline.py
python/helpers/coveragepy/coverage/codeunit.py [deleted file]
python/helpers/coveragepy/coverage/collector.py
python/helpers/coveragepy/coverage/config.py
python/helpers/coveragepy/coverage/control.py
python/helpers/coveragepy/coverage/data.py
python/helpers/coveragepy/coverage/debug.py
python/helpers/coveragepy/coverage/env.py [new file with mode: 0644]
python/helpers/coveragepy/coverage/execfile.py
python/helpers/coveragepy/coverage/files.py
python/helpers/coveragepy/coverage/fullcoverage/encodings.py [deleted file]
python/helpers/coveragepy/coverage/html.py
python/helpers/coveragepy/coverage/htmlfiles/coverage_html.js
python/helpers/coveragepy/coverage/htmlfiles/index.html
python/helpers/coveragepy/coverage/htmlfiles/jquery.debounce.min.js [new file with mode: 0644]
python/helpers/coveragepy/coverage/htmlfiles/jquery.min.js
python/helpers/coveragepy/coverage/htmlfiles/keybd_closed.png
python/helpers/coveragepy/coverage/htmlfiles/keybd_open.png
python/helpers/coveragepy/coverage/htmlfiles/pyfile.html
python/helpers/coveragepy/coverage/htmlfiles/style.css
python/helpers/coveragepy/coverage/misc.py
python/helpers/coveragepy/coverage/multiproc.py [new file with mode: 0644]
python/helpers/coveragepy/coverage/parser.py
python/helpers/coveragepy/coverage/phystokens.py
python/helpers/coveragepy/coverage/pickle2json.py [new file with mode: 0644]
python/helpers/coveragepy/coverage/plugin.py [new file with mode: 0644]
python/helpers/coveragepy/coverage/plugin_support.py [new file with mode: 0644]
python/helpers/coveragepy/coverage/python.py [new file with mode: 0644]
python/helpers/coveragepy/coverage/pytracer.py [new file with mode: 0644]
python/helpers/coveragepy/coverage/report.py
python/helpers/coveragepy/coverage/results.py
python/helpers/coveragepy/coverage/summary.py
python/helpers/coveragepy/coverage/templite.py
python/helpers/coveragepy/coverage/tracer.c [deleted file]
python/helpers/coveragepy/coverage/version.py
python/helpers/coveragepy/coverage/xmlreport.py
python/python-community-ide-resources/resources/idea/PyCharmCoreApplicationInfo.xml
python/src/com/jetbrains/python/run/PyVirtualEnvReader.kt
resources-en/src/inspectionDescriptions/SimplifyStreamApiCallChains.html
updater/src/com/intellij/updater/CreateAction.java
updater/src/com/intellij/updater/Patch.java

index 270d58e2ae95baf3198f31c42f8b17b7b3d22fa0..f680844b84154a7cf3391bf715039ec32c0f17aa 100644 (file)
@@ -1,7 +1,7 @@
 <component name="libraryTable">
   <library name="Eclipse">
     <CLASSES>
-      <root url="jar://$PROJECT_DIR$/lib/ecj-4.5.2.jar!/" />
+      <root url="jar://$PROJECT_DIR$/lib/ecj-4.6.1.jar!/" />
     </CLASSES>
     <JAVADOC />
     <SOURCES />
index 93bf229db65e632dd4dfb4b703816d8d1099b9f6..5d49a22c38205d7dab13b21c3ec848dc0cc020c4 100755 (executable)
@@ -3,21 +3,22 @@
 # Waits for the parent process to terminate, then executes specified commands.
 
 import os
+import signal
 import sys
 import time
 
-if len(sys.argv) < 2:
-    raise Exception('At least one argument expected')
+if len(sys.argv) < 3:
+    raise Exception('usage: restart.py <pid> <path> [optional command]')
 
-pid = os.getppid()
+signal.signal(signal.SIGHUP, signal.SIG_IGN)
+
+pid = int(sys.argv[1])
 while os.getppid() == pid:
     time.sleep(0.5)
 
-if len(sys.argv) > 2:
-    os.spawnv(os.P_WAIT, sys.argv[2], sys.argv[2:])
+if len(sys.argv) > 3:
+    to_launch = sys.argv[3:]
+    os.spawnv(os.P_WAIT, to_launch[0], to_launch)
 
-to_launch = sys.argv[1]
-if sys.platform == 'darwin':
-    os.execv('/usr/bin/open', ['/usr/bin/open', to_launch])
-else:
-    os.execv(to_launch, [to_launch])
+to_launch = ['/usr/bin/open', sys.argv[2]] if sys.platform == 'darwin' else [sys.argv[2]]
+os.execv(to_launch[0], to_launch)
index 5aa4c074f6d2567ed75d8875bc3520a3460a0d56..d922e54f3883a0c75bec4cbe783acce11f91678f 100644 (file)
@@ -25,6 +25,7 @@ import com.intellij.openapi.compiler.CompilerManager;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.packaging.artifacts.Artifact;
 import com.intellij.packaging.impl.compiler.ArtifactCompileScope;
@@ -89,54 +90,79 @@ public class InternalProjectTaskRunner extends ProjectTaskRunner {
                                            @Nullable CompileStatusNotification compileNotification,
                                            @NotNull Map<Class<? extends ProjectTask>, List<ProjectTask>> tasksMap) {
     Collection<? extends ProjectTask> buildTasks = tasksMap.get(ModuleBuildTask.class);
+    if (ContainerUtil.isEmpty(buildTasks)) return;
+    ModulesBuildSettings modulesBuildSettings = assembleModulesBuildSettings(buildTasks);
+
+    CompilerManager compilerManager = CompilerManager.getInstance(project);
+    CompileScope scope = createScope(compilerManager, context,
+                                     modulesBuildSettings.modules,
+                                     modulesBuildSettings.includeDependentModules,
+                                     modulesBuildSettings.includeRuntimeDependencies);
+    if (modulesBuildSettings.isIncrementalBuild) {
+      compilerManager.make(scope, compileNotification);
+    }
+    else {
+      compilerManager.compile(scope, compileNotification);
+    }
+  }
 
+  private static class ModulesBuildSettings {
+    final boolean isIncrementalBuild;
+    final boolean includeDependentModules;
+    final boolean includeRuntimeDependencies;
+    final Collection<Module> modules;
+
+    public ModulesBuildSettings(boolean isIncrementalBuild,
+                                boolean includeDependentModules,
+                                boolean includeRuntimeDependencies,
+                                Collection<Module> modules) {
+      this.isIncrementalBuild = isIncrementalBuild;
+      this.includeDependentModules = includeDependentModules;
+      this.includeRuntimeDependencies = includeRuntimeDependencies;
+      this.modules = modules;
+    }
+  }
 
-    if (!ContainerUtil.isEmpty(buildTasks)) {
-      List<Module> modules = new SmartList<>();
+  private static ModulesBuildSettings assembleModulesBuildSettings(Collection<? extends ProjectTask> buildTasks) {
+    Collection<Module> modules = new SmartList<>();
+    Collection<ModuleBuildTask> incrementalTasks = ContainerUtil.newSmartList();
+    Collection<ModuleBuildTask> excludeDependentTasks = ContainerUtil.newSmartList();
+    Collection<ModuleBuildTask> excludeRuntimeTasks = ContainerUtil.newSmartList();
 
-      Boolean isIncrementalBuild = null;
-      Boolean includeDependentModules = null;
-      Boolean includeRuntimeDependencies = null;
+    for (ProjectTask buildProjectTask : buildTasks) {
+      ModuleBuildTask moduleBuildTask = (ModuleBuildTask)buildProjectTask;
+      modules.add(moduleBuildTask.getModule());
 
-      for (ProjectTask buildProjectTask : buildTasks) {
-        ModuleBuildTask moduleBuildTask = (ModuleBuildTask)buildProjectTask;
-        assertModuleBuildSettings(moduleBuildTask, isIncrementalBuild, includeDependentModules, includeRuntimeDependencies);
-        modules.add(moduleBuildTask.getModule());
-        if (!moduleBuildTask.isIncrementalBuild()) {
-          isIncrementalBuild = false;
-        }
-        if (moduleBuildTask.isIncludeDependentModules()) {
-          includeDependentModules = true;
-        }
-        if (moduleBuildTask.isIncludeRuntimeDependencies()) {
-          includeRuntimeDependencies = true;
-        }
+      if (moduleBuildTask.isIncrementalBuild()) {
+        incrementalTasks.add(moduleBuildTask);
       }
-      CompilerManager compilerManager = CompilerManager.getInstance(project);
-      CompileScope scope = createScope(
-        compilerManager, context, modules, includeDependentModules != null, includeRuntimeDependencies != null);
-      if (isIncrementalBuild == null) {
-        compilerManager.make(scope, compileNotification);
+      if (!moduleBuildTask.isIncludeDependentModules()) {
+        excludeDependentTasks.add(moduleBuildTask);
       }
-      else {
-        compilerManager.compile(scope, compileNotification);
+      if (!moduleBuildTask.isIncludeRuntimeDependencies()) {
+        excludeRuntimeTasks.add(moduleBuildTask);
       }
     }
-  }
 
-  private static void assertModuleBuildSettings(ModuleBuildTask moduleBuildTask,
-                                                Boolean isIncrementalBuild,
-                                                Boolean includeDependentModules,
-                                                Boolean includeRuntimeDependencies) {
-    if (isIncrementalBuild != null && moduleBuildTask.isIncrementalBuild()) {
-      LOG.warn("Incremental build setting for the module '" + moduleBuildTask.getModule().getName() + "' will be ignored");
+    boolean isIncrementalBuild = incrementalTasks.size() == buildTasks.size();
+    boolean includeDependentModules = excludeDependentTasks.size() != buildTasks.size();
+    boolean includeRuntimeDependencies = excludeRuntimeTasks.size() != buildTasks.size();
+
+    if (!isIncrementalBuild && !incrementalTasks.isEmpty()) {
+      assertModuleBuildSettingsConsistent(incrementalTasks, "will be built ignoring incremental build setting");
     }
-    if (includeDependentModules != null && !moduleBuildTask.isIncludeDependentModules()) {
-      LOG.warn("'Module '" + moduleBuildTask.getModule().getName() + "' will be built along with dependent modules");
+    if (includeDependentModules && !excludeDependentTasks.isEmpty()) {
+      assertModuleBuildSettingsConsistent(excludeDependentTasks, "will be built along with dependent modules");
     }
-    if (includeRuntimeDependencies != null && !moduleBuildTask.isIncludeRuntimeDependencies()) {
-      LOG.warn("'Module '" + moduleBuildTask.getModule().getName() + "' will be built along with runtime dependencies");
+    if (includeRuntimeDependencies && !excludeRuntimeTasks.isEmpty()) {
+      assertModuleBuildSettingsConsistent(excludeRuntimeTasks, "will be built along with runtime dependencies");
     }
+    return new ModulesBuildSettings(isIncrementalBuild, includeDependentModules, includeRuntimeDependencies, modules);
+  }
+
+  private static void assertModuleBuildSettingsConsistent(Collection<ModuleBuildTask> moduleBuildTasks, String warnMsg) {
+    String moduleNames = StringUtil.join(moduleBuildTasks, task -> task.getModule().getName(), ", ");
+    LOG.warn("Module" + (moduleBuildTasks.size() > 1 ? "s": "") + " : '" + moduleNames + "' " + warnMsg);
   }
 
   private static CompileScope createScope(CompilerManager compilerManager,
index bd47b8918fd9209e70e1958fb649d8dfbadaaadd..ea908fe312d2c5ddd127456b09aac39d1b286b8a 100644 (file)
@@ -882,21 +882,24 @@ public abstract class DebuggerUtilsEx extends DebuggerUtils {
     PsiElement body = lambda.getBody();
     if (body == null || !intersects(lineRange, body)) return null;
     if (body instanceof PsiCodeBlock) {
-      for (PsiStatement statement : ((PsiCodeBlock)body).getStatements()) {
-        // return first statement starting on the line
-        if (lineRange.contains(statement.getTextOffset())) {
-          return statement;
-        }
-        // otherwise check all children
-        else if (intersects(lineRange, statement)) {
-          for (PsiElement element : SyntaxTraverser.psiTraverser(statement)) {
-            if (lineRange.contains(element.getTextOffset())) {
-              return element;
+      PsiStatement[] statements = ((PsiCodeBlock)body).getStatements();
+      if (statements.length > 0) {
+        for (PsiStatement statement : statements) {
+          // return first statement starting on the line
+          if (lineRange.contains(statement.getTextOffset())) {
+            return statement;
+          }
+          // otherwise check all children
+          else if (intersects(lineRange, statement)) {
+            for (PsiElement element : SyntaxTraverser.psiTraverser(statement)) {
+              if (lineRange.contains(element.getTextOffset())) {
+                return element;
+              }
             }
           }
         }
+        return null;
       }
-      return null;
     }
     return body;
   }
index 990fea7bd2811b83159f8b90b6e966fa13047c66..d63f2bc7bd4fd775c208972af94de9b44089e4a2 100644 (file)
@@ -120,6 +120,7 @@ public class JUnitUtil {
     if (psiMethod.getParameterList().getParametersCount() > 0) return false;
     if (psiMethod.hasModifierProperty(PsiModifier.STATIC) && SUITE_METHOD_NAME.equals(psiMethod.getName())) return false;
     if (!psiMethod.getName().startsWith("test")) return false;
+    if (psiMethod.hasModifierProperty(PsiModifier.STATIC)) return false;
     PsiClass testCaseClass = getTestCaseClassOrNull(location);
     return testCaseClass != null && psiMethod.getContainingClass().isInheritor(testCaseClass, true) && PsiType.VOID.equals(psiMethod.getReturnType());
   }
index 48dbf15eda4bee5639b2525dfb28a4c5a188c222..d45440dbe99a092aa7c571a58a1b76a4617c4e94 100644 (file)
@@ -298,9 +298,7 @@ public class LambdaCanBeMethodReferenceInspection extends BaseJavaBatchLocalInsp
           return null;
         }
         PsiType type = typeElement.getType();
-        if (type instanceof PsiPrimitiveType) return null;
-        type = type.getDeepComponentType();
-        if (type instanceof PsiClassType && (((PsiClassType)type).resolve() instanceof PsiTypeParameter)) return null;
+        if (type instanceof PsiPrimitiveType || PsiUtil.resolveClassInType(type) instanceof PsiTypeParameter) return null;
         return expression;
       }
     }
index e29355b85b9caf45b2d9f81a9d3523338b9b8acc..30c38eae0a3e2acee28d643aa686ad5ea56450d0 100644 (file)
@@ -20,7 +20,9 @@ import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.TextRange;
 import com.intellij.psi.*;
+import com.intellij.psi.codeStyle.CodeStyleManager;
 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
+import com.intellij.psi.impl.PsiDiamondTypeUtil;
 import com.intellij.psi.util.*;
 import com.siyeh.ig.psiutils.BoolUtils;
 import org.jetbrains.annotations.Contract;
@@ -30,6 +32,7 @@ import org.jetbrains.annotations.Nullable;
 
 import java.text.MessageFormat;
 import java.util.Arrays;
+import java.util.stream.Stream;
 
 /**
  * @author Pavel.Dolgov
@@ -57,6 +60,9 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
   private static final String ALL_MATCH_METHOD = "allMatch";
 
   private static final String COUNTING_COLLECTOR = "counting";
+  private static final String TO_LIST_COLLECTOR = "toList";
+  private static final String TO_SET_COLLECTOR = "toSet";
+  private static final String TO_COLLECTION_COLLECTOR = "toCollection";
   private static final String MIN_BY_COLLECTOR = "minBy";
   private static final String MAX_BY_COLLECTOR = "maxBy";
   private static final String MAPPING_COLLECTOR = "mapping";
@@ -150,6 +156,13 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
         }
       }
 
+      @Contract("null -> false")
+      private boolean isCollectionStream(PsiMethodCallExpression qualifierCall) {
+        if (qualifierCall == null) return false;
+        PsiMethod qualifier = qualifierCall.resolveMethod();
+        return isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTION, STREAM_METHOD, 0);
+      }
+
       private void handleStreamForEach(PsiMethodCallExpression methodCall, PsiMethod method) {
         final String name;
         if (isCallOf(method, CommonClassNames.JAVA_UTIL_STREAM_STREAM, FOR_EACH_METHOD, 1)) {
@@ -162,9 +175,7 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
           return;
         }
         final PsiMethodCallExpression qualifierCall = getQualifierMethodCall(methodCall);
-        if (qualifierCall == null) return;
-        final PsiMethod qualifier = qualifierCall.resolveMethod();
-        if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTION, STREAM_METHOD, 0)) {
+        if (isCollectionStream(qualifierCall)) {
           final ReplaceStreamMethodFix fix = new ReplaceStreamMethodFix(name, FOR_EACH_METHOD, true);
           holder
             .registerProblem(methodCall, getCallChainRange(methodCall, qualifierCall), fix.getMessage(), new SimplifyCallChainFix(fix));
@@ -176,7 +187,7 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
         if(parameter instanceof PsiMethodCallExpression) {
           PsiMethodCallExpression collectorCall = (PsiMethodCallExpression)parameter;
           PsiMethod collectorMethod = collectorCall.resolveMethod();
-          ReplaceCollectorFix fix = null;
+          ReplaceCollectorFix fix;
           if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, COUNTING_COLLECTOR, 0)) {
             fix = new ReplaceCollectorFix(COUNTING_COLLECTOR, "count()", false);
           } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, MIN_BY_COLLECTOR, 1)) {
@@ -197,9 +208,26 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
             fix = new ReplaceCollectorFix(SUMMING_LONG_COLLECTOR, "mapToLong({0}).sum()", false);
           } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, SUMMING_DOUBLE_COLLECTOR, 1)) {
             fix = new ReplaceCollectorFix(SUMMING_DOUBLE_COLLECTOR, "mapToDouble({0}).sum()", false);
+          } else {
+            PsiType type = methodCall.getType();
+            if(type instanceof PsiClassType && !(((PsiClassType)type).resolve() instanceof PsiTypeParameter)) {
+              String replacement = collectorToCollection(collectorCall);
+              if (replacement != null) {
+                PsiMethodCallExpression qualifier = getQualifierMethodCall(methodCall);
+                if (isCollectionStream(qualifier)) {
+                  PsiElement startElement = qualifier.getMethodExpression().getReferenceNameElement();
+                  if (startElement != null) {
+                    holder.registerProblem(methodCall, new TextRange(startElement.getTextOffset() - methodCall.getTextOffset(),
+                                                                     methodCall.getTextLength()),
+                                           "Can be replaced with '" + replacement + "' constructor",
+                                           new SimplifyCallChainFix(new SimplifyCollectionCreationFix(replacement)));
+                  }
+                }
+              }
+            }
+            return;
           }
-          if (fix != null &&
-              collectorCall.getArgumentList().getExpressions().length == collectorMethod.getParameterList().getParametersCount()) {
+          if (collectorCall.getArgumentList().getExpressions().length == collectorMethod.getParameterList().getParametersCount()) {
             TextRange range = methodCall.getTextRange();
             PsiElement nameElement = methodCall.getMethodExpression().getReferenceNameElement();
             if(nameElement != null) {
@@ -247,6 +275,51 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
     };
   }
 
+  private static boolean isCollectionConstructor(PsiMethod ctor) {
+    if(!ctor.getModifierList().hasExplicitModifier(PsiModifier.PUBLIC)) return false;
+    PsiParameterList list = ctor.getParameterList();
+    if(list.getParametersCount() != 1) return false;
+    PsiParameter parameter = list.getParameters()[0];
+    PsiTypeElement typeElement = parameter.getTypeElement();
+    if(typeElement == null) return false;
+    PsiType type = typeElement.getType();
+    if(!(type instanceof PsiClassType)) return false;
+    PsiClass aClass = ((PsiClassType)type).resolve();
+    if(aClass == null) return false;
+    return CommonClassNames.JAVA_UTIL_COLLECTION.equals(aClass.getQualifiedName());
+  }
+
+  @Nullable
+  private static String collectorToCollection(PsiMethodCallExpression call) {
+    PsiMethod method = call.resolveMethod();
+    if(isCallOf(method, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, TO_LIST_COLLECTOR, 0)) {
+      return CommonClassNames.JAVA_UTIL_ARRAY_LIST;
+    }
+    if(isCallOf(method, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, TO_SET_COLLECTOR, 0)) {
+      return CommonClassNames.JAVA_UTIL_HASH_SET;
+    }
+    if(isCallOf(method, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, TO_COLLECTION_COLLECTOR, 1)) {
+      PsiExpression[] expressions = call.getArgumentList().getExpressions();
+      if(expressions.length == 1 && expressions[0] instanceof PsiMethodReferenceExpression) {
+        PsiMethodReferenceExpression methodRef = (PsiMethodReferenceExpression)expressions[0];
+        if(methodRef.isConstructor()) {
+          PsiElement element = methodRef.resolve();
+          if(element instanceof PsiMethod) {
+            PsiMethod ctor = (PsiMethod)element;
+            if(ctor.getParameterList().getParametersCount() == 0) {
+              PsiClass aClass = ctor.getContainingClass();
+              if (aClass != null &&
+                  Stream.of(aClass.getConstructors()).anyMatch(SimplifyStreamApiCallChainsInspection::isCollectionConstructor)) {
+                return aClass.getQualifiedName();
+              }
+            }
+          }
+        }
+      }
+    }
+    return null;
+  }
+
   static boolean isParentNegated(PsiMethodCallExpression methodCall) {
     PsiElement parent = PsiUtil.skipParenthesizedExprUp(methodCall.getParent());
     return parent instanceof PsiExpression && BoolUtils.isNegation((PsiExpression)parent);
@@ -689,4 +762,51 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
       }
     }
   }
+
+  private static class SimplifyCollectionCreationFix implements CallChainFix {
+    private String myReplacement;
+
+    public SimplifyCollectionCreationFix(String replacement) {
+      myReplacement = replacement;
+    }
+
+    @Override
+    public String getName() {
+      return "Replace with '"+myReplacement+"' constructor";
+    }
+
+    @Override
+    public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
+      PsiElement element = descriptor.getStartElement();
+      if(!(element instanceof PsiMethodCallExpression)) return;
+      PsiMethodCallExpression collectCall = (PsiMethodCallExpression)element;
+      PsiType type = collectCall.getType();
+      if(!(type instanceof PsiClassType)) return;
+      PsiClass resolvedType = ((PsiClassType)type).resolve();
+      if(resolvedType == null || resolvedType instanceof PsiTypeParameter) return;
+      PsiMethodCallExpression streamCall = getQualifierMethodCall(collectCall);
+      if(streamCall == null) return;
+      PsiExpression collectionExpression = streamCall.getMethodExpression().getQualifierExpression();
+      if(collectionExpression == null) return;
+      String typeText = type.getCanonicalText();
+      if(CommonClassNames.JAVA_UTIL_LIST.equals(resolvedType.getQualifiedName()) ||
+         CommonClassNames.JAVA_UTIL_SET.equals(resolvedType.getQualifiedName())) {
+        PsiType[] parameters = ((PsiClassType)type).getParameters();
+        if(parameters.length != 1) return;
+        typeText = myReplacement + "<" + parameters[0].getCanonicalText() + ">";
+      }
+      if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) return;
+      PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
+      PsiExpression result = factory
+        .createExpressionFromText("new " + typeText + "(" + collectionExpression.getText() + ")", element);
+      PsiNewExpression newExpression = (PsiNewExpression)element.replace(result);
+      PsiJavaCodeReferenceElement classReference = newExpression.getClassOrAnonymousClassReference();
+      LOG.assertTrue(classReference != null);
+      JavaCodeStyleManager.getInstance(project).shortenClassReferences(classReference);
+      if (PsiDiamondTypeUtil.canCollapseToDiamond(newExpression, newExpression, null)) {
+        PsiDiamondTypeUtil.replaceExplicitWithDiamond(classReference.getParameterList());
+      }
+      CodeStyleManager.getInstance(project).reformat(newExpression);
+    }
+  }
 }
index d31afee0a8990df0863c553c7779f2c77f60d21b..a570297296108d817b219bf6fc8a070e0fd9dc75 100644 (file)
@@ -79,13 +79,7 @@ public class ConstructorInsertHandler implements InsertHandler<LookupElementDeco
     final PsiExpression enclosing = PsiTreeUtil.getContextOfType(position, PsiExpression.class, true);
     final PsiAnonymousClass anonymousClass = PsiTreeUtil.getParentOfType(position, PsiAnonymousClass.class);
     final boolean inAnonymous = anonymousClass != null && anonymousClass.getParent() == enclosing;
-    boolean fillTypeArgs = false;
     if (delegate instanceof PsiTypeLookupItem) {
-      fillTypeArgs = !isRawTypeExpected(context, (PsiTypeLookupItem)delegate) &&
-                     psiClass.getTypeParameters().length > 0 &&
-                     ((PsiTypeLookupItem)delegate).calcGenerics(position, context).isEmpty() &&
-                     context.getCompletionChar() != '(';
-
       if (context.getDocument().getTextLength() > context.getTailOffset() &&
           context.getDocument().getCharsSequence().charAt(context.getTailOffset()) == '<') {
         PsiJavaCodeReferenceElement ref = PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getTailOffset(), PsiJavaCodeReferenceElement.class, false);
@@ -124,14 +118,15 @@ public class ConstructorInsertHandler implements InsertHandler<LookupElementDeco
       final int offset = context.getTailOffset();
 
       document.insertString(offset, " {}");
-      editor.getCaretModel().moveToOffset(offset + 2);
+      OffsetKey insideBraces = context.trackOffset(offset + 2, true);
 
       final PsiFile file = context.getFile();
       PsiDocumentManager.getInstance(file.getProject()).commitDocument(document);
       reformatEnclosingExpressionListAtOffset(file, offset);
 
-      if (fillTypeArgs && JavaCompletionUtil.promptTypeArgs(context, context.getOffset(insideRef))) return;
+      if (promptTypeOrConstructorArgs(context, delegate, context.getOffset(insideRef))) return;
 
+      editor.getCaretModel().moveToOffset(context.getOffset(insideBraces));
       context.setLaterRunnable(generateAnonymousBody(editor, file));
     }
     else {
@@ -147,10 +142,30 @@ public class ConstructorInsertHandler implements InsertHandler<LookupElementDeco
       if (mySmart) {
         FeatureUsageTracker.getInstance().triggerFeatureUsed(JavaCompletionFeatures.AFTER_NEW);
       }
-      if (fillTypeArgs) {
-        JavaCompletionUtil.promptTypeArgs(context, context.getOffset(insideRef));
-      }
+      promptTypeOrConstructorArgs(context, delegate, context.getOffset(insideRef));
+    }
+  }
+
+  private static boolean promptTypeOrConstructorArgs(InsertionContext context, LookupElement delegate, int refOffset) {
+    if (shouldFillTypeArgs(context, delegate) && JavaCompletionUtil.promptTypeArgs(context, refOffset)) {
+      return true;
     }
+
+    PsiMethod constructor = JavaConstructorCallElement.extractCalledConstructor(delegate);
+    return constructor != null && JavaMethodCallElement.startArgumentLiveTemplate(context, constructor);
+  }
+
+  private static boolean shouldFillTypeArgs(InsertionContext context, LookupElement delegate) {
+    if (!(delegate instanceof PsiTypeLookupItem) ||
+        isRawTypeExpected(context, (PsiTypeLookupItem)delegate) ||
+        !((PsiClass)delegate.getObject()).hasTypeParameters()) {
+      return false;
+    }
+
+    PsiElement position = SmartCompletionDecorator.getPosition(context, delegate);
+    return position != null &&
+           ((PsiTypeLookupItem)delegate).calcGenerics(position, context).isEmpty() &&
+           context.getCompletionChar() != '(';
   }
 
   private static void reformatEnclosingExpressionListAtOffset(@NotNull PsiFile file, int offset) {
@@ -195,7 +210,7 @@ public class ConstructorInsertHandler implements InsertHandler<LookupElementDeco
                                           LookupElement delegate,
                                           final PsiClass psiClass,
                                           final boolean forAnonymous) {
-    if (context.getCompletionChar() == '[' || JavaConstructorCallElement.isWrapped(delegate)) {
+    if (context.getCompletionChar() == '[') {
       return false;
     }
 
index 4acc787e7b49ed02d9cdf2af17f200f58c57b8ed..d85925410b8337afd63decb66585f5988ff1e6d8 100644 (file)
@@ -18,6 +18,7 @@ package com.intellij.codeInsight.completion;
 import com.intellij.codeInsight.lookup.LookupElement;
 import com.intellij.codeInsight.lookup.LookupElementDecorator;
 import com.intellij.codeInsight.lookup.LookupElementPresentation;
+import com.intellij.codeInsight.lookup.TypedLookupItem;
 import com.intellij.openapi.util.Key;
 import com.intellij.openapi.util.registry.Registry;
 import com.intellij.openapi.util.text.StringUtil;
@@ -28,6 +29,7 @@ import com.intellij.psi.util.CachedValuesManager;
 import com.intellij.psi.util.PsiTreeUtil;
 import com.intellij.util.containers.ContainerUtil;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Collections;
 import java.util.List;
@@ -36,16 +38,17 @@ import java.util.function.Supplier;
 /**
  * @author peter
  */
-public class JavaConstructorCallElement extends JavaMethodCallElement {
+public class JavaConstructorCallElement extends LookupElementDecorator<LookupElement> implements TypedLookupItem {
   private static final Key<JavaConstructorCallElement> WRAPPING_CONSTRUCTOR_CALL = Key.create("WRAPPING_CONSTRUCTOR_CALL");
-  @NotNull private final LookupElement myClassItem;
+  @NotNull private final PsiMethod myConstructor;
   @NotNull private final PsiClassType myType;
+  @NotNull private final PsiSubstitutor mySubstitutor;
 
-  private JavaConstructorCallElement(@NotNull LookupElement classItem, @NotNull PsiMethod constructor, @NotNull Supplier<PsiClassType> type) {
-    super(constructor);
-    myClassItem = classItem;
-    myType = type.get();
-    setQualifierSubstitutor(myType.resolveGenerics().getSubstitutor());
+  private JavaConstructorCallElement(@NotNull LookupElement classItem, @NotNull PsiMethod constructor, @NotNull PsiClassType type) {
+    super(classItem);
+    myConstructor = constructor;
+    myType = type;
+    mySubstitutor = myType.resolveGenerics().getSubstitutor();
 
     markClassItemWrapped(classItem);
   }
@@ -61,26 +64,36 @@ public class JavaConstructorCallElement extends JavaMethodCallElement {
 
   @NotNull
   @Override
-  public PsiType getType() {
-    return myType;
+  public PsiMethod getObject() {
+    return myConstructor;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return this == o || super.equals(o) && myConstructor.equals(((JavaConstructorCallElement)o).myConstructor);
   }
 
   @Override
-  public void handleInsert(InsertionContext context) {
-    myClassItem.handleInsert(context);
-    super.handleInsert(context);
+  public int hashCode() {
+    return 31 * super.hashCode() + myConstructor.hashCode();
+  }
+
+  @NotNull
+  @Override
+  public PsiType getType() {
+    return myType;
   }
 
   @Override
   public void renderElement(LookupElementPresentation presentation) {
-    myClassItem.renderElement(presentation);
+    super.renderElement(presentation);
 
     String tailText = StringUtil.notNullize(presentation.getTailText());
     int genericsEnd = tailText.lastIndexOf('>') + 1;
 
     presentation.clearTail();
     presentation.appendTailText(tailText.substring(0, genericsEnd), false);
-    presentation.appendTailText(MemberLookupHelper.getMethodParameterString(getObject(), getSubstitutor()), false);
+    presentation.appendTailText(MemberLookupHelper.getMethodParameterString(myConstructor, mySubstitutor), false);
     presentation.appendTailText(tailText.substring(genericsEnd), true);
   }
 
@@ -94,7 +107,7 @@ public class JavaConstructorCallElement extends JavaMethodCallElement {
     if (Registry.is("java.completion.show.constructors") && isConstructorCallPlace(position)) {
       List<PsiMethod> constructors = ContainerUtil.filter(psiClass.getConstructors(), c -> shouldSuggestConstructor(psiClass, position, c));
       if (!constructors.isEmpty()) {
-        return ContainerUtil.map(constructors, c -> new JavaConstructorCallElement(classItem, c, type));
+        return ContainerUtil.map(constructors, c -> new JavaConstructorCallElement(classItem, c, type.get()));
       }
     }
     return Collections.singletonList(classItem);
@@ -117,8 +130,10 @@ public class JavaConstructorCallElement extends JavaMethodCallElement {
     });
   }
 
-  static boolean isWrapped(LookupElement element) {
-    return element.getUserData(WRAPPING_CONSTRUCTOR_CALL) != null;
+  @Nullable
+  static PsiMethod extractCalledConstructor(@NotNull LookupElement element) {
+    JavaConstructorCallElement callItem = element.getUserData(WRAPPING_CONSTRUCTOR_CALL);
+    return callItem != null ? callItem.getObject() : null;
   }
 
 }
index 065527c5564232eceb979dd263dcf7219b880556..21a494ed31f875b5c3c64af412147816466e8b34 100644 (file)
@@ -180,10 +180,7 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> implements Type
       }
     }
 
-    context.commitDocument();
-    if (hasParams && context.getCompletionChar() != Lookup.COMPLETE_STATEMENT_SELECT_CHAR && Registry.is("java.completion.argument.live.template")) {
-      startArgumentLiveTemplate(context, method);
-    }
+    startArgumentLiveTemplate(context, method);
   }
 
   private void importOrQualify(Document document, PsiFile file, PsiMethod method, int startOffset) {
@@ -198,7 +195,7 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> implements Type
     qualifyMethodCall(file, startOffset, document);
   }
 
-  public static final Key<JavaMethodCallElement> ARGUMENT_TEMPLATE_ACTIVE = Key.create("ARGUMENT_TEMPLATE_ACTIVE");
+  public static final Key<PsiMethod> ARGUMENT_TEMPLATE_ACTIVE = Key.create("ARGUMENT_TEMPLATE_ACTIVE");
   @NotNull
   private static Template createArgTemplate(PsiMethod method,
                                             int caretOffset,
@@ -226,19 +223,25 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> implements Type
     return template;
   }
 
-  private void startArgumentLiveTemplate(InsertionContext context, PsiMethod method) {
-    Editor editor = context.getEditor();
+  public static boolean startArgumentLiveTemplate(InsertionContext context, PsiMethod method) {
+    if (method.getParameterList().getParametersCount() == 0 ||
+        context.getCompletionChar() == Lookup.COMPLETE_STATEMENT_SELECT_CHAR ||
+        !Registry.is("java.completion.argument.live.template")) {
+      return false;
+    }
 
-    PsiCallExpression call = PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getStartOffset(), PsiCallExpression.class, false);
+    Editor editor = context.getEditor();
+    context.commitDocument();
+    PsiCall call = PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getStartOffset(), PsiCall.class, false);
     PsiExpressionList argList = call == null ? null : call.getArgumentList();
     if (argList == null || argList.getExpressions().length > 0) {
-      return;
+      return false;
     }
 
     TextRange argRange = argList.getTextRange();
     int caretOffset = editor.getCaretModel().getOffset();
     if (!argRange.contains(caretOffset)) {
-      return;
+      return false;
     }
 
     Template template = createArgTemplate(method, caretOffset, argList, argRange);
@@ -247,16 +250,17 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> implements Type
     TemplateManager.getInstance(method.getProject()).startTemplate(editor, template);
 
     TemplateState templateState = TemplateManagerImpl.getTemplateState(editor);
-    if (templateState == null) return;
+    if (templateState == null) return false;
 
     setupNonFilledArgumentRemoving(editor, templateState);
 
-    editor.putUserData(ARGUMENT_TEMPLATE_ACTIVE, this);
+    editor.putUserData(ARGUMENT_TEMPLATE_ACTIVE, method);
     Disposer.register(templateState, () -> {
-      if (editor.getUserData(ARGUMENT_TEMPLATE_ACTIVE) == this) {
+      if (editor.getUserData(ARGUMENT_TEMPLATE_ACTIVE) == method) {
         editor.putUserData(ARGUMENT_TEMPLATE_ACTIVE, null);
       }
     });
+    return true;
   }
 
   private static void setupNonFilledArgumentRemoving(final Editor editor, final TemplateState templateState) {
index 7dbb7f3a3fce740d5ebffe74b472a81cd71233d5..cf3dcba1c3a52331c5dd96aeee76ebf959f8a832 100644 (file)
@@ -31,8 +31,8 @@ import com.intellij.psi.util.PsiTreeUtil;
 import com.intellij.refactoring.util.LambdaRefactoringUtil;
 import com.intellij.util.IncorrectOperationException;
 import com.siyeh.ig.psiutils.ParenthesesUtils;
-import com.siyeh.ig.style.MethodRefCanBeReplacedWithLambdaInspection;
 import one.util.streamex.StreamEx;
+import org.jetbrains.annotations.Contract;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -87,8 +87,7 @@ public class InlineStreamMapAction extends PsiElementBaseIntentionAction {
       return lambdaExpression.getParameterList().getParametersCount() == 1 &&
              (!requireExpressionLambda || LambdaUtil.extractSingleExpressionFromBody(lambdaExpression.getBody()) != null);
     } else if(expression instanceof PsiMethodReferenceExpression) {
-      PsiMethodReferenceExpression methodReference = (PsiMethodReferenceExpression)expression;
-      return !MethodRefCanBeReplacedWithLambdaInspection.isWithSideEffects(methodReference);
+      return LambdaRefactoringUtil.canConvertToLambda((PsiMethodReferenceExpression)expression);
     }
     return false;
   }
@@ -161,12 +160,29 @@ public class InlineStreamMapAction extends PsiElementBaseIntentionAction {
       }
     }
     if(nextName.equals("flatMap") && prevClassName.equals(CommonClassNames.JAVA_UTIL_STREAM_STREAM)) {
-      String mapMethod = translateMap(prevName);
-      return "flatM"+mapMethod.substring(1);
+      return mapToFlatMap(prevName);
     }
     return null;
   }
 
+  @Contract(pure = true)
+  @Nullable
+  private static String mapToFlatMap(String mapMethod) {
+    switch (mapMethod) {
+      case "map":
+        return "flatMap";
+      case "mapToInt":
+        return "flatMapToInt";
+      case "mapToLong":
+        return "flatMapToLong";
+      case "mapToDouble":
+        return "flatMapToDouble";
+    }
+    // Something unsupported passed: ignore
+    return null;
+  }
+
+  @Contract(pure = true)
   @NotNull
   private static String translateMap(String nextMethod) {
     switch (nextMethod) {
index 6763e30c186487bcd0806607a10c340573579bf1..e5d467213976fe282e098ab37442fa19ad748e69 100644 (file)
@@ -77,7 +77,6 @@ public class LambdaRefactoringUtil {
     final PsiParameter[] psiParameters = resolve instanceof PsiMethod ? ((PsiMethod)resolve).getParameterList().getParameters() : null;
 
     final StringBuilder buf = new StringBuilder("(");
-    LOG.assertTrue(functionalInterfaceType != null);
     buf.append(GenericsUtil.getVariableTypeByExpressionType(functionalInterfaceType).getCanonicalText()).append(")(");
     final PsiParameterList parameterList = interfaceMethod.getParameterList();
     final PsiParameter[] parameters = parameterList.getParameters();
@@ -103,6 +102,7 @@ public class LambdaRefactoringUtil {
         else {
           initialName = parameter.getName();
         }
+        LOG.assertTrue(initialName != null);
         baseName = codeStyleManager.variableNameToPropertyName(initialName, VariableKind.PARAMETER);
       }
 
@@ -265,4 +265,16 @@ public class LambdaRefactoringUtil {
       }
     }
   }
+
+  /**
+   * Checks whether method reference can be converted to lambda without significant semantics change
+   * (i.e. method reference qualifier has no side effects)
+   *
+   * @param methodReferenceExpression method reference to check
+   * @return true if method reference can be converted to lambda
+   */
+  public static boolean canConvertToLambda(PsiMethodReferenceExpression methodReferenceExpression) {
+    final PsiExpression qualifierExpression = methodReferenceExpression.getQualifierExpression();
+    return qualifierExpression != null && !SideEffectChecker.mayHaveSideEffects(qualifierExpression);
+  }
 }
diff --git a/java/java-tests/testData/codeInsight/completion/signature/AnonymousNonDefaultConstructor.java b/java/java-tests/testData/codeInsight/completion/signature/AnonymousNonDefaultConstructor.java
new file mode 100644 (file)
index 0000000..3a09e0a
--- /dev/null
@@ -0,0 +1,8 @@
+abstract class Foo{
+    public Foo(int x) {
+    }
+
+    {
+        Foo f = new F<caret>
+    }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/completion/signature/AnonymousNonDefaultConstructor_after.java b/java/java-tests/testData/codeInsight/completion/signature/AnonymousNonDefaultConstructor_after.java
new file mode 100644 (file)
index 0000000..49dde1b
--- /dev/null
@@ -0,0 +1,8 @@
+abstract class Foo{
+    public Foo(int x) {
+    }
+
+    {
+        Foo f = new Foo(<selection>x</selection><caret>) {}
+    }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/completion/signature/NonDefaultConstructor.java b/java/java-tests/testData/codeInsight/completion/signature/NonDefaultConstructor.java
new file mode 100644 (file)
index 0000000..1d0e66a
--- /dev/null
@@ -0,0 +1,8 @@
+class Foo{
+    Foo(int arg) {
+    }
+
+    {
+        Foo f = new F<caret>
+    }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/completion/signature/NonDefaultConstructor_after.java b/java/java-tests/testData/codeInsight/completion/signature/NonDefaultConstructor_after.java
new file mode 100644 (file)
index 0000000..d45f63d
--- /dev/null
@@ -0,0 +1,8 @@
+class Foo{
+    Foo(int arg) {
+    }
+
+    {
+        Foo f = new Foo(<selection>arg</selection><caret>)
+    }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/completion/signature/OnlyDefaultConstructor.java b/java/java-tests/testData/codeInsight/completion/signature/OnlyDefaultConstructor.java
new file mode 100644 (file)
index 0000000..adb41ad
--- /dev/null
@@ -0,0 +1,5 @@
+class Foo{
+    {
+        Foo f = new F<caret>
+    }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/completion/signature/OnlyDefaultConstructor_after.java b/java/java-tests/testData/codeInsight/completion/signature/OnlyDefaultConstructor_after.java
new file mode 100644 (file)
index 0000000..e21f309
--- /dev/null
@@ -0,0 +1,5 @@
+class Foo{
+    {
+        Foo f = new Foo()<caret>
+    }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/completion/signature/SeveralConstructors.java b/java/java-tests/testData/codeInsight/completion/signature/SeveralConstructors.java
new file mode 100644 (file)
index 0000000..fecb2c4
--- /dev/null
@@ -0,0 +1,12 @@
+class Foo{
+    Foo(int arg) {
+    }
+    Foo(boolean arg) {
+    }
+    Foo() {
+    }
+
+    {
+        Foo f = new F<caret>
+    }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/streamApiCallChains/afterStreamToCollection.java b/java/java-tests/testData/inspection/streamApiCallChains/afterStreamToCollection.java
new file mode 100644 (file)
index 0000000..dccf708
--- /dev/null
@@ -0,0 +1,10 @@
+// "Replace with 'java.util.TreeSet' constructor" "true"
+
+import java.util.*;
+import java.util.stream.*;
+
+class Test {
+  public static void test(List<String> s) {
+      new TreeSet<>(s).contains("abc");
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/streamApiCallChains/afterStreamToCollectionGeneric.java b/java/java-tests/testData/inspection/streamApiCallChains/afterStreamToCollectionGeneric.java
new file mode 100644 (file)
index 0000000..4a40cf0
--- /dev/null
@@ -0,0 +1,10 @@
+// "Replace with 'java.util.TreeSet' constructor" "true"
+
+import java.util.*;
+import java.util.stream.*;
+
+class Test {
+  public static <T, T1 extends T> void test(List<T1> s) {
+      new TreeSet<T>(s).contains("abc");
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/streamApiCallChains/afterStreamToCollectionMyTypeAddAll.java b/java/java-tests/testData/inspection/streamApiCallChains/afterStreamToCollectionMyTypeAddAll.java
new file mode 100644 (file)
index 0000000..4fb25ba
--- /dev/null
@@ -0,0 +1,18 @@
+// "Replace with 'Test.MyType' constructor" "true"
+
+import java.util.*;
+import java.util.stream.*;
+
+class Test {
+  static class MyType extends ArrayList<String> {
+    public MyType() {}
+
+    public MyType(Collection<String> coll) {
+      super(coll);
+    }
+  }
+
+  public static void test(List<String> s) {
+      new MyType(s).contains("abc");
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/streamApiCallChains/afterStreamToCollectionMyTypeGeneric.java b/java/java-tests/testData/inspection/streamApiCallChains/afterStreamToCollectionMyTypeGeneric.java
new file mode 100644 (file)
index 0000000..8b73ffd
--- /dev/null
@@ -0,0 +1,18 @@
+// "Replace with 'Test.MyType' constructor" "true"
+
+import java.util.*;
+import java.util.stream.*;
+
+class Test {
+  static class MyType<A,B> extends ArrayList<String> {
+    public MyType() {}
+
+    public MyType(Collection<String> coll) {
+      super(coll);
+    }
+  }
+
+  public static void testMy(List<String> s) {
+      new MyType<String, Number>(s).contains("abc");
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/streamApiCallChains/afterStreamToCollectionOtherType.java b/java/java-tests/testData/inspection/streamApiCallChains/afterStreamToCollectionOtherType.java
new file mode 100644 (file)
index 0000000..6b1baec
--- /dev/null
@@ -0,0 +1,10 @@
+// "Replace with 'java.util.TreeSet' constructor" "true"
+
+import java.util.*;
+import java.util.stream.*;
+
+class Test {
+  public static void test(List<String> s) {
+      new TreeSet<Object>(s).contains("abc");
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/streamApiCallChains/afterStreamToList.java b/java/java-tests/testData/inspection/streamApiCallChains/afterStreamToList.java
new file mode 100644 (file)
index 0000000..81ea9ab
--- /dev/null
@@ -0,0 +1,11 @@
+// "Replace with 'java.util.ArrayList' constructor" "true"
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.*;
+
+class Test {
+  public static void test(List<String> s) {
+      new ArrayList<>(s).contains("abc");
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/streamApiCallChains/afterStreamToListOtherType.java b/java/java-tests/testData/inspection/streamApiCallChains/afterStreamToListOtherType.java
new file mode 100644 (file)
index 0000000..8ee4751
--- /dev/null
@@ -0,0 +1,11 @@
+// "Replace with 'java.util.ArrayList' constructor" "true"
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.*;
+
+class Test {
+  public static void test(List<String> s) {
+      new ArrayList<Object>(s).contains("abc");
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/streamApiCallChains/afterStreamToSet.java b/java/java-tests/testData/inspection/streamApiCallChains/afterStreamToSet.java
new file mode 100644 (file)
index 0000000..e340cfe
--- /dev/null
@@ -0,0 +1,11 @@
+// "Replace with 'java.util.HashSet' constructor" "true"
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.stream.*;
+
+class Test {
+  public static void test(List<String> s) {
+      new HashSet<>(s).contains("abc");
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollection.java b/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollection.java
new file mode 100644 (file)
index 0000000..31eb7be
--- /dev/null
@@ -0,0 +1,10 @@
+// "Replace with 'java.util.TreeSet' constructor" "true"
+
+import java.util.*;
+import java.util.stream.*;
+
+class Test {
+  public static void test(List<String> s) {
+    s.str<caret>eam().collect(Collectors.toCollection(TreeSet<String>::new)).contains("abc");
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionGeneric.java b/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionGeneric.java
new file mode 100644 (file)
index 0000000..5ea3bff
--- /dev/null
@@ -0,0 +1,10 @@
+// "Replace with 'java.util.TreeSet' constructor" "true"
+
+import java.util.*;
+import java.util.stream.*;
+
+class Test {
+  public static <T, T1 extends T> void test(List<T1> s) {
+    s.stream().colle<caret>ct(Collectors.toCollection(TreeSet<T>::new)).contains("abc");
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionInvalid.java b/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionInvalid.java
new file mode 100644 (file)
index 0000000..3523cbd
--- /dev/null
@@ -0,0 +1,10 @@
+// "Replace with 'java.util.TreeSet' constructor" "false"
+
+import java.util.*;
+import java.util.stream.*;
+
+class Test {
+  public static void test(List<String> s) {
+    s.str<caret>eam().collect(Collectors.toCollection(TreeSet<? extends String>::new)).contains("abc");
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionMyType.java b/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionMyType.java
new file mode 100644 (file)
index 0000000..70e4bdf
--- /dev/null
@@ -0,0 +1,14 @@
+// "Replace with 'Test.MyType' constructor" "false"
+
+import java.util.*;
+import java.util.stream.*;
+
+class Test {
+  static class MyType extends ArrayList<String> {
+
+  }
+
+  public static void test(List<String> s) {
+    s.str<caret>eam().collect(Collectors.toCollection(MyType::new)).contains("abc");
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionMyTypeAddAll.java b/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionMyTypeAddAll.java
new file mode 100644 (file)
index 0000000..770e70a
--- /dev/null
@@ -0,0 +1,18 @@
+// "Replace with 'Test.MyType' constructor" "true"
+
+import java.util.*;
+import java.util.stream.*;
+
+class Test {
+  static class MyType extends ArrayList<String> {
+    public MyType() {}
+
+    public MyType(Collection<String> coll) {
+      super(coll);
+    }
+  }
+
+  public static void test(List<String> s) {
+    s.str<caret>eam().collect(Collectors.toCollection(MyType::new)).contains("abc");
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionMyTypeAddAllPrivate.java b/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionMyTypeAddAllPrivate.java
new file mode 100644 (file)
index 0000000..30ff25b
--- /dev/null
@@ -0,0 +1,18 @@
+// "Replace with 'Test.MyType' constructor" "false"
+
+import java.util.*;
+import java.util.stream.*;
+
+class Test {
+  static class MyType extends ArrayList<String> {
+    public MyType() {}
+
+    private MyType(Collection<String> coll) {
+      super(coll);
+    }
+  }
+
+  public static void test(List<String> s) {
+    s.str<caret>eam().collect(Collectors.toCollection(MyType::new)).contains("abc");
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionMyTypeGeneric.java b/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionMyTypeGeneric.java
new file mode 100644 (file)
index 0000000..8b207a2
--- /dev/null
@@ -0,0 +1,18 @@
+// "Replace with 'Test.MyType' constructor" "true"
+
+import java.util.*;
+import java.util.stream.*;
+
+class Test {
+  static class MyType<A,B> extends ArrayList<String> {
+    public MyType() {}
+
+    public MyType(Collection<String> coll) {
+      super(coll);
+    }
+  }
+
+  public static void testMy(List<String> s) {
+    s.stream().collect(Collectors.toCollection(MyType<caret><String, Number>::new)).contains("abc");
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionOtherType.java b/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToCollectionOtherType.java
new file mode 100644 (file)
index 0000000..492cc61
--- /dev/null
@@ -0,0 +1,10 @@
+// "Replace with 'java.util.TreeSet' constructor" "true"
+
+import java.util.*;
+import java.util.stream.*;
+
+class Test {
+  public static void test(List<String> s) {
+    s.str<caret>eam().collect(Collectors.toCollection(TreeSet<Object>::new)).contains("abc");
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToList.java b/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToList.java
new file mode 100644 (file)
index 0000000..dcf6c38
--- /dev/null
@@ -0,0 +1,10 @@
+// "Replace with 'java.util.ArrayList' constructor" "true"
+
+import java.util.List;
+import java.util.stream.*;
+
+class Test {
+  public static void test(List<String> s) {
+    s.stream().collect(Collectors.toL<caret>ist()).contains("abc");
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToListOtherType.java b/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToListOtherType.java
new file mode 100644 (file)
index 0000000..10edac2
--- /dev/null
@@ -0,0 +1,10 @@
+// "Replace with 'java.util.ArrayList' constructor" "true"
+
+import java.util.List;
+import java.util.stream.*;
+
+class Test {
+  public static void test(List<String> s) {
+    s.stream().collect(Collectors.<Object>toL<caret>ist()).contains("abc");
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToSet.java b/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamToSet.java
new file mode 100644 (file)
index 0000000..574e4e3
--- /dev/null
@@ -0,0 +1,10 @@
+// "Replace with 'java.util.HashSet' constructor" "true"
+
+import java.util.List;
+import java.util.stream.*;
+
+class Test {
+  public static void test(List<String> s) {
+    s.stream().co<caret>llect(Collectors.toSet()).contains("abc");
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/SignatureCompletionTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/completion/SignatureCompletionTest.groovy
new file mode 100644 (file)
index 0000000..c2a504f
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInsight.completion
+
+import com.intellij.JavaTestUtil
+import com.intellij.codeInsight.template.impl.TemplateManagerImpl
+import com.intellij.openapi.util.registry.Registry
+/**
+ * @author peter
+ */
+class SignatureCompletionTest extends LightFixtureCompletionTestCase {
+
+  @Override
+  protected String getBasePath() {
+    return JavaTestUtil.getRelativeJavaTestDataPath() + "/codeInsight/completion/signature/"
+  }
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp()
+    Registry.get("java.completion.argument.live.template").value = true
+    Registry.get("java.completion.show.constructors").value = true
+    TemplateManagerImpl.setTemplateTesting(getProject(), getTestRootDisposable())
+  }
+
+  @Override
+  protected void tearDown() throws Exception {
+    Registry.get("java.completion.argument.live.template").value = false
+    Registry.get("java.completion.show.constructors").value = false
+    super.tearDown()
+  }
+
+  private checkResult() {
+    checkResultByFile(getTestName(false) + "_after.java")
+  }
+
+  private void doFirstItemTest() {
+    configureByTestName()
+    myFixture.type('\n')
+    checkResult()
+  }
+
+  void testOnlyDefaultConstructor() { doFirstItemTest() }
+
+  void testNonDefaultConstructor() { doFirstItemTest() }
+
+  void testAnonymousNonDefaultConstructor() { doFirstItemTest() }
+
+  void testSeveralConstructors() {
+    myFixture.configureByFile(getTestName(false) + ".java")
+    myFixture.complete(CompletionType.SMART)
+    def items = myFixture.lookup.items
+    assert items.size() == 3
+  }
+
+}
index d6cf7e6a9ad0dee953af6cede0219b8e56d9d9b4..5768afa9a7180145835554691ea1d314a915098d 100644 (file)
@@ -100,7 +100,7 @@ public class OrderEntryTest extends DaemonAnalyzerTestCase {
 
   private IntentionAction findActionAndCheck(final ActionHint actionHint, Collection<HighlightInfo> infosBefore) {
     List<IntentionAction> actions = LightQuickFixTestCase.getAvailableActions(getEditor(), getFile());
-    return actionHint.findAndCheck(actions, () -> "Infos: " + infosBefore);
+    return actionHint.findAndCheck(actions, "Infos: " + infosBefore);
   }
 
   public void testAddDependency() throws Exception {
index 425a6558ba4e26cd08e0eef5a98253bc60bd525b..b0bc698ad4c62bd49ff26dd0a35fe75a2bb26af6 100644 (file)
@@ -25,7 +25,6 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.util.Collection;
-import java.util.function.Supplier;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -72,19 +71,19 @@ public class ActionHint {
    * if this ActionHint asserts that no action should be present.
    *
    * @param actions actions collection to search inside
-   * @param infoSupplier a supplier which provides additional info which will be appended to exception message if check fails
+   * @param errorMessage an additional error message which will be appended to exception message if check fails
    * @return the action or null
    * @throws AssertionError if no action is found, but it should present, or if action is found, but it should not present.
    */
   @Nullable
-  public IntentionAction findAndCheck(Collection<IntentionAction> actions, Supplier<String> infoSupplier) {
+  public IntentionAction findAndCheck(@NotNull Collection<IntentionAction> actions, @NotNull String errorMessage) {
     IntentionAction result = actions.stream().filter(t -> t.getText().equals(myExpectedText)).findFirst().orElse(null);
     if(result == null && myShouldPresent) {
       fail("Action with text '" + myExpectedText + "' not found\nAvailable actions: " +
            actions.stream().map(IntentionAction::getText).collect(Collectors.joining(", ", "[", "]\n")) +
-           infoSupplier.get());
+           errorMessage);
     } else if(result != null && !myShouldPresent) {
-      fail("Action with text '" + myExpectedText + "' is present, but should not\n" + infoSupplier.get());
+      fail("Action with text '" + myExpectedText + "' is present, but should not\n" + errorMessage);
     }
     return result;
   }
index 19ec879a4f3585bf685186df6be29246d5b5c39c..7abf74ec5e7bab380f8a26a5386ee14e2102f3e1 100644 (file)
@@ -103,7 +103,7 @@ public abstract class LightQuickFixTestCase extends LightDaemonAnalyzerTestCase
                               String testName,
                               QuickFixTestCase quickFix) throws Exception {
     IntentionAction action = actionHint.findAndCheck(quickFix.getAvailableActions(),
-                                                     () -> "Test: "+testFullPath+"\nInfos: "+quickFix.doHighlighting());
+                                                     "Test: "+testFullPath+"\nInfos: "+quickFix.doHighlighting());
     if (action != null) {
       String text = action.getText();
       quickFix.invoke(action);
@@ -157,7 +157,7 @@ public abstract class LightQuickFixTestCase extends LightDaemonAnalyzerTestCase
   }
 
   protected IntentionAction findActionAndCheck(@NotNull ActionHint hint, String testFullPath) {
-    return hint.findAndCheck(getAvailableActions(), () -> "Test: "+testFullPath);
+    return hint.findAndCheck(getAvailableActions(), "Test: "+testFullPath);
   }
 
   protected IntentionAction findActionWithText(@NotNull String text) {
index a76b5d8fcf02ae13a5f2a3fb116e33c20bfa96df..22630d16bfd6f4b4385a93e842b14641645abdc6 100644 (file)
@@ -26,7 +26,7 @@ import org.jetbrains.jps.incremental.CompileContext;
 import org.jetbrains.jps.incremental.Utils;
 import org.jetbrains.jps.model.java.compiler.JavaCompilers;
 
-import javax.tools.*;
+import javax.tools.JavaCompiler;
 import java.io.File;
 import java.io.FilenameFilter;
 import java.util.Collections;
@@ -34,6 +34,9 @@ import java.util.List;
 import java.util.ServiceLoader;
 
 /**
+ * The latest version of ecj batch compiler can be found here:
+ * http://download.eclipse.org/eclipse/downloads/
+ *
  * @author nik
  */
 public class EclipseCompilerTool extends JavaCompilingTool {
index cbff0ab596f978cc7ebbabb55b348172c3f87f50..176ea7d64a3d9a8194b58877c830c6e3b08d6e3f 100644 (file)
@@ -44,9 +44,6 @@ public interface JsonElementTypes {
       else if (type == BOOLEAN_LITERAL) {
         return new JsonBooleanLiteralImpl(node);
       }
-      else if (type == LITERAL) {
-        return new JsonLiteralImpl(node);
-      }
       else if (type == NULL_LITERAL) {
         return new JsonNullLiteralImpl(node);
       }
@@ -65,9 +62,6 @@ public interface JsonElementTypes {
       else if (type == STRING_LITERAL) {
         return new JsonStringLiteralImpl(node);
       }
-      else if (type == VALUE) {
-        return new JsonValueImpl(node);
-      }
       throw new AssertionError("Unknown element type: " + type);
     }
   }
index 5046debe9f7bf646de919f3690ee38715406b2b0..e3dd969ea831d762fcc06f704813b822cee5b7f0 100644 (file)
@@ -64,9 +64,6 @@ public class JsonParser implements PsiParser, LightPsiParser {
   }
 
   public static final TokenSet[] EXTENDS_SETS_ = new TokenSet[] {
-    create_token_set_(ARRAY, OBJECT),
-    create_token_set_(BOOLEAN_LITERAL, LITERAL, NULL_LITERAL, NUMBER_LITERAL,
-      STRING_LITERAL),
     create_token_set_(ARRAY, BOOLEAN_LITERAL, LITERAL, NULL_LITERAL,
       NUMBER_LITERAL, OBJECT, REFERENCE_EXPRESSION, STRING_LITERAL,
       VALUE),
@@ -78,12 +75,12 @@ public class JsonParser implements PsiParser, LightPsiParser {
     if (!recursion_guard_(b, l, "array")) return false;
     if (!nextTokenIs(b, L_BRACKET)) return false;
     boolean r, p;
-    Marker m = enter_section_(b, l, _NONE_, null);
+    Marker m = enter_section_(b, l, _NONE_, ARRAY, null);
     r = consumeToken(b, L_BRACKET);
     p = r; // pin = 1
     r = r && report_error_(b, array_1(b, l + 1));
     r = p && consumeToken(b, R_BRACKET) && r;
-    exit_section_(b, l, m, ARRAY, r, p, null);
+    exit_section_(b, l, m, r, p, null);
     return r || p;
   }
 
@@ -104,11 +101,11 @@ public class JsonParser implements PsiParser, LightPsiParser {
   static boolean array_element(PsiBuilder b, int l) {
     if (!recursion_guard_(b, l, "array_element")) return false;
     boolean r, p;
-    Marker m = enter_section_(b, l, _NONE_, null);
+    Marker m = enter_section_(b, l, _NONE_);
     r = value(b, l + 1);
     p = r; // pin = 1
     r = r && array_element_1(b, l + 1);
-    exit_section_(b, l, m, null, r, p, not_bracket_or_next_value_parser_);
+    exit_section_(b, l, m, r, p, not_bracket_or_next_value_parser_);
     return r || p;
   }
 
@@ -127,9 +124,9 @@ public class JsonParser implements PsiParser, LightPsiParser {
   private static boolean array_element_1_1(PsiBuilder b, int l) {
     if (!recursion_guard_(b, l, "array_element_1_1")) return false;
     boolean r;
-    Marker m = enter_section_(b, l, _AND_, null);
+    Marker m = enter_section_(b, l, _AND_);
     r = consumeToken(b, R_BRACKET);
-    exit_section_(b, l, m, null, r, false, null);
+    exit_section_(b, l, m, r, false, null);
     return r;
   }
 
@@ -139,10 +136,10 @@ public class JsonParser implements PsiParser, LightPsiParser {
     if (!recursion_guard_(b, l, "boolean_literal")) return false;
     if (!nextTokenIs(b, "<boolean literal>", FALSE, TRUE)) return false;
     boolean r;
-    Marker m = enter_section_(b, l, _NONE_, "<boolean literal>");
+    Marker m = enter_section_(b, l, _NONE_, BOOLEAN_LITERAL, "<boolean literal>");
     r = consumeToken(b, TRUE);
     if (!r) r = consumeToken(b, FALSE);
-    exit_section_(b, l, m, BOOLEAN_LITERAL, r, false, null);
+    exit_section_(b, l, m, r, false, null);
     return r;
   }
 
@@ -168,12 +165,12 @@ public class JsonParser implements PsiParser, LightPsiParser {
   public static boolean literal(PsiBuilder b, int l) {
     if (!recursion_guard_(b, l, "literal")) return false;
     boolean r;
-    Marker m = enter_section_(b, l, _COLLAPSE_, "<literal>");
+    Marker m = enter_section_(b, l, _COLLAPSE_, LITERAL, "<literal>");
     r = string_literal(b, l + 1);
     if (!r) r = number_literal(b, l + 1);
     if (!r) r = boolean_literal(b, l + 1);
     if (!r) r = null_literal(b, l + 1);
-    exit_section_(b, l, m, LITERAL, r, false, null);
+    exit_section_(b, l, m, r, false, null);
     return r;
   }
 
@@ -182,9 +179,9 @@ public class JsonParser implements PsiParser, LightPsiParser {
   static boolean not_brace_or_next_value(PsiBuilder b, int l) {
     if (!recursion_guard_(b, l, "not_brace_or_next_value")) return false;
     boolean r;
-    Marker m = enter_section_(b, l, _NOT_, null);
+    Marker m = enter_section_(b, l, _NOT_);
     r = !not_brace_or_next_value_0(b, l + 1);
-    exit_section_(b, l, m, null, r, false, null);
+    exit_section_(b, l, m, r, false, null);
     return r;
   }
 
@@ -204,9 +201,9 @@ public class JsonParser implements PsiParser, LightPsiParser {
   static boolean not_bracket_or_next_value(PsiBuilder b, int l) {
     if (!recursion_guard_(b, l, "not_bracket_or_next_value")) return false;
     boolean r;
-    Marker m = enter_section_(b, l, _NOT_, null);
+    Marker m = enter_section_(b, l, _NOT_);
     r = !not_bracket_or_next_value_0(b, l + 1);
-    exit_section_(b, l, m, null, r, false, null);
+    exit_section_(b, l, m, r, false, null);
     return r;
   }
 
@@ -251,12 +248,12 @@ public class JsonParser implements PsiParser, LightPsiParser {
     if (!recursion_guard_(b, l, "object")) return false;
     if (!nextTokenIs(b, L_CURLY)) return false;
     boolean r, p;
-    Marker m = enter_section_(b, l, _NONE_, null);
+    Marker m = enter_section_(b, l, _NONE_, OBJECT, null);
     r = consumeToken(b, L_CURLY);
     p = r; // pin = 1
     r = r && report_error_(b, object_1(b, l + 1));
     r = p && consumeToken(b, R_CURLY) && r;
-    exit_section_(b, l, m, OBJECT, r, p, null);
+    exit_section_(b, l, m, r, p, null);
     return r || p;
   }
 
@@ -277,11 +274,11 @@ public class JsonParser implements PsiParser, LightPsiParser {
   static boolean object_element(PsiBuilder b, int l) {
     if (!recursion_guard_(b, l, "object_element")) return false;
     boolean r, p;
-    Marker m = enter_section_(b, l, _NONE_, null);
+    Marker m = enter_section_(b, l, _NONE_);
     r = property(b, l + 1);
     p = r; // pin = 1
     r = r && object_element_1(b, l + 1);
-    exit_section_(b, l, m, null, r, p, not_brace_or_next_value_parser_);
+    exit_section_(b, l, m, r, p, not_brace_or_next_value_parser_);
     return r || p;
   }
 
@@ -300,9 +297,9 @@ public class JsonParser implements PsiParser, LightPsiParser {
   private static boolean object_element_1_1(PsiBuilder b, int l) {
     if (!recursion_guard_(b, l, "object_element_1_1")) return false;
     boolean r;
-    Marker m = enter_section_(b, l, _AND_, null);
+    Marker m = enter_section_(b, l, _AND_);
     r = consumeToken(b, R_CURLY);
-    exit_section_(b, l, m, null, r, false, null);
+    exit_section_(b, l, m, r, false, null);
     return r;
   }
 
@@ -311,11 +308,11 @@ public class JsonParser implements PsiParser, LightPsiParser {
   public static boolean property(PsiBuilder b, int l) {
     if (!recursion_guard_(b, l, "property")) return false;
     boolean r, p;
-    Marker m = enter_section_(b, l, _NONE_, "<property>");
+    Marker m = enter_section_(b, l, _NONE_, PROPERTY, "<property>");
     r = property_name(b, l + 1);
     p = r; // pin = 1
     r = r && property_1(b, l + 1);
-    exit_section_(b, l, m, PROPERTY, r, p, null);
+    exit_section_(b, l, m, r, p, null);
     return r || p;
   }
 
@@ -323,11 +320,11 @@ public class JsonParser implements PsiParser, LightPsiParser {
   private static boolean property_1(PsiBuilder b, int l) {
     if (!recursion_guard_(b, l, "property_1")) return false;
     boolean r, p;
-    Marker m = enter_section_(b, l, _NONE_, null);
+    Marker m = enter_section_(b, l, _NONE_);
     r = consumeToken(b, COLON);
     p = r; // pin = 1
     r = r && value(b, l + 1);
-    exit_section_(b, l, m, null, r, p, null);
+    exit_section_(b, l, m, r, p, null);
     return r || p;
   }
 
@@ -361,10 +358,10 @@ public class JsonParser implements PsiParser, LightPsiParser {
     if (!recursion_guard_(b, l, "string_literal")) return false;
     if (!nextTokenIs(b, "<string literal>", DOUBLE_QUOTED_STRING, SINGLE_QUOTED_STRING)) return false;
     boolean r;
-    Marker m = enter_section_(b, l, _NONE_, "<string literal>");
+    Marker m = enter_section_(b, l, _NONE_, STRING_LITERAL, "<string literal>");
     r = consumeToken(b, SINGLE_QUOTED_STRING);
     if (!r) r = consumeToken(b, DOUBLE_QUOTED_STRING);
-    exit_section_(b, l, m, STRING_LITERAL, r, false, null);
+    exit_section_(b, l, m, r, false, null);
     return r;
   }
 
@@ -373,12 +370,12 @@ public class JsonParser implements PsiParser, LightPsiParser {
   public static boolean value(PsiBuilder b, int l) {
     if (!recursion_guard_(b, l, "value")) return false;
     boolean r;
-    Marker m = enter_section_(b, l, _COLLAPSE_, "<value>");
+    Marker m = enter_section_(b, l, _COLLAPSE_, VALUE, "<value>");
     r = object(b, l + 1);
     if (!r) r = array(b, l + 1);
     if (!r) r = literal(b, l + 1);
     if (!r) r = reference_expression(b, l + 1);
-    exit_section_(b, l, m, VALUE, r, false, null);
+    exit_section_(b, l, m, r, false, null);
     return r;
   }
 
index 018aeb334e022c611c34f8fdf00c6e1961466312..d6e698ef7abf41833619497eb84eaa068d3147f3 100644 (file)
@@ -17,8 +17,12 @@ public class JsonArrayImpl extends JsonContainerImpl implements JsonArray {
     super(node);
   }
 
+  public void accept(@NotNull JsonElementVisitor visitor) {
+    visitor.visitArray(this);
+  }
+
   public void accept(@NotNull PsiElementVisitor visitor) {
-    if (visitor instanceof JsonElementVisitor) ((JsonElementVisitor)visitor).visitArray(this);
+    if (visitor instanceof JsonElementVisitor) accept((JsonElementVisitor)visitor);
     else super.accept(visitor);
   }
 
index b7a6e607ff1ed89531c56fa18738ab8e4923a0f8..f8958b047083a3d1a40ec8da40cc70515226f0ab 100644 (file)
@@ -16,8 +16,12 @@ public class JsonBooleanLiteralImpl extends JsonLiteralImpl implements JsonBoole
     super(node);
   }
 
+  public void accept(@NotNull JsonElementVisitor visitor) {
+    visitor.visitBooleanLiteral(this);
+  }
+
   public void accept(@NotNull PsiElementVisitor visitor) {
-    if (visitor instanceof JsonElementVisitor) ((JsonElementVisitor)visitor).visitBooleanLiteral(this);
+    if (visitor instanceof JsonElementVisitor) accept((JsonElementVisitor)visitor);
     else super.accept(visitor);
   }
 
index 765b2c06a107135543ae8a9b791b4c42d3fb24a1..56365d5028586786f6ffa1114219fc03a585b69f 100644 (file)
@@ -16,8 +16,12 @@ public class JsonContainerImpl extends JsonValueImpl implements JsonContainer {
     super(node);
   }
 
+  public void accept(@NotNull JsonElementVisitor visitor) {
+    visitor.visitContainer(this);
+  }
+
   public void accept(@NotNull PsiElementVisitor visitor) {
-    if (visitor instanceof JsonElementVisitor) ((JsonElementVisitor)visitor).visitContainer(this);
+    if (visitor instanceof JsonElementVisitor) accept((JsonElementVisitor)visitor);
     else super.accept(visitor);
   }
 
index da4d688e4873896d6050d8cb265710da7344fe2b..840158283bd5225d110de681082a96d54114851c 100644 (file)
@@ -10,14 +10,18 @@ import com.intellij.psi.util.PsiTreeUtil;
 import static com.intellij.json.JsonElementTypes.*;
 import com.intellij.json.psi.*;
 
-public class JsonLiteralImpl extends JsonLiteralMixin implements JsonLiteral {
+public abstract class JsonLiteralImpl extends JsonLiteralMixin implements JsonLiteral {
 
   public JsonLiteralImpl(ASTNode node) {
     super(node);
   }
 
+  public void accept(@NotNull JsonElementVisitor visitor) {
+    visitor.visitLiteral(this);
+  }
+
   public void accept(@NotNull PsiElementVisitor visitor) {
-    if (visitor instanceof JsonElementVisitor) ((JsonElementVisitor)visitor).visitLiteral(this);
+    if (visitor instanceof JsonElementVisitor) accept((JsonElementVisitor)visitor);
     else super.accept(visitor);
   }
 
index c74a4616e5b8d776c1be26a31e4e951fd84119ba..55274253e2bdd1c983263ef037077f5d789dd57e 100644 (file)
@@ -16,8 +16,12 @@ public class JsonNullLiteralImpl extends JsonLiteralImpl implements JsonNullLite
     super(node);
   }
 
+  public void accept(@NotNull JsonElementVisitor visitor) {
+    visitor.visitNullLiteral(this);
+  }
+
   public void accept(@NotNull PsiElementVisitor visitor) {
-    if (visitor instanceof JsonElementVisitor) ((JsonElementVisitor)visitor).visitNullLiteral(this);
+    if (visitor instanceof JsonElementVisitor) accept((JsonElementVisitor)visitor);
     else super.accept(visitor);
   }
 
index f61e828ed29b8d2ee8b5592939e419b830e8d9b4..7ff647107787bfb7db41be3e1a34b2fb31ca014b 100644 (file)
@@ -16,8 +16,12 @@ public class JsonNumberLiteralImpl extends JsonLiteralImpl implements JsonNumber
     super(node);
   }
 
+  public void accept(@NotNull JsonElementVisitor visitor) {
+    visitor.visitNumberLiteral(this);
+  }
+
   public void accept(@NotNull PsiElementVisitor visitor) {
-    if (visitor instanceof JsonElementVisitor) ((JsonElementVisitor)visitor).visitNumberLiteral(this);
+    if (visitor instanceof JsonElementVisitor) accept((JsonElementVisitor)visitor);
     else super.accept(visitor);
   }
 
index a0a80e8802476c314c44abe39ada41b86d923f46..a5410cc10d3cc27cce907231d9eee16c9188eae8 100644 (file)
@@ -17,8 +17,12 @@ public class JsonObjectImpl extends JsonObjectMixin implements JsonObject {
     super(node);
   }
 
+  public void accept(@NotNull JsonElementVisitor visitor) {
+    visitor.visitObject(this);
+  }
+
   public void accept(@NotNull PsiElementVisitor visitor) {
-    if (visitor instanceof JsonElementVisitor) ((JsonElementVisitor)visitor).visitObject(this);
+    if (visitor instanceof JsonElementVisitor) accept((JsonElementVisitor)visitor);
     else super.accept(visitor);
   }
 
index 90422bb3a91eb9de3aee15815931b9674d0ffef9..5ec5a10946059072dd27eea3f35117fc2593b1de 100644 (file)
@@ -17,8 +17,12 @@ public class JsonPropertyImpl extends JsonPropertyMixin implements JsonProperty
     super(node);
   }
 
+  public void accept(@NotNull JsonElementVisitor visitor) {
+    visitor.visitProperty(this);
+  }
+
   public void accept(@NotNull PsiElementVisitor visitor) {
-    if (visitor instanceof JsonElementVisitor) ((JsonElementVisitor)visitor).visitProperty(this);
+    if (visitor instanceof JsonElementVisitor) accept((JsonElementVisitor)visitor);
     else super.accept(visitor);
   }
 
index c4d2ec080b8ae0bbe7d608f029cc3eed22d8b700..1a538f3bb3f0f03ae140b1904877419bde67e8a7 100644 (file)
@@ -16,8 +16,12 @@ public class JsonReferenceExpressionImpl extends JsonValueImpl implements JsonRe
     super(node);
   }
 
+  public void accept(@NotNull JsonElementVisitor visitor) {
+    visitor.visitReferenceExpression(this);
+  }
+
   public void accept(@NotNull PsiElementVisitor visitor) {
-    if (visitor instanceof JsonElementVisitor) ((JsonElementVisitor)visitor).visitReferenceExpression(this);
+    if (visitor instanceof JsonElementVisitor) accept((JsonElementVisitor)visitor);
     else super.accept(visitor);
   }
 
index c899097a4a76b8dd3d1e67cd926041e2a75f6c0c..33bb4ae9ae0f1ebcae32b949603952b6d58c0445 100644 (file)
@@ -18,8 +18,12 @@ public class JsonStringLiteralImpl extends JsonStringLiteralMixin implements Jso
     super(node);
   }
 
+  public void accept(@NotNull JsonElementVisitor visitor) {
+    visitor.visitStringLiteral(this);
+  }
+
   public void accept(@NotNull PsiElementVisitor visitor) {
-    if (visitor instanceof JsonElementVisitor) ((JsonElementVisitor)visitor).visitStringLiteral(this);
+    if (visitor instanceof JsonElementVisitor) accept((JsonElementVisitor)visitor);
     else super.accept(visitor);
   }
 
index c8fe9a4682fec346e75e51a29aeb66f8dc8946e9..545f94751d71a6715ad5b4d0104481a7768fe7cb 100644 (file)
@@ -10,14 +10,18 @@ import com.intellij.psi.util.PsiTreeUtil;
 import static com.intellij.json.JsonElementTypes.*;
 import com.intellij.json.psi.*;
 
-public class JsonValueImpl extends JsonElementImpl implements JsonValue {
+public abstract class JsonValueImpl extends JsonElementImpl implements JsonValue {
 
   public JsonValueImpl(ASTNode node) {
     super(node);
   }
 
+  public void accept(@NotNull JsonElementVisitor visitor) {
+    visitor.visitValue(this);
+  }
+
   public void accept(@NotNull PsiElementVisitor visitor) {
-    if (visitor instanceof JsonElementVisitor) ((JsonElementVisitor)visitor).visitValue(this);
+    if (visitor instanceof JsonElementVisitor) accept((JsonElementVisitor)visitor);
     else super.accept(visitor);
   }
 
index 2e103f41eee81a4450709997afca01a61e974973..556052e3a3d0c7441ee2d2bf697995a9f45ab0af 100644 (file)
@@ -128,7 +128,7 @@ literal ::= string_literal | number_literal | boolean_literal | null_literal {
   mixin="com.intellij.json.psi.impl.JsonLiteralMixin"
 }
 
-fake container ::= object | literal
+fake container ::=
 
 reference_expression ::= INDENTIFIER
 
diff --git a/lib/ecj-4.5.2.jar b/lib/ecj-4.5.2.jar
deleted file mode 100644 (file)
index 7457eda..0000000
Binary files a/lib/ecj-4.5.2.jar and /dev/null differ
diff --git a/lib/ecj-4.6.1.jar b/lib/ecj-4.6.1.jar
new file mode 100644 (file)
index 0000000..1c7e9ca
Binary files /dev/null and b/lib/ecj-4.6.1.jar differ
index 9ca949275b3cc06870f948dc8ebeff93718d0fec..38879ce13c8bbdd7bddd0e06835b269b68352b67 100644 (file)
@@ -13,7 +13,7 @@ httpcore-4.4.5.jar
 httpclient-4.5.2.jar
 fluent-hc-4.5.2.jar
 httpmime-4.5.2.jar
-ecj-4.5.2.jar
+ecj-4.6.1.jar
 groovy-all-2.4.6.jar
 gson-2.5.jar
 guava-19.0.jar
index 9c4e4a3cdcf98203b68aacd37e7ac1517128c9b6..bfb79838045a1b8b16040a60404feb3eea11c365 100644 (file)
@@ -260,13 +260,20 @@ public class TransactionGuardImpl extends TransactionGuard {
   @Override
   public void submitTransactionLater(@NotNull final Disposable parentDisposable, @NotNull final Runnable transaction) {
     final TransactionIdImpl id = getContextTransaction();
-    Runnable runnable = new Runnable() {
+    final ModalityState startModality = ModalityState.defaultModalityState();
+    invokeLater(new Runnable() {
       @Override
       public void run() {
-        submitTransaction(parentDisposable, id, transaction);
+        boolean allowWriting = ModalityState.current() == startModality;
+        AccessToken token = startActivity(allowWriting);
+        try {
+          submitTransaction(parentDisposable, id, transaction);
+        }
+        finally {
+          token.finish();
+        }
       }
-    };
-    invokeLater(runnable);
+    });
   }
 
   private static void invokeLater(Runnable runnable) {
index 28f83801977e24f34e5cbfef1372af1edfbb5e2f..fc56425127b85dc156f8e30dadde72ce465733b8 100644 (file)
@@ -58,11 +58,11 @@ public class DiffPsiFileSupport {
   }
 
 
-  private static boolean isDiffFile(@Nullable PsiFile file) {
+  public static boolean isDiffFile(@Nullable PsiFile file) {
     return file != null && isDiffFile(file.getVirtualFile());
   }
 
-  private static boolean isDiffFile(@Nullable VirtualFile file) {
+  public static boolean isDiffFile(@Nullable VirtualFile file) {
     return file != null && file.getUserData(KEY) == Boolean.TRUE;
   }
 }
index cffa472ff73b1c86c505e1bc655f98e37b4f0a7b..cfb1832cf095c557e4d4072698ea7f672cd00bb7 100644 (file)
@@ -180,12 +180,7 @@ public class GeneralCommandLine implements UserDataHolder {
     return myParentEnvironmentType != ParentEnvironmentType.NONE;
   }
 
-  /** @deprecated use {@link #withParentEnvironmentType(ParentEnvironmentType)} (to be removed in IDEA 2017.*) */
-  public GeneralCommandLine withPassParentEnvironment(boolean passParentEnvironment) {
-    return withParentEnvironmentType(passParentEnvironment ? ParentEnvironmentType.CONSOLE : ParentEnvironmentType.NONE);
-  }
-
-  /** @deprecated use {@link #withParentEnvironmentType(ParentEnvironmentType)} (to be removed in IDEA 2017.*) */
+  /** @deprecated use {@link #withParentEnvironmentType(ParentEnvironmentType)} (to be removed in IDEA 2018.*) */
   public void setPassParentEnvironment(boolean passParentEnvironment) {
     withParentEnvironmentType(passParentEnvironment ? ParentEnvironmentType.CONSOLE : ParentEnvironmentType.NONE);
   }
index 9fb0645567d588d93d6f25f46ab4b54e1be6c5d3..24965ef91f0cebb3c3bc4706efb5aef3e4ca37fa 100644 (file)
@@ -136,7 +136,7 @@ public class FileChooserDialogImpl extends DialogWrapper implements FileChooserD
       restoreSelection(null); // select last opened file
     }
     else {
-      selectInTree(toSelect, true);
+      selectInTree(toSelect, true, true);
     }
 
     show();
@@ -454,10 +454,10 @@ public class FileChooserDialogImpl extends DialogWrapper implements FileChooserD
 
       public void dropFiles(final List<VirtualFile> files) {
         if (!myChooserDescriptor.isChooseMultiple() && files.size() > 0) {
-          selectInTree(new VirtualFile[]{files.get(0)}, true);
+          selectInTree(new VirtualFile[]{files.get(0)}, true, true);
         }
         else {
-          selectInTree(VfsUtilCore.toVirtualFileArray(files), true);
+          selectInTree(VfsUtilCore.toVirtualFileArray(files), true, true);
         }
       }
     });
@@ -675,7 +675,7 @@ public class FileChooserDialogImpl extends DialogWrapper implements FileChooserD
   private void selectInTree(final VirtualFile vFile, String fromText) {
     if (vFile != null && vFile.isValid()) {
       if (fromText == null || fromText.equalsIgnoreCase(myPathTextField.getTextFieldText())) {
-        selectInTree(new VirtualFile[]{vFile}, false);
+        selectInTree(new VirtualFile[]{vFile}, false, fromText == null);
       }
     }
     else {
@@ -683,7 +683,7 @@ public class FileChooserDialogImpl extends DialogWrapper implements FileChooserD
     }
   }
 
-  private void selectInTree(final VirtualFile[] array, final boolean requestFocus) {
+  private void selectInTree(VirtualFile[] array, boolean requestFocus, boolean updatePathNeeded) {
     myTreeIsUpdating = true;
     final List<VirtualFile> fileList = Arrays.asList(array);
     if (!Arrays.asList(myFileSystemTree.getSelectedFiles()).containsAll(fileList)) {
@@ -691,20 +691,22 @@ public class FileChooserDialogImpl extends DialogWrapper implements FileChooserD
         if (!myFileSystemTree.areHiddensShown() && !Arrays.asList(myFileSystemTree.getSelectedFiles()).containsAll(fileList)) {
           // try to select files in hidden folders
           myFileSystemTree.showHiddens(true);
-          selectInTree(array, requestFocus);
+          selectInTree(array, requestFocus, updatePathNeeded);
           return;
         }
         if (array.length == 1 && !Arrays.asList(myFileSystemTree.getSelectedFiles()).containsAll(fileList)) {
           // try to select a parent of a missed file
           VirtualFile parent = array[0].getParent();
           if (parent != null && parent.isValid()) {
-            selectInTree(new VirtualFile[]{parent}, requestFocus);
+            selectInTree(new VirtualFile[]{parent}, requestFocus, updatePathNeeded);
             return;
           }
         }
 
         reportFileNotFound();
-        updatePathFromTree(fileList, true);
+        if (updatePathNeeded) {
+          updatePathFromTree(fileList, true);
+        }
         if (requestFocus) {
           //noinspection SSBasedInspection
           SwingUtilities.invokeLater(() -> myFileSystemTree.getTree().requestFocus());
@@ -713,7 +715,9 @@ public class FileChooserDialogImpl extends DialogWrapper implements FileChooserD
     }
     else {
       reportFileNotFound();
-      updatePathFromTree(fileList, true);
+      if (updatePathNeeded) {
+        updatePathFromTree(fileList, true);
+      }
     }
   }
 
index 3c21c3a1550406f3eac887bdcd873081c0c6a3c5..ed2e48dcf121df09bea888f43a98221ab2217af2 100644 (file)
@@ -36,7 +36,6 @@ import com.intellij.util.ui.UIUtil;
 import org.jetbrains.annotations.NotNull;
 
 import javax.swing.*;
-import javax.swing.border.EmptyBorder;
 import java.awt.*;
 import java.awt.event.*;
 import java.util.ArrayList;
@@ -272,12 +271,6 @@ public class EditorComboBox extends JComboBox implements DocumentListener {
     setEditor();
 
     super.addNotify();
-    if (UIUtil.isUnderDarcula() || UIUtil.isUnderIntelliJLaF()) {
-      final JScrollPane scrollPane = UIUtil.findComponentOfType(myEditorField, JScrollPane.class);
-      if (scrollPane != null) {
-        scrollPane.setBorder(new EmptyBorder(1,0,1,0));
-      }
-    }
     myEditorField.getFocusTarget().addFocusListener(new FocusAdapter() {
       @Override
       public void focusGained(FocusEvent e) {
index ad3fac4234eaf76cde1f1eff7aed522fdadbadda..4eb4a60bcbde20bf993b39e09ee26200f66f7472 100644 (file)
@@ -21,6 +21,7 @@ import com.intellij.openapi.application.PathManager;
 import com.intellij.openapi.util.SystemInfo;
 import com.intellij.openapi.util.io.FileUtilRt;
 import com.intellij.openapi.util.text.StringUtil;
+import com.sun.jna.Library;
 import com.sun.jna.Native;
 import com.sun.jna.Pointer;
 import com.sun.jna.WString;
@@ -40,13 +41,19 @@ public class Restarter {
 
   public static boolean isSupported() {
     if (SystemInfo.isWindows) {
-      return JnaLoader.isLoaded() && new File(PathManager.getBinPath(), "restarter.exe").exists();
+      return JnaLoader.isLoaded() &&
+             new File(PathManager.getBinPath(), "restarter.exe").exists();
     }
+
     if (SystemInfo.isMac) {
-      return PathManager.getHomePath().contains(".app") && new File(PathManager.getBinPath(), "restarter").canExecute();
+      return PathManager.getHomePath().contains(".app") &&
+             new File(PathManager.getBinPath(), "restarter").canExecute();
     }
+
     if (SystemInfo.isUnix) {
-      return CreateDesktopEntryAction.getLauncherScript() != null && new File(PathManager.getBinPath(), "restart.py").canExecute();
+      return JnaLoader.isLoaded() &&
+             CreateDesktopEntryAction.getLauncherScript() != null &&
+             new File(PathManager.getBinPath(), "restart.py").canExecute();
     }
 
     return false;
@@ -135,7 +142,12 @@ public class Restarter {
   private static void restartOnUnix(String... beforeRestart) throws IOException {
     String launcherScript = CreateDesktopEntryAction.getLauncherScript();
     if (launcherScript == null) throw new IOException("Launcher script not found in " + PathManager.getBinPath());
+
+    LibC lib = (LibC)Native.loadLibrary("c", LibC.class);
+    int pid = lib.getpid();
+
     doScheduleRestart(new File(PathManager.getBinPath(), "restart.py"), commands -> {
+      commands.add(String.valueOf(pid));
       commands.add(launcherScript);
       Collections.addAll(commands, beforeRestart);
     });
@@ -182,4 +194,9 @@ public class Restarter {
   private interface Shell32 extends StdCallLibrary {
     Pointer CommandLineToArgvW(WString command_line, IntByReference argc);
   }
+
+  @SuppressWarnings("SpellCheckingInspection")
+  private interface LibC extends Library {
+    int getpid();
+  }
 }
\ No newline at end of file
index 3c9cedb99107ec1d6473239d726f530d2a03f33d..18093567b21e12dccc1012daa0f36aecbf7442c8 100644 (file)
@@ -377,4 +377,14 @@ class TransactionTest extends LightPlatformTestCase {
     assert log == ['1', '2']
   }
 
+  void "test submitTransactionLater vs app invokeLater ordering in the same modality state"() {
+    TransactionGuard.submitTransaction testRootDisposable, {
+      log << '1'
+      guard.submitTransactionLater testRootDisposable, { log << '2' }
+      app.invokeLater { log << '3' }
+    }
+    UIUtil.dispatchAllInvocationEvents()
+    assert log == ['1', '2', '3']
+  }
+
 }
index ff0227571fedb9eb5def05e26bf42bdea463d804..6522283735f2c6a51ff8c8787e17604290c9292a 100644 (file)
@@ -2059,8 +2059,8 @@ class.with.only.private.constructors.problem.descriptor=Class <code>#ref</code>
 property.value.set.to.itself.display.name=Property value set to itself
 equals.with.itself.display.name='equals()' called on itself
 equals.with.itself.problem.descriptor=<code>#ref()</code> called on itself
-junit4.method.naming.convention.display.name=JUnit 4 test method naming convention
-junit4.method.naming.convention.element.description=JUnit 4 test method
+junit4.method.naming.convention.display.name=JUnit 4+ test method naming convention
+junit4.method.naming.convention.element.description=JUnit 4+ test method
 junit3.method.naming.convention.display.name=JUnit 3 test method naming convention
 junit3.method.naming.convention.element.description=JUnit 3 test method
 introduce.holder.class.quickfix=Introduce holder class
index 3479e2f04e564b0df644979533261f23b4de3ac2..1753ca0dd1b1c08ea87a8304b48a209a930a96b3 100644 (file)
@@ -50,13 +50,8 @@ public class AssertEqualsHint {
       return null;
     }
     final PsiClass containingClass = method.getContainingClass();
-    final boolean messageOnLastPosition = InheritanceUtil.isInheritor(containingClass, JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_ASSERTIONS) ||
-                                          InheritanceUtil.isInheritor(containingClass, "org.testng.Assert");
-    if (!InheritanceUtil.isInheritor(containingClass, JUnitCommonClassNames.JUNIT_FRAMEWORK_ASSERT) &&
-        !InheritanceUtil.isInheritor(containingClass, JUnitCommonClassNames.ORG_JUNIT_ASSERT) &&
-        !InheritanceUtil.isInheritor(containingClass, JUnitCommonClassNames.JUNIT_FRAMEWORK_TEST_CASE) &&
-        !InheritanceUtil.isInheritor(containingClass, "org.testng.AssertJUnit") &&
-        !messageOnLastPosition) {
+    final boolean messageOnLastPosition = isMessageOnLastPosition(containingClass);
+    if (!isMessageOnFirstPosition(containingClass) && !messageOnLastPosition) {
       return null;
     }
     final PsiParameterList parameterList = method.getParameterList();
@@ -83,6 +78,18 @@ public class AssertEqualsHint {
     return new AssertEqualsHint(argumentIndex, method);
   }
 
+  public static boolean isMessageOnFirstPosition(PsiClass containingClass) {
+    return InheritanceUtil.isInheritor(containingClass, JUnitCommonClassNames.JUNIT_FRAMEWORK_ASSERT) ||
+           InheritanceUtil.isInheritor(containingClass, JUnitCommonClassNames.ORG_JUNIT_ASSERT) ||
+           InheritanceUtil.isInheritor(containingClass, JUnitCommonClassNames.JUNIT_FRAMEWORK_TEST_CASE) ||
+           InheritanceUtil.isInheritor(containingClass, "org.testng.AssertJUnit");
+  }
+
+  public static boolean isMessageOnLastPosition(PsiClass containingClass) {
+    return InheritanceUtil.isInheritor(containingClass, JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_ASSERTIONS) ||
+           InheritanceUtil.isInheritor(containingClass, "org.testng.Assert");
+  }
+
   public static String areExpectedActualTypesCompatible(PsiMethodCallExpression expression) {
     final AssertEqualsHint assertEqualsHint = create(expression);
     if (assertEqualsHint == null) return null;
index 87d5ed2a37fb4eef3affca660018a560a2c071d8..9c98e1dc0f34adbb569ac19651f277a56cbdc84b 100644 (file)
@@ -82,8 +82,9 @@ public class AssertsWithoutMessagesInspection extends BaseInspection {
         return;
       }
       final PsiClass containingClass = method.getContainingClass();
-      if (!InheritanceUtil.isInheritor(containingClass, JUnitCommonClassNames.JUNIT_FRAMEWORK_ASSERT) &&
-          !InheritanceUtil.isInheritor(containingClass, JUnitCommonClassNames.ORG_JUNIT_ASSERT)) {
+      final boolean messageOnFirstPosition = AssertEqualsHint.isMessageOnFirstPosition(containingClass);
+      final boolean messageOnLastPosition = AssertEqualsHint.isMessageOnLastPosition(containingClass);
+      if (!messageOnFirstPosition && !messageOnLastPosition) {
         return;
       }
       final PsiParameterList parameterList = method.getParameterList();
@@ -98,7 +99,7 @@ public class AssertsWithoutMessagesInspection extends BaseInspection {
       }
       final PsiType stringType = TypeUtils.getStringType(expression);
       final PsiParameter[] parameters = parameterList.getParameters();
-      final PsiType parameterType1 = parameters[0].getType();
+      final PsiType parameterType1 = parameters[messageOnFirstPosition ? 0 : parameters.length - 1].getType();
       if (!parameterType1.equals(stringType)) {
         registerMethodCallError(expression);
         return;
@@ -106,7 +107,7 @@ public class AssertsWithoutMessagesInspection extends BaseInspection {
       if (parameters.length != 2) {
         return;
       }
-      final PsiType parameterType2 = parameters[1].getType();
+      final PsiType parameterType2 = parameters[messageOnFirstPosition ? parameterCount - 1 : 0].getType();
       if (!parameterType2.equals(stringType)) {
         return;
       }
index e02bfb7bc0fdc5a2459afd024dfd6ed9f45bc6e2..2f92530545dc4c362492cdf5f2ba3d046c38b87b 100644 (file)
@@ -31,7 +31,7 @@ import java.util.Set;
 public class ConstantJUnitAssertArgumentInspection extends BaseInspection {
 
   @NonNls
-  private static final Set<String> ASSERT_METHODS = new HashSet();
+  private static final Set<String> ASSERT_METHODS = new HashSet<>();
 
   static {
     ASSERT_METHODS.add("assertTrue");
@@ -60,28 +60,24 @@ public class ConstantJUnitAssertArgumentInspection extends BaseInspection {
     return new ConstantJUnitAssertArgumentVisitor();
   }
 
-  private static class ConstantJUnitAssertArgumentVisitor
-    extends BaseInspectionVisitor {
+  private static class ConstantJUnitAssertArgumentVisitor extends BaseInspectionVisitor {
 
     @Override
-    public void visitMethodCallExpression(
-      PsiMethodCallExpression expression) {
-      final PsiReferenceExpression methodExpression =
-        expression.getMethodExpression();
-      @NonNls final String methodName =
-        methodExpression.getReferenceName();
+    public void visitMethodCallExpression(PsiMethodCallExpression expression) {
+      final PsiReferenceExpression methodExpression = expression.getMethodExpression();
+      @NonNls final String methodName = methodExpression.getReferenceName();
       if (!ASSERT_METHODS.contains(methodName)) {
         return;
       }
+
       final PsiMethod method = expression.resolveMethod();
       if (method == null) {
         return;
       }
       final PsiClass containingClass = method.getContainingClass();
-      if (!InheritanceUtil.isInheritor(containingClass,
-                                       JUnitCommonClassNames.JUNIT_FRAMEWORK_ASSERT) &&
-          !InheritanceUtil.isInheritor(containingClass,
-                                       JUnitCommonClassNames.ORG_JUNIT_ASSERT)) {
+      final boolean messageOnFirstPosition = AssertEqualsHint.isMessageOnFirstPosition(containingClass);
+      final boolean messageOnLastPosition = AssertEqualsHint.isMessageOnLastPosition(containingClass);
+      if (!messageOnFirstPosition && !messageOnLastPosition) {
         return;
       }
       final PsiExpressionList argumentList = expression.getArgumentList();
@@ -89,11 +85,11 @@ public class ConstantJUnitAssertArgumentInspection extends BaseInspection {
       if (arguments.length == 0) {
         return;
       }
-      final PsiExpression lastArgument = arguments[arguments.length - 1];
-      if (!PsiUtil.isConstantExpression(lastArgument)) {
+      final PsiExpression argument = arguments[messageOnFirstPosition ? arguments.length - 1 : 0];
+      if (!PsiUtil.isConstantExpression(argument)) {
         return;
       }
-      registerError(lastArgument);
+      registerError(argument);
     }
   }
 }
index 07d5ecfb455d20b3b29e93ae32529ac07ee67953..459d63cbcf01663ac702f4e5ccb659e064a22176 100644 (file)
  */
 package com.siyeh.ig.junit;
 
+import com.intellij.codeInsight.TestFrameworks;
+import com.intellij.psi.PsiClass;
 import com.intellij.psi.PsiIdentifier;
 import com.intellij.psi.PsiMethod;
+import com.intellij.testIntegration.TestFramework;
 import com.siyeh.InspectionGadgetsBundle;
 import com.siyeh.ig.BaseInspectionVisitor;
 import com.siyeh.ig.naming.ConventionInspection;
@@ -68,7 +71,7 @@ public class JUnit4MethodNamingConventionInspectionBase extends ConventionInspec
     @Override
     public void visitMethod(PsiMethod method) {
       super.visitMethod(method);
-      if (!TestUtils.isJUnit4TestMethod(method) || !TestUtils.isRunnable(method)) {
+      if (!TestUtils.isAnnotatedTestMethod(method)) {
         return;
       }
       final PsiIdentifier nameIdentifier = method.getNameIdentifier();
index d44126fd027c3bf9cfd8503b385d3df2ecc8b025..d64e43fd7a60bec423b7bdb268e41e876dd3f438 100644 (file)
  */
 package com.siyeh.ig.junit;
 
+import com.intellij.codeInsight.TestFrameworks;
 import com.intellij.psi.*;
 import com.intellij.psi.util.InheritanceUtil;
+import com.intellij.testIntegration.TestFramework;
 import com.siyeh.InspectionGadgetsBundle;
 import com.siyeh.ig.BaseInspectionVisitor;
 import com.siyeh.ig.naming.ConventionInspection;
@@ -82,12 +84,12 @@ public class JUnitTestClassNamingConventionInspectionBase extends ConventionInsp
       if (aClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
         return;
       }
-      if (!InheritanceUtil.isInheritor(aClass,
-                                       JUnitCommonClassNames.JUNIT_FRAMEWORK_TEST_CASE)) {
-        if (!hasJUnit4TestMethods(aClass)) {
-          return;
-        }
+
+      final TestFramework framework = TestFrameworks.detectFramework(aClass);
+      if (framework == null || !framework.getName().startsWith("JUnit") || !framework.isTestClass(aClass)) {
+        return;
       }
+
       final String name = aClass.getName();
       if (name == null) {
         return;
@@ -107,19 +109,5 @@ public class JUnitTestClassNamingConventionInspectionBase extends ConventionInsp
         registerClassError(aClass, name);
       }
     }
-
-    private boolean hasJUnit4TestMethods(@NotNull PsiClass aClass) {
-      //use this if this method turns out to have bad performance:
-      //if (!TestUtils.isTest(aClass)) {
-      //    return false;
-      //}
-      final PsiMethod[] methods = aClass.getMethods();
-      for (PsiMethod method : methods) {
-        if (TestUtils.isJUnit4TestMethod(method)) {
-          return true;
-        }
-      }
-      return false;
-    }
   }
 }
index ace8cd165d8f512e4ccb64c872ea9887f5929e20..f885a677f53f73d92163b4832fb326d380736d3c 100644 (file)
@@ -57,7 +57,7 @@ public class TestMethodInProductCodeInspection extends BaseInspection {
     public void visitMethod(PsiMethod method) {
       final PsiClass containingClass = method.getContainingClass();
       if (TestUtils.isInTestSourceContent(containingClass) ||
-          !TestUtils.isJUnit4TestMethod(method)) {
+          !TestUtils.isAnnotatedTestMethod(method)) {
         return;
       }
       registerMethodError(method);
index f794e08a9c083e33d8c2bb54c189f46ac572059a..5f2b42d2d5334e1f5a3b2d711dabcdee934d94af 100644 (file)
@@ -37,6 +37,7 @@ public class TestMethodWithoutAssertionInspectionBase extends BaseInspection {
     methodMatcher = new MethodMatcher(true, "assertionMethods")
       .add(JUnitCommonClassNames.ORG_JUNIT_ASSERT, "assert.*|fail.*")
       .add(JUnitCommonClassNames.JUNIT_FRAMEWORK_ASSERT, "assert.*|fail.*")
+      .add(JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_ASSERTIONS, "assert.*|fail.*")
       .add("org.mockito.Mockito", "verify.*")
       .add("org.mockito.InOrder", "verify")
       .add("org.junit.rules.ExpectedException", "expect.*")
index 85fe6d20e8ca1b8fd9e783e0c6c495f799ce7c83..82cd4f109dd88dfb7f9fcf0f8a23a98dad677fce 100644 (file)
@@ -93,7 +93,7 @@ public class InstanceMethodNamingConventionInspectionBase extends ConventionInsp
         return;
       }
       if (TestUtils.isRunnable(method)) {
-        if (TestUtils.isJUnit4TestMethod(method) && isInspectionEnabled("JUnit4MethodNamingConvention", method)) {
+        if (TestUtils.isAnnotatedTestMethod(method) && isInspectionEnabled("JUnit4MethodNamingConvention", method)) {
           return;
         }
         if (TestUtils.isJUnit3TestMethod(method) && isInspectionEnabled("JUnit3MethodNamingConvention", method)) {
index eadaa038c86e0e0c494b5ddfdf03c9018723e7ca..68c85078e2719843ea149c7cda17c4a94eadcd6c 100644 (file)
@@ -24,6 +24,7 @@ import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.*;
 import com.intellij.psi.util.InheritanceUtil;
 import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.testIntegration.TestFramework;
 import com.siyeh.ig.junit.JUnitCommonClassNames;
 import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
@@ -62,7 +63,11 @@ public class TestUtils {
   }
 
   public static boolean isJUnitTestMethod(@Nullable PsiMethod method) {
-    return isRunnable(method) && (isJUnit3TestMethod(method) || isJUnit4TestMethod(method));
+    if (method == null) return false;
+    final PsiClass containingClass = method.getContainingClass();
+    if (containingClass == null) return false;
+    final TestFramework framework = TestFrameworks.detectFramework(containingClass);
+    return framework != null && framework.getName().startsWith("JUnit") && framework.isTestMethod(method);
   }
 
   public static boolean isRunnable(PsiMethod method) {
@@ -99,6 +104,21 @@ public class TestUtils {
     return method != null && AnnotationUtil.isAnnotated(method, "org.junit.Test", true);
   }
 
+  public static boolean isAnnotatedTestMethod(@Nullable PsiMethod method) {
+    if (method == null) return false;
+    final PsiClass containingClass = method.getContainingClass();
+    if (containingClass == null) return false;
+    final TestFramework testFramework = TestFrameworks.detectFramework(containingClass);
+    if (testFramework == null) return false;
+    if (testFramework.isTestMethod(method)) {
+      final String testFrameworkName = testFramework.getName();
+      return testFrameworkName.equals("JUnit4") || testFrameworkName.equals("JUnit5");
+    }
+    return false;
+  }
+
+
+
   public static boolean isJUnitTestClass(@Nullable PsiClass targetClass) {
     return targetClass != null && InheritanceUtil.isInheritor(targetClass, JUnitCommonClassNames.JUNIT_FRAMEWORK_TEST_CASE);
   }
index 59d5d2a62d9b60ca3637d898184cbdd8d2615591..b6922df84944dffc3c65d9b9890ec626f19a7b5c 100644 (file)
@@ -18,8 +18,7 @@ package com.siyeh.ig.junit;
 import com.siyeh.ig.InspectionGadgetsFix;
 import com.siyeh.ig.fixes.RenameFix;
 
-public class JUnitTestClassNamingConventionInspection
-  extends JUnitTestClassNamingConventionInspectionBase {
+public class JUnitTestClassNamingConventionInspection extends JUnitTestClassNamingConventionInspectionBase {
 
   @Override
   protected InspectionGadgetsFix buildFix(Object... infos) {
index 4d755dcf77d18443e7bf58a614696d61864e9a62..9cfe67755b83ee0e736e307236c65b4dd8613176 100644 (file)
@@ -34,14 +34,10 @@ import com.siyeh.InspectionGadgetsBundle;
 import com.siyeh.ig.BaseInspection;
 import com.siyeh.ig.BaseInspectionVisitor;
 import com.siyeh.ig.InspectionGadgetsFix;
-import com.siyeh.ig.psiutils.SideEffectChecker;
 import org.jetbrains.annotations.Nls;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.ArrayList;
-import java.util.List;
-
 public class MethodRefCanBeReplacedWithLambdaInspection extends BaseInspection {
 
   @Nls
@@ -74,16 +70,6 @@ public class MethodRefCanBeReplacedWithLambdaInspection extends BaseInspection {
     return null;
   }
 
-  public static boolean isWithSideEffects(PsiMethodReferenceExpression methodReferenceExpression) {
-    final PsiExpression qualifierExpression = methodReferenceExpression.getQualifierExpression();
-    if (qualifierExpression != null) {
-      final List<PsiElement> sideEffects = new ArrayList<>();
-      SideEffectChecker.checkSideEffects(qualifierExpression, sideEffects);
-      return !sideEffects.isEmpty();
-    }
-    return false;
-  }
-
   private static class MethodRefToLambdaVisitor extends BaseInspectionVisitor {
     @Override
     public void visitMethodReferenceExpression(PsiMethodReferenceExpression methodReferenceExpression) {
@@ -92,12 +78,13 @@ public class MethodRefCanBeReplacedWithLambdaInspection extends BaseInspection {
       if (interfaceType != null &&
           LambdaUtil.getFunctionalInterfaceMethod(interfaceType) != null &&
           methodReferenceExpression.resolve() != null) {
-        registerError(methodReferenceExpression, getFixFactory(isWithSideEffects(methodReferenceExpression), isOnTheFly()));
+        registerError(methodReferenceExpression,
+                      getFixFactory(LambdaRefactoringUtil.canConvertToLambda(methodReferenceExpression), isOnTheFly()));
       }
     }
 
-    private static FixFactory getFixFactory(boolean withSideEffects, boolean onTheFly) {
-      if (!withSideEffects) return MethodRefToLambdaFix::new;
+    private static FixFactory getFixFactory(boolean canConvert, boolean onTheFly) {
+      if (canConvert) return MethodRefToLambdaFix::new;
       if (onTheFly || ApplicationManager.getApplication().isUnitTestMode()) return SideEffectsMethodRefToLambdaFix::new;
       return null;
     }
index b2f7c7986a580d48173e759b4cad909778c0bbdf..213d1d6a20f71e19e66fd17665ad7bad5e3cb188 100644 (file)
@@ -1,11 +1,11 @@
 <html>
 <body>
-Reports JUnit 4 test methods whose names are either too short, too long, or do not follow the specified regular expression pattern.
+Reports JUnit 4+ test methods whose names are either too short, too long, or do not follow the specified regular expression pattern.
 When this inspection is enabled, the <i>Instance method naming convention</i> inspection
-will ignore JUnit 4 test methods automatically.
+will ignore JUnit 4+ test methods automatically.
 <!-- tooltip end -->
 <p>
-Use the fields below to specify minimum length, maximum length and regular expression expected for JUnit 4 test method names.
+Use the fields below to specify minimum length, maximum length and regular expression expected for JUnit 4+ test method names.
 Specify <b>0</b> to not check the length of names. Regular expressions are in standard <b>java.util.regex</b> format.
 <p>
 </body>
index deb66cd95ef45a779b6d688c18330b63b43ebc52..767453325178064f1880879619ab03b8281e7415 100644 (file)
@@ -1,6 +1,6 @@
 <html>
 <body>
-Reports JUnit 4.0 @Test methods in product source trees.
+Reports JUnit 4+ @Test methods in product source trees.
 This most likely indicates programmer error, and can result in test code being shipped
 into production.
 <!-- tooltip end -->
index 69379a9abb87311083df21f7b41df62100d80e15..4f23a659ba16ce53d6287539b6d53835ce50ef43 100644 (file)
@@ -3,13 +3,13 @@ import org.junit.Test;
 public class JUnit4MethodNamingConvention {
 
   @Test
-  public void <warning descr="JUnit 4 test method name 'a' is too short (1 < 4)">a</warning>() {}
+  public void <warning descr="JUnit 4+ test method name 'a' is too short (1 < 4)">a</warning>() {}
 
   @Test
-  public void <warning descr="JUnit 4 test method name 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz' is too long (78 > 64)">abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz</warning>() {}
+  public void <warning descr="JUnit 4+ test method name 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz' is too long (78 > 64)">abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz</warning>() {}
 
   @Test
-  public void <warning descr="JUnit 4 test method name 'more$$$' doesn't match regex '[a-z][A-Za-z_\d]*'">more$$$</warning>() {}
+  public void <warning descr="JUnit 4+ test method name 'more$$$' doesn't match regex '[a-z][A-Za-z_\d]*'">more$$$</warning>() {}
 
   @Test
   public void assure_foo_is_never_null() {}
index eb41bcfe34a9a2464b77382b9608dd82453b87f4..98c4d88becba990c93a1c0ad1aa10a5bc62a90de 100644 (file)
@@ -140,8 +140,7 @@ public abstract class SimpleCoverageAnnotator extends BaseCoverageAnnotator {
   protected FileCoverageInfo collectBaseFileCoverage(@NotNull final VirtualFile file,
                                                      @NotNull final Annotator annotator,
                                                      @NotNull final ProjectData projectData,
-                                                     @NotNull final Map<String, String> normalizedFiles2Files)
-  {
+                                                     @NotNull final Map<String, String> normalizedFiles2Files) {
     final String filePath = normalizeFilePath(file.getPath());
 
     // process file
@@ -166,8 +165,7 @@ public abstract class SimpleCoverageAnnotator extends BaseCoverageAnnotator {
   private static @Nullable ClassData getClassData(
     final @NotNull String filePath,
     final @NotNull ProjectData data,
-    final @NotNull Map<String, String> normalizedFiles2Files)
-  {
+    final @NotNull Map<String, String> normalizedFiles2Files) {
     final String originalFileName = normalizedFiles2Files.get(filePath);
     if (originalFileName == null) {
       return null;
@@ -272,8 +270,7 @@ public abstract class SimpleCoverageAnnotator extends BaseCoverageAnnotator {
                        @NotNull final CoverageSuitesBundle suite,
                        final @NotNull CoverageDataManager dataManager, @NotNull final ProjectData data,
                        final Project project,
-                       final Annotator annotator)
-  {
+                       final Annotator annotator) {
     if (!contentRoot.isValid()) {
       return;
     }
@@ -395,7 +392,7 @@ public abstract class SimpleCoverageAnnotator extends BaseCoverageAnnotator {
   }
 
   @Nullable
-  private static FileCoverageInfo fileInfoForCoveredFile(@NotNull final ClassData classData) {
+  private FileCoverageInfo fileInfoForCoveredFile(@NotNull final ClassData classData) {
     final Object[] lines = classData.getLines();
 
     // class data lines = [0, 1, ... count] but first element with index = #0 is fake and isn't
@@ -408,27 +405,31 @@ public abstract class SimpleCoverageAnnotator extends BaseCoverageAnnotator {
 
     final FileCoverageInfo info = new FileCoverageInfo();
 
-    int srcLinesCount = 0;
-    int coveredLinesCount = 0;
+    info.coveredLineCount = 0;
+    info.totalLineCount = 0;
     // let's count covered lines
     for (int i = 1; i <= count; i++) {
       final LineData lineData = classData.getLineData(i);
-      if (lineData == null) {
-        // Ignore not src code
-        continue;
-      }
-      final int status = lineData.getStatus();
-      // covered - if src code & covered (or inferred covered)
-      if (status != LineCoverage.NONE) {
-        coveredLinesCount++;
-      }
-      srcLinesCount++;
+
+      processLineData(info, lineData);
     }
-    info.totalLineCount = srcLinesCount;
-    info.coveredLineCount = coveredLinesCount;
     return info;
   }
 
+  protected void processLineData(@NotNull  FileCoverageInfo info, @Nullable  LineData lineData) {
+    if (lineData == null) {
+      // Ignore not src code
+      return;
+    }
+    final int status = lineData.getStatus();
+    // covered - if src code & covered (or inferred covered)
+
+    if (status != LineCoverage.NONE) {
+      info.coveredLineCount++;
+    }
+    info.totalLineCount++;
+  }
+
   @Nullable
   protected FileCoverageInfo fillInfoForUncoveredFile(@NotNull File file) {
     return null;
index 49408103f50f000d8cc39980c40c467e1bb720ad..a3de593134f661b5d0a41eab708d16117de7ab31 100644 (file)
@@ -21,6 +21,7 @@ import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.psi.PsiAnnotation;
 import com.intellij.psi.PsiNameValuePair;
 import com.intellij.psi.StubBasedPsiElement;
+import com.intellij.psi.stubs.EmptyStub;
 import com.intellij.util.IncorrectOperationException;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
@@ -30,14 +31,13 @@ import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
 import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationArgumentList;
 import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationNameValuePair;
 import org.jetbrains.plugins.groovy.lang.psi.impl.GrStubElementBase;
-import org.jetbrains.plugins.groovy.lang.psi.stubs.GrAnnotationArgumentListStub;
 
-public class GrAnnotationArgumentListImpl extends GrStubElementBase<GrAnnotationArgumentListStub>
-  implements GrAnnotationArgumentList, StubBasedPsiElement<GrAnnotationArgumentListStub> {
+public class GrAnnotationArgumentListImpl extends GrStubElementBase<EmptyStub>
+  implements GrAnnotationArgumentList, StubBasedPsiElement<EmptyStub> {
 
   private static final Logger LOG = Logger.getInstance(GrAnnotationArgumentListImpl.class);
 
-  public GrAnnotationArgumentListImpl(@NotNull GrAnnotationArgumentListStub stub) {
+  public GrAnnotationArgumentListImpl(@NotNull EmptyStub stub) {
     super(stub, GroovyElementTypes.ANNOTATION_ARGUMENTS);
   }
 
index a549bbf1cf95ca2f18f3fdbfbea134b4b48e1e65..ad347d0a543299308e1720175cf1dc3a6b303a42 100644 (file)
@@ -21,6 +21,7 @@ import com.intellij.openapi.util.TextRange;
 import com.intellij.psi.*;
 import com.intellij.psi.tree.IElementType;
 import com.intellij.reference.SoftReference;
+import com.intellij.testFramework.LightVirtualFile;
 import com.intellij.util.ArrayUtilRt;
 import com.intellij.util.IncorrectOperationException;
 import com.intellij.util.containers.ContainerUtilRt;
@@ -112,6 +113,7 @@ public class GrAnnotationNameValuePairImpl extends GrStubElementBase<GrNameValue
         GrAnnotation annotation = GroovyPsiElementFactory.getInstance(getProject()).createAnnotationFromText(
           "@F(" + text + ")", this
         );
+        ((LightVirtualFile)annotation.getContainingFile().getViewProvider().getVirtualFile()).setWritable(false);
         PsiAnnotationMemberValue value = annotation.findAttributeValue(null);
         myDetachedValue = new SoftReference<>(result = value);
       }
index 866bdb0f812dfc41d3d4342d852a19ccc6bbbbfe..f5231c5fec571ea73956760f9cec640857c72807 100644 (file)
  */
 package org.jetbrains.plugins.groovy.lang.psi.stubs.elements;
 
-import com.intellij.psi.stubs.StubElement;
-import com.intellij.psi.stubs.StubInputStream;
-import com.intellij.psi.stubs.StubOutputStream;
+import com.intellij.psi.stubs.EmptyStub;
+import com.intellij.psi.stubs.EmptyStubElementType;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.groovy.GroovyLanguage;
 import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationArgumentList;
 import org.jetbrains.plugins.groovy.lang.psi.impl.auxiliary.annotation.GrAnnotationArgumentListImpl;
-import org.jetbrains.plugins.groovy.lang.psi.stubs.GrAnnotationArgumentListStub;
 
-import java.io.IOException;
-
-public class GrAnnotationArgumentListElementType extends GrStubElementType<GrAnnotationArgumentListStub, GrAnnotationArgumentList> {
+public class GrAnnotationArgumentListElementType extends EmptyStubElementType<GrAnnotationArgumentList> {
 
   public GrAnnotationArgumentListElementType() {
-    super("annotation arguments");
+    super("annotation arguments", GroovyLanguage.INSTANCE);
   }
 
   @Override
@@ -37,23 +34,7 @@ public class GrAnnotationArgumentListElementType extends GrStubElementType<GrAnn
   }
 
   @Override
-  public void serialize(@NotNull GrAnnotationArgumentListStub stub, @NotNull StubOutputStream dataStream) throws IOException {
-  }
-
-  @NotNull
-  @Override
-  public GrAnnotationArgumentListStub deserialize(@NotNull StubInputStream dataStream, StubElement parentStub) throws IOException {
-    return new GrAnnotationArgumentListStub(parentStub);
-  }
-
-  @Override
-  public GrAnnotationArgumentList createPsi(@NotNull GrAnnotationArgumentListStub stub) {
+  public GrAnnotationArgumentList createPsi(@NotNull EmptyStub stub) {
     return new GrAnnotationArgumentListImpl(stub);
   }
-
-  @NotNull
-  @Override
-  public GrAnnotationArgumentListStub createStub(@NotNull GrAnnotationArgumentList psi, StubElement parentStub) {
-    return new GrAnnotationArgumentListStub(parentStub);
-  }
 }
index 829e6d02c2e55f593aa192dc95cab1b86540c8dc..ff40490b581439434f874142755958d220b5ae86 100644 (file)
@@ -40,7 +40,7 @@ import java.io.IOException;
  * @author ilyas
  */
 public class GrStubFileElementType extends IStubFileElementType<GrFileStub> {
-  public static final int STUB_VERSION = 31;
+  public static final int STUB_VERSION = 32;
 
   public GrStubFileElementType(Language language) {
     super(language);
index 2508e5c57e4ba2c69fd3bf5d3f1d0d3fd3758998..4b97dcc8b35d9fa4b39c0c908d4b3ea84127eda2 100644 (file)
@@ -17,13 +17,8 @@ package org.jetbrains.plugins.groovy.lang.psi.stubs
 
 import com.intellij.psi.stubs.StubBase
 import com.intellij.psi.stubs.StubElement
-import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes.ANNOTATION_ARGUMENTS
 import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes.ANNOTATION_MEMBER_VALUE_PAIR
-import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationArgumentList
 import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationNameValuePair
 
 class GrNameValuePairStub(parent: StubElement<*>?, val name: String?, val value: String?)
 : StubBase<GrAnnotationNameValuePair>(parent, ANNOTATION_MEMBER_VALUE_PAIR)
-
-class GrAnnotationArgumentListStub(parent: StubElement<*>?)
-: StubBase<GrAnnotationArgumentList>(parent, ANNOTATION_ARGUMENTS)
index 365fbb2a9dafbed5e60df505a70760ac255719a8..bf0742c74dbab78b896d30dba59f0529d2f0901d 100644 (file)
@@ -25,6 +25,7 @@ import com.intellij.execution.process.ProcessHandler;
 import com.intellij.execution.rmi.RemoteProcessSupport;
 import com.intellij.execution.runners.ProgramRunner;
 import com.intellij.notification.Notification;
+import com.intellij.notification.NotificationListener;
 import com.intellij.notification.NotificationType;
 import com.intellij.openapi.Disposable;
 import com.intellij.openapi.actionSystem.AnAction;
@@ -33,7 +34,9 @@ import com.intellij.openapi.components.PersistentStateComponent;
 import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.components.State;
 import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.options.ShowSettingsUtil;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
 import com.intellij.openapi.projectRoots.*;
 import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl;
 import com.intellij.openapi.roots.ProjectRootManager;
@@ -51,6 +54,7 @@ import org.jetbrains.annotations.Nullable;
 import org.jetbrains.annotations.TestOnly;
 import org.jetbrains.idea.maven.execution.MavenExecutionOptions;
 import org.jetbrains.idea.maven.execution.MavenRunnerSettings;
+import org.jetbrains.idea.maven.execution.RunnerBundle;
 import org.jetbrains.idea.maven.model.MavenExplicitProfiles;
 import org.jetbrains.idea.maven.model.MavenId;
 import org.jetbrains.idea.maven.model.MavenModel;
@@ -59,10 +63,12 @@ import org.jetbrains.idea.maven.project.MavenGeneralSettings;
 import org.jetbrains.idea.maven.project.MavenProjectsManager;
 import org.jetbrains.idea.maven.utils.MavenLog;
 import org.jetbrains.idea.maven.utils.MavenProgressIndicator;
+import org.jetbrains.idea.maven.utils.MavenSettings;
 import org.jetbrains.idea.maven.utils.MavenUtil;
 import org.slf4j.Logger;
 import org.slf4j.impl.Log4jLoggerFactory;
 
+import javax.swing.event.HyperlinkEvent;
 import java.io.File;
 import java.rmi.RemoteException;
 import java.rmi.server.UnicastRemoteObject;
@@ -283,14 +289,43 @@ public class MavenServerManager extends RemoteObjectWrapper<MavenServer> impleme
           }
         }
 
-        final String currentMavenVersion = forceMaven2 ? "2.2.1" : getCurrentMavenVersion();
-        params.getVMParametersList().addProperty(MavenServerEmbedder.MAVEN_EMBEDDER_VERSION, currentMavenVersion);
+        final File mavenHome;
+        final String mavenVersion;
+        final File currentMavenHomeFile = forceMaven2 ? BundledMavenPathHolder.myBundledMaven2Home : getCurrentMavenHomeFile();
+        if (currentMavenHomeFile == null) {
+          mavenHome = BundledMavenPathHolder.myBundledMaven3Home;
+          mavenVersion = getMavenVersion(mavenHome);
+
+          Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
+          final Project project = openProjects.length == 1 ? openProjects[0] : null;
+          if (project != null) {
+            new Notification(MavenUtil.MAVEN_NOTIFICATION_GROUP, "", RunnerBundle.message(
+              "external.maven.home.invalid.substitution.warning.with.fix", myState.mavenHome, mavenVersion), NotificationType.WARNING,
+                             new NotificationListener() {
+                               @Override
+                               public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
+                                 ShowSettingsUtil.getInstance().showSettingsDialog(project, MavenSettings.DISPLAY_NAME);
+                               }
+                             }).notify(null);
+          }
+          else {
+            new Notification(MavenUtil.MAVEN_NOTIFICATION_GROUP, "", RunnerBundle.message(
+              "external.maven.home.invalid.substitution.warning", myState.mavenHome, mavenVersion), NotificationType.WARNING).notify(null);
+          }
+        }
+        else {
+          mavenHome = currentMavenHomeFile;
+          mavenVersion = getMavenVersion(mavenHome);
+        }
+        assert mavenVersion != null;
+
+        params.getVMParametersList().addProperty(MavenServerEmbedder.MAVEN_EMBEDDER_VERSION, mavenVersion);
         String sdkConfigLocation = "Settings | Build, Execution, Deployment | Build Tools | Maven | Importing | JDK for Importer";
-        verifyMavenSdkRequirements(jdk, currentMavenVersion, sdkConfigLocation);
+        verifyMavenSdkRequirements(jdk, mavenVersion, sdkConfigLocation);
 
         final List<String> classPath = new ArrayList<>();
         classPath.add(PathUtil.getJarPathForClass(org.apache.log4j.Logger.class));
-        if (currentMavenVersion == null || StringUtil.compareVersionNumbers(currentMavenVersion, "3.1") < 0) {
+        if (StringUtil.compareVersionNumbers(mavenVersion, "3.1") < 0) {
           classPath.add(PathUtil.getJarPathForClass(Logger.class));
           classPath.add(PathUtil.getJarPathForClass(Log4jLoggerFactory.class));
         }
@@ -299,7 +334,7 @@ public class MavenServerManager extends RemoteObjectWrapper<MavenServer> impleme
         ContainerUtil.addIfNotNull(classPath, PathUtil.getJarPathForClass(Query.class));
         params.getClassPath().add(PathManager.getResourceRoot(getClass(), "/messages/CommonBundle.properties"));
         params.getClassPath().addAll(classPath);
-        params.getClassPath().addAllFiles(collectClassPathAndLibsFolder(forceMaven2));
+        params.getClassPath().addAllFiles(collectClassPathAndLibsFolder(mavenVersion, mavenHome));
 
         String embedderXmx = System.getProperty("idea.maven.embedder.xmx");
         if (embedderXmx != null) {
@@ -381,14 +416,12 @@ public class MavenServerManager extends RemoteObjectWrapper<MavenServer> impleme
     return MavenUtil.getMavenVersion(mavenHome);
   }
 
+  @Nullable
   public String getCurrentMavenVersion() {
     return getMavenVersion(myState.mavenHome);
   }
 
-  public List<File> collectClassPathAndLibsFolder(boolean forceMaven2) {
-    final String currentMavenVersion = forceMaven2 ? "2.2.1" : getCurrentMavenVersion();
-    File mavenHome = forceMaven2 ? BundledMavenPathHolder.myBundledMaven2Home : currentMavenVersion == null ? BundledMavenPathHolder.myBundledMaven3Home : getCurrentMavenHomeFile();
-
+  private static List<File> collectClassPathAndLibsFolder(@NotNull String mavenVersion, @NotNull File mavenHome) {
     final File pluginFileOrDir = new File(PathUtil.getJarPathForClass(MavenServerManager.class));
     final List<File> classpath = new ArrayList<>();
     final String root = pluginFileOrDir.getParent();
@@ -396,11 +429,11 @@ public class MavenServerManager extends RemoteObjectWrapper<MavenServer> impleme
     if (pluginFileOrDir.isDirectory()) {
       classpath.add(new File(root, "maven-server-api"));
       File parentFile = getMavenPluginParentFile();
-      if (forceMaven2 || (currentMavenVersion != null && StringUtil.compareVersionNumbers(currentMavenVersion, "3") < 0)) {
+      if (StringUtil.compareVersionNumbers(mavenVersion, "3") < 0) {
         classpath.add(new File(root, "maven2-server-impl"));
         addDir(classpath, new File(parentFile, "maven2-server-impl/lib"));
         // use bundled maven 2.2.1 for all 2.0.x version (since we use org.apache.maven.project.interpolation.StringSearchModelInterpolator introduced in 2.1.0)
-        if (StringUtil.compareVersionNumbers(currentMavenVersion, "2.1.0") < 0) {
+        if (StringUtil.compareVersionNumbers(mavenVersion, "2.1.0") < 0) {
           mavenHome = BundledMavenPathHolder.myBundledMaven2Home;
         }
       }
@@ -408,7 +441,7 @@ public class MavenServerManager extends RemoteObjectWrapper<MavenServer> impleme
         classpath.add(new File(root, "maven3-server-common"));
         addDir(classpath, new File(parentFile, "maven3-server-common/lib"));
 
-        if (currentMavenVersion == null || StringUtil.compareVersionNumbers(currentMavenVersion, "3.1") < 0) {
+        if (StringUtil.compareVersionNumbers(mavenVersion, "3.1") < 0) {
           classpath.add(new File(root, "maven30-server-impl"));
         }
         else {
@@ -419,7 +452,7 @@ public class MavenServerManager extends RemoteObjectWrapper<MavenServer> impleme
     else {
       classpath.add(new File(root, "maven-server-api.jar"));
 
-      if (forceMaven2 || (currentMavenVersion != null && StringUtil.compareVersionNumbers(currentMavenVersion, "3") < 0)) {
+      if (StringUtil.compareVersionNumbers(mavenVersion, "3") < 0) {
         classpath.add(new File(root, "maven2-server-impl.jar"));
         addDir(classpath, new File(root, "maven2-server-lib"));
       }
@@ -427,7 +460,7 @@ public class MavenServerManager extends RemoteObjectWrapper<MavenServer> impleme
         classpath.add(new File(root, "maven3-server-common.jar"));
         addDir(classpath, new File(root, "maven3-server-lib"));
 
-        if (currentMavenVersion == null || StringUtil.compareVersionNumbers(currentMavenVersion, "3.1") < 0) {
+        if (StringUtil.compareVersionNumbers(mavenVersion, "3.1") < 0) {
           classpath.add(new File(root, "maven30-server-impl.jar"));
         }
         else {
@@ -602,7 +635,7 @@ public class MavenServerManager extends RemoteObjectWrapper<MavenServer> impleme
 
   public boolean isUseMaven2() {
     final String version = getCurrentMavenVersion();
-    return StringUtil.compareVersionNumbers(version, "3") < 0 && StringUtil.compareVersionNumbers(version, "2") >= 0;
+    return version != null && StringUtil.compareVersionNumbers(version, "3") < 0 && StringUtil.compareVersionNumbers(version, "2") >= 0;
   }
 
   @TestOnly
index 4ebc0700a113eb0b8af5ed117d5d9a48b1fa6c04..8e9ec5416fa805acb4d2359c025fe2e0ec89c9eb 100644 (file)
@@ -16,6 +16,9 @@ external.maven.home.does.not.exist.with.fix=Specified Maven home directory ({0})
 external.maven.home.invalid={0} is not a valid Maven home directory
 external.maven.home.invalid.with.fix={0} is not a valid Maven home directory. <a href="#">Configure Maven home</a>
 
+external.maven.home.invalid.substitution.warning=Invalid Maven home directory configured <br>{0} <br>Bundled maven {1} will be used
+external.maven.home.invalid.substitution.warning.with.fix=Invalid Maven home directory configured <br>{0} <br>Bundled maven {1} will be used.  <a href="#">Configure Maven home</a>. 
+
 embedded.executor.caption=Executing Maven - using embedded Maven
 embedded.cannot.create=Cannot create Maven Embedder
 embedded.build.failed=BUILD FAILED
index f652c7ae1fb717b627457170c4ee11b3f4ba924a..84fa8f433273bdae80dab592c504db86fc846e9f 100644 (file)
@@ -1,15 +1,15 @@
 // This is a generated file. Not intended for manual editing.
 package com.jetbrains.commandInterface.commandLine;
 
-import com.intellij.lang.ASTNode;
-import com.intellij.lang.LightPsiParser;
 import com.intellij.lang.PsiBuilder;
 import com.intellij.lang.PsiBuilder.Marker;
-import com.intellij.lang.PsiParser;
-import com.intellij.psi.tree.IElementType;
-
 import static com.jetbrains.commandInterface.commandLine.CommandLineElementTypes.*;
 import static com.jetbrains.commandInterface.commandLine.CommandLineParserUtil.*;
+import com.intellij.psi.tree.IElementType;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.tree.TokenSet;
+import com.intellij.lang.PsiParser;
+import com.intellij.lang.LightPsiParser;
 
 @SuppressWarnings({"SimplifiableIfStatement", "UnusedAssignment"})
 public class CommandLineParser implements PsiParser, LightPsiParser {
@@ -47,11 +47,11 @@ public class CommandLineParser implements PsiParser, LightPsiParser {
   public static boolean argument(PsiBuilder b, int l) {
     if (!recursion_guard_(b, l, "argument")) return false;
     boolean r;
-    Marker m = enter_section_(b, l, _NONE_, "<argument>");
+    Marker m = enter_section_(b, l, _NONE_, ARGUMENT, "<argument>");
     r = consumeToken(b, LITERAL_STARTS_FROM_LETTER);
     if (!r) r = consumeToken(b, LITERAL_STARTS_FROM_DIGIT);
     if (!r) r = consumeToken(b, LITERAL_STARTS_FROM_SYMBOL);
-    exit_section_(b, l, m, ARGUMENT, r, false, null);
+    exit_section_(b, l, m, r, false, null);
     return r;
   }
 
@@ -79,10 +79,10 @@ public class CommandLineParser implements PsiParser, LightPsiParser {
     if (!recursion_guard_(b, l, "option")) return false;
     if (!nextTokenIs(b, "<option>", LONG_OPTION_NAME_TOKEN, SHORT_OPTION_NAME_TOKEN)) return false;
     boolean r;
-    Marker m = enter_section_(b, l, _NONE_, "<option>");
+    Marker m = enter_section_(b, l, _NONE_, OPTION, "<option>");
     r = option_0(b, l + 1);
     if (!r) r = option_1(b, l + 1);
-    exit_section_(b, l, m, OPTION, r, false, null);
+    exit_section_(b, l, m, r, false, null);
     return r;
   }
 
index 3600d86bc96a58ba3b5720a0f6fbaa38f865dab1..02d91dff0f79aa16682b8e9b25fbe84f891087b1 100644 (file)
@@ -1,12 +1,13 @@
 // This is a generated file. Not intended for manual editing.
 package com.jetbrains.commandInterface.commandLine.psi;
 
+import java.util.List;
+import org.jetbrains.annotations.*;
 import com.intellij.psi.PsiElement;
+import com.jetbrains.commandInterface.commandLine.CommandLinePart;
 import com.jetbrains.commandInterface.command.Argument;
 import com.jetbrains.commandInterface.command.Help;
 import com.jetbrains.commandInterface.command.Option;
-import com.jetbrains.commandInterface.commandLine.CommandLinePart;
-import org.jetbrains.annotations.Nullable;
 
 public interface CommandLineArgument extends CommandLinePart {
 
index 5660f65030a46b93447500ff875330727ac3fb82..3bdc0f09430d1f1d25e63ba9eb9d307b08aee153 100644 (file)
@@ -1,19 +1,18 @@
 // This is a generated file. Not intended for manual editing.
 package com.jetbrains.commandInterface.commandLine.psi.impl;
 
+import java.util.List;
+import org.jetbrains.annotations.*;
 import com.intellij.lang.ASTNode;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiElementVisitor;
+import com.intellij.psi.util.PsiTreeUtil;
+import static com.jetbrains.commandInterface.commandLine.CommandLineElementTypes.*;
+import com.jetbrains.commandInterface.commandLine.CommandLineElement;
+import com.jetbrains.commandInterface.commandLine.psi.*;
 import com.jetbrains.commandInterface.command.Argument;
 import com.jetbrains.commandInterface.command.Help;
 import com.jetbrains.commandInterface.command.Option;
-import com.jetbrains.commandInterface.commandLine.CommandLineElement;
-import com.jetbrains.commandInterface.commandLine.psi.CommandLineArgument;
-import com.jetbrains.commandInterface.commandLine.psi.CommandLineVisitor;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import static com.jetbrains.commandInterface.commandLine.CommandLineElementTypes.*;
 
 public class CommandLineArgumentImpl extends CommandLineElement implements CommandLineArgument {
 
@@ -21,8 +20,12 @@ public class CommandLineArgumentImpl extends CommandLineElement implements Comma
     super(node);
   }
 
+  public void accept(@NotNull CommandLineVisitor visitor) {
+    visitor.visitArgument(this);
+  }
+
   public void accept(@NotNull PsiElementVisitor visitor) {
-    if (visitor instanceof CommandLineVisitor) ((CommandLineVisitor)visitor).visitArgument(this);
+    if (visitor instanceof CommandLineVisitor) accept((CommandLineVisitor)visitor);
     else super.accept(visitor);
   }
 
index bcd0e883f0bcded388d02ae419cdc8994f41b8f1..40e799cb4012ebd6d1be4f85d6241e9e51fae6a2 100644 (file)
@@ -17,8 +17,12 @@ public class CommandLineCommandImpl extends CommandLineElement implements Comman
     super(node);
   }
 
+  public void accept(@NotNull CommandLineVisitor visitor) {
+    visitor.visitCommand(this);
+  }
+
   public void accept(@NotNull PsiElementVisitor visitor) {
-    if (visitor instanceof CommandLineVisitor) ((CommandLineVisitor)visitor).visitCommand(this);
+    if (visitor instanceof CommandLineVisitor) accept((CommandLineVisitor)visitor);
     else super.accept(visitor);
   }
 
index 919c39e2df7d843a6ed33d73f6ae875f58bd005c..dbfb17896c45da809fc212b55cfe3f7bbe1b76af 100644 (file)
@@ -18,8 +18,12 @@ public class CommandLineOptionImpl extends CommandLineElement implements Command
     super(node);
   }
 
+  public void accept(@NotNull CommandLineVisitor visitor) {
+    visitor.visitOption(this);
+  }
+
   public void accept(@NotNull PsiElementVisitor visitor) {
-    if (visitor instanceof CommandLineVisitor) ((CommandLineVisitor)visitor).visitOption(this);
+    if (visitor instanceof CommandLineVisitor) accept((CommandLineVisitor)visitor);
     else super.accept(visitor);
   }
 
index 193b7a107ebd3716e69333d11df05537c8208ed3..192239926b7f5843b708ee7ba035df6b1b31b5f9 100644 (file)
@@ -1,3 +1,6 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
+
 """Code coverage measurement for Python.
 
 Ned Batchelder
@@ -5,73 +8,16 @@ http://nedbatchelder.com/code/coverage
 
 """
 
-from coverage.version import __version__, __url__
+from coverage.version import __version__, __url__, version_info
 
-from coverage.control import coverage, process_startup
+from coverage.control import Coverage, process_startup
 from coverage.data import CoverageData
-from coverage.cmdline import main, CoverageScript
 from coverage.misc import CoverageException
+from coverage.plugin import CoveragePlugin, FileTracer, FileReporter
+from coverage.pytracer import PyTracer
 
-# Module-level functions.  The original API to this module was based on
-# functions defined directly in the module, with a singleton of the coverage()
-# class.  That design hampered programmability, so the current api uses
-# explicitly-created coverage objects.  But for backward compatibility, here we
-# define the top-level functions to create the singleton when they are first
-# called.
-
-# Singleton object for use with module-level functions.  The singleton is
-# created as needed when one of the module-level functions is called.
-_the_coverage = None
-
-def _singleton_method(name):
-    """Return a function to the `name` method on a singleton `coverage` object.
-
-    The singleton object is created the first time one of these functions is
-    called.
-
-    """
-    # Disable pylint msg W0612, because a bunch of variables look unused, but
-    # they're accessed via locals().
-    # pylint: disable=W0612
-
-    def wrapper(*args, **kwargs):
-        """Singleton wrapper around a coverage method."""
-        global _the_coverage
-        if not _the_coverage:
-            _the_coverage = coverage(auto_data=True)
-        return getattr(_the_coverage, name)(*args, **kwargs)
-
-    import inspect
-    meth = getattr(coverage, name)
-    args, varargs, kw, defaults = inspect.getargspec(meth)
-    argspec = inspect.formatargspec(args[1:], varargs, kw, defaults)
-    docstring = meth.__doc__
-    wrapper.__doc__ = ("""\
-        A first-use-singleton wrapper around coverage.%(name)s.
-
-        This wrapper is provided for backward compatibility with legacy code.
-        New code should use coverage.%(name)s directly.
-
-        %(name)s%(argspec)s:
-
-        %(docstring)s
-        """ % locals()
-        )
-
-    return wrapper
-
-
-# Define the module-level functions.
-use_cache = _singleton_method('use_cache')
-start =     _singleton_method('start')
-stop =      _singleton_method('stop')
-erase =     _singleton_method('erase')
-exclude =   _singleton_method('exclude')
-analysis =  _singleton_method('analysis')
-analysis2 = _singleton_method('analysis2')
-report =    _singleton_method('report')
-annotate =  _singleton_method('annotate')
-
+# Backward compatibility.
+coverage = Coverage
 
 # On Windows, we encode and decode deep enough that something goes wrong and
 # the encodings.utf_8 module is loaded and then unloaded, I don't know why.
@@ -87,34 +33,3 @@ try:
     del sys.modules['coverage.coverage']
 except KeyError:
     pass
-
-
-# COPYRIGHT AND LICENSE
-#
-# Copyright 2001 Gareth Rees.  All rights reserved.
-# Copyright 2004-2013 Ned Batchelder.  All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-# 1. Redistributions of source code must retain the above copyright
-#    notice, this list of conditions and the following disclaimer.
-#
-# 2. Redistributions in binary form must reproduce the above copyright
-#    notice, this list of conditions and the following disclaimer in the
-#    documentation and/or other materials provided with the
-#    distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
-# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
-# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
-# DAMAGE.
index 55e0d259e04acfb78ba6c4cd2386606aa65a6ad4..35ab87a56bf42edb948641bb97b43939ca86e69a 100644 (file)
@@ -1,4 +1,8 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
+
 """Coverage.py's main entry point."""
+
 import sys
 from coverage.cmdline import main
 sys.exit(main())
index 5c396784445cabaa28be0dac7a7316d67bfd7111..4060450fffb3bd5d8360421234e973495c13ab9e 100644 (file)
@@ -1,10 +1,19 @@
-"""Source file annotation for Coverage."""
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
 
-import os, re
+"""Source file annotation for coverage.py."""
 
-from coverage.backward import sorted                    # pylint: disable=W0622
+import io
+import os
+import re
+
+from coverage.files import flat_rootname
+from coverage.misc import isolate_module
 from coverage.report import Reporter
 
+os = isolate_module(os)
+
+
 class AnnotateReporter(Reporter):
     """Generate annotated source files showing line coverage.
 
@@ -42,61 +51,53 @@ class AnnotateReporter(Reporter):
         """
         self.report_files(self.annotate_file, morfs, directory)
 
-    def annotate_file(self, cu, analysis):
+    def annotate_file(self, fr, analysis):
         """Annotate a single file.
 
-        `cu` is the CodeUnit for the file to annotate.
+        `fr` is the FileReporter for the file to annotate.
 
         """
-        if not cu.relative:
-            return
-
-        filename = cu.filename
-        source = cu.source_file()
-        if self.directory:
-            dest_file = os.path.join(self.directory, cu.flat_rootname())
-            dest_file += ".py,cover"
-        else:
-            dest_file = filename + ",cover"
-        dest = open(dest_file, 'w')
-
         statements = sorted(analysis.statements)
         missing = sorted(analysis.missing)
         excluded = sorted(analysis.excluded)
 
-        lineno = 0
-        i = 0
-        j = 0
-        covered = True
-        while True:
-            line = source.readline()
-            if line == '':
-                break
-            lineno += 1
-            while i < len(statements) and statements[i] < lineno:
-                i += 1
-            while j < len(missing) and missing[j] < lineno:
-                j += 1
-            if i < len(statements) and statements[i] == lineno:
-                covered = j >= len(missing) or missing[j] > lineno
-            if self.blank_re.match(line):
-                dest.write('  ')
-            elif self.else_re.match(line):
-                # Special logic for lines containing only 'else:'.
-                if i >= len(statements) and j >= len(missing):
-                    dest.write('! ')
-                elif i >= len(statements) or j >= len(missing):
-                    dest.write('> ')
-                elif statements[i] == missing[j]:
-                    dest.write('! ')
+        if self.directory:
+            dest_file = os.path.join(self.directory, flat_rootname(fr.relative_filename()))
+            if dest_file.endswith("_py"):
+                dest_file = dest_file[:-3] + ".py"
+            dest_file += ",cover"
+        else:
+            dest_file = fr.filename + ",cover"
+
+        with io.open(dest_file, 'w', encoding='utf8') as dest:
+            i = 0
+            j = 0
+            covered = True
+            source = fr.source()
+            for lineno, line in enumerate(source.splitlines(True), start=1):
+                while i < len(statements) and statements[i] < lineno:
+                    i += 1
+                while j < len(missing) and missing[j] < lineno:
+                    j += 1
+                if i < len(statements) and statements[i] == lineno:
+                    covered = j >= len(missing) or missing[j] > lineno
+                if self.blank_re.match(line):
+                    dest.write(u'  ')
+                elif self.else_re.match(line):
+                    # Special logic for lines containing only 'else:'.
+                    if i >= len(statements) and j >= len(missing):
+                        dest.write(u'! ')
+                    elif i >= len(statements) or j >= len(missing):
+                        dest.write(u'> ')
+                    elif statements[i] == missing[j]:
+                        dest.write(u'! ')
+                    else:
+                        dest.write(u'> ')
+                elif lineno in excluded:
+                    dest.write(u'- ')
+                elif covered:
+                    dest.write(u'> ')
                 else:
-                    dest.write('> ')
-            elif lineno in excluded:
-                dest.write('- ')
-            elif covered:
-                dest.write('> ')
-            else:
-                dest.write('! ')
-            dest.write(line)
-        source.close()
-        dest.close()
+                    dest.write(u'! ')
+
+                dest.write(line)
diff --git a/python/helpers/coveragepy/coverage/backunittest.py b/python/helpers/coveragepy/coverage/backunittest.py
new file mode 100644 (file)
index 0000000..09574cc
--- /dev/null
@@ -0,0 +1,42 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
+
+"""Implementations of unittest features from the future."""
+
+# Use unittest2 if it's available, otherwise unittest.  This gives us
+# back-ported features for 2.6.
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
+
+
+def unittest_has(method):
+    """Does `unittest.TestCase` have `method` defined?"""
+    return hasattr(unittest.TestCase, method)
+
+
+class TestCase(unittest.TestCase):
+    """Just like unittest.TestCase, but with assert methods added.
+
+    Designed to be compatible with 3.1 unittest.  Methods are only defined if
+    `unittest` doesn't have them.
+
+    """
+    # pylint: disable=missing-docstring
+
+    # Many Pythons have this method defined.  But PyPy3 has a bug with it
+    # somehow (https://bitbucket.org/pypy/pypy/issues/2092), so always use our
+    # own implementation that works everywhere, at least for the ways we're
+    # calling it.
+    def assertCountEqual(self, s1, s2):
+        """Assert these have the same elements, regardless of order."""
+        self.assertEqual(sorted(s1), sorted(s2))
+
+    if not unittest_has('assertRaisesRegex'):
+        def assertRaisesRegex(self, *args, **kwargs):
+            return self.assertRaisesRegexp(*args, **kwargs)
+
+    if not unittest_has('assertRegex'):
+        def assertRegex(self, *args, **kwargs):
+            return self.assertRegexpMatches(*args, **kwargs)
index 7d2685459782c85c59ff7b78cf8d02adf4b2ca89..700c3ebd1c0afac0bea547894d05f7b1af3c4c88 100644 (file)
@@ -1,60 +1,29 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
+
 """Add things to old Pythons so I can pretend they are newer."""
 
-# This file does lots of tricky stuff, so disable a bunch of lintisms.
-# pylint: disable=F0401,W0611,W0622
-# F0401: Unable to import blah
-# W0611: Unused import blah
-# W0622: Redefining built-in blah
+# This file does lots of tricky stuff, so disable a bunch of pylint warnings.
+# pylint: disable=redefined-builtin
+# pylint: disable=unused-import
+# pxlint: disable=no-name-in-module
 
-import os, re, sys
+import sys
 
-# Python 2.3 doesn't have `set`
-try:
-    set = set       # new in 2.4
-except NameError:
-    from sets import Set as set
+from coverage import env
 
-# Python 2.3 doesn't have `sorted`.
-try:
-    sorted = sorted
-except NameError:
-    def sorted(iterable):
-        """A 2.3-compatible implementation of `sorted`."""
-        lst = list(iterable)
-        lst.sort()
-        return lst
 
-# Python 2.3 doesn't have `reversed`.
+# Pythons 2 and 3 differ on where to get StringIO.
 try:
-    reversed = reversed
-except NameError:
-    def reversed(iterable):
-        """A 2.3-compatible implementation of `reversed`."""
-        lst = list(iterable)
-        return lst[::-1]
-
-# rpartition is new in 2.5
-try:
-    "".rpartition
-except AttributeError:
-    def rpartition(s, sep):
-        """Implement s.rpartition(sep) for old Pythons."""
-        i = s.rfind(sep)
-        if i == -1:
-            return ('', '', s)
-        else:
-            return (s[:i], sep, s[i+len(sep):])
-else:
-    def rpartition(s, sep):
-        """A common interface for new Pythons."""
-        return s.rpartition(sep)
+    from cStringIO import StringIO
+except ImportError:
+    from io import StringIO
 
-# Pythons 2 and 3 differ on where to get StringIO
+# In py3, ConfigParser was renamed to the more-standard configparser
 try:
-    from cStringIO import StringIO
-    BytesIO = StringIO
+    import configparser
 except ImportError:
-    from io import StringIO, BytesIO
+    import ConfigParser as configparser
 
 # What's a string called?
 try:
@@ -62,6 +31,12 @@ try:
 except NameError:
     string_class = str
 
+# What's a Unicode string called?
+try:
+    unicode_class = unicode
+except NameError:
+    unicode_class = str
+
 # Where do pickles come from?
 try:
     import cPickle as pickle
@@ -72,7 +47,16 @@ except ImportError:
 try:
     range = xrange
 except NameError:
-    range = range
+    range = range       # pylint: disable=redefined-variable-type
+
+# shlex.quote is new, but there's an undocumented implementation in "pipes",
+# who knew!?
+try:
+    from shlex import quote as shlex_quote
+except ImportError:
+    # Useful function, available under a different (undocumented) name
+    # in Python versions earlier than 3.3.
+    from pipes import quote as shlex_quote
 
 # A function to iterate listlessly over a dict's items.
 try:
@@ -86,71 +70,32 @@ else:
         """Produce the items from dict `d`."""
         return d.iteritems()
 
-# Exec is a statement in Py2, a function in Py3
-if sys.version_info >= (3, 0):
-    def exec_code_object(code, global_map):
-        """A wrapper around exec()."""
-        exec(code, global_map)
-else:
-    # OK, this is pretty gross.  In Py2, exec was a statement, but that will
-    # be a syntax error if we try to put it in a Py3 file, even if it is never
-    # executed.  So hide it inside an evaluated string literal instead.
-    eval(
-        compile(
-            "def exec_code_object(code, global_map):\n"
-            "    exec code in global_map\n",
-            "<exec_function>", "exec"
-            )
-        )
-
-# Reading Python source and interpreting the coding comment is a big deal.
-if sys.version_info >= (3, 0):
-    # Python 3.2 provides `tokenize.open`, the best way to open source files.
-    import tokenize
-    try:
-        open_source = tokenize.open     # pylint: disable=E1101
-    except AttributeError:
-        from io import TextIOWrapper
-        detect_encoding = tokenize.detect_encoding  # pylint: disable=E1101
-        # Copied from the 3.2 stdlib:
-        def open_source(fname):
-            """Open a file in read only mode using the encoding detected by
-            detect_encoding().
-            """
-            buffer = open(fname, 'rb')
-            encoding, _ = detect_encoding(buffer.readline)
-            buffer.seek(0)
-            text = TextIOWrapper(buffer, encoding, line_buffering=True)
-            text.mode = 'r'
-            return text
+# Getting the `next` function from an iterator is different in 2 and 3.
+try:
+    iter([]).next
+except AttributeError:
+    def iternext(seq):
+        """Get the `next` function for iterating over `seq`."""
+        return iter(seq).__next__
 else:
-    def open_source(fname):
-        """Open a source file the best way."""
-        return open(fname, "rU")
-
+    def iternext(seq):
+        """Get the `next` function for iterating over `seq`."""
+        return iter(seq).next
 
 # Python 3.x is picky about bytes and strings, so provide methods to
 # get them right, and make them no-ops in 2.x
-if sys.version_info >= (3, 0):
+if env.PY3:
     def to_bytes(s):
         """Convert string `s` to bytes."""
         return s.encode('utf8')
 
-    def to_string(b):
-        """Convert bytes `b` to a string."""
-        return b.decode('utf8')
-
     def binary_bytes(byte_values):
         """Produce a byte string with the ints from `byte_values`."""
         return bytes(byte_values)
 
-    def byte_to_int(byte_value):
-        """Turn an element of a bytes object into an int."""
-        return byte_value
-
     def bytes_to_ints(bytes_value):
         """Turn a bytes object into a sequence of ints."""
-        # In Py3, iterating bytes gives ints.
+        # In Python 3, iterating bytes gives ints.
         return bytes_value
 
 else:
@@ -158,27 +103,70 @@ else:
         """Convert string `s` to bytes (no-op in 2.x)."""
         return s
 
-    def to_string(b):
-        """Convert bytes `b` to a string (no-op in 2.x)."""
-        return b
-
     def binary_bytes(byte_values):
         """Produce a byte string with the ints from `byte_values`."""
-        return "".join([chr(b) for b in byte_values])
-
-    def byte_to_int(byte_value):
-        """Turn an element of a bytes object into an int."""
-        return ord(byte_value)
+        return "".join(chr(b) for b in byte_values)
 
     def bytes_to_ints(bytes_value):
         """Turn a bytes object into a sequence of ints."""
         for byte in bytes_value:
             yield ord(byte)
 
-# Md5 is available in different places.
+
+try:
+    # In Python 2.x, the builtins were in __builtin__
+    BUILTINS = sys.modules['__builtin__']
+except KeyError:
+    # In Python 3.x, they're in builtins
+    BUILTINS = sys.modules['builtins']
+
+
+# imp was deprecated in Python 3.3
 try:
-    import hashlib
-    md5 = hashlib.md5
+    import importlib
+    import importlib.util
+    imp = None
 except ImportError:
-    import md5
-    md5 = md5.new
+    importlib = None
+
+# We only want to use importlib if it has everything we need.
+try:
+    importlib_util_find_spec = importlib.util.find_spec
+except Exception:
+    import imp
+    importlib_util_find_spec = None
+
+# What is the .pyc magic number for this version of Python?
+try:
+    PYC_MAGIC_NUMBER = importlib.util.MAGIC_NUMBER
+except AttributeError:
+    PYC_MAGIC_NUMBER = imp.get_magic()
+
+
+def import_local_file(modname, modfile=None):
+    """Import a local file as a module.
+
+    Opens a file in the current directory named `modname`.py, imports it
+    as `modname`, and returns the module object.  `modfile` is the file to
+    import if it isn't in the current directory.
+
+    """
+    try:
+        from importlib.machinery import SourceFileLoader
+    except ImportError:
+        SourceFileLoader = None
+
+    if modfile is None:
+        modfile = modname + '.py'
+    if SourceFileLoader:
+        mod = SourceFileLoader(modname, modfile).load_module()
+    else:
+        for suff in imp.get_suffixes():                 # pragma: part covered
+            if suff[0] == '.py':
+                break
+
+        with open(modfile, 'r') as f:
+            # pylint: disable=undefined-loop-variable
+            mod = imp.load_module(modname, f, modfile, suff)
+
+    return mod
index 85360638528e6bece33b1f5a1e4dd08e5c2ac3d0..d823c67c9200d7578b6610d5bf2a00e2f0c8347d 100644 (file)
@@ -1,62 +1,9 @@
-"""Bytecode manipulation for coverage.py"""
-
-import opcode, types
-
-from coverage.backward import byte_to_int
-
-class ByteCode(object):
-    """A single bytecode."""
-    def __init__(self):
-        # The offset of this bytecode in the code object.
-        self.offset = -1
-
-        # The opcode, defined in the `opcode` module.
-        self.op = -1
-
-        # The argument, a small integer, whose meaning depends on the opcode.
-        self.arg = -1
-
-        # The offset in the code object of the next bytecode.
-        self.next_offset = -1
-
-        # The offset to jump to.
-        self.jump_to = -1
-
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
 
-class ByteCodes(object):
-    """Iterator over byte codes in `code`.
-
-    Returns `ByteCode` objects.
-
-    """
-    # pylint: disable=R0924
-    def __init__(self, code):
-        self.code = code
-
-    def __getitem__(self, i):
-        return byte_to_int(self.code[i])
-
-    def __iter__(self):
-        offset = 0
-        while offset < len(self.code):
-            bc = ByteCode()
-            bc.op = self[offset]
-            bc.offset = offset
-
-            next_offset = offset+1
-            if bc.op >= opcode.HAVE_ARGUMENT:
-                bc.arg = self[offset+1] + 256*self[offset+2]
-                next_offset += 2
-
-                label = -1
-                if bc.op in opcode.hasjrel:
-                    label = next_offset + bc.arg
-                elif bc.op in opcode.hasjabs:
-                    label = bc.arg
-                bc.jump_to = label
+"""Bytecode manipulation for coverage.py"""
 
-            bc.next_offset = offset = next_offset
-            yield bc
+import types
 
 
 class CodeObjects(object):
index ea112a8b8f2d425be141bdde38a9603d9c685b34..09e8232313cdf7e5a751018bf5f124d26e2a88c6 100644 (file)
-"""Command-line support for Coverage."""
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
 
-import optparse, os, sys, time, traceback
+"""Command-line support for coverage.py."""
 
-from coverage.backward import sorted                # pylint: disable=W0622
+import glob
+import optparse
+import os.path
+import sys
+import textwrap
+import traceback
+
+from coverage import env
+from coverage.collector import CTracer
 from coverage.execfile import run_python_file, run_python_module
 from coverage.misc import CoverageException, ExceptionDuringRun, NoSource
-from coverage.debug import info_formatter
+from coverage.debug import info_formatter, info_header
 
 
 class Opts(object):
     """A namespace class for individual options we'll build parsers from."""
 
     append = optparse.make_option(
-        '-a', '--append', action='store_false', dest="erase_first",
-        help="Append coverage data to .coverage, otherwise it is started "
-                "clean with each run."
-        )
+        '-a', '--append', action='store_true',
+        help="Append coverage data to .coverage, otherwise it starts clean each time.",
+    )
     branch = optparse.make_option(
         '', '--branch', action='store_true',
-        help="Measure branch coverage in addition to statement coverage."
-        )
+        help="Measure branch coverage in addition to statement coverage.",
+    )
+    CONCURRENCY_CHOICES = [
+        "thread", "gevent", "greenlet", "eventlet", "multiprocessing",
+    ]
+    concurrency = optparse.make_option(
+        '', '--concurrency', action='store', metavar="LIB",
+        choices=CONCURRENCY_CHOICES,
+        help=(
+            "Properly measure code using a concurrency library. "
+            "Valid values are: %s."
+        ) % ", ".join(CONCURRENCY_CHOICES),
+    )
     debug = optparse.make_option(
         '', '--debug', action='store', metavar="OPTS",
-        help="Debug options, separated by commas"
-        )
+        help="Debug options, separated by commas",
+    )
     directory = optparse.make_option(
         '-d', '--directory', action='store', metavar="DIR",
-        help="Write the output files to DIR."
-        )
+        help="Write the output files to DIR.",
+    )
     fail_under = optparse.make_option(
         '', '--fail-under', action='store', metavar="MIN", type="int",
-        help="Exit with a status of 2 if the total coverage is less than MIN."
-        )
+        help="Exit with a status of 2 if the total coverage is less than MIN.",
+    )
     help = optparse.make_option(
         '-h', '--help', action='store_true',
-        help="Get help on this command."
-        )
+        help="Get help on this command.",
+    )
     ignore_errors = optparse.make_option(
         '-i', '--ignore-errors', action='store_true',
-        help="Ignore errors while reading source files."
-        )
+        help="Ignore errors while reading source files.",
+    )
     include = optparse.make_option(
         '', '--include', action='store',
         metavar="PAT1,PAT2,...",
-        help="Include files only when their filename path matches one of "
-                "these patterns.  Usually needs quoting on the command line."
-        )
+        help=(
+            "Include only files whose paths match one of these patterns. "
+            "Accepts shell-style wildcards, which must be quoted."
+        ),
+    )
     pylib = optparse.make_option(
         '-L', '--pylib', action='store_true',
-        help="Measure coverage even inside the Python installed library, "
-                "which isn't done by default."
-        )
+        help=(
+            "Measure coverage even inside the Python installed library, "
+            "which isn't done by default."
+        ),
+    )
     show_missing = optparse.make_option(
         '-m', '--show-missing', action='store_true',
-        help="Show line numbers of statements in each module that weren't "
-                "executed."
-        )
-    old_omit = optparse.make_option(
-        '-o', '--omit', action='store',
-        metavar="PAT1,PAT2,...",
-        help="Omit files when their filename matches one of these patterns. "
-                "Usually needs quoting on the command line."
-        )
+        help="Show line numbers of statements in each module that weren't executed.",
+    )
+    skip_covered = optparse.make_option(
+        '--skip-covered', action='store_true',
+        help="Skip files with 100% coverage.",
+    )
     omit = optparse.make_option(
         '', '--omit', action='store',
         metavar="PAT1,PAT2,...",
-        help="Omit files when their filename matches one of these patterns. "
-                "Usually needs quoting on the command line."
-        )
+        help=(
+            "Omit files whose paths match one of these patterns. "
+            "Accepts shell-style wildcards, which must be quoted."
+        ),
+    )
     output_xml = optparse.make_option(
         '-o', '', action='store', dest="outfile",
         metavar="OUTFILE",
-        help="Write the XML report to this file. Defaults to 'coverage.xml'"
-        )
+        help="Write the XML report to this file. Defaults to 'coverage.xml'",
+    )
     parallel_mode = optparse.make_option(
         '-p', '--parallel-mode', action='store_true',
-        help="Append the machine name, process id and random number to the "
-                ".coverage data file name to simplify collecting data from "
-                "many processes."
-        )
+        help=(
+            "Append the machine name, process id and random number to the "
+            ".coverage data file name to simplify collecting data from "
+            "many processes."
+        ),
+    )
     module = optparse.make_option(
         '-m', '--module', action='store_true',
-        help="<pyfile> is an importable Python module, not a script path, "
-                "to be run as 'python -m' would run it."
-        )
+        help=(
+            "<pyfile> is an importable Python module, not a script path, "
+            "to be run as 'python -m' would run it."
+        ),
+    )
     rcfile = optparse.make_option(
         '', '--rcfile', action='store',
-        help="Specify configuration file.  Defaults to '.coveragerc'"
-        )
+        help="Specify configuration file.  Defaults to '.coveragerc'",
+    )
     source = optparse.make_option(
         '', '--source', action='store', metavar="SRC1,SRC2,...",
-        help="A list of packages or directories of code to be measured."
-        )
+        help="A list of packages or directories of code to be measured.",
+    )
     timid = optparse.make_option(
         '', '--timid', action='store_true',
-        help="Use a simpler but slower trace method.  Try this if you get "
-                "seemingly impossible results!"
-        )
+        help=(
+            "Use a simpler but slower trace method.  Try this if you get "
+            "seemingly impossible results!"
+        ),
+    )
     title = optparse.make_option(
         '', '--title', action='store', metavar="TITLE",
-        help="A text string to use as the title on the HTML."
-        )
+        help="A text string to use as the title on the HTML.",
+    )
     version = optparse.make_option(
         '', '--version', action='store_true',
-        help="Display version information and exit."
-        )
+        help="Display version information and exit.",
+    )
 
 
 class CoverageOptionParser(optparse.OptionParser, object):
-    """Base OptionParser for coverage.
+    """Base OptionParser for coverage.py.
 
     Problems don't exit the program.
     Defaults are initialized for all options.
@@ -120,24 +148,26 @@ class CoverageOptionParser(optparse.OptionParser, object):
             add_help_option=False, *args, **kwargs
             )
         self.set_defaults(
-            actions=[],
+            action=None,
+            append=None,
             branch=None,
+            concurrency=None,
             debug=None,
             directory=None,
             fail_under=None,
             help=None,
             ignore_errors=None,
             include=None,
+            module=None,
             omit=None,
             parallel_mode=None,
-            module=None,
             pylib=None,
             rcfile=True,
             show_missing=None,
+            skip_covered=None,
             source=None,
             timid=None,
             title=None,
-            erase_first=None,
             version=None,
             )
 
@@ -152,7 +182,7 @@ class CoverageOptionParser(optparse.OptionParser, object):
         """Used to stop the optparse error handler ending the process."""
         pass
 
-    def parse_args(self, args=None, options=None):
+    def parse_args_ok(self, args=None, options=None):
         """Call optparse.parse_args, but return a triple:
 
         (ok, options, args)
@@ -171,189 +201,187 @@ class CoverageOptionParser(optparse.OptionParser, object):
         raise self.OptionParserError
 
 
-class ClassicOptionParser(CoverageOptionParser):
-    """Command-line parser for coverage.py classic arguments."""
+class GlobalOptionParser(CoverageOptionParser):
+    """Command-line parser for coverage.py global option arguments."""
 
     def __init__(self):
-        super(ClassicOptionParser, self).__init__()
-
-        self.add_action('-a', '--annotate', 'annotate')
-        self.add_action('-b', '--html', 'html')
-        self.add_action('-c', '--combine', 'combine')
-        self.add_action('-e', '--erase', 'erase')
-        self.add_action('-r', '--report', 'report')
-        self.add_action('-x', '--execute', 'execute')
+        super(GlobalOptionParser, self).__init__()
 
         self.add_options([
-            Opts.directory,
             Opts.help,
-            Opts.ignore_errors,
-            Opts.pylib,
-            Opts.show_missing,
-            Opts.old_omit,
-            Opts.parallel_mode,
-            Opts.timid,
             Opts.version,
         ])
 
-    def add_action(self, dash, dashdash, action_code):
-        """Add a specialized option that is the action to execute."""
-        option = self.add_option(dash, dashdash, action='callback',
-            callback=self._append_action
-            )
-        option.action_code = action_code
-
-    def _append_action(self, option, opt_unused, value_unused, parser):
-        """Callback for an option that adds to the `actions` list."""
-        parser.values.actions.append(option.action_code)
-
 
 class CmdOptionParser(CoverageOptionParser):
     """Parse one of the new-style commands for coverage.py."""
 
-    def __init__(self, action, options=None, defaults=None, usage=None,
-                cmd=None, description=None
-                ):
-        """Create an OptionParser for a coverage command.
+    def __init__(self, action, options, defaults=None, usage=None, description=None):
+        """Create an OptionParser for a coverage.py command.
 
-        `action` is the slug to put into `options.actions`.
+        `action` is the slug to put into `options.action`.
         `options` is a list of Option's for the command.
         `defaults` is a dict of default value for options.
         `usage` is the usage string to display in help.
-        `cmd` is the command name, if different than `action`.
         `description` is the description of the command, for the help text.
 
         """
         if usage:
             usage = "%prog " + usage
         super(CmdOptionParser, self).__init__(
-            prog="coverage %s" % (cmd or action),
             usage=usage,
             description=description,
         )
-        self.set_defaults(actions=[action], **(defaults or {}))
-        if options:
-            self.add_options(options)
-        self.cmd = cmd or action
+        self.set_defaults(action=action, **(defaults or {}))
+        self.add_options(options)
+        self.cmd = action
 
     def __eq__(self, other):
         # A convenience equality, so that I can put strings in unit test
         # results, and they will compare equal to objects.
         return (other == "<CmdOptionParser:%s>" % self.cmd)
 
+    def get_prog_name(self):
+        """Override of an undocumented function in optparse.OptionParser."""
+        program_name = super(CmdOptionParser, self).get_prog_name()
+
+        # Include the sub-command for this parser as part of the command.
+        return "%(command)s %(subcommand)s" % {'command': program_name, 'subcommand': self.cmd}
+
+
 GLOBAL_ARGS = [
-    Opts.rcfile,
+    Opts.debug,
     Opts.help,
+    Opts.rcfile,
     ]
 
 CMDS = {
-    'annotate': CmdOptionParser("annotate",
+    'annotate': CmdOptionParser(
+        "annotate",
         [
             Opts.directory,
             Opts.ignore_errors,
-            Opts.omit,
             Opts.include,
+            Opts.omit,
             ] + GLOBAL_ARGS,
-        usage = "[options] [modules]",
-        description = "Make annotated copies of the given files, marking "
-            "statements that are executed with > and statements that are "
-            "missed with !."
+        usage="[options] [modules]",
+        description=(
+            "Make annotated copies of the given files, marking statements that are executed "
+            "with > and statements that are missed with !."
         ),
+    ),
 
-    'combine': CmdOptionParser("combine", GLOBAL_ARGS,
-        usage = " ",
-        description = "Combine data from multiple coverage files collected "
+    'combine': CmdOptionParser(
+        "combine",
+        [
+            Opts.append,
+            ] + GLOBAL_ARGS,
+        usage="[options] <path1> <path2> ... <pathN>",
+        description=(
+            "Combine data from multiple coverage files collected "
             "with 'run -p'.  The combined results are written to a single "
-            "file representing the union of the data."
+            "file representing the union of the data. The positional "
+            "arguments are data files or directories containing data files. "
+            "If no paths are provided, data files in the default data file's "
+            "directory are combined."
         ),
+    ),
 
-    'debug': CmdOptionParser("debug", GLOBAL_ARGS,
-        usage = "<topic>",
-        description = "Display information on the internals of coverage.py, "
+    'debug': CmdOptionParser(
+        "debug", GLOBAL_ARGS,
+        usage="<topic>",
+        description=(
+            "Display information on the internals of coverage.py, "
             "for diagnosing problems. "
             "Topics are 'data' to show a summary of the collected data, "
             "or 'sys' to show installation information."
         ),
+    ),
 
-    'erase': CmdOptionParser("erase", GLOBAL_ARGS,
-        usage = " ",
-        description = "Erase previously collected coverage data."
-        ),
+    'erase': CmdOptionParser(
+        "erase", GLOBAL_ARGS,
+        description="Erase previously collected coverage data.",
+    ),
 
-    'help': CmdOptionParser("help", GLOBAL_ARGS,
-        usage = "[command]",
-        description = "Describe how to use coverage.py"
-        ),
+    'help': CmdOptionParser(
+        "help", GLOBAL_ARGS,
+        usage="[command]",
+        description="Describe how to use coverage.py",
+    ),
 
-    'html': CmdOptionParser("html",
+    'html': CmdOptionParser(
+        "html",
         [
             Opts.directory,
             Opts.fail_under,
             Opts.ignore_errors,
-            Opts.omit,
             Opts.include,
+            Opts.omit,
             Opts.title,
             ] + GLOBAL_ARGS,
-        usage = "[options] [modules]",
-        description = "Create an HTML report of the coverage of the files.  "
+        usage="[options] [modules]",
+        description=(
+            "Create an HTML report of the coverage of the files.  "
             "Each file gets its own page, with the source decorated to show "
             "executed, excluded, and missed lines."
         ),
+    ),
 
-    'report': CmdOptionParser("report",
+    'report': CmdOptionParser(
+        "report",
         [
             Opts.fail_under,
             Opts.ignore_errors,
-            Opts.omit,
             Opts.include,
+            Opts.omit,
             Opts.show_missing,
+            Opts.skip_covered,
             ] + GLOBAL_ARGS,
-        usage = "[options] [modules]",
-        description = "Report coverage statistics on modules."
-        ),
+        usage="[options] [modules]",
+        description="Report coverage statistics on modules."
+    ),
 
-    'run': CmdOptionParser("execute",
+    'run': CmdOptionParser(
+        "run",
         [
             Opts.append,
             Opts.branch,
-            Opts.debug,
+            Opts.concurrency,
+            Opts.include,
+            Opts.module,
+            Opts.omit,
             Opts.pylib,
             Opts.parallel_mode,
-            Opts.module,
-            Opts.timid,
             Opts.source,
-            Opts.omit,
-            Opts.include,
+            Opts.timid,
             ] + GLOBAL_ARGS,
-        defaults = {'erase_first': True},
-        cmd = "run",
-        usage = "[options] <pyfile> [program options]",
-        description = "Run a Python program, measuring code execution."
-        ),
+        usage="[options] <pyfile> [program options]",
+        description="Run a Python program, measuring code execution."
+    ),
 
-    'xml': CmdOptionParser("xml",
+    'xml': CmdOptionParser(
+        "xml",
         [
             Opts.fail_under,
             Opts.ignore_errors,
-            Opts.omit,
             Opts.include,
+            Opts.omit,
             Opts.output_xml,
             ] + GLOBAL_ARGS,
-        cmd = "xml",
-        usage = "[options] [modules]",
-        description = "Generate an XML report of coverage results."
-        ),
-    }
+        usage="[options] [modules]",
+        description="Generate an XML report of coverage results."
+    ),
+}
 
 
 OK, ERR, FAIL_UNDER = 0, 1, 2
 
 
 class CoverageScript(object):
-    """The command-line interface to Coverage."""
+    """The command-line interface to coverage.py."""
 
     def __init__(self, _covpkg=None, _run_python_file=None,
-                 _run_python_module=None, _help_fn=None):
+                 _run_python_module=None, _help_fn=None, _path_exists=None):
         # _covpkg is for dependency injection, so we can test this code.
         if _covpkg:
             self.covpkg = _covpkg
@@ -365,12 +393,26 @@ class CoverageScript(object):
         self.run_python_file = _run_python_file or run_python_file
         self.run_python_module = _run_python_module or run_python_module
         self.help_fn = _help_fn or self.help
-        self.classic = False
+        self.path_exists = _path_exists or os.path.exists
+        self.global_option = False
 
         self.coverage = None
 
+        self.program_name = os.path.basename(sys.argv[0])
+        if self.program_name == '__main__.py':
+            self.program_name = 'coverage'
+        if env.WINDOWS:
+            # entry_points={'console_scripts':...} on Windows makes files
+            # called coverage.exe, coverage3.exe, and coverage-3.5.exe. These
+            # invoke coverage-script.py, coverage3-script.py, and
+            # coverage-3.5-script.py.  argv[0] is the .py file, but we want to
+            # get back to the original form.
+            auto_suffix = "-script.py"
+            if self.program_name.endswith(auto_suffix):
+                self.program_name = self.program_name[:-len(auto_suffix)]
+
     def command_line(self, argv):
-        """The bulk of the command line interface to Coverage.
+        """The bulk of the command line interface to coverage.py.
 
         `argv` is the argument list to process.
 
@@ -382,11 +424,11 @@ class CoverageScript(object):
             self.help_fn(topic='minimum_help')
             return OK
 
-        # The command syntax we parse depends on the first argument.  Classic
-        # syntax always starts with an option.
-        self.classic = argv[0].startswith('-')
-        if self.classic:
-            parser = ClassicOptionParser()
+        # The command syntax we parse depends on the first argument.  Global
+        # switch syntax always starts with an option.
+        self.global_option = argv[0].startswith('-')
+        if self.global_option:
+            parser = GlobalOptionParser()
         else:
             parser = CMDS.get(argv[0])
             if not parser:
@@ -395,7 +437,7 @@ class CoverageScript(object):
             argv = argv[1:]
 
         parser.help_fn = self.help_fn
-        ok, options, args = parser.parse_args(argv)
+        ok, options, args = parser.parse_args_ok(argv)
         if not ok:
             return ERR
 
@@ -403,9 +445,9 @@ class CoverageScript(object):
         if self.do_help(options, args, parser):
             return OK
 
-        # Check for conflicts and problems in the options.
-        if not self.args_ok(options, args):
-            return ERR
+        # We need to be able to import from the current directory, because
+        # plugins may try to, for example, to read Django settings.
+        sys.path[0] = ''
 
         # Listify the list options.
         source = unshell_list(options.source)
@@ -415,74 +457,101 @@ class CoverageScript(object):
 
         # Do something.
         self.coverage = self.covpkg.coverage(
-            data_suffix = options.parallel_mode,
-            cover_pylib = options.pylib,
-            timid = options.timid,
-            branch = options.branch,
-            config_file = options.rcfile,
-            source = source,
-            omit = omit,
-            include = include,
-            debug = debug,
+            data_suffix=options.parallel_mode,
+            cover_pylib=options.pylib,
+            timid=options.timid,
+            branch=options.branch,
+            config_file=options.rcfile,
+            source=source,
+            omit=omit,
+            include=include,
+            debug=debug,
+            concurrency=options.concurrency,
             )
 
-        if 'debug' in options.actions:
+        if options.action == "debug":
             return self.do_debug(args)
 
-        if 'erase' in options.actions or options.erase_first:
+        elif options.action == "erase":
             self.coverage.erase()
-        else:
-            self.coverage.load()
+            return OK
 
-        if 'execute' in options.actions:
-            self.do_execute(options, args)
+        elif options.action == "run":
+            return self.do_run(options, args)
 
-        if 'combine' in options.actions:
-            self.coverage.combine()
+        elif options.action == "combine":
+            if options.append:
+                self.coverage.load()
+            data_dirs = args or None
+            self.coverage.combine(data_dirs)
             self.coverage.save()
+            return OK
 
         # Remaining actions are reporting, with some common options.
         report_args = dict(
-            morfs = args,
-            ignore_errors = options.ignore_errors,
-            omit = omit,
-            include = include,
+            morfs=unglob_args(args),
+            ignore_errors=options.ignore_errors,
+            omit=omit,
+            include=include,
             )
 
-        if 'report' in options.actions:
+        self.coverage.load()
+
+        total = None
+        if options.action == "report":
             total = self.coverage.report(
-                show_missing=options.show_missing, **report_args)
-        if 'annotate' in options.actions:
+                show_missing=options.show_missing,
+                skip_covered=options.skip_covered, **report_args)
+        elif options.action == "annotate":
             self.coverage.annotate(
                 directory=options.directory, **report_args)
-        if 'html' in options.actions:
+        elif options.action == "html":
             total = self.coverage.html_report(
                 directory=options.directory, title=options.title,
                 **report_args)
-        if 'xml' in options.actions:
+        elif options.action == "xml":
             outfile = options.outfile
             total = self.coverage.xml_report(outfile=outfile, **report_args)
 
-        if options.fail_under is not None:
-            if total >= options.fail_under:
-                return OK
-            else:
-                return FAIL_UNDER
-        else:
-            return OK
+        if total is not None:
+            # Apply the command line fail-under options, and then use the config
+            # value, so we can get fail_under from the config file.
+            if options.fail_under is not None:
+                self.coverage.set_option("report:fail_under", options.fail_under)
+
+            if self.coverage.get_option("report:fail_under"):
+                # Total needs to be rounded, but don't want to report 100
+                # unless it is really 100.
+                if 99 < total < 100:
+                    total = 99
+                else:
+                    total = round(total)
+
+                if total >= self.coverage.get_option("report:fail_under"):
+                    return OK
+                else:
+                    return FAIL_UNDER
+
+        return OK
 
     def help(self, error=None, topic=None, parser=None):
         """Display an error message, or the named topic."""
         assert error or topic or parser
         if error:
             print(error)
-            print("Use 'coverage help' for help.")
+            print("Use '%s help' for help." % (self.program_name,))
         elif parser:
             print(parser.format_help().strip())
         else:
-            help_msg = HELP_TOPICS.get(topic, '').strip()
+            help_params = dict(self.covpkg.__dict__)
+            help_params['program_name'] = self.program_name
+            if CTracer is not None:
+                help_params['extension_modifier'] = 'with C extension'
+            else:
+                help_params['extension_modifier'] = 'without C extension'
+            help_msg = textwrap.dedent(HELP_TOPICS.get(topic, '')).strip()
             if help_msg:
-                print(help_msg % self.covpkg.__dict__)
+                print(help_msg.format(**help_params))
             else:
                 print("Don't know topic %r" % topic)
 
@@ -494,13 +563,13 @@ class CoverageScript(object):
         """
         # Handle help.
         if options.help:
-            if self.classic:
+            if self.global_option:
                 self.help_fn(topic='help')
             else:
                 self.help_fn(parser=parser)
             return True
 
-        if "help" in options.actions:
+        if options.action == "help":
             if args:
                 for a in args:
                     parser = CMDS.get(a)
@@ -519,98 +588,97 @@ class CoverageScript(object):
 
         return False
 
-    def args_ok(self, options, args):
-        """Check for conflicts and problems in the options.
-
-        Returns True if everything is ok, or False if not.
-
-        """
-        for i in ['erase', 'execute']:
-            for j in ['annotate', 'html', 'report', 'combine']:
-                if (i in options.actions) and (j in options.actions):
-                    self.help_fn("You can't specify the '%s' and '%s' "
-                              "options at the same time." % (i, j))
-                    return False
-
-        if not options.actions:
-            self.help_fn(
-                "You must specify at least one of -e, -x, -c, -r, -a, or -b."
-                )
-            return False
-        args_allowed = (
-            'execute' in options.actions or
-            'annotate' in options.actions or
-            'html' in options.actions or
-            'debug' in options.actions or
-            'report' in options.actions or
-            'xml' in options.actions
-            )
-        if not args_allowed and args:
-            self.help_fn("Unexpected arguments: %s" % " ".join(args))
-            return False
+    def do_run(self, options, args):
+        """Implementation of 'coverage run'."""
 
-        if 'execute' in options.actions and not args:
+        if not args:
             self.help_fn("Nothing to do.")
-            return False
-
-        return True
+            return ERR
 
-    def do_execute(self, options, args):
-        """Implementation of 'coverage run'."""
+        if options.append and self.coverage.get_option("run:parallel"):
+            self.help_fn("Can't append to data files in parallel mode.")
+            return ERR
 
-        # Set the first path element properly.
-        old_path0 = sys.path[0]
+        if options.concurrency == "multiprocessing":
+            # Can't set other run-affecting command line options with
+            # multiprocessing.
+            for opt_name in ['branch', 'include', 'omit', 'pylib', 'source', 'timid']:
+                # As it happens, all of these options have no default, meaning
+                # they will be None if they have not been specified.
+                if getattr(options, opt_name) is not None:
+                    self.help_fn(
+                        "Options affecting multiprocessing must be specified "
+                        "in a configuration file."
+                    )
+                    return ERR
+
+        if not self.coverage.get_option("run:parallel"):
+            if not options.append:
+                self.coverage.erase()
 
         # Run the script.
         self.coverage.start()
         code_ran = True
         try:
-            try:
-                if options.module:
-                    sys.path[0] = ''
-                    self.run_python_module(args[0], args)
-                else:
-                    filename = args[0]
-                    sys.path[0] = os.path.abspath(os.path.dirname(filename))
-                    self.run_python_file(filename, args)
-            except NoSource:
-                code_ran = False
-                raise
+            if options.module:
+                self.run_python_module(args[0], args)
+            else:
+                filename = args[0]
+                self.run_python_file(filename, args)
+        except NoSource:
+            code_ran = False
+            raise
         finally:
             self.coverage.stop()
             if code_ran:
+                if options.append:
+                    data_file = self.coverage.get_option("run:data_file")
+                    if self.path_exists(data_file):
+                        self.coverage.combine(data_paths=[data_file])
                 self.coverage.save()
 
-            # Restore the old path
-            sys.path[0] = old_path0
+        return OK
 
     def do_debug(self, args):
         """Implementation of 'coverage debug'."""
 
         if not args:
-            self.help_fn("What information would you like: data, sys?")
+            self.help_fn("What information would you like: config, data, sys?")
             return ERR
+
         for info in args:
             if info == 'sys':
-                print("-- sys ----------------------------------------")
-                for line in info_formatter(self.coverage.sysinfo()):
+                sys_info = self.coverage.sys_info()
+                print(info_header("sys"))
+                for line in info_formatter(sys_info):
                     print(" %s" % line)
             elif info == 'data':
-                print("-- data ---------------------------------------")
                 self.coverage.load()
-                print("path: %s" % self.coverage.data.filename)
-                print("has_arcs: %r" % self.coverage.data.has_arcs())
-                summary = self.coverage.data.summary(fullpath=True)
-                if summary:
+                data = self.coverage.data
+                print(info_header("data"))
+                print("path: %s" % self.coverage.data_files.filename)
+                if data:
+                    print("has_arcs: %r" % data.has_arcs())
+                    summary = data.line_counts(fullpath=True)
                     filenames = sorted(summary.keys())
                     print("\n%d files:" % len(filenames))
                     for f in filenames:
-                        print("%s: %d lines" % (f, summary[f]))
+                        line = "%s: %d lines" % (f, summary[f])
+                        plugin = data.file_tracer(f)
+                        if plugin:
+                            line += " [%s]" % plugin
+                        print(line)
                 else:
                     print("No data collected")
+            elif info == 'config':
+                print(info_header("config"))
+                config_info = self.coverage.config.__dict__.items()
+                for line in info_formatter(config_info):
+                    print(" %s" % line)
             else:
                 self.help_fn("Don't know what you mean by %r" % info)
                 return ERR
+
         return OK
 
 
@@ -618,98 +686,63 @@ def unshell_list(s):
     """Turn a command-line argument into a list."""
     if not s:
         return None
-    if sys.platform == 'win32':
-        # When running coverage as coverage.exe, some of the behavior
+    if env.WINDOWS:
+        # When running coverage.py as coverage.exe, some of the behavior
         # of the shell is emulated: wildcards are expanded into a list of
-        # filenames.  So you have to single-quote patterns on the command
+        # file names.  So you have to single-quote patterns on the command
         # line, but (not) helpfully, the single quotes are included in the
         # argument, so we have to strip them off here.
         s = s.strip("'")
     return s.split(',')
 
 
+def unglob_args(args):
+    """Interpret shell wildcards for platforms that need it."""
+    if env.WINDOWS:
+        globbed = []
+        for arg in args:
+            if '?' in arg or '*' in arg:
+                globbed.extend(glob.glob(arg))
+            else:
+                globbed.append(arg)
+        args = globbed
+    return args
+
+
 HELP_TOPICS = {
-# -------------------------
-'classic':
-r"""Coverage.py version %(__version__)s
-Measure, collect, and report on code coverage in Python programs.
-
-Usage:
-
-coverage -x [-p] [-L] [--timid] MODULE.py [ARG1 ARG2 ...]
-    Execute the module, passing the given command-line arguments, collecting
-    coverage data.  With the -p option, include the machine name and process
-    id in the .coverage file name.  With -L, measure coverage even inside the
-    Python installed library, which isn't done by default.  With --timid, use a
-    simpler but slower trace method.
-
-coverage -e
-    Erase collected coverage data.
-
-coverage -c
-    Combine data from multiple coverage files (as created by -p option above)
-    and store it into a single file representing the union of the coverage.
-
-coverage -r [-m] [-i] [-o DIR,...] [FILE1 FILE2 ...]
-    Report on the statement coverage for the given files.  With the -m
-    option, show line numbers of the statements that weren't executed.
-
-coverage -b -d DIR [-i] [-o DIR,...] [FILE1 FILE2 ...]
-    Create an HTML report of the coverage of the given files.  Each file gets
-    its own page, with the file listing decorated to show executed, excluded,
-    and missed lines.
-
-coverage -a [-d DIR] [-i] [-o DIR,...] [FILE1 FILE2 ...]
-    Make annotated copies of the given files, marking statements that
-    are executed with > and statements that are missed with !.
-
--d DIR
-    Write output files for -b or -a to this directory.
-
--i  Ignore errors while reporting or annotating.
-
--o DIR,...
-    Omit reporting or annotating files when their filename path starts with
-    a directory listed in the omit list.
-    e.g. coverage -i -r -o c:\python25,lib\enthought\traits
-
-Coverage data is saved in the file .coverage by default.  Set the
-COVERAGE_FILE environment variable to save it somewhere else.
-""",
-# -------------------------
-'help': """\
-Coverage.py, version %(__version__)s
-Measure, collect, and report on code coverage in Python programs.
-
-usage: coverage <command> [options] [args]
-
-Commands:
-    annotate    Annotate source files with execution information.
-    combine     Combine a number of data files.
-    erase       Erase previously collected coverage data.
-    help        Get help on using coverage.py.
-    html        Create an HTML report.
-    report      Report coverage stats on modules.
-    run         Run a Python program and measure code execution.
-    xml         Create an XML report of coverage results.
-
-Use "coverage help <command>" for detailed help on any command.
-Use "coverage help classic" for help on older command syntax.
-For more information, see %(__url__)s
-""",
-# -------------------------
-'minimum_help': """\
-Code coverage for Python.  Use 'coverage help' for help.
-""",
-# -------------------------
-'version': """\
-Coverage.py, version %(__version__)s.  %(__url__)s
-""",
+    'help': """\
+        Coverage.py, version {__version__} {extension_modifier}
+        Measure, collect, and report on code coverage in Python programs.
+
+        usage: {program_name} <command> [options] [args]
+
+        Commands:
+            annotate    Annotate source files with execution information.
+            combine     Combine a number of data files.
+            erase       Erase previously collected coverage data.
+            help        Get help on using coverage.py.
+            html        Create an HTML report.
+            report      Report coverage stats on modules.
+            run         Run a Python program and measure code execution.
+            xml         Create an XML report of coverage results.
+
+        Use "{program_name} help <command>" for detailed help on any command.
+        For full documentation, see {__url__}
+    """,
+
+    'minimum_help': """\
+        Code coverage for Python.  Use '{program_name} help' for help.
+    """,
+
+    'version': """\
+        Coverage.py, version {__version__} {extension_modifier}
+        Documentation at {__url__}
+    """,
 }
 
 
 def main(argv=None):
-    """The main entry point to Coverage.
+    """The main entry point to coverage.py.
 
     This is installed as the script entry point.
 
@@ -717,26 +750,19 @@ def main(argv=None):
     if argv is None:
         argv = sys.argv[1:]
     try:
-        start = time.clock()
         status = CoverageScript().command_line(argv)
-        end = time.clock()
-        if 0:
-            print("time: %.3fs" % (end - start))
-    except ExceptionDuringRun:
+    except ExceptionDuringRun as err:
         # An exception was caught while running the product code.  The
         # sys.exc_info() return tuple is packed into an ExceptionDuringRun
         # exception.
-        _, err, _ = sys.exc_info()
         traceback.print_exception(*err.args)
         status = ERR
-    except CoverageException:
+    except CoverageException as err:
         # A controlled error inside coverage.py: print the message to the user.
-        _, err, _ = sys.exc_info()
         print(err)
         status = ERR
-    except SystemExit:
+    except SystemExit as err:
         # The user called `sys.exit()`.  Exit with their argument, if any.
-        _, err, _ = sys.exc_info()
         if err.args:
             status = err.args[0]
         else:
diff --git a/python/helpers/coveragepy/coverage/codeunit.py b/python/helpers/coveragepy/coverage/codeunit.py
deleted file mode 100644 (file)
index ca1ae5c..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-"""Code unit (module) handling for Coverage."""
-
-import glob, os
-
-from coverage.backward import open_source, string_class, StringIO
-from coverage.misc import CoverageException
-
-
-def code_unit_factory(morfs, file_locator):
-    """Construct a list of CodeUnits from polymorphic inputs.
-
-    `morfs` is a module or a filename, or a list of same.
-
-    `file_locator` is a FileLocator that can help resolve filenames.
-
-    Returns a list of CodeUnit objects.
-
-    """
-    # Be sure we have a list.
-    if not isinstance(morfs, (list, tuple)):
-        morfs = [morfs]
-
-    # On Windows, the shell doesn't expand wildcards.  Do it here.
-    globbed = []
-    for morf in morfs:
-        if isinstance(morf, string_class) and ('?' in morf or '*' in morf):
-            globbed.extend(glob.glob(morf))
-        else:
-            globbed.append(morf)
-    morfs = globbed
-
-    code_units = [CodeUnit(morf, file_locator) for morf in morfs]
-
-    return code_units
-
-
-class CodeUnit(object):
-    """Code unit: a filename or module.
-
-    Instance attributes:
-
-    `name` is a human-readable name for this code unit.
-    `filename` is the os path from which we can read the source.
-    `relative` is a boolean.
-
-    """
-    def __init__(self, morf, file_locator):
-        self.file_locator = file_locator
-
-        if hasattr(morf, '__file__'):
-            f = morf.__file__
-        else:
-            f = morf
-        # .pyc files should always refer to a .py instead.
-        if f.endswith('.pyc') or f.endswith('.pyo'):
-            f = f[:-1]
-        elif f.endswith('$py.class'): # Jython
-            f = f[:-9] + ".py"
-        self.filename = self.file_locator.canonical_filename(f)
-
-        if hasattr(morf, '__name__'):
-            n = modname = morf.__name__
-            self.relative = True
-        else:
-            n = os.path.splitext(morf)[0]
-            rel = self.file_locator.relative_filename(n)
-            if os.path.isabs(n):
-                self.relative = (rel != n)
-            else:
-                self.relative = True
-            n = rel
-            modname = None
-        self.name = n
-        self.modname = modname
-
-    def __repr__(self):
-        return "<CodeUnit name=%r filename=%r>" % (self.name, self.filename)
-
-    # Annoying comparison operators. Py3k wants __lt__ etc, and Py2k needs all
-    # of them defined.
-
-    def __lt__(self, other):
-        return self.name < other.name
-    def __le__(self, other):
-        return self.name <= other.name
-    def __eq__(self, other):
-        return self.name == other.name
-    def __ne__(self, other):
-        return self.name != other.name
-    def __gt__(self, other):
-        return self.name > other.name
-    def __ge__(self, other):
-        return self.name >= other.name
-
-    def flat_rootname(self):
-        """A base for a flat filename to correspond to this code unit.
-
-        Useful for writing files about the code where you want all the files in
-        the same directory, but need to differentiate same-named files from
-        different directories.
-
-        For example, the file a/b/c.py might return 'a_b_c'
-
-        """
-        if self.modname:
-            return self.modname.replace('.', '_')
-        else:
-            root = os.path.splitdrive(self.name)[1]
-            return root.replace('\\', '_').replace('/', '_').replace('.', '_')
-
-    def source_file(self):
-        """Return an open file for reading the source of the code unit."""
-        if os.path.exists(self.filename):
-            # A regular text file: open it.
-            return open_source(self.filename)
-
-        # Maybe it's in a zip file?
-        source = self.file_locator.get_zip_data(self.filename)
-        if source is not None:
-            return StringIO(source)
-
-        # Couldn't find source.
-        raise CoverageException(
-            "No source for code '%s'." % self.filename
-            )
-
-    def should_be_python(self):
-        """Does it seem like this file should contain Python?
-
-        This is used to decide if a file reported as part of the exection of
-        a program was really likely to have contained Python in the first
-        place.
-
-        """
-        # Get the file extension.
-        _, ext = os.path.splitext(self.filename)
-
-        # Anything named *.py* should be Python.
-        if ext.startswith('.py'):
-            return True
-        # A file with no extension should be Python.
-        if not ext:
-            return True
-        # Everything else is probably not Python.
-        return False
index 8ba7d87cd4e055b2e11efccd487bc7493f58d19d..3e28b3b149f458e33e5c5e252cd7f92bab5576fd 100644 (file)
-"""Raw data collector for Coverage."""
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
+
+"""Raw data collector for coverage.py."""
+
+import os
+import sys
+
+from coverage import env
+from coverage.backward import iitems
+from coverage.files import abs_file
+from coverage.misc import CoverageException, isolate_module
+from coverage.pytracer import PyTracer
+
+os = isolate_module(os)
 
-import os, sys, threading
 
 try:
     # Use the C extension code when we can, for speed.
-    from coverage.tracer import CTracer         # pylint: disable=F0401,E0611
+    from coverage.tracer import CTracer, CFileDisposition
 except ImportError:
     # Couldn't import the C extension, maybe it isn't built.
     if os.getenv('COVERAGE_TEST_TRACER') == 'c':
-        # During testing, we use the COVERAGE_TEST_TRACER env var to indicate
-        # that we've fiddled with the environment to test this fallback code.
-        # If we thought we had a C tracer, but couldn't import it, then exit
-        # quickly and clearly instead of dribbling confusing errors. I'm using
-        # sys.exit here instead of an exception because an exception here
-        # causes all sorts of other noise in unittest.
-        sys.stderr.write(
-            "*** COVERAGE_TEST_TRACER is 'c' but can't import CTracer!\n"
-            )
+        # During testing, we use the COVERAGE_TEST_TRACER environment variable
+        # to indicate that we've fiddled with the environment to test this
+        # fallback code.  If we thought we had a C tracer, but couldn't import
+        # it, then exit quickly and clearly instead of dribbling confusing
+        # errors. I'm using sys.exit here instead of an exception because an
+        # exception here causes all sorts of other noise in unittest.
+        sys.stderr.write("*** COVERAGE_TEST_TRACER is 'c' but can't import CTracer!\n")
         sys.exit(1)
     CTracer = None
 
 
-class PyTracer(object):
-    """Python implementation of the raw data tracer."""
-
-    # Because of poor implementations of trace-function-manipulating tools,
-    # the Python trace function must be kept very simple.  In particular, there
-    # must be only one function ever set as the trace function, both through
-    # sys.settrace, and as the return value from the trace function.  Put
-    # another way, the trace function must always return itself.  It cannot
-    # swap in other functions, or return None to avoid tracing a particular
-    # frame.
-    #
-    # The trace manipulator that introduced this restriction is DecoratorTools,
-    # which sets a trace function, and then later restores the pre-existing one
-    # by calling sys.settrace with a function it found in the current frame.
-    #
-    # Systems that use DecoratorTools (or similar trace manipulations) must use
-    # PyTracer to get accurate results.  The command-line --timid argument is
-    # used to force the use of this tracer.
-
-    def __init__(self):
-        self.data = None
-        self.should_trace = None
-        self.should_trace_cache = None
-        self.warn = None
-        self.cur_file_data = None
-        self.last_line = 0
-        self.data_stack = []
-        self.last_exc_back = None
-        self.last_exc_firstlineno = 0
-        self.arcs = False
-        self.thread = None
-        self.stopped = False
-
-    def _trace(self, frame, event, arg_unused):
-        """The trace function passed to sys.settrace."""
-
-        if self.stopped:
-            return
-
-        if 0:
-            sys.stderr.write("trace event: %s %r @%d\n" % (
-                event, frame.f_code.co_filename, frame.f_lineno
-            ))
-
-        if self.last_exc_back:
-            if frame == self.last_exc_back:
-                # Someone forgot a return event.
-                if self.arcs and self.cur_file_data:
-                    pair = (self.last_line, -self.last_exc_firstlineno)
-                    self.cur_file_data[pair] = None
-                self.cur_file_data, self.last_line = self.data_stack.pop()
-            self.last_exc_back = None
-
-        if event == 'call':
-            # Entering a new function context.  Decide if we should trace
-            # in this file.
-            self.data_stack.append((self.cur_file_data, self.last_line))
-            filename = frame.f_code.co_filename
-            if filename not in self.should_trace_cache:
-                tracename = self.should_trace(filename, frame)
-                self.should_trace_cache[filename] = tracename
-            else:
-                tracename = self.should_trace_cache[filename]
-            #print("called, stack is %d deep, tracename is %r" % (
-            #               len(self.data_stack), tracename))
-            if tracename:
-                if tracename not in self.data:
-                    self.data[tracename] = {}
-                self.cur_file_data = self.data[tracename]
-            else:
-                self.cur_file_data = None
-            # Set the last_line to -1 because the next arc will be entering a
-            # code block, indicated by (-1, n).
-            self.last_line = -1
-        elif event == 'line':
-            # Record an executed line.
-            if self.cur_file_data is not None:
-                if self.arcs:
-                    #print("lin", self.last_line, frame.f_lineno)
-                    self.cur_file_data[(self.last_line, frame.f_lineno)] = None
-                else:
-                    #print("lin", frame.f_lineno)
-                    self.cur_file_data[frame.f_lineno] = None
-            self.last_line = frame.f_lineno
-        elif event == 'return':
-            if self.arcs and self.cur_file_data:
-                first = frame.f_code.co_firstlineno
-                self.cur_file_data[(self.last_line, -first)] = None
-            # Leaving this function, pop the filename stack.
-            self.cur_file_data, self.last_line = self.data_stack.pop()
-            #print("returned, stack is %d deep" % (len(self.data_stack)))
-        elif event == 'exception':
-            #print("exc", self.last_line, frame.f_lineno)
-            self.last_exc_back = frame.f_back
-            self.last_exc_firstlineno = frame.f_code.co_firstlineno
-        return self._trace
-
-    def start(self):
-        """Start this Tracer.
+class FileDisposition(object):
+    """A simple value type for recording what to do with a file."""
+    pass
 
-        Return a Python function suitable for use with sys.settrace().
 
-        """
-        self.thread = threading.currentThread()
-        sys.settrace(self._trace)
-        return self._trace
-
-    def stop(self):
-        """Stop this Tracer."""
-        self.stopped = True
-        if self.thread != threading.currentThread():
-            # Called on a different thread than started us: we can't unhook
-            # ourseves, but we've set the flag that we should stop, so we won't
-            # do any more tracing.
-            return
-
-        if hasattr(sys, "gettrace") and self.warn:
-            if sys.gettrace() != self._trace:
-                msg = "Trace function changed, measurement is likely wrong: %r"
-                self.warn(msg % (sys.gettrace(),))
-        #print("Stopping tracer on %s" % threading.current_thread().ident)
-        sys.settrace(None)
-
-    def get_stats(self):
-        """Return a dictionary of statistics, or None."""
-        return None
+def should_start_context(frame):
+    """Who-Tests-What hack: Determine whether this frame begins a new who-context."""
+    fn_name = frame.f_code.co_name
+    if fn_name.startswith("test"):
+        return fn_name
 
 
 class Collector(object):
@@ -170,12 +65,17 @@ class Collector(object):
     # the top, and resumed when they become the top again.
     _collectors = []
 
-    def __init__(self, should_trace, timid, branch, warn):
+    # The concurrency settings we support here.
+    SUPPORTED_CONCURRENCIES = set(["greenlet", "eventlet", "gevent", "thread"])
+
+    def __init__(self, should_trace, check_include, timid, branch, warn, concurrency):
         """Create a collector.
 
-        `should_trace` is a function, taking a filename, and returning a
-        canonicalized filename, or None depending on whether the file should
-        be traced or not.
+        `should_trace` is a function, taking a file name, and returning a
+        `coverage.FileDisposition object`.
+
+        `check_include` is a function taking a file name and a frame. It returns
+        a boolean: True if the file should be traced, False if not.
 
         If `timid` is true, then a slower simpler trace function will be
         used.  This is important for some environments where manipulation of
@@ -189,10 +89,55 @@ class Collector(object):
         `warn` is a warning function, taking a single string message argument,
         to be used if a warning needs to be issued.
 
+        `concurrency` is a list of strings indicating the concurrency libraries
+        in use.  Valid values are "greenlet", "eventlet", "gevent", or "thread"
+        (the default).  Of these four values, only one can be supplied.  Other
+        values are ignored.
+
         """
         self.should_trace = should_trace
+        self.check_include = check_include
         self.warn = warn
         self.branch = branch
+        self.threading = None
+
+        self.concur_id_func = None
+
+        # We can handle a few concurrency options here, but only one at a time.
+        these_concurrencies = self.SUPPORTED_CONCURRENCIES.intersection(concurrency)
+        if len(these_concurrencies) > 1:
+            raise CoverageException("Conflicting concurrency settings: %s" % concurrency)
+        self.concurrency = these_concurrencies.pop() if these_concurrencies else ''
+
+        try:
+            if self.concurrency == "greenlet":
+                import greenlet
+                self.concur_id_func = greenlet.getcurrent
+            elif self.concurrency == "eventlet":
+                import eventlet.greenthread     # pylint: disable=import-error,useless-suppression
+                self.concur_id_func = eventlet.greenthread.getcurrent
+            elif self.concurrency == "gevent":
+                import gevent                   # pylint: disable=import-error,useless-suppression
+                self.concur_id_func = gevent.getcurrent
+            elif self.concurrency == "thread" or not self.concurrency:
+                # It's important to import threading only if we need it.  If
+                # it's imported early, and the program being measured uses
+                # gevent, then gevent's monkey-patching won't work properly.
+                import threading
+                self.threading = threading
+            else:
+                raise CoverageException("Don't understand concurrency=%s" % concurrency)
+        except ImportError:
+            raise CoverageException(
+                "Couldn't trace with concurrency=%s, the module isn't installed." % (
+                    self.concurrency,
+                )
+            )
+
+        # Who-Tests-What is just a hack at the moment, so turn it on with an
+        # environment variable.
+        self.wtw = int(os.getenv('COVERAGE_WTW', 0))
+
         self.reset()
 
         if timid:
@@ -203,8 +148,15 @@ class Collector(object):
             # trace function.
             self._trace_class = CTracer or PyTracer
 
+        if self._trace_class is CTracer:
+            self.file_disposition_class = CFileDisposition
+            self.supports_plugins = True
+        else:
+            self.file_disposition_class = FileDisposition
+            self.supports_plugins = False
+
     def __repr__(self):
-        return "<Collector at 0x%x>" % id(self)
+        return "<Collector at 0x%x: %s>" % (id(self), self.tracer_name())
 
     def tracer_name(self):
         """Return the class name of the tracer we're using."""
@@ -212,14 +164,46 @@ class Collector(object):
 
     def reset(self):
         """Clear collected data, and prepare to collect more."""
-        # A dictionary mapping filenames to dicts with linenumber keys,
-        # or mapping filenames to dicts with linenumber pairs as keys.
+        # A dictionary mapping file names to dicts with line number keys (if not
+        # branch coverage), or mapping file names to dicts with line number
+        # pairs as keys (if branch coverage).
         self.data = {}
 
-        # A cache of the results from should_trace, the decision about whether
-        # to trace execution in a file. A dict of filename to (filename or
-        # None).
-        self.should_trace_cache = {}
+        # A dict mapping contexts to data dictionaries.
+        self.contexts = {}
+        self.contexts[None] = self.data
+
+        # A dictionary mapping file names to file tracer plugin names that will
+        # handle them.
+        self.file_tracers = {}
+
+        # The .should_trace_cache attribute is a cache from file names to
+        # coverage.FileDisposition objects, or None.  When a file is first
+        # considered for tracing, a FileDisposition is obtained from
+        # Coverage.should_trace.  Its .trace attribute indicates whether the
+        # file should be traced or not.  If it should be, a plugin with dynamic
+        # file names can decide not to trace it based on the dynamic file name
+        # being excluded by the inclusion rules, in which case the
+        # FileDisposition will be replaced by None in the cache.
+        if env.PYPY:
+            import __pypy__                     # pylint: disable=import-error
+            # Alex Gaynor said:
+            # should_trace_cache is a strictly growing key: once a key is in
+            # it, it never changes.  Further, the keys used to access it are
+            # generally constant, given sufficient context. That is to say, at
+            # any given point _trace() is called, pypy is able to know the key.
+            # This is because the key is determined by the physical source code
+            # line, and that's invariant with the call site.
+            #
+            # This property of a dict with immutable keys, combined with
+            # call-site-constant keys is a match for PyPy's module dict,
+            # which is optimized for such workloads.
+            #
+            # This gives a 20% benefit on the workload described at
+            # https://bitbucket.org/pypy/pypy/issue/1871/10x-slower-than-cpython-under-coverage
+            self.should_trace_cache = __pypy__.newdict("module")
+        else:
+            self.should_trace_cache = {}
 
         # Our active Tracers.
         self.tracers = []
@@ -228,12 +212,35 @@ class Collector(object):
         """Start a new Tracer object, and store it in self.tracers."""
         tracer = self._trace_class()
         tracer.data = self.data
-        tracer.arcs = self.branch
+        tracer.trace_arcs = self.branch
         tracer.should_trace = self.should_trace
         tracer.should_trace_cache = self.should_trace_cache
         tracer.warn = self.warn
+
+        if hasattr(tracer, 'concur_id_func'):
+            tracer.concur_id_func = self.concur_id_func
+        elif self.concur_id_func:
+            raise CoverageException(
+                "Can't support concurrency=%s with %s, only threads are supported" % (
+                    self.concurrency, self.tracer_name(),
+                )
+            )
+
+        if hasattr(tracer, 'file_tracers'):
+            tracer.file_tracers = self.file_tracers
+        if hasattr(tracer, 'threading'):
+            tracer.threading = self.threading
+        if hasattr(tracer, 'check_include'):
+            tracer.check_include = self.check_include
+        if self.wtw:
+            if hasattr(tracer, 'should_start_context'):
+                tracer.should_start_context = should_start_context
+            if hasattr(tracer, 'switch_context'):
+                tracer.switch_context = self.switch_context
+
         fn = tracer.start()
         self.tracers.append(tracer)
+
         return fn
 
     # The trace function has to be set individually on each thread before
@@ -242,16 +249,16 @@ class Collector(object):
     # install this as a trace function, and the first time it's called, it does
     # the real trace installation.
 
-    def _installation_trace(self, frame_unused, event_unused, arg_unused):
+    def _installation_trace(self, frame, event, arg):
         """Called on new threads, installs the real tracer."""
-        # Remove ourselves as the trace function
+        # Remove ourselves as the trace function.
         sys.settrace(None)
         # Install the real tracer.
         fn = self._start_tracer()
         # Invoke the real trace function with the current event, to be sure
         # not to lose an event.
         if fn:
-            fn = fn(frame_unused, event_unused, arg_unused)
+            fn = fn(frame, event, arg)
         # Return the new trace function to continue tracing in this scope.
         return fn
 
@@ -259,39 +266,47 @@ class Collector(object):
         """Start collecting trace information."""
         if self._collectors:
             self._collectors[-1].pause()
-        self._collectors.append(self)
-        #print("Started: %r" % self._collectors, file=sys.stderr)
 
-        # Check to see whether we had a fullcoverage tracer installed.
+        # Check to see whether we had a fullcoverage tracer installed. If so,
+        # get the stack frames it stashed away for us.
         traces0 = []
-        if hasattr(sys, "gettrace"):
-            fn0 = sys.gettrace()
-            if fn0:
-                tracer0 = getattr(fn0, '__self__', None)
-                if tracer0:
-                    traces0 = getattr(tracer0, 'traces', [])
-
-        # Install the tracer on this thread.
-        fn = self._start_tracer()
+        fn0 = sys.gettrace()
+        if fn0:
+            tracer0 = getattr(fn0, '__self__', None)
+            if tracer0:
+                traces0 = getattr(tracer0, 'traces', [])
+
+        try:
+            # Install the tracer on this thread.
+            fn = self._start_tracer()
+        except:
+            if self._collectors:
+                self._collectors[-1].resume()
+            raise
+
+        # If _start_tracer succeeded, then we add ourselves to the global
+        # stack of collectors.
+        self._collectors.append(self)
 
+        # Replay all the events from fullcoverage into the new trace function.
         for args in traces0:
             (frame, event, arg), lineno = args
             try:
                 fn(frame, event, arg, lineno=lineno)
             except TypeError:
-                raise Exception(
-                    "fullcoverage must be run with the C trace function."
-                )
+                raise Exception("fullcoverage must be run with the C trace function.")
 
         # Install our installation tracer in threading, to jump start other
         # threads.
-        threading.settrace(self._installation_trace)
+        if self.threading:
+            self.threading.settrace(self._installation_trace)
 
     def stop(self):
         """Stop collecting trace information."""
-        #print >>sys.stderr, "Stopping: %r" % self._collectors
         assert self._collectors
-        assert self._collectors[-1] is self
+        assert self._collectors[-1] is self, (
+            "Expected current collector to be %r, but it's %r" % (self, self._collectors[-1])
+        )
 
         self.pause()
         self.tracers = []
@@ -310,44 +325,48 @@ class Collector(object):
             if stats:
                 print("\nCoverage.py tracer stats:")
                 for k in sorted(stats.keys()):
-                    print("%16s: %s" % (k, stats[k]))
-        threading.settrace(None)
+                    print("%20s: %s" % (k, stats[k]))
+        if self.threading:
+            self.threading.settrace(None)
 
     def resume(self):
         """Resume tracing after a `pause`."""
         for tracer in self.tracers:
             tracer.start()
-        threading.settrace(self._installation_trace)
-
-    def get_line_data(self):
-        """Return the line data collected.
-
-        Data is { filename: { lineno: None, ...}, ...}
-
-        """
-        if self.branch:
-            # If we were measuring branches, then we have to re-build the dict
-            # to show line data.
-            line_data = {}
-            for f, arcs in self.data.items():
-                line_data[f] = ldf = {}
-                for l1, _ in list(arcs.keys()):
-                    if l1:
-                        ldf[l1] = None
-            return line_data
+        if self.threading:
+            self.threading.settrace(self._installation_trace)
         else:
-            return self.data
+            self._start_tracer()
 
-    def get_arc_data(self):
-        """Return the arc data collected.
+    def switch_context(self, new_context):
+        """Who-Tests-What hack: switch to a new who-context."""
+        # Make a new data dict, or find the existing one, and switch all the
+        # tracers to use it.
+        data = self.contexts.setdefault(new_context, {})
+        for tracer in self.tracers:
+            tracer.data = data
 
-        Data is { filename: { (l1, l2): None, ...}, ...}
+    def save_data(self, covdata):
+        """Save the collected data to a `CoverageData`.
 
-        Note that no data is collected or returned if the Collector wasn't
-        created with `branch` true.
+        Also resets the collector.
 
         """
+        def abs_file_dict(d):
+            """Return a dict like d, but with keys modified by `abs_file`."""
+            return dict((abs_file(k), v) for k, v in iitems(d))
+
         if self.branch:
-            return self.data
+            covdata.add_arcs(abs_file_dict(self.data))
         else:
-            return {}
+            covdata.add_lines(abs_file_dict(self.data))
+        covdata.add_file_tracers(abs_file_dict(self.file_tracers))
+
+        if self.wtw:
+            # Just a hack, so just hack it.
+            import pprint
+            out_file = "coverage_wtw_{:06}.py".format(os.getpid())
+            with open(out_file, "w") as wtw_out:
+                pprint.pprint(self.contexts, wtw_out)
+
+        self.reset()
index 87318ff12452b6f0f833920f2cd1fd04e56b71d7..d6f5af0a6f5ab6f86ddd3dccfb8bf3c09995536a 100644 (file)
@@ -1,31 +1,68 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
+
 """Config file for coverage.py"""
 
-import os, re, sys
-from coverage.backward import string_class, iitems
+import collections
+import os
+import re
+import sys
+
+from coverage.backward import configparser, iitems, string_class
+from coverage.misc import contract, CoverageException, isolate_module
 
-# In py3, # ConfigParser was renamed to the more-standard configparser
-try:
-    import configparser                             # pylint: disable=F0401
-except ImportError:
-    import ConfigParser as configparser
+os = isolate_module(os)
 
 
 class HandyConfigParser(configparser.RawConfigParser):
     """Our specialization of ConfigParser."""
 
+    def __init__(self, section_prefix):
+        configparser.RawConfigParser.__init__(self)
+        self.section_prefix = section_prefix
+
     def read(self, filename):
-        """Read a filename as UTF-8 configuration data."""
+        """Read a file name as UTF-8 configuration data."""
         kwargs = {}
         if sys.version_info >= (3, 2):
             kwargs['encoding'] = "utf-8"
         return configparser.RawConfigParser.read(self, filename, **kwargs)
 
-    def get(self, *args, **kwargs):
-        v = configparser.RawConfigParser.get(self, *args, **kwargs)
+    def has_option(self, section, option):
+        section = self.section_prefix + section
+        return configparser.RawConfigParser.has_option(self, section, option)
+
+    def has_section(self, section):
+        section = self.section_prefix + section
+        return configparser.RawConfigParser.has_section(self, section)
+
+    def options(self, section):
+        section = self.section_prefix + section
+        return configparser.RawConfigParser.options(self, section)
+
+    def get_section(self, section):
+        """Get the contents of a section, as a dictionary."""
+        d = {}
+        for opt in self.options(section):
+            d[opt] = self.get(section, opt)
+        return d
+
+    def get(self, section, *args, **kwargs):
+        """Get a value, replacing environment variables also.
+
+        The arguments are the same as `RawConfigParser.get`, but in the found
+        value, ``$WORD`` or ``${WORD}`` are replaced by the value of the
+        environment variable ``WORD``.
+
+        Returns the finished value.
+
+        """
+        section = self.section_prefix + section
+        v = configparser.RawConfigParser.get(self, section, *args, **kwargs)
         def dollar_replace(m):
             """Called for each $replacement."""
             # Only one of the groups will have matched, just get its text.
-            word = [w for w in m.groups() if w is not None][0]
+            word = next(w for w in m.groups() if w is not None)     # pragma: part covered
             if word == "$":
                 return "$"
             else:
@@ -59,28 +96,39 @@ class HandyConfigParser(configparser.RawConfigParser):
                     values.append(value)
         return values
 
-    def getlinelist(self, section, option):
-        """Read a list of full-line strings.
+    def getregexlist(self, section, option):
+        """Read a list of full-line regexes.
 
         The value of `section` and `option` is treated as a newline-separated
-        list of strings.  Each value is stripped of whitespace.
+        list of regexes.  Each value is stripped of whitespace.
 
         Returns the list of strings.
 
         """
-        value_list = self.get(section, option)
-        return list(filter(None, value_list.split('\n')))
-
-
-# The default line exclusion regexes
+        line_list = self.get(section, option)
+        value_list = []
+        for value in line_list.splitlines():
+            value = value.strip()
+            try:
+                re.compile(value)
+            except re.error as e:
+                raise CoverageException(
+                    "Invalid [%s].%s value %r: %s" % (section, option, value, e)
+                )
+            if value:
+                value_list.append(value)
+        return value_list
+
+
+# The default line exclusion regexes.
 DEFAULT_EXCLUDE = [
-    '(?i)# *pragma[: ]*no *cover',
-    ]
+    r'(?i)#\s*pragma[:\s]?\s*no\s*cover',
+]
 
 # The default partial branch regexes, to be modified by the user.
 DEFAULT_PARTIAL = [
-    '(?i)# *pragma[: ]*no *branch',
-    ]
+    r'(?i)#\s*pragma[:\s]?\s*no\s*branch',
+]
 
 # The default partial branch regexes, based on Python semantics.
 # These are any Python branching constructs that can't actually execute all
@@ -88,7 +136,7 @@ DEFAULT_PARTIAL = [
 DEFAULT_PARTIAL_ALWAYS = [
     'while (True|1|False|0):',
     'if (True|1|False|0):',
-    ]
+]
 
 
 class CoverageConfig(object):
@@ -106,44 +154,44 @@ class CoverageConfig(object):
 
         # Defaults for [run]
         self.branch = False
+        self.concurrency = None
         self.cover_pylib = False
         self.data_file = ".coverage"
+        self.debug = []
+        self.note = None
         self.parallel = False
-        self.timid = False
+        self.plugins = []
         self.source = None
-        self.debug = []
+        self.timid = False
 
         # Defaults for [report]
         self.exclude_list = DEFAULT_EXCLUDE[:]
+        self.fail_under = 0
         self.ignore_errors = False
         self.include = None
         self.omit = None
-        self.partial_list = DEFAULT_PARTIAL[:]
         self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:]
+        self.partial_list = DEFAULT_PARTIAL[:]
         self.precision = 0
         self.show_missing = False
+        self.skip_covered = False
 
         # Defaults for [html]
-        self.html_dir = "htmlcov"
         self.extra_css = None
+        self.html_dir = "htmlcov"
         self.html_title = "Coverage report"
 
         # Defaults for [xml]
         self.xml_output = "coverage.xml"
+        self.xml_package_depth = 99
 
         # Defaults for [paths]
         self.paths = {}
 
-    def from_environment(self, env_var):
-        """Read configuration from the `env_var` environment variable."""
-        # Timidity: for nose users, read an environment variable.  This is a
-        # cheap hack, since the rest of the command line arguments aren't
-        # recognized, but it solves some users' problems.
-        env = os.environ.get(env_var, '')
-        if env:
-            self.timid = ('--timid' in env)
+        # Options for plugins
+        self.plugin_options = {}
 
-    MUST_BE_LIST = ["omit", "include", "debug"]
+    MUST_BE_LIST = ["omit", "include", "debug", "plugins", "concurrency"]
 
     def from_args(self, **kwargs):
         """Read config values from `kwargs`."""
@@ -153,61 +201,167 @@ class CoverageConfig(object):
                     v = [v]
                 setattr(self, k, v)
 
-    def from_file(self, filename):
+    @contract(filename=str)
+    def from_file(self, filename, section_prefix=""):
         """Read configuration from a .rc file.
 
         `filename` is a file name to read.
 
+        Returns True or False, whether the file could be read.
+
         """
         self.attempted_config_files.append(filename)
 
-        cp = HandyConfigParser()
-        files_read = cp.read(filename)
-        if files_read is not None:  # return value changed in 2.4
-            self.config_files.extend(files_read)
+        cp = HandyConfigParser(section_prefix)
+        try:
+            files_read = cp.read(filename)
+        except configparser.Error as err:
+            raise CoverageException("Couldn't read config file %s: %s" % (filename, err))
+        if not files_read:
+            return False
+
+        self.config_files.extend(files_read)
 
+        try:
+            for option_spec in self.CONFIG_FILE_OPTIONS:
+                self._set_attr_from_config_option(cp, *option_spec)
+        except ValueError as err:
+            raise CoverageException("Couldn't read config file %s: %s" % (filename, err))
+
+        # Check that there are no unrecognized options.
+        all_options = collections.defaultdict(set)
         for option_spec in self.CONFIG_FILE_OPTIONS:
-            self.set_attr_from_config_option(cp, *option_spec)
+            section, option = option_spec[1].split(":")
+            all_options[section].add(option)
+
+        for section, options in iitems(all_options):
+            if cp.has_section(section):
+                for unknown in set(cp.options(section)) - options:
+                    if section_prefix:
+                        section = section_prefix + section
+                    raise CoverageException(
+                        "Unrecognized option '[%s] %s=' in config file %s" % (
+                            section, unknown, filename
+                        )
+                    )
 
         # [paths] is special
         if cp.has_section('paths'):
             for option in cp.options('paths'):
                 self.paths[option] = cp.getlist('paths', option)
 
+        # plugins can have options
+        for plugin in self.plugins:
+            if cp.has_section(plugin):
+                self.plugin_options[plugin] = cp.get_section(plugin)
+
+        return True
+
     CONFIG_FILE_OPTIONS = [
+        # These are *args for _set_attr_from_config_option:
+        #   (attr, where, type_="")
+        #
+        #   attr is the attribute to set on the CoverageConfig object.
+        #   where is the section:name to read from the configuration file.
+        #   type_ is the optional type to apply, by using .getTYPE to read the
+        #       configuration value from the file.
+
         # [run]
         ('branch', 'run:branch', 'boolean'),
+        ('concurrency', 'run:concurrency', 'list'),
         ('cover_pylib', 'run:cover_pylib', 'boolean'),
         ('data_file', 'run:data_file'),
         ('debug', 'run:debug', 'list'),
         ('include', 'run:include', 'list'),
+        ('note', 'run:note'),
         ('omit', 'run:omit', 'list'),
         ('parallel', 'run:parallel', 'boolean'),
+        ('plugins', 'run:plugins', 'list'),
         ('source', 'run:source', 'list'),
         ('timid', 'run:timid', 'boolean'),
 
         # [report]
-        ('exclude_list', 'report:exclude_lines', 'linelist'),
+        ('exclude_list', 'report:exclude_lines', 'regexlist'),
+        ('fail_under', 'report:fail_under', 'int'),
         ('ignore_errors', 'report:ignore_errors', 'boolean'),
         ('include', 'report:include', 'list'),
         ('omit', 'report:omit', 'list'),
-        ('partial_list', 'report:partial_branches', 'linelist'),
-        ('partial_always_list', 'report:partial_branches_always', 'linelist'),
+        ('partial_always_list', 'report:partial_branches_always', 'regexlist'),
+        ('partial_list', 'report:partial_branches', 'regexlist'),
         ('precision', 'report:precision', 'int'),
         ('show_missing', 'report:show_missing', 'boolean'),
+        ('skip_covered', 'report:skip_covered', 'boolean'),
+        ('sort', 'report:sort'),
 
         # [html]
-        ('html_dir', 'html:directory'),
         ('extra_css', 'html:extra_css'),
+        ('html_dir', 'html:directory'),
         ('html_title', 'html:title'),
 
         # [xml]
         ('xml_output', 'xml:output'),
-        ]
+        ('xml_package_depth', 'xml:package_depth', 'int'),
+    ]
 
-    def set_attr_from_config_option(self, cp, attr, where, type_=''):
+    def _set_attr_from_config_option(self, cp, attr, where, type_=''):
         """Set an attribute on self if it exists in the ConfigParser."""
         section, option = where.split(":")
         if cp.has_option(section, option):
-            method = getattr(cp, 'get'+type_)
+            method = getattr(cp, 'get' + type_)
             setattr(self, attr, method(section, option))
+
+    def get_plugin_options(self, plugin):
+        """Get a dictionary of options for the plugin named `plugin`."""
+        return self.plugin_options.get(plugin, {})
+
+    def set_option(self, option_name, value):
+        """Set an option in the configuration.
+
+        `option_name` is a colon-separated string indicating the section and
+        option name.  For example, the ``branch`` option in the ``[run]``
+        section of the config file would be indicated with `"run:branch"`.
+
+        `value` is the new value for the option.
+
+        """
+
+        # Check all the hard-coded options.
+        for option_spec in self.CONFIG_FILE_OPTIONS:
+            attr, where = option_spec[:2]
+            if where == option_name:
+                setattr(self, attr, value)
+                return
+
+        # See if it's a plugin option.
+        plugin_name, _, key = option_name.partition(":")
+        if key and plugin_name in self.plugins:
+            self.plugin_options.setdefault(plugin_name, {})[key] = value
+            return
+
+        # If we get here, we didn't find the option.
+        raise CoverageException("No such option: %r" % option_name)
+
+    def get_option(self, option_name):
+        """Get an option from the configuration.
+
+        `option_name` is a colon-separated string indicating the section and
+        option name.  For example, the ``branch`` option in the ``[run]``
+        section of the config file would be indicated with `"run:branch"`.
+
+        Returns the value of the option.
+
+        """
+
+        # Check all the hard-coded options.
+        for option_spec in self.CONFIG_FILE_OPTIONS:
+            attr, where = option_spec[:2]
+            if where == option_name:
+                return getattr(self, attr)
+
+        # See if it's a plugin option.
+        plugin_name, _, key = option_name.partition(":")
+        if key and plugin_name in self.plugins:
+            return self.plugin_options.get(plugin_name, {}).get(key)
+
+        # If we get here, we didn't find the option.
+        raise CoverageException("No such option: %r" % option_name)
index f75a3dda5b1b479a45e52c50d1390b7d076a8b1f..d3e6708563c0516124f657dae06d27fff02973f1 100644 (file)