| // Copyright 2017 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package remap handles tracking the locations of Go tokens in a source text |
| // across a rewrite by the Go formatter. |
| package remap |
| |
| import ( |
| "fmt" |
| "go/scanner" |
| "go/token" |
| ) |
| |
| // A Location represents a span of byte offsets in the source text. |
| type Location struct { |
| Pos, End int // End is exclusive |
| } |
| |
| // A Map represents a mapping between token locations in an input source text |
| // and locations in the correspnding output text. |
| type Map map[Location]Location |
| |
| // Find reports whether the specified span is recorded by m, and if so returns |
| // the new location it was mapped to. If the input span was not found, the |
| // returned location is the same as the input. |
| func (m Map) Find(pos, end int) (Location, bool) { |
| key := Location{ |
| Pos: pos, |
| End: end, |
| } |
| if loc, ok := m[key]; ok { |
| return loc, true |
| } |
| return key, false |
| } |
| |
| func (m Map) add(opos, oend, npos, nend int) { |
| m[Location{Pos: opos, End: oend}] = Location{Pos: npos, End: nend} |
| } |
| |
| // Compute constructs a location mapping from input to output. An error is |
| // reported if any of the tokens of output cannot be mapped. |
| func Compute(input, output []byte) (Map, error) { |
| itok := tokenize(input) |
| otok := tokenize(output) |
| if len(itok) != len(otok) { |
| return nil, fmt.Errorf("wrong number of tokens, %d ≠ %d", len(itok), len(otok)) |
| } |
| m := make(Map) |
| for i, ti := range itok { |
| to := otok[i] |
| if ti.Token != to.Token { |
| return nil, fmt.Errorf("token %d type mismatch: %s ≠ %s", i+1, ti, to) |
| } |
| m.add(ti.pos, ti.end, to.pos, to.end) |
| } |
| return m, nil |
| } |
| |
| // tokinfo records the span and type of a source token. |
| type tokinfo struct { |
| pos, end int |
| token.Token |
| } |
| |
| func tokenize(src []byte) []tokinfo { |
| fs := token.NewFileSet() |
| var s scanner.Scanner |
| s.Init(fs.AddFile("src", fs.Base(), len(src)), src, nil, scanner.ScanComments) |
| var info []tokinfo |
| for { |
| pos, next, lit := s.Scan() |
| switch next { |
| case token.SEMICOLON: |
| continue |
| } |
| info = append(info, tokinfo{ |
| pos: int(pos - 1), |
| end: int(pos + token.Pos(len(lit)) - 1), |
| Token: next, |
| }) |
| if next == token.EOF { |
| break |
| } |
| } |
| return info |
| } |