Linting tools help to maintain the quality of 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.

Buf offers linting for Protobuf files via the buf lint command in the Buf CLI. In this tutorial, we'll explore how to set up linting for Protobuf files to maintain code quality and consistency in your projects.

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

1. Define a module

To lint your Protobuf sources, first initialize a Buf module. Modules identify a collection of Protobuf files for Buf to treat as a unit. Run the following command in the root of the directory that holds your Protobuf definitions.

$ buf mod init

That creates a buf.yaml file with some basic defaults:

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

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

2. Run lint

Run buf lint on your module by specifying the filepath to the directory containing the buf.yaml file. 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

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 example scripts:

# 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

6. Editor integration

The machine readable error output integrates with IDEs, scripts, and other tools. Currently, we provide editor integration for Vim, Visual Studio Code, and JetBrains IDEs, and we may support other editors in the future.

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. 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.