diff --git a/Makefile b/Makefile index 9c8ae11..8157e49 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ BIN := .tmp/bin export PATH := $(abspath $(BIN)):$(PATH) export GOBIN := $(abspath $(BIN)) COPYRIGHT_YEARS := 2024-2025 -LICENSE_IGNORE := --ignore testdata/ +LICENSE_IGNORE := --ignore testdata/ --ignore .github/ --ignore .golangci.yml --ignore buf.gen.yaml -BUF_VERSION := v1.67.0 -GOLANGCI_LINT_VERSION := v2.13.1 +BUF_VERSION := v1.75.0 +GOLANGCI_LINT_VERSION := v2.15.0 #GO_GET_PKGS := .PHONY: help diff --git a/check/checktest/checktest.go b/check/checktest/checktest.go index f2f4350..939d1aa 100644 --- a/check/checktest/checktest.go +++ b/check/checktest/checktest.go @@ -21,7 +21,9 @@ package checktest import ( "context" "errors" + "os" "path/filepath" + "slices" "strconv" "testing" @@ -30,15 +32,15 @@ import ( "buf.build/go/bufplugin/descriptor" "buf.build/go/bufplugin/internal/pkg/xslices" "buf.build/go/bufplugin/option" - "github.com/bufbuild/protocompile" - "github.com/bufbuild/protocompile/linker" - "github.com/bufbuild/protocompile/parser" - "github.com/bufbuild/protocompile/protoutil" - "github.com/bufbuild/protocompile/reporter" - "github.com/bufbuild/protocompile/wellknownimports" + "github.com/bufbuild/protocompile/experimental/fdp" + "github.com/bufbuild/protocompile/experimental/incremental" + "github.com/bufbuild/protocompile/experimental/incremental/queries" + "github.com/bufbuild/protocompile/experimental/ir" + "github.com/bufbuild/protocompile/experimental/report" + "github.com/bufbuild/protocompile/experimental/seq" + "github.com/bufbuild/protocompile/experimental/source" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/types/descriptorpb" ) @@ -337,68 +339,83 @@ func compile( ) ([]descriptor.FileDescriptor, error) { dirPaths = fromSlashPaths(dirPaths) filePaths = fromSlashPaths(filePaths) + toSlashFilePaths := make([]string, len(filePaths)) toSlashFilePathMap := make(map[string]struct{}, len(filePaths)) - for _, filePath := range filePaths { - toSlashFilePathMap[filepath.ToSlash(filePath)] = struct{}{} - } - - var warningErrorsWithPos []reporter.ErrorWithPos - compiler := protocompile.Compiler{ - Resolver: wellknownimports.WithStandardImports( - &protocompile.SourceResolver{ - ImportPaths: dirPaths, - }, - ), - Reporter: reporter.NewReporter( - func(err reporter.ErrorWithPos) error { - return err - }, - func(errorWithPos reporter.ErrorWithPos) { - warningErrorsWithPos = append(warningErrorsWithPos, errorWithPos) - }, - ), - // This is what buf uses. - SourceInfoMode: protocompile.SourceInfoExtraOptionLocations, - } - files, err := compiler.Compile(ctx, filePaths...) + for i, filePath := range filePaths { + toSlashFilePaths[i] = filepath.ToSlash(filePath) + toSlashFilePathMap[toSlashFilePaths[i]] = struct{}{} + } + + openers := make(source.Openers, 0, len(dirPaths)+1) + for _, dirPath := range dirPaths { + openers = append(openers, &source.FS{FS: os.DirFS(dirPath)}) + } + openers = append(openers, source.WKTs()) + + executor := incremental.New( + // Unused imports and unspecified syntax are read directly from the IR, + // so warnings are not needed. + incremental.WithReportOptions(report.Options{SuppressWarnings: true}), + ) + results, diagnosticReport, err := incremental.Run( + ctx, + executor, + queries.Link{ + Opener: &openers, + Session: new(ir.Session), + Workspace: source.NewWorkspace(toSlashFilePaths...), + }, + ) if err != nil { return nil, err } - syntaxUnspecifiedFilePaths := make(map[string]struct{}) - filePathToUnusedDependencyFilePaths := make(map[string]map[string]struct{}) - for _, warningErrorWithPos := range warningErrorsWithPos { - maybeAddSyntaxUnspecified(syntaxUnspecifiedFilePaths, warningErrorWithPos) - maybeAddUnusedDependency(filePathToUnusedDependencyFilePaths, warningErrorWithPos) + if text, errorCount, _ := (report.Renderer{Compact: true}).RenderString(diagnosticReport); errorCount > 0 { + return nil, errors.New(text) + } + if results[0].Fatal != nil { + return nil, results[0].Fatal } - fileDescriptorSet := fileDescriptorSetForFileDescriptors(files) + irFiles := slices.DeleteFunc(slices.Clone(results[0].Value), func(irFile *ir.File) bool { + return irFile == nil + }) - protoFileDescriptors := make([]*descriptorv1.FileDescriptor, len(fileDescriptorSet.GetFile())) - for i, fileDescriptorProto := range fileDescriptorSet.GetFile() { - _, isNotImport := toSlashFilePathMap[fileDescriptorProto.GetName()] - _, isSyntaxUnspecified := syntaxUnspecifiedFilePaths[fileDescriptorProto.GetName()] - unusedDependencyIndexes := unusedDependencyIndexesForFilePathToUnusedDependencyFilePaths( - fileDescriptorProto, - filePathToUnusedDependencyFilePaths[fileDescriptorProto.GetName()], + var protoFileDescriptors []*descriptorv1.FileDescriptor + for irFile := range ir.TopoSort(irFiles) { + fileDescriptorProto, err := fdp.DescriptorProto( + irFile, + fdp.IncludeSourceCodeInfo(true), + // This is what buf uses. + fdp.GenerateExtraOptionLocations(true), ) + if err != nil { + return nil, err + } + _, isNotImport := toSlashFilePathMap[fileDescriptorProto.GetName()] var protoModuleName *descriptorv1.ModuleName if isNotImport && moduleName != nil { protoModuleName = moduleName.ToProto() } - protoFileDescriptors[i] = descriptorv1.FileDescriptor_builder{ + protoFileDescriptors = append(protoFileDescriptors, descriptorv1.FileDescriptor_builder{ FileDescriptorProto: fileDescriptorProto, IsImport: !isNotImport, - IsSyntaxUnspecified: isSyntaxUnspecified, - UnusedDependency: unusedDependencyIndexes, + IsSyntaxUnspecified: irFile.AST().Syntax().IsZero(), + UnusedDependency: unusedDependencyIndexes(fileDescriptorProto, irFile), ModuleName: protoModuleName, - }.Build() + }.Build()) } return descriptor.FileDescriptorsForProtoFileDescriptors(protoFileDescriptors) } -func unusedDependencyIndexesForFilePathToUnusedDependencyFilePaths( +func unusedDependencyIndexes( fileDescriptorProto *descriptorpb.FileDescriptorProto, - unusedDependencyFilePaths map[string]struct{}, + irFile *ir.File, ) []int32 { + unusedDependencyFilePaths := make(map[string]struct{}) + for irImport := range seq.Values(irFile.Imports()) { + if !irImport.Used { + unusedDependencyFilePaths[irImport.Path()] = struct{}{} + } + } unusedDependencyIndexes := make([]int32, 0, len(unusedDependencyFilePaths)) if len(unusedDependencyFilePaths) == 0 { return unusedDependencyIndexes @@ -412,55 +429,6 @@ func unusedDependencyIndexesForFilePathToUnusedDependencyFilePaths( return unusedDependencyIndexes } -func maybeAddSyntaxUnspecified( - syntaxUnspecifiedFilePaths map[string]struct{}, - errorWithPos reporter.ErrorWithPos, -) { - if !errors.Is(errorWithPos, parser.ErrNoSyntax) { - return - } - syntaxUnspecifiedFilePaths[errorWithPos.GetPosition().Filename] = struct{}{} -} - -func maybeAddUnusedDependency( - filePathToUnusedDependencyFilePaths map[string]map[string]struct{}, - errorWithPos reporter.ErrorWithPos, -) { - var errorUnusedImport linker.ErrorUnusedImport - if !errors.As(errorWithPos, &errorUnusedImport) { - return - } - pos := errorWithPos.GetPosition() - unusedDependencyFilePaths, ok := filePathToUnusedDependencyFilePaths[pos.Filename] - if !ok { - unusedDependencyFilePaths = make(map[string]struct{}) - filePathToUnusedDependencyFilePaths[pos.Filename] = unusedDependencyFilePaths - } - unusedDependencyFilePaths[errorUnusedImport.UnusedImport()] = struct{}{} -} - -func fileDescriptorSetForFileDescriptors[D protoreflect.FileDescriptor](files []D) *descriptorpb.FileDescriptorSet { - soFar := make(map[string]struct{}, len(files)) - slice := make([]*descriptorpb.FileDescriptorProto, 0, len(files)) - for _, file := range files { - toFileDescriptorProtoSlice(file, &slice, soFar) - } - return &descriptorpb.FileDescriptorSet{File: slice} -} - -func toFileDescriptorProtoSlice(file protoreflect.FileDescriptor, results *[]*descriptorpb.FileDescriptorProto, soFar map[string]struct{}) { - if _, exists := soFar[file.Path()]; exists { - return - } - soFar[file.Path()] = struct{}{} - // Add dependencies first so the resulting slice is in topological order - imports := file.Imports() - for i, length := 0, imports.Len(); i < length; i++ { - toFileDescriptorProtoSlice(imports.Get(i).FileDescriptor, results, soFar) - } - *results = append(*results, protoutil.ProtoFromFileDescriptor(file)) -} - func fromSlashPaths(paths []string) []string { fromSlashPaths := make([]string, len(paths)) for i, path := range paths { diff --git a/go.mod b/go.mod index 34fe5f5..5f9b74c 100644 --- a/go.mod +++ b/go.mod @@ -1,28 +1,32 @@ module buf.build/go/bufplugin -go 1.26.0 +go 1.26.7 require ( buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.12-20261002170247-538130002972.2 buf.build/go/protovalidate v1.1.3 buf.build/go/spdx v0.3.0 - github.com/bufbuild/protocompile v0.14.1 + github.com/bufbuild/protocompile v0.14.2-0.20261020145055-56248e843b50 github.com/stretchr/testify v1.12.1 - google.golang.org/protobuf v1.36.12 + google.golang.org/protobuf v1.37.0-rc.1 pluginrpc.com/pluginrpc v0.6.0 ) require ( + buf.build/gen/go/bufbuild/protodescriptor/protocolbuffers/go v1.36.12-20250109164928-1da0de137947.1 // indirect buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.1 // indirect buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.12-20241007202033-cf42259fcbfc.2 // indirect cel.dev/expr v0.25.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/google/cel-go v0.29.0 // indirect github.com/mattn/go-isatty v0.0.24 // indirect + github.com/petermattis/goid v0.0.0-20260716134002-a9b348f0a2b9 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.10 // indirect + github.com/tidwall/btree v1.8.1 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect - golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect - golang.org/x/sync v0.16.0 // indirect + golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597 // indirect + golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.28.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a // indirect diff --git a/go.sum b/go.sum index f3f8393..b29cd66 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.12-20261002170247-538130002972.2 h1:nNNsSGs3l9F8q4NfHaIa8RdGfucw957dlFuNf9Z3H/w= buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.12-20261002170247-538130002972.2/go.mod h1:Owcuj2phVyvThVYCD74iSV8ki0dAXw/uxkdcdZScdCc= +buf.build/gen/go/bufbuild/protodescriptor/protocolbuffers/go v1.36.12-20250109164928-1da0de137947.1 h1:hPzyu9O/M/OguwU08/CntzemR43sEaBw0FIJxnFWVqc= +buf.build/gen/go/bufbuild/protodescriptor/protocolbuffers/go v1.36.12-20250109164928-1da0de137947.1/go.mod h1:jMWijYwl5JMuBr0OqOy5wSWh2mkSIfkMlwNsSY1aL6w= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.1 h1:Slv0uGxx219srASyiaI5C9cDlyG8kNDcXpTSYcuAeE4= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.1/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.12-20241007202033-cf42259fcbfc.2 h1:9xw8g8Q6zHLC7CO/jtpl2UK0kQticxIG+mI6N86f2/M= @@ -12,28 +14,40 @@ cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= +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.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= -github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= +github.com/bufbuild/protocompile v0.14.2-0.20261020145055-56248e843b50 h1:JjQ4WXOPRj7b404ouvTJNdYHi2Ib4Bfl5VoSBluKbGs= +github.com/bufbuild/protocompile v0.14.2-0.20261020145055-56248e843b50/go.mod h1:mcyF/iWO0TSVh4LvVBH5gJO1N9pQyooNy+XR4tNlbO0= github.com/google/cel-go v0.29.0 h1:fEG+Ja3YRwNOqnQxTyJwoByAUAvTuxUGiro/jhrm4F4= github.com/google/cel-go v0.29.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/petermattis/goid v0.0.0-20260716134002-a9b348f0a2b9 h1:UyKlK0Ke63afxhHrgJAk8KlCt+kP9KYBRUsWG6lK2WM= +github.com/petermattis/goid v0.0.0-20260716134002-a9b348f0a2b9/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/protocolbuffers/protoscope v0.0.0-20221109213918-8e7a6aafa2c9 h1:arwj11zP0yJIxIRiDn22E0H8PxfF7TsTrc2wIPFIsf4= +github.com/protocolbuffers/protoscope v0.0.0-20221109213918-8e7a6aafa2c9/go.mod h1:SKZx6stCn03JN3BOWTwvVIO2ajMkb/zQdTceXYhKw/4= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA= +github.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= -golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE= -golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597 h1:qLvzZeaANDgyVOA8pyHCOStGlXn0rseXma+GQjeuv2g= +golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= @@ -42,7 +56,9 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a h1: google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a/go.mod h1:y2yVLIE/CSMCPXaHnSKXxu1spLPnglFLegmgdY23uuE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc= google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= -google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= -google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.37.0-rc.1 h1:RQUh2I7V8DjoPJlj2K1BXNUKIOpK2ZdPRfG0HK766Yg= +google.golang.org/protobuf v1.37.0-rc.1/go.mod h1:X7eSFOSeyjM8zqq7vd+xpgLluojDTuH3nwq1zr6OCKg= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= pluginrpc.com/pluginrpc v0.6.0 h1:Dx5r3y9Nbbt02NSm+bKHvs+WgXM34lalD5grkjd8sno= pluginrpc.com/pluginrpc v0.6.0/go.mod h1:9FPMdIy8nmkALE5eSe2ipE42G83WpZnA2+I7M7ADijY=