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  }