blob: a7e6a4c579d5d290c4bad769ffba98fc152e9296 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You 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 buflintconfig
import (
"bytes"
"encoding/json"
"io"
"sort"
"strings"
)
import (
"github.com/apache/dubbo-kubernetes/pkg/bufman/bufpkg/bufanalysis"
lintv1 "github.com/apache/dubbo-kubernetes/pkg/bufman/gen/proto/go/lint/v1"
)
const (
// These versions match the versions in bufconfig. We cannot take an explicit dependency
// on bufconfig without creating a circular dependency.
v1Beta1Version = "v1beta1"
v1Version = "v1"
)
// Config is the lint check config.
type Config struct {
// Use is a list of rule and/or category IDs that are included in the lint check.
Use []string
// Except is a list of the rule and/or category IDs that are excluded from the lint check.
Except []string
// IgnoreRootPaths is a list of paths of directories and/or files that should be ignored by the lint check.
// All paths are relative to the root of the module.
IgnoreRootPaths []string
// IgnoreIDOrCategoryToRootPaths is a map of rule and/or category IDs to directory and/or file paths to exclude from the
// lint check.
IgnoreIDOrCategoryToRootPaths map[string][]string
// EnumZeroValueSuffix controls the behavior of the ENUM_ZERO_VALUE lint rule ID. By default, this rule
// verifies that the zero value of all enums ends in _UNSPECIFIED. This config allows the user to override
// this value with the given string.
EnumZeroValueSuffix string
// RPCAllowSameRequestResponse allows the same message type for both the request and response of an RPC.
RPCAllowSameRequestResponse bool
// RPCAllowGoogleProtobufEmptyRequests allows the RPC requests to use the google.protobuf.Empty message.
RPCAllowGoogleProtobufEmptyRequests bool
// RPCAllowGoogleProtobufEmptyResponse allows the RPC responses to use the google.protobuf.Empty message.
RPCAllowGoogleProtobufEmptyResponses bool
// ServiceSuffix applies to the SERVICE_SUFFIX rule ID. By default, the rule verifies that all service names
// end with the suffix Service. This allows users to override the value with the given string.
ServiceSuffix string
// AllowCommentIgnores turns on comment-driven ignores.
AllowCommentIgnores bool
// Version represents the version of the lint rule and category IDs that should be used with this config.
Version string
}
// NewConfigV1Beta1 returns a new Config.
func NewConfigV1Beta1(externalConfig ExternalConfigV1Beta1) *Config {
return &Config{
Use: externalConfig.Use,
Except: externalConfig.Except,
IgnoreRootPaths: externalConfig.Ignore,
IgnoreIDOrCategoryToRootPaths: externalConfig.IgnoreOnly,
EnumZeroValueSuffix: externalConfig.EnumZeroValueSuffix,
RPCAllowSameRequestResponse: externalConfig.RPCAllowSameRequestResponse,
RPCAllowGoogleProtobufEmptyRequests: externalConfig.RPCAllowGoogleProtobufEmptyRequests,
RPCAllowGoogleProtobufEmptyResponses: externalConfig.RPCAllowGoogleProtobufEmptyResponses,
ServiceSuffix: externalConfig.ServiceSuffix,
AllowCommentIgnores: externalConfig.AllowCommentIgnores,
Version: v1Beta1Version,
}
}
// NewConfigV1 returns a new Config.
func NewConfigV1(externalConfig ExternalConfigV1) *Config {
return &Config{
Use: externalConfig.Use,
Except: externalConfig.Except,
IgnoreRootPaths: externalConfig.Ignore,
IgnoreIDOrCategoryToRootPaths: externalConfig.IgnoreOnly,
EnumZeroValueSuffix: externalConfig.EnumZeroValueSuffix,
RPCAllowSameRequestResponse: externalConfig.RPCAllowSameRequestResponse,
RPCAllowGoogleProtobufEmptyRequests: externalConfig.RPCAllowGoogleProtobufEmptyRequests,
RPCAllowGoogleProtobufEmptyResponses: externalConfig.RPCAllowGoogleProtobufEmptyResponses,
ServiceSuffix: externalConfig.ServiceSuffix,
AllowCommentIgnores: externalConfig.AllowCommentIgnores,
Version: v1Version,
}
}
// ConfigForProto returns the Config given the proto.
func ConfigForProto(protoConfig *lintv1.Config) *Config {
return &Config{
Use: protoConfig.GetUseIds(),
Except: protoConfig.GetExceptIds(),
IgnoreRootPaths: protoConfig.GetIgnorePaths(),
IgnoreIDOrCategoryToRootPaths: ignoreIDOrCategoryToRootPathsForProto(protoConfig.GetIgnoreIdPaths()),
EnumZeroValueSuffix: protoConfig.GetEnumZeroValueSuffix(),
RPCAllowSameRequestResponse: protoConfig.GetRpcAllowSameRequestResponse(),
RPCAllowGoogleProtobufEmptyRequests: protoConfig.GetRpcAllowGoogleProtobufEmptyRequests(),
RPCAllowGoogleProtobufEmptyResponses: protoConfig.GetRpcAllowGoogleProtobufEmptyResponses(),
ServiceSuffix: protoConfig.GetServiceSuffix(),
AllowCommentIgnores: protoConfig.GetAllowCommentIgnores(),
Version: protoConfig.GetVersion(),
}
}
// ProtoForConfig takes a *Config and returns the proto representation.
func ProtoForConfig(config *Config) *lintv1.Config {
return &lintv1.Config{
UseIds: config.Use,
ExceptIds: config.Except,
IgnorePaths: config.IgnoreRootPaths,
IgnoreIdPaths: protoForIgnoreIDOrCategoryToRootPaths(config.IgnoreIDOrCategoryToRootPaths),
EnumZeroValueSuffix: config.EnumZeroValueSuffix,
RpcAllowSameRequestResponse: config.RPCAllowSameRequestResponse,
RpcAllowGoogleProtobufEmptyRequests: config.RPCAllowGoogleProtobufEmptyRequests,
RpcAllowGoogleProtobufEmptyResponses: config.RPCAllowGoogleProtobufEmptyResponses,
ServiceSuffix: config.ServiceSuffix,
AllowCommentIgnores: config.AllowCommentIgnores,
Version: config.Version,
}
}
// ExternalConfigV1Beta1 is an external config.
type ExternalConfigV1Beta1 struct {
Use []string `json:"use,omitempty" yaml:"use,omitempty"`
Except []string `json:"except,omitempty" yaml:"except,omitempty"`
// IgnoreRootPaths
Ignore []string `json:"ignore,omitempty" yaml:"ignore,omitempty"`
// IgnoreIDOrCategoryToRootPaths
IgnoreOnly map[string][]string `json:"ignore_only,omitempty" yaml:"ignore_only,omitempty"`
EnumZeroValueSuffix string `json:"enum_zero_value_suffix,omitempty" yaml:"enum_zero_value_suffix,omitempty"`
RPCAllowSameRequestResponse bool `json:"rpc_allow_same_request_response,omitempty" yaml:"rpc_allow_same_request_response,omitempty"`
RPCAllowGoogleProtobufEmptyRequests bool `json:"rpc_allow_google_protobuf_empty_requests,omitempty" yaml:"rpc_allow_google_protobuf_empty_requests,omitempty"`
RPCAllowGoogleProtobufEmptyResponses bool `json:"rpc_allow_google_protobuf_empty_responses,omitempty" yaml:"rpc_allow_google_protobuf_empty_responses,omitempty"`
ServiceSuffix string `json:"service_suffix,omitempty" yaml:"service_suffix,omitempty"`
AllowCommentIgnores bool `json:"allow_comment_ignores,omitempty" yaml:"allow_comment_ignores,omitempty"`
}
// ExternalConfigV1 is an external config.
type ExternalConfigV1 struct {
Use []string `json:"use,omitempty" yaml:"use,omitempty"`
Except []string `json:"except,omitempty" yaml:"except,omitempty"`
// IgnoreRootPaths
Ignore []string `json:"ignore,omitempty" yaml:"ignore,omitempty"`
// IgnoreIDOrCategoryToRootPaths
IgnoreOnly map[string][]string `json:"ignore_only,omitempty" yaml:"ignore_only,omitempty"`
EnumZeroValueSuffix string `json:"enum_zero_value_suffix,omitempty" yaml:"enum_zero_value_suffix,omitempty"`
RPCAllowSameRequestResponse bool `json:"rpc_allow_same_request_response,omitempty" yaml:"rpc_allow_same_request_response,omitempty"`
RPCAllowGoogleProtobufEmptyRequests bool `json:"rpc_allow_google_protobuf_empty_requests,omitempty" yaml:"rpc_allow_google_protobuf_empty_requests,omitempty"`
RPCAllowGoogleProtobufEmptyResponses bool `json:"rpc_allow_google_protobuf_empty_responses,omitempty" yaml:"rpc_allow_google_protobuf_empty_responses,omitempty"`
ServiceSuffix string `json:"service_suffix,omitempty" yaml:"service_suffix,omitempty"`
AllowCommentIgnores bool `json:"allow_comment_ignores,omitempty" yaml:"allow_comment_ignores,omitempty"`
}
// ExternalConfigV1Beta1ForConfig takes a *Config and returns the v1beta1 externalconfig representation.
func ExternalConfigV1Beta1ForConfig(config *Config) ExternalConfigV1Beta1 {
return ExternalConfigV1Beta1{
Use: config.Use,
Except: config.Except,
Ignore: config.IgnoreRootPaths,
IgnoreOnly: config.IgnoreIDOrCategoryToRootPaths,
EnumZeroValueSuffix: config.EnumZeroValueSuffix,
RPCAllowSameRequestResponse: config.RPCAllowSameRequestResponse,
RPCAllowGoogleProtobufEmptyRequests: config.RPCAllowGoogleProtobufEmptyRequests,
RPCAllowGoogleProtobufEmptyResponses: config.RPCAllowGoogleProtobufEmptyResponses,
ServiceSuffix: config.ServiceSuffix,
AllowCommentIgnores: config.AllowCommentIgnores,
}
}
// ExternalConfigV1ForConfig takes a *Config and returns the v1 externalconfig representation.
func ExternalConfigV1ForConfig(config *Config) ExternalConfigV1 {
return ExternalConfigV1{
Use: config.Use,
Except: config.Except,
Ignore: config.IgnoreRootPaths,
IgnoreOnly: config.IgnoreIDOrCategoryToRootPaths,
EnumZeroValueSuffix: config.EnumZeroValueSuffix,
RPCAllowSameRequestResponse: config.RPCAllowSameRequestResponse,
RPCAllowGoogleProtobufEmptyRequests: config.RPCAllowGoogleProtobufEmptyRequests,
RPCAllowGoogleProtobufEmptyResponses: config.RPCAllowGoogleProtobufEmptyResponses,
ServiceSuffix: config.ServiceSuffix,
AllowCommentIgnores: config.AllowCommentIgnores,
}
}
// BytesForConfig takes a *Config and returns the deterministic []byte representation.
// We use an unexported intermediary JSON form and sort all fields to ensure that the bytes
// associated with the *Config are deterministic.
func BytesForConfig(config *Config) ([]byte, error) {
if config == nil {
return nil, nil
}
return json.Marshal(configToJSON(config))
}
type configJSON struct {
Use []string `json:"use,omitempty"`
Except []string `json:"except,omitempty"`
IgnoreRootPaths []string `json:"ignore_root_paths,omitempty"`
IgnoreIDOrCategoryToRootPaths []idPathsJSON `json:"ignore_id_to_root_paths,omitempty"`
EnumZeroValueSuffix string `json:"enum_zero_value_suffix,omitempty"`
RPCAllowSameRequestResponse bool `json:"rpc_allow_same_request_response,omitempty"`
RPCAllowGoogleProtobufEmptyRequests bool `json:"rpc_allow_google_protobuf_empty_requests,omitempty"`
RPCAllowGoogleProtobufEmptyResponses bool `json:"rpc_allow_google_protobuf_empty_response,omitempty"`
ServiceSuffix string `json:"service_suffix,omitempty"`
AllowCommentIgnores bool `json:"allow_comment_ignores,omitempty"`
Version string `json:"version,omitempty"`
}
type idPathsJSON struct {
ID string `json:"id,omitempty"`
Paths []string `json:"paths,omitempty"`
}
func configToJSON(config *Config) *configJSON {
ignoreIDPathsJSON := make([]idPathsJSON, 0, len(config.IgnoreIDOrCategoryToRootPaths))
for ignoreID, rootPaths := range config.IgnoreIDOrCategoryToRootPaths {
rootPathsCopy := make([]string, len(rootPaths))
copy(rootPathsCopy, rootPaths)
sort.Strings(rootPathsCopy)
ignoreIDPathsJSON = append(ignoreIDPathsJSON, idPathsJSON{
ID: ignoreID,
Paths: rootPathsCopy,
})
}
sort.Slice(ignoreIDPathsJSON, func(i, j int) bool { return ignoreIDPathsJSON[i].ID < ignoreIDPathsJSON[j].ID })
// We should not be sorting in place for the config structure, since it will mutate the
// underlying config ordering.
use := make([]string, len(config.Use))
copy(use, config.Use)
except := make([]string, len(config.Except))
copy(except, config.Except)
ignoreRootPaths := make([]string, len(config.IgnoreRootPaths))
copy(ignoreRootPaths, config.IgnoreRootPaths)
sort.Strings(use)
sort.Strings(except)
sort.Strings(ignoreRootPaths)
return &configJSON{
Use: use,
Except: except,
IgnoreRootPaths: ignoreRootPaths,
IgnoreIDOrCategoryToRootPaths: ignoreIDPathsJSON,
EnumZeroValueSuffix: config.EnumZeroValueSuffix,
RPCAllowSameRequestResponse: config.RPCAllowSameRequestResponse,
RPCAllowGoogleProtobufEmptyRequests: config.RPCAllowGoogleProtobufEmptyRequests,
RPCAllowGoogleProtobufEmptyResponses: config.RPCAllowGoogleProtobufEmptyResponses,
ServiceSuffix: config.ServiceSuffix,
AllowCommentIgnores: config.AllowCommentIgnores,
Version: config.Version,
}
}
// PrintFileAnnotations prints the FileAnnotations to the Writer.
//
// Also accepts config-ignore-yaml.
func PrintFileAnnotations(
writer io.Writer,
fileAnnotations []bufanalysis.FileAnnotation,
formatString string,
) error {
switch s := strings.ToLower(strings.TrimSpace(formatString)); s {
case "config-ignore-yaml":
return printFileAnnotationsConfigIgnoreYAML(writer, fileAnnotations)
default:
return bufanalysis.PrintFileAnnotations(writer, fileAnnotations, s)
}
}
func printFileAnnotationsConfigIgnoreYAML(
writer io.Writer,
fileAnnotations []bufanalysis.FileAnnotation,
) error {
if len(fileAnnotations) == 0 {
return nil
}
ignoreIDToRootPathMap := make(map[string]map[string]struct{})
for _, fileAnnotation := range fileAnnotations {
fileInfo := fileAnnotation.FileInfo()
if fileInfo == nil || fileAnnotation.Type() == "" {
continue
}
rootPathMap, ok := ignoreIDToRootPathMap[fileAnnotation.Type()]
if !ok {
rootPathMap = make(map[string]struct{})
ignoreIDToRootPathMap[fileAnnotation.Type()] = rootPathMap
}
rootPathMap[fileInfo.Path()] = struct{}{}
}
if len(ignoreIDToRootPathMap) == 0 {
return nil
}
sortedIgnoreIDs := make([]string, 0, len(ignoreIDToRootPathMap))
ignoreIDToSortedRootPaths := make(map[string][]string, len(ignoreIDToRootPathMap))
for id, rootPathMap := range ignoreIDToRootPathMap {
sortedIgnoreIDs = append(sortedIgnoreIDs, id)
rootPaths := make([]string, 0, len(rootPathMap))
for rootPath := range rootPathMap {
rootPaths = append(rootPaths, rootPath)
}
sort.Strings(rootPaths)
ignoreIDToSortedRootPaths[id] = rootPaths
}
sort.Strings(sortedIgnoreIDs)
buffer := bytes.NewBuffer(nil)
_, _ = buffer.WriteString(`version: v1
lint:
ignore_only:
`)
for _, id := range sortedIgnoreIDs {
_, _ = buffer.WriteString(" ")
_, _ = buffer.WriteString(id)
_, _ = buffer.WriteString(":\n")
for _, rootPath := range ignoreIDToSortedRootPaths[id] {
_, _ = buffer.WriteString(" - ")
_, _ = buffer.WriteString(rootPath)
_, _ = buffer.WriteString("\n")
}
}
_, err := writer.Write(buffer.Bytes())
return err
}
func ignoreIDOrCategoryToRootPathsForProto(protoIgnoreIDPaths []*lintv1.IDPaths) map[string][]string {
if protoIgnoreIDPaths == nil {
return nil
}
ignoreIDOrCategoryToRootPaths := make(map[string][]string)
for _, protoIgnoreIDPath := range protoIgnoreIDPaths {
ignoreIDOrCategoryToRootPaths[protoIgnoreIDPath.GetId()] = protoIgnoreIDPath.GetPaths()
}
return ignoreIDOrCategoryToRootPaths
}
func protoForIgnoreIDOrCategoryToRootPaths(ignoreIDOrCategoryToRootPaths map[string][]string) []*lintv1.IDPaths {
if ignoreIDOrCategoryToRootPaths == nil {
return nil
}
idPathsProto := make([]*lintv1.IDPaths, 0, len(ignoreIDOrCategoryToRootPaths))
for id, paths := range ignoreIDOrCategoryToRootPaths {
idPathsProto = append(idPathsProto, &lintv1.IDPaths{
Id: id,
Paths: paths,
})
}
return idPathsProto
}