diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index 645a9a755..7c9409f59 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -1178,7 +1178,9 @@ class Config */ public function reportTypeStatsForFile($file_path) { - return $this->project_files && $this->project_files->reportTypeStats($file_path); + return $this->project_files + && $this->project_files->allows($file_path) + && $this->project_files->reportTypeStats($file_path); } /** diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/PropertyAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/PropertyAssignmentAnalyzer.php index c52e281d6..75caca954 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/PropertyAssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/PropertyAssignmentAnalyzer.php @@ -133,7 +133,15 @@ class PropertyAssignmentAnalyzer } if ($lhs_type->hasMixed()) { - $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } if (IssueBuffer::accepts( new MixedPropertyAssignment( @@ -148,7 +156,15 @@ class PropertyAssignmentAnalyzer return null; } - $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } if ($lhs_type->isNull()) { if (IssueBuffer::accepts( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php index de1888f45..e502410d6 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php @@ -214,7 +214,15 @@ class AssignmentAnalyzer $statements_analyzer ); - $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } if (!$assign_var instanceof PhpParser\Node\Expr\PropertyFetch && !strpos($root_var_id ?? '', '->') @@ -230,7 +238,15 @@ class AssignmentAnalyzer } } } else { - $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } if ($var_id && isset($context->byref_constraints[$var_id]) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php index f3e9209df..347963fc0 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php @@ -937,8 +937,16 @@ class BinaryOpAnalyzer || $left_type_part instanceof TTemplateParam || $right_type_part instanceof TTemplateParam ) { - if ($statements_source && $codebase) { - $codebase->analyzer->incrementMixedCount($statements_source->getFilePath()); + if ($statements_source && $codebase && $context) { + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_source->getFilePath() === $statements_source->getRootFilePath() + && (!(($source = $statements_source->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_source->getFilePath()); + } } if ($left_type_part instanceof TMixed || $left_type_part instanceof TTemplateParam) { @@ -987,8 +995,16 @@ class BinaryOpAnalyzer return $result_type; } - if ($statements_source && $codebase) { - $codebase->analyzer->incrementNonMixedCount($statements_source->getFilePath()); + if ($statements_source && $codebase && $context) { + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_source->getFilePath() === $statements_source->getRootFilePath() + && (!(($parent_source = $statements_source->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_source->getFilePath()); + } } if ($left_type_part instanceof TArray @@ -1273,7 +1289,15 @@ class BinaryOpAnalyzer $result_type = Type::getString(); if ($left_type->hasMixed() || $right_type->hasMixed()) { - $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } if ($left_type->hasMixed()) { if (IssueBuffer::accepts( @@ -1300,7 +1324,15 @@ class BinaryOpAnalyzer return; } - $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } if ($left_type->isNull()) { if (IssueBuffer::accepts( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php index e975f0a29..705a9b48e 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php @@ -455,7 +455,15 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\ case Type\Atomic\TNonEmptyMixed::class: case Type\Atomic\TObject::class: case Type\Atomic\TObjectWithProperties::class: - $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } $has_mixed_method_call = true; @@ -488,7 +496,15 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\ return; } - $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } $has_valid_method_call_type = true; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php index 9e58538c8..2ab978422 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php @@ -1193,7 +1193,15 @@ class CallAnalyzer if (!isset($arg->value->inferredType)) { if ($function_param && !$function_param->by_ref) { - $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } $param_type = $function_param->type; @@ -1486,7 +1494,15 @@ class CallAnalyzer } if ($arg_type->hasMixed()) { - $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } if (IssueBuffer::accepts( new MixedArgument( @@ -2079,7 +2095,16 @@ class CallAnalyzer } if ($input_type->hasMixed()) { - $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } + if (IssueBuffer::accepts( new MixedArgument( 'Argument ' . ($argument_offset + 1) . $method_identifier . ' cannot be mixed, expecting ' . @@ -2108,7 +2133,15 @@ class CallAnalyzer return null; } - $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } $param_type = TypeAnalyzer::simplifyUnionType( $codebase, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php index dfc115a2f..961ebbde0 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php @@ -630,7 +630,15 @@ class ArrayFetchAnalyzer if ($type instanceof TString) { if ($in_assignment && $replacement_type) { if ($replacement_type->hasMixed()) { - $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } if (IssueBuffer::accepts( new MixedStringOffsetAssignment( @@ -642,7 +650,15 @@ class ArrayFetchAnalyzer // fall through } } else { - $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } } } @@ -692,7 +708,15 @@ class ArrayFetchAnalyzer } if ($type instanceof TMixed || $type instanceof TTemplateParam || $type instanceof TEmpty) { - $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } if (!$context->inside_isset) { if ($in_assignment) { @@ -723,7 +747,15 @@ class ArrayFetchAnalyzer break; } - $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } if ($type instanceof Type\Atomic\TFalse && $array_type->ignore_falsable_issues) { continue; @@ -880,7 +912,15 @@ class ArrayFetchAnalyzer } if ($offset_type->hasMixed()) { - $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } if (IssueBuffer::accepts( new MixedArrayOffset( @@ -892,7 +932,15 @@ class ArrayFetchAnalyzer // fall through } } else { - $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } if ($expected_offset_types) { $invalid_offset_type = $expected_offset_types[0]; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/PropertyFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/PropertyFetchAnalyzer.php index 44dcc8c77..655de0757 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/PropertyFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/PropertyFetchAnalyzer.php @@ -88,7 +88,15 @@ class PropertyFetchAnalyzer // we don't need to check anything $stmt->inferredType = $context->vars_in_scope[$var_id]; - $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } if ($codebase->store_node_types && (!$context->collect_initializations @@ -217,7 +225,15 @@ class PropertyFetchAnalyzer } if ($stmt_var_type->hasMixed()) { - $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } if (IssueBuffer::accepts( new MixedPropertyFetch( @@ -247,7 +263,15 @@ class PropertyFetchAnalyzer } } - $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getRootFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && (!(($parent_source = $statements_analyzer->getSource()) + instanceof \Psalm\Internal\Analyzer\FunctionLikeAnalyzer) + || !$parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getRootFilePath()); + } if ($stmt_var_type->isNullable() && !$stmt_var_type->ignore_nullable_issues && !$context->inside_isset) { if (IssueBuffer::accepts( diff --git a/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php index 7d38c7769..f5c5c44bc 100644 --- a/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php @@ -155,7 +155,13 @@ class ReturnAnalyzer } } - $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && !($source->getSource() instanceof TraitAnalyzer) + ) { + $codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath()); + } if (IssueBuffer::accepts( new MixedReturnStatement( @@ -170,7 +176,13 @@ class ReturnAnalyzer return null; } - $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + if (!$context->collect_initializations + && !$context->collect_mutations + && $statements_analyzer->getFilePath() === $statements_analyzer->getRootFilePath() + && !($source->getSource() instanceof TraitAnalyzer) + ) { + $codebase->analyzer->incrementNonMixedCount($statements_analyzer->getFilePath()); + } if ($local_return_type->isVoid()) { if (IssueBuffer::accepts( diff --git a/src/Psalm/Internal/Codebase/Analyzer.php b/src/Psalm/Internal/Codebase/Analyzer.php index f8cbad5d7..b61f735e3 100644 --- a/src/Psalm/Internal/Codebase/Analyzer.php +++ b/src/Psalm/Internal/Codebase/Analyzer.php @@ -292,6 +292,7 @@ class Analyzer $scanned_files = $codebase->scanner->getScannedFiles(); $codebase->file_reference_provider->setAnalyzedMethods($this->analyzed_methods); $codebase->file_reference_provider->setFileMaps($this->getFileMaps()); + $codebase->file_reference_provider->setTypeCoverage($this->mixed_counts); $codebase->file_reference_provider->updateReferenceCache($codebase, $scanned_files); if ($codebase->diff_methods) { @@ -332,6 +333,7 @@ class Analyzer $diff_map = $statements_provider->getDiffMap(); $all_referencing_methods = $codebase->file_reference_provider->getMethodsReferencing(); + $this->mixed_counts = $codebase->file_reference_provider->getTypeCoverage(); $classlikes = $codebase->classlikes; @@ -414,6 +416,7 @@ class Analyzer foreach ($this->files_to_analyze as $file_path) { $codebase->file_reference_provider->clearExistingIssuesForFile($file_path); $codebase->file_reference_provider->clearExistingFileMapsForFile($file_path); + $this->setMixedCountsForFile($file_path, [0, 0]); } } @@ -599,7 +602,13 @@ class Analyzer */ public function getMixedCounts() { - return $this->mixed_counts; + $all_deep_scanned_files = []; + + foreach ($this->files_to_analyze as $file_path => $_) { + $all_deep_scanned_files[$file_path] = true; + } + + return array_intersect_key($this->mixed_counts, $all_deep_scanned_files); } /** @@ -636,13 +645,34 @@ class Analyzer } /** - * @return string + * @return array{int, int} */ - public function getTypeInferenceSummary() + public function getTotalTypeCoverage(\Psalm\Codebase $codebase) { $mixed_count = 0; $nonmixed_count = 0; + foreach ($codebase->file_reference_provider->getTypeCoverage() as $file_path => $counts) { + if (!$this->config->reportTypeStatsForFile($file_path)) { + continue; + } + + list($path_mixed_count, $path_nonmixed_count) = $counts; + + if (isset($this->mixed_counts[$file_path])) { + $mixed_count += $path_mixed_count; + $nonmixed_count += $path_nonmixed_count; + } + } + + return [$mixed_count, $nonmixed_count]; + } + + /** + * @return string + */ + public function getTypeInferenceSummary(\Psalm\Codebase $codebase) + { $all_deep_scanned_files = []; foreach ($this->files_to_analyze as $file_path => $_) { @@ -653,17 +683,7 @@ class Analyzer } } - foreach ($all_deep_scanned_files as $file_path => $_) { - if (!$this->config->reportTypeStatsForFile($file_path)) { - continue; - } - - if (isset($this->mixed_counts[$file_path])) { - list($path_mixed_count, $path_nonmixed_count) = $this->mixed_counts[$file_path]; - $mixed_count += $path_mixed_count; - $nonmixed_count += $path_nonmixed_count; - } - } + list($mixed_count, $nonmixed_count) = $this->getTotalTypeCoverage($codebase); $total = $mixed_count + $nonmixed_count; @@ -674,12 +694,13 @@ class Analyzer } if (!$total) { - return 'Psalm was unable to infer types in any of ' - . $total_files . ' file' . ($total_files > 1 ? 's' : ''); + return 'Psalm was unable to infer types in the codebase'; } - return 'Psalm was able to infer types for ' . number_format(100 * $nonmixed_count / $total, 3) . '%' - . ' of analyzed code (' . $total_files . ' file' . ($total_files > 1 ? 's' : '') . ')'; + $percentage = $nonmixed_count === $total ? '100' : number_format(100 * $nonmixed_count / $total, 4); + + return 'Psalm was able to infer types for ' . $percentage . '%' + . ' of the codebase'; } /** diff --git a/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php b/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php index ee00f9413..6d3dabbff 100644 --- a/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php +++ b/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php @@ -34,6 +34,7 @@ class FileReferenceCacheProvider const CLASS_METHOD_CACHE_NAME = 'class_method_references'; const ISSUES_CACHE_NAME = 'issues'; const FILE_MAPS_CACHE_NAME = 'file_maps'; + const TYPE_COVERAGE_CACHE_NAME = 'type_coverage'; const CONFIG_HASH_CACHE_NAME = 'config'; /** @@ -267,6 +268,45 @@ class FileReferenceCacheProvider } } + /** + * @return array|false + */ + public function getTypeCoverage() + { + $cache_directory = Config::getInstance()->getCacheDirectory(); + + $type_coverage_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::TYPE_COVERAGE_CACHE_NAME; + + if ($cache_directory + && file_exists($type_coverage_cache_location) + && !$this->config_changed + ) { + /** @var array */ + $type_coverage_cache = unserialize(file_get_contents($type_coverage_cache_location)); + return $type_coverage_cache; + } + + return false; + } + + /** + * @param array $mixed_counts + * @return void + */ + public function setTypeCoverage(array $mixed_counts) + { + $cache_directory = Config::getInstance()->getCacheDirectory(); + + if ($cache_directory) { + $type_coverage_cache_location = $cache_directory . DIRECTORY_SEPARATOR . self::TYPE_COVERAGE_CACHE_NAME; + + file_put_contents( + $type_coverage_cache_location, + serialize($mixed_counts) + ); + } + } + /** * @return string|false */ diff --git a/src/Psalm/Internal/Provider/FileReferenceProvider.php b/src/Psalm/Internal/Provider/FileReferenceProvider.php index dfb53f3d6..975cfde49 100644 --- a/src/Psalm/Internal/Provider/FileReferenceProvider.php +++ b/src/Psalm/Internal/Provider/FileReferenceProvider.php @@ -88,6 +88,11 @@ class FileReferenceProvider */ private static $file_maps = []; + /** + * @var array + */ + private static $mixed_counts = []; + /** * @var ?FileReferenceCacheProvider */ @@ -297,6 +302,14 @@ class FileReferenceProvider self::$issues = $issues; + $mixed_counts = $this->cache->getTypeCoverage(); + + if ($mixed_counts === false) { + return false; + } + + self::$mixed_counts = $mixed_counts; + self::$file_maps = $this->cache->getFileMapCache() ?: []; return true; @@ -338,6 +351,7 @@ class FileReferenceProvider $this->cache->setCachedMethodReferences(self::$class_method_references); $this->cache->setCachedIssues(self::$issues); $this->cache->setFileMapCache(self::$file_maps); + $this->cache->setTypeCoverage(self::$mixed_counts); $this->cache->setAnalyzedMethodCache(self::$analyzed_methods); } } @@ -447,6 +461,23 @@ class FileReferenceProvider self::$file_maps = $file_maps; } + /** + * @return array + */ + public function getTypeCoverage() + { + return self::$mixed_counts; + } + + /** + * @param array $mixed_counts + * @return void + */ + public function setTypeCoverage(array $mixed_counts) + { + self::$mixed_counts = array_merge(self::$mixed_counts, $mixed_counts); + } + /** * @return array> */ diff --git a/src/Psalm/IssueBuffer.php b/src/Psalm/IssueBuffer.php index 9c66fa781..120adbcb4 100644 --- a/src/Psalm/IssueBuffer.php +++ b/src/Psalm/IssueBuffer.php @@ -387,10 +387,8 @@ class IssueBuffer echo 'Checks took ' . number_format(microtime(true) - $start_time, 2) . ' seconds'; echo ' and used ' . number_format(memory_get_peak_usage() / (1024 * 1024), 3) . 'MB of memory' . "\n"; - if ($is_full) { - $analysis_summary = $codebase->analyzer->getTypeInferenceSummary(); - echo $analysis_summary . "\n"; - } + $analysis_summary = $codebase->analyzer->getTypeInferenceSummary($codebase); + echo $analysis_summary . "\n"; if ($add_stats) { echo '-----------------' . "\n"; diff --git a/tests/ProjectCheckerTest.php b/tests/ProjectCheckerTest.php index b15112946..5496f3382 100644 --- a/tests/ProjectCheckerTest.php +++ b/tests/ProjectCheckerTest.php @@ -84,9 +84,15 @@ class ProjectAnalyzerTest extends TestCase $this->assertSame(0, \Psalm\IssueBuffer::getErrorCount()); + $codebase = $this->project_analyzer->getCodebase(); + + $this->assertSame([0, 4], $codebase->analyzer->getTotalTypeCoverage($codebase)); + $this->assertSame( - 'Psalm was able to infer types for 100.000% of analyzed code (2 files)', - $this->project_analyzer->getCodebase()->analyzer->getTypeInferenceSummary() + 'Psalm was able to infer types for 100% of the codebase', + $codebase->analyzer->getTypeInferenceSummary( + $codebase + ) ); } @@ -153,8 +159,10 @@ class ProjectAnalyzerTest extends TestCase \Psalm\IssueBuffer::finish($this->project_analyzer, true, microtime(true)); $this->assertSame( - 'Psalm was able to infer types for 100.000% of analyzed code (2 files)', - $this->project_analyzer->getCodebase()->analyzer->getTypeInferenceSummary() + 'Psalm was able to infer types for 100% of the codebase', + $this->project_analyzer->getCodebase()->analyzer->getTypeInferenceSummary( + $this->project_analyzer->getCodebase() + ) ); $this->project_analyzer->getCodebase()->reloadFiles($this->project_analyzer, []); @@ -165,7 +173,9 @@ class ProjectAnalyzerTest extends TestCase $this->assertSame( 'No files analyzed', - $this->project_analyzer->getCodebase()->analyzer->getTypeInferenceSummary() + $this->project_analyzer->getCodebase()->analyzer->getTypeInferenceSummary( + $this->project_analyzer->getCodebase() + ) ); } @@ -192,8 +202,10 @@ class ProjectAnalyzerTest extends TestCase \Psalm\IssueBuffer::finish($this->project_analyzer, true, microtime(true)); $this->assertSame( - 'Psalm was able to infer types for 100.000% of analyzed code (2 files)', - $this->project_analyzer->getCodebase()->analyzer->getTypeInferenceSummary() + 'Psalm was able to infer types for 100% of the codebase', + $this->project_analyzer->getCodebase()->analyzer->getTypeInferenceSummary( + $this->project_analyzer->getCodebase() + ) ); $bat_file_path = getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'DummyProject' . DIRECTORY_SEPARATOR . 'Bat.php'; @@ -220,8 +232,10 @@ class Bat $this->assertSame(0, \Psalm\IssueBuffer::getErrorCount()); $this->assertSame( - 'Psalm was able to infer types for 100.000% of analyzed code (1 file)', - $this->project_analyzer->getCodebase()->analyzer->getTypeInferenceSummary() + 'Psalm was able to infer types for 100% of the codebase', + $this->project_analyzer->getCodebase()->analyzer->getTypeInferenceSummary( + $this->project_analyzer->getCodebase() + ) ); } @@ -251,8 +265,10 @@ class Bat $this->assertSame(0, \Psalm\IssueBuffer::getErrorCount()); $this->assertSame( - 'Psalm was able to infer types for 100.000% of analyzed code (2 files)', - $this->project_analyzer->getCodebase()->analyzer->getTypeInferenceSummary() + 'Psalm was able to infer types for 100% of the codebase', + $this->project_analyzer->getCodebase()->analyzer->getTypeInferenceSummary( + $this->project_analyzer->getCodebase() + ) ); } @@ -282,8 +298,10 @@ class Bat $this->assertSame(0, \Psalm\IssueBuffer::getErrorCount()); $this->assertSame( - 'Psalm was able to infer types for 100.000% of analyzed code (1 file)', - $this->project_analyzer->getCodebase()->analyzer->getTypeInferenceSummary() + 'Psalm was able to infer types for 100% of the codebase', + $this->project_analyzer->getCodebase()->analyzer->getTypeInferenceSummary( + $this->project_analyzer->getCodebase() + ) ); } @@ -313,8 +331,10 @@ class Bat $this->assertSame(0, \Psalm\IssueBuffer::getErrorCount()); $this->assertSame( - 'Psalm was able to infer types for 100.000% of analyzed code (1 file)', - $this->project_analyzer->getCodebase()->analyzer->getTypeInferenceSummary() + 'Psalm was able to infer types for 100% of the codebase', + $this->project_analyzer->getCodebase()->analyzer->getTypeInferenceSummary( + $this->project_analyzer->getCodebase() + ) ); } }