1414use PhpParser \Node \Name ;
1515use PhpParser \Node \NullableType ;
1616use PhpParser \Node \Param ;
17+ use PhpParser \Node \Scalar \Float_ ;
18+ use PhpParser \Node \Scalar \Int_ ;
19+ use PhpParser \Node \Scalar \String_ ;
1720use PhpParser \Node \Stmt \ClassMethod ;
1821use PhpParser \Node \Stmt \Function_ ;
1922use PhpParser \Node \UnionType ;
23+ use Rector \PhpParser \Node \Value \ValueResolver ;
2024use Rector \Rector \AbstractRector ;
2125use Rector \ValueObject \PhpVersionFeature ;
2226use Rector \VersionBonding \Contract \MinPhpVersionInterface ;
2832 */
2933final class OptionalParametersAfterRequiredRector extends AbstractRector implements MinPhpVersionInterface
3034{
35+ public function __construct (
36+ private readonly ValueResolver $ valueResolver ,
37+ ) {
38+ }
39+
3140 public function getRuleDefinition (): RuleDefinition
3241 {
33- return new RuleDefinition ('Add null default value when a required parameter follows an optional one ' , [
42+ return new RuleDefinition ('Add reasonable default value when a required parameter follows an optional one ' , [
3443 new CodeSample (
3544 <<<'CODE_SAMPLE'
3645class SomeObject
3746{
38- public function run($optional = 1, $required)
47+ public function run($optional = 1, int $required)
3948 {
4049 }
4150}
@@ -45,7 +54,7 @@ public function run($optional = 1, $required)
4554 <<<'CODE_SAMPLE'
4655class SomeObject
4756{
48- public function run($optional = 1, $required = null )
57+ public function run($optional = 1, int $required = 0 )
4958 {
5059 }
5160}
@@ -84,47 +93,109 @@ public function refactor(Node $node): ClassMethod|Function_|Closure|null
8493 $ previousParam = $ node ->params [$ key - 1 ] ?? null ;
8594 if ($ previousParam instanceof Param && $ previousParam ->default instanceof Expr) {
8695 $ hasChanged = true ;
96+ $ this ->processParam ($ param );
97+ }
98+ }
8799
88- $ param ->default = new ConstFetch (new Name ('null ' ));
100+ return $ hasChanged ? $ node : null ;
101+ }
89102
90- if (! $ param ->type instanceof Node) {
91- continue ;
92- }
103+ public function provideMinPhpVersion (): int
104+ {
105+ return PhpVersionFeature::NULLABLE_TYPE ;
106+ }
93107
94- if ($ param ->type instanceof NullableType) {
95- continue ;
96- }
108+ /**
109+ * Look first found type reasonable value
110+ *
111+ * @param Node[] $types
112+ */
113+ private function mapReasonableParamValue (array $ types ): Expr
114+ {
115+ foreach ($ types as $ type ) {
116+ if ($ this ->isName ($ type , 'string ' )) {
117+ return new String_ ('' );
118+ }
97119
98- if ($ param ->type instanceof UnionType) {
99- foreach ($ param ->type ->types as $ unionedType ) {
100- if ($ unionedType instanceof Identifier && $ this ->isName ($ unionedType , 'null ' )) {
101- continue 2 ;
102- }
103- }
120+ if ($ this ->isName ($ type , 'int ' )) {
121+ return new Int_ (0 );
122+ }
104123
105- $ param -> type -> types [] = new Identifier ( ' null ' );
106- continue ;
107- }
124+ if ( $ this -> isName ( $ type, ' float ' )) {
125+ return new Float_ ( 0.0 ) ;
126+ }
108127
109- if ($ param ->type instanceof IntersectionType) {
110- $ param ->type = new UnionType ([$ param ->type , new Identifier ('null ' )]);
128+ if ($ this ->isName ($ type , 'bool ' )) {
129+ return $ this ->nodeFactory ->createFalse ();
130+ }
111131
112- continue ;
113- }
132+ if ($ this ->isName ($ type , 'array ' )) {
133+ return $ this ->nodeFactory ->createArray ([]);
134+ }
114135
115- if ($ param -> type instanceof ComplexType ) {
116- continue ;
117- }
136+ if ($ this -> isName ( $ type, ' true ' ) ) {
137+ return $ this -> nodeFactory -> createTrue () ;
138+ }
118139
119- $ param ->type = new NullableType ($ param ->type );
140+ if ($ this ->isName ($ type , 'false ' )) {
141+ return $ this ->nodeFactory ->createFalse ();
120142 }
121143 }
122144
123- return $ hasChanged ? $ node : null ;
145+ return new ConstFetch ( new Name ( ' null ' )) ;
124146 }
125147
126- public function provideMinPhpVersion ( ): int
148+ private function processParam ( Param $ param ): void
127149 {
128- return PhpVersionFeature::NULLABLE_TYPE ;
150+ if (! $ param ->type instanceof Node) {
151+ $ param ->default = new ConstFetch (new Name ('null ' ));
152+ return ;
153+ }
154+
155+ if ($ param ->type instanceof NullableType) {
156+ $ param ->default = new ConstFetch (new Name ('null ' ));
157+ return ;
158+ }
159+
160+ if ($ param ->type instanceof IntersectionType) {
161+ $ param ->default = new ConstFetch (new Name ('null ' ));
162+ $ param ->type = new UnionType ([$ param ->type , new Identifier ('null ' )]);
163+ return ;
164+ }
165+
166+ if ($ param ->type instanceof UnionType) {
167+ foreach ($ param ->type ->types as $ unionedType ) {
168+ if ($ unionedType instanceof Identifier && $ this ->isName ($ unionedType , 'null ' )) {
169+ $ param ->default = new ConstFetch (new Name ('null ' ));
170+ return ;
171+ }
172+ }
173+
174+ $ reasonableValue = $ this ->mapReasonableParamValue ($ param ->type ->types );
175+ if ($ this ->valueResolver ->isNull ($ reasonableValue )) {
176+ $ param ->default = new ConstFetch (new Name ('null ' ));
177+ $ param ->type ->types [] = new Identifier ('null ' );
178+ return ;
179+ }
180+
181+ $ param ->default = $ reasonableValue ;
182+ return ;
183+ }
184+
185+ if ($ param ->type instanceof ComplexType) {
186+ return ;
187+ }
188+
189+ $ reasonableValue = $ this ->mapReasonableParamValue ([$ param ->type ]);
190+ if ($ this ->valueResolver ->isNull ($ reasonableValue )) {
191+ if (! $ param ->type instanceof Identifier || ! $ this ->isNames ($ param ->type , ['null ' , 'mixed ' ])) {
192+ $ param ->type = new NullableType ($ param ->type );
193+ }
194+
195+ $ param ->default = new ConstFetch (new Name ('null ' ));
196+ return ;
197+ }
198+
199+ $ param ->default = $ reasonableValue ;
129200 }
130201}
0 commit comments