### Example: Initialize Plugin Spec Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/info-package.md Example of how to initialize and populate the Spec struct with license information. This is useful when configuring a plugin's metadata. ```go spec := &check.Spec{ Rules: [...], Info: &info.Spec{ SPDXLicenseID: "apache-2.0", LicenseURL: "https://github.com/myorg/buf-plugin-example/blob/main/LICENSE", }, } ``` -------------------------------- ### Get Plugin Info and Display License Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/info-package.md Example of retrieving plugin information and printing the SPDX license ID if available. ```go info, err := client.GetPluginInfo(ctx) if err != nil { return err } if id := info.SPDXLicenseID(); id != "" { fmt.Printf("Plugin is licensed under %s\n", id) } ``` -------------------------------- ### Complete Plugin with Info Specification Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/info-package.md Example of a Go program that uses bufplugin-go to define a linter with custom rules and plugin information, including SPDX license ID and license URL. This setup allows the plugin to display detailed information when the buf-plugin-example --help command is executed. ```go package main import ( "context" "buf.build/go/bufplugin/check" "buf.build/go/bufplugin/check/checkutil" "buf.build/go/bufplugin/info" "google.golang.org/protobuf/reflect/protoreflect" ) func main() { check.Main(&check.Spec{ Rules: []*check.RuleSpec{ { ID: "MY_RULE", Default: true, Purpose: "Checks that messages follow naming conventions.", Type: check.RuleTypeLint, Handler: checkutil.NewMessageRuleHandler(checkMessage, checkutil.WithoutImports()), }, }, Info: &info.Spec{ SPDXLicenseID: "apache-2.0", LicenseURL: "https://github.com/example/buf-plugin-example/blob/main/LICENSE", }, }) } func checkMessage( ctx context.Context, rw check.ResponseWriter, req check.Request, msg protoreflect.MessageDescriptor, ) error { name := msg.Name() if !isUpperCamelCase(string(name)) { rw.AddAnnotation( check.WithMessagef("Message name %q should be UpperCamelCase", name), check.WithDescriptor(msg), ) } return nil } func isUpperCamelCase(s string) bool { if len(s) == 0 { return false } return rune(s[0]) >= 'A' && rune(s[0]) <= 'Z' } ``` -------------------------------- ### Install Buf Plugin to PATH Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/quick-start.md Move the compiled plugin binary to a directory included in your system's PATH for easy access. This example uses `$HOME/.local/bin`. ```bash mkdir -p $HOME/.local/bin mv buf-plugin-example $HOME/.local/bin/ export PATH="$HOME/.local/bin:$PATH" ``` -------------------------------- ### Example Usage of NewServer Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/check-package.md Demonstrates how to create a new Bufplugin server instance. Ensure error handling is implemented. ```go server, err := check.NewServer(spec) if err != nil { return err } ``` -------------------------------- ### Example Plugin Using Checkutil Go Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/checkutil-package.md An example of a buf plugin that uses checkutil to define a lint rule for field names. It demonstrates setting up the check.Main function with a RuleSpec and a custom handler. ```go package main import ( "context" "strings" "buf.build/go/bufplugin/check" "buf.build/go/bufplugin/check/checkutil" "google.golang.org/protobuf/reflect/protoreflect" ) func main() { check.Main(&check.Spec{ Rules: []*check.RuleSpec{ { ID: "FIELD_LOWER_SNAKE_CASE", Default: true, Purpose: "Ensures all field names are lower_snake_case.", Type: check.RuleTypeLint, Handler: checkutil.NewFieldRuleHandler(checkField, checkutil.WithoutImports()), }, }, }) } func checkField( ctx context.Context, rw check.ResponseWriter, req check.Request, field protoreflect.FieldDescriptor, ) error { name := string(field.Name()) if name != toLowerSnakeCase(name) { rw.AddAnnotation( check.WithMessagef("Field name %q must be lower_snake_case", name), check.WithDescriptor(field), ) } return nil } func toLowerSnakeCase(s string) string { // implementation return strings.ToLower(s) } ``` -------------------------------- ### Example RuleSpec Initialization Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/check-package.md This example shows how to initialize a RuleSpec for a lint rule. Ensure the ID and Type fields are correctly set according to the rule's purpose. ```go check.RuleSpec{ ID: "MY_LINT_RULE", Type: check.RuleTypeLint, // ... } ``` -------------------------------- ### Buf Configuration Example Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/architecture.md Illustrates the structure of a buf.yaml configuration file, including version, linting rules, and plugin definitions with options. ```yaml buf.yaml │ ├─ version: v2 ├─ lint: │ └─ use: [FIELD_LOWER_SNAKE_CASE, ...] │ └─ plugins: └─ [0]: ├─ plugin: buf-plugin-example └─ options: ├─ suffix: "_time" └─ ... ``` -------------------------------- ### Example: Retrieve and Print Plugin Info Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/info-package.md Demonstrates how to use the Client interface to retrieve plugin information and print the SPDX license ID. This is useful for dynamically accessing plugin metadata. ```go info, err := client.GetPluginInfo(ctx) if err != nil { return err } fmt.Println("License:", info.SPDXLicenseID()) ``` -------------------------------- ### Initialize Go Module and Get Buf Plugin Dependency Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/quick-start.md Sets up a new Go module and fetches the bufplugin library. This is the first step in creating a new Buf plugin. ```bash go mod init buf.build/private/bufplugin-example go get buf.build/go/bufplugin@latest ``` -------------------------------- ### Example Debugging Output Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/migration-testing.md This is an example of the output you might see when a migration test fails. It indicates the test name, the expected annotation that was not found, the RuleID, the error message, the file path, and the start line and column numbers. ```text FAIL: TestFieldLowerSnakeCase expected annotation not found: RuleID: FIELD_LOWER_SNAKE_CASE Message: Field name "MyField" should be lower_snake_case FilePath: test.proto StartLine: 4, StartColumn: 9 ``` -------------------------------- ### AddAnnotation Example Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/check-package.md Demonstrates how to use the AddAnnotation method with options to report a violation. It specifies the violation message and the descriptor associated with the violation. ```go responseWriter.AddAnnotation( check.WithMessage("Field name should be lower_snake_case"), check.WithDescriptor(fieldDescriptor), ) ``` -------------------------------- ### Basic Test Example for Lint Rules Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/migration-testing.md Demonstrates a basic test case for lint rules using `checktest.CheckTestCases`. This is useful for verifying individual lint rule behavior against specific proto definitions. ```go package main import ( t"testing" "buf.build/go/bufplugin/check/checktest" ) func TestRules(t *testing.T) { checktest.CheckTestCases(t, spec, []checktest.Case{ { Description: "field is lowercase", Proto: " syntax = \"proto3\"; message Foo { string field_name = 1; } ", }, { Description: "field is uppercase", Proto: " syntax = \"proto3\"; message Foo { string FieldName = 1; } ", ExpectedAnnotations: []checktest.ExpectedAnnotation{ { RuleID: "FIELD_LOWER_SNAKE_CASE", Message: `Field name "FieldName" should be lower_snake_case`, FilePath: "test.proto", StartLine: 4, StartColumn: 9, }, }, }, }) } ``` -------------------------------- ### Main Entrypoint for Bufplugin Check Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/check-package.md Use Main as the entrypoint for a plugin. It takes a Spec defining the rules and registers them with the pluginrpc framework, starting the plugin server and handling all RPC communication with the Buf CLI. Configure parallelism using MainWithParallelism. ```go func Main(spec *Spec, options ...MainOption) ``` ```go func main() { check.Main( &check.Spec{ Rules: []*check.RuleSpec{ { ID: "TIMESTAMP_SUFFIX", Default: true, Purpose: "Checks that all google.protobuf.Timestamp fields end in _time.", Type: check.RuleTypeLint, Handler: checkutil.NewFieldRuleHandler(handleTimestampSuffix, checkutil.WithoutImports()), }, }, }, check.MainWithParallelism(4), ) } ``` -------------------------------- ### Configure buf.yaml for Custom Plugin Source: https://github.com/bufbuild/bufplugin-go/blob/main/README.md Example of how to configure buf.yaml to use a custom plugin and its rules. Ensure the plugin binary is in your system's PATH. ```yaml version: v2 lint: use: - TIMESTAMP_SUFFIX plugins: - plugin: buf-plugin-timestamp-suffix options: timestamp_suffix: _time # set to the suffix you'd like to enforce ``` -------------------------------- ### Buf.yaml Configuration for Plugin Options Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/quick-start.md Configure plugin options, such as a suffix, within the `buf.yaml` file. This example shows how to specify the `suffix` option for a plugin. ```yaml version: v2 lint: use: - STANDARD # or omit for custom-only - FIELD_LOWER_SNAKE_CASE plugins: - plugin: buf-plugin-example options: suffix: "_time" ``` -------------------------------- ### Test Buf Plugin Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/quick-start.md Run `buf lint` in your project to test the installed plugin. Buf will automatically discover and use plugins found in your PATH. ```bash buf lint ``` -------------------------------- ### Testing Rules with CheckTestCases in Go Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/README.md Provides a framework for testing plugin rules using predefined test cases. It includes examples for both valid and invalid proto definitions, along with expected annotations for failures. ```go checktest.CheckTestCases(t, spec, []checktest.Case{ { Description: "valid case", Proto: "syntax = \"proto3\"; message Foo { string field = 1; }", }, { Description: "invalid case", Proto: "syntax = \"proto3\"; message Foo { string Field = 1; }", ExpectedAnnotations: []checktest.ExpectedAnnotation{ { RuleID: "RULE_ID", Message: "expected message", FilePath: "test.proto", StartLine: 0, StartColumn: 0, }, }, }, }) ``` -------------------------------- ### Test Case for Multiple Rules Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/migration-testing.md This example demonstrates how to test a proto definition that triggers multiple distinct lint rules. It's useful for verifying that your linter can identify and report violations from different rule sets concurrently. ```go { Description: "multiple rules fire", Proto: " syntax = \"proto3\"; message foo { string FieldName = 1; } ", ExpectedAnnotations: []checktest.ExpectedAnnotation{ { RuleID: "MESSAGE_UPPER_CAMEL_CASE", Message: `Message name "foo" should be UpperCamelCase`, FilePath: "test.proto", StartLine: 3, StartColumn: 8, }, { RuleID: "FIELD_LOWER_SNAKE_CASE", Message: `Field name "FieldName" should be lower_snake_case`, FilePath: "test.proto", StartLine: 4, StartColumn: 9, }, }, } ``` -------------------------------- ### Lint Handlers with checkutil Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/quick-start.md These Go code examples demonstrate how to use various checkutil handlers for iterating through Protocol Buffers elements during linting. They cover files, messages, fields, services, methods, and enums, with an option to exclude imports. ```go // Iterate files handler := checkutil.NewFileRuleHandler(checkFile, checkutil.WithoutImports()) ``` ```go // Iterate messages handler := checkutil.NewMessageRuleHandler(checkMessage, checkutil.WithoutImports()) ``` ```go // Iterate fields handler := checkutil.NewFieldRuleHandler(checkField, checkutil.WithoutImports()) ``` ```go // Iterate services handler := checkutil.NewServiceRuleHandler(checkService, checkutil.WithoutImports()) ``` ```go // Iterate methods handler := checkutil.NewMethodRuleHandler(checkMethod, checkutil.WithoutImports()) ``` ```go // Iterate enums handler := checkutil.NewEnumRuleHandler(checkEnum, checkutil.WithoutImports()) ``` -------------------------------- ### buf.yaml Configuration for Timestamp Suffix Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/option-package.md Example of how to configure the `buf-plugin-timestamp-suffix` plugin in `buf.yaml`, specifying custom options like the suffix. ```yaml version: v2 lint: use: - TIMESTAMP_SUFFIX plugins: - plugin: buf-plugin-timestamp-suffix options: suffix: "_time" ``` -------------------------------- ### Check File Syntax and Import Status Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/descriptor-package.md This example demonstrates how to use the FileDescriptor interface to check if a file is an import and if its syntax was unspecified. It adds an annotation if the file is not an import and has no explicit syntax declaration. ```go handler := checkutil.NewFileRuleHandler( func(ctx context.Context, rw check.ResponseWriter, req check.Request, fd descriptor.FileDescriptor) error { if !fd.IsImport() && fd.IsSyntaxUnspecified() { rw.AddAnnotation( check.WithMessage("File must have explicit syntax declaration"), check.WithDescriptor(fd.ProtoreflectFileDescriptor()), ) } return nil }, ) ``` -------------------------------- ### Example Protobuf Definition Source: https://github.com/bufbuild/bufplugin-go/blob/main/README.md A sample protobuf message definition that includes fields which might be subject to linting rules, such as timestamp fields. ```protobuf syntax = "proto3"; package foo; import "google/protobuf/timestamp.proto"; message Foo { google.protobuf.Timestamp start = 1; google.protobuf.Timestamp end_time = 2; } ``` -------------------------------- ### Main Function Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/check-package.md The Main function is the entrypoint for a Bufplugin. It takes a Spec defining the rules and registers them with the pluginrpc framework, starting the plugin server and handling RPC communication with the Buf CLI. ```APIDOC ## Main(spec *Spec, options ...MainOption) ### Description Main is the entrypoint for a plugin. It takes a Spec defining the rules and registers them with the pluginrpc framework. This function starts the plugin server and handles all RPC communication with the Buf CLI. ### Parameters #### Path Parameters - None #### Query Parameters - None #### Request Body - None ### Request Example ```go func main() { check.Main( &check.Spec{ Rules: []*check.RuleSpec{ { ID: "TIMESTAMP_SUFFIX", Default: true, Purpose: "Checks that all google.protobuf.Timestamp fields end in _time.", Type: check.RuleTypeLint, Handler: checkutil.NewFieldRuleHandler(handleTimestampSuffix, checkutil.WithoutImports()), }, }, }, check.MainWithParallelism(4), ) } ``` ### Response #### Success Response (200) - void #### Response Example - None ``` -------------------------------- ### Example of Validating Plugin Info Spec Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/info-package.md Demonstrates how to call ValidateSpec to check the validity of plugin information within a spec. ```go if err := info.ValidateSpec(spec.Info); err != nil { return fmt.Errorf("invalid plugin info: %w", err) } ``` -------------------------------- ### Example Usage of ValidateSpec Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/check-package.md This example demonstrates how to call ValidateSpec and handle potential validation errors, typically used within tests to ensure spec correctness. ```go if err := check.ValidateSpec(spec); err != nil { log.Fatal(err) } ``` -------------------------------- ### Compare FileLocations in Go Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/descriptor-package.md Use this function to compare two FileLocations for sorting or ordering purposes. It returns -1, 0, or 1 based on the comparison of file path, start line, start column, end line, end column, and source path. ```go func CompareFileLocations(one FileLocation, two FileLocation) int ``` ```go locations := []FileLocation{loc3, loc1, loc2} sort.Slice(locations, func(i, j int) bool { return descriptor.CompareFileLocations(locations[i], locations[j]) < 0 }) ``` -------------------------------- ### Minimal Buf Plugin Example (main.go) Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/quick-start.md A basic Buf plugin that implements a lint rule to check if field names are in lower_snake_case. It uses bufplugin-go's check package for defining rules and handlers. ```go package main import ( "context" "buf.build/go/bufplugin/check" "buf.build/go/bufplugin/check/checkutil" "buf.build/go/bufplugin/info" "google.golang.org/protobuf/reflect/protoreflect" ) func main() { check.Main(&check.Spec{ Rules: []*check.RuleSpec{ { ID: "FIELD_LOWER_SNAKE_CASE", Default: true, Purpose: "Ensures all field names are lower_snake_case.", Type: check.RuleTypeLint, Handler: checkutil.NewFieldRuleHandler(checkField, checkutil.WithoutImports()), }, }, Info: &info.Spec{ SPDXLicenseID: "apache-2.0", LicenseURL: "https://github.com/example/buf-plugin-example/blob/main/LICENSE", }, }) } func checkField( ctx context.Context, rw check.ResponseWriter, req check.Request, field protoreflect.FieldDescriptor, ) error { fieldName := string(field.Name()) if !isLowerSnakeCase(fieldName) { rw.AddAnnotation( check.WithMessagef("Field name %q should be lower_snake_case", fieldName), check.WithDescriptor(field), ) } return nil } func isLowerSnakeCase(s string) bool { for _, c := range s { if c >= 'A' && c <= 'Z' { return false } } return true } ``` -------------------------------- ### Buf Plugin Go Options Interface Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/option-package.md Defines the Options interface for retrieving plugin configuration values. Use Value to get a specific option with error handling, MustValue to get a value that must exist, or UnmarshalJSON to deserialize all options into a struct. ```go type Options interface { // Value gets the JSON value for the given key. // // Returns an error if the key is not present. Value(key string) (any, error) // MustValue gets the JSON value for the given key. // // Panics if the key is not present. MustValue(key string) any // UnmarshalJSON unmarshals all options into the given value. // // The value should be a pointer to a struct, typically generated from a .proto definition. UnmarshalJSON(value any) error } ``` -------------------------------- ### Get Option Value with Default Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/option-package.md Retrieves an option value, providing a default if the option is not present or an error occurs. ```go suffix := "_time" if s, err := options.Value("timestamp_suffix"); err == nil { suffix = s.(string) } ``` -------------------------------- ### Breaking Change Handlers with checkutil Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/quick-start.md These Go code examples illustrate the use of checkutil handlers for detecting backwards-incompatible changes in Protocol Buffers definitions. They provide handlers for comparing pairs of files, messages, fields, services, and methods. ```go // Pair files handler := checkutil.NewFilePairRuleHandler(checkFilePair) ``` ```go // Pair messages handler := checkutil.NewMessagePairRuleHandler(checkMessagePair) ``` ```go // Pair fields handler := checkutil.NewFieldPairRuleHandler(checkFieldPair) ``` ```go // Pair services handler := checkutil.NewServicePairRuleHandler(checkServicePair) ``` ```go // Pair methods handler := checkutil.NewMethodPairRuleHandler(checkMethodPair) ``` -------------------------------- ### Check File Properties with Buf Plugin Go Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/quick-start.md Use `checkutil.NewFileRuleHandler` to create a custom rule that checks file properties. This example adds an annotation if a file lacks an explicit syntax declaration. ```go handler := checkutil.NewFileRuleHandler( func(ctx context.Context, rw check.ResponseWriter, req check.Request, fd descriptor.FileDescriptor) error { if fd.IsSyntaxUnspecified() { rw.AddAnnotation( check.WithMessage("File must have explicit syntax declaration"), check.WithDescriptor(fd.ProtoreflectFileDescriptor()), ) } return nil }, checkutil.WithoutImports(), ) ``` -------------------------------- ### Example Lint Error Message Source: https://github.com/bufbuild/bufplugin-go/blob/main/README.md An example of a lint error message that would be returned by 'buf lint' when a custom plugin detects a violation. ```text foo.proto:8:3:Fields of type google.protobuf.Timestamp must end in "_time" but field name was "start". (buf-plugin-timestamp-suffix) ``` -------------------------------- ### CompareFileLocations Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/descriptor-package.md CompareFileLocations returns the result of comparing two FileLocations based on file path, start line, start column, end line, end column, and source path. ```APIDOC ## CompareFileLocations(one FileLocation, two FileLocation) int ### Description Compares two FileLocations and returns -1 if one < two, 0 if one == two, and 1 if one > two. ### Parameters - **one** (FileLocation) - The first FileLocation to compare. - **two** (FileLocation) - The second FileLocation to compare. ### Returns - **int**: -1 if one < two, 0 if one == two, 1 if one > two. ### Comparison Order 1. File path 2. Start line 3. Start column 4. End line 5. End column 6. Source path ### Example ```go locations := []FileLocation{loc3, loc1, loc2} sort.Slice(locations, func(i, j int) bool { return descriptor.CompareFileLocations(locations[i], locations[j]) < 0 }) ``` ``` -------------------------------- ### Build Buf Plugin Binary Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/quick-start.md Compile your Go plugin into an executable binary. Ensure you are in the plugin's root directory. ```bash go build -o buf-plugin-example ./cmd/buf-plugin-example ``` -------------------------------- ### Initialize Options with EmptyOptions Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/option-package.md Provides a default, empty Options instance when no configuration options are passed to a plugin. This prevents nil pointer errors. ```go var EmptyOptions Options // Example usage: if options == nil { options = option.EmptyOptions } ``` -------------------------------- ### Plugin Options and Configuration in Go Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/quick-start.md Define and parse plugin options in Go, allowing configuration from buf.yaml. The `parseOptions` function demonstrates how to retrieve these settings. ```go type PluginOptions struct { Suffix string `json:"suffix"` } func parseOptions(req check.Request) *PluginOptions { opts := &PluginOptions{Suffix: "_time"} // Parse from req.Options() if needed return opts } func checkField( ctx context.Context, rw check.ResponseWriter, req check.Request, field protoreflect.FieldDescriptor, ) error { opts := parseOptions(req) fieldName := string(field.Name()) if !strings.HasSuffix(fieldName, opts.Suffix) { rw.AddAnnotation( check.WithMessagef("Field must end with %q", opts.Suffix), check.WithDescriptor(field), ) } return nil } ``` -------------------------------- ### Get Single Option Value Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/option-package.md Retrieves a single option value by its key. Handles potential errors if the key is not found. ```go suffix, err := options.Value("timestamp_suffix") if err != nil { return err } suffixStr := suffix.(string) ``` -------------------------------- ### Plugin with Options Parsing in Go Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/README.md Demonstrates how to parse plugin options from the request to configure plugin behavior. It marshals options to JSON and unmarshals them into a configuration struct. ```go func parseOptions(req check.Request) (*Config, error) { config := &Config{Default: "value"} raw, _ := json.Marshal(req.Options()) json.Unmarshal(raw, config) return config, nil } func checkHandler(...) error { config, err := parseOptions(req) // use config } ``` -------------------------------- ### FileLocation Interface Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/types.md The FileLocation interface represents a source location within a proto file, including start and end line and column information. ```APIDOC ## FileLocation Interface ### Description FileLocation represents a source location within a proto file. ### Methods - **FileDescriptor**() `FileDescriptor` - Returns the FileDescriptor containing the location. - **StartLine**() `int32` - Returns the start line (0-indexed). - **StartColumn**() `int32` - Returns the start column (0-indexed). - **EndLine**() `int32` - Returns the end line. - **EndColumn**() `int32` - Returns the end column. ``` -------------------------------- ### Test Case for Multiple Files Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/migration-testing.md Test the plugin's behavior when processing multiple proto files, including imports. This ensures cross-file analysis and linting accuracy. ```go { Description: "checks multiple files", Proto: ` syntax = "proto3"; import "other.proto"; message Foo { string MyField = 1; } `, ExpectedAnnotations: []checktest.ExpectedAnnotation{ { RuleID: "FIELD_LOWER_SNAKE_CASE", Message: `Field name "MyField" should be lower_snake_case`, FilePath: "test.proto", StartLine: 5, StartColumn: 9, }, }, } ``` -------------------------------- ### Configure Plugins in buf.yaml Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/README.md Use this configuration in buf.yaml to specify linting rules, exceptions, ignored files, and plugins with their options. ```yaml version: v2 lint: use: - STANDARD # built-in rules - PLUGIN_RULE_ID # rule from plugin except: - OTHER_RULE_ID ignore_only: - path.proto plugins: - plugin: buf-plugin-example options: option_key: "option_value" ``` -------------------------------- ### Integrating Plugin Info into Check Spec Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/info-package.md Shows how to set plugin information (SPDXLicenseID and LicenseURL) within a check.Spec for automatic serving via GetPluginInfo RPC. ```go check.Main(&check.Spec{ Rules: []*check.RuleSpec{ // rules... }, Info: &info.Spec{ SPDXLicenseID: "apache-2.0", LicenseURL: "https://github.com/example/buf-plugin-example/blob/main/LICENSE", }, }) ``` -------------------------------- ### Test with Custom Options Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/migration-testing.md Use this snippet to test migration behavior when custom options are applied. ```go { Description: "with custom options", Proto: "...", Options: map[string]any{"key": "value"}, ExpectedAnnotations: []checktest.ExpectedAnnotation{...}, } ``` -------------------------------- ### NewFileLocation Constructor Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/descriptor-package.md Creates a FileLocation from a FileDescriptor and protoreflect SourceLocation. Use when you need to represent a specific location within a proto file. ```go func NewFileLocation( fileDescriptor FileDescriptor, sourceLocation protoreflect.SourceLocation, ) FileLocation ``` ```go sourceLocation := fd.ProtoreflectFileDescriptor().SourceLocations().ByDescriptor(messageDescriptor) fileLocation := descriptor.NewFileLocation(fd, sourceLocation) ``` -------------------------------- ### Accessing Options in a Rule Handler Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/option-package.md Demonstrates how to retrieve and use options within a Buf plugin's rule handler. Options are accessed via `req.Options()` and can be parsed on-demand. ```go func checkFieldTimestampSuffix( ctx context.Context, rw check.ResponseWriter, req check.Request, field protoreflect.FieldDescriptor, ) error { options := req.Options() // Get suffix from options or use default suffix := "_time" if s, err := options.Value("suffix"); err == nil { suffix = s.(string) } // Check field name ends with suffix fieldName := string(field.Name()) if !strings.HasSuffix(fieldName, suffix) { rw.AddAnnotation( check.WithMessagef("Timestamp field must end with %q", suffix), check.WithDescriptor(field), ) } return nil } ``` -------------------------------- ### Test Default Configuration Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/migration-testing.md Use this snippet to test the default behavior of a migration without any custom options. ```go { Description: "default configuration", Proto: "...", ExpectedAnnotations: []checktest.ExpectedAnnotation{...}, } ``` -------------------------------- ### Define Plugin Info Client Interface Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/info-package.md Defines the interface for retrieving plugin information. It includes a method to fetch plugin metadata. ```go type Client interface { GetPluginInfo(ctx context.Context, options ...GetPluginInfoCallOption) (PluginInfo, error) } ``` -------------------------------- ### Plugin with Typed Options Structure Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/option-package.md Shows how to define a Go struct for plugin options and a function to parse them from the request. Includes default values and JSON unmarshalling. ```go package main import ( "context" "encoding/json" "buf.build/go/bufplugin/check" "buf.build/go/bufplugin/check/checkutil" "google.golang.org/protobuf/reflect/protoreflect" ) // Options represents the plugin configuration type Options struct { Suffix string `json:"suffix"` } // parseOptions extracts options from the request func parseOptions(req check.Request) (*Options, error) { opts := &Options{Suffix: "_time"} // default if req.Options() == nil { return opts, nil } // Try to unmarshal JSON into struct raw, err := json.Marshal(req.Options()) if err != nil { return nil, err } if err := json.Unmarshal(raw, opts); err != nil { return nil, err } return opts, nil } func main() { check.Main(&check.Spec{ Rules: []*check.RuleSpec{ { ID: "TIMESTAMP_SUFFIX", Default: true, Purpose: "Checks that Timestamp fields end with configured suffix.", Type: check.RuleTypeLint, Handler: checkutil.NewFieldRuleHandler(checkTimestamp, checkutil.WithoutImports()), }, }, }) } func checkTimestamp( ctx context.Context, rw check.ResponseWriter, req check.Request, field protoreflect.FieldDescriptor, ) error { opts, err := parseOptions(req) if err != nil { return err } // Check if field is Timestamp type fieldType := field.Message() if fieldType == nil || fieldType.FullName() != "google.protobuf.Timestamp" { return nil } fieldName := string(field.Name()) if !hasFieldNameSuffix(fieldName, opts.Suffix) { rw.AddAnnotation( check.WithMessagef("Timestamp field %q must end with %q", fieldName, opts.Suffix), check.WithDescriptor(field), ) } return nil } func hasFieldNameSuffix(name, suffix string) bool { // implementation return true } ``` -------------------------------- ### Implement a Lint Rule with Bufplugin Go Source: https://github.com/bufbuild/bufplugin-go/blob/main/README.md This snippet shows the basic structure for implementing a lint rule in a buf plugin. It includes setting up the plugin specification, defining a rule with an ID, default status, purpose, type, and a handler function. The handler checks for a specific condition (field name casing) and adds an annotation if the condition is not met. Requires buf.build/go/bufplugin and google.golang.org/protobuf/reflect/protoreflect imports. ```go package main import ( "context" "buf.build/go/bufplugin/check" "buf.build/go/bufplugin/check/checkutil" "google.golang.org/protobuf/reflect/protoreflect" ) func main() { check.Main( &check.Spec{ Rules: []*check.RuleSpec{ { ID: "PLUGIN_FIELD_LOWER_SNAKE_CASE", Default: true, Purpose: "Checks that all field names are lower_snake_case.", Type: check.RuleTypeLint, Handler: checkutil.NewFieldRuleHandler(checkFieldLowerSnakeCase, checkutil.WithoutImports()), }, }, }, ) } func checkFieldLowerSnakeCase( _ context.Context, responseWriter check.ResponseWriter, _ check.Request, fieldDescriptor protoreflect.FieldDescriptor, ) error { fieldName := string(fieldDescriptor.Name()) fieldNameToLowerSnakeCase := toLowerSnakeCase(fieldName) if fieldName != fieldNameToLowerSnakeCase { responseWriter.AddAnnotation( check.WithMessagef( "Field name %q should be lower_snake_case, such as %q.", fieldName, fieldNameToLowerSnakeCase, ), check.WithDescriptor(fieldDescriptor), ) } return nil } func toLowerSnakeCase(fieldName string) string { // The actual logic for toLowerSnakeCase would go here. return "TODO" } ``` -------------------------------- ### Define Plugin Specification (Spec) Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/types.md Spec defines the complete plugin specification passed to check.Main(). It includes rules, categories, plugin metadata, and an optional preprocessing hook. ```go type Spec struct { Rules []*RuleSpec Categories []*CategorySpec Info *info.Spec Before func(ctx context.Context, request Request) (context.Context, Request, error) } ``` -------------------------------- ### Run Go Tests Verbose Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/migration-testing.md Use the `-v` flag with `go test` to see which specific test cases are failing. This is useful for pinpointing issues during migration testing. ```bash go test -v ``` -------------------------------- ### NewServer Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/check-package.md Creates a pluginrpc.Server that implements the Bufplugin check API. This server handles various RPC commands like Check, ListRules, ListCategories, and GetPluginInfo. ```APIDOC ## NewServer ### Description Creates a pluginrpc.Server that implements the Bufplugin check API. This server handles various RPC commands like Check, ListRules, ListCategories, and GetPluginInfo. ### Method `func NewServer(spec *Spec, options ...ServerOption) (pluginrpc.Server, error)` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```go server, err := check.NewServer(spec) if err != nil { return err } ``` ### Response #### Success Response - `pluginrpc.Server`: The created server instance. - `error`: An error if the server creation fails. #### Response Example None explicitly provided in source. ``` -------------------------------- ### Define Plugin Metadata with Spec Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/info-package.md Defines the structure for plugin metadata including SPDX license ID and license URL. Use this to specify licensing information for your plugin. ```go type Spec struct { SPDXLicenseID string LicenseURL string } ``` -------------------------------- ### NewClient Function Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/check-package.md Creates a new Client instance from a pluginrpc.Client, allowing for optional configuration via ClientOptions. ```APIDOC ## NewClient Function ### NewClient(pluginrpcClient pluginrpc.Client, options ...ClientOption) Client ```go func NewClient(pluginrpcClient pluginrpc.Client, options ...ClientOption) Client ``` NewClient creates a new Client from a pluginrpc.Client. | Parameter | Type | Required | Description | |-----------|------|----------|---------| | pluginrpcClient | `pluginrpc.Client` | yes | Connection to plugin | | options | `...ClientOption` | no | Configuration | **Returns**: `Client` **Source**: `check/client.go` ``` -------------------------------- ### CheckRequest and Rule Handler Interaction Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/architecture.md Shows the data flow from CheckRequest to Rule Handler, demonstrating how options are accessed. ```go CheckRequest { FileDescriptors: [...], Options: {"suffix": "_time", ...}, RuleIds: ["FIELD_LOWER_SNAKE_CASE", ...], } Rule Handler { req.Options().Value("suffix") → "_time" } ``` -------------------------------- ### Create File Rule Handler Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/checkutil-package.md Creates a handler that invokes a callback function for each file in the request. Use WithoutImports() to exclude imported files from iteration. ```go handler := checkutil.NewFileRuleHandler( func(ctx context.Context, rw check.ResponseWriter, req check.Request, file descriptor.FileDescriptor) error { // check file properties return nil }, checkutil.WithoutImports(), ) ``` -------------------------------- ### Run Test Cases with checktest Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/migration-testing.md Use `CheckTestCases` to execute a series of test cases against a plugin spec. This function compiles .proto files and applies the defined rules, reporting any discrepancies. ```go func CheckTestCases( t *testing.T, spec *check.Spec, cases []Case, ) ``` ```go import ( "testing" "buf.build/go/bufplugin/check" "buf.build/go/bufplugin/check/checktest" "github.com/stretchr/testify/require" ) func TestFieldLowerSnakeCase(t *testing.T) { checktest.CheckTestCases(t, spec, []checktest.Case{ { Description: "valid lowercase field", Proto: "syntax = \"proto3\";\nmessage Foo { string field = 1; }", }, { Description: "invalid uppercase field", Proto: "syntax = \"proto3\";\nmessage Foo { string Field = 1; }", ExpectedAnnotations: []checktest.ExpectedAnnotation{ { RuleID: "FIELD_LOWER_SNAKE_CASE", Message: "Field name \"Field\" should be lower_snake_case", FilePath: "test.proto", StartLine: 1, StartColumn: 38, }, }, }, }) } ``` -------------------------------- ### Create Message Rule Handler Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/checkutil-package.md Creates a handler that iterates over every message type in all files. The callback function is invoked for each message, allowing for checks on message properties like naming conventions. Use WithoutImports() to exclude imported files. ```go handler := checkutil.NewMessageRuleHandler( func(ctx context.Context, rw check.ResponseWriter, req check.Request, msg protoreflect.MessageDescriptor) error { msgName := msg.Name() if !isUpperCamelCase(msgName) { rw.AddAnnotation( check.WithMessagef("Message %q should be UpperCamelCase", msgName), check.WithDescriptor(msg), ) } return nil }, checkutil.WithoutImports(), ) ``` -------------------------------- ### PluginInfo Interface Definition Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/info-package.md Defines the interface for accessing plugin metadata like SPDX license ID and license URL. ```go type PluginInfo interface { SPDXLicenseID() string LicenseURL() string } ``` -------------------------------- ### Create New Request in Go Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/check-package.md Constructs a new Request object for a plugin check. Use this function to provide the necessary file descriptors and configuration options. ```go func NewRequest( fileDescriptors []descriptor.FileDescriptor, options ...RequestOption, ) (Request, error) ``` ```go request, err := check.NewRequest( fileDescriptors, check.WithAgainstFileDescriptors(previousVersions), check.WithOptions(opts), check.WithRuleIDs("MY_RULE_ID"), ) ``` -------------------------------- ### PluginInfo Interface Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/info-package.md The PluginInfo interface provides methods to access metadata about a plugin, such as its SPDX license identifier and a URL to its license text. This information is typically returned by the GetPluginInfo RPC. ```APIDOC ## PluginInfo Interface ### Description Provides access to plugin metadata returned from GetPluginInfo. ### Methods | Method | Returns | Description | |-----------------|---------|--------------------------| | `SPDXLicenseID()` | `string` | SPDX license identifier | | `LicenseURL()` | `string` | URL to license text | ### Example Usage ```go info, err := client.GetPluginInfo(ctx) if err != nil { return err } if id := info.SPDXLicenseID(); id != "" { fmt.Printf("Plugin is licensed under %s\n", id) } ``` ``` -------------------------------- ### NewClient Function Signature Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/check-package.md Creates a new Client from a pluginrpc.Client. This function is used to instantiate a client for interacting with a plugin. ```go func NewClient(pluginrpcClient pluginrpc.Client, options ...ClientOption) Client ``` -------------------------------- ### Options Interface Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/option-package.md The Options interface provides methods to access plugin configuration options passed from the Buf CLI. It allows retrieving individual option values by key or unmarshalling all options into a Go struct. ```APIDOC ## type Options interface ### Description The Options interface provides methods to access plugin configuration options passed from the Buf CLI. It allows retrieving individual option values by key or unmarshalling all options into a Go struct. ### Methods - **Value(key string) (any, error)**: Gets the JSON value for the given key. Returns an error if the key is not present. - **MustValue(key string) any**: Gets the JSON value for the given key. Panics if the key is not present. - **UnmarshalJSON(value any) error**: Unmarshals all options into the given value. The value should be a pointer to a struct, typically generated from a .proto definition. ### Usage Patterns 1. **Get single option**: ```go suffix, err := options.Value("timestamp_suffix") if err != nil { return err } suffixStr := suffix.(string) ``` 2. **Get with default**: ```go suffix := "_time" if s, err := options.Value("timestamp_suffix"); err == nil { suffix = s.(string) } ``` 3. **Unmarshal to struct**: ```go type Config struct { TimestampSuffix string `json:"timestamp_suffix"` } var config Config if err := options.UnmarshalJSON(&config); err != nil { return err } ``` ### Source `option/option.go` ``` -------------------------------- ### Use FullName for Comparisons in Buf Plugin Go Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/quick-start.md This function demonstrates how to use the `FullName()` method of a message descriptor for comparisons, specifically to skip well-known types like those in the `google.protobuf` package. ```go func checkMessage( ctx context.Context, rw check.ResponseWriter, req check.Request, msg protoreflect.MessageDescriptor, ) error { fullName := string(msg.FullName()) if strings.HasPrefix(fullName, "google.protobuf.") { // Skip well-known types return nil } return nil } ``` -------------------------------- ### Write Meaningful Messages in Go Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/quick-start.md Provide specific and informative messages for detected issues. Use `WithMessagef` for formatted messages. ```go // Good check.WithMessagef("Field %q should be lower_snake_case, such as %q", name, toLowerSnakeCase(name)) // Poor check.WithMessage("naming issue") ``` -------------------------------- ### Define Options Interface for Plugin Configuration Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/types.md The Options interface provides methods to access plugin configuration values. It allows retrieving values by key, with a panic option for mandatory values, and supports unmarshalling all options into a struct. ```go type Options interface { Value(key string) (any, error) MustValue(key string) any UnmarshalJSON(value any) error } ``` -------------------------------- ### Client.GetPluginInfo Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/info-package.md Retrieve plugin metadata including license information. ```APIDOC ## GetPluginInfo ### Description Retrieve plugin metadata. ### Method `GetPluginInfo` ### Signature `GetPluginInfo(ctx context.Context, options ...GetPluginInfoCallOption) (PluginInfo, error)` ### Parameters #### Options - `options` (...GetPluginInfoCallOption) - Optional - Options for the GetPluginInfo call. ### Response #### Success Response - `PluginInfo` - An object containing plugin metadata. #### Response Example ```go info, err := client.GetPluginInfo(ctx) if err != nil { return err } fmt.Println("License:", info.SPDXLicenseID()) ``` ``` -------------------------------- ### Test Timestamp Suffix Option Handling Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/migration-testing.md Verify that the plugin correctly handles the 'suffix' option for timestamp fields. Includes tests for both default and custom suffixes. ```go func TestTimestampSuffixOption(t *testing.T) { checktest.CheckTestCases(t, spec, []checktest.Case{ { Description: "default suffix _time", Proto: ` syntax = "proto3"; import "google/protobuf/timestamp.proto"; message Foo { google.protobuf.Timestamp start = 1; } `, ExpectedAnnotations: []checktest.ExpectedAnnotation{ { RuleID: "TIMESTAMP_SUFFIX", Message: `Timestamp field "start" must end with "_time"`, FilePath: "test.proto", StartLine: 5, StartColumn: 2, }, }, }, { Description: "custom suffix _ts", Proto: ` syntax = "proto3"; import "google/protobuf/timestamp.proto"; message Foo { google.protobuf.Timestamp start = 1; } `, Options: map[string]any{ "suffix": "_ts", }, ExpectedAnnotations: []checktest.ExpectedAnnotation{ { RuleID: "TIMESTAMP_SUFFIX", Message: `Timestamp field "start" must end with "_ts"`, FilePath: "test.proto", StartLine: 5, StartColumn: 2, }, }, }, }) } ``` -------------------------------- ### Client Interface Definition Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/check-package.md Defines the interface for a client that consumes a plugin. Use this interface to interact with buf plugin services for checks and listings. ```go type Client interface { Check(ctx context.Context, request Request, options ...CheckCallOption) (Response, error) ListRules(ctx context.Context, options ...ListRulesCallOption) ([]Rule, error) ListCategories(ctx context.Context, options ...ListCategoriesCallOption) ([]Category, error) // Includes methods from info.Client } ``` -------------------------------- ### FileLocation Interface Definition Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/descriptor-package.md Defines the FileLocation interface for representing positions within a proto file. ```go type FileLocation interface { FileDescriptor() FileDescriptor StartLine() int32 StartColumn() int32 EndLine() int32 EndColumn() int32 } ``` -------------------------------- ### Create Field Rule Handler Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/checkutil-package.md Creates a handler that iterates over every field, including extensions, in all messages. The callback function is executed for each field to check properties like naming conventions. Use WithoutImports() to exclude imported files. ```go handler := checkutil.NewFieldRuleHandler( func(ctx context.Context, rw check.ResponseWriter, req check.Request, field protoreflect.FieldDescriptor) error { fieldName := field.Name() if !isLowerSnakeCase(fieldName) { rw.AddAnnotation( check.WithMessagef("Field %q should be lower_snake_case", fieldName), check.WithDescriptor(field), ) } return nil }, checkutil.WithoutImports(), ) ``` -------------------------------- ### ClientWithCaching Function Signature Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/check-package.md Enables caching of static data for the client, including ListRules, ListCategories, and GetPluginInfo results. Use this option when creating a client to improve performance by caching frequently accessed data. ```go func ClientWithCaching() ClientOption ``` -------------------------------- ### NewServiceRuleHandler Go Function Source: https://github.com/bufbuild/bufplugin-go/blob/main/_autodocs/checkutil-package.md Creates a rule handler that iterates over every service in all files. The callback signature is func(ctx context.Context, rw check.ResponseWriter, req check.Request, sd protoreflect.ServiceDescriptor) error. ```go func NewServiceRuleHandler( f func(context.Context, check.ResponseWriter, check.Request, protoreflect.ServiceDescriptor) error, options ...IteratorOption, ) check.RuleHandler ```