Skip to content

Commit

Permalink
Suggest most similar module in missing module exception
Browse files Browse the repository at this point in the history
* added suggestion on missing modules

* added one test

* changed expectException method

* changed hasModule to getModule

* fixed exception message

* Use string concatenation instead of variable interpolation

Because it was hard to read.

Co-authored-by: Gintautas Miselis <gintautas@miselis.lt>
  • Loading branch information
c33s and Naktibalda committed Jan 17, 2021
1 parent 7b0db63 commit f1e1daa
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 2 deletions.
32 changes: 31 additions & 1 deletion src/Codeception/Lib/ModuleContainer.php
Expand Up @@ -22,6 +22,11 @@ class ModuleContainer
*/
const MODULE_NAMESPACE = '\\Codeception\\Module\\';

/**
* @var integer
*/
const MAXIMUM_LEVENSHTEIN_DISTANCE = 5;

public static $packages = [
'AMQP' => 'codeception/module-amqp',
'Apc' => 'codeception/module-apc',
Expand Down Expand Up @@ -275,12 +280,37 @@ public function hasModule($moduleName)
public function getModule($moduleName)
{
if (!$this->hasModule($moduleName)) {
throw new ModuleException(__CLASS__, "Module $moduleName couldn't be connected");
$this->throwMissingModuleExceptionWithSuggestion(__CLASS__, $moduleName);
}

return $this->modules[$moduleName];
}

public function throwMissingModuleExceptionWithSuggestion($className, $moduleName)
{
$suggestedModuleNameInfo = $this->getModuleSuggestion($moduleName);
throw new ModuleException($className, "Module $moduleName couldn't be connected" . $suggestedModuleNameInfo);
}

protected function getModuleSuggestion($missingModuleName)
{
$shortestLevenshteinDistance = null;
$suggestedModuleName = null;
foreach ($this->modules as $moduleName => $module) {
$levenshteinDistance = levenshtein($missingModuleName, $moduleName);
if ($shortestLevenshteinDistance === null || $levenshteinDistance <= $shortestLevenshteinDistance) {
$shortestLevenshteinDistance = $levenshteinDistance;
$suggestedModuleName = $moduleName;
}
}

if ($suggestedModuleName !== null && $shortestLevenshteinDistance <= self::MAXIMUM_LEVENSHTEIN_DISTANCE) {
return " (did you mean '$suggestedModuleName'?)";
}

return '';
}

/**
* Get the module for an action.
*
Expand Down
2 changes: 1 addition & 1 deletion src/Codeception/Module.php
Expand Up @@ -337,7 +337,7 @@ protected function getModules()
protected function getModule($name)
{
if (!$this->hasModule($name)) {
throw new Exception\ModuleException(__CLASS__, "Module $name couldn't be connected");
$this->moduleContainer->throwMissingModuleExceptionWithSuggestion(__CLASS__, $name);
}
return $this->moduleContainer->getModule($name);
}
Expand Down
18 changes: 18 additions & 0 deletions tests/unit/Codeception/Lib/ModuleContainerTest.php
@@ -1,6 +1,7 @@
<?php
namespace Codeception\Lib;

use Codeception\Exception\ModuleException;
use Codeception\Lib\Interfaces\ConflictsWithModule;
use Codeception\Lib\Interfaces\DependsOnModule;
use Codeception\Test\Unit;
Expand Down Expand Up @@ -376,6 +377,23 @@ public function testInjectModuleIntoHelper()
$this->moduleContainer->create('Codeception\Lib\HelperModule');
$this->moduleContainer->hasModule('Codeception\Lib\HelperModule');
}

public function testSuggestMissingModule()
{
$correctModule = 'Codeception\Lib\HelperModule';
$wrongModule = 'Codeception\Lib\Helpamodule';

$config = ['modules' => [
'enabled' => [$correctModule],
]];
$this->moduleContainer = new ModuleContainer(Stub::make('Codeception\Lib\Di'), $config);
$this->moduleContainer->create('Codeception\Lib\HelperModule');

$message = "Codeception\Lib\ModuleContainer: Module $wrongModule couldn't be connected (did you mean '$correctModule'?)";
$this->expectException('\Codeception\Exception\ModuleException');
$this->expectExceptionMessage($message);
$this->moduleContainer->getModule($wrongModule);
}
}

class StubModule extends \Codeception\Module
Expand Down

0 comments on commit f1e1daa

Please sign in to comment.