github.com/googleapis/api-linter@v1.65.2/lint/config.go (about) 1 // Copyright 2019 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package lint 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "io" 21 "os" 22 "path/filepath" 23 "strings" 24 25 "github.com/bmatcuk/doublestar/v4" 26 "gopkg.in/yaml.v3" 27 ) 28 29 // Configs determine if a rule is enabled or not on a file path. 30 type Configs []Config 31 32 // Config stores rule configurations for certain files 33 // that the file path must match any of the included paths 34 // but none of the excluded ones. 35 type Config struct { 36 IncludedPaths []string `json:"included_paths" yaml:"included_paths"` 37 ExcludedPaths []string `json:"excluded_paths" yaml:"excluded_paths"` 38 EnabledRules []string `json:"enabled_rules" yaml:"enabled_rules"` 39 DisabledRules []string `json:"disabled_rules" yaml:"disabled_rules"` 40 } 41 42 // ReadConfigsFromFile reads Configs from a file. 43 // It supports JSON(.json) and YAML(.yaml or .yml) files. 44 func ReadConfigsFromFile(path string) (Configs, error) { 45 var parse func(io.Reader) (Configs, error) 46 switch filepath.Ext(path) { 47 case ".json": 48 parse = ReadConfigsJSON 49 case ".yaml", ".yml": 50 parse = ReadConfigsYAML 51 } 52 if parse == nil { 53 return nil, fmt.Errorf("reading Configs: unsupported format `%q` with file path `%q`", filepath.Ext(path), path) 54 } 55 56 f, err := os.Open(path) 57 if err != nil { 58 return nil, fmt.Errorf("readConfig: %s", err.Error()) 59 } 60 defer f.Close() 61 62 return parse(f) 63 } 64 65 // ReadConfigsJSON reads Configs from a JSON file. 66 func ReadConfigsJSON(f io.Reader) (Configs, error) { 67 b, err := io.ReadAll(f) 68 if err != nil { 69 return nil, err 70 } 71 var c Configs 72 if err := json.Unmarshal(b, &c); err != nil { 73 return nil, err 74 } 75 return c, nil 76 } 77 78 // ReadConfigsYAML reads Configs from a YAML(.yml or .yaml) file. 79 func ReadConfigsYAML(f io.Reader) (Configs, error) { 80 b, err := io.ReadAll(f) 81 if err != nil { 82 return nil, err 83 } 84 var c Configs 85 if err := yaml.Unmarshal(b, &c); err != nil { 86 return nil, err 87 } 88 return c, nil 89 } 90 91 // IsRuleEnabled returns true if a rule is enabled by the configs. 92 func (configs Configs) IsRuleEnabled(rule string, path string) bool { 93 // Enabled by default if the rule does not belong to one of the default 94 // disabled groups. Otherwise, needs to be explicitly enabled. 95 enabled := !matchRule(rule, defaultDisabledRules...) 96 for _, c := range configs { 97 if c.matchPath(path) { 98 if matchRule(rule, c.DisabledRules...) { 99 enabled = false 100 } 101 if matchRule(rule, c.EnabledRules...) { 102 enabled = true 103 } 104 } 105 } 106 107 return enabled 108 } 109 110 func (c Config) matchPath(path string) bool { 111 if matchPath(path, c.ExcludedPaths...) { 112 return false 113 } 114 return len(c.IncludedPaths) == 0 || matchPath(path, c.IncludedPaths...) 115 } 116 117 func matchPath(path string, pathPatterns ...string) bool { 118 for _, pattern := range pathPatterns { 119 if matched, _ := doublestar.Match(pattern, path); matched { 120 return true 121 } 122 } 123 return false 124 } 125 126 func matchRule(rule string, rulePrefixes ...string) bool { 127 rule = strings.ToLower(rule) 128 for _, prefix := range rulePrefixes { 129 prefix = strings.ToLower(prefix) 130 prefix = strings.TrimSuffix(prefix, nameSeparator) // "core::" -> "core" 131 prefix = strings.TrimPrefix(prefix, nameSeparator) // "::http-body" -> "http-body" 132 if prefix == "all" || 133 prefix == rule || 134 strings.HasPrefix(rule, prefix+nameSeparator) || // e.g., "core" matches "core::http-body", but not "core-rules::http-body" 135 strings.HasSuffix(rule, nameSeparator+prefix) || // e.g., "http-body" matches "core::http-body", but not "core::google-http-body" 136 strings.Contains(rule, nameSeparator+prefix+nameSeparator) { // e.g., "http-body" matches "core::http-body::post", but not "core::google-http-body::post" 137 return true 138 } 139 } 140 return false 141 }