Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ linters:
- linters:
- containedctx
# we actually want to embed a context here
path: private/bufpkg/bufimage/parser_accessor_handler.go
path: private/bufpkg/bufimage/module_file_resolver.go
- linters:
- containedctx
# we actually want to embed a context here
Expand Down
24 changes: 23 additions & 1 deletion cmd/buf/buf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4493,7 +4493,29 @@ func TestProtoFileNoWorkspaceOrModule(t *testing.T) {
nil,
bufctl.ExitCodeFileAnnotation,
"", // no stdout
filepath.FromSlash(`testdata/protofileref/noworkspaceormodule/fail/import.proto:3:8:import "`)+`google/type/date.proto": file does not exist`,
fmt.Sprintf(
`warning: missing `+"`package`"+` declaration
--> %[1]s
= note: not explicitly specifying a package places the file in the unnamed
package; using it strongly is discouraged

error: imported file does not exist
--> %[1]s:3:1
|
3 | import "google/type/date.proto";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ imported here

error: cannot find `+"`google.type.Date`"+` in this scope
--> %[1]s:6:3
|
6 | google.type.Date date = 1;
| ^^^^^^^^^^^^^^^^ not found in this scope
|
= help: the full name of this scope is `+"`A`"+`

encountered 2 errors and 1 warning`,
filepath.FromSlash("testdata/protofileref/noworkspaceormodule/fail/import.proto"),
),
"build",
filepath.Join("testdata", "protofileref", "noworkspaceormodule", "fail", "import.proto"),
)
Expand Down
31 changes: 29 additions & 2 deletions cmd/buf/imports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package main

import (
"fmt"
"io"
"path/filepath"
"strings"
Expand Down Expand Up @@ -148,7 +149,16 @@ func TestInvalidNonexistentImport(t *testing.T) {
t.Parallel()
testRunStderrWithCache(
t, nil, bufctl.ExitCodeFileAnnotation,
filepath.FromSlash(`testdata/imports/failure/people/people/v1/people1.proto:5:8:import "nonexistent.proto": file does not exist`),
fmt.Sprintf(
`error: imported file does not exist
--> %s:5:1
|
5 | import "nonexistent.proto";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ imported here

encountered 1 error`,
filepath.FromSlash("testdata/imports/failure/people/people/v1/people1.proto"),
),
"build",
filepath.Join("testdata", "imports", "failure", "people"),
)
Expand All @@ -158,7 +168,24 @@ func TestInvalidNonexistentImportFromDirectDep(t *testing.T) {
t.Parallel()
testRunStderrWithCache(
t, nil, bufctl.ExitCodeFileAnnotation,
filepath.FromSlash(`testdata/imports/failure/students/students/v1/students.proto:`)+`6:8:import "people/v1/people_nonexistent.proto": file does not exist`,
fmt.Sprintf(
`error: imported file does not exist
--> %[1]s:6:1
|
6 | import "people/v1/people_nonexistent.proto"; // but nonexistent file in explicit direct import
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ imported here

error: cannot find `+"`people.v1.Person2`"+` in this scope
--> %[1]s:10:3
|
10 | people.v1.Person2 person2 = 2;
| ^^^^^^^^^^^^^^^^^ not found in this scope
|
= help: the full name of this scope is `+"`students.v1.Student`"+`

encountered 2 errors`,
filepath.FromSlash("testdata/imports/failure/students/students/v1/students.proto"),
),
"build",
filepath.Join("testdata", "imports", "failure", "students"),
)
Expand Down
2 changes: 1 addition & 1 deletion cmd/buf/internal/command/breaking/breaking.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ func run(
}
}
if len(allFileAnnotations) > 0 {
allFileAnnotationSet := bufanalysis.NewFileAnnotationSet(allFileAnnotations...)
allFileAnnotationSet := bufanalysis.NewFileAnnotationSet(nil, allFileAnnotations...)
if err := bufanalysis.PrintFileAnnotationSet(
container.Stdout(),
allFileAnnotationSet,
Expand Down
2 changes: 1 addition & 1 deletion cmd/buf/internal/command/lint/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func run(
}
}
if len(allFileAnnotations) > 0 {
allFileAnnotationSet := bufanalysis.NewFileAnnotationSet(allFileAnnotations...)
allFileAnnotationSet := bufanalysis.NewFileAnnotationSet(nil, allFileAnnotations...)
if flags.ErrorFormat == "config-ignore-yaml" {
if err := bufcli.PrintFileAnnotationSetLintConfigIgnoreYAMLV1(
container.Stdout(),
Expand Down
46 changes: 42 additions & 4 deletions cmd/buf/workspace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,30 @@ func TestWorkspaceDir(t *testing.T) {
"lint",
filepath.Join("testdata", "workspace", "success", baseDirPath),
)
dirImportError := fmt.Sprintf(`error: imported file does not exist
--> %[1]s:5:1
|
5 | import "request.proto";
| ^^^^^^^^^^^^^^^^^^^^^^^ imported here

error: cannot find %[2]s in this scope
--> %[1]s:8:5
|
8 | request.Request req = 1;
| ^^^^^^^^^^^^^^^ not found in this scope
|
= help: the full name of this scope is %[3]s

encountered 2 errors`,
filepath.FromSlash("testdata/workspace/success/"+baseDirPath+"/proto/rpc.proto"),
"`request.Request`",
"`example.RPC`",
)
testRunStdoutStderrNoWarn(
t,
nil,
bufctl.ExitCodeFileAnnotation,
filepath.FromSlash(`testdata/workspace/success/`+baseDirPath+`/proto/rpc.proto:5:8:import "request.proto": file does not exist`),
dirImportError,
"",
"lint",
filepath.Join("testdata", "workspace", "success", baseDirPath),
Expand All @@ -156,7 +175,7 @@ func TestWorkspaceDir(t *testing.T) {
t,
nil,
bufctl.ExitCodeFileAnnotation,
filepath.FromSlash(`testdata/workspace/success/`+baseDirPath+`/proto/rpc.proto:5:8:import "request.proto": file does not exist`),
dirImportError,
"",
"lint",
filepath.Join("testdata", "workspace", "success", baseDirPath),
Expand Down Expand Up @@ -366,12 +385,31 @@ func TestWorkspaceDetached(t *testing.T) {
// we'd consider this a bug: you specified the proto directory, and no controlling workspace
// was discovered, therefore you build as if proto was the input directory, which results in
// request.proto not existing as an import.
detachedImportError := fmt.Sprintf(`error: imported file does not exist
--> %[1]s:5:1
|
5 | import "request.proto";
| ^^^^^^^^^^^^^^^^^^^^^^^ imported here

error: cannot find %[2]s in this scope
--> %[1]s:8:5
|
8 | request.Request req = 1;
| ^^^^^^^^^^^^^^^ not found in this scope
|
= help: the full name of this scope is %[3]s

encountered 2 errors`,
filepath.FromSlash("testdata/workspace/success/"+dirPath+"/proto/rpc.proto"),
"`request.Request`",
"`example.RPC`",
)
testRunStdoutStderrNoWarn(
t,
nil,
bufctl.ExitCodeFileAnnotation,
``,
filepath.FromSlash(`testdata/workspace/success/`+dirPath+`/proto/rpc.proto:5:8:import "request.proto": file does not exist`),
detachedImportError,
"build",
filepath.Join("testdata", "workspace", "success", dirPath, "proto"),
)
Expand All @@ -392,7 +430,7 @@ func TestWorkspaceDetached(t *testing.T) {
t,
nil,
bufctl.ExitCodeFileAnnotation,
filepath.FromSlash(`testdata/workspace/success/`+dirPath+`/proto/rpc.proto:5:8:import "request.proto": file does not exist`),
detachedImportError,
``,
"lint",
filepath.Join("testdata", "workspace", "success", dirPath, "proto"),
Expand Down
52 changes: 34 additions & 18 deletions cmd/buf/workspace_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package main

import (
"fmt"
"path/filepath"
"testing"

Expand All @@ -26,24 +27,39 @@ import (
func TestWorkspaceSymlinkFail(t *testing.T) {
t.Parallel()
// The workspace includes a symlink that isn't buildable.
testRunStdoutStderrNoWarn(
t,
nil,
bufctl.ExitCodeFileAnnotation,
``,
filepath.FromSlash(`testdata/workspace/fail/symlink/b/b.proto:5:8:import "c.proto": file does not exist`),
"build",
filepath.Join("testdata", "workspace", "fail", "symlink"),
)
testRunStdoutStderrNoWarn(
t,
nil,
bufctl.ExitCodeFileAnnotation,
``,
filepath.FromSlash(`testdata/workspace/fail/v2/symlink/b/b.proto:5:8:import "c.proto": file does not exist`),
"build",
filepath.Join("testdata", "workspace", "fail", "v2", "symlink"),
)
for _, dirPath := range []string{
"symlink",
"v2/symlink",
} {
symlinkImportError := fmt.Sprintf(`error: imported file does not exist
--> %[1]s:5:1
|
5 | import "c.proto";
| ^^^^^^^^^^^^^^^^^ imported here

error: cannot find %[2]s in this scope
--> %[1]s:8:5
|
8 | c.C c = 1;
| ^^^ not found in this scope
|
= help: the full name of this scope is %[3]s

encountered 2 errors`,
filepath.FromSlash("testdata/workspace/fail/"+dirPath+"/b/b.proto"),
"`c.C`",
"`b.B`",
)
testRunStdoutStderrNoWarn(
t,
nil,
bufctl.ExitCodeFileAnnotation,
``,
symlinkImportError,
"build",
filepath.Join("testdata", "workspace", "fail", filepath.FromSlash(dirPath)),
)
}
}

func TestWorkspaceSymlink(t *testing.T) {
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.25.7

require (
buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.11-20250718181942-e35f9b667443.1
buf.build/gen/go/bufbuild/protodescriptor/protocolbuffers/go v1.36.11-20250109164928-1da0de137947.1
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1
buf.build/gen/go/bufbuild/registry/connectrpc/go v1.19.1-20260126144947-819582968857.2
buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.11-20260126144947-819582968857.1
Expand All @@ -16,7 +17,7 @@ require (
buf.build/go/standard v0.1.1-0.20260320140628-2996a887cf13
connectrpc.com/connect v1.19.1
connectrpc.com/otelconnect v0.9.0
github.com/bufbuild/protocompile v0.14.2-0.20260319203231-019757e4c592
github.com/bufbuild/protocompile v0.14.2-0.20260323163443-aa1a63ac3dac
github.com/bufbuild/protoplugin v0.0.0-20250218205857-750e09ce93e1
github.com/cli/browser v1.3.0
github.com/gofrs/flock v0.13.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4=
github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs=
github.com/bufbuild/protocompile v0.14.2-0.20260319203231-019757e4c592 h1:LP+okT4NAncx5csNnPka2j8rKcq1ekvrdQfQp3jD0TI=
github.com/bufbuild/protocompile v0.14.2-0.20260319203231-019757e4c592/go.mod h1:o4sxIWZ71DJt7/jan0/vi3XbGCQMTBk5KCo27vgT45Q=
github.com/bufbuild/protocompile v0.14.2-0.20260323163443-aa1a63ac3dac h1:aAuMdUyQnZtkFgb8gFDPy5ZZaNRbgOtVOU7Cy++8Ih4=
github.com/bufbuild/protocompile v0.14.2-0.20260323163443-aa1a63ac3dac/go.mod h1:DhgqsRznX/F0sGkUYtTQJRP+q8xMReQRQ3qr+n1opWU=
github.com/bufbuild/protoplugin v0.0.0-20250218205857-750e09ce93e1 h1:V1xulAoqLqVg44rY97xOR+mQpD2N+GzhMHVwJ030WEU=
github.com/bufbuild/protoplugin v0.0.0-20250218205857-750e09ce93e1/go.mod h1:c5D8gWRIZ2HLWO3gXYTtUfw/hbJyD8xikv2ooPxnklQ=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
Expand Down
24 changes: 22 additions & 2 deletions private/bufpkg/bufanalysis/bufanalysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
"io"
"strconv"
"strings"

"github.com/bufbuild/protocompile/experimental/report"
)

const (
Expand Down Expand Up @@ -207,14 +209,27 @@ type FileAnnotationSet interface {
// These will be deduplicated and sorted.
FileAnnotations() []FileAnnotation

// diagnosticReport returns the diagnostic [report.Report] for the [FileAnnotationSet],
// if set.
//
// This may be nil. If non-nil, it will be used as the output for
// [PrintFileAnnotationSet] when the format is set to "text", rendered
// with a [report.Renderer] configured by the given print options.
diagnosticReport() *report.Report

isFileAnnotationSet()
}

// NewFileAnnotationSet returns a new FileAnnotationSet.
//
// If len(fileAnnotations) is 0, this returns nil.
func NewFileAnnotationSet(fileAnnotations ...FileAnnotation) FileAnnotationSet {
return newFileAnnotationSet(fileAnnotations)
//
// The diagnosticReport is the [report.Report] from the compiler, if available.
// If non-nil, it will be rendered by [PrintFileAnnotationSet] when the format
// is "text". Otherwise, the individual file annotations will be used, same as
// all other print formats.
func NewFileAnnotationSet(diagnosticReport *report.Report, fileAnnotations ...FileAnnotation) FileAnnotationSet {
return newFileAnnotationSet(diagnosticReport, fileAnnotations)
}

// PrintFileAnnotationSet prints the file annotations separated by newlines.
Expand All @@ -226,6 +241,11 @@ func PrintFileAnnotationSet(writer io.Writer, fileAnnotationSet FileAnnotationSe

switch format {
case FormatText:
if diagnosticReport := fileAnnotationSet.diagnosticReport(); diagnosticReport != nil {
renderer := report.Renderer{}
_, _, err := renderer.Render(diagnosticReport, writer)
return err
}
return printAsText(writer, fileAnnotationSet.FileAnnotations())
case FormatJSON:
return printAsJSON(writer, fileAnnotationSet.FileAnnotations())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestBasic(t *testing.T) {
),
}
sb := &strings.Builder{}
err := bufanalysis.PrintFileAnnotationSet(sb, bufanalysis.NewFileAnnotationSet(fileAnnotations...), "text")
err := bufanalysis.PrintFileAnnotationSet(sb, bufanalysis.NewFileAnnotationSet(nil, fileAnnotations...), "text")
require.NoError(t, err)
assert.Equal(
t,
Expand All @@ -59,7 +59,7 @@ path/to/file.proto:2:1:Hello. (buf-plugin-foo)
sb.String(),
)
sb.Reset()
err = bufanalysis.PrintFileAnnotationSet(sb, bufanalysis.NewFileAnnotationSet(fileAnnotations...), "json")
err = bufanalysis.PrintFileAnnotationSet(sb, bufanalysis.NewFileAnnotationSet(nil, fileAnnotations...), "json")
require.NoError(t, err)
assert.Equal(
t,
Expand All @@ -69,7 +69,7 @@ path/to/file.proto:2:1:Hello. (buf-plugin-foo)
sb.String(),
)
sb.Reset()
err = bufanalysis.PrintFileAnnotationSet(sb, bufanalysis.NewFileAnnotationSet(fileAnnotations...), "msvs")
err = bufanalysis.PrintFileAnnotationSet(sb, bufanalysis.NewFileAnnotationSet(nil, fileAnnotations...), "msvs")
require.NoError(t, err)
assert.Equal(t,
`path/to/file.proto(1,1) : error FOO : Hello.
Expand All @@ -78,7 +78,7 @@ path/to/file.proto(2,1) : error FOO : Hello. (buf-plugin-foo)
sb.String(),
)
sb.Reset()
err = bufanalysis.PrintFileAnnotationSet(sb, bufanalysis.NewFileAnnotationSet(fileAnnotations...), "junit")
err = bufanalysis.PrintFileAnnotationSet(sb, bufanalysis.NewFileAnnotationSet(nil, fileAnnotations...), "junit")
require.NoError(t, err)
assert.Equal(t,
`<testsuites>
Expand All @@ -98,6 +98,7 @@ path/to/file.proto(2,1) : error FOO : Hello. (buf-plugin-foo)
err = bufanalysis.PrintFileAnnotationSet(
sb,
bufanalysis.NewFileAnnotationSet(
nil,
append(
fileAnnotations,
newFileAnnotation(
Expand Down Expand Up @@ -135,7 +136,7 @@ path/to/file.proto(2,1) : error FOO : Hello. (buf-plugin-foo)
sb.String(),
)
sb.Reset()
err = bufanalysis.PrintFileAnnotationSet(sb, bufanalysis.NewFileAnnotationSet(fileAnnotations...), "gitlab-code-quality")
err = bufanalysis.PrintFileAnnotationSet(sb, bufanalysis.NewFileAnnotationSet(nil, fileAnnotations...), "gitlab-code-quality")
require.NoError(t, err)
assert.Equal(t,
`[{"description":"Hello.","check_name":"FOO","fingerprint":"7fa769d9df9f6db3b793316aa485c307df262ece452189a1c434e77480e9a6a26759f7616faf70c654639075b2fd170d3b66eef686ad402c72b550305883a7b7","location":{"path":"path/to/file.proto","positions":{"begin":{"line":1},"end":{"line":1}}},"severity":"minor"},{"description":"Hello.","check_name":"FOO","fingerprint":"60eab160b8308bb2c5fb823e200a54d58749513e2f2ad28447584f7223812f106a746ab7bf5fa5493e2e320163f86b46d098cb398f1795715617a825665e2a89","location":{"path":"path/to/file.proto","positions":{"begin":{"line":2,"column":1},"end":{"line":2,"column":1}}},"severity":"minor"}]
Expand Down
Loading
Loading