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