github.com/bakjos/protoreflect@v1.9.2/desc/protoparse/ast/file.go (about) 1 package ast 2 3 import "fmt" 4 5 // FileDeclNode is a placeholder interface for AST nodes that represent files. 6 // This allows NoSourceNode to be used in place of *FileNode for some usages. 7 type FileDeclNode interface { 8 Node 9 GetSyntax() Node 10 } 11 12 var _ FileDeclNode = (*FileNode)(nil) 13 var _ FileDeclNode = NoSourceNode{} 14 15 // FileNode is the root of the AST hierarchy. It represents an entire 16 // protobuf source file. 17 type FileNode struct { 18 compositeNode 19 Syntax *SyntaxNode // nil if file has no syntax declaration 20 Decls []FileElement 21 22 // Any comments that follow the last token in the file. 23 FinalComments []Comment 24 // Any whitespace at the end of the file (after the last token or 25 // last comment in the file). 26 FinalWhitespace string 27 } 28 29 // NewFileElement creates a new *FileNode. The syntax parameter is optional. If it 30 // is absent, it means the file had no syntax declaration. 31 // 32 // This function panics if the concrete type of any element of decls is not 33 // from this package. 34 func NewFileNode(syntax *SyntaxNode, decls []FileElement) *FileNode { 35 numChildren := len(decls) 36 if syntax != nil { 37 numChildren++ 38 } 39 children := make([]Node, 0, numChildren) 40 if syntax != nil { 41 children = append(children, syntax) 42 } 43 for _, decl := range decls { 44 children = append(children, decl) 45 } 46 47 for _, decl := range decls { 48 switch decl := decl.(type) { 49 case *PackageNode, *ImportNode, *OptionNode, *MessageNode, 50 *EnumNode, *ExtendNode, *ServiceNode, *EmptyDeclNode: 51 default: 52 panic(fmt.Sprintf("invalid FileElement type: %T", decl)) 53 } 54 } 55 56 return &FileNode{ 57 compositeNode: compositeNode{ 58 children: children, 59 }, 60 Syntax: syntax, 61 Decls: decls, 62 } 63 } 64 65 func NewEmptyFileNode(filename string) *FileNode { 66 return &FileNode{ 67 compositeNode: compositeNode{ 68 children: []Node{NewNoSourceNode(filename)}, 69 }, 70 } 71 } 72 73 func (f *FileNode) GetSyntax() Node { 74 return f.Syntax 75 } 76 77 // FileElement is an interface implemented by all AST nodes that are 78 // allowed as top-level declarations in the file. 79 type FileElement interface { 80 Node 81 fileElement() 82 } 83 84 var _ FileElement = (*ImportNode)(nil) 85 var _ FileElement = (*PackageNode)(nil) 86 var _ FileElement = (*OptionNode)(nil) 87 var _ FileElement = (*MessageNode)(nil) 88 var _ FileElement = (*EnumNode)(nil) 89 var _ FileElement = (*ExtendNode)(nil) 90 var _ FileElement = (*ServiceNode)(nil) 91 var _ FileElement = (*EmptyDeclNode)(nil) 92 93 // SyntaxNode represents a syntax declaration, which if present must be 94 // the first non-comment content. Example: 95 // 96 // syntax = "proto2"; 97 // 98 // Files that don't have a syntax node are assumed to use proto2 syntax. 99 type SyntaxNode struct { 100 compositeNode 101 Keyword *KeywordNode 102 Equals *RuneNode 103 Syntax StringValueNode 104 Semicolon *RuneNode 105 } 106 107 // NewSyntaxNode creates a new *SyntaxNode. All four arguments must be non-nil: 108 // - keyword: The token corresponding to the "syntax" keyword. 109 // - equals: The token corresponding to the "=" rune. 110 // - syntax: The actual syntax value, e.g. "proto2" or "proto3". 111 // - semicolon: The token corresponding to the ";" rune that ends the declaration. 112 func NewSyntaxNode(keyword *KeywordNode, equals *RuneNode, syntax StringValueNode, semicolon *RuneNode) *SyntaxNode { 113 if keyword == nil { 114 panic("keyword is nil") 115 } 116 if equals == nil { 117 panic("equals is nil") 118 } 119 if syntax == nil { 120 panic("syntax is nil") 121 } 122 if semicolon == nil { 123 panic("semicolon is nil") 124 } 125 children := []Node{keyword, equals, syntax, semicolon} 126 return &SyntaxNode{ 127 compositeNode: compositeNode{ 128 children: children, 129 }, 130 Keyword: keyword, 131 Equals: equals, 132 Syntax: syntax, 133 Semicolon: semicolon, 134 } 135 } 136 137 // ImportNode represents an import statement. Example: 138 // 139 // import "google/protobuf/empty.proto"; 140 type ImportNode struct { 141 compositeNode 142 Keyword *KeywordNode 143 // Optional; if present indicates this is a public import 144 Public *KeywordNode 145 // Optional; if present indicates this is a weak import 146 Weak *KeywordNode 147 Name StringValueNode 148 Semicolon *RuneNode 149 } 150 151 // NewImportNode creates a new *ImportNode. The public and weak arguments are optional 152 // and only one or the other (or neither) may be specified, not both. When public is 153 // non-nil, it indicates the "public" keyword in the import statement and means this is 154 // a public import. When weak is non-nil, it indicates the "weak" keyword in the import 155 // statement and means this is a weak import. When both are nil, this is a normal import. 156 // The other arguments must be non-nil: 157 // - keyword: The token corresponding to the "import" keyword. 158 // - public: The token corresponding to the optional "public" keyword. 159 // - weak: The token corresponding to the optional "weak" keyword. 160 // - name: The actual imported file name. 161 // - semicolon: The token corresponding to the ";" rune that ends the declaration. 162 func NewImportNode(keyword *KeywordNode, public *KeywordNode, weak *KeywordNode, name StringValueNode, semicolon *RuneNode) *ImportNode { 163 if keyword == nil { 164 panic("keyword is nil") 165 } 166 if name == nil { 167 panic("name is nil") 168 } 169 if semicolon == nil { 170 panic("semicolon is nil") 171 } 172 numChildren := 3 173 if public != nil || weak != nil { 174 numChildren++ 175 } 176 children := make([]Node, 0, numChildren) 177 children = append(children, keyword) 178 if public != nil { 179 children = append(children, public) 180 } else if weak != nil { 181 children = append(children, weak) 182 } 183 children = append(children, name, semicolon) 184 185 return &ImportNode{ 186 compositeNode: compositeNode{ 187 children: children, 188 }, 189 Keyword: keyword, 190 Public: public, 191 Weak: weak, 192 Name: name, 193 Semicolon: semicolon, 194 } 195 } 196 197 func (*ImportNode) fileElement() {} 198 199 // PackageNode represents a package declaration. Example: 200 // 201 // package foobar.com; 202 type PackageNode struct { 203 compositeNode 204 Keyword *KeywordNode 205 Name IdentValueNode 206 Semicolon *RuneNode 207 } 208 209 func (*PackageNode) fileElement() {} 210 211 // NewPackageNode creates a new *PackageNode. All three arguments must be non-nil: 212 // - keyword: The token corresponding to the "package" keyword. 213 // - name: The package name declared for the file. 214 // - semicolon: The token corresponding to the ";" rune that ends the declaration. 215 func NewPackageNode(keyword *KeywordNode, name IdentValueNode, semicolon *RuneNode) *PackageNode { 216 if keyword == nil { 217 panic("keyword is nil") 218 } 219 if name == nil { 220 panic("name is nil") 221 } 222 if semicolon == nil { 223 panic("semicolon is nil") 224 } 225 children := []Node{keyword, name, semicolon} 226 return &PackageNode{ 227 compositeNode: compositeNode{ 228 children: children, 229 }, 230 Keyword: keyword, 231 Name: name, 232 Semicolon: semicolon, 233 } 234 }