├── .gitignore ├── internal └── bufls │ ├── cmd │ └── bufls │ │ ├── testdata │ │ ├── local │ │ │ ├── namespace │ │ │ │ ├── buf.yaml │ │ │ │ └── foo │ │ │ │ │ ├── foo.proto │ │ │ │ │ └── foo │ │ │ │ │ └── foo_foo.proto │ │ │ ├── nopackage │ │ │ │ └── foo.proto │ │ │ ├── withcachedependency │ │ │ │ ├── buf.lock │ │ │ │ └── baz.proto │ │ │ ├── withworkspacedependency │ │ │ │ ├── buf.lock │ │ │ │ └── workspace.proto │ │ │ ├── buf.work.yaml │ │ │ ├── map │ │ │ │ └── map.proto │ │ │ ├── extend │ │ │ │ └── extend.proto │ │ │ └── group │ │ │ │ └── group.proto │ │ └── cache │ │ │ └── v1 │ │ │ └── module │ │ │ ├── lock │ │ │ └── buf.build │ │ │ │ └── test-owner │ │ │ │ ├── test-repository │ │ │ │ └── 6e230f46113f498392c82d12b1a07b70 │ │ │ │ └── another-test-repository │ │ │ │ └── 84c3cad756d2435982d9e3b72680fa96 │ │ │ ├── sum │ │ │ └── buf.build │ │ │ │ └── test-owner │ │ │ │ ├── test-repository │ │ │ │ └── 6e230f46113f498392c82d12b1a07b70 │ │ │ │ └── another-test-repository │ │ │ │ └── 84c3cad756d2435982d9e3b72680fa96 │ │ │ └── data │ │ │ └── buf.build │ │ │ └── test-owner │ │ │ ├── another-test-repository │ │ │ └── 84c3cad756d2435982d9e3b72680fa96 │ │ │ │ ├── buf.lock │ │ │ │ ├── buf.yaml │ │ │ │ └── qux.proto │ │ │ └── test-repository │ │ │ └── 6e230f46113f498392c82d12b1a07b70 │ │ │ ├── buf.yaml │ │ │ ├── buf.lock │ │ │ └── bar.proto │ │ ├── bufls.go │ │ ├── command │ │ ├── serve │ │ │ └── serve.go │ │ └── definition │ │ │ └── definition.go │ │ └── bufls_test.go │ ├── version.go │ ├── location.go │ ├── buflscli │ └── buflscli.go │ ├── bufls.go │ ├── mapper.go │ ├── handler.go │ └── engine.go ├── .github ├── dependabot.yml ├── workflows │ ├── add-to-project.yaml │ ├── ci.yaml │ └── pr-title.yaml └── CODE_OF_CONDUCT.md ├── cmd └── bufls │ └── bufls.go ├── Makefile ├── go.mod ├── README.md ├── LICENSE └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /.tmp/ 3 | *.pprof 4 | *.svg 5 | cover.out 6 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/local/namespace/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/cache/v1/module/lock/buf.build/test-owner/test-repository/6e230f46113f498392c82d12b1a07b70: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/cache/v1/module/lock/buf.build/test-owner/another-test-repository/84c3cad756d2435982d9e3b72680fa96: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/cache/v1/module/sum/buf.build/test-owner/test-repository/6e230f46113f498392c82d12b1a07b70: -------------------------------------------------------------------------------- 1 | b3-SD4bN9mzWXnwxRD7SK93yKuWfJWSiW1_oFFa4_wpC20= -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/cache/v1/module/sum/buf.build/test-owner/another-test-repository/84c3cad756d2435982d9e3b72680fa96: -------------------------------------------------------------------------------- 1 | b3-3RyvatbpWGnQdYXLdzDhmA5XJ74KOJ3f-RvdJOt5zmY= -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/cache/v1/module/data/buf.build/test-owner/another-test-repository/84c3cad756d2435982d9e3b72680fa96/buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v1 3 | deps: [] 4 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/cache/v1/module/data/buf.build/test-owner/another-test-repository/84c3cad756d2435982d9e3b72680fa96/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | name: buf.build/test-owner/another-test-repository 3 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/local/namespace/foo/foo.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package foo; 4 | 5 | message Something { 6 | message Nested { 7 | string name = 1; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/local/namespace/foo/foo/foo_foo.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package foo.foo; 4 | 5 | import "foo/foo.proto"; 6 | 7 | message FooFoo { 8 | Something.Nested nested = 1; 9 | } 10 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/cache/v1/module/data/buf.build/test-owner/test-repository/6e230f46113f498392c82d12b1a07b70/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | name: buf.build/test-owner/test-repository 3 | deps: 4 | - buf.build/test-owner/another-test-repository 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "gomod" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/cache/v1/module/data/buf.build/test-owner/test-repository/6e230f46113f498392c82d12b1a07b70/buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v1 3 | deps: 4 | - remote: buf.build 5 | owner: test-owner 6 | repository: another-test-repository 7 | commit: 84c3cad756d2435982d9e3b72680fa96 8 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/cache/v1/module/data/buf.build/test-owner/another-test-repository/84c3cad756d2435982d9e3b72680fa96/qux.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package qux.v1; 4 | 5 | message Qux { 6 | message Nested { 7 | message Nested {} 8 | string name = 1; 9 | } 10 | enum NestedEnum { 11 | QUX_NESTED_ENUM_UNSPECIFIED = 0; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/local/nopackage/foo.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message Foo {} 4 | 5 | message Bar { 6 | message Baz {} 7 | 8 | message Qux { 9 | Baz baz = 1; 10 | } 11 | 12 | message Quux { 13 | Bar.Baz baz = 1; 14 | } 15 | 16 | .Bar.Baz baz = 1; 17 | } 18 | 19 | message Quuz { 20 | .Foo foo = 1; 21 | .Bar.Qux qux = 2; 22 | } 23 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/local/withcachedependency/buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v1 3 | deps: 4 | - remote: buf.build 5 | owner: test-owner 6 | repository: test-repository 7 | commit: 6e230f46113f498392c82d12b1a07b70 8 | - remote: buf.build 9 | owner: test-owner 10 | repository: another-test-repository 11 | commit: 84c3cad756d2435982d9e3b72680fa96 12 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/local/withworkspacedependency/buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v1 3 | deps: 4 | - remote: buf.build 5 | owner: test-owner 6 | repository: test-repository 7 | commit: 6e230f46113f498392c82d12b1a07b70 8 | - remote: buf.build 9 | owner: test-owner 10 | repository: another-test-repository 11 | commit: 84c3cad756d2435982d9e3b72680fa96 12 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/local/withworkspacedependency/workspace.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package with.workspace.dependency.v1; 4 | 5 | import "baz.proto"; 6 | 7 | message Foo { 8 | with.cache.dependency.v1.Baz baz = 1; 9 | with.cache.dependency.v1.Qux qux = 2; 10 | } 11 | 12 | message Bar { 13 | .with.cache.dependency.v1.Baz baz = 1; 14 | .with.cache.dependency.v1.Qux qux = 2; 15 | } 16 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/local/withcachedependency/baz.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package with.cache.dependency.v1; 4 | 5 | import "bar.proto"; 6 | 7 | message Baz { 8 | bar.v1.Bar bar = 1; 9 | bar.v1.Bar.Nested nested = 2; 10 | bar.v1.Bar.NestedEnum nested_enum = 3; 11 | } 12 | 13 | message Qux { 14 | .bar.v1.Bar bar = 1; 15 | .bar.v1.Bar.Nested nested = 2; 16 | .bar.v1.Bar.NestedEnum nested_enum = 3; 17 | } 18 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/local/buf.work.yaml: -------------------------------------------------------------------------------- 1 | # We define a single workspace that captures all of the testdata 2 | # to override the buf.work.yaml defined at the base of the repository. 3 | # 4 | # We can remove this file once https://github.com/bufbuild/buf/issues/1056 5 | # is resolved. 6 | version: v1 7 | directories: 8 | - extend 9 | - group 10 | - map 11 | - namespace 12 | - nopackage 13 | - withcachedependency 14 | - withworkspacedependency 15 | -------------------------------------------------------------------------------- /.github/workflows/add-to-project.yaml: -------------------------------------------------------------------------------- 1 | name: Add issues and PRs to project 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | - reopened 8 | - transferred 9 | pull_request_target: 10 | types: 11 | - opened 12 | - reopened 13 | issue_comment: 14 | types: 15 | - created 16 | 17 | jobs: 18 | call-workflow-add-to-project: 19 | name: Call workflow to add issue to project 20 | uses: bufbuild/base-workflows/.github/workflows/add-to-project.yaml@main 21 | secrets: inherit 22 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/cache/v1/module/data/buf.build/test-owner/test-repository/6e230f46113f498392c82d12b1a07b70/bar.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package bar.v1; 4 | 5 | import "qux.proto"; 6 | 7 | message Bar { 8 | message Nested { 9 | string name = 1; 10 | } 11 | enum NestedEnum { 12 | BAR_NESTED_ENUM_UNSPECIFIED = 0; 13 | } 14 | 15 | .qux.v1.Qux qux = 1; 16 | .qux.v1.Qux.Nested qux_nested = 2; 17 | .qux.v1.Qux.Nested.Nested qux_nested_nested = 3; 18 | .qux.v1.Qux.NestedEnum qux_nested_enum = 4; 19 | } 20 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/local/map/map.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package mapv1; 4 | 5 | message Object { 6 | map items = 1; 7 | map nested_items = 2; 8 | 9 | message NestedObject { 10 | message FurtherNestedObject { 11 | map nested_items = 1; 12 | map objects = 2; 13 | } 14 | map further_nested_objects = 1; 15 | } 16 | } 17 | 18 | message Value { 19 | message Nested {} 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: push 3 | # Prevent writing to the repository using the CI token. 4 | # Ref: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions 5 | permissions: read-all 6 | jobs: 7 | ci: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout Code 11 | uses: actions/checkout@v3 12 | with: 13 | fetch-depth: 1 14 | - name: Install Go 15 | uses: actions/setup-go@v3 16 | with: 17 | go-version: 1.19.x 18 | cache: true 19 | check-latest: true 20 | - name: Test 21 | run: make test && make checkgenerate 22 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/local/extend/extend.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package extendv1; 4 | 5 | import "google/protobuf/descriptor.proto"; 6 | 7 | message Option {} 8 | 9 | message Object { 10 | extend google.protobuf.MessageOptions { 11 | Option opt = 10000; 12 | Object object = 10001; 13 | Nested nested = 10002; 14 | Object.Nested object_nested = 10003; 15 | } 16 | 17 | message Nested { 18 | extend google.protobuf.MessageOptions { 19 | .extendv1.Option opt_full = 10004; 20 | .extendv1.Object object_full = 10005; 21 | .extendv1.Object.Nested object_nested_full = 10006; 22 | } 23 | Option opt = 1; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/pr-title.yaml: -------------------------------------------------------------------------------- 1 | name: Lint PR Title 2 | # Prevent writing to the repository using the CI token. 3 | # Ref: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions 4 | permissions: 5 | pull-requests: read 6 | on: 7 | pull_request: 8 | # By default, a workflow only runs when a pull_request's activity type is opened, 9 | # synchronize, or reopened. We explicity override here so that PR titles are 10 | # re-linted when the PR text content is edited. 11 | types: 12 | - opened 13 | - edited 14 | - reopened 15 | - synchronize 16 | jobs: 17 | lint: 18 | uses: bufbuild/base-workflows/.github/workflows/pr-title.yaml@main 19 | -------------------------------------------------------------------------------- /internal/bufls/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Buf Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bufls 16 | 17 | // Version is the CLI version of bufls. 18 | const Version = "0.0.1-dev" 19 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/testdata/local/group/group.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package mapv1; 4 | 5 | message Foo { 6 | optional group One = 1 { 7 | optional string name = 2; 8 | } 9 | 10 | message Nested { 11 | optional One one = 1; 12 | optional group Two = 2 { 13 | optional string name = 3; 14 | } 15 | } 16 | 17 | message AnotherNested { 18 | optional Foo.One one = 1; 19 | } 20 | 21 | message LastNested { 22 | optional .mapv1.Foo.One one = 1; 23 | } 24 | 25 | optional Nested.Two two = 2; 26 | optional One something = 3; 27 | } 28 | 29 | message Bar { 30 | optional Foo.One one = 1; 31 | optional Foo.Nested.Two two = 2; 32 | } 33 | 34 | message Baz { 35 | optional .mapv1.Foo.One one = 1; 36 | optional .mapv1.Foo.Nested.Two two = 2; 37 | } 38 | -------------------------------------------------------------------------------- /cmd/bufls/bufls.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Buf Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import "github.com/bufbuild/buf-language-server/internal/bufls/cmd/bufls" 18 | 19 | func main() { 20 | bufls.Main("bufls") 21 | } 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # See https://tech.davis-hansson.com/p/make/ 2 | SHELL := bash 3 | .DELETE_ON_ERROR: 4 | .SHELLFLAGS := -eu -o pipefail -c 5 | .DEFAULT_GOAL := test 6 | MAKEFLAGS += --warn-undefined-variables 7 | MAKEFLAGS += --no-builtin-rules 8 | MAKEFLAGS += --no-print-directory 9 | COPYRIGHT_YEARS := 2022 10 | LICENSE_IGNORE := -e /testdata/ 11 | 12 | .PHONY: test 13 | test: 14 | go test -race ./... 15 | 16 | .PHONY: build 17 | build: 18 | go build ./... 19 | 20 | .PHONY: install 21 | install: 22 | go install ./... 23 | 24 | .PHONY: upgrade 25 | upgrade: 26 | go get -u -t ./... 27 | go mod tidy -v 28 | 29 | .PHONY: generate 30 | generate: 31 | go install github.com/bufbuild/buf/private/pkg/licenseheader/cmd/license-header@v1.7.0 32 | @# We want to operate on a list of modified and new files, excluding 33 | @# deleted and ignored files. git-ls-files can't do this alone. comm -23 takes 34 | @# two files and prints the union, dropping lines common to both (-3) and 35 | @# those only in the second file (-2). We make one git-ls-files call for 36 | @# the modified, cached, and new (--others) files, and a second for the 37 | @# deleted files. 38 | comm -23 \ 39 | <(git ls-files --cached --modified --others --no-empty-directory --exclude-standard | sort -u | grep -v $(LICENSE_IGNORE) ) \ 40 | <(git ls-files --deleted | sort -u) | \ 41 | xargs license-header \ 42 | --license-type apache \ 43 | --copyright-holder "Buf Technologies, Inc." \ 44 | --year-range "$(COPYRIGHT_YEARS)" 45 | 46 | .PHONY: checkgenerate 47 | checkgenerate: 48 | @# Used in CI to verify that `make generate` doesn't produce a diff. 49 | test -z "$$(git status --porcelain | tee /dev/stderr)" 50 | -------------------------------------------------------------------------------- /internal/bufls/location.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Buf Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bufls 16 | 17 | import ( 18 | "fmt" 19 | "path/filepath" 20 | ) 21 | 22 | type location struct { 23 | path string 24 | line int 25 | column int 26 | } 27 | 28 | func newLocation( 29 | path string, 30 | line int, 31 | column int, 32 | ) (*location, error) { 33 | if filepath.Ext(path) != ".proto" { 34 | return nil, fmt.Errorf("location path %s must be a .proto file", path) 35 | } 36 | if line <= 0 { 37 | return nil, fmt.Errorf("location line %d must be a positive integer", line) 38 | } 39 | if column <= 0 { 40 | return nil, fmt.Errorf("location column %d must be a positive integer", column) 41 | } 42 | return &location{ 43 | path: path, 44 | line: line, 45 | column: column, 46 | }, nil 47 | } 48 | 49 | func (p *location) Path() string { 50 | return p.path 51 | } 52 | 53 | func (p *location) Line() int { 54 | return p.line 55 | } 56 | 57 | func (p *location) Column() int { 58 | return p.column 59 | } 60 | 61 | func (p *location) String() string { 62 | return fmt.Sprintf("%s:%d:%d", p.path, p.line, p.column) 63 | } 64 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/bufls.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Buf Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bufls 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/bufbuild/buf-language-server/internal/bufls" 21 | "github.com/bufbuild/buf-language-server/internal/bufls/cmd/bufls/command/definition" 22 | "github.com/bufbuild/buf-language-server/internal/bufls/cmd/bufls/command/serve" 23 | "github.com/bufbuild/buf/private/pkg/app/appcmd" 24 | "github.com/bufbuild/buf/private/pkg/app/appflag" 25 | ) 26 | 27 | // Main is the entrypoint to the buf CLI. 28 | func Main(name string) { 29 | appcmd.Main(context.Background(), NewRootCommand(name)) 30 | } 31 | 32 | // NewRootCommand returns a new root command. 33 | // 34 | // This is public for use in testing. 35 | func NewRootCommand(name string) *appcmd.Command { 36 | builder := appflag.NewBuilder( 37 | name, 38 | appflag.BuilderWithTracing(), 39 | ) 40 | return &appcmd.Command{ 41 | Use: name, 42 | Short: "The Protobuf Language Server", 43 | Long: "A tool that's compatible with any editor that speaks the Language Server Protocol (LSP).", 44 | Version: bufls.Version, 45 | BindPersistentFlags: appcmd.BindMultiple(builder.BindRoot), 46 | SubCommands: []*appcmd.Command{ 47 | definition.NewCommand("definition", builder), 48 | serve.NewCommand("serve", builder), 49 | }, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bufbuild/buf-language-server 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/bufbuild/buf v1.11.0 7 | github.com/bufbuild/protocompile v0.1.0 8 | github.com/spf13/cobra v1.6.1 9 | github.com/spf13/pflag v1.0.5 10 | go.lsp.dev/jsonrpc2 v0.10.0 11 | go.lsp.dev/protocol v0.12.0 12 | go.lsp.dev/uri v0.3.0 13 | go.uber.org/multierr v1.9.0 14 | go.uber.org/zap v1.24.0 15 | google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 16 | ) 17 | 18 | require ( 19 | github.com/bufbuild/connect-go v1.4.0 // indirect 20 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect 21 | github.com/davecgh/go-spew v1.1.1 // indirect 22 | github.com/felixge/fgprof v0.9.3 // indirect 23 | github.com/gofrs/flock v0.8.1 // indirect 24 | github.com/gofrs/uuid v4.3.1+incompatible // indirect 25 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 26 | github.com/google/pprof v0.0.0-20221203041831-ce31453925ec // indirect 27 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 28 | github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84 // indirect 29 | github.com/klauspost/compress v1.15.13 // indirect 30 | github.com/klauspost/pgzip v1.2.5 // indirect 31 | github.com/pkg/profile v1.7.0 // indirect 32 | github.com/pmezard/go-difflib v1.0.0 // indirect 33 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 34 | github.com/segmentio/asm v1.1.3 // indirect 35 | github.com/segmentio/encoding v0.3.4 // indirect 36 | github.com/stretchr/testify v1.8.1 // indirect 37 | go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 // indirect 38 | go.opencensus.io v0.24.0 // indirect 39 | go.opentelemetry.io/otel v1.11.2 // indirect 40 | go.opentelemetry.io/otel/metric v0.34.0 // indirect 41 | go.opentelemetry.io/otel/trace v1.11.2 // indirect 42 | go.uber.org/atomic v1.10.0 // indirect 43 | golang.org/x/net v0.4.0 // indirect 44 | golang.org/x/sync v0.1.0 // indirect 45 | golang.org/x/sys v0.3.0 // indirect 46 | golang.org/x/term v0.3.0 // indirect 47 | golang.org/x/text v0.5.0 // indirect 48 | gopkg.in/yaml.v3 v3.0.1 // indirect 49 | ) 50 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/command/serve/serve.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Buf Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package serve 16 | 17 | import ( 18 | "context" 19 | "os" 20 | 21 | "github.com/bufbuild/buf-language-server/internal/bufls" 22 | "github.com/bufbuild/buf-language-server/internal/bufls/buflscli" 23 | "github.com/bufbuild/buf/private/buf/bufcli" 24 | "github.com/bufbuild/buf/private/pkg/app/appcmd" 25 | "github.com/bufbuild/buf/private/pkg/app/appflag" 26 | "github.com/spf13/cobra" 27 | "github.com/spf13/pflag" 28 | "go.lsp.dev/protocol" 29 | "go.uber.org/multierr" 30 | ) 31 | 32 | const ( 33 | disableSymlinksFlagName = "disable-symlinks" 34 | ) 35 | 36 | // NewCommand returns a new Command. 37 | func NewCommand( 38 | name string, 39 | builder appflag.Builder, 40 | ) *appcmd.Command { 41 | flags := newFlags() 42 | return &appcmd.Command{ 43 | Use: name, 44 | Short: "Start the language server.", 45 | Args: cobra.MaximumNArgs(1), 46 | Run: builder.NewRunFunc( 47 | func(ctx context.Context, container appflag.Container) error { 48 | return run(ctx, container, flags) 49 | }, 50 | ), 51 | BindFlags: flags.Bind, 52 | } 53 | } 54 | 55 | type flags struct { 56 | DisableSymlinks bool 57 | } 58 | 59 | func newFlags() *flags { 60 | return &flags{} 61 | } 62 | 63 | func (f *flags) Bind(flagSet *pflag.FlagSet) { 64 | bufcli.BindDisableSymlinks(flagSet, &f.DisableSymlinks, disableSymlinksFlagName) 65 | } 66 | 67 | func run( 68 | ctx context.Context, 69 | container appflag.Container, 70 | flags *flags, 71 | ) (retErr error) { 72 | conn := buflscli.NewConn(os.Stdin, os.Stdout) 73 | defer func() { 74 | retErr = multierr.Append(retErr, conn.Close()) 75 | }() 76 | engine, err := buflscli.NewEngine(ctx, container, flags.DisableSymlinks) 77 | if err != nil { 78 | return err 79 | } 80 | handler := bufls.NewHandler(container.Logger(), engine) 81 | conn.Go( 82 | ctx, 83 | protocol.ServerHandler( 84 | handler, 85 | nil, 86 | ), 87 | ) 88 | <-conn.Done() 89 | return nil 90 | } 91 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/command/definition/definition.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Buf Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package definition 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/bufbuild/buf-language-server/internal/bufls" 21 | "github.com/bufbuild/buf-language-server/internal/bufls/buflscli" 22 | "github.com/bufbuild/buf/private/buf/bufcli" 23 | "github.com/bufbuild/buf/private/pkg/app/appcmd" 24 | "github.com/bufbuild/buf/private/pkg/app/appflag" 25 | "github.com/spf13/cobra" 26 | "github.com/spf13/pflag" 27 | ) 28 | 29 | const ( 30 | disableSymlinksFlagName = "disable-symlinks" 31 | ) 32 | 33 | // NewCommand returns a new Command. 34 | func NewCommand( 35 | name string, 36 | builder appflag.Builder, 37 | ) *appcmd.Command { 38 | flags := newFlags() 39 | return &appcmd.Command{ 40 | Use: name + " ", 41 | Short: "Writes the declaration location of selected identifiers.", 42 | Args: cobra.MaximumNArgs(1), 43 | Run: builder.NewRunFunc( 44 | func(ctx context.Context, container appflag.Container) error { 45 | return run(ctx, container, flags) 46 | }, 47 | ), 48 | BindFlags: flags.Bind, 49 | } 50 | } 51 | 52 | type flags struct { 53 | DisableSymlinks bool 54 | } 55 | 56 | func newFlags() *flags { 57 | return &flags{} 58 | } 59 | 60 | func (f *flags) Bind(flagSet *pflag.FlagSet) { 61 | bufcli.BindDisableSymlinks(flagSet, &f.DisableSymlinks, disableSymlinksFlagName) 62 | } 63 | 64 | func run( 65 | ctx context.Context, 66 | container appflag.Container, 67 | flags *flags, 68 | ) (retErr error) { 69 | engine, err := buflscli.NewEngine(ctx, container, flags.DisableSymlinks) 70 | if err != nil { 71 | return err 72 | } 73 | location, err := bufls.ParseLocation(container.Arg(0)) 74 | if err != nil { 75 | return err 76 | } 77 | definitionLocation, err := engine.Definition(ctx, location) 78 | if err != nil { 79 | return err 80 | } 81 | if _, err := container.Stdout().Write([]byte(definitionLocation.String() + "\n")); err != nil { 82 | return err 83 | } 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /internal/bufls/buflscli/buflscli.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Buf Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package buflscli 16 | 17 | import ( 18 | "context" 19 | "io" 20 | 21 | "github.com/bufbuild/buf-language-server/internal/bufls" 22 | "github.com/bufbuild/buf/private/buf/bufcli" 23 | "github.com/bufbuild/buf/private/bufpkg/bufimage/bufimagebuild" 24 | "github.com/bufbuild/buf/private/bufpkg/bufmodule/bufmodulebuild" 25 | "github.com/bufbuild/buf/private/pkg/app/appflag" 26 | "github.com/bufbuild/buf/private/pkg/command" 27 | "go.lsp.dev/jsonrpc2" 28 | "go.uber.org/multierr" 29 | ) 30 | 31 | // NewEngine returns a new bufls.Engine. 32 | func NewEngine( 33 | ctx context.Context, 34 | container appflag.Container, 35 | disableSymlinks bool, 36 | ) (bufls.Engine, error) { 37 | clientConfig, err := bufcli.NewConnectClientConfig(container) 38 | if err != nil { 39 | return nil, err 40 | } 41 | moduleReader, err := bufcli.NewModuleReaderAndCreateCacheDirsWithExternalPaths( 42 | container, 43 | clientConfig, 44 | ) 45 | if err != nil { 46 | return nil, err 47 | } 48 | runner := command.NewRunner() 49 | storageosProvider := bufcli.NewStorageosProvider(disableSymlinks) 50 | moduleConfigReader, err := bufcli.NewWireModuleConfigReaderForModuleReader( 51 | container, 52 | storageosProvider, 53 | runner, 54 | clientConfig, 55 | moduleReader, 56 | ) 57 | if err != nil { 58 | return nil, err 59 | } 60 | moduleFileSetBuilder := bufmodulebuild.NewModuleFileSetBuilder( 61 | container.Logger(), 62 | moduleReader, 63 | ) 64 | imageBuilder := bufimagebuild.NewBuilder(container.Logger()) 65 | return bufls.NewEngine( 66 | container.Logger(), 67 | container, 68 | moduleConfigReader, 69 | moduleFileSetBuilder, 70 | imageBuilder, 71 | ), nil 72 | } 73 | 74 | // NewConn returns a new jsonrpc2.Conn backed by the given io.{Read,Write}Closer 75 | // (which is usually os.Stdin and os.Stdout). 76 | func NewConn(readCloser io.ReadCloser, writeCloser io.WriteCloser) jsonrpc2.Conn { 77 | return jsonrpc2.NewConn( 78 | jsonrpc2.NewStream( 79 | &readWriteCloser{ 80 | readCloser: readCloser, 81 | writeCloser: writeCloser, 82 | }, 83 | ), 84 | ) 85 | } 86 | 87 | type readWriteCloser struct { 88 | readCloser io.ReadCloser 89 | writeCloser io.WriteCloser 90 | } 91 | 92 | func (r *readWriteCloser) Read(b []byte) (int, error) { 93 | return r.readCloser.Read(b) 94 | } 95 | 96 | func (r *readWriteCloser) Write(b []byte) (int, error) { 97 | return r.writeCloser.Write(b) 98 | } 99 | 100 | func (r *readWriteCloser) Close() error { 101 | return multierr.Append(r.readCloser.Close(), r.writeCloser.Close()) 102 | } 103 | -------------------------------------------------------------------------------- /internal/bufls/bufls.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Buf Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bufls 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "strconv" 21 | "strings" 22 | 23 | "github.com/bufbuild/buf/private/buf/bufwire" 24 | "github.com/bufbuild/buf/private/bufpkg/bufimage/bufimagebuild" 25 | "github.com/bufbuild/buf/private/bufpkg/bufmodule/bufmodulebuild" 26 | "github.com/bufbuild/buf/private/pkg/app/appflag" 27 | "go.lsp.dev/protocol" 28 | "go.uber.org/zap" 29 | ) 30 | 31 | // Handler is a Protobuf language server handler. 32 | // 33 | // For details, see https://github.com/golang/tools/tree/master/internal/lsp/protocol/typescript 34 | type Handler interface { 35 | protocol.Server 36 | } 37 | 38 | // NewHandler returns a new Handler. 39 | func NewHandler(logger *zap.Logger, engine Engine) Handler { 40 | return newHandler( 41 | logger, 42 | engine, 43 | ) 44 | } 45 | 46 | // Engine is a Protobuf language server engine. 47 | // 48 | // This is used by both the Handler that speaks the LSP, 49 | // as well as the bufls sub-commands (e.g. 'bufls definition'). 50 | type Engine interface { 51 | Definition(context.Context, Location) (Location, error) 52 | } 53 | 54 | // NewEngine returns a new Protobuf language server engine. 55 | func NewEngine( 56 | logger *zap.Logger, 57 | container appflag.Container, 58 | moduleConfigReader bufwire.ModuleConfigReader, 59 | moduleFileSetBuilder bufmodulebuild.ModuleFileSetBuilder, 60 | imageBuilder bufimagebuild.Builder, 61 | ) Engine { 62 | return newEngine( 63 | logger, 64 | container, 65 | moduleConfigReader, 66 | moduleFileSetBuilder, 67 | imageBuilder, 68 | ) 69 | } 70 | 71 | // Location is a source code location. 72 | type Location interface { 73 | fmt.Stringer 74 | 75 | // Path is the unnormalized path of this location. 76 | Path() string 77 | // Line is the line number of the location. 78 | Line() int 79 | // Column is the column number of the location. 80 | Column() int 81 | } 82 | 83 | // ParseLocation parses a :: into a Location. 84 | func ParseLocation(location string) (Location, error) { 85 | split := strings.Split(location, ":") 86 | if len(split) != 3 { 87 | return nil, fmt.Errorf("location %s is not structured as ::", location) 88 | } 89 | var ( 90 | path = split[0] 91 | line = split[1] 92 | column = split[2] 93 | ) 94 | lineNumber, err := strconv.Atoi(line) 95 | if err != nil { 96 | return nil, fmt.Errorf("location line %s must be an integer", line) 97 | } 98 | columnNumber, err := strconv.Atoi(column) 99 | if err != nil { 100 | return nil, fmt.Errorf("location column %s must be an integer", column) 101 | } 102 | return newLocation( 103 | path, 104 | lineNumber, 105 | columnNumber, 106 | ) 107 | } 108 | -------------------------------------------------------------------------------- /internal/bufls/mapper.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Buf Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bufls 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "net/url" 21 | 22 | "go.lsp.dev/protocol" 23 | "go.lsp.dev/uri" 24 | ) 25 | 26 | // textDocumentPositionParamsToLocation maps the protocol.TextDocumentPositionParams into 27 | // a Location. The text document is zero-based, so we always make sure to increment the 28 | // positional values by one. 29 | func textDocumentPositionParamsToLocation(params protocol.TextDocumentPositionParams) (Location, error) { 30 | uri := params.TextDocument.URI 31 | if uri == "" { 32 | return nil, errors.New("text document uri is required") 33 | } 34 | path, err := uriToPath(uri) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return newLocation( 39 | path, 40 | int(params.Position.Line)+1, 41 | int(params.Position.Character)+1, 42 | ) 43 | } 44 | 45 | // uriToPath maps the URI into a filepath. We would normally be able to 46 | // just call uri.Filename(), but the library panics if the URI is not a 47 | // file:// scheme, so we may as well reimplement the lightweight transformation 48 | // rather than wrapping this in a recover. 49 | func uriToPath(protocolURI protocol.URI) (string, error) { 50 | parsedURI, err := url.ParseRequestURI(string(protocolURI)) 51 | if err != nil { 52 | return "", fmt.Errorf("text document uri is invalid: %w", err) 53 | } 54 | if parsedURI.Scheme != uri.FileScheme { 55 | return "", fmt.Errorf("text document uri must specify the %s scheme, got %v", uri.FileScheme, parsedURI.Scheme) 56 | } 57 | return parsedURI.Path, nil 58 | } 59 | 60 | // locationToProtocolLocation maps the Location into a protocol.Location. 61 | // The protocol.Location is zero-based, so we always decrement the positional 62 | // values by one and validate that the final values are >= 0. 63 | func locationToProtocolLocation(location Location) (protocol.Location, error) { 64 | protocolRange, err := locationToProtocolRange(location) 65 | if err != nil { 66 | return protocol.Location{}, err 67 | } 68 | return protocol.Location{ 69 | URI: uri.File(location.Path()), 70 | Range: protocolRange, 71 | }, nil 72 | } 73 | 74 | // locationToProtocolRange maps the Location into a protocol.Range. 75 | // So far, we only need to capture the start of the range, so we leave 76 | // the end position empty for now. 77 | func locationToProtocolRange(location Location) (protocol.Range, error) { 78 | protocolPosition, err := locationToProtocolPosition(location) 79 | if err != nil { 80 | return protocol.Range{}, err 81 | } 82 | return protocol.Range{ 83 | Start: protocolPosition, 84 | }, nil 85 | } 86 | 87 | // locationToProtocolPosition maps the Location into a protocol.Position. 88 | func locationToProtocolPosition(location Location) (protocol.Position, error) { 89 | line := location.Line() 90 | if line <= 0 { 91 | return protocol.Position{}, fmt.Errorf("text document line must be >= 1, got %d", line) 92 | } 93 | column := location.Column() 94 | if column <= 0 { 95 | return protocol.Position{}, fmt.Errorf("text document column must be >= 1, got %d", column) 96 | } 97 | return protocol.Position{ 98 | Line: uint32(line) - 1, 99 | Character: uint32(column) - 1, 100 | }, nil 101 | } 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Buf Language Server 2 | 3 | > [!IMPORTANT] 4 | > **This repo is no longer under active development**. 5 | > 6 | > However, Buf is working on a new language server implementation that will be integrated directly into the 7 | > [Buf CLI](https://github.com/bufbuild/buf). 8 | > 9 | > For more information, see [this PR](https://github.com/bufbuild/buf/pull/2662). 10 | 11 | 12 | `bufls` is a prototype for the beginnings of a Protobuf language server compatible with 13 | [Buf](https://github.com/bufbuild/buf) modules and workspaces. This currently 14 | only supports go-to-definition. 15 | 16 | **This is a proof-of-concept** that we wanted to share with 17 | the community. We do not actively maintain this, and there are no guarantees 18 | in terms of stability, but we want to hear your feedback! 19 | 20 | For details on where we could go with this, please refer to [future work](#future-work). 21 | 22 | ## Usage 23 | 24 | Regardless of the LSP-compatible editor you use, you'll need to install 25 | `bufls` so that it's available on your `$PATH`. 26 | 27 | ```bash 28 | go install github.com/bufbuild/buf-language-server/cmd/bufls@latest 29 | ``` 30 | 31 | ### Vim 32 | 33 | With [vim-lsp], the only configuration you need is the following: 34 | 35 | ```vim 36 | Plug 'prabirshrestha/vim-lsp' 37 | 38 | augroup LspBuf 39 | au! 40 | autocmd User lsp_setup call lsp#register_server({ 41 | \ 'name': 'bufls', 42 | \ 'cmd': {server_info->['bufls', 'serve']}, 43 | \ 'whitelist': ['proto'], 44 | \ }) 45 | autocmd FileType proto nmap gd (lsp-definition) 46 | augroup END 47 | ``` 48 | 49 | [vim-lsp]: https://github.com/prabirshrestha/vim-lsp 50 | 51 | ## Supported features 52 | 53 | Buf's language server behaves similarly to the rest of the `buf` CLI. If 54 | the user has a `buf.work.yaml` defined, the modules defined in the workspace 55 | will take precedence over the modules specified in the `buf.lock` (i.e. the 56 | modules found in the module cache). The language server requires that inputs 57 | are of the [protofile] type. 58 | 59 | [protofile]: https://docs.buf.build/reference/inputs#protofile 60 | 61 | ### Go to definition 62 | 63 | Go to definition resolves the definition location of a symbol at a 64 | given text document position (i.e. [textDocument/definition]). 65 | 66 | This feature is currently only implemented on the `textDocument/definition` 67 | endpoint. It may make sense to move this to `textDocument/typeDefinition` 68 | and/or `textDocument/typeImplementation`. The Protobuf grammar is far more 69 | limited than a programming language grammar, so not all of the semantics 70 | for each LSP endpoint apply here. 71 | 72 | Today, this feature is only supported for messages and enums. The well-known 73 | types (WKT), and [custom] options are not yet supported. 74 | 75 | [textDocument/definition]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition 76 | 77 | ## Implementation 78 | 79 | Protobuf compilation is _fast_, so the implementation is currently naive. Every 80 | editor command will compile the input file (e.g. `file://proto/pet/v1/pet.proto`) 81 | from scratch (there isn't any caching). Simple caching is fairly straightforward, 82 | but the cache would need to be cleared whenever a file is edited during the same 83 | language server session, which would require a file watcher. For now, performance 84 | is fine as-is (even for workspaces and large modules), but we might need to revisit 85 | this later as build graphs continue to grow. 86 | 87 | ## Future work 88 | 89 | ### More LSP features 90 | 91 | This is just the tip of the iceberg - there's way more that a fully-featured Protobuf 92 | language server can do for the Protobuf community. For starters, the following set of 93 | endpoints are next in line: 94 | 95 | - [textDocument/completion] 96 | - [textDocument/codeLens] 97 | - [textDocument/foldingRange] 98 | - [textDocument/formatting] 99 | - [textDocument/hover] 100 | 101 | [textDocument/completion]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion 102 | [textDocument/codeLens]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeLens 103 | [textDocument/foldingRange]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_foldingRange 104 | [textDocument/formatting]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting 105 | [textDocument/hover]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover 106 | 107 | ### Go to definition 108 | 109 | A couple features remain for full go to definition support: 110 | 111 | - Add go to definition support for [custom] options. 112 | - Add go to definition support for the well-known types (i.e. synthesize the WKT in the module cache). 113 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | conduct@buf.build. All complaints will be reviewed and investigated promptly 64 | and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available 126 | at [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | 134 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022 Buf Technologies, Inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /internal/bufls/handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Buf Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bufls 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | 21 | "go.lsp.dev/protocol" 22 | "go.uber.org/zap" 23 | ) 24 | 25 | type handler struct { 26 | logger *zap.Logger 27 | engine Engine 28 | } 29 | 30 | func newHandler(logger *zap.Logger, engine Engine) *handler { 31 | return &handler{ 32 | logger: logger, 33 | engine: engine, 34 | } 35 | } 36 | 37 | func (h *handler) Definition(ctx context.Context, params *protocol.DefinitionParams) ([]protocol.Location, error) { 38 | inputLocation, err := textDocumentPositionParamsToLocation(params.TextDocumentPositionParams) 39 | if err != nil { 40 | return nil, err 41 | } 42 | outputLocation, err := h.engine.Definition(ctx, inputLocation) 43 | if err != nil { 44 | return nil, err 45 | } 46 | protocolLocation, err := locationToProtocolLocation(outputLocation) 47 | if err != nil { 48 | return nil, err 49 | } 50 | return []protocol.Location{ 51 | protocolLocation, 52 | }, nil 53 | } 54 | 55 | func (h *handler) Initialize(ctx context.Context, params *protocol.InitializeParams) (*protocol.InitializeResult, error) { 56 | return &protocol.InitializeResult{ 57 | Capabilities: protocol.ServerCapabilities{ 58 | DefinitionProvider: true, 59 | }, 60 | ServerInfo: &protocol.ServerInfo{ 61 | Name: "bufls", 62 | Version: Version, 63 | }, 64 | }, nil 65 | } 66 | 67 | func (h *handler) Initialized(ctx context.Context, params *protocol.InitializedParams) error { 68 | return nil 69 | } 70 | 71 | func (h *handler) Shutdown(ctx context.Context) error { 72 | return nil 73 | } 74 | 75 | func (h *handler) Exit(ctx context.Context) error { 76 | return nil 77 | } 78 | 79 | func (h *handler) WorkDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) error { 80 | return errors.New("unimplemented") 81 | } 82 | 83 | func (h *handler) LogTrace(ctx context.Context, params *protocol.LogTraceParams) error { 84 | return errors.New("unimplemented") 85 | } 86 | 87 | func (h *handler) SetTrace(ctx context.Context, params *protocol.SetTraceParams) error { 88 | return errors.New("unimplemented") 89 | } 90 | 91 | func (h *handler) CodeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) { 92 | return nil, errors.New("unimplemented") 93 | } 94 | 95 | func (h *handler) CodeLens(ctx context.Context, params *protocol.CodeLensParams) ([]protocol.CodeLens, error) { 96 | return nil, errors.New("unimplemented") 97 | } 98 | 99 | func (h *handler) CodeLensResolve(ctx context.Context, params *protocol.CodeLens) (*protocol.CodeLens, error) { 100 | return nil, errors.New("unimplemented") 101 | } 102 | 103 | func (h *handler) ColorPresentation(ctx context.Context, params *protocol.ColorPresentationParams) ([]protocol.ColorPresentation, error) { 104 | return nil, errors.New("unimplemented") 105 | } 106 | 107 | func (h *handler) Completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) { 108 | return nil, errors.New("unimplemented") 109 | } 110 | 111 | func (h *handler) CompletionResolve(ctx context.Context, params *protocol.CompletionItem) (*protocol.CompletionItem, error) { 112 | return nil, errors.New("unimplemented") 113 | } 114 | 115 | func (h *handler) Declaration(ctx context.Context, params *protocol.DeclarationParams) ([]protocol.Location, error) { 116 | return nil, errors.New("unimplemented") 117 | } 118 | 119 | func (h *handler) DidChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) error { 120 | return errors.New("unimplemented") 121 | } 122 | 123 | func (h *handler) DidChangeConfiguration(ctx context.Context, params *protocol.DidChangeConfigurationParams) error { 124 | return errors.New("unimplemented") 125 | } 126 | 127 | func (h *handler) DidChangeWatchedFiles(ctx context.Context, params *protocol.DidChangeWatchedFilesParams) error { 128 | return errors.New("unimplemented") 129 | } 130 | 131 | func (h *handler) DidChangeWorkspaceFolders(ctx context.Context, params *protocol.DidChangeWorkspaceFoldersParams) error { 132 | return errors.New("unimplemented") 133 | } 134 | 135 | func (h *handler) DidClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error { 136 | return errors.New("unimplemented") 137 | } 138 | 139 | func (h *handler) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error { 140 | return errors.New("unimplemented") 141 | } 142 | 143 | func (h *handler) DidSave(ctx context.Context, params *protocol.DidSaveTextDocumentParams) error { 144 | return errors.New("unimplemented") 145 | } 146 | 147 | func (h *handler) DocumentColor(ctx context.Context, params *protocol.DocumentColorParams) ([]protocol.ColorInformation, error) { 148 | return nil, errors.New("unimplemented") 149 | } 150 | 151 | func (h *handler) DocumentHighlight(ctx context.Context, params *protocol.DocumentHighlightParams) ([]protocol.DocumentHighlight, error) { 152 | return nil, errors.New("unimplemented") 153 | } 154 | 155 | func (h *handler) DocumentLink(ctx context.Context, params *protocol.DocumentLinkParams) ([]protocol.DocumentLink, error) { 156 | return nil, errors.New("unimplemented") 157 | } 158 | 159 | func (h *handler) DocumentLinkResolve(ctx context.Context, params *protocol.DocumentLink) (*protocol.DocumentLink, error) { 160 | return nil, errors.New("unimplemented") 161 | } 162 | 163 | func (h *handler) DocumentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) ([]interface{}, error) { 164 | return nil, errors.New("unimplemented") 165 | } 166 | 167 | func (h *handler) ExecuteCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) { 168 | return nil, errors.New("unimplemented") 169 | } 170 | 171 | func (h *handler) FoldingRanges(ctx context.Context, params *protocol.FoldingRangeParams) ([]protocol.FoldingRange, error) { 172 | return nil, errors.New("unimplemented") 173 | } 174 | 175 | func (h *handler) Formatting(ctx context.Context, params *protocol.DocumentFormattingParams) ([]protocol.TextEdit, error) { 176 | return nil, errors.New("unimplemented") 177 | } 178 | 179 | func (h *handler) Hover(ctx context.Context, params *protocol.HoverParams) (*protocol.Hover, error) { 180 | return nil, errors.New("unimplemented") 181 | } 182 | 183 | func (h *handler) Implementation(ctx context.Context, params *protocol.ImplementationParams) ([]protocol.Location, error) { 184 | return nil, errors.New("unimplemented") 185 | } 186 | 187 | func (h *handler) OnTypeFormatting(ctx context.Context, params *protocol.DocumentOnTypeFormattingParams) ([]protocol.TextEdit, error) { 188 | return nil, errors.New("unimplemented") 189 | } 190 | 191 | func (h *handler) PrepareRename(ctx context.Context, params *protocol.PrepareRenameParams) (*protocol.Range, error) { 192 | return nil, errors.New("unimplemented") 193 | } 194 | 195 | func (h *handler) RangeFormatting(ctx context.Context, params *protocol.DocumentRangeFormattingParams) ([]protocol.TextEdit, error) { 196 | return nil, errors.New("unimplemented") 197 | } 198 | 199 | func (h *handler) References(ctx context.Context, params *protocol.ReferenceParams) ([]protocol.Location, error) { 200 | return nil, errors.New("unimplemented") 201 | } 202 | 203 | func (h *handler) Rename(ctx context.Context, params *protocol.RenameParams) (*protocol.WorkspaceEdit, error) { 204 | return nil, errors.New("unimplemented") 205 | } 206 | 207 | func (h *handler) SignatureHelp(ctx context.Context, params *protocol.SignatureHelpParams) (*protocol.SignatureHelp, error) { 208 | return nil, errors.New("unimplemented") 209 | } 210 | 211 | func (h *handler) Symbols(ctx context.Context, params *protocol.WorkspaceSymbolParams) ([]protocol.SymbolInformation, error) { 212 | return nil, errors.New("unimplemented") 213 | } 214 | 215 | func (h *handler) TypeDefinition(ctx context.Context, params *protocol.TypeDefinitionParams) ([]protocol.Location, error) { 216 | return nil, errors.New("unimplemented") 217 | } 218 | 219 | func (h *handler) WillSave(ctx context.Context, params *protocol.WillSaveTextDocumentParams) error { 220 | return errors.New("unimplemented") 221 | } 222 | 223 | func (h *handler) WillSaveWaitUntil(ctx context.Context, params *protocol.WillSaveTextDocumentParams) ([]protocol.TextEdit, error) { 224 | return nil, errors.New("unimplemented") 225 | } 226 | 227 | func (h *handler) ShowDocument(ctx context.Context, params *protocol.ShowDocumentParams) (*protocol.ShowDocumentResult, error) { 228 | return nil, errors.New("unimplemented") 229 | } 230 | 231 | func (h *handler) WillCreateFiles(ctx context.Context, params *protocol.CreateFilesParams) (*protocol.WorkspaceEdit, error) { 232 | return nil, errors.New("unimplemented") 233 | } 234 | 235 | func (h *handler) DidCreateFiles(ctx context.Context, params *protocol.CreateFilesParams) error { 236 | return errors.New("unimplemented") 237 | } 238 | 239 | func (h *handler) WillRenameFiles(ctx context.Context, params *protocol.RenameFilesParams) (*protocol.WorkspaceEdit, error) { 240 | return nil, errors.New("unimplemented") 241 | } 242 | 243 | func (h *handler) DidRenameFiles(ctx context.Context, params *protocol.RenameFilesParams) error { 244 | return errors.New("unimplemented") 245 | } 246 | 247 | func (h *handler) WillDeleteFiles(ctx context.Context, params *protocol.DeleteFilesParams) (*protocol.WorkspaceEdit, error) { 248 | return nil, errors.New("unimplemented") 249 | } 250 | 251 | func (h *handler) DidDeleteFiles(ctx context.Context, params *protocol.DeleteFilesParams) error { 252 | return errors.New("unimplemented") 253 | } 254 | 255 | func (h *handler) CodeLensRefresh(ctx context.Context) error { 256 | return errors.New("unimplemented") 257 | } 258 | 259 | func (h *handler) PrepareCallHierarchy(ctx context.Context, params *protocol.CallHierarchyPrepareParams) ([]protocol.CallHierarchyItem, error) { 260 | return nil, errors.New("unimplemented") 261 | } 262 | 263 | func (h *handler) IncomingCalls(ctx context.Context, params *protocol.CallHierarchyIncomingCallsParams) ([]protocol.CallHierarchyIncomingCall, error) { 264 | return nil, errors.New("unimplemented") 265 | } 266 | 267 | func (h *handler) OutgoingCalls(ctx context.Context, params *protocol.CallHierarchyOutgoingCallsParams) ([]protocol.CallHierarchyOutgoingCall, error) { 268 | return nil, errors.New("unimplemented") 269 | } 270 | 271 | func (h *handler) SemanticTokensFull(ctx context.Context, params *protocol.SemanticTokensParams) (*protocol.SemanticTokens, error) { 272 | return nil, errors.New("unimplemented") 273 | } 274 | 275 | func (h *handler) SemanticTokensFullDelta(ctx context.Context, params *protocol.SemanticTokensDeltaParams) (interface{}, error) { 276 | return nil, errors.New("unimplemented") 277 | } 278 | 279 | func (h *handler) SemanticTokensRange(ctx context.Context, params *protocol.SemanticTokensRangeParams) (*protocol.SemanticTokens, error) { 280 | return nil, errors.New("unimplemented") 281 | } 282 | 283 | func (h *handler) SemanticTokensRefresh(ctx context.Context) error { 284 | return errors.New("unimplemented") 285 | } 286 | 287 | func (h *handler) LinkedEditingRange(ctx context.Context, params *protocol.LinkedEditingRangeParams) (*protocol.LinkedEditingRanges, error) { 288 | return nil, errors.New("unimplemented") 289 | } 290 | 291 | func (h *handler) Moniker(ctx context.Context, params *protocol.MonikerParams) ([]protocol.Moniker, error) { 292 | return nil, errors.New("unimplemented") 293 | } 294 | 295 | func (h *handler) Request(ctx context.Context, method string, params interface{}) (interface{}, error) { 296 | return nil, errors.New("unimplemented") 297 | } 298 | -------------------------------------------------------------------------------- /internal/bufls/cmd/bufls/bufls_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Buf Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bufls 16 | 17 | import ( 18 | "io" 19 | "os" 20 | "path/filepath" 21 | "strings" 22 | "testing" 23 | 24 | "github.com/bufbuild/buf/private/pkg/app/appcmd" 25 | "github.com/bufbuild/buf/private/pkg/app/appcmd/appcmdtesting" 26 | ) 27 | 28 | func TestDefinition(t *testing.T) { 29 | t.Parallel() 30 | testDefinitionExtend(t) 31 | testDefinitionGroup(t) 32 | testDefinitionMap(t) 33 | testDefinitionNamespace(t) 34 | testDefinitionNoPackage(t) 35 | testDefinitionWithCacheDependency(t) 36 | testDefinitionWithWorkspaceDependency(t) 37 | } 38 | 39 | func TestDefinitionError(t *testing.T) { 40 | t.Parallel() 41 | testDefintionError( 42 | t, 43 | "testdata/local/nopackage/foo.proto:3:1", // The 'message' keyword. 44 | "could not resolve definition for location testdata/local/nopackage/foo.proto:3:1", 45 | ) 46 | testDefintionError( 47 | t, 48 | "testdata/local/nopackage/foo.proto:13:8", // A '.' delimiter. 49 | "could not resolve definition for location testdata/local/nopackage/foo.proto:13:8", 50 | ) 51 | } 52 | 53 | func TestLocationError(t *testing.T) { 54 | t.Parallel() 55 | testDefintionError( 56 | t, 57 | "testdata/local/nopackage/foo.proto", 58 | "location testdata/local/nopackage/foo.proto is not structured as ::", 59 | ) 60 | testDefintionError( 61 | t, 62 | "testdata/local/nopackage/foo:1:1", 63 | "location path testdata/local/nopackage/foo must be a .proto file", 64 | ) 65 | testDefintionError( 66 | t, 67 | "testdata/local/nopackage/foo.proto:-2:1", 68 | "location line -2 must be a positive integer", 69 | ) 70 | testDefintionError( 71 | t, 72 | "testdata/local/nopackage/foo.proto:1:0", 73 | "location column 0 must be a positive integer", 74 | ) 75 | } 76 | 77 | func testDefinitionExtend(t *testing.T) { 78 | testDefintionSuccess( 79 | t, 80 | "testdata/local/extend/extend.proto:11:5", 81 | "testdata/local/extend/extend.proto:7:9", 82 | ) 83 | testDefintionSuccess( 84 | t, 85 | "testdata/local/extend/extend.proto:12:5", 86 | "testdata/local/extend/extend.proto:9:9", 87 | ) 88 | testDefintionSuccess( 89 | t, 90 | "testdata/local/extend/extend.proto:13:5", 91 | "testdata/local/extend/extend.proto:17:11", 92 | ) 93 | testDefintionSuccess( 94 | t, 95 | "testdata/local/extend/extend.proto:14:5", 96 | "testdata/local/extend/extend.proto:9:9", 97 | ) 98 | testDefintionSuccess( 99 | t, 100 | "testdata/local/extend/extend.proto:14:12", 101 | "testdata/local/extend/extend.proto:17:11", 102 | ) 103 | testDefintionSuccess( 104 | t, 105 | "testdata/local/extend/extend.proto:19:17", 106 | "testdata/local/extend/extend.proto:7:9", 107 | ) 108 | testDefintionSuccess( 109 | t, 110 | "testdata/local/extend/extend.proto:20:17", 111 | "testdata/local/extend/extend.proto:9:9", 112 | ) 113 | testDefintionSuccess( 114 | t, 115 | "testdata/local/extend/extend.proto:21:24", 116 | "testdata/local/extend/extend.proto:17:11", 117 | ) 118 | } 119 | 120 | func testDefinitionGroup(t *testing.T) { 121 | testDefintionSuccess( 122 | t, 123 | "testdata/local/group/group.proto:18:14", 124 | "testdata/local/group/group.proto:5:9", 125 | ) 126 | testDefintionSuccess( 127 | t, 128 | "testdata/local/group/group.proto:18:18", 129 | "testdata/local/group/group.proto:6:18", 130 | ) 131 | testDefintionSuccess( 132 | t, 133 | "testdata/local/group/group.proto:22:21", 134 | "testdata/local/group/group.proto:5:9", 135 | ) 136 | testDefintionSuccess( 137 | t, 138 | "testdata/local/group/group.proto:22:21", 139 | "testdata/local/group/group.proto:5:9", 140 | ) 141 | testDefintionSuccess( 142 | t, 143 | "testdata/local/group/group.proto:22:25", 144 | "testdata/local/group/group.proto:6:18", 145 | ) 146 | testDefintionSuccess( 147 | t, 148 | "testdata/local/group/group.proto:25:12", 149 | "testdata/local/group/group.proto:10:11", 150 | ) 151 | testDefintionSuccess( 152 | t, 153 | "testdata/local/group/group.proto:26:12", 154 | "testdata/local/group/group.proto:6:18", 155 | ) 156 | testDefintionSuccess( 157 | t, 158 | "testdata/local/group/group.proto:30:12", 159 | "testdata/local/group/group.proto:5:9", 160 | ) 161 | testDefintionSuccess( 162 | t, 163 | "testdata/local/group/group.proto:30:16", 164 | "testdata/local/group/group.proto:6:18", 165 | ) 166 | testDefintionSuccess( 167 | t, 168 | "testdata/local/group/group.proto:31:23", 169 | "testdata/local/group/group.proto:12:20", 170 | ) 171 | testDefintionSuccess( 172 | t, 173 | "testdata/local/group/group.proto:31:23", 174 | "testdata/local/group/group.proto:12:20", 175 | ) 176 | testDefintionSuccess( 177 | t, 178 | "testdata/local/group/group.proto:35:23", 179 | "testdata/local/group/group.proto:6:18", 180 | ) 181 | testDefintionSuccess( 182 | t, 183 | "testdata/local/group/group.proto:35:23", 184 | "testdata/local/group/group.proto:6:18", 185 | ) 186 | } 187 | 188 | func testDefinitionMap(t *testing.T) { 189 | testDefintionSuccess( 190 | t, 191 | "testdata/local/map/map.proto:6:15", 192 | "testdata/local/map/map.proto:18:9", 193 | ) 194 | testDefintionSuccess( 195 | t, 196 | "testdata/local/map/map.proto:7:21", 197 | "testdata/local/map/map.proto:19:11", 198 | ) 199 | testDefintionSuccess( 200 | t, 201 | "testdata/local/map/map.proto:12:19", 202 | "testdata/local/map/map.proto:5:9", 203 | ) 204 | testDefintionSuccess( 205 | t, 206 | "testdata/local/map/map.proto:14:24", 207 | "testdata/local/map/map.proto:5:9", 208 | ) 209 | testDefintionSuccess( 210 | t, 211 | "testdata/local/map/map.proto:14:31", 212 | "testdata/local/map/map.proto:9:11", 213 | ) 214 | testDefintionSuccess( 215 | t, 216 | "testdata/local/map/map.proto:14:44", 217 | "testdata/local/map/map.proto:10:13", 218 | ) 219 | } 220 | 221 | func testDefinitionNamespace(t *testing.T) { 222 | testDefintionSuccess( 223 | t, 224 | "testdata/local/namespace/foo/foo/foo_foo.proto:8:3", 225 | "testdata/local/namespace/foo/foo.proto:5:9", 226 | ) 227 | testDefintionSuccess( 228 | t, 229 | "testdata/local/namespace/foo/foo/foo_foo.proto:8:13", 230 | "testdata/local/namespace/foo/foo.proto:6:11", 231 | ) 232 | } 233 | 234 | func testDefinitionNoPackage(t *testing.T) { 235 | testDefintionSuccess( 236 | t, 237 | "testdata/local/nopackage/foo.proto:3:9", 238 | "testdata/local/nopackage/foo.proto:3:9", 239 | ) 240 | testDefintionSuccess( 241 | t, 242 | "testdata/local/nopackage/foo.proto:9:5", 243 | "testdata/local/nopackage/foo.proto:6:11", 244 | ) 245 | testDefintionSuccess( 246 | t, 247 | "testdata/local/nopackage/foo.proto:13:5", 248 | "testdata/local/nopackage/foo.proto:5:9", 249 | ) 250 | testDefintionSuccess( 251 | t, 252 | "testdata/local/nopackage/foo.proto:13:9", 253 | "testdata/local/nopackage/foo.proto:6:11", 254 | ) 255 | testDefintionSuccess( 256 | t, 257 | "testdata/local/nopackage/foo.proto:16:4", 258 | "testdata/local/nopackage/foo.proto:5:9", 259 | ) 260 | testDefintionSuccess( 261 | t, 262 | "testdata/local/nopackage/foo.proto:16:8", 263 | "testdata/local/nopackage/foo.proto:6:11", 264 | ) 265 | testDefintionSuccess( 266 | t, 267 | "testdata/local/nopackage/foo.proto:20:4", 268 | "testdata/local/nopackage/foo.proto:3:9", 269 | ) 270 | testDefintionSuccess( 271 | t, 272 | "testdata/local/nopackage/foo.proto:21:8", 273 | "testdata/local/nopackage/foo.proto:8:11", 274 | ) 275 | } 276 | 277 | func testDefinitionWithCacheDependency(t *testing.T) { 278 | testDefintionSuccessWithCache( 279 | t, 280 | "testdata/cache", 281 | "testdata/local/withcachedependency/baz.proto:8:10", 282 | "testdata/cache/v1/module/data/buf.build/test-owner/test-repository/6e230f46113f498392c82d12b1a07b70/bar.proto:7:9", 283 | ) 284 | } 285 | 286 | func testDefinitionWithWorkspaceDependency(t *testing.T) { 287 | testDefintionSuccessWithCache( 288 | t, 289 | "testdata/cache", 290 | "testdata/local/withworkspacedependency/workspace.proto:8:28", 291 | "testdata/local/withcachedependency/baz.proto:7:9", 292 | ) 293 | testDefintionSuccessWithCache( 294 | t, 295 | "testdata/cache", 296 | "testdata/local/withworkspacedependency/workspace.proto:9:28", 297 | "testdata/local/withcachedependency/baz.proto:13:9", 298 | ) 299 | testDefintionSuccessWithCache( 300 | t, 301 | "testdata/cache", 302 | "testdata/local/withworkspacedependency/workspace.proto:13:29", 303 | "testdata/local/withcachedependency/baz.proto:7:9", 304 | ) 305 | testDefintionSuccessWithCache( 306 | t, 307 | "testdata/cache", 308 | "testdata/local/withworkspacedependency/workspace.proto:14:29", 309 | "testdata/local/withcachedependency/baz.proto:13:9", 310 | ) 311 | } 312 | 313 | func testDefintionSuccess( 314 | t *testing.T, 315 | inputLocation string, 316 | outputLocation string, 317 | ) { 318 | testRunStdout( 319 | t, 320 | nil, 321 | 0, 322 | filepath.FromSlash(outputLocation), 323 | "definition", 324 | filepath.FromSlash(inputLocation), 325 | ) 326 | } 327 | 328 | func testDefintionSuccessWithCache( 329 | t *testing.T, 330 | cacheDir string, 331 | inputLocation string, 332 | outputLocation string, 333 | ) { 334 | testRunStdoutWithCache( 335 | t, 336 | nil, 337 | 0, 338 | filepath.FromSlash(outputLocation), 339 | filepath.FromSlash(cacheDir), 340 | "definition", 341 | filepath.FromSlash(inputLocation), 342 | ) 343 | } 344 | 345 | func testDefintionError( 346 | t *testing.T, 347 | inputLocation string, 348 | outputError string, 349 | ) { 350 | testRunStdoutStderr( 351 | t, 352 | nil, 353 | 1, 354 | "", 355 | filepath.FromSlash(outputError), 356 | "definition", 357 | filepath.FromSlash(inputLocation), 358 | ) 359 | } 360 | 361 | func testRunStdout(t *testing.T, stdin io.Reader, expectedExitCode int, expectedStdout string, args ...string) { 362 | appcmdtesting.RunCommandExitCodeStdout( 363 | t, 364 | func(use string) *appcmd.Command { return NewRootCommand(use) }, 365 | expectedExitCode, 366 | expectedStdout, 367 | newEnvFunc(t, ""), 368 | stdin, 369 | args..., 370 | ) 371 | } 372 | 373 | func testRunStdoutWithCache(t *testing.T, stdin io.Reader, expectedExitCode int, expectedStdout string, cacheDir string, args ...string) { 374 | appcmdtesting.RunCommandExitCodeStdout( 375 | t, 376 | func(use string) *appcmd.Command { return NewRootCommand(use) }, 377 | expectedExitCode, 378 | expectedStdout, 379 | newEnvFunc(t, cacheDir), 380 | stdin, 381 | args..., 382 | ) 383 | } 384 | 385 | func testRunStdoutStderr(t *testing.T, stdin io.Reader, expectedExitCode int, expectedStdout string, expectedStderr string, args ...string) { 386 | appcmdtesting.RunCommandExitCodeStdoutStderr( 387 | t, 388 | func(use string) *appcmd.Command { return NewRootCommand(use) }, 389 | expectedExitCode, 390 | expectedStdout, 391 | expectedStderr, 392 | newEnvFunc(t, ""), 393 | stdin, 394 | // we do not want warnings to be part of our stderr test calculation 395 | append( 396 | args, 397 | "--no-warn", 398 | )..., 399 | ) 400 | } 401 | 402 | func newEnvFunc(tb testing.TB, cacheDir string) func(string) map[string]string { 403 | if cacheDir == "" { 404 | cacheDir = tb.TempDir() 405 | } 406 | return func(use string) map[string]string { 407 | return map[string]string{ 408 | useEnvVar(use, "CACHE_DIR"): cacheDir, 409 | useEnvVar(use, "HOME"): tb.TempDir(), 410 | "PATH": os.Getenv("PATH"), 411 | } 412 | } 413 | } 414 | 415 | func useEnvVar(use string, suffix string) string { 416 | return strings.ToUpper(use) + "_" + suffix 417 | } 418 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= 4 | github.com/bufbuild/buf v1.11.0 h1:jq/ckkxg87GaEgyHMqGGvZkVh8FeZuYLBF9DyOQK1RU= 5 | github.com/bufbuild/buf v1.11.0/go.mod h1:IoaGkUhois4gv4EqTGT7H+4bbhZUvlYutECLgfoE2cA= 6 | github.com/bufbuild/connect-go v1.4.0 h1:N94D0tGxuM2cSI7hM/aL8mtxL6+8rtHuFcIj9oGRp5s= 7 | github.com/bufbuild/connect-go v1.4.0/go.mod h1:9iNvh/NOsfhNBUH5CtvXeVUskQO1xsrEviH7ZArwZ3I= 8 | github.com/bufbuild/protocompile v0.1.0 h1:HjgJBI85hY/qmW5tw/66sNDZ7z0UDdVSi/5r40WHw4s= 9 | github.com/bufbuild/protocompile v0.1.0/go.mod h1:ix/MMMdsT3fzxfw91dvbfzKW3fRRnuPCP47kpAm5m/4= 10 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 11 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 12 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 13 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 14 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 15 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 16 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= 17 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 18 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 19 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 20 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 21 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 22 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 23 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 24 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 25 | github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= 26 | github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= 27 | github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= 28 | github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= 29 | github.com/gofrs/uuid v4.3.1+incompatible h1:0/KbAdpx3UXAx1kEOWHJeOkpbgRFGHVgv+CFIY7dBJI= 30 | github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 31 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 32 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 33 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 34 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 35 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 36 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 37 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 38 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 39 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 40 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 41 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 42 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 43 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 44 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 45 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 46 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 47 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 48 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 49 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 50 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 51 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 52 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 53 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 54 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 55 | github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= 56 | github.com/google/pprof v0.0.0-20221203041831-ce31453925ec h1:fR20TYVVwhK4O7r7y+McjRYyaTH6/vjwJOajE+XhlzM= 57 | github.com/google/pprof v0.0.0-20221203041831-ce31453925ec/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= 58 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 59 | github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= 60 | github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 61 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 62 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 63 | github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84 h1:2uT3aivO7NVpUPGcQX7RbHijHMyWix/yCnIrCWc+5co= 64 | github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84/go.mod h1:Zi/ZFkEqFHTm7qkjyNJjaWH4LQA9LQhGJyF0lTYGpxw= 65 | github.com/jhump/protoreflect v1.14.0 h1:MBbQK392K3u8NTLbKOCIi3XdI+y+c6yt5oMq0X3xviw= 66 | github.com/klauspost/compress v1.15.13 h1:NFn1Wr8cfnenSJSA46lLq4wHCcBzKTSjnBIexDMMOV0= 67 | github.com/klauspost/compress v1.15.13/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= 68 | github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= 69 | github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 70 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 71 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 72 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 73 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 74 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 75 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 76 | github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= 77 | github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= 78 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 79 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 80 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 81 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 82 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 83 | github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= 84 | github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= 85 | github.com/segmentio/encoding v0.3.4 h1:WM4IBnxH8B9TakiM2QD5LyNl9JSndh88QbHqVC+Pauc= 86 | github.com/segmentio/encoding v0.3.4/go.mod h1:n0JeuIqEQrQoPDGsjo8UNd1iA0U8d8+oHAA4E3G3OxM= 87 | github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= 88 | github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= 89 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 90 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 91 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 92 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 93 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 94 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 95 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 96 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 97 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 98 | go.lsp.dev/jsonrpc2 v0.10.0 h1:Pr/YcXJoEOTMc/b6OTmcR1DPJ3mSWl/SWiU1Cct6VmI= 99 | go.lsp.dev/jsonrpc2 v0.10.0/go.mod h1:fmEzIdXPi/rf6d4uFcayi8HpFP1nBF99ERP1htC72Ac= 100 | go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 h1:hCzQgh6UcwbKgNSRurYWSqh8MufqRRPODRBblutn4TE= 101 | go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2/go.mod h1:gtSHRuYfbCT0qnbLnovpie/WEmqyJ7T4n6VXiFMBtcw= 102 | go.lsp.dev/protocol v0.12.0 h1:tNprUI9klQW5FAFVM4Sa+AbPFuVQByWhP1ttNUAjIWg= 103 | go.lsp.dev/protocol v0.12.0/go.mod h1:Qb11/HgZQ72qQbeyPfJbu3hZBH23s1sr4st8czGeDMQ= 104 | go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo= 105 | go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I= 106 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 107 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 108 | go.opentelemetry.io/otel v1.11.2 h1:YBZcQlsVekzFsFbjygXMOXSs6pialIZxcjfO/mBDmR0= 109 | go.opentelemetry.io/otel v1.11.2/go.mod h1:7p4EUV+AqgdlNV9gL97IgUZiVR3yrFXYo53f9BM3tRI= 110 | go.opentelemetry.io/otel/metric v0.34.0 h1:MCPoQxcg/26EuuJwpYN1mZTeCYAUGx8ABxfW07YkjP8= 111 | go.opentelemetry.io/otel/metric v0.34.0/go.mod h1:ZFuI4yQGNCupurTXCwkeD/zHBt+C2bR7bw5JqUm/AP8= 112 | go.opentelemetry.io/otel/trace v1.11.2 h1:Xf7hWSF2Glv0DE3MH7fBHvtpSBsjcBUe5MYAmZM/+y0= 113 | go.opentelemetry.io/otel/trace v1.11.2/go.mod h1:4N+yC7QEz7TTsG9BSRLNAa63eg5E06ObSbKPmxQ/pKA= 114 | go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= 115 | go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 116 | go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= 117 | go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= 118 | go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= 119 | go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= 120 | go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= 121 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 122 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 123 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 124 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 125 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 126 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 127 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 128 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 129 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 130 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 131 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 132 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 133 | golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= 134 | golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= 135 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 136 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 137 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 138 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 139 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 140 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 141 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 142 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 143 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 144 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 145 | golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 146 | golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 147 | golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= 148 | golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 149 | golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= 150 | golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= 151 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 152 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 153 | golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= 154 | golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 155 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 156 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 157 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 158 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 159 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 160 | golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= 161 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 162 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 163 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 164 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 165 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 166 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 167 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 168 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 169 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 170 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 171 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 172 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 173 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 174 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 175 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 176 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 177 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 178 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 179 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 180 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 181 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 182 | google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk= 183 | google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 184 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 185 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= 186 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 187 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 188 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 189 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 190 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 191 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 192 | -------------------------------------------------------------------------------- /internal/bufls/engine.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Buf Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bufls 16 | 17 | import ( 18 | "bytes" 19 | "context" 20 | "errors" 21 | "fmt" 22 | "strings" 23 | 24 | "github.com/bufbuild/buf/private/buf/buffetch" 25 | "github.com/bufbuild/buf/private/buf/bufwire" 26 | "github.com/bufbuild/buf/private/buf/bufwork" 27 | "github.com/bufbuild/buf/private/bufpkg/bufanalysis" 28 | "github.com/bufbuild/buf/private/bufpkg/bufimage" 29 | "github.com/bufbuild/buf/private/bufpkg/bufimage/bufimagebuild" 30 | "github.com/bufbuild/buf/private/bufpkg/bufmodule" 31 | "github.com/bufbuild/buf/private/bufpkg/bufmodule/bufmodulebuild" 32 | "github.com/bufbuild/buf/private/pkg/app/appflag" 33 | "github.com/bufbuild/protocompile/ast" 34 | "github.com/bufbuild/protocompile/parser" 35 | "github.com/bufbuild/protocompile/reporter" 36 | "go.uber.org/multierr" 37 | "go.uber.org/zap" 38 | "google.golang.org/protobuf/reflect/protodesc" 39 | "google.golang.org/protobuf/reflect/protoreflect" 40 | "google.golang.org/protobuf/reflect/protoregistry" 41 | "google.golang.org/protobuf/types/descriptorpb" 42 | ) 43 | 44 | // errBreak is a sentinel error used to break out of an ast.Walk without 45 | // returning an actionable error. 46 | var errBreak = errors.New("break") 47 | 48 | type engine struct { 49 | logger *zap.Logger 50 | container appflag.Container 51 | moduleConfigReader bufwire.ModuleConfigReader 52 | moduleFileSetBuilder bufmodulebuild.ModuleFileSetBuilder 53 | imageBuilder bufimagebuild.Builder 54 | } 55 | 56 | func newEngine( 57 | logger *zap.Logger, 58 | container appflag.Container, 59 | moduleConfigReader bufwire.ModuleConfigReader, 60 | moduleFileSetBuilder bufmodulebuild.ModuleFileSetBuilder, 61 | imageBuilder bufimagebuild.Builder, 62 | ) *engine { 63 | return &engine{ 64 | logger: logger, 65 | container: container, 66 | moduleConfigReader: moduleConfigReader, 67 | moduleFileSetBuilder: moduleFileSetBuilder, 68 | imageBuilder: imageBuilder, 69 | } 70 | } 71 | 72 | func (e *engine) Definition(ctx context.Context, location Location) (_ Location, retErr error) { 73 | externalPath := location.Path() 74 | moduleFileSet, image, err := e.buildForExternalPath(ctx, externalPath) 75 | if err != nil { 76 | return nil, err 77 | } 78 | moduleFile, err := moduleFileForExternalPath(ctx, moduleFileSet, image, externalPath) 79 | if err != nil { 80 | return nil, err 81 | } 82 | defer func() { 83 | retErr = multierr.Append(retErr, moduleFile.Close()) 84 | }() 85 | fileNode, err := parser.Parse(moduleFile.ExternalPath(), moduleFile, reporter.NewHandler(nil)) 86 | if err != nil { 87 | return nil, err 88 | } 89 | // TODO: We currently iterate O(n) in the file to determine what identifier the 90 | // location points to. We can do a lot better - O(logn) at least and O(1) at best. 91 | // We might be able to add functionality to protocompile so that it can resolve a 92 | // node from a span more efficiently. 93 | ancestorTracker := new(ast.AncestorTracker) 94 | var node ast.TerminalNode 95 | var parentNode ast.Node 96 | var messagePath []*ast.MessageNode 97 | visitor := &ast.SimpleVisitor{ 98 | DoVisitTerminalNode: func(terminalNode ast.TerminalNode) error { 99 | info := fileNode.NodeInfo(terminalNode) 100 | if locationIsWithinNode(location, info) { 101 | // At this point, the terminal node can only represent 102 | // a couple different things - any of the keywords, primitive 103 | // types, or an identifier (e.g. pet.v1.Pet). 104 | node = terminalNode 105 | parentNode = ancestorTracker.Parent() 106 | for _, parent := range ancestorTracker.Path() { 107 | // Capture all of the messages in the parent path 108 | // so that we can recover the message's full name 109 | // (i.e. for nested messages). 110 | messageNode, ok := parent.(*ast.MessageNode) 111 | if !ok { 112 | continue 113 | } 114 | messagePath = append(messagePath, messageNode) 115 | } 116 | return errBreak 117 | } 118 | return nil 119 | }, 120 | } 121 | if err := ast.Walk(fileNode, visitor, ancestorTracker.AsWalkOptions()...); err != nil && !errors.Is(err, errBreak) { 122 | return nil, err 123 | } 124 | if node == nil { 125 | return nil, newCannotResolveLocationError(location) 126 | } 127 | identifier, err := resolveIdentifierFromNode( 128 | location, 129 | image, 130 | fileNode, 131 | node, 132 | parentNode, 133 | messagePath, 134 | ) 135 | if err != nil { 136 | return nil, err 137 | } 138 | return e.findLocationForIdentifier( 139 | ctx, 140 | location, 141 | moduleFileSet, 142 | image, 143 | fileNode, 144 | identifier, 145 | ) 146 | } 147 | 148 | // resolvedIdentifierFromNode returns the full name of the descriptor associated with 149 | // the given node and/or parent node (e.g. pet.v1.Pet). 150 | // 151 | // TODO: The protocompile library already needs to perform the reference resolution 152 | // algorithm (i.e. during the linking phase). We can simplify a lot of this logic by 153 | // depending on protocompile's implementation rather than reimplementing our own 154 | // version here. 155 | func resolveIdentifierFromNode( 156 | location Location, 157 | image bufimage.Image, 158 | fileNode *ast.FileNode, 159 | node ast.TerminalNode, 160 | parentNode ast.Node, 161 | messagePath []*ast.MessageNode, 162 | ) (string, error) { 163 | identNode, ok := node.(*ast.IdentNode) 164 | if !ok { 165 | // This node isn't an identifier, so it must be a keyword, a separator 166 | // (e.g. '.' or ','), or a primitive literal (the other valid values of 167 | // ast.TerminalNode). 168 | // 169 | // There's nothing for us to do (i.e. there isn't anywhere we can jump 170 | // to to show where the message keyword is defined). 171 | return "", newCannotResolveLocationError(location) 172 | } 173 | var identifier string 174 | if identNode != nil { 175 | identifier = string(identNode.AsIdentifier()) 176 | } 177 | if compoundIdentNode, ok := parentNode.(*ast.CompoundIdentNode); ok { 178 | // If the parent is a *ast.CompoundIdentNode then it either represents 179 | // a nested descriptor, or a descriptor from another package. 180 | // 181 | // In either case, we use *ast.IdentNode to recognize where the user's 182 | // cursor is, and include all of the other children up to (and including) 183 | // that identifier so that it's appropriately scoped. 184 | // 185 | // For example, the following cursor positions resolve to the following 186 | // descriptors: 187 | // 188 | // * foo.v1.[F]oo.Bar => foo.v1.Foo 189 | // * foo.v1.Foo.[B]ar => foo.v1.Foo.Bar 190 | // 191 | var compoundIdentifier string 192 | if compoundIdentNode.LeadingDot != nil { 193 | compoundIdentifier += "." 194 | } 195 | for i, component := range compoundIdentNode.Components { 196 | compoundIdentifier += component.Val 197 | if component == identNode { 198 | // This is the component that the user's cursor is on, 199 | // so we stop here. 200 | break 201 | } 202 | if len(compoundIdentNode.Dots) > i { 203 | // The length of Dots is always one less than the length 204 | // of Components. 205 | compoundIdentifier += "." 206 | } 207 | } 208 | identifier = compoundIdentifier 209 | } 210 | if len(identifier) == 0 { 211 | // Unreachable, but included for additional safety. 212 | return "", newCannotResolveLocationError(location) 213 | } 214 | if strings.HasPrefix(identifier, ".") { 215 | // If the identifier has a leading dot, then the descriptor must already 216 | // be fully-qualified. We work with identifiers in terms of their 217 | // full name (i.e. the protoreflect.FullName representation), so we 218 | // can simply trim the leading dot, and return early. 219 | return strings.TrimPrefix(identifier, "."), nil 220 | } 221 | // At this point, the identifier might represent a nested descriptor in the same file. 222 | // Unfortunately, it's not enough to consult the *ast.AncestorTracker used in the 223 | // ast.Walk - the identifier could represent a nested message in another message defined 224 | // at the top-level. 225 | // 226 | // For example, 227 | // 228 | // package foo.v1; 229 | // 230 | // message Foo { 231 | // foo.v1.Bar.Baz baz = 1; 232 | // } 233 | // 234 | // message Bar { 235 | // message Baz {} 236 | // } 237 | // 238 | identifierComponents := strings.Split(identifier, ".") 239 | var resolvedIdentifier bool 240 | if len(messagePath) > 0 { 241 | for i := len(messagePath) - 1; i >= 0; i-- { 242 | // Start from the innermost message so that we preserve 243 | // Protobuf's scoping precedence rules. 244 | // 245 | // All of the messages and enums defined in the current 246 | // message could define the descriptor we're looking for. 247 | if resolvedIdentifier { 248 | break 249 | } 250 | var messageElements []ast.MessageElement 251 | for _, decl := range messagePath[i].Decls { 252 | switch node := decl.(type) { 253 | case *ast.EnumNode, *ast.GroupNode, *ast.MessageNode: 254 | messageElements = append(messageElements, node) 255 | } 256 | } 257 | for _, messageElement := range messageElements { 258 | if _, ok := findNestedDescriptor(fileNode, messageElement, identifierComponents...); ok { 259 | // A nested message can be referenced in the same way as 260 | // a top-level message, so we need to consult the other 261 | // messages defined in the same scope (but only at the 262 | // innermost level). 263 | // 264 | // For example, 265 | // 266 | // message Foo { 267 | // message Bar { 268 | // string baz = 1; 269 | // } 270 | // // This message is referencing the nested Foo.Bar 271 | // // message, not the top-level .Bar message. 272 | // Bar bar = 1; 273 | // } 274 | // 275 | // message Bar {} 276 | // 277 | for j := i; j >= 0; j-- { 278 | // Start from the innermost message so that we format 279 | // the name correctly. We only go up until the element 280 | // in the messagePath that finished the resolution. 281 | identifier = messagePath[j].Name.Val + "." + identifier 282 | } 283 | // If the identifier represents a nested message in the 284 | // same package, then we need to preprend the package prefix. 285 | // 286 | // Otherwise, the identifier must already have the package 287 | // prefix to the package where it's defined. 288 | resolvedIdentifier = true 289 | break 290 | } 291 | } 292 | } 293 | if !resolvedIdentifier { 294 | // We weren't able to resolve the identifier from a nested message 295 | // in the messagePath, so we next need to check if the nested message 296 | // is referenced by its full name (excluding the package prefix). To 297 | // do so, we start from all the top-level messages and verify if a 298 | // type with that name exists. 299 | // 300 | // For example, 301 | // 302 | // package foo.v1; 303 | // 304 | // message Foo { 305 | // Bar.Baz baz = 1; 306 | // } 307 | // 308 | // message Bar { 309 | // message Baz {} 310 | // } 311 | // 312 | for _, decl := range fileNode.Decls { 313 | switch node := decl.(type) { 314 | case *ast.EnumNode, *ast.MessageNode: 315 | messageElement, ok := node.(ast.MessageElement) 316 | if !ok { 317 | // Unreachable, but included for additional safety. 318 | return "", fmt.Errorf("expected a message element, got %T", node) 319 | } 320 | if _, ok := findNestedDescriptor(fileNode, messageElement, identifierComponents...); ok { 321 | resolvedIdentifier = true 322 | break 323 | } 324 | } 325 | } 326 | } 327 | } 328 | packageName := packageNameForFile(fileNode) 329 | if len(packageName) == 0 { 330 | // The identifier must already be fully-qualified, or its 331 | // part of the default, empty package. 332 | return identifier, nil 333 | } 334 | if resolvedIdentifier { 335 | packageNamePrefix := packageName + "." 336 | if !strings.HasPrefix(identifier, packageNamePrefix) { 337 | // The identifier might already contain the package 338 | // prefix even if the descriptor is defined in the 339 | // same package. 340 | // 341 | // For example, 342 | // 343 | // package foo.v1; 344 | // 345 | // message Foo { 346 | // foo.v1.Bar bar = 1; 347 | // } 348 | // 349 | // message Bar {} 350 | // 351 | identifier = packageNamePrefix + identifier 352 | } 353 | return identifier, nil 354 | } 355 | // At this point, we know that the identifier isn't defined in the current 356 | // file, so we continue with the reference resolution algorithm and search 357 | // for a valid reference in the package hierarchy. 358 | scopeSplit := strings.Split(packageName, ".") 359 | for i := len(scopeSplit); i >= 0; i-- { 360 | candidateScope := strings.Join(scopeSplit[:i], ".") 361 | var fileDescriptorProtos []*descriptorpb.FileDescriptorProto 362 | for _, imageFile := range image.Files() { 363 | fileDescriptorProto := imageFile.Proto() 364 | if fileDescriptorProto.GetPackage() != candidateScope { 365 | continue 366 | } 367 | fileDescriptorProtos = append(fileDescriptorProtos, fileDescriptorProto) 368 | } 369 | if len(fileDescriptorProtos) > 0 { 370 | for _, fileDescriptorProto := range fileDescriptorProtos { 371 | foundIdentifier, matchedFirstComponent := isNestedDescriptorFromFile(fileDescriptorProto, identifierComponents...) 372 | if foundIdentifier { 373 | descriptorName := strings.Join(identifierComponents, ".") 374 | return candidateScope + "." + descriptorName, nil 375 | } 376 | if matchedFirstComponent { 377 | // If the first component was matched and we failed to find the entire identifier, 378 | // this is a failure. 379 | return "", newCannotResolveLocationError(location) 380 | } 381 | } 382 | } 383 | } 384 | return identifier, nil 385 | } 386 | 387 | // findLocationForIdentifier returns the location of the node identified by the identValueNode based 388 | // on the file node. Note that the retruned location will not always be found in the given fileNode - it 389 | // will often be defined in another file in the module, or one of the module's dependencies. 390 | func (e *engine) findLocationForIdentifier( 391 | ctx context.Context, 392 | inputLocation Location, 393 | moduleFileSet bufmodule.ModuleFileSet, 394 | image bufimage.Image, 395 | fileNode *ast.FileNode, 396 | identifier string, 397 | ) (_ Location, retErr error) { 398 | if len(identifier) == 0 { 399 | return nil, errors.New("identifier must be non-empty") 400 | } 401 | files, err := protodesc.NewFiles(bufimage.ImageToFileDescriptorSet(image)) 402 | if err != nil { 403 | return nil, err 404 | } 405 | identifierFullName := protoreflect.FullName(identifier) 406 | descriptor, err := files.FindDescriptorByName(identifierFullName) 407 | if err != nil && !errors.Is(err, protoregistry.NotFound) { 408 | return nil, err 409 | } 410 | if errors.Is(err, protoregistry.NotFound) { 411 | // TODO: The identifier is either a [custom] option, or one of the well-known types. 412 | // 413 | // If the identifier is a WKT, we might want to initialize the local module cache 414 | // with a synthesized version of the well-known types that we can always jump to 415 | // (e.g. ~/.cache/buf/v1/wkt). 416 | return nil, newCannotResolveLocationError(inputLocation) 417 | } 418 | // Now that we know where the identifier is defined, parse the 419 | // file into an AST to locate where it's defined. 420 | // 421 | // By default, we assume that the file we've already parsed is 422 | // the same file that defines the identifier so that we don't 423 | // unnecessarily parse the same file more than once. 424 | parentFileNode := fileNode 425 | parentFilePath := descriptor.ParentFile().Path() 426 | parentModuleFile, err := moduleFileSet.GetModuleFile(ctx, parentFilePath) 427 | if err != nil { 428 | return nil, err 429 | } 430 | defer func() { 431 | retErr = multierr.Append(retErr, parentModuleFile.Close()) 432 | }() 433 | if fileNode.Name() != parentModuleFile.ExternalPath() { 434 | // We only need to parse the file if it's different than the input. 435 | parentFileNode, err = parser.Parse(parentModuleFile.ExternalPath(), parentModuleFile, reporter.NewHandler(nil)) 436 | if err != nil { 437 | return nil, err 438 | } 439 | } 440 | // We manually iterate over the file's ast.MessageElement values 441 | // so that we can more clearly track the path from the descriptor's 442 | // name to the associated ast.NodeInfo. 443 | // 444 | // Alternatively, we could use ast.Walk in combination with the 445 | // *ast.AncestorTracker, but we would need to compare the tracker's 446 | // path with the target path for every visited message and/or enum node. 447 | packageNamePrefix := packageNameForFile(parentFileNode) + "." 448 | descriptorName := strings.TrimPrefix(identifier, packageNamePrefix) 449 | descriptorNameComponents := strings.Split(descriptorName, ".") 450 | var nodeInfo *ast.NodeInfo 451 | for _, decl := range parentFileNode.Decls { 452 | switch node := decl.(type) { 453 | case *ast.EnumNode, *ast.MessageNode: 454 | messageElement, ok := node.(ast.MessageElement) 455 | if !ok { 456 | // Unreachable, but included for additional safety. 457 | return nil, fmt.Errorf("expected a message element, got %T", node) 458 | } 459 | if targetNodeInfo, ok := findNestedDescriptor(parentFileNode, messageElement, descriptorNameComponents...); ok { 460 | nodeInfo = &targetNodeInfo 461 | break 462 | } 463 | } 464 | } 465 | if nodeInfo == nil { 466 | // Should be unreachable, but could be an internal error / bug if we get here. 467 | return nil, fmt.Errorf("could not find %s in %s", identifier, parentModuleFile.ExternalPath()) 468 | } 469 | start := nodeInfo.Start() 470 | return newLocation( 471 | parentModuleFile.ExternalPath(), 472 | start.Line, 473 | start.Col, 474 | ) 475 | } 476 | 477 | // buildForExternalPath returns the ModuleFileSet that defines the ModuleFile identified by 478 | // the given path, as well as the Image that contains the transitive closure of files that 479 | // can be reached from the path. 480 | func (e *engine) buildForExternalPath( 481 | ctx context.Context, 482 | externalPath string, 483 | ) (_ bufmodule.ModuleFileSet, _ bufimage.Image, retErr error) { 484 | refParser := buffetch.NewRefParser( 485 | e.logger, 486 | buffetch.RefParserWithProtoFileRefAllowed(), 487 | ) 488 | sourceRef, err := refParser.GetSourceRef(ctx, externalPath) 489 | if err != nil { 490 | return nil, nil, err 491 | } 492 | moduleConfigs, err := e.moduleConfigReader.GetModuleConfigs( 493 | ctx, 494 | e.container, 495 | sourceRef, 496 | "", 497 | nil, 498 | nil, 499 | false, 500 | ) 501 | if err != nil { 502 | return nil, nil, err 503 | } 504 | if len(moduleConfigs) == 0 { 505 | // Unreachable - we should always have at least one module. 506 | return nil, nil, fmt.Errorf("could not build module for %s", externalPath) 507 | } 508 | // We only want to build a single ModuleFileSet and Image (for performance). 509 | // The Module that defines the externalPath as a target file is able to reach 510 | // all of the references we need, so we only need to build that one. 511 | for _, moduleConfig := range moduleConfigs { 512 | fileInfos, err := moduleConfig.Module().TargetFileInfos(ctx) 513 | if err != nil { 514 | return nil, nil, err 515 | } 516 | var found bool 517 | for _, fileInfo := range fileInfos { 518 | if fileInfo.ExternalPath() == externalPath { 519 | found = true 520 | break 521 | } 522 | } 523 | if !found { 524 | continue 525 | } 526 | moduleFileSet, err := e.moduleFileSetBuilder.Build( 527 | ctx, 528 | moduleConfig.Module(), 529 | bufmodulebuild.WithWorkspace(moduleConfig.Workspace()), 530 | ) 531 | if err != nil { 532 | return nil, nil, err 533 | } 534 | image, fileAnnotations, err := e.imageBuilder.Build( 535 | ctx, 536 | moduleFileSet, 537 | ) 538 | if err != nil { 539 | return nil, nil, err 540 | } 541 | if len(fileAnnotations) > 0 { 542 | return nil, nil, fileAnnotationsToError(fileAnnotations) 543 | } 544 | return moduleFileSet, image, nil 545 | } 546 | // Unreacable - if we got this far, we should always find the module. 547 | return nil, nil, fmt.Errorf("could not find %s in any module", externalPath) 548 | } 549 | 550 | // findNestedDescriptor returns the ast.NodeInfo associated with the given 551 | // identifierComponents, if any. This function recursively searches through 552 | // the given messageElement, popping the first identifier component off the 553 | // list to approach the base case. 554 | // 555 | // We use the ast.MessageElement type here so that it permits *ast.MessageNode, 556 | // *ast.EnumNode, and *ast.GroupNode values. We validate that those types are the 557 | // only ones permitted. 558 | func findNestedDescriptor(fileNode *ast.FileNode, messageElement ast.MessageElement, identifierComponents ...string) (ast.NodeInfo, bool) { 559 | if len(identifierComponents) == 0 { 560 | return ast.NodeInfo{}, false 561 | } 562 | targetName := identifierComponents[0] 563 | if len(identifierComponents) == 1 { 564 | var name *ast.IdentNode 565 | switch node := messageElement.(type) { 566 | case *ast.EnumNode: 567 | name = node.Name 568 | case *ast.MessageNode: 569 | name = node.Name 570 | case *ast.GroupNode: 571 | name = node.Name 572 | } 573 | if name.Val != targetName { 574 | return ast.NodeInfo{}, false 575 | } 576 | return fileNode.NodeInfo(name), true 577 | } 578 | // We need to recurse into the nested message definitions, 579 | // which could either be a standard nested message, or a 580 | // group (for "proto2"). 581 | var name string 582 | var messageBody ast.MessageBody 583 | switch node := messageElement.(type) { 584 | case *ast.GroupNode: 585 | name = node.Name.Val 586 | messageBody = node.MessageBody 587 | case *ast.MessageNode: 588 | name = node.Name.Val 589 | messageBody = node.MessageBody 590 | } 591 | if name != targetName { 592 | return ast.NodeInfo{}, false 593 | } 594 | for _, messageDecl := range messageBody.Decls { 595 | switch nestedNode := messageDecl.(type) { 596 | case *ast.EnumNode, *ast.GroupNode, *ast.MessageNode: 597 | if nodeInfo, ok := findNestedDescriptor(fileNode, nestedNode, identifierComponents[1:]...); ok { 598 | return nodeInfo, true 599 | } 600 | } 601 | } 602 | return ast.NodeInfo{}, false 603 | } 604 | 605 | // isNestedDescriptorFromFile is behaviorally similar to findNestedDescriptor, but it's tailored 606 | // to the upstream *descriptorpb.FileDescriptorProto type contained within the bufimage.Image. 607 | // 608 | // We only need to search for the identifier at the top-level in this case. 609 | func isNestedDescriptorFromFile(fileDescriptorProto *descriptorpb.FileDescriptorProto, identifierComponents ...string) (bool, bool) { 610 | if len(identifierComponents) == 0 { 611 | return false, false 612 | } 613 | name := identifierComponents[0] 614 | for _, descriptorProto := range fileDescriptorProto.GetMessageType() { 615 | if descriptorProto.GetName() == name { 616 | if len(identifierComponents) == 1 { 617 | return true, true 618 | } 619 | return isNestedDescriptorFromMessage(descriptorProto, identifierComponents[1:]...), true 620 | } 621 | } 622 | for _, enumDescriptorProto := range fileDescriptorProto.GetEnumType() { 623 | if len(identifierComponents) == 1 && enumDescriptorProto.GetName() == name { 624 | // The enum can only match if it's the last component we're looking for. 625 | return true, true 626 | } 627 | } 628 | return false, false 629 | } 630 | 631 | // isNestedDescriptorFromMessage acts the same as isNestedDescriptorFromFile, but is used 632 | // for *descriptorpb.DescriptorProto types. 633 | func isNestedDescriptorFromMessage(descriptorProto *descriptorpb.DescriptorProto, identifierComponents ...string) bool { 634 | if len(identifierComponents) == 0 { 635 | return false 636 | } 637 | name := identifierComponents[0] 638 | for _, nestedDescriptorProto := range descriptorProto.GetNestedType() { 639 | if nestedDescriptorProto.GetName() == name { 640 | if len(identifierComponents) == 1 { 641 | return true 642 | } 643 | return isNestedDescriptorFromMessage(nestedDescriptorProto, identifierComponents[1:]...) 644 | } 645 | } 646 | for _, enumDescriptorProto := range descriptorProto.GetEnumType() { 647 | if len(identifierComponents) == 1 && enumDescriptorProto.GetName() == name { 648 | // The enum can only match if it's the last component we're looking for. 649 | return true 650 | } 651 | } 652 | return false 653 | } 654 | 655 | // moduleFileForExternalPath returns the ModuleFile associated with the given 656 | // externalPath in the ModuleFileSet. We use the Image here so that we only 657 | // iterate over the reachable files. 658 | func moduleFileForExternalPath( 659 | ctx context.Context, 660 | moduleFileSet bufmodule.ModuleFileSet, 661 | image bufimage.Image, 662 | externalPath string, 663 | ) (bufmodule.ModuleFile, error) { 664 | for _, fileInfo := range image.Files() { 665 | if externalPath != fileInfo.ExternalPath() { 666 | continue 667 | } 668 | moduleFile, err := moduleFileSet.GetModuleFile( 669 | ctx, 670 | fileInfo.Path(), 671 | ) 672 | if err != nil { 673 | return nil, err 674 | } 675 | return moduleFile, nil 676 | } 677 | // TODO: https://github.com/bufbuild/buf/issues/1056 678 | // 679 | // This will only happen if a buf.work.yaml exists in a parent 680 | // directory, but it does not contain the target file. 681 | // 682 | // This is also a problem for other commands that interact 683 | // with buffetch.ProtoFileRef. 684 | return nil, fmt.Errorf( 685 | "input %s was not found - is the directory containing this file defined in your %s?", 686 | externalPath, 687 | bufwork.ExternalConfigV1FilePath, 688 | ) 689 | } 690 | 691 | // packageNameForFile returns the package name defined in the given *ast.FileNode, 692 | // if any. 693 | func packageNameForFile(fileNode *ast.FileNode) string { 694 | for _, fileElement := range fileNode.Decls { 695 | packageNode, ok := fileElement.(*ast.PackageNode) 696 | if ok { 697 | return string(packageNode.Name.AsIdentifier()) 698 | } 699 | } 700 | return "" 701 | } 702 | 703 | // locationIsWithinNode returns true if the given location is contained 704 | // within the node. 705 | func locationIsWithinNode(location Location, nodeInfo ast.NodeInfo) bool { 706 | var ( 707 | start = nodeInfo.Start() 708 | end = nodeInfo.End() 709 | ) 710 | // This is an "open range", so the locaton.Column() must be strictly 711 | // less than the end.Col. 712 | return location.Line() >= start.Line && location.Line() <= end.Line && location.Column() >= start.Col && location.Column() < end.Col 713 | } 714 | 715 | // fileAnnotationsToError maps the given fileAnnotations into an error. 716 | func fileAnnotationsToError(fileAnnotations []bufanalysis.FileAnnotation) error { 717 | buffer := bytes.NewBuffer(nil) 718 | if err := bufanalysis.PrintFileAnnotations( 719 | buffer, 720 | fileAnnotations, 721 | bufanalysis.FormatText.String(), 722 | ); err != nil { 723 | return err 724 | } 725 | // It's important that we trim the trailing newline so that the CLI 726 | // (and other clients) don't receive a trailing newline in their 727 | // error messages. 728 | return errors.New(strings.TrimSuffix(buffer.String(), "\n")) 729 | } 730 | 731 | // newCannotResolveLocationError returns an error that describes that the location 732 | // could not be resolved. 733 | func newCannotResolveLocationError(location Location) error { 734 | return fmt.Errorf("could not resolve definition for location %s", location) 735 | } 736 | --------------------------------------------------------------------------------