-
-
Notifications
You must be signed in to change notification settings - Fork 6
/
annotation.go
127 lines (117 loc) · 3.1 KB
/
annotation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package tl
import (
"errors"
"fmt"
"strings"
"unicode"
)
// Common values for Annotation.Name.
const (
// AnnotationDescription is description of definition or class.
AnnotationDescription = "description"
// AnnotationClass is annotation for class.
AnnotationClass = "class"
// AnnotationParamDescription is annotation for parameter named "description".
AnnotationParamDescription = "param_description"
)
// Annotation represents an annotation comment, like //@name value.
type Annotation struct {
// Name of annotation.
//
// Can be:
// * "description" if Value is class or definition description
// * "class" if Value is class name, like //@class Foo @description Foo class
// * "param_description" if Value is description for "description" parameter
// Otherwise, it is description of Name parameter.
Name string `json:"name"`
// Value of annotation. Can be description or class name if Name is "class".
Value string `json:"value"`
}
func (a Annotation) String() string {
var b strings.Builder
b.WriteString("//")
b.WriteRune('@')
b.WriteString(a.Name)
b.WriteRune(' ')
b.WriteString(a.Value)
return b.String()
}
// singleLineAnnotations encodes multiple annotations on single line.
//
// NB: newlines are not quoted if present.
func singleLineAnnotations(a []Annotation) string {
var b strings.Builder
for i, ann := range a {
str := ann.String()
if i > 0 {
str = strings.Replace(str, "//", " ", 1)
}
b.WriteString(str)
}
return b.String()
}
// parseAnnotation parses one or multiple annotations on the line.
func parseAnnotation(line string) ([]Annotation, error) {
if !strings.HasPrefix(line, "//") {
return nil, errors.New("annotation should be comment")
}
line = strings.TrimSpace(strings.TrimLeft(line, "/"))
if line == "" {
return nil, errors.New("blank comment")
}
if !strings.HasPrefix(line, "@") {
return nil, errors.New("invalid annotation start")
}
// Probably this can be simplified.
var annotations []Annotation
for line != "" {
nameEnd := strings.Index(line, " ")
if nameEnd <= 1 {
return nil, errors.New("failed to find name end")
}
name := line[1:nameEnd]
if !isValidName(name) {
return nil, errors.New("invalid annotation name")
}
line = line[nameEnd:]
nextAnnotationPos := strings.Index(line, "@")
if nextAnnotationPos < 0 {
// No more annotations.
value := strings.TrimSpace(line)
if !isValidAnnotationValue(value) {
return nil, fmt.Errorf("invalid annotation value %q", value)
}
annotations = append(annotations, Annotation{
Name: name,
Value: value,
})
break
}
// There will be more.
value := strings.TrimSpace(line[:nextAnnotationPos])
if !isValidAnnotationValue(value) {
return nil, fmt.Errorf("invalid annotation value %q", value)
}
annotations = append(annotations, Annotation{
Name: name,
Value: value,
})
line = line[nextAnnotationPos:]
}
return annotations, nil
}
func isValidAnnotationValue(v string) bool {
if v == "" {
return false
}
for _, s := range v {
if unicode.IsControl(s) {
return false
}
if unicode.IsPrint(s) {
continue
}
return false
}
return true
}