Andrea Campanella | 3614a92 | 2021-02-25 12:40:42 +0100 | [diff] [blame^] | 1 | // Copyright 2017 The Go Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
khenaidoo | ab1f7bd | 2019-11-14 14:00:27 -0500 | [diff] [blame] | 4 | |
Andrea Campanella | 3614a92 | 2021-02-25 12:40:42 +0100 | [diff] [blame^] | 5 | // Package remap handles tracking the locations of Go tokens in a source text |
| 6 | // across a rewrite by the Go formatter. |
khenaidoo | ab1f7bd | 2019-11-14 14:00:27 -0500 | [diff] [blame] | 7 | package remap |
| 8 | |
| 9 | import ( |
| 10 | "fmt" |
| 11 | "go/scanner" |
| 12 | "go/token" |
| 13 | ) |
| 14 | |
| 15 | // A Location represents a span of byte offsets in the source text. |
| 16 | type Location struct { |
| 17 | Pos, End int // End is exclusive |
| 18 | } |
| 19 | |
| 20 | // A Map represents a mapping between token locations in an input source text |
| 21 | // and locations in the correspnding output text. |
| 22 | type Map map[Location]Location |
| 23 | |
| 24 | // Find reports whether the specified span is recorded by m, and if so returns |
| 25 | // the new location it was mapped to. If the input span was not found, the |
| 26 | // returned location is the same as the input. |
| 27 | func (m Map) Find(pos, end int) (Location, bool) { |
| 28 | key := Location{ |
| 29 | Pos: pos, |
| 30 | End: end, |
| 31 | } |
| 32 | if loc, ok := m[key]; ok { |
| 33 | return loc, true |
| 34 | } |
| 35 | return key, false |
| 36 | } |
| 37 | |
| 38 | func (m Map) add(opos, oend, npos, nend int) { |
| 39 | m[Location{Pos: opos, End: oend}] = Location{Pos: npos, End: nend} |
| 40 | } |
| 41 | |
| 42 | // Compute constructs a location mapping from input to output. An error is |
| 43 | // reported if any of the tokens of output cannot be mapped. |
| 44 | func Compute(input, output []byte) (Map, error) { |
| 45 | itok := tokenize(input) |
| 46 | otok := tokenize(output) |
| 47 | if len(itok) != len(otok) { |
| 48 | return nil, fmt.Errorf("wrong number of tokens, %d ≠ %d", len(itok), len(otok)) |
| 49 | } |
| 50 | m := make(Map) |
| 51 | for i, ti := range itok { |
| 52 | to := otok[i] |
| 53 | if ti.Token != to.Token { |
| 54 | return nil, fmt.Errorf("token %d type mismatch: %s ≠ %s", i+1, ti, to) |
| 55 | } |
| 56 | m.add(ti.pos, ti.end, to.pos, to.end) |
| 57 | } |
| 58 | return m, nil |
| 59 | } |
| 60 | |
| 61 | // tokinfo records the span and type of a source token. |
| 62 | type tokinfo struct { |
| 63 | pos, end int |
| 64 | token.Token |
| 65 | } |
| 66 | |
| 67 | func tokenize(src []byte) []tokinfo { |
| 68 | fs := token.NewFileSet() |
| 69 | var s scanner.Scanner |
| 70 | s.Init(fs.AddFile("src", fs.Base(), len(src)), src, nil, scanner.ScanComments) |
| 71 | var info []tokinfo |
| 72 | for { |
| 73 | pos, next, lit := s.Scan() |
| 74 | switch next { |
| 75 | case token.SEMICOLON: |
| 76 | continue |
| 77 | } |
| 78 | info = append(info, tokinfo{ |
| 79 | pos: int(pos - 1), |
| 80 | end: int(pos + token.Pos(len(lit)) - 1), |
| 81 | Token: next, |
| 82 | }) |
| 83 | if next == token.EOF { |
| 84 | break |
| 85 | } |
| 86 | } |
| 87 | return info |
| 88 | } |