github.com/googleapis/api-linter@v1.65.2/rules/aip0191/csharp_namespace.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 aip0191 16 17 import ( 18 "fmt" 19 "regexp" 20 "strings" 21 22 "github.com/googleapis/api-linter/lint" 23 "github.com/googleapis/api-linter/locations" 24 "github.com/jhump/protoreflect/desc" 25 "github.com/stoewer/go-strcase" 26 "golang.org/x/text/cases" 27 "golang.org/x/text/language" 28 ) 29 30 var csharpNamespace = &lint.FileRule{ 31 Name: lint.NewRuleName(191, "csharp-namespace"), 32 OnlyIf: func(f *desc.FileDescriptor) bool { 33 fops := f.GetFileOptions() 34 return fops != nil && fops.GetCsharpNamespace() != "" 35 }, 36 LintFile: func(f *desc.FileDescriptor) []lint.Problem { 37 ns := f.GetFileOptions().GetCsharpNamespace() 38 delim := "." 39 40 // Check for invalid characters. 41 if !csharpValidChars.MatchString(ns) { 42 return []lint.Problem{{ 43 Message: "Invalid characters: C# namespaces only allow [A-Za-z0-9.].", 44 Descriptor: f, 45 Location: locations.FileCsharpNamespace(f), 46 }} 47 } 48 49 // Check that upper camel case is used. 50 upperCamel := []string{} 51 for _, segment := range strings.Split(ns, delim) { 52 wantSegment := csharpVersionRegexp.ReplaceAllStringFunc( 53 strcase.UpperCamelCase(segment), 54 func(s string) string { 55 point := csharpVersionRegexp.FindStringSubmatch(s)[1] 56 if point != "" { 57 s = strings.ReplaceAll(s, point, strings.ToUpper(point)) 58 } 59 stability := csharpVersionRegexp.FindStringSubmatch(s)[2] 60 title := cases.Title(language.AmericanEnglish) 61 return strings.ReplaceAll(s, stability, title.String(stability)) 62 }, 63 ) 64 upperCamel = append(upperCamel, wantSegment) 65 } 66 if want := strings.Join(upperCamel, delim); ns != want { 67 return []lint.Problem{{ 68 Message: "C# namespaces use UpperCamelCase.", 69 Suggestion: fmt.Sprintf("option csharp_namespace = %q;", want), 70 Descriptor: f, 71 Location: locations.FileCsharpNamespace(f), 72 }} 73 } 74 75 for _, s := range f.GetServices() { 76 n := s.GetName() 77 if !packagingServiceNameEquals(n, ns, delim) { 78 msg := fmt.Sprintf("Case of C# namespace and service name %q must match.", n) 79 return []lint.Problem{{ 80 Message: msg, 81 Descriptor: f, 82 Location: locations.FileCsharpNamespace(f), 83 }} 84 } 85 } 86 87 return nil 88 }, 89 } 90 91 var ( 92 csharpValidChars = regexp.MustCompile("^[A-Za-z0-9.]+$") 93 csharpVersionRegexp = regexp.MustCompile("[0-9]+(p[0-9]+)?(alpha|beta|main)") 94 )