As more and more developers adopt Protobuf in their projects, it becomes increasingly important to ensure that the code is clean, consistent, and easy to read. This is where linting and formatting come in.

Linting and formatting tools help to maintain the quality of the code by enforcing a set of rules for style, syntax, and best practices. They can catch errors early, make the code easier to understand, and reduce the amount of manual code review required. In this how-to guide, we will explore how to set up linting and formatting for Protobuf in order to maintain code quality and consistency in your projects.

We recommend completing the Tour of Buf for an introduction to Protobuf linting with the buf lint command.

1. Define a module

To get started linting your Protobuf sources, create a Buf module by adding a buf.yaml file to the root of the directory that holds your Protobuf definitions. You can create the default buf.yaml file with this command:

$ buf mod init

That creates this file:

buf.yaml
version: v1
breaking:
    use:
        - FILE
lint:
    use:
        - DEFAULT

As you can see, the default configuration applies the DEFAULT rules.

2. Run lint & format

You can run buf lint on your module by specifying the filepath to the directory containing the buf.yaml. It uses the current directory by default, so you can target the input defined in the current directory with this command:

$ buf lint

The buf lint command performs these actions in order:

  • Discovers all of the Protobuf files per your buf.yaml configuration.
  • Copies them into memory.
  • Compiles them
  • Runs the compilation result against the configured lint rules.

For a more practical look at linting Protobuf sources with Buf, see the linting example project.

Error syntax

Any lint errors discovered are printed out in this format:

Lint error syntax
{file}:{line}:{column}:{message}
Examplepet/v1/pet.proto:47:9:Service name "PetStore" should be suffixed with "Service".
Legend:
{variable}

Here's a full example output:

$ buf lint
Output
google/type/datetime.proto:17:1:Package name "google.type" should be suffixed with a correctly formed version, such as "google.type.v1". pet/v1/pet.proto:42:10:Field name "petID" should be lower_snake_case, such as "pet_id". pet/v1/pet.proto:47:9:Service name "PetStore" should be suffixed with "Service".

2.1. JSON output

You can print lint output as JSON:

$ buf lint --error-format=json
Output
{"path":"google/type/datetime.proto","start_line":17,"start_column":1,"end_line":17,"end_column":21,"type":"PACKAGE_VERSION_SUFFIX","message":"Package name \"google.type\" should be suffixed with a correctly formed version, such as \"google.type.v1\"."} {"path":"pet/v1/pet.proto","start_line":42,"start_column":10,"end_line":42,"end_column":15,"type":"FIELD_LOWER_SNAKE_CASE","message":"Field name \"petID\" should be lower_snake_case, such as \"pet_id\"."} {"path":"pet/v1/pet.proto","start_line":47,"start_column":9,"end_line":47,"end_column":17,"type":"SERVICE_SUFFIX","message":"Service name \"PetStore\" should be suffixed with \"Service\"."}

2.2. Copy errors into your configuration

We can output errors in a format that you can copy into your buf.yaml configuration file. This enables you to ignore specific lint errors and gradually correct them over time:

$ buf lint --error-format=config-ignore-yaml
Output
version: v1 lint: ignore_only: FIELD_LOWER_SNAKE_CASE: - pet/v1/pet.proto PACKAGE_VERSION_SUFFIX: - google/type/datetime.proto SERVICE_SUFFIX: - pet/v1/pet.proto

2.3. Automatically format your .proto files

The buf format command rewrites .proto files in-place according to an opinionated style. Rewrite the file(s) in-place with -w. For example,

# Rewrite a single file in-place
$ buf format -w

3. Common use cases

buf can lint inputs beyond your local Protobuf files, such as Git repositories and tarballs. This can be useful in a variety of scenarios, such as using protoc output as buf input. Here are some examples script:

# Lint output from protoc passed to stdin.
protoc -I . --include_source_info $(find . -name '*.proto') -o /dev/stdout | buf lint -

# Lint a remote git repository on the fly and override the config to be your local config file.
buf lint 'https://github.com/googleapis/googleapis.git' --config buf.yaml

# Lint a module published to the Buf Schema Registry.
buf lint buf.build/acme/petapis

For remote locations that require authentication, see HTTPS Authentication and SSH Authentication.

4. Limit to specific files

By default, the buf CLI builds all files under your buf.yaml configuration file. But you can optionally lint only specific files or directories. This is an advanced feature that's mostly intended to be used by other systems, like editors. In general, it's better to let the buf CLI discover all files and handle this for you. But if you do need this, you can use the --path flag:

$ buf lint \
  --path path/to/foo.proto \
  --path path/to/bar.proto

You can also combine this with an in-line configuration override:

$ buf lint \
  --path path/to/foo.proto \
  --path path/to/bar.proto \
  --config '{"version":"v1","lint":{"use":["BASIC"]}}'

5. Docker

Buf ships a Docker image, bufbuild/buf, that enables you to use buf as part of your Docker workflow. Here's an example:

$ docker run \
  --volume "$(pwd):/workspace" \
  --workdir /workspace \
  bufbuild/buf lint

Conclusion

In conclusion, linting your Protobuf sources with Buf is an important step in ensuring the consistency and correctness of your Protobuf definitions. By defining a Buf module and running buf lint, you can quickly identify and correct issues with your Protobuf files, such as field naming conventions, package name suffixes, and more. You can also use the buf format command to automatically rewrite your .proto files according to an opinionated style. By following the steps outlined in this guide, you can improve the quality of your Protobuf definitions and avoid errors and inconsistencies in your APIs.

Next steps

Check out our style guide for a greater understanding of Protobuf development best practices.