Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions internal/apply/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/rectorphp/argtyper/internal/aggregate"
"github.com/rectorphp/argtyper/internal/phpast"
"github.com/rectorphp/php-parser-in-go/pkg/ast"
"github.com/rectorphp/php-parser-in-go/pkg/token"
)

// Source adds parameter types to a single PHP source file. It returns the new
Expand Down Expand Up @@ -95,14 +96,48 @@ func (a *applier) setType(param *ast.Parameter, resolved aggregate.Resolved) {
nullable := resolved.Nullable || hasNullDefault(param)
typeText := typeText(resolved.Type)

// Take the whitespace that sat before the variable (indentation, or the
// space after a comma or modifier) and put it in front of the new type, so
// the type slots into the variable's old position. A single space then
// separates the type from the variable.
leading := takeVarLeading(param)
identifier := &ast.Identifier{IdentifierTkn: &token.Token{Value: []byte(typeText)}}

if nullable {
param.Type = &ast.Nullable{Expr: &ast.Identifier{Value: []byte(typeText + " ")}}
param.Type = &ast.Nullable{
QuestionTkn: &token.Token{Value: []byte("?"), FreeFloating: leading},
Expr: identifier,
}
} else {
param.Type = &ast.Identifier{Value: []byte(typeText + " ")}
identifier.IdentifierTkn.FreeFloating = leading
param.Type = identifier
}
a.added++
}

// takeVarLeading returns the leading whitespace tokens of the parameter's
// variable and replaces them with a single space.
func takeVarLeading(param *ast.Parameter) []*token.Token {
variable, ok := param.Var.(*ast.ExprVariable)
if !ok {
return nil
}

leadingToken := variable.DollarTkn
if leadingToken == nil {
if name, ok := variable.Name.(*ast.Identifier); ok {
leadingToken = name.IdentifierTkn
}
}
if leadingToken == nil {
return nil
}

leading := leadingToken.FreeFloating
leadingToken.FreeFloating = []*token.Token{{Value: []byte(" ")}}
return leading
}

// typeText turns a resolved type into the text written into source. The trailing
// space that separates the type from the variable is added by the caller.
func typeText(resolved string) string {
Expand Down
21 changes: 21 additions & 0 deletions internal/apply/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,27 @@ func TestApply(t *testing.T) {
target: "<?php\nclass A extends Base {\n private function set($v) {}\n public function go() { $this->set(1); }\n}",
want: "<?php\nclass A extends Base {\n private function set(int $v) {}\n public function go() { $this->set(1); }\n}",
},
{
name: "keeps comma spacing when typing a later parameter",
target: "<?php\nfinal class A {\n public function set($name, $count) {}\n public function go() { $this->set(\"x\", 1); }\n}",
want: "<?php\nfinal class A {\n public function set(string $name, int $count) {}\n public function go() { $this->set(\"x\", 1); }\n}",
},
{
name: "types promoted constructor parameter with modifier",
target: "<?php\nfinal class A {\n public function __construct(private $guest) {}\n}",
callers: []string{
"<?php\nnew A(true);",
},
want: "<?php\nfinal class A {\n public function __construct(private bool $guest) {}\n}",
},
{
name: "keeps indentation on multiline parameters",
target: "<?php\nfunction make(\n $a,\n $b\n) {}",
callers: []string{
"<?php\nmake(1, \"x\");",
},
want: "<?php\nfunction make(\n int $a,\n string $b\n) {}",
},
{
name: "adds array type",
target: "<?php\nfunction take($items) {}",
Expand Down
Loading