blob: 706810e77e5bbc2a36bf80daebc9650bc06227a9 [file] [edit]
// Copyright 2020-2025 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bufcheck
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"io/fs"
"log/slog"
"strings"
"buf.build/go/bufplugin/check"
"buf.build/go/bufplugin/descriptor"
"buf.build/go/bufplugin/option"
"buf.build/go/standard/xlog/xslog"
"buf.build/go/standard/xslices"
"buf.build/go/standard/xstrings"
"github.com/bufbuild/buf/private/bufpkg/bufanalysis"
"github.com/bufbuild/buf/private/bufpkg/bufcheck/bufcheckserver"
"github.com/bufbuild/buf/private/bufpkg/bufconfig"
"github.com/bufbuild/buf/private/bufpkg/bufimage"
"github.com/bufbuild/buf/private/bufpkg/bufparse"
"github.com/bufbuild/buf/private/bufpkg/bufplugin"
"github.com/bufbuild/buf/private/bufpkg/bufpolicy"
"github.com/bufbuild/buf/private/bufpkg/bufpolicy/bufpolicyconfig"
"github.com/bufbuild/buf/private/pkg/normalpath"
"github.com/bufbuild/buf/private/pkg/protosourcepath"
"github.com/bufbuild/buf/private/pkg/protoversion"
"github.com/bufbuild/buf/private/pkg/syserror"
"github.com/google/uuid"
"pluginrpc.com/pluginrpc"
)
type client struct {
logger *slog.Logger
stderr io.Writer
fileVersionToDefaultCheckClient map[bufconfig.FileVersion]check.Client
runnerProvider RunnerProvider
pluginReadFile func(string) ([]byte, error)
pluginKeyProvider bufplugin.PluginKeyProvider
pluginDataProvider bufplugin.PluginDataProvider
policyReadFile func(string) ([]byte, error)
policyKeyProvider bufpolicy.PolicyKeyProvider
policyDataProvider bufpolicy.PolicyDataProvider
policyPluginKeyProvider bufpolicy.PolicyPluginKeyProvider
policyPluginDataProvider bufpolicy.PolicyPluginDataProvider
}
func newClient(
logger *slog.Logger,
options ...ClientOption,
) (*client, error) {
clientOptions := newClientOptions()
for _, option := range options {
option(clientOptions)
}
// We want to keep our check.Clients static for caching instead of creating them on every lint and breaking call.
v1beta1DefaultCheckClient, err := check.NewClientForSpec(bufcheckserver.V1Beta1Spec, check.ClientWithCaching())
if err != nil {
return nil, syserror.Wrap(err)
}
v1DefaultCheckClient, err := check.NewClientForSpec(bufcheckserver.V1Spec, check.ClientWithCaching())
if err != nil {
return nil, syserror.Wrap(err)
}
v2DefaultCheckClient, err := check.NewClientForSpec(bufcheckserver.V2Spec, check.ClientWithCaching())
if err != nil {
return nil, syserror.Wrap(err)
}
return &client{
logger: logger,
stderr: clientOptions.stderr,
fileVersionToDefaultCheckClient: map[bufconfig.FileVersion]check.Client{
bufconfig.FileVersionV1Beta1: v1beta1DefaultCheckClient,
bufconfig.FileVersionV1: v1DefaultCheckClient,
bufconfig.FileVersionV2: v2DefaultCheckClient,
},
runnerProvider: clientOptions.runnerProvider,
pluginReadFile: clientOptions.pluginReadFile,
pluginKeyProvider: clientOptions.pluginKeyProvider,
pluginDataProvider: clientOptions.pluginDataProvider,
policyReadFile: clientOptions.policyReadFile,
policyKeyProvider: clientOptions.policyKeyProvider,
policyDataProvider: clientOptions.policyDataProvider,
policyPluginKeyProvider: clientOptions.policyPluginKeyProvider,
policyPluginDataProvider: clientOptions.policyPluginDataProvider,
}, nil
}
func (c *client) Lint(
ctx context.Context,
lintConfig bufconfig.LintConfig,
image bufimage.Image,
options ...LintOption,
) error {
defer xslog.DebugProfile(c.logger)()
lintOptions := newLintOptions()
for _, option := range options {
option.applyToLint(lintOptions)
}
// Run lint checks.
var annotations []*annotation
lintAnnotations, err := c.lint(
ctx,
image,
lintConfig,
lintOptions.pluginConfigs,
nil, // policyConfig.
lintOptions.relatedCheckConfigs,
len(lintOptions.policyConfigs) > 0, // hasPolicyConfigs.
)
if err != nil {
return err
}
annotations = append(annotations, lintAnnotations...)
// Run lint policy checks.
policies, err := c.getPolicies(ctx, lintOptions.policyConfigs)
if err != nil {
return err
}
for index, policy := range policies {
policyConfig := lintOptions.policyConfigs[index]
policyLintConfig, err := policyToBufConfigLintConfig(policy, policyConfig)
if err != nil {
return err
}
pluginConfigs, err := policyToBufConfigPluginConfigs(policy)
if err != nil {
return err
}
policyAnnotations, err := c.lint(
ctx,
image,
policyLintConfig,
pluginConfigs,
policyConfig,
nil, // relatedCheckConfigs.
true, // hasPolicyConfigs.
)
if err != nil {
return err
}
annotations = append(annotations, policyAnnotations...)
}
if len(annotations) == 0 {
return nil
}
return bufanalysis.NewFileAnnotationSet(
annotationsToFileAnnotations(
imageToPathToExternalPath(
image,
),
annotations,
)...,
)
}
func (c *client) lint(
ctx context.Context,
image bufimage.Image,
lintConfig bufconfig.LintConfig,
pluginConfigs []bufconfig.PluginConfig,
policyConfig bufconfig.PolicyConfig,
relatedCheckConfigs []bufconfig.CheckConfig,
hasPolicyConfigs bool,
) ([]*annotation, error) {
if lintConfig.Disabled() {
return nil, nil
}
allRules, allCategories, err := c.allRulesAndCategories(
ctx,
lintConfig.FileVersion(),
pluginConfigs,
policyConfig,
lintConfig.DisableBuiltin(),
)
if err != nil {
return nil, err
}
config, err := configForLintConfig(lintConfig, allRules, allCategories, relatedCheckConfigs)
if err != nil {
return nil, err
}
configName := bufconfig.DefaultBufYAMLFileName
if policyConfig != nil {
configName = policyConfig.Name()
}
logRulesConfig(c.logger, configName, config.rulesConfig, hasPolicyConfigs)
files, err := descriptor.FileDescriptorsForProtoFileDescriptors(imageToProtoFileDescriptors(image))
if err != nil {
// An Image may be invalid if it does not contain all of the required dependencies.
return nil, fmt.Errorf("input image: %w", err)
}
request, err := check.NewRequest(
files,
check.WithRuleIDs(config.RuleIDs...),
check.WithOptions(config.DefaultOptions),
)
if err != nil {
return nil, err
}
multiClient, err := c.getMultiClient(
ctx,
lintConfig.FileVersion(),
pluginConfigs,
policyConfig,
lintConfig.DisableBuiltin(),
config.DefaultOptions,
)
if err != nil {
return nil, err
}
annotations, err := multiClient.Check(ctx, request)
if err != nil {
return nil, err
}
if len(annotations) == 0 {
return nil, nil
}
return filterAnnotations(config, annotations)
}
func (c *client) Breaking(
ctx context.Context,
breakingConfig bufconfig.BreakingConfig,
image bufimage.Image,
againstImage bufimage.Image,
options ...BreakingOption,
) error {
defer xslog.DebugProfile(c.logger)()
breakingOptions := newBreakingOptions()
for _, option := range options {
option.applyToBreaking(breakingOptions)
}
// Run breaking checks.
var annotations []*annotation
breakingAnnotations, err := c.breaking(
ctx,
image,
againstImage,
breakingConfig,
breakingOptions.pluginConfigs,
nil, // policyConfig.
breakingOptions.excludeImports,
breakingOptions.relatedCheckConfigs,
len(breakingOptions.policyConfigs) > 0, // hasPolicyConfigs.
)
if err != nil {
return err
}
annotations = append(annotations, breakingAnnotations...)
// Run breaking policy checks.
policies, err := c.getPolicies(ctx, breakingOptions.policyConfigs)
if err != nil {
return err
}
for index, policy := range policies {
policyConfig := breakingOptions.policyConfigs[index]
policyBreakingConfig, err := policyToBufConfigBreakingConfig(policy, policyConfig)
if err != nil {
return err
}
pluginConfigs, err := policyToBufConfigPluginConfigs(policy)
if err != nil {
return err
}
policyAnnotations, err := c.breaking(
ctx,
image,
againstImage,
policyBreakingConfig,
pluginConfigs,
policyConfig,
breakingOptions.excludeImports,
nil, // relatedCheckConfigs.
true, // hasPolicyConfigs.
)
if err != nil {
return err
}
annotations = append(annotations, policyAnnotations...)
}
if len(annotations) == 0 {
return nil
}
return bufanalysis.NewFileAnnotationSet(
annotationsToFileAnnotations(
imageToPathToExternalPath(
image,
),
annotations,
)...,
)
}
func (c *client) breaking(
ctx context.Context,
image bufimage.Image,
againstImage bufimage.Image,
breakingConfig bufconfig.BreakingConfig,
pluginConfigs []bufconfig.PluginConfig,
policyConfig bufconfig.PolicyConfig,
excludeImports bool,
relatedCheckConfigs []bufconfig.CheckConfig,
hasPolicyConfigs bool,
) ([]*annotation, error) {
if breakingConfig.Disabled() {
return nil, nil
}
allRules, allCategories, err := c.allRulesAndCategories(
ctx,
breakingConfig.FileVersion(),
pluginConfigs,
policyConfig,
breakingConfig.DisableBuiltin(),
)
if err != nil {
return nil, err
}
config, err := configForBreakingConfig(
breakingConfig,
allRules,
allCategories,
excludeImports,
relatedCheckConfigs,
)
if err != nil {
return nil, err
}
configName := bufconfig.DefaultBufYAMLFileName
if policyConfig != nil {
configName = policyConfig.Name()
}
logRulesConfig(c.logger, configName, config.rulesConfig, hasPolicyConfigs)
fileDescriptors, err := descriptor.FileDescriptorsForProtoFileDescriptors(imageToProtoFileDescriptors(image))
if err != nil {
// An Image may be invalid if it does not contain all of the required dependencies.
return nil, fmt.Errorf("input image: %w", err)
}
againstFileDescriptors, err := descriptor.FileDescriptorsForProtoFileDescriptors(imageToProtoFileDescriptors(againstImage))
if err != nil {
// An Image may be invalid if it does not contain all of the required dependencies.
return nil, fmt.Errorf("against image: %w", err)
}
request, err := check.NewRequest(
fileDescriptors,
check.WithRuleIDs(config.RuleIDs...),
check.WithAgainstFileDescriptors(againstFileDescriptors),
check.WithOptions(config.DefaultOptions),
)
if err != nil {
return nil, err
}
multiClient, err := c.getMultiClient(
ctx,
breakingConfig.FileVersion(),
pluginConfigs,
policyConfig,
breakingConfig.DisableBuiltin(),
config.DefaultOptions,
)
if err != nil {
return nil, err
}
annotations, err := multiClient.Check(ctx, request)
if err != nil {
return nil, err
}
if len(annotations) == 0 {
return nil, nil
}
return filterAnnotations(config, annotations)
}
func (c *client) ConfiguredRules(
ctx context.Context,
ruleType check.RuleType,
checkConfig bufconfig.CheckConfig,
options ...ConfiguredRulesOption,
) ([]Rule, error) {
defer xslog.DebugProfile(c.logger)()
configuredRulesOptions := newConfiguredRulesOptions()
for _, option := range options {
option.applyToConfiguredRules(configuredRulesOptions)
}
rules, categories, err := c.allRulesAndCategories(
ctx,
checkConfig.FileVersion(),
configuredRulesOptions.pluginConfigs,
nil, // PolicyConfig.
checkConfig.DisableBuiltin(),
)
if err != nil {
return nil, err
}
rulesConfig, err := rulesConfigForCheckConfig(checkConfig, rules, categories, ruleType, configuredRulesOptions.relatedCheckConfigs)
if err != nil {
return nil, err
}
logRulesConfig(c.logger, "", rulesConfig, len(configuredRulesOptions.policyConfigs) > 0)
allRules := rulesForRuleIDs(rules, rulesConfig.RuleIDs)
policies, err := c.getPolicies(ctx, configuredRulesOptions.policyConfigs)
if err != nil {
return nil, err
}
for index, policy := range policies {
policyConfig := configuredRulesOptions.policyConfigs[index]
pluginConfigs, err := policyToBufConfigPluginConfigs(policy)
if err != nil {
return nil, err
}
// Load the check config for the rule type.
var policyCheckConfig bufconfig.CheckConfig
switch ruleType {
case check.RuleTypeLint:
policyCheckConfig, err = policyToBufConfigLintConfig(policy, policyConfig)
case check.RuleTypeBreaking:
policyCheckConfig, err = policyToBufConfigBreakingConfig(policy, policyConfig)
default:
return nil, fmt.Errorf("unknown check.RuleType: %v", ruleType)
}
if err != nil {
return nil, err
}
policyRules, policyCategories, err := c.allRulesAndCategories(ctx, policyCheckConfig.FileVersion(), pluginConfigs, policyConfig, false)
if err != nil {
return nil, err
}
policyRulesConfig, err := rulesConfigForCheckConfig(policyCheckConfig, policyRules, policyCategories, ruleType, nil)
if err != nil {
return nil, err
}
allRules = append(allRules, rulesForRuleIDs(policyRules, policyRulesConfig.RuleIDs)...)
}
return allRules, nil
}
func (c *client) AllRules(
ctx context.Context,
ruleType check.RuleType,
fileVersion bufconfig.FileVersion,
options ...AllRulesOption,
) ([]Rule, error) {
defer xslog.DebugProfile(c.logger)()
allRulesOptions := newAllRulesOptions()
for _, option := range options {
option.applyToAllRules(allRulesOptions)
}
rules, _, err := c.allRulesAndCategories(ctx, fileVersion, allRulesOptions.pluginConfigs, nil, false)
if err != nil {
return nil, err
}
policies, err := c.getPolicies(ctx, allRulesOptions.policyConfigs)
if err != nil {
return nil, err
}
for index, policy := range policies {
policyConfig := allRulesOptions.policyConfigs[index]
pluginConfigs, err := policyToBufConfigPluginConfigs(policy)
if err != nil {
return nil, err
}
policyRules, _, err := c.allRulesAndCategories(ctx, fileVersion, pluginConfigs, policyConfig, false)
if err != nil {
return nil, err
}
rules = append(rules, policyRules...)
}
return rulesForType(rules, ruleType), nil
}
func (c *client) AllCategories(
ctx context.Context,
fileVersion bufconfig.FileVersion,
options ...AllCategoriesOption,
) ([]Category, error) {
defer xslog.DebugProfile(c.logger)()
allCategoriesOptions := newAllCategoriesOptions()
for _, option := range options {
option.applyToAllCategories(allCategoriesOptions)
}
_, categories, err := c.allRulesAndCategories(ctx, fileVersion, allCategoriesOptions.pluginConfigs, nil, false)
return categories, err
}
func (c *client) allRulesAndCategories(
ctx context.Context,
fileVersion bufconfig.FileVersion,
pluginConfigs []bufconfig.PluginConfig,
policyConfig bufconfig.PolicyConfig, // May be nil.
disableBuiltin bool,
) ([]Rule, []Category, error) {
// Just passing through to fulfill all contracts, ie checkClientSpec has non-nil Options.
// Options are not used here.
// config struct really just needs refactoring.
multiClient, err := c.getMultiClient(ctx, fileVersion, pluginConfigs, policyConfig, disableBuiltin, option.EmptyOptions)
if err != nil {
return nil, nil, err
}
return multiClient.ListRulesAndCategories(ctx)
}
func (c *client) getMultiClient(
ctx context.Context,
fileVersion bufconfig.FileVersion,
pluginConfigs []bufconfig.PluginConfig,
policyConfig bufconfig.PolicyConfig,
disableBuiltin bool,
defaultOptions option.Options,
) (*multiClient, error) {
var policyConfigName string
if policyConfig != nil {
policyConfigName = policyConfig.Name()
}
var checkClientSpecs []*checkClientSpec
if !disableBuiltin {
defaultCheckClient, ok := c.fileVersionToDefaultCheckClient[fileVersion]
if !ok {
return nil, fmt.Errorf("unknown FileVersion: %v", fileVersion)
}
checkClientSpecs = append(
checkClientSpecs,
// We do not set PluginName for default check.Clients.
newCheckClientSpec("", policyConfigName, defaultCheckClient, defaultOptions),
)
}
plugins, err := c.getPlugins(ctx, pluginConfigs, policyConfig)
if err != nil {
return nil, err
}
for index, pluginConfig := range pluginConfigs {
options, err := option.NewOptions(pluginConfig.Options())
if err != nil {
return nil, fmt.Errorf("could not parse options for plugin %q: %w", pluginConfig.Name(), err)
}
if c.runnerProvider == nil {
return nil, fmt.Errorf("must set a RunnerProvider to use plugins")
}
runner, err := c.runnerProvider.NewRunner(plugins[index])
if err != nil {
return nil, fmt.Errorf("could not create runner for plugin %q: %w", pluginConfig.Name(), err)
}
checkClient := check.NewClient(
pluginrpc.NewClient(
runner,
pluginrpc.ClientWithStderr(c.stderr),
// We have to set binary as some things cannot be encoded as JSON.
// Example: google.protobuf.Timestamps with positive seconds and negative nanos.
// We still want to send this over the wire to lint.
//
// FormatBinary is the default, but we're just being explicit here.
pluginrpc.ClientWithFormat(pluginrpc.FormatBinary),
),
check.ClientWithCaching(),
)
checkClientSpecs = append(
checkClientSpecs,
newCheckClientSpec(pluginConfig.Name(), policyConfigName, checkClient, options),
)
}
return newMultiClient(c.logger, checkClientSpecs), nil
}
func (c *client) getPlugins(ctx context.Context, pluginConfigs []bufconfig.PluginConfig, policyConfig bufconfig.PolicyConfig) ([]bufplugin.Plugin, error) {
if len(pluginConfigs) == 0 {
return nil, nil
}
plugins := make([]bufplugin.Plugin, len(pluginConfigs))
var indexedPluginRefs []xslices.Indexed[bufparse.Ref]
for index, pluginConfig := range pluginConfigs {
switch pluginConfig.Type() {
case bufconfig.PluginConfigTypeLocal:
plugin, err := bufplugin.NewLocalPlugin(
pluginConfig.Name(),
pluginConfig.Args(),
)
if err != nil {
return nil, fmt.Errorf("could not create local Plugin %q: %w", pluginConfig.Name(), err)
}
plugins[index] = plugin
case bufconfig.PluginConfigTypeLocalWasm:
if c.pluginReadFile == nil {
// Local Wasm plugins are not supported without a pluginReadFile.
return nil, fmt.Errorf("unable to read local Wasm Plugin %q", pluginConfig.Name())
}
var pluginFullName bufparse.FullName
if ref := pluginConfig.Ref(); ref != nil {
pluginFullName = ref.FullName()
}
plugin, err := bufplugin.NewLocalWasmPlugin(
pluginFullName,
pluginConfig.Name(),
pluginConfig.Args(),
func() ([]byte, error) {
return c.pluginReadFile(pluginConfig.Name())
},
)
if err != nil {
return nil, err
}
plugins[index] = plugin
case bufconfig.PluginConfigTypeRemoteWasm:
pluginRef := pluginConfig.Ref()
if pluginRef == nil {
return nil, syserror.Newf("missing Ref for remote PluginConfig %q", pluginConfig.Name())
}
indexedPluginRefs = append(indexedPluginRefs, xslices.Indexed[bufparse.Ref]{
Value: pluginRef,
Index: index,
})
default:
return nil, fmt.Errorf("unknown PluginConfig type %q", pluginConfig.Type())
}
}
// Load the remote plugin data for each plugin ref.
if len(indexedPluginRefs) > 0 {
pluginKeyProvider := c.pluginKeyProvider
pluginDataProvider := c.pluginDataProvider
if policyConfig != nil {
// Resolve the Plugin providers for the policy config.
pluginKeyProvider = c.policyPluginKeyProvider.GetPluginKeyProviderForPolicy(policyConfig.Name())
pluginDataProvider = c.policyPluginDataProvider.GetPluginDataProviderForPolicy(policyConfig.Name())
}
pluginRefs := xslices.IndexedToValues(indexedPluginRefs)
pluginKeys, err := pluginKeyProvider.GetPluginKeysForPluginRefs(ctx, pluginRefs, bufplugin.DigestTypeP1)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
if policyConfig != nil {
return nil, fmt.Errorf("unable to resolve plugins for policy %q: %w", policyConfig.Name(), err)
}
return nil, fmt.Errorf("unable to resolve plugins: %w", err)
}
return nil, err
}
pluginDatas, err := pluginDataProvider.GetPluginDatasForPluginKeys(ctx, pluginKeys)
if err != nil {
return nil, err
}
if len(pluginDatas) != len(pluginRefs) {
return nil, syserror.Newf("expected %d PluginData, got %d", len(pluginRefs), len(pluginDatas))
}
for dataIndex, indexedPluginRef := range indexedPluginRefs {
pluginData := pluginDatas[dataIndex]
pluginRef := indexedPluginRef.Value
index := indexedPluginRef.Index
pluginConfig := pluginConfigs[index]
plugin, err := bufplugin.NewRemoteWasmPlugin(
pluginRef.FullName(),
pluginConfig.Args(),
pluginData.PluginKey().CommitID(),
pluginData.Data,
)
if err != nil {
return nil, fmt.Errorf("could not create remote Plugin %q: %w", pluginRef.String(), err)
}
plugins[index] = plugin
}
}
return plugins, nil
}
func (c *client) getPolicies(ctx context.Context, policyConfigs []bufconfig.PolicyConfig) ([]bufpolicy.Policy, error) {
if len(policyConfigs) == 0 {
return nil, nil
}
policies := make([]bufpolicy.Policy, len(policyConfigs))
var indexedPolicyRefs []xslices.Indexed[bufparse.Ref]
for index, policyConfig := range policyConfigs {
if ref := policyConfig.Ref(); ref != nil {
indexedPolicyRefs = append(indexedPolicyRefs, xslices.Indexed[bufparse.Ref]{
Value: ref,
Index: index,
})
continue
}
// Local policy config.
if c.policyReadFile == nil {
// Local policy configs are not supported without a policyReadFile.
return nil, fmt.Errorf("unable to read local Policy %q", policyConfig.Name())
}
policyData, err := c.policyReadFile(policyConfig.Name())
if err != nil {
return nil, fmt.Errorf("could not read local policy config %q: %w", policyConfig.Name(), err)
}
reader := bytes.NewReader(policyData)
policyFile, err := bufpolicyconfig.ReadBufPolicyYAMLFile(reader, policyConfig.Name())
if err != nil {
return nil, fmt.Errorf("could not read policy file %q: %w", policyConfig.Name(), err)
}
policy, err := bufpolicy.NewPolicy("", nil, policyConfig.Name(), uuid.Nil, policyFile.PolicyConfig)
if err != nil {
return nil, err
}
policies[index] = policy
}
// Load the remote policy data for each policy ref.
if len(indexedPolicyRefs) > 0 {
policyRefs := xslices.IndexedToValues(indexedPolicyRefs)
policyKeys, err := c.policyKeyProvider.GetPolicyKeysForPolicyRefs(ctx, policyRefs, bufpolicy.DigestTypeO1)
if err != nil {
return nil, fmt.Errorf("could not get PolicyKeys for PolicyRefs: %w", err)
}
policyDatas, err := c.policyDataProvider.GetPolicyDatasForPolicyKeys(ctx, policyKeys)
if err != nil {
return nil, fmt.Errorf("could not get PolicyDatas for PolicyKeys: %w", err)
}
if len(policyDatas) != len(policyRefs) {
return nil, syserror.Newf("expected %d PolicyData, got %d", len(policyRefs), len(policyDatas))
}
for dataIndex, indexedPolicyRef := range indexedPolicyRefs {
policyData := policyDatas[dataIndex]
policyKey := policyData.PolicyKey()
index := indexedPolicyRef.Index
policy, err := bufpolicy.NewPolicy("", policyKey.FullName(), policyKey.FullName().String(), policyKey.CommitID(), func() (bufpolicy.PolicyConfig, error) {
return policyData.Config()
})
if err != nil {
return nil, err
}
policies[index] = policy
}
}
return policies, nil
}
func filterAnnotations(
config *config,
annotations []*annotation,
) ([]*annotation, error) {
return xslices.FilterError(
annotations,
func(annotation *annotation) (bool, error) {
ignore, err := ignoreAnnotation(config, annotation)
if err != nil {
return false, err
}
return !ignore, nil
},
)
}
func ignoreAnnotation(
config *config,
annotation *annotation,
) (bool, error) {
if fileLocation := annotation.FileLocation(); fileLocation != nil {
ignore, err := ignoreFileLocation(config, annotation.RuleID(), fileLocation)
if err != nil {
return false, err
}
if ignore {
return true, nil
}
}
if againstFileLocation := annotation.AgainstFileLocation(); againstFileLocation != nil {
return ignoreFileLocation(config, annotation.RuleID(), againstFileLocation)
}
return false, nil
}
func ignoreFileLocation(
config *config,
ruleID string,
fileLocation descriptor.FileLocation,
) (bool, error) {
fileDescriptor := fileLocation.FileDescriptor()
if config.ExcludeImports && fileDescriptor.IsImport() {
return true, nil
}
protoreflectFileDescriptor := fileDescriptor.ProtoreflectFileDescriptor()
path := protoreflectFileDescriptor.Path()
if normalpath.MapHasEqualOrContainingPath(config.IgnoreRootPaths, path, normalpath.Relative) {
return true, nil
}
// If the config says to ignore this specific rule for this path, ignore this location, otherwise we look for other forms of ignores.
if ignoreRootPaths, ok := config.IgnoreRuleIDToRootPaths[ruleID]; ok && normalpath.MapHasEqualOrContainingPath(ignoreRootPaths, path, normalpath.Relative) {
return true, nil
}
// Not a great design, but will never be triggered by lint since this is never set.
if config.IgnoreUnstablePackages {
if packageVersion, ok := protoversion.NewPackageVersionForPackage(string(protoreflectFileDescriptor.Package())); ok {
if packageVersion.StabilityLevel() != protoversion.StabilityLevelStable {
return true, nil
}
}
}
// Not a great design, but will never be triggered by breaking since this is never set.
// Therefore, never called for an againstLocation (since lint never has againstLocations).
if config.AllowCommentIgnores && config.CommentIgnorePrefix != "" {
sourcePath := fileLocation.SourcePath()
if len(sourcePath) == 0 {
return false, nil
}
associatedSourcePaths, err := protosourcepath.GetAssociatedSourcePaths(sourcePath)
if err != nil {
return false, err
}
sourceLocations := protoreflectFileDescriptor.SourceLocations()
for _, associatedSourcePath := range associatedSourcePaths {
sourceLocation := sourceLocations.ByPath(associatedSourcePath)
if leadingComments := sourceLocation.LeadingComments; leadingComments != "" {
for _, line := range xstrings.SplitTrimLinesNoEmpty(leadingComments) {
if checkCommentLineForCheckIgnore(line, config.CommentIgnorePrefix, ruleID) {
return true, nil
}
}
}
}
}
return false, nil
}
// checkCommentLineForCheckIgnore checks that the comment line starts with the configured
// comment ignore prefix, a space and the ruleID of the check.
//
// All of the following comments are valid, ignoring SERVICE_PASCAL_CASE and this rule only:
//
// // buf:lint:ignore SERVICE_PASCAL_CASE, SERVICE_SUFFIX (only SERVICE_PASCAL_CASE is ignored)
// // buf:lint:ignore SERVICE_PASCAL_CASE
// // buf:lint:ignore SERVICE_PASCAL_CASEsome other comment
// // buf:lint:ignore SERVICE_PASCAL_CASE some other comment
//
// While the following is invalid and a nop
//
// // buf:lint:ignoreSERVICE_PASCAL_CASE
func checkCommentLineForCheckIgnore(
commentLine string,
commentIgnorePrefix string,
ruleID string,
) bool {
fullIgnorePrefix := commentIgnorePrefix + " " + ruleID
return strings.HasPrefix(commentLine, fullIgnorePrefix)
}
type lintOptions struct {
pluginConfigs []bufconfig.PluginConfig
policyConfigs []bufconfig.PolicyConfig
relatedCheckConfigs []bufconfig.CheckConfig
}
func newLintOptions() *lintOptions {
return &lintOptions{}
}
type breakingOptions struct {
pluginConfigs []bufconfig.PluginConfig
policyConfigs []bufconfig.PolicyConfig
excludeImports bool
relatedCheckConfigs []bufconfig.CheckConfig
}
func newBreakingOptions() *breakingOptions {
return &breakingOptions{}
}
type configuredRulesOptions struct {
pluginConfigs []bufconfig.PluginConfig
policyConfigs []bufconfig.PolicyConfig
relatedCheckConfigs []bufconfig.CheckConfig
}
func newConfiguredRulesOptions() *configuredRulesOptions {
return &configuredRulesOptions{}
}
type allRulesOptions struct {
pluginConfigs []bufconfig.PluginConfig
policyConfigs []bufconfig.PolicyConfig
}
func newAllRulesOptions() *allRulesOptions {
return &allRulesOptions{}
}
type allCategoriesOptions struct {
pluginConfigs []bufconfig.PluginConfig
policyConfigs []bufconfig.PolicyConfig
}
func newAllCategoriesOptions() *allCategoriesOptions {
return &allCategoriesOptions{}
}
type clientOptions struct {
stderr io.Writer
runnerProvider RunnerProvider
pluginReadFile func(string) ([]byte, error)
pluginKeyProvider bufplugin.PluginKeyProvider
pluginDataProvider bufplugin.PluginDataProvider
policyReadFile func(string) ([]byte, error)
policyKeyProvider bufpolicy.PolicyKeyProvider
policyDataProvider bufpolicy.PolicyDataProvider
policyPluginKeyProvider bufpolicy.PolicyPluginKeyProvider
policyPluginDataProvider bufpolicy.PolicyPluginDataProvider
}
func newClientOptions() *clientOptions {
return &clientOptions{
pluginKeyProvider: bufplugin.NopPluginKeyProvider,
pluginDataProvider: bufplugin.NopPluginDataProvider,
policyKeyProvider: bufpolicy.NopPolicyKeyProvider,
policyDataProvider: bufpolicy.NopPolicyDataProvider,
policyPluginKeyProvider: bufpolicy.NopPolicyPluginKeyProvider,
policyPluginDataProvider: bufpolicy.NopPolicyPluginDataProvider,
}
}
type excludeImportsOption struct{}
func (e *excludeImportsOption) applyToBreaking(breakingOptions *breakingOptions) {
breakingOptions.excludeImports = true
}
type pluginConfigsOption struct {
pluginConfigs []bufconfig.PluginConfig
}
func (p *pluginConfigsOption) applyToLint(lintOptions *lintOptions) {
lintOptions.pluginConfigs = append(lintOptions.pluginConfigs, p.pluginConfigs...)
}
func (p *pluginConfigsOption) applyToBreaking(breakingOptions *breakingOptions) {
breakingOptions.pluginConfigs = append(breakingOptions.pluginConfigs, p.pluginConfigs...)
}
func (p *pluginConfigsOption) applyToConfiguredRules(configuredRulesOptions *configuredRulesOptions) {
configuredRulesOptions.pluginConfigs = append(configuredRulesOptions.pluginConfigs, p.pluginConfigs...)
}
func (p *pluginConfigsOption) applyToAllRules(allRulesOptions *allRulesOptions) {
allRulesOptions.pluginConfigs = append(allRulesOptions.pluginConfigs, p.pluginConfigs...)
}
func (p *pluginConfigsOption) applyToAllCategories(allCategoriesOptions *allCategoriesOptions) {
allCategoriesOptions.pluginConfigs = append(allCategoriesOptions.pluginConfigs, p.pluginConfigs...)
}
type policyConfigsOption struct {
policyConfigs []bufconfig.PolicyConfig
}
func (p *policyConfigsOption) applyToLint(lintOptions *lintOptions) {
lintOptions.policyConfigs = append(lintOptions.policyConfigs, p.policyConfigs...)
}
func (p *policyConfigsOption) applyToBreaking(breakingOptions *breakingOptions) {
breakingOptions.policyConfigs = append(breakingOptions.policyConfigs, p.policyConfigs...)
}
func (p *policyConfigsOption) applyToConfiguredRules(configuredRulesOptions *configuredRulesOptions) {
configuredRulesOptions.policyConfigs = append(configuredRulesOptions.policyConfigs, p.policyConfigs...)
}
func (p *policyConfigsOption) applyToAllRules(allRulesOptions *allRulesOptions) {
allRulesOptions.policyConfigs = append(allRulesOptions.policyConfigs, p.policyConfigs...)
}
func (p *policyConfigsOption) applyToAllCategories(allCategoriesOptions *allCategoriesOptions) {
allCategoriesOptions.policyConfigs = append(allCategoriesOptions.policyConfigs, p.policyConfigs...)
}
type relatedCheckConfigsOption struct {
relatedCheckConfigs []bufconfig.CheckConfig
}
func (r *relatedCheckConfigsOption) applyToLint(lintOptions *lintOptions) {
lintOptions.relatedCheckConfigs = append(lintOptions.relatedCheckConfigs, r.relatedCheckConfigs...)
}
func (r *relatedCheckConfigsOption) applyToBreaking(breakingOptions *breakingOptions) {
breakingOptions.relatedCheckConfigs = append(breakingOptions.relatedCheckConfigs, r.relatedCheckConfigs...)
}
func (r *relatedCheckConfigsOption) applyToConfiguredRules(configuredRulesOptions *configuredRulesOptions) {
configuredRulesOptions.relatedCheckConfigs = append(configuredRulesOptions.relatedCheckConfigs, r.relatedCheckConfigs...)
}