github.com/googleapis/api-linter@v1.65.2/lint/lint.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 provides lint functions for Google APIs that register rules and user configurations, 16 // apply those rules to a lint request, and produce lint results. 17 package lint 18 19 import ( 20 "errors" 21 "fmt" 22 "runtime/debug" 23 "strings" 24 25 "github.com/jhump/protoreflect/desc" 26 ) 27 28 // Linter checks API files and returns a list of detected problems. 29 type Linter struct { 30 rules RuleRegistry 31 configs Configs 32 debug bool 33 ignoreCommentDisables bool 34 } 35 36 // LinterOption prvoides the ability to configure the Linter. 37 type LinterOption func(l *Linter) 38 39 // Debug is a LinterOption for setting if debug mode is on. 40 func Debug(debug bool) LinterOption { 41 return func(l *Linter) { 42 l.debug = debug 43 } 44 } 45 46 // IgnoreCommentDisables sets the flag for ignoring comments which disable rules. 47 func IgnoreCommentDisables(ignoreCommentDisables bool) LinterOption { 48 return func(l *Linter) { 49 l.ignoreCommentDisables = ignoreCommentDisables 50 } 51 } 52 53 // New creates and returns a linter with the given rules and configs. 54 func New(rules RuleRegistry, configs Configs, opts ...LinterOption) *Linter { 55 l := &Linter{ 56 rules: rules, 57 configs: configs, 58 } 59 60 for _, opt := range opts { 61 opt(l) 62 } 63 64 return l 65 } 66 67 // LintProtos checks protobuf files and returns a list of problems or an error. 68 func (l *Linter) LintProtos(files ...*desc.FileDescriptor) ([]Response, error) { 69 var responses []Response 70 for _, proto := range files { 71 resp, err := l.lintFileDescriptor(proto) 72 if err != nil { 73 return nil, err 74 } 75 responses = append(responses, resp) 76 } 77 return responses, nil 78 } 79 80 // run executes rules on the request. 81 // 82 // It uses the proto file path to determine which rules will 83 // be applied to the request, according to the list of Linter 84 // configs. 85 func (l *Linter) lintFileDescriptor(fd *desc.FileDescriptor) (Response, error) { 86 resp := Response{ 87 FilePath: fd.GetName(), 88 Problems: []Problem{}, 89 } 90 var errMessages []string 91 92 for name, rule := range l.rules { 93 // Run the linter rule against this file, and throw away any problems 94 // which should have been disabled. 95 if l.configs.IsRuleEnabled(string(name), fd.GetName()) { 96 if problems, err := l.runAndRecoverFromPanics(rule, fd); err == nil { 97 for _, p := range problems { 98 if p.Descriptor == nil { 99 errMessages = append(errMessages, fmt.Sprintf("rule %q missing required Descriptor in returned Problem", rule.GetName())) 100 continue 101 } 102 if ruleIsEnabled(rule, p.Descriptor, p.Location, aliasMap, l.ignoreCommentDisables) { 103 p.RuleID = rule.GetName() 104 resp.Problems = append(resp.Problems, p) 105 } 106 } 107 } else { 108 errMessages = append(errMessages, err.Error()) 109 } 110 } 111 } 112 113 var err error 114 if len(errMessages) != 0 { 115 err = errors.New(strings.Join(errMessages, "; ")) 116 } 117 118 return resp, err 119 } 120 121 func (l *Linter) runAndRecoverFromPanics(rule ProtoRule, fd *desc.FileDescriptor) (probs []Problem, err error) { 122 defer func() { 123 if r := recover(); r != nil { 124 if l.debug { 125 debug.PrintStack() 126 } 127 if rerr, ok := r.(error); ok { 128 err = rerr 129 } else { 130 err = fmt.Errorf("panic occurred during rule execution: %v", r) 131 } 132 } 133 }() 134 135 return rule.Lint(fd), nil 136 }