github.com/terraform-linters/tflint-plugin-sdk@v0.22.0/internal/token_scanner.go (about) 1 package internal 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl/v2" 7 "github.com/hashicorp/hcl/v2/hclsyntax" 8 ) 9 10 // tokenScanner is a token-based scanner for HCL. 11 // The scanner scans tokens one by one and returns its position and token details. 12 type tokenScanner struct { 13 tokens hclsyntax.Tokens 14 pos hcl.Pos 15 index int 16 filename string 17 } 18 19 func newTokenScanner(source []byte, filename string) (*tokenScanner, hcl.Diagnostics) { 20 tokens, diags := hclsyntax.LexConfig(source, filename, hcl.InitialPos) 21 if diags.HasErrors() { 22 return nil, diags 23 } 24 return &tokenScanner{ 25 tokens: tokens, 26 pos: hcl.InitialPos, 27 index: 0, 28 filename: filename, 29 }, nil 30 } 31 32 type tokenPos int 33 34 const ( 35 tokenStart tokenPos = iota 36 tokenEnd 37 ) 38 39 // seek moves the currnet position to the given position. 40 // The destination token is determined by the given match condtion. 41 // 42 // match tokenStart: 43 // 44 // | <- pos 45 // foo=1 46 // |-| token is "=" 47 // 48 // match tokenEnd: 49 // 50 // | <- pos 51 // foo=1 52 // |-| token is "foo" 53 func (s *tokenScanner) seek(to hcl.Pos, match tokenPos) error { 54 switch { 55 case s.tokenPos(match).Byte == to.Byte: 56 return nil 57 case to.Byte < s.tokenPos(match).Byte: 58 for s.scanBackward() { 59 if to.Byte == s.tokenPos(match).Byte { 60 s.pos = to 61 return nil 62 } 63 } 64 case s.tokenPos(match).Byte < to.Byte: 65 for s.scan() { 66 if s.tokenPos(match).Byte == to.Byte { 67 s.pos = to 68 return nil 69 } 70 } 71 default: 72 panic("unreachable") 73 } 74 75 return fmt.Errorf("no token found at %s:%d,%d", s.filename, to.Line, to.Column) 76 } 77 78 func (s *tokenScanner) seekByIndex(idx int, pos tokenPos) error { 79 if idx < 0 || len(s.tokens) <= idx { 80 return fmt.Errorf("index out of range: %d", idx) 81 } 82 s.index = idx 83 s.pos = s.tokenPos(pos) 84 return nil 85 } 86 87 // seekTokenStart moves the current position to the start of the current token. 88 func (s *tokenScanner) seekTokenStart() { 89 s.pos = s.token().Range.Start 90 } 91 92 func (s *tokenScanner) seekTokenEnd() { 93 s.pos = s.token().Range.End 94 } 95 96 // scan moves the current position to the next token. 97 // position is always set to the end of the token. 98 func (s *tokenScanner) scan() bool { 99 i := s.index + 1 100 if i >= len(s.tokens) { 101 s.seekTokenEnd() 102 return false 103 } 104 s.index = i 105 s.seekTokenEnd() 106 return true 107 } 108 109 func (s *tokenScanner) scanIf(tokenType hclsyntax.TokenType) bool { 110 i := s.index + 1 111 if i >= len(s.tokens) { 112 return false 113 } 114 if s.tokens[i].Type != tokenType { 115 return false 116 } 117 s.scan() 118 return true 119 } 120 121 // scanBackward moves the current position to the previous token. 122 // position is always set to the start of the token. 123 func (s *tokenScanner) scanBackward() bool { 124 i := s.index - 1 125 if i < 0 { 126 s.seekTokenStart() 127 return false 128 } 129 s.index = i 130 s.seekTokenStart() 131 return true 132 } 133 134 func (s *tokenScanner) scanBackwardIf(tokenType hclsyntax.TokenType) bool { 135 i := s.index - 1 136 if i < 0 { 137 return false 138 } 139 if s.tokens[i].Type != tokenType { 140 return false 141 } 142 s.scanBackward() 143 return true 144 } 145 146 func (s *tokenScanner) token() hclsyntax.Token { 147 return s.tokens[s.index] 148 } 149 150 func (s *tokenScanner) tokenPos(pos tokenPos) hcl.Pos { 151 switch pos { 152 case tokenStart: 153 return s.token().Range.Start 154 case tokenEnd: 155 return s.token().Range.End 156 default: 157 panic("unreachable") 158 } 159 }