Skip to content
Closed
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
52 changes: 52 additions & 0 deletions cmd/buf/buf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4333,6 +4333,39 @@ func TestFormatInvalidIncludePackageFiles(t *testing.T) {
)
}

func TestFormatInvalidMultipleFileArguments(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
// Test that multiple positional arguments cannot be combined with --path flags
testRunStderrContainsNoWarn(
t,
nil,
1,
[]string{
"Failure: cannot specify both positional file arguments and --path flags",
},
"format",
"--path",
filepath.Join("testdata", "format", "multifile", "file1.proto"),
filepath.Join("testdata", "format", "multifile", "file2.proto"),
filepath.Join("testdata", "format", "multifile", "file3.proto"),
)
// Test that multiple files require directory output (not single file)
testRunStderrContainsNoWarn(
t,
nil,
1,
[]string{
"Failure: --output must be a directory when formatting multiple files",
},
"format",
"-o",
filepath.Join(tempDir, "output.proto"),
filepath.Join("testdata", "format", "multifile", "file1.proto"),
filepath.Join("testdata", "format", "multifile", "file2.proto"),
)
}

func TestFormatInvalidInputDoesNotCreateDirectory(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
Expand Down Expand Up @@ -4364,6 +4397,25 @@ func TestFormatInvalidInputDoesNotCreateDirectory(t *testing.T) {
assert.True(t, os.IsNotExist(err))
}

func TestFormatMultipleFiles(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
formattedDir := filepath.Join(tempDir, "multifile.formatted")
// Format the multiple file paths to a temp directory
testRunStdout(
t,
nil,
0,
``,
"format",
filepath.Join("testdata", "format", "multifile", "file1.proto"),
filepath.Join("testdata", "format", "multifile", "file2.proto"),
filepath.Join("testdata", "format", "multifile", "file3.proto"),
"-o",
formattedDir,
)
}

func TestConvertRoundTrip(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
Expand Down
50 changes: 45 additions & 5 deletions cmd/buf/internal/command/format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func NewCommand(
) *appcmd.Command {
flags := newFlags()
return &appcmd.Command{
Use: name + " <source>",
Use: name + " [<source>...]",
Short: "Format Protobuf files",
Long: `
By default, the source is the current directory and the formatted content is written to stdout.
Expand Down Expand Up @@ -145,6 +145,10 @@ Rewrite a single file in-place:

$ buf format simple.proto -w

Rewrite multiple files in-place:

$ buf format file1.proto file2.proto file3.proto -w

Rewrite an entire directory in-place:

$ buf format proto -w
Expand All @@ -158,7 +162,6 @@ Write a diff and rewrite the file(s) in-place:

The -w and -o flags cannot be used together in a single invocation.
`,
Args: appcmd.MaximumNArgs(1),
Run: builder.NewRunFunc(
func(ctx context.Context, container appext.Container) error {
return run(ctx, container, flags)
Expand Down Expand Up @@ -243,9 +246,40 @@ func run(
container appext.Container,
flags *flags,
) (retErr error) {
source, err := bufcli.GetInputValue(container, flags.InputHashtag, ".")
if err != nil {
return err
// Handle multiple positional arguments
var source string
numArgs := container.NumArgs()

// Special case: check for inputHashtag flag
if flags.InputHashtag != "" {
if numArgs > 0 {
return appcmd.NewInvalidArgumentErrorf("only 1 argument allowed but %d arguments specified", numArgs+1)
}
source = "-#" + flags.InputHashtag
} else if numArgs == 0 {
// No arguments provided, use default
source = "."
} else if numArgs == 1 {
// Single argument - use as source
arg := container.Arg(0)
if arg == "" {
return appcmd.NewInvalidArgumentError("first argument is present but empty")
}
source = arg
} else {
// Multiple arguments - treat all as file paths within the current directory
if len(flags.Paths) > 0 {
return appcmd.NewInvalidArgumentError("cannot specify both positional file arguments and --path flags")
}
source = "."
// Collect all arguments as paths
for i := range numArgs {
arg := container.Arg(i)
if arg == "" {
return appcmd.NewInvalidArgumentErrorf("argument %d is present but empty", i+1)
}
flags.Paths = append(flags.Paths, arg)
}
}
// We use getDirOrProtoFileRef to see if we have a valid DirOrProtoFileRef, and if so,
// whether or not we have IncludePackageFiles Set.
Expand Down Expand Up @@ -287,6 +321,12 @@ func run(
if err := validateNoIncludePackageFiles(dirOrProtoFileRef); err != nil {
return err
}
// When formatting multiple files, output must be a directory
if numArgs > 1 && flags.Output != "-" {
if _, ok := dirOrProtoFileRef.(buffetch.DirRef); !ok {
return appcmd.NewInvalidArgumentErrorf("--%s must be a directory when formatting multiple files", outputFlagName)
}
}

controller, err := bufcli.NewController(
container,
Expand Down
8 changes: 8 additions & 0 deletions cmd/buf/testdata/format/multifile/file1.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
syntax = "proto3";

package multifile;

message Object1 {
string key = 1;
bytes value = 2;
}
9 changes: 9 additions & 0 deletions cmd/buf/testdata/format/multifile/file2.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
syntax = "proto3";

package multifile;


message Object2 {
string name = 1;
int32 id = 2;
}
8 changes: 8 additions & 0 deletions cmd/buf/testdata/format/multifile/file3.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
syntax = "proto3";

package multifile;

message Object3 {
string description = 1;
bool active = 2;
}