馃彴 Self-documenting Identifier Name Analyser for Go
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 208 lines 4.0 kB view raw
1package analyze 2 3import ( 4 "github.com/Fuwn/kivia/internal/collect" 5 "strings" 6 "unicode" 7 "unicode/utf8" 8) 9 10type Options struct { 11 MinEvaluationLength int 12} 13 14type Result struct { 15 Violations []Violation `json:"violations"` 16} 17 18type Violation struct { 19 Identifier collect.Identifier `json:"identifier"` 20 Reason string `json:"reason"` 21} 22 23func Run(identifiers []collect.Identifier, options Options) (Result, error) { 24 minimumEvaluationLength := options.MinEvaluationLength 25 26 if minimumEvaluationLength <= 0 { 27 minimumEvaluationLength = 1 28 } 29 30 resources, err := getResources() 31 32 if err != nil { 33 return Result{}, err 34 } 35 36 violations := make([]Violation, 0) 37 38 for _, identifier := range identifiers { 39 if utf8.RuneCountInString(strings.TrimSpace(identifier.Name)) < minimumEvaluationLength { 40 continue 41 } 42 43 evaluation := evaluateIdentifier(identifier, resources, minimumEvaluationLength) 44 45 if !evaluation.isViolation { 46 continue 47 } 48 49 violation := Violation{ 50 Identifier: identifier, 51 Reason: evaluation.reason, 52 } 53 violations = append(violations, violation) 54 } 55 56 return Result{Violations: violations}, nil 57} 58 59type evaluationResult struct { 60 isViolation bool 61 reason string 62} 63 64func evaluateIdentifier(identifier collect.Identifier, resources resources, minimumTokenLength int) evaluationResult { 65 name := strings.TrimSpace(identifier.Name) 66 67 if name == "" { 68 return evaluationResult{} 69 } 70 71 tokens := tokenize(name) 72 73 if len(tokens) == 0 { 74 return evaluationResult{} 75 } 76 77 for _, token := range tokens { 78 if utf8.RuneCountInString(token) < minimumTokenLength { 79 continue 80 } 81 82 if !isAlphabeticToken(token) { 83 continue 84 } 85 86 if resources.dictionary.IsWord(token) { 87 continue 88 } 89 90 if isUpperCaseToken(name, token) { 91 continue 92 } 93 94 if isDisallowedAbbreviation(token, resources) { 95 return evaluationResult{isViolation: true, reason: "Contains abbreviation: " + token + "."} 96 } 97 98 return evaluationResult{isViolation: true, reason: "Term not found in dictionary: " + token + "."} 99 } 100 101 return evaluationResult{} 102} 103 104func isUpperCaseToken(identifierName string, token string) bool { 105 tokenLength := utf8.RuneCountInString(token) 106 107 if tokenLength < 2 || tokenLength > 8 { 108 return false 109 } 110 111 return strings.Contains(identifierName, strings.ToUpper(token)) 112} 113 114func tokenize(name string) []string { 115 name = strings.TrimSpace(name) 116 117 if name == "" { 118 return nil 119 } 120 121 parts := strings.FieldsFunc(name, func(r rune) bool { 122 return r == '_' || r == '-' || r == ' ' 123 }) 124 125 if len(parts) == 0 { 126 return nil 127 } 128 129 result := make([]string, 0, len(parts)*2) 130 131 for _, part := range parts { 132 if part == "" { 133 continue 134 } 135 136 result = append(result, splitCamel(part)...) 137 } 138 139 return result 140} 141 142func splitCamel(input string) []string { 143 if input == "" { 144 return nil 145 } 146 147 runes := []rune(input) 148 149 if len(runes) == 0 { 150 return nil 151 } 152 153 tokens := make([]string, 0, 2) 154 start := 0 155 156 for index := 1; index < len(runes); index++ { 157 current := runes[index] 158 previous := runes[index-1] 159 next := rune(0) 160 161 if index+1 < len(runes) { 162 next = runes[index+1] 163 } 164 165 isBoundary := false 166 167 if unicode.IsLower(previous) && unicode.IsUpper(current) { 168 isBoundary = true 169 } 170 171 if unicode.IsDigit(previous) != unicode.IsDigit(current) { 172 isBoundary = true 173 } 174 175 if unicode.IsUpper(previous) && unicode.IsUpper(current) && next != 0 && unicode.IsLower(next) { 176 isBoundary = true 177 } 178 179 if isBoundary { 180 tokens = append(tokens, strings.ToLower(string(runes[start:index]))) 181 start = index 182 } 183 } 184 185 tokens = append(tokens, strings.ToLower(string(runes[start:]))) 186 187 return tokens 188} 189 190func isDisallowedAbbreviation(token string, resources resources) bool { 191 _, hasExpansion := resources.dictionary.AbbreviationExpansion(token) 192 193 return hasExpansion 194} 195 196func isAlphabeticToken(token string) bool { 197 if token == "" { 198 return false 199 } 200 201 for _, character := range token { 202 if !unicode.IsLetter(character) { 203 return false 204 } 205 } 206 207 return true 208}