Skip to content

Commit

Permalink
Replace doctrine/inflector with simpler mapping function (#5165)
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm authored Oct 24, 2023
1 parent b45a987 commit 1c54292
Show file tree
Hide file tree
Showing 6 changed files with 493 additions and 54 deletions.
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
"clue/ndjson-react": "^1.3",
"composer/semver": "^3.3.2",
"composer/xdebug-handler": "^3.0.3",
"doctrine/inflector": "^2.0.6",
"fidry/cpu-core-counter": "^0.5.1",
"illuminate/container": "^10.20",
"nette/utils": "^3.2",
Expand Down
162 changes: 155 additions & 7 deletions rules/Naming/ExpectedNameResolver/InflectorSingularResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Rector\Naming\ExpectedNameResolver;

use Doctrine\Inflector\Inflector;
use Nette\Utils\Strings;
use Rector\Core\Util\StringUtils;

Expand Down Expand Up @@ -37,11 +36,6 @@ final class InflectorSingularResolver
*/
private const CAMELCASE = 'camelcase';

public function __construct(
private readonly Inflector $inflector
) {
}

public function resolve(string $currentName): string
{
$matchBy = Strings::match($currentName, self::BY_MIDDLE_REGEX);
Expand Down Expand Up @@ -95,7 +89,7 @@ private function singularizeCamelParts(string $currentName): string

$resolvedName = '';
foreach ($camelCases as $camelCase) {
$value = $this->inflector->singularize($camelCase[self::CAMELCASE]);
$value = $this->singularize($camelCase[self::CAMELCASE]);

if (in_array($camelCase[self::CAMELCASE], ['is', 'has'], true)) {
$value = $camelCase[self::CAMELCASE];
Expand All @@ -106,4 +100,158 @@ private function singularizeCamelParts(string $currentName): string

return $resolvedName;
}

// see https://gist.github.com/peter-mcconnell/9757549
private function singularize(string $word): string
{
$singular = [
'/(quiz)zes$/i' => '\\1',
'/(matr)ices$/i' => '\\1ix',
'/(vert|ind)ices$/i' => '\\1ex',
'/^(ox)en/i' => '\\1',
'/^(axe)s$/i' => '\\1',
'/(alias|status|iris|hoax|hero|gas|fax|circus|canvas|atlas)es$/i' => '\\1',
'/([octop|vir])i$/i' => '\\1us',
'/(cris|ax|test)es$/i' => '\\1is',
'/(shoe|grave|glove|foe|dive|database|curve|cookie|cave|cache|avalanche|abuse)s$/i' => '\\1',
'/(o)es$/i' => '\\1',
'/(bus|lens)es$/i' => '\\1',
'/([m|l])ice$/i' => '\\1ouse',
'/(x|ch|ss|sh)es$/i' => '\\1',
'/(m)ovies$/i' => '\\1ovie',
'/(s)eries$/i' => '\\1eries',
'/([^aeiouy]|qu)ies$/i' => '\\1y',
'/([lr])ves$/i' => '\\1f',
'/(tive)s$/i' => '\\1',
'/(hive)s$/i' => '\\1',
'/([^f])ves$/i' => '\\1fe',
'/(^analy)ses$/i' => '\\1sis',
'/((a)naly|(b)a|(d)iagno|empha|(p)arenthe|(p)rogno|(s)ynop|(t)he|(oa)|neuro)ses$/i' => '\\1\\2sis',
'/([ti]|memorand|curricul)a$/i' => '\\1um',
'/(n)ews$/i' => '\\1ews',
'/s$/i' => '',
];

$irregular = [
'alumnus' => 'alumni',
'person' => 'people',
'man' => 'men',
'bacillus' => 'bacilli',
'criterion' => 'criteria',
'fungus' => 'fungi',
'foot' => 'feet',
'goose' => 'geese',
'genus' => 'genera',
'hippopotamus' => 'hippopotami',
'child' => 'children',
'code' => 'codes',
'octopus' => 'octopuses',
'olive' => 'olives',
'chateau' => 'chateaux',
'plateau' => 'plateaux',
'niveau' => 'niveaux',
'passerby' => 'passersby',
'save' => 'saves',
'sex' => 'sexes',
'syllabus' => 'syllabi',
'stimulus' => 'stimuli',
'sku' => 'skus',
'sieve' => 'sieves',
'taxon' => 'taxa',
'taxi' => 'taxis',
'tax' => 'taxes',
'tooth' => 'teeth',
'tights' => 'tights',
'Thief' => 'Thieves',
'terminus' => 'termini',
'larva' => 'larvae',
'leaf' => 'leaves',
'loaf' => 'loaves',
'move' => 'moves',
'nucleus' => 'nuclei',
'valve' => 'valves',
'wave' => 'waves',
'zombie' => 'zombies',
];

// keep words ending in $ignore
$ignore = [
'breeches',
'britches',
'cantus',
'chassis',
'corps',
'coreopsis',
'contretemps',
'coitus',
'clothes',
'clippers',
'data',
'diabetes',
'debris',
'equipment',
'gallows',
'hijinks',
'herpes',
'headquarters',
'information',
'rice',
'socialmedia',
'jeans',
'jackanapes',
'nodemedia',
'money',
'mumps',
'mews',
'innings',
'nexus',
'rhinoceros',
'rabies',
'pants',
'police',
'pliers',
'progress',
'proceedings',
'pincers',
'scissors',
'species',
'series',
'status',
'shorts',
'shears',
'fish',
'sheep',
'press',
'sms',
'trousers',
'trivia',
'yengeese',
];

$lower_word = strtolower($word);
foreach ($ignore as $ignore_word) {
if (substr($lower_word, (-1 * strlen($ignore_word))) === $ignore_word) {
return $word;
}
}

foreach ($irregular as $singular_word => $plural_word) {
$arr = Strings::match($word, '/(' . $plural_word . ')$/i');
if ($arr !== null) {
return Strings::replace(
$word,
'/(' . $plural_word . ')$/i',
substr($arr[0], 0, 1) . substr($singular_word, 1)
);
}
}

foreach ($singular as $rule => $replacement) {
if (Strings::match($word, $rule) !== null) {
return Strings::replace($word, $rule, $replacement);
}
}

return $word;
}
}
8 changes: 4 additions & 4 deletions rules/Naming/Naming/PropertyNaming.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use PHPStan\Type\TypeWithClassName;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Util\StringUtils;
use Rector\Naming\RectorNamingInflector;
use Rector\Naming\ExpectedNameResolver\InflectorSingularResolver;
use Rector\Naming\ValueObject\ExpectedName;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\StaticTypeMapper\ValueObject\Type\AliasedObjectType;
Expand Down Expand Up @@ -55,7 +55,7 @@ final class PropertyNaming
private const GET_PREFIX_REGEX = '#^get(?<root_name>[A-Z].+)#';

public function __construct(
private readonly RectorNamingInflector $rectorNamingInflector,
private readonly InflectorSingularResolver $inflectorSingularResolver,
private readonly NodeTypeResolver $nodeTypeResolver,
) {
}
Expand All @@ -69,7 +69,7 @@ public function getExpectedNameFromMethodName(string $methodName): ?ExpectedName

$originalName = lcfirst((string) $matches['root_name']);

return new ExpectedName($originalName, $this->rectorNamingInflector->singularize($originalName));
return new ExpectedName($originalName, $this->inflectorSingularResolver->resolve($originalName));
}

public function getExpectedNameFromType(Type $type): ?ExpectedName
Expand Down Expand Up @@ -108,7 +108,7 @@ public function getExpectedNameFromType(Type $type): ?ExpectedName

// prolong too short generic names with one namespace up
$originalName = $this->prolongIfTooShort($shortClassName, $className);
return new ExpectedName($originalName, $this->rectorNamingInflector->singularize($originalName));
return new ExpectedName($originalName, $this->inflectorSingularResolver->resolve($originalName));
}

public function fqnToVariableName(ThisType | ObjectType | string $objectType): string
Expand Down
35 changes: 0 additions & 35 deletions rules/Naming/RectorNamingInflector.php

This file was deleted.

7 changes: 0 additions & 7 deletions src/DependencyInjection/LazyContainerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

namespace Rector\Core\DependencyInjection;

use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\Rules\English\InflectorFactory;
use Illuminate\Container\Container;
use PhpParser\Lexer;
use PHPStan\Analyser\NodeScopeResolver;
Expand Down Expand Up @@ -403,11 +401,6 @@ public function create(): RectorConfig
->needs('$commands')
->giveTagged(Command::class);

$rectorConfig->singleton(Inflector::class, static function (): Inflector {
$inflectorFactory = new InflectorFactory();
return $inflectorFactory->build();
});

$rectorConfig->tag(ProcessCommand::class, Command::class);
$rectorConfig->tag(WorkerCommand::class, Command::class);
$rectorConfig->tag(SetupCICommand::class, Command::class);
Expand Down
Loading

0 comments on commit 1c54292

Please sign in to comment.